如何在d3的每行中制作带有虚线段的多线图表
how to make multiline chart with dashed line segment in each line in d3
数据如下所示:
var data = [
{
name: 'USA',
values: [
{ date: '2000', price: '100', isDashed:true },
{ date: '2001', price: '110', isDashed:true },
{ date: '2002', price: '145', isDashed:false },...
,
{
name: 'Canada',
values: [
{ date: '2000', price: '200',isDashed:true },
{ date: '2001', price: '120',isDashed:true },
{ date: '2002', price: '33',isDashed:true }...
]
}
]
我正在关注这个 link https://codesandbox.io/s/multi-line-chart-example-forked-hjix5?file=/src/index.js
问题是,在我的数据中有一个标志 isDashed 当它为真时我需要在图表中像这样(例如)虚线段。
var data = [{
name: "USA",
values: [{
date: "2000",
price: "100",
r: 1
},
{
date: "2001",
price: "110",
r: 1
},
{
date: "2002",
price: "145"
},
{
date: "2003",
price: "241"
},
{
date: "2004",
price: "101"
},
{
date: "2005",
price: "90"
},
{
date: "2006",
price: "10"
},
{
date: "2007",
price: "35"
},
{
date: "2008",
price: "21"
},
{
date: "2009",
price: "201"
}
]
},
{
name: "Canada",
values: [{
date: "2000",
price: "200"
},
{
date: "2001",
price: "120"
},
{
date: "2002",
price: "33"
},
{
date: "2003",
price: "21"
},
{
date: "2004",
price: "51"
},
{
date: "2005",
price: "190"
},
{
date: "2006",
price: "120"
},
{
date: "2007",
price: "85"
},
{
date: "2008",
price: "221"
},
{
date: "2009",
price: "101"
}
]
},
{
name: "Maxico",
values: [{
date: "2000",
price: "50"
},
{
date: "2001",
price: "10"
},
{
date: "2002",
price: "5"
},
{
date: "2003",
price: "71"
},
{
date: "2004",
price: "20"
},
{
date: "2005",
price: "9"
},
{
date: "2006",
price: "220"
},
{
date: "2007",
price: "235"
},
{
date: "2008",
price: "61"
},
{
date: "2009",
price: "10"
}
]
}
];
var width = 500;
var height = 300;
var margin = 50;
var duration = 250;
var lineOpacity = "0.25";
var lineOpacityHover = "0.85";
var otherLinesOpacityHover = "0.1";
var lineStroke = "1.5px";
var lineStrokeHover = "2.5px";
var circleOpacity = "0.85";
var circleOpacityOnLineHover = "0.25";
var circleRadius = 3;
var circleRadiusHover = 6;
/* Format Data */
var parseDate = d3.timeParse("%Y");
data.forEach(function(d) {
d.values.forEach(function(d) {
d.date = parseDate(d.date);
d.price = +d.price;
d.r = +d.r;
});
});
/* Scale */
var xScale = d3
.scaleTime()
.domain(d3.extent(data[0].values, (d) => d.date))
.range([0, width - margin]);
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data[0].values, (d) => d.price)])
.range([height - margin, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
/* Add SVG */
var svg = d3
.select("#chart")
.append("svg")
.attr("width", width + margin + "px")
.attr("height", height + margin + "px")
.append("g")
.attr("transform", `translate(${margin}, ${margin})`);
/* Add line into SVG */
var line = d3
.line()
.x((d) => xScale(d.date))
.y((d) => yScale(d.price));
let lines = svg.append("g").attr("class", "lines");
lines
.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.on("mouseover", function(d, i) {
svg
.append("text")
.attr("class", "title-text")
.style("fill", color(i))
.text(d.name)
.attr("text-anchor", "middle")
.attr("x", (width - margin) / 2)
.attr("y", 5)
.attr("r", 5);
})
.on("mouseout", function(d) {
svg.select(".title-text").remove();
})
.append("path")
.attr("class", "line")
.attr("d", (d) => line(d.values))
.style("stroke", (d, i) => color(i))
.style("opacity", lineOpacity)
.on("mouseover", function(d) {
d3.selectAll(".line").style("opacity", otherLinesOpacityHover);
d3.selectAll(".circle").style("opacity", circleOpacityOnLineHover);
d3.select(this)
.style("opacity", lineOpacityHover)
.style("stroke-width", lineStrokeHover)
.style("cursor", "pointer");
})
.on("mouseout", function(d) {
d3.selectAll(".line").style("opacity", lineOpacity);
d3.selectAll(".circle").style("opacity", circleOpacity);
d3.select(this)
.style("stroke-width", lineStroke)
.style("cursor", "none");
});
/* Add circles in the line */
lines
.selectAll("circle-group")
.data(data)
.enter()
.append("g")
.style("fill", (d, i) => color(i))
.selectAll("circle")
.data((d) => d.values)
.enter()
.append("g")
.attr("class", "circle")
.on("mouseover", function(d) {
d3.select(this)
.style("cursor", "pointer")
.append("text")
.attr("class", "text")
.text(`${d.price}`)
.attr("x", (d) => xScale(d.date) + 5)
.attr("y", (d) => yScale(d.price) - 10);
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.transition()
.duration(duration)
.selectAll(".text")
.remove();
})
.append("circle")
.attr("cx", (d) => xScale(d.date))
.attr("cy", (d) => yScale(d.price))
.attr("r", circleRadius)
.style("opacity", circleOpacity)
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(duration)
.attr("r", circleRadiusHover);
})
.on("mouseout", function(d) {
d3.select(this).transition().duration(duration).attr("r", circleRadius);
});
/* Add Axis into SVG */
var xAxis = d3.axisBottom(xScale).ticks(5);
var yAxis = d3.axisLeft(yScale).ticks(5);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height - margin})`)
.call(xAxis);
svg
.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("y", 15)
.attr("transform", "rotate(-90)")
.attr("fill", "#000")
.text("Total values");
.App {
font-family: sans-serif;
text-align: center;
}
svg {
font-family: Sans-Serif, Arial;
}
.line {
stroke-width: 2;
fill: none;
}
.axis path {
stroke: black;
}
.text {
font-size: 12px;
}
.title-text {
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<div id="chart" />
</div>
正如我在评论中所说,您似乎希望线段既是虚线又是稍微亮一点的颜色。最简单的解决方法是只绘制两条单独的线段。更难的解决方案是使用 stroke-dasharray 和线性渐变的组合作为描边,但这可能是 bug-prone.
由于您似乎不太关心,下面的代码显示了更简单的解决方法:
var data = [{
name: "USA",
values: [{
date: "2000",
price: "100",
r: 1,
isDashed: true
},
{
date: "2001",
price: "110",
r: 1,
isDashed: true
},
{
date: "2002",
price: "145",
isDashed: true
},
{
date: "2003",
price: "241",
isDashed: true
},
{
date: "2004",
price: "101",
isDashed: true
},
{
date: "2005",
price: "90",
isDashed: true
},
{
date: "2006",
price: "10",
isDashed: true
},
{
date: "2007",
price: "35"
},
{
date: "2008",
price: "21"
},
{
date: "2009",
price: "201"
}
]
},
{
name: "Canada",
values: [{
date: "2000",
price: "200"
},
{
date: "2001",
price: "120"
},
{
date: "2002",
price: "33"
},
{
date: "2003",
price: "21"
},
{
date: "2004",
price: "51",
isDashed: true
},
{
date: "2005",
price: "190",
isDashed: true
},
{
date: "2006",
price: "120",
isDashed: true
},
{
date: "2007",
price: "85"
},
{
date: "2008",
price: "221"
},
{
date: "2009",
price: "101"
}
]
},
{
name: "Maxico",
values: [{
date: "2000",
price: "50"
},
{
date: "2001",
price: "10"
},
{
date: "2002",
price: "5",
isDashed: true
},
{
date: "2003",
price: "71"
},
{
date: "2004",
price: "20"
},
{
date: "2005",
price: "9"
},
{
date: "2006",
price: "220"
},
{
date: "2007",
price: "235"
},
{
date: "2008",
price: "61"
},
{
date: "2009",
price: "10"
}
]
}
];
var width = 500;
var height = 300;
var margin = 50;
var duration = 250;
var lineOpacity = "0.25";
var lineOpacityHover = "0.85";
var otherLinesOpacityHover = "0.1";
var lineStroke = "1.5px";
var lineStrokeHover = "2.5px";
var circleOpacity = "0.85";
var circleOpacityOnLineHover = "0.25";
var circleRadius = 3;
var circleRadiusHover = 6;
/* Format Data */
var parseDate = d3.timeParse("%Y");
data.forEach(function(d) {
d.values.forEach(function(d) {
d.date = parseDate(d.date);
d.price = +d.price;
d.r = +d.r;
});
});
/* Scale */
var xScale = d3
.scaleTime()
.domain(d3.extent(data[0].values, (d) => d.date))
.range([0, width - margin]);
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data[0].values, (d) => d.price)])
.range([height - margin, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
/* Add SVG */
var svg = d3
.select("#chart")
.append("svg")
.attr("width", width + margin + "px")
.attr("height", height + margin + "px")
.append("g")
.attr("transform", `translate(${margin}, ${margin})`);
/* Add line into SVG */
var line = d3
.line()
.defined(d => !isNaN(d.price)) // to make it deal with NaNs
.x((d) => xScale(d.date))
.y((d) => yScale(d.price));
let lines = svg.append("g").attr("class", "lines");
let lineGroups = lines
.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.on("mouseover", function(d, i) {
svg
.append("text")
.attr("class", "title-text")
.style("fill", color(i))
.text(d.name)
.attr("text-anchor", "middle")
.attr("x", (width - margin) / 2)
.attr("y", 5)
.attr("r", 5);
})
.on("mouseout", function(d) {
svg.select(".title-text").remove();
});
function getDashedParts(values, lookingForDashed) {
const results = [];
let previousWasMatch = false;
// For every node we add, we need also the node just before,
// so we can draw a line between them
values.forEach((v, i) => {
if(lookingForDashed === !!v.isDashed) {
if (!previousWasMatch && i > 0) {
results.push(values[i - 1]);
}
results.push(v);
previousWasMatch = true;
} else {
results.push({ date: v.date, price: NaN });
previousWasMatch = false;
}
});
// console.log(results);
return results;
}
lineGroups
.append("path")
.attr("class", "line")
.attr("d", (d) => line(getDashedParts(d.values, false)));
lineGroups
.append("path")
.attr("class", "line line-dashed")
.attr("d", (d) => line(getDashedParts(d.values, true)));
lineGroups.each(function(d, i) {
const lineParts = d3.select(this).selectAll("path");
lineParts
.style("stroke", color(i))
.style("opacity", lineOpacity)
.on("mouseover", () => {
d3.selectAll(".line").style("opacity", otherLinesOpacityHover);
d3.selectAll(".circle").style("opacity", circleOpacityOnLineHover);
lineParts
.style("opacity", lineOpacityHover)
.style("stroke-width", lineStrokeHover)
.style("cursor", "pointer");
})
.on("mouseout", () => {
d3.selectAll(".line").style("opacity", lineOpacity);
d3.selectAll(".circle").style("opacity", circleOpacity);
lineParts
.style("stroke-width", lineStroke)
.style("cursor", "none");
});
});
/* Add circles in the line */
lines
.selectAll("circle-group")
.data(data)
.enter()
.append("g")
.style("fill", (d, i) => color(i))
.selectAll("circle")
.data((d) => d.values)
.enter()
.append("g")
.attr("class", "circle")
.on("mouseover", function(d) {
d3.select(this)
.style("cursor", "pointer")
.append("text")
.attr("class", "text")
.text(`${d.price}`)
.attr("x", (d) => xScale(d.date) + 5)
.attr("y", (d) => yScale(d.price) - 10);
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.transition()
.duration(duration)
.selectAll(".text")
.remove();
})
.append("circle")
.attr("cx", (d) => xScale(d.date))
.attr("cy", (d) => yScale(d.price))
.attr("r", circleRadius)
.style("opacity", circleOpacity)
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(duration)
.attr("r", circleRadiusHover);
})
.on("mouseout", function(d) {
d3.select(this).transition().duration(duration).attr("r", circleRadius);
});
/* Add Axis into SVG */
var xAxis = d3.axisBottom(xScale).ticks(5);
var yAxis = d3.axisLeft(yScale).ticks(5);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height - margin})`)
.call(xAxis);
svg
.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("y", 15)
.attr("transform", "rotate(-90)")
.attr("fill", "#000")
.text("Total values");
.App {
font-family: sans-serif;
text-align: center;
}
svg {
font-family: Sans-Serif, Arial;
}
.line {
stroke-width: 2;
fill: none;
}
.line-dashed {
stroke-dasharray: 5,5;
}
.axis path {
stroke: black;
}
.text {
font-size: 12px;
}
.title-text {
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<div id="chart" />
</div>
我做了什么:
- 我将
isDefined()
添加到您对 d3.line()
的定义中,以便它可以处理 NaN;
- 我用 isDashed true 或 false 过滤了每个数组的值。当我找到一个匹配的元素而前一个不匹配时,我将这两个元素都添加到结果集中。这样,它们之间就画了一条线,就不会出现空隙了;
- 我稍微更改了鼠标悬停行为,这样如果将鼠标悬停在虚线和实线段上,它们就会同时突出显示。
数据如下所示:
var data = [
{
name: 'USA',
values: [
{ date: '2000', price: '100', isDashed:true },
{ date: '2001', price: '110', isDashed:true },
{ date: '2002', price: '145', isDashed:false },...
,
{
name: 'Canada',
values: [
{ date: '2000', price: '200',isDashed:true },
{ date: '2001', price: '120',isDashed:true },
{ date: '2002', price: '33',isDashed:true }...
]
}
]
我正在关注这个 link https://codesandbox.io/s/multi-line-chart-example-forked-hjix5?file=/src/index.js
问题是,在我的数据中有一个标志 isDashed 当它为真时我需要在图表中像这样(例如)虚线段。
var data = [{
name: "USA",
values: [{
date: "2000",
price: "100",
r: 1
},
{
date: "2001",
price: "110",
r: 1
},
{
date: "2002",
price: "145"
},
{
date: "2003",
price: "241"
},
{
date: "2004",
price: "101"
},
{
date: "2005",
price: "90"
},
{
date: "2006",
price: "10"
},
{
date: "2007",
price: "35"
},
{
date: "2008",
price: "21"
},
{
date: "2009",
price: "201"
}
]
},
{
name: "Canada",
values: [{
date: "2000",
price: "200"
},
{
date: "2001",
price: "120"
},
{
date: "2002",
price: "33"
},
{
date: "2003",
price: "21"
},
{
date: "2004",
price: "51"
},
{
date: "2005",
price: "190"
},
{
date: "2006",
price: "120"
},
{
date: "2007",
price: "85"
},
{
date: "2008",
price: "221"
},
{
date: "2009",
price: "101"
}
]
},
{
name: "Maxico",
values: [{
date: "2000",
price: "50"
},
{
date: "2001",
price: "10"
},
{
date: "2002",
price: "5"
},
{
date: "2003",
price: "71"
},
{
date: "2004",
price: "20"
},
{
date: "2005",
price: "9"
},
{
date: "2006",
price: "220"
},
{
date: "2007",
price: "235"
},
{
date: "2008",
price: "61"
},
{
date: "2009",
price: "10"
}
]
}
];
var width = 500;
var height = 300;
var margin = 50;
var duration = 250;
var lineOpacity = "0.25";
var lineOpacityHover = "0.85";
var otherLinesOpacityHover = "0.1";
var lineStroke = "1.5px";
var lineStrokeHover = "2.5px";
var circleOpacity = "0.85";
var circleOpacityOnLineHover = "0.25";
var circleRadius = 3;
var circleRadiusHover = 6;
/* Format Data */
var parseDate = d3.timeParse("%Y");
data.forEach(function(d) {
d.values.forEach(function(d) {
d.date = parseDate(d.date);
d.price = +d.price;
d.r = +d.r;
});
});
/* Scale */
var xScale = d3
.scaleTime()
.domain(d3.extent(data[0].values, (d) => d.date))
.range([0, width - margin]);
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data[0].values, (d) => d.price)])
.range([height - margin, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
/* Add SVG */
var svg = d3
.select("#chart")
.append("svg")
.attr("width", width + margin + "px")
.attr("height", height + margin + "px")
.append("g")
.attr("transform", `translate(${margin}, ${margin})`);
/* Add line into SVG */
var line = d3
.line()
.x((d) => xScale(d.date))
.y((d) => yScale(d.price));
let lines = svg.append("g").attr("class", "lines");
lines
.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.on("mouseover", function(d, i) {
svg
.append("text")
.attr("class", "title-text")
.style("fill", color(i))
.text(d.name)
.attr("text-anchor", "middle")
.attr("x", (width - margin) / 2)
.attr("y", 5)
.attr("r", 5);
})
.on("mouseout", function(d) {
svg.select(".title-text").remove();
})
.append("path")
.attr("class", "line")
.attr("d", (d) => line(d.values))
.style("stroke", (d, i) => color(i))
.style("opacity", lineOpacity)
.on("mouseover", function(d) {
d3.selectAll(".line").style("opacity", otherLinesOpacityHover);
d3.selectAll(".circle").style("opacity", circleOpacityOnLineHover);
d3.select(this)
.style("opacity", lineOpacityHover)
.style("stroke-width", lineStrokeHover)
.style("cursor", "pointer");
})
.on("mouseout", function(d) {
d3.selectAll(".line").style("opacity", lineOpacity);
d3.selectAll(".circle").style("opacity", circleOpacity);
d3.select(this)
.style("stroke-width", lineStroke)
.style("cursor", "none");
});
/* Add circles in the line */
lines
.selectAll("circle-group")
.data(data)
.enter()
.append("g")
.style("fill", (d, i) => color(i))
.selectAll("circle")
.data((d) => d.values)
.enter()
.append("g")
.attr("class", "circle")
.on("mouseover", function(d) {
d3.select(this)
.style("cursor", "pointer")
.append("text")
.attr("class", "text")
.text(`${d.price}`)
.attr("x", (d) => xScale(d.date) + 5)
.attr("y", (d) => yScale(d.price) - 10);
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.transition()
.duration(duration)
.selectAll(".text")
.remove();
})
.append("circle")
.attr("cx", (d) => xScale(d.date))
.attr("cy", (d) => yScale(d.price))
.attr("r", circleRadius)
.style("opacity", circleOpacity)
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(duration)
.attr("r", circleRadiusHover);
})
.on("mouseout", function(d) {
d3.select(this).transition().duration(duration).attr("r", circleRadius);
});
/* Add Axis into SVG */
var xAxis = d3.axisBottom(xScale).ticks(5);
var yAxis = d3.axisLeft(yScale).ticks(5);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height - margin})`)
.call(xAxis);
svg
.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("y", 15)
.attr("transform", "rotate(-90)")
.attr("fill", "#000")
.text("Total values");
.App {
font-family: sans-serif;
text-align: center;
}
svg {
font-family: Sans-Serif, Arial;
}
.line {
stroke-width: 2;
fill: none;
}
.axis path {
stroke: black;
}
.text {
font-size: 12px;
}
.title-text {
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<div id="chart" />
</div>
正如我在评论中所说,您似乎希望线段既是虚线又是稍微亮一点的颜色。最简单的解决方法是只绘制两条单独的线段。更难的解决方案是使用 stroke-dasharray 和线性渐变的组合作为描边,但这可能是 bug-prone.
由于您似乎不太关心,下面的代码显示了更简单的解决方法:
var data = [{
name: "USA",
values: [{
date: "2000",
price: "100",
r: 1,
isDashed: true
},
{
date: "2001",
price: "110",
r: 1,
isDashed: true
},
{
date: "2002",
price: "145",
isDashed: true
},
{
date: "2003",
price: "241",
isDashed: true
},
{
date: "2004",
price: "101",
isDashed: true
},
{
date: "2005",
price: "90",
isDashed: true
},
{
date: "2006",
price: "10",
isDashed: true
},
{
date: "2007",
price: "35"
},
{
date: "2008",
price: "21"
},
{
date: "2009",
price: "201"
}
]
},
{
name: "Canada",
values: [{
date: "2000",
price: "200"
},
{
date: "2001",
price: "120"
},
{
date: "2002",
price: "33"
},
{
date: "2003",
price: "21"
},
{
date: "2004",
price: "51",
isDashed: true
},
{
date: "2005",
price: "190",
isDashed: true
},
{
date: "2006",
price: "120",
isDashed: true
},
{
date: "2007",
price: "85"
},
{
date: "2008",
price: "221"
},
{
date: "2009",
price: "101"
}
]
},
{
name: "Maxico",
values: [{
date: "2000",
price: "50"
},
{
date: "2001",
price: "10"
},
{
date: "2002",
price: "5",
isDashed: true
},
{
date: "2003",
price: "71"
},
{
date: "2004",
price: "20"
},
{
date: "2005",
price: "9"
},
{
date: "2006",
price: "220"
},
{
date: "2007",
price: "235"
},
{
date: "2008",
price: "61"
},
{
date: "2009",
price: "10"
}
]
}
];
var width = 500;
var height = 300;
var margin = 50;
var duration = 250;
var lineOpacity = "0.25";
var lineOpacityHover = "0.85";
var otherLinesOpacityHover = "0.1";
var lineStroke = "1.5px";
var lineStrokeHover = "2.5px";
var circleOpacity = "0.85";
var circleOpacityOnLineHover = "0.25";
var circleRadius = 3;
var circleRadiusHover = 6;
/* Format Data */
var parseDate = d3.timeParse("%Y");
data.forEach(function(d) {
d.values.forEach(function(d) {
d.date = parseDate(d.date);
d.price = +d.price;
d.r = +d.r;
});
});
/* Scale */
var xScale = d3
.scaleTime()
.domain(d3.extent(data[0].values, (d) => d.date))
.range([0, width - margin]);
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data[0].values, (d) => d.price)])
.range([height - margin, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
/* Add SVG */
var svg = d3
.select("#chart")
.append("svg")
.attr("width", width + margin + "px")
.attr("height", height + margin + "px")
.append("g")
.attr("transform", `translate(${margin}, ${margin})`);
/* Add line into SVG */
var line = d3
.line()
.defined(d => !isNaN(d.price)) // to make it deal with NaNs
.x((d) => xScale(d.date))
.y((d) => yScale(d.price));
let lines = svg.append("g").attr("class", "lines");
let lineGroups = lines
.selectAll(".line-group")
.data(data)
.enter()
.append("g")
.attr("class", "line-group")
.on("mouseover", function(d, i) {
svg
.append("text")
.attr("class", "title-text")
.style("fill", color(i))
.text(d.name)
.attr("text-anchor", "middle")
.attr("x", (width - margin) / 2)
.attr("y", 5)
.attr("r", 5);
})
.on("mouseout", function(d) {
svg.select(".title-text").remove();
});
function getDashedParts(values, lookingForDashed) {
const results = [];
let previousWasMatch = false;
// For every node we add, we need also the node just before,
// so we can draw a line between them
values.forEach((v, i) => {
if(lookingForDashed === !!v.isDashed) {
if (!previousWasMatch && i > 0) {
results.push(values[i - 1]);
}
results.push(v);
previousWasMatch = true;
} else {
results.push({ date: v.date, price: NaN });
previousWasMatch = false;
}
});
// console.log(results);
return results;
}
lineGroups
.append("path")
.attr("class", "line")
.attr("d", (d) => line(getDashedParts(d.values, false)));
lineGroups
.append("path")
.attr("class", "line line-dashed")
.attr("d", (d) => line(getDashedParts(d.values, true)));
lineGroups.each(function(d, i) {
const lineParts = d3.select(this).selectAll("path");
lineParts
.style("stroke", color(i))
.style("opacity", lineOpacity)
.on("mouseover", () => {
d3.selectAll(".line").style("opacity", otherLinesOpacityHover);
d3.selectAll(".circle").style("opacity", circleOpacityOnLineHover);
lineParts
.style("opacity", lineOpacityHover)
.style("stroke-width", lineStrokeHover)
.style("cursor", "pointer");
})
.on("mouseout", () => {
d3.selectAll(".line").style("opacity", lineOpacity);
d3.selectAll(".circle").style("opacity", circleOpacity);
lineParts
.style("stroke-width", lineStroke)
.style("cursor", "none");
});
});
/* Add circles in the line */
lines
.selectAll("circle-group")
.data(data)
.enter()
.append("g")
.style("fill", (d, i) => color(i))
.selectAll("circle")
.data((d) => d.values)
.enter()
.append("g")
.attr("class", "circle")
.on("mouseover", function(d) {
d3.select(this)
.style("cursor", "pointer")
.append("text")
.attr("class", "text")
.text(`${d.price}`)
.attr("x", (d) => xScale(d.date) + 5)
.attr("y", (d) => yScale(d.price) - 10);
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.transition()
.duration(duration)
.selectAll(".text")
.remove();
})
.append("circle")
.attr("cx", (d) => xScale(d.date))
.attr("cy", (d) => yScale(d.price))
.attr("r", circleRadius)
.style("opacity", circleOpacity)
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(duration)
.attr("r", circleRadiusHover);
})
.on("mouseout", function(d) {
d3.select(this).transition().duration(duration).attr("r", circleRadius);
});
/* Add Axis into SVG */
var xAxis = d3.axisBottom(xScale).ticks(5);
var yAxis = d3.axisLeft(yScale).ticks(5);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height - margin})`)
.call(xAxis);
svg
.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("y", 15)
.attr("transform", "rotate(-90)")
.attr("fill", "#000")
.text("Total values");
.App {
font-family: sans-serif;
text-align: center;
}
svg {
font-family: Sans-Serif, Arial;
}
.line {
stroke-width: 2;
fill: none;
}
.line-dashed {
stroke-dasharray: 5,5;
}
.axis path {
stroke: black;
}
.text {
font-size: 12px;
}
.title-text {
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<div id="chart" />
</div>
我做了什么:
- 我将
isDefined()
添加到您对d3.line()
的定义中,以便它可以处理 NaN; - 我用 isDashed true 或 false 过滤了每个数组的值。当我找到一个匹配的元素而前一个不匹配时,我将这两个元素都添加到结果集中。这样,它们之间就画了一条线,就不会出现空隙了;
- 我稍微更改了鼠标悬停行为,这样如果将鼠标悬停在虚线和实线段上,它们就会同时突出显示。