block by micahstubbs e4f5c830c264d26621b80b754219ae1b

D3 v4 X-Value Mouseover with Droplines

Full Screen

a D3 version 4 fork of @micahstubbs‘ bl.ock ES2015 X-Value Mouseover with Droplines

D3 v4 syntax cues taken from mbostock‘s bl.ock Line Chart 4.0

index.html

<!DOCTYPE html>
<meta charset='utf-8'>
<body>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script src='https://npmcdn.com/babel-core@5.8.34/browser.min.js'></script>
<script lang='babel' type='text/babel'>
  const margin = { top: 20, right: 50, bottom: 30, left: 50 };
  const width = 960 - margin.left - margin.right;
  const height = 500 - margin.top - margin.bottom;

  const parseTime = d3.timeParse('%d-%b-%y');
  const bisectDate = d3.bisector(d => d.date).left;
  const formatValue = d3.format(',.2f');
  const formatCurrency = d => `$${formatValue(d)}`;

  d3.select('body')
    .style('font', '10px sans-serif')

  const x = d3.scaleTime()
    .range([0, width]);

  const y = d3.scaleLinear()
    .range([height, 0]);

  const line = d3.line()
    .x(d => x(d.date))
    .y(d => y(d.close));

  const svg = d3.select('body').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})`);

  d3.tsv('data.tsv', type, (error, data) => {
    if (error) throw error;

    data.sort((a, b) => a.date - b.date);

    x.domain([data[0].date, data[data.length - 1].date]);
    y.domain(d3.extent(data, d => d.close));

    svg.append('g')
      .attr('class', 'x axis axis--x')
      .attr('transform', `translate(0, ${height})`)
      .call(d3.axisBottom(x));

    svg.append('g')
      .attr('class', 'y axis axis--y')
      .call(d3.axisLeft(y))
      .append('text')
        .attr('class', 'axis-title')
        .attr('transform', 'rotate(-90)')
        .attr('y', 6)
        .attr('dy', '.71em')
        .style('text-anchor', 'end')
        .text('Price ($)');

    // style the axes
    d3.selectAll('.axis path')
      .styles({
        fill: 'none',
        stroke: '#000',
        'shape-rendering': 'crispEdges'
      });

    d3.selectAll('.axis line')
      .styles({
        fill: 'none',
        stroke: '#000',
        'shape-rendering': 'crispEdges'
      });

    d3.selectAll('.axis--x path')
      .style('display', 'none');

    svg.append('path')
      .datum(data)
      .attr('class', 'line')
      .attr('d', line);

    const focus = svg.append('g')
      .attr('class', 'focus')
      .style('display', 'none');

    focus.append('circle')
      .attr('r', 4.5);

    focus.append('line')
      .classed('x', true);

    focus.append('line')
      .classed('y', true);

    focus.append('text')
      .attr('x', 9)
      .attr('dy', '.35em');

    svg.append('rect')
      .attr('class', 'overlay')
      .attr('width', width)
      .attr('height', height)
      .on('mouseover', () => focus.style('display', null))
      .on('mouseout', () => focus.style('display', 'none'))
      .on('mousemove', mousemove);

    d3.selectAll('.line')
      .styles({
        fill: 'none',
        stroke: 'steelblue',
        'stroke-width': '1.5px'
      });

    d3.select('.overlay')
      .styles({
        fill: 'none',
        'pointer-events': 'all'
      });

    d3.selectAll('.focus')
      .style('opacity', 0.7);

    d3.selectAll('.focus circle')
      .styles({
        fill: 'none',
        stroke: 'black'
      });

    d3.selectAll('.focus line')
      .styles({
        fill: 'none',
        'stroke': 'black',
        'stroke-width': '1.5px',
        'stroke-dasharray': '3 3'
      });

    function mousemove() {
      const x0 = x.invert(d3.mouse(this)[0]);
      const i = bisectDate(data, x0, 1);
      const d0 = data[i - 1];
      const d1 = data[i];
      const d = x0 - d0.date > d1.date - x0 ? d1 : d0;
      focus.attr('transform', `translate(${x(d.date)}, ${y(d.close)})`);
      focus.select('line.x')
        .attr('x1', 0)
        .attr('x2', -x(d.date))
        .attr('y1', 0)
        .attr('y2', 0);

      focus.select('line.y')
        .attr('x1', 0)
        .attr('x2', 0)
        .attr('y1', 0)
        .attr('y2', height - y(d.close));

      focus.select('text').text(formatCurrency(d.close));
    }
  });

  function type(d) {
    d.date = parseTime(d.date);
    d.close = +d.close;
    return d;
  }
</script>