Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In d3.js, skip append() for null data

I'm drawing a line graph out of little circle bullets. However, the data has holes in it, which are represented by null's in my array. Naturally, wherever there's no data, there shouldn't be circles. But d3's append() method adds them anyway. How do I avoid this?

Here's a jsFiddle mockup reproducing my problem exactly.

I'm interested in NOT having that series of circles that lie on the X axis of my graph, since those are all nulls.

Relevant code from the jsfiddle link:

svg.selectAll('circle').data(values).enter()
  .append('circle')// <-- I don't want to do this for null's
  .attr('fill', '#c00')
  .attr('r', 3)
  .attr('cx', xi)
  .attr('cy', yFlipped)
like image 376
meetamit Avatar asked May 31 '12 00:05

meetamit


People also ask

What does D3 append do?

append() function is used to append a new element to the HTML tag name as given in the parameters to the end of the element. If the type that is given is a function then it must be evaluated for each element that is in the selection.

What does D3 JavaScript?

What is D3? D3 is a JavaScript library and framework for creating visualizations. D3 creates visualizations by binding the data and graphical elements to the Document Object Model. D3 associates (binding) the data (stuff you want to visualize) with the DOM.


3 Answers

One option is to represent your data differently, so that you aren't dependent on the index to compute the x-coordinate. For example, if you represented each datum as an object (e.g., {x: 0, y: 0.2840042}) then you could compute the x-coordinate as x(d.x) rather than x(i).

Another option would be to set the radius to zero when the value is null, so the circles are hidden: circle.attr("r", function(d) { return d == null ? 0 : 3; }). Or, you could hide the circles: circle.style("display", function(d) { return d == null ? "none" : null; }).

You could also remove the null elements after appending them: circle.filter(function(d) { return d == null; }).remove(). That would work for the initial creation, but I wouldn't recommend it because the index would change if you reselected the elements later.

like image 166
mbostock Avatar answered Oct 01 '22 14:10

mbostock


The simplest option is to filter the nulls out of the data being passed in to .data(…), augmenting the data to maintain the index:

svg.selectAll('circle')
  .data(values
    .map(function(v, idx) { return v == null? null : { idx: idx, value: v })
    .filter(function(v) { return v != null })
  )
  .enter()
    .append('circle')
    .attr('fill', '#c00')
    .attr('r', 3)
    .attr('cx', function(d) { return d.idx * 10 }) // or whatever
    .attr('cy', function(d) { return d.value.y }) // or whatever

Note that you can also follow this pattern individual sub-elements, even if they aren't naturally lists. For example, consider a situation where you want to conditionally add a second circle:

var circles = [
    { color: 'red', cx: 30, cy: 30, subCircleColor: 'blue' },
    { color: 'blue', cx: 60, cy: 60, subCircleColor: 'green' },
    { color: 'green', cx: 90, cy: 90 },
];

// Create a group which will hold the circles, since the result will
// be:
//    <g class="circles">
//      <circle color="{{ color }}" ... />
//      <circle class="sub-circle" color="{{ subCircleColor }}" ... />
//    </g>
var circlesGroups = svg.selectAll("g.circles")
    .data(circles)
    .enter()
        .append("g").attr({"class": "circles"})

// Add the first circle to the group
circlesGroups
    .append("circle").attr({
        "fill": function(d) { return d.color },
        "r": 20,
        "cx": function(d) { return d.cx },
        "cy": function(d) { return d.cy },
    })

// If there is a subCircleColor, add the second circle to the group
circlesGroups.selectAll("circle.sub-circle")
    .data(function(d) {
        if (d.subCircleColor)
            return [d];
        return [];
    })
    .enter()
        .append("circle").attr({
            "class": "sub-circle",
            "fill": function(d) { return d.subCircleColor; },
            "r": 10,
            "cx": function(d) { return d.cx },
            "cy": function(d) { return d.cy },
        })

Fiddle: http://jsfiddle.net/3d6e648k/

like image 42
David Wolever Avatar answered Oct 01 '22 13:10

David Wolever


Try this pattern, which can either delete or hide your circles.

// Step 1: hides all circles which are "null"
d3.selectAll(".yourItem")
.data(data)
.enter()
    .append("circle")
    .attr("visibility", function(d,i){
        if(yourFunction(d) == null) return "hidden";
})

// Step 2: optional, deletes all circles which are "hidden"
d3.selectAll("circle[visibility=hidden]").remove();
like image 1
Monarch Wadia Avatar answered Oct 01 '22 15:10

Monarch Wadia