Search code examples
javascriptd3.jsbullet

Bullet Chart Ticks in D3 - Boothead Example


I'm trying to have the chart tickets in a D3 bullet chart follow the data itself, as per the 2nd example here: Bullet chart ticks & labels in D3.js

The issue is that the source of this (http://boothead.github.io/d3/ex/bullet.html) no longer exists on the internet, the only thing out there is the gif in this post that I've linked.

enter image description here Does anyone have the original copy of this project or have any advice?

I'm using the first example by mbostock and trying to replicate the bottom one.

Many thanks


Solution

  • In the original bullet.js from the bostock example https://bl.ocks.org/mbostock/4061961

    Instead of getting the ticks from the scale you get the values from the range, measure and mark

    change around line 109

      // var tickVals = x1.ticks(8);
      var tickVals = rangez.concat(measurez).concat(markerz);
      // Update the tick groups.
      var tick = g.selectAll("g.tick")
          .data(tickVals, function(d) {
            return this.textContent || format(d);
          });
    


    Edit

    There is a problem if you update the data based on a new fetch from the server. some of the ticks end up on the wrong location. If the number of markers,measures,ranges also change they also end up at the wrong location.

    It depends on the selection you supply to the bullet call.

    The confusion is caused by the poor naming of the main program.

      var svg = d3.select("body").selectAll("svg")
          .data(data)
        .enter().append("svg")
          .attr("class", "bullet")
          .attr("width", svgWidth)
          .attr("height", svgHeight)
        .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
          .call(chart);
    

    The name suggests that svg is a selection of svg elements. This is incorrect, it is a selection of g elements.

    The update() function should reflect this

    function updateData() {
        d3.json("mailboxes.json", function (error, data) {
            d3.select("body")
              .selectAll("svg")
              .select('g')
              .data(data)
              .call(chart.duration(500));
        });
    }
    

    If the number of bullet graphs changes on the update there is the problem that they are not created or deleted if needed. So we need to make a function that can be used for initial and update calls.

    function drawCharts() {
        d3.json("mailboxes.json", function (error, data) {
            var svg = d3.select("body").selectAll("svg").data(data);
            svg.exit().remove();
            svg.enter().append("svg")
                .attr("class", "bullet")
                .attr("width", svgWidth)
                .attr("height", svgHeight)
              .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
            d3.select("body")
              .selectAll("svg")
              .select('g')
              .data(data)
              .call(chart.duration(500));
        });
    }
    

    A better change in bullet.js [109] would be:

      // var tickVals = x1.ticks(8);
      var tickVals = [];
      rangez.concat(measurez).concat(markerz).forEach(e => {
          if (tickVals.indexOf(e) == -1) tickVals.push(e);
      });
      // Update the tick groups.
      var tick = g.selectAll("g.tick").data(tickVals);
    

    That is do not use the value of the tick to match, in case we have multiple values in the ticks, and remove the duplicates.

    We also need to change the update of the ticks, about 30 lines down

      tickUpdate.select("text")
          .attr("y", height * 7 / 6);
    

    to

      tickUpdate.select("text")
          .text(format)
          .attr("y", height * 7 / 6);