Home Examples Documentation Playground Source

Stardust Example: Index Chart

A index chart of five stocks. The axes, legend and rules are rendered with D3; the lines are rendered with Stardust.

Data from nasdaq.com.

index.html

<!DOCTYPE html>
<meta charset="utf-8" />
<title>Stardust Example: Scatterplot</title>
<link rel="stylesheet" href="../common/style.css" type="text/css" />
<style type="text/css">
  .chart canvas,
  .chart svg {
    position: absolute;
    left: 0;
    top: 0;
  }

  .chart svg {
    pointer-events: none;
  }

  .axis path,
  .axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
  }

  .axis text {
    font-family: sans-serif;
    font-size: 13px;
  }

  .hline,
  .vline {
    stroke: black;
    shape-rendering: crispEdges;
  }

  .vline {
    stroke: rgb(220, 57, 18);
  }
</style>
<div class="chart">
  <canvas id="main-canvas"></canvas>
  <svg id="main-svg"></svg>
</div>
<div class="initializing"><p>Initializing...</p></div>
<script src="//d3js.org/d3.v3.min.js" type="text/javascript"></script>
<script src="../common/stardust/stardust.bundle.js" type="text/javascript"></script>
<script src="../common/utils.js" type="text/javascript"></script>
<script type="text/javascript">
  let width = 960;
  let height = 500;
  let margin_left = 70;
  let margin_right = 10;
  let margin_top = 10;
  let margin_bottom = 30;
  let canvas = d3.select("#main-canvas").node();
  let svg = d3.select("#main-svg");
  let platform = Stardust.platform("webgl-2d", canvas, width, height);
  svg.attr("width", width).attr("height", height);

  loadData("stock.csv", data => {
    let names = ["MSFT", "AAPL", "IBM", "GOOGL", "AMZN"];
    let colorsOriginal = [
      [0x66, 0xc2, 0xa5],
      [0xfc, 0x8d, 0x62],
      [0x8d, 0xa0, 0xcb],
      [0xe7, 0x8a, 0xc3],
      [0xa6, 0xd8, 0x54]
    ];
    colors = colorsOriginal.map(x => [x[0] / 255, x[1] / 255, x[2] / 255, 1]);
    let polyline = Stardust.mark.polyline();

    let polylines = Stardust.mark.create(polyline, platform);

    let ranges = names.map(d => {
      return [d3.min(data, x => +x[d]), d3.max(data, x => +x[d])];
    });

    let xScale = d3.time
      .scale()
      .domain([d3.min(data, d => +d.Time * 1000), d3.max(data, d => +d.Time * 1000)])
      .range([margin_left, width - margin_right]);

    let yScale = Stardust.scale
      .linear()
      .domain([0, 1000])
      .range([height - margin_bottom, margin_top]);

    let legendItems = svg
      .append("g")
      .selectAll("g")
      .data(names)
      .enter()
      .append("g");
    legendItems.attr("transform", (d, i) => `translate(${margin_left + 20}, ${margin_top + 20 * i + 10})`);
    legendItems
      .append("line")
      .attr("x1", 0)
      .attr("x2", 15)
      .attr("y1", 0)
      .attr("y2", 0)
      .style("stroke", (d, i) => `rgb(${colorsOriginal[i].join(",")})`);
    legendItems
      .append("text")
      .attr("x", 20)
      .attr("y", 5)
      .text(d => d)
      .style("fill", (d, i) => `rgb(${colorsOriginal[i].join(",")})`);

    svg
      .append("g")
      .classed("axis", true)
      .attr("transform", `translate(0, ${height - margin_bottom})`)
      .call(
        d3.svg
          .axis()
          .scale(xScale)
          .orient("bottom")
          .tickFormat(d3.time.format("%Y"))
      );
    let gYAxis = svg
      .append("g")
      .classed("axis", true)
      .attr("transform", `translate(${margin_left}, 0)`);

    let hline = svg
      .append("line")
      .classed("hline", true)
      .attr("x1", margin_left)
      .attr("x2", width - margin_right);
    let vline = svg
      .append("line")
      .classed("vline", true)
      .attr("y1", margin_top)
      .attr("y2", height - margin_bottom);

    let indexScale = d3.scale
      .linear()
      .domain([1 / 3, 3])
      .range(yScale.range());

    polylines
      .attr("p", Stardust.scale.Vector2(d => xScale(d.time), yScale(d => d.value)))
      .attr("width", 1)
      .attr("color", [0, 0, 0, 0.4]);

    let refIdx = 0;

    polylines.instance(
      (d, i) => {
        return data.map(x => {
          return { time: +x.Time * 1000, value: +x[d] };
        });
      },
      (d, i) => {
        yScale.domain([data[refIdx][d] * indexScale.domain()[0], data[refIdx][d] * indexScale.domain()[1]]);
        return { color: colors[i] };
      }
    );

    polylines.data(names);

    let rerender = () => {
      indexScale.domain([
        d3.min(ranges, (x, i) => x[0] / data[refIdx][names[i]]),
        d3.max(ranges, (x, i) => x[1] / data[refIdx][names[i]])
      ]);
      hline.attr("y1", indexScale(1));
      hline.attr("y2", indexScale(1));
      vline.attr("x1", xScale(+data[refIdx].Time * 1000));
      vline.attr("x2", xScale(+data[refIdx].Time * 1000));
      gYAxis.call(
        d3.svg
          .axis()
          .scale(indexScale)
          .orient("left")
          .tickFormat(d3.format(".0%"))
      );
      platform.clear();
      polylines.render();
    };

    rerender();

    d3.select(canvas).on("mousemove", () => {
      let left = canvas.getBoundingClientRect().left;
      let x = d3.event.clientX;
      let d3xscale = d3.scale
        .linear()
        .domain(xScale.domain())
        .range(xScale.range());
      let t = d3xscale.invert(x - left) / 1000;
      let idx = Math.floor(((t - data[0].Time) / (data[data.length - 1].Time - data[0].Time)) * (data.length - 1));
      idx = Math.max(Math.min(idx, data.length - 1), 0);
      refIdx = idx;
      requestAnimationFrame(rerender);
    });
  });
</script>