Zach Kramer
Zach Kramer

Reputation: 61

Simple d3.js line chart with alternating labels

simple d3 line chart... need the line to span the whole svg and the labels to be anchored to the path line itself. bonus points if there's optional code to add markers as well. thanks in advance.

<html>
    <head>
        <script src="https://d3js.org/d3.v5.min.js"></script>
        <script>
            function createLineChart(data,id) {
            var svg = d3.select("body").append("svg")
            .attr("width", 800)
            .attr("height", 600);

            var line = d3.line()
            .x(function(d, i) { return i * 50 + 50; })
            .y(function(d) { return 300 - d; });

            svg.append("path")
            .datum(data)
            .attr("d", line)
            .attr("stroke", "black")
            .attr("stroke-width", 2)
            .attr("fill", "none");
            
            data.forEach(function(d, i) {
            if (i > 0) {
                var percentChange = (d - data[i - 1]) / data[i - 1] * 100;
                var color = percentChange >= 0 ? "green" : "red";
                var y = percentChange >= 0 ? d - 15 : d + 15;

                svg.append("text")
                .text(percentChange.toFixed(1) + "%")
                .attr("x", i * 50 + 50)
                .attr("y", y)
                .attr("text-anchor", "middle")
                .attr("fill", color);
            }
            });


            }
        </script>

    </head>

    <body>
            
    </body>
    <script>
        var data = [10, 20, 15, 40, 50, 60];
        createLineChart(data);
    </script>
</html>

Upvotes: 1

Views: 554

Answers (1)

Mark McClure
Mark McClure

Reputation: 4964

I guess the main things you need to read up on are d3 scales and selections (particularly joins). In particular:

  • We'll define the width w and height h of the SVG and then defining scales to fit the path into the SVG.
  • We'll use the data to define each of the path, markers, and text in terms of the data.

createLineChart([10, 20, 15, 40, 50, 60], { mark_points: true })

function createLineChart(data, opts = {}) {
  let { w = 800, h = 500, mark_points = false } = opts;
  let pad = 50;
  
  let x_scale = d3
    .scaleLinear()
    .domain([0, data.length - 1])
    .range([pad, w - pad]);
  let [ymin, ymax] = d3.extent(data);
  let y_scale = d3
    .scaleLinear()
    .domain([ymin, ymax])
    .range([h - pad, pad]);
    
  let svg = d3.select('#container')
    .append("svg")
    .attr("width", w)
    .attr("height", h)
    .style("border", "solid 1px black");

  let line = d3
    .line()
    .x(function (d, i) {
      return x_scale(i);
    })
    .y(function (d) {
      return y_scale(d);
    });

  svg
    .append("path")
    .datum(data)
    .attr("d", line)
    .attr("stroke", "black")
    .attr("stroke-width", 2)
    .attr("fill", "none");

  svg
    .selectAll("circle")
    .data(data)
    .join("circle")
    .attr("cx", (_, i) => x_scale(i))
    .attr("cy", (d) => y_scale(d))
    .attr("r", 5)
    .attr("fill", "black");

  if (mark_points) {
    svg
      .selectAll("text")
      .data(data)
      .join("text")
      .attr("x", (_, i) => x_scale(i))
      .attr("y", (d) => y_scale(d))
      .attr("dx", -3)
      .attr("dy", 12)
      .attr("font-size", 16)
      .attr("text-anchor", "end")
      .attr("fill", (_, i) =>
        data[i] < data[i - 1]
          ? "red"
          : data[i] == data[i - 1]
          ? "blue"
          : "green"
      )
      .text(function (_, i) {
        if (i > 0) {
          let change = (data[i] - data[i - 1]) / data[i - 1];
          return d3.format("0.2%")(change);
        } else {
          return "";
        }
      });
  }

  svg
    .append("g")
    .attr("transform", `translate(0, ${h - pad})`)
    .call(d3.axisBottom(x_scale).ticks(data.length));
  svg
    .append("g")
    .attr("transform", `translate(${pad})`)
    .call(d3.axisLeft(y_scale).ticks(5));
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="container"></div>

Upvotes: 1

Related Questions