Search code examples
javascriptd3.jschartsline

How to create Stacked Line Chart in d3js Multiple Y Axis and common X Axis


Example Linechart

How can i draw a linechart like this in d3js?


Solution

  • You can draw individual line charts which are translated vertically. Translating them vertically can be achieved by placing each chart into a SVG group (g) and translating the group by setting the transform attribute.

    They all share the same x axis with the same domain and range.

    const x = d3.scaleLinear()
      .domain([d3.min(t), d3.max(t)])
      .range([0, width]);
    

    Here is the entire code:

    // dummy data with common domain for all charts
    const t = d3.range(200).map(o => o * 0.1 + 1);
    // three series of data => three charts
    const data = [
      t.map(t => (1 + Math.cos(t))/2),
      t.map(t => 1 - 1/Math.pow(0.2*t, 2)),
      t.map(t => (1 + Math.sin(t))/2),
    ];
    
    // three different colors for three graphs
    const graphColor = ['#F2CD5C', '#a71df2', '#A61f69'];
    
    // we need to add some margin to see the full axis
    const margin = { top: 10, bottom: 75, left: 30, right: 10 };
    // static width and height - change this to whatever suits you
    const width = 300;
    const height = 400;
    
    // the height of each stacked
    const singleChartHeight = height / data.length;
    
    const svg = d3.select('#stacked-charts')
      .append('svg')
      // set the size of the chart
      .attr('width', width + margin.left + margin.right)
      .attr('height', height + margin.top + margin.bottom)
      .attr("viewBox", [0, 0, width, height]);
    
    // create a common x-axis for all charts
    const x = d3.scaleLinear()
      .domain([d3.min(t), d3.max(t)])
      .range([0, width]);
    
    // a global group with padding
    const coreGroup = svg.append('g')
      .attr('class', 'stacked-charts')
      // translate the padding
      .attr('transform', 'translate(' + margin.left + ' 0)');
    
    // create three different y-axes - one for each series
    const y = data.map(dataSeries => {
      return d3.scaleLinear()
        .domain([d3.min(dataSeries), d3.max(dataSeries)])
        .range([singleChartHeight, 0]);
    });
    
    // the line generator
    const lineGenerator = d3.line();
    
    // create a chart for each series of data
    data.forEach((dataSeries, chartIndex) => {
      // create an individual group
      const chartGroup = coreGroup.append('g')
        .attr('class', 'chart')
        // stack by translating vertically
        .attr('transform', 'translate(0 ' + (chartIndex * singleChartHeight) + ')');
    
      // use x/y axes to create the points
      const points = t.map((timestamp, timeStampIndex) => {
        return [
          // the x value
          x(timestamp),
          // the y value from one of the three axes
          y[chartIndex](dataSeries[timeStampIndex])
        ]
      });
      // create the SVG path for the line
      const path = lineGenerator(points);
    
      // draw the graph
      chartGroup.append('path')
        .attr('class', 'graph')
        .attr('d', path)
        .attr('fill', 'none')
        .attr('stroke', graphColor[chartIndex]);
    
      // add x axis
      chartGroup.append('g')
        .attr('class', 'x-axis')
        .call(d3.axisBottom(x))
        .attr('transform', 'translate(0 '+ singleChartHeight + ')');
    
      // add y axis
      chartGroup.append('g')
        .attr('class', 'y-axis')
        .call(d3.axisLeft(y[chartIndex]));
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <div style="text-align: center;">
      <h1>Stacked Linear Charts</h1>
      <div id="stacked-charts"></div>
    </div>

    If you want all charts to have the same y-scale, they could also share the same y-axis.