不能在多线图 d3 上放置圆圈
can't put circles on multi-line chart d3
我尝试了很多绑定数据的方法,但都没有成功。
我的部分代码是这样的:
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>
首先,你的问题是,对于一条线,你传递了一个 array 数据点,但是对于一个圆,你传递了一个 single 数据点。简单地应用 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
第 1 行变为 0,第 2 行变为 1,等等。对于圆,我们希望它变为 0 5 次,1 5 次,但它变成了0, 1, 2, ..., 10
。因为这就是计数器的作用,所以它们很重要。
您可以通过添加一个 属性 来更改数据,告诉您一个圆属于哪条线,或者您可以靠近它并将所有依赖于一条线的圆放入一个 g
-元素。这意味着为每个 line/circle 组合附加一个 g
。
它有一些优点。一是 fill
可以从那个 g
元素继承,因此您可以继续使用 colorScape(i)
。另一个是您甚至可以将行移动到组中,从而为您提供一种非常简单的方式来突出显示 show/hide 或对其进行其他操作。
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>
我尝试了很多绑定数据的方法,但都没有成功。
我的部分代码是这样的:
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>
首先,你的问题是,对于一条线,你传递了一个 array 数据点,但是对于一个圆,你传递了一个 single 数据点。简单地应用 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
第 1 行变为 0,第 2 行变为 1,等等。对于圆,我们希望它变为 0 5 次,1 5 次,但它变成了0, 1, 2, ..., 10
。因为这就是计数器的作用,所以它们很重要。
您可以通过添加一个 属性 来更改数据,告诉您一个圆属于哪条线,或者您可以靠近它并将所有依赖于一条线的圆放入一个 g
-元素。这意味着为每个 line/circle 组合附加一个 g
。
它有一些优点。一是 fill
可以从那个 g
元素继承,因此您可以继续使用 colorScape(i)
。另一个是您甚至可以将行移动到组中,从而为您提供一种非常简单的方式来突出显示 show/hide 或对其进行其他操作。
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>