The Old County The Old County - 2 months ago 10
Javascript Question

d3.js an Inverse bubble chart

enter image description here

I am interested in creating something like this. Usually we see people drawing a bubble - I am keen to draw the space to represent the bubble. I would perhaps place this mask/chart in a shared component -- that is conjoined only by a background image -- so maybe embed this in a bootstrap part like col-md-8.

I've added the the subtraction mask -- and some label/pointer stuff - but its not rendering.

http://jsfiddle.net/NYEaX/1525/

var data = [{
"label": "My Property Value over 3 yrs.",
"value": "148",
"direction": "up"
}]


so the json for this may be something like

$(document).ready(function() {

function maskMaker(el) {

var backcolor = $(el).data("color");
var backopacity = $(el).data("opacity");

// Set the main elements for the series chart
var svgroot = d3.select($(el)[0]).append("svg");
var mask = svgroot
.append("defs")
.append("mask")
.attr("id", "myMask");

mask.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "1200px")
.attr("height", 500)
.style("fill", "white")
.style("opacity", backopacity);

mask.append("circle")
.attr("cx", 550)
.attr("cy", 250)
.attr("r", 150);



var data = [{
label: "text",
x: 222,
y: 222
}]

//__labels
var labels = mask.append("g")
.attr("class", "labels")

//__ enter
var labels = labels.selectAll("text")
.data(data);

labels.enter()
.append("text")
.attr("text-anchor", "middle")

//__ update
labels
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.text(function(d) {
return d.label;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)

labels
.transition()
.duration(300)

//__ exit
labels.exit().remove();
//__labels
//__labels


//__pointers
var pointers = mask.append("g")
.attr("class", "pointers")

pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);

var pointers = pointers.selectAll("path.pointer")
.data(data);

//__ enter
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");

//__ update
pointers
.attr("d", function(d) {
if (d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)

pointers
.transition()
.duration(300)

//__ exit
pointers.exit().remove();
//__pointers



var svg = svgroot
.attr("class", "series")
.attr("width", "1200px")
.attr("height", "500px")
.append("g")
.attr("transform", "translate(0,0)")

var rect = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "750px")
.attr("height", 500)
.attr("mask", "url(#myMask)")
.style("fill", backcolor);

}

//var el = $(".mask"); //selector

$('[data-role="mask"]').each(function(index) {
console.log("test")
maskMaker(this);
});
});





latest answer

http://jsfiddle.net/NYEaX/1535/

Answer

You need to do several things:

  1. In SVG DOM have the label and the pointer after the rectangle with the mask (or the rectangle itself before them). This will make them topmost. There is no z-index in SVG.
  2. Add a declaration of the marker to the same 'defs' node at the beginning of SVG
  3. Set pointer target values d.cx and d.cy (in the example below I set them to ordinary values)
  4. Implement enter-update-exit pattern differently. In your example code with comments '__ update' will only be executed for existing elements in the selection, whereas it is empty on first run. See https://bl.ocks.org/mbostock/3808218 on how to merge operations on just added elements and already existing ones.

    labels.enter()
      .append("text")
      .attr("text-anchor", "middle")
    
    //__ update
    //labels
    
      .attr("x", function(d) {
        return d.x;
      })
      ... 
    

A working example here: http://jsfiddle.net/NYEaX/1528/

Comments