block by micahstubbs eaa7007094ac6030e470d4b47cd4f727

slide rule for timeseries

Full Screen

a line chart with a slide rule style bar that moves with the horizontal position of the mouse.

a #d3js skeuomorph

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

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('rect')
      .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 rect')
      .style({
        fill: 'black',
        'fill-opacity': 0.05,
        'stroke': 'black',
        'stroke-width': '1px'
      })

    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;
      const cursorOffset = 0;
      const slideRuleWidth = 20;
      focus.attr('transform', `translate(${x(d.date)}, ${y(d.close)})`);

      focus.select('rect.y')
        .attr('x', -slideRuleWidth - cursorOffset)
        .attr('y', 0 - y(d.close))
        .attr('width', slideRuleWidth)
        .attr('height', height);

      focus.select('text').text(formatCurrency(d.close));
    }
  });
</script>