skip to Main Content

I am trying to add a legend to the d3.js example named
Line chart with zoom in d3.js

1°) I added the script:

// Add legend rectangle
line.append('rect')
          .attr('x', width - 80)
          .attr('y', 90)
          .attr('width', 10)
          .attr('height', 10)
          .style('fill', "steelblue");

// Add legend text 
line.append("text")
          .attr('x', width - 65)
          .attr('y', 100)
          .text('Blue line');

2°) I reduced the x axis in the existing code to separate the legend and the chart:

var x = d3.scaleTime()
      .domain(d3.extent(data, function(d) { return d.date; }))
      .range([ 0, width - 100  ]);

Unfortunately, the legend merges with the chart when I zoom! How to solve this?

After zooming

2

Answers


  1. Chosen as BEST ANSWER

    Many thanks for this good idea, Mark. I applied it to my multi line chart and it works :-)


  2. The example you are following follows a frequently used coding convention in d3 of separating out the "drawing area" of the chart around a series of margins.

    // set the dimensions and margins of the graph
    var margin = { top: 10, right: 100, bottom: 30, left: 60 },
      width = 460 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;
    
    // append the svg object to the body of the page
    var svg = d3
      .select('#my_dataviz')
      .append('svg')
      .attr('width', width + margin.left + margin.right)
      .attr('height', height + margin.top + margin.bottom)
      .append('g')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
    

    Instead of attempting to adjust the x axis scale the way you are doing, I would increase the right margin and move your legend there.

    Here’s an example:

    <!DOCTYPE html>
    
    <html>
      <head>
        <link rel="stylesheet" href="lib/style.css" />
        <script src="lib/script.js"></script>
      </head>
    
      <body>
        <!-- Load d3.js -->
        <script src="https://d3js.org/d3.v4.js"></script>
    
        <!-- Create a div where the graph will take place -->
        <div id="my_dataviz"></div>
    
        <script>
          // set the dimensions and margins of the graph
          var margin = { top: 10, right: 100, bottom: 30, left: 60 },
            width = 460 - margin.left - margin.right,
            height = 400 - margin.top - margin.bottom;
    
          // append the svg object to the body of the page
          var svg = d3
            .select('#my_dataviz')
            .append('svg')
            .attr('width', width + margin.left + margin.right)
            .attr('height', height + margin.top + margin.bottom)
            .append('g')
            .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
    
          //Read the data
          d3.csv(
            'https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv',
    
            // When reading the csv, I must format variables:
            function (d) {
              return { date: d3.timeParse('%Y-%m-%d')(d.date), value: d.value };
            },
    
            // Now I can use this dataset:
            function (data) {
              // Add X axis --> it is a date format
              var x = d3
                .scaleTime()
                .domain(
                  d3.extent(data, function (d) {
                    return d.date;
                  })
                )
                .range([0, width]);
              xAxis = svg
                .append('g')
                .attr('transform', 'translate(0,' + height + ')')
                .call(d3.axisBottom(x));
    
              // Add Y axis
              var y = d3
                .scaleLinear()
                .domain([
                  0,
                  d3.max(data, function (d) {
                    return +d.value;
                  }),
                ])
                .range([height, 0]);
              yAxis = svg.append('g').call(d3.axisLeft(y));
    
              // Add a clipPath: everything out of this area won't be drawn.
              var clip = svg
                .append('defs')
                .append('svg:clipPath')
                .attr('id', 'clip')
                .append('svg:rect')
                .attr('width', width)
                .attr('height', height)
                .attr('x', 0)
                .attr('y', 0);
    
              // Add brushing
              var brush = d3
                .brushX() // Add the brush feature using the d3.brush function
                .extent([
                  [0, 0],
                  [width, height],
                ]) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area
                .on('end', updateChart); // Each time the brush selection changes, trigger the 'updateChart' function
    
              // Create the line variable: where both the line and the brush take place
              var line = svg.append('g').attr('clip-path', 'url(#clip)');
    
              // Add the line
              line
                .append('path')
                .datum(data)
                .attr('class', 'line') // I add the class line to be able to modify this line later on.
                .attr('fill', 'none')
                .attr('stroke', 'steelblue')
                .attr('stroke-width', 1.5)
                .attr(
                  'd',
                  d3
                    .line()
                    .x(function (d) {
                      return x(d.date);
                    })
                    .y(function (d) {
                      return y(d.value);
                    })
                );
    
              // Add the brushing
              line.append('g').attr('class', 'brush').call(brush);
    
              // A function that set idleTimeOut to null
              var idleTimeout;
              function idled() {
                idleTimeout = null;
              }
    
              // add a legend
              let legend = svg.append('g')
                .attr('transform', 'translate(' + (width+10) + ', 10)')
                .attr('class', 'legend');
    
              legend.append('rect')
                .attr('width', 10)
                .attr('height', 10)
                .attr('fill', 'steelblue');
    
              legend.append('text')
                .attr('x', 12)
                .attr('y', 10)
                .text('My Line!');
    
              // A function that update the chart for given boundaries
              function updateChart() {
                // What are the selected boundaries?
                extent = d3.event.selection;
    
                // If no selection, back to initial coordinate. Otherwise, update X axis domain
                if (!extent) {
                  if (!idleTimeout) return (idleTimeout = setTimeout(idled, 350)); // This allows to wait a little bit
                  x.domain([4, 8]);
                } else {
                  x.domain([x.invert(extent[0]), x.invert(extent[1])]);
                  line.select('.brush').call(brush.move, null); // This remove the grey brush area as soon as the selection has been done
                }
    
                // Update axis and line position
                xAxis.transition().duration(1000).call(d3.axisBottom(x));
                line
                  .select('.line')
                  .transition()
                  .duration(1000)
                  .attr(
                    'd',
                    d3
                      .line()
                      .x(function (d) {
                        return x(d.date);
                      })
                      .y(function (d) {
                        return y(d.value);
                      })
                  );
              }
    
              // If user double click, reinitialize the chart
              svg.on('dblclick', function () {
                x.domain(
                  d3.extent(data, function (d) {
                    return d.date;
                  })
                );
                xAxis.transition().call(d3.axisBottom(x));
                line
                  .select('.line')
                  .transition()
                  .attr(
                    'd',
                    d3
                      .line()
                      .x(function (d) {
                        return x(d.date);
                      })
                      .y(function (d) {
                        return y(d.value);
                      })
                  );
              });
            }
          );
        </script>
      </body>
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search