I have a d3 bubble chart script which works fine embedded in a php view file. Unfortunately, the code in the site loads the php view files via jQuery files. So I am trying to embed the d3 script in the jQuery file. It works to some degree but I keep getting the "Uncaught TypeError: svg.append(...).data(...).selectAll(...).join(...).attr is not a function". After doing some research, I am suspecting the error may be a jQuery ".attr() is not a function" error rather than a d3 error.
This is the function in the jQuery file, with the d3 code:
this.Update = function (data) {
if (_.isUndefined(data) || _.isNull(data)) { data = {}; }
console.log('Bubble data: ', data);
// set the dimensions and margins of the graph
const width = 700
const height = 460
// append the svg object to the body of the page
const svg = d3.select("#bubble_viz")
.append("svg")
.attr("width", width)
.attr("height", height)
console.log('SVG: ', svg);
// Color palette for continents?
const color = d3.scale.ordinal()
.domain(["main_conference_room", "reception", "training_room", "it_closet", "kitchen", "alessandros_office", "neils_office"])
//.domain(["Main Conference Room", "Reception", "Training Room", "IT Closet", "Kitchen", "Alessandros Office", "Neils Office"])
//.range(["#98ccd9", "#97db69", "#cc7ead", "#d6b456", "#a17acf", "#d1ce6b", "#72d47a"]);
.range(["#98ccd9", "#79b9c9", "#549eb0", "#6f939b", "#217185", "#cfe3e8", "#044e61"]);
// Size scale for locations
const size = d3.scale.linear()
.domain([0, 100])
.range([15,130]) // circle will be between 7 and 55 px wide
// create a tooltip
const Tooltip = d3.select("#bubble_viz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "#ebeef2")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
const mouseover = function(event, d) {
Tooltip
.style("opacity", 1)
}
const mousemove = function(event, d) {
Tooltip
.html('<b>' + d.zone + '</b>' + "<br>" + d.value + " visitors")
.style("left", (event.x/2-300) + "px")
.style("top", (event.y/2-200) + "px")
}
var mouseleave = function(event, d) {
Tooltip
.style("opacity", 0)
}
// Initialize the circle: all located at the center of the svg area
var node = svg.append("g")
.data(data)
.selectAll("circle")
.join("circle")
.attr("class", "node")
.attr("r", d => size(d.value))
.attr("cx", width)
.attr("cy", height)
//.html(function(d) { return d.key })//.text("Hello!") //function(d) { return d.key + " - " + d.value + " visitors"}
.style("fill", d => color(d.zone))
.style("fill-opacity", 0.8)
.attr("stroke", "black")
.style("stroke-width", 1)
.on("mouseover", mouseover) // What to do when hovered
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
.call(d3.drag() // call specific function when circle is dragged
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
let texts = svg.selectAll(null)
.data(data)
.enter()
.append('text')
.attr("text-anchor", "middle")
.text(d => d.zone)
.attr('color', 'black')
.attr('font-size', 15)
// Features of the forces applied to the nodes:
const simulation = d3.forceSimulation()
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("charge", d3.forceManyBody().strength(.1)) // Nodes are attracted one each other of value is > 0
.force("collide", d3.forceCollide().strength(.2).radius(function(d){ return (size(d.value)+3) }).iterations(1)) // Force that avoids circle overlapping
// Apply these forces to the nodes and update their positions.
// Once the force algorithm is happy with positions ('alpha' value is low enough), simulations will stop.
simulation
.nodes(data)
.on("tick", function(d){
node
.attr("cx", d => d.x)
.attr("cy", d => d.y)
texts
.attr("cx", d => d.x)
.attr("cy", d => d.y)
});
// What happens when a circle is dragged?
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(.03).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(.03);
d.fx = null;
d.fy = null;
}
//if (this.Chart && data.chartData) { this.Chart.Update(data.chartData); }
};
The data that gets passed to the function, looks like this:
[{"zone_id":"alessandros_office","value":73},
{"zone_id":"it_closet","value":70},
{"zone_id":"kitchen","value":67},
{"zone_id":"main_conference_room","value":137},
{"zone_id":"neils_office","value":53},
{"zone_id":"training_room","value":26}]
And this is the code at the top of the jQuery file that includes the d3 library:
define([
'vendor-heatmap',
'heatmap/bubble/bubble',
'jquery',
'https://d3js.org/d3.v6.js'
], function(BubbleClass) {
And finally, this is the exact error:
Uncaught TypeError: svg.append(...).data(...).selectAll(...).join(...).attr is not a function
at HeatMapBubbleClass.Update (bubble.js?_version=22.03.03:143:22)
at Object.success (bubble.js?_version=22.03.03:77:31)
at i (jquery.min.js?_version=22.03.03:2:27983)
at Object.fireWith [as resolveWith] (jquery.min.js?_version=22.03.03:2:28749)
at A (jquery.min.js?_version=22.03.03:4:14203)
at XMLHttpRequest.<anonymous> (jquery.min.js?_version=22.03.03:4:16491)
There are a number of points along the way I had to debug, and did which involve the file reading the d3 library so it is doing that. Am I running into a jQuery .attr() error? Or is there something else? This is a Laravel site. The controller is pulling the data from the database and passing it to the jQuery as part of an AJAX call, all of which is working fine. If this is not a good solution, any suggestions?