block by micahstubbs d66a1662fd64a08051dc473f0d1f956e

ES2015 X-Value Mouseover with Droplines

Full Screen

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

this example also converts all CSS styles previously inside of the <style></style> HTML tags into inline styles applied in Javascript to d3 selections.

this CSS that styles the droplines:

.focus line {
  fill: none;
  stroke: black;
  stroke-width: 1.5px;
  stroke-dasharray: 3 3;
}

becomes this Javascript that does the same thing:

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

inline styles can make it easier to quickly include d3js examples like this into a larger Javascript project that is automatically built from many source files into one bundle file.

index.html

<!DOCTYPE html>
<meta charset='utf-8'>
<body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.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 parseDate = d3.time.format('%d-%b-%y').parse;
  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.time.scale()
      .range([0, width]);

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

  const xAxis = d3.svg.axis()
      .scale(x)
      .orient('bottom');

  const yAxis = d3.svg.axis()
      .scale(y)
      .orient('left');

  const line = d3.svg.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', (error, data) => {
    if (error) throw error;

    data.forEach(d => {
      d.date = parseDate(d.date);
      d.close = +d.close;
    });

    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')
        .attr('transform', `translate(0, ${height})`)
        .call(xAxis);

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

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

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

    d3.selectAll('.x.axis 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')
      .style({
        fill: 'none',
        stroke: 'steelblue',
        'stroke-width': '1.5px'
      });

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

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

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

    d3.selectAll('.focus line')
      .style({
        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));
    }
  });
</script>