Reputation: 667
I am using two paths here to indicate the present and missing data. I am drawing the solid line path on top of the dotted line path to achieve this. If I pass the data unaltered to the dottedLinePath
it draws a continuous curved line, however, if I add .defined(x => x.value !== null
to it, the lines of the defined data become straight with no curve. How can I get the path with line.defined(x => x.value !== null)
to follow the same curve?
<div className="position-relative" style={{ width, height }}>
<svg className="position-absolute" width={width} height={height}>
<defs>
<linearGradient id={areaGradientId} x1={0} y1={0} x2={0} y2={1}>
<stop offset="0%" stopColor={color} stopOpacity={0.25} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
<g transform={`translate(${padding.left},${padding.top})`}>
<path d={area(data) || ''} fill={`url(#${areaGradientId})`} />
<path
d={dashedLine(data) || ''}
fill="none"
stroke={color}
strokeWidth={3}
strokeDasharray="6, 4"
/>
<path
d={line(data) || ''}
fill="none"
stroke={color}
strokeWidth={3}
/>
{data.map(d =>
!d.value ? (
<rect
width={6}
height={11}
key={Number(d.start_date)}
className="text-white"
x={x(d.start_date)}
y={y(d.value as number)}
fill={color}
stroke="currentColor"
strokeWidth={2}
/>
) : (
<circle
key={Number(d.start_date)}
className="text-white u-mb-16"
cx={x(d.start_date)}
cy={y(d.value)}
r={radius}
fill={color}
stroke="currentColor"
strokeWidth={2}
/>
),
)}
</g>
</svg>
Upvotes: 2
Views: 302
Reputation: 13129
It's a bit of a mouthful, but you can give the line a custom stroke-dasharray to draw gaps for missing data. I think the following is generic enough to use for your example as well.
const margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
const data = d3.range(10).map(i => ({
x: i,
y: Math.random()
}));
var x = d3.scaleLinear()
.domain([0, data.length - 1])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, 1])
.range([height, 0]);
var line = d3.line()
.x(d => x(d.x))
.y(d => y(d.y))
.curve(d3.curveMonotoneX);
function getLengthAtPoint(targetX, node, length) {
let result, currentX = node.getPointAtLength(length).x,
previousLength;
while (Math.abs(currentX - targetX) > 1) {
length += (targetX - currentX);
currentX = node.getPointAtLength(length).x;
}
return length;
}
function getStroke(data) {
// Get the total length
const totalLength = this.getTotalLength();
// We find all places where the next value is undefined
// And follow that until the first defined value
const isDefined = d => ![2, 5, 6, 7].includes(d.x);
let currentLength = 0,
defined = true,
strokeDasharray = "";
data.forEach((d, i) => {
if (!isDefined(d)) {
if (defined) {
// This is the first undefined piece, mark the previous point as
// the last one
defined = false;
const newLength = getLengthAtPoint(x(data[i - 1].x), this, currentLength);
// Add a dotted line until `newLength`
while (newLength - currentLength > 10) {
strokeDasharray += "5 5 ";
currentLength += 10;
}
// We have a few pixels left
if (newLength - currentLength > 5) {
// Add an interrupted dotted line
currentLength += 5;
strokeDasharray += "5 " + (newLength - currentLength) + " ";
} else {
strokeDasharray += (newLength - currentLength) + " 0 ";
}
currentLength = newLength;
}
} else {
if (!defined) {
// This is the first defined piece
defined = true;
const newLength = getLengthAtPoint(x(data[i - 1].x), this, currentLength);
// Add a gap until `newLength`
strokeDasharray += "0 " + (newLength - currentLength) + " ";
currentLength = newLength;
}
}
});
return strokeDasharray;
}
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y));
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line)
.attr("stroke-dasharray", getStroke);
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Upvotes: 1