Sai
Sai

Reputation: 21

How to draw a multi-line chart with d3 version7 graph with string in x-axis?

I am a working on multiline chart using d3 version7 , I have data like

data1: sample1[] = [
{
symbol: 'a',
x1: '2022-01-01',
y1: 150
},
{
symbol: 'c',
x1: '2022-01-01',
y1: 300
},
{
symbol: 'a',
x1: '2022-01-02',
y1: 200
},
{
symbol: 'c',
x1: '2022-01-02',
y1: 700
},
{
symbol: 'a',
x1: '2022-01-03',
y1: 750
},
{
symbol: 'c',
x1: '2022-01-03',
y1: 100
},
];

In X-axis I want to display x1 as string and group with a symbol. I am able to plot x-axis and y-axis from below code

const x = d3.scaleBand()
      .domain(this.data1.map((d: any) => { return d.x1 }))
      .range([0, this.width]);
    this.svg.append("g")
      .attr("transform", `translate(0, ${this.height})`)
      .call(d3.axisBottom(x).ticks(5));
    // Add Y axis
    const y = d3.scaleLinear()
      .domain([0, d3.max(this.data1, ((d: any) => {
        return d.y1;[![enter image description here][1]][1]
      }))])
      .range([this.height, 0]);
    this.svg.append("g")
      .call(
        d3.axisLeft(y)
      );

And It looks like this screenshot of axis

Now When draw a multiple line using this code

const dataNest = Array.from(
      d3.group(this.data1, d => d.symbol), ([key, value]) => ({ key, value })
    );
    var legendSpace = this.width / dataNest.length; // spacing for the legend
    dataNest.forEach((d1: any, i: any) => {
      const xScale = d3
        .scaleBand()
        .domain(d1.value.map((d: any) => {  return d.x1 }))
        // .domain([0, d3.max(this.data1.map((d: any) => d.x1))])
        .range([0, this.width])
      // y-scale
      const yScale = d3
        .scaleLinear()
        .domain([0, d3.max(this.data1.map((d: any) => d.y1))])
        .range([this.height, 0]);
      // Data line
      const line = d3
        .line()
        .x((d: any) => d.x1)
        .y((d: any) => yScale(d.y1));
      this.svg.append("path")
        .data(dataNest)
        .join("path")
        .attr("fill", "none")
        .attr("class", "line")
        .style("stroke", this.colors[i])
        .attr("d", line(d1.value));
      // Add the Legend
      this.svg.append("text")
        .attr("x", (legendSpace / 2) + i * legendSpace)  // space legend
        .attr("y", this.height + (this.margin.bottom / 2) + 15)
        .attr("class", "legend")    // style the legend
        .style("fill", this.colors[i])
        .text(d1.key);
    });

I am not able to plot any line and I am getting an error saying, Error: attribute d: Expected number, "MNaN,160LNaN,146.…". So anyone have solution for displaying the multiline chart.

Upvotes: 1

Views: 1899

Answers (1)

deristnochda
deristnochda

Reputation: 585

In your line generator function, you don't use the xScale to convert values to pixels.

  const line = d3
    .line()
    .x((d: any) => d.x1)
    .y((d: any) => yScale(d.y1));

should be

  const line = d3
    .line()
    .x((d: any) => xScale(d.x1))
    .y((d: any) => yScale(d.y1));

A few additional notes:

  • In every loop of the forEach you define a new xScale and yScale that are identical to the existing x and y used for rendering the axes. Just use x and y.
  • The same goes for the line generator, which should be the same for all lines.
  • Why do you use a forEach on the dataNest array? This is what the d3 data join is for as worked out in the snippet below.
  • Use an appropriate scale for the data type. scaleBand is for categorical data and something like bar charts. You have dates and should use scaleTime. If you really don't want to use a time scale, stick to scalePoint for line charts as the bandwidth is fixed to zero.

const data1 = [
{symbol: 'a', x1: '2022-01-01', y1: 150},
{symbol: 'c', x1: '2022-01-01', y1: 300},
{symbol: 'a', x1: '2022-01-02', y1: 200},
{symbol: 'c', x1: '2022-01-02', y1: 700},
{symbol: 'a', x1: '2022-01-03', y1: 750},
{symbol: 'c', x1: '2022-01-03', y1: 100},
];

const width = 500,
      height = 180;

const svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height);

const x = d3.scalePoint()
  .domain(data1.map(d => d.x1))
  .range([30, width-30]);
svg.append("g")
  .attr("transform", `translate(0, ${height-30})`)
  .call(d3.axisBottom(x).ticks(5));
const y = d3.scaleLinear()
  .domain([0, d3.max(data1, d => d.y1)])
  .range([height-30, 0]);
svg.append("g")
  .attr("transform", `translate(30, 0)`)
  .call(d3.axisLeft(y));
    
const dataNest = Array.from(
  d3.group(data1, d => d.symbol), ([key, value]) => ({ key, value })
);
const line = d3.line()
  .x(d => x(d.x1))
  .y(d => y(d.y1));
svg.selectAll("path.line")
  .data(dataNest)
  .join("path")
    .attr("class", "line")
    .attr("fill", "none")
    .style("stroke", d => d.key === 'a' ? 'blue' : 'red')
    .attr("d", d => line(d.value));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>

Upvotes: 1

Related Questions