How to fix "svg.append(...).selectAll(...).data(...).join(...).attr is not a function" with d3v6

Viewed 28

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?

0 Answers
Related