не удается поместить круги на многострочный график d3

#d3.js #linechart

#d3.js #линейный график

Вопрос:

Я перепробовал много способов привязки данных, но безуспешно.

часть моего кода выглядит так:

  let circleData = data.map((i) => i.values);
      mainGraph
        // .append("g")
        .selectAll("circle")
        .data(circleData)
        .enter()
        .append("circle")
        .attrs({
          cx: (d) => xScale(d.name),
          cy: (d) => yScale(d.value),
          r: 3,
          opacity: 1,
        });
  

Я так запутался в функции данных, используйте тот же способ, чтобы нарисовать линию, она работает нормально, но когда дело доходит до кругов, казалось, что данные вообще не могут считываться. Я надеюсь, что кто-нибудь поможет мне понять это, большое вам спасибо!

 const data = [{
  category: "series_1",
  values: [{
      name: "A",
      value: 10
    },
    {
      name: "B",
      value: 21
    },
    {
      name: "C",
      value: 19
    },
    {
      name: "D",
      value: 23
    },
    {
      name: "E",
      value: 20
    },
  ],
}, ];
let counter = 1;
const add_set = (arr) => {
  let copy = JSON.parse(JSON.stringify(arr[0]));
  const random = () => Math.floor(Math.random() * 20   1);
  const add = (arr) => {
    counter  ;
    copy.values.map((i) => (i.value = random()));
    copy.category = `series_${counter}`;
    arr.push(copy);
  };
  add(arr);
};
add_set(data);

//No.1 define the svg
let graphWidth = 600,
  graphHeight = 300;
let margin = {
  top: 60,
  right: 10,
  bottom: 30,
  left: 45
};
let totalWidth = graphWidth   margin.left   margin.right,
  totalHeight = graphHeight   margin.top   margin.bottom;
let svg = d3
  .select("body")
  .append("svg")
  .attr("width", totalWidth)
  .attr("height", totalHeight);
//No.2 define mainGraph
let mainGraph = svg
  .append("g")
  .attr("transform", "translate("   margin.left   ","   margin.top   ")");
//No.3 define axises
let categoriesNames = data[0].values.map((d) => d.name);
let xScale = d3
  .scalePoint()
  .domain(categoriesNames)
  .range([0, graphWidth]); // scalepoint make the axis starts with value compared with scaleBand
let colorScale = d3.scaleOrdinal(d3.schemeCategory10);
colorScale.domain(data.map((d) => d.category));

let yScale = d3
  .scaleLinear()
  .range([graphHeight, 0])
  .domain([
    d3.min(data, (i) => d3.min(i.values, (x) => x.value)),
    d3.max(data, (i) => d3.max(i.values, (x) => x.value)),
  ]); //* If an arrow function is simply returning a single line of code, you can omit the statement brackets and the return keyword

//No.4 set axises
mainGraph
  .append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0,"   graphHeight   ")")
  .call(d3.axisBottom(xScale));
mainGraph.append("g").attr("class", "y axis").call(d3.axisLeft(yScale));
//No.5 make lines
let lineGenerator = d3
  .line()
  .x((d) => xScale(d.name))
  .y((d) => yScale(d.value))
  .curve(d3.curveMonotoneX);

var lines = mainGraph
  .selectAll(".path")
  .data(data.map((i) => i.values))
  .enter()
  .append("path")
  .attr("d", lineGenerator)
  .attr("fill", "none")
  .attr("stroke-width", 3)
  .attr("stroke", (d, i) => colorScale(i));

//No.6 append circles
let circleData = data.map((i) => i.values);
mainGraph
  // .append("g")
  .selectAll("circle")
  .data(circleData)
  .enter()
  .append("circle")
  .attrs({
    cx: (d) => xScale(d.name),
    cy: (d) => yScale(d.value),
    r: 3,
    opacity: 1,
  });  
 .line {
  stroke: blue;
  fill: none;
}  
 <script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>  

Ответ №1:

Во-первых, ваша проблема в том, что для линии вы передаете массив точек данных, а для круга вы передаете одну точку данных. Простое применение console.log(circleData) говорит мне, что circleData это массив массивов.


Существует несколько способов заставить ваш код работать. Один из них — просто объединить все точки данных окружности в один массив, используя . circleData.flat() Плюсом является то, что это, безусловно, самое простое решение. Недостатком является то, что вы используете (d, i) => colorScale(i) , и i теперь перепутались.

 //No.6 append circles
let circleData = data.map((i) => i.values).flat();
mainGraph
  .selectAll("circle")
  .data(circleData)
  .enter()
  .append("circle")
  .attrs({
    cx: (d) => xScale(d.name),
    cy: (d) => yScale(d.value),
    r: 3,
    opacity: 1,
  });
  

Для строк i становится 0 для строки 1, 1 для строки 2 и т.д. Для кругов мы хотели бы, чтобы оно стало 0 пять раз и 1 пять раз, но вместо этого оно становится 0, 1, 2, ..., 10 . Потому что это то, что делают счетчики, они считают.

Вы можете изменить данные, добавив свойство, которое сообщает вам, к какой линии принадлежит круг, или вы можете использовать его и поместить все круги, которые зависят от линии, в одноэлементный g . Это означает добавление a g для каждой комбинации линий / кругов.

У этого есть некоторые недостатки. Один fill из них может быть унаследован от этого g элемента, поэтому вы можете продолжать использовать colorScape(i) . Другим является то, что вы можете даже переместить линию в группу, что дает вам очень простой способ выделить, показать / скрыть или сделать с ней другие действия.

 const data = [{
  category: "series_1",
  values: [{
      name: "A",
      value: 10
    },
    {
      name: "B",
      value: 21
    },
    {
      name: "C",
      value: 19
    },
    {
      name: "D",
      value: 23
    },
    {
      name: "E",
      value: 20
    },
  ],
}, ];
let counter = 1;
const add_set = (arr) => {
  let copy = JSON.parse(JSON.stringify(arr[0]));
  const random = () => Math.floor(Math.random() * 20   1);
  const add = (arr) => {
    counter  ;
    copy.values.map((i) => (i.value = random()));
    copy.category = `series_${counter}`;
    arr.push(copy);
  };
  add(arr);
};
add_set(data);

//No.1 define the svg
let graphWidth = 600,
  graphHeight = 300;
let margin = {
  top: 60,
  right: 10,
  bottom: 30,
  left: 45
};
let totalWidth = graphWidth   margin.left   margin.right,
  totalHeight = graphHeight   margin.top   margin.bottom;
let svg = d3
  .select("body")
  .append("svg")
  .attr("width", totalWidth)
  .attr("height", totalHeight);
//No.2 define mainGraph
let mainGraph = svg
  .append("g")
  .attr("transform", "translate("   margin.left   ","   margin.top   ")");
//No.3 define axises
let categoriesNames = data[0].values.map((d) => d.name);
let xScale = d3
  .scalePoint()
  .domain(categoriesNames)
  .range([0, graphWidth]); // scalepoint make the axis starts with value compared with scaleBand
let colorScale = d3.scaleOrdinal(d3.schemeCategory10);
colorScale.domain(data.map((d) => d.category));

let yScale = d3
  .scaleLinear()
  .range([graphHeight, 0])
  .domain([
    d3.min(data, (i) => d3.min(i.values, (x) => x.value)),
    d3.max(data, (i) => d3.max(i.values, (x) => x.value)),
  ]); //* If an arrow function is simply returning a single line of code, you can omit the statement brackets and the return keyword

//No.4 set axises
mainGraph
  .append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0,"   graphHeight   ")")
  .call(d3.axisBottom(xScale));
mainGraph.append("g").attr("class", "y axis").call(d3.axisLeft(yScale));
//No.5 make lines
let lineGenerator = d3
  .line()
  .x((d) => xScale(d.name))
  .y((d) => yScale(d.value))
  .curve(d3.curveMonotoneX);

var lines = mainGraph
  .selectAll(".path")
  .data(data.map((i) => i.values))
  .enter()
  .append("path")
  .attr("d", lineGenerator)
  .attr("fill", "none")
  .attr("stroke-width", 3)
  .attr("stroke", (d, i) => colorScale(i));

//No.6 append circles
let circleData = data.map((i) => i.values);
mainGraph
  .selectAll(".circle-container")
  .data(circleData)
  .enter()
  .append("g")
  .attr("class", "circle-container")
  .attr("fill", (d, i) => console.log(d) || colorScale(i))
  .selectAll("circle")
  .data((d) => d)
  .enter()
  .append("circle")
  .attrs({
    cx: (d) => xScale(d.name),
    cy: (d) => yScale(d.value),
    r: 3,
    opacity: 1,
  });  
 .line {
  stroke: blue;
  fill: none;
}  
 <script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>