Closed Fil closed 7 years ago
Great question! If you look at the implementation details of d3-line-chunked, you'll find that it draws two paths: typically one dashed and one solid. The only difference between the two is that the solid line also includes a clipping path that masks the solid lines over "undefined" regions. This strategy is nice because it works regardless of the line interpolation method (linear, monotone, etc) used.
In Vega, it is straightforward to similarly draw two lines: one dashed and and one solid. Using the defined
property for the solid line, you can also remove segments corresponding to missing data. For linear, and step-* interpolation this works nicely. However, for curved interpolation (cardinal, monotone, etc) you'll unfortunately get results where the two curves do not align. I've included a full spec below that demonstrates this approach.
So, if you are satisfied with non-curved interpolation you should be good to go. Otherwise, we will need to consider alternative approaches (perhaps including new transforms or mark implementations) to service the general case. Feel free to open a new, more targeted, feature request issue if so!
{
"$schema": "https://vega.github.io/schema/vega/v3.0.json",
"width": 400,
"height": 200,
"padding": 5,
"signals": [
{
"name": "interpolate", "value": "linear",
"bind": {
"input": "select",
"options": [
"basis",
"bundle",
"cardinal",
"catmull-rom",
"linear",
"monotone",
"natural",
"step",
"step-after",
"step-before"
]
}
}
],
"data": [
{
"name": "table",
"values": [
{"u": 1, "v": 28}, {"u": 2, "v": 55}, {"u": 3},
{"u": 4, "v": 34}, {"u": 5, "v": 36}, {"u": 6}, {"u": 7},
{"u": 8, "v": 48}, {"u": 9, "v": 52}, {"u": 10, "v": 49}
]
},
{
"name": "drop",
"source": "table",
"transform": [
{
"type": "filter",
"expr": "datum.v != null"
}
]
}
],
"scales": [
{
"name": "xscale",
"type": "linear",
"range": "width",
"zero": false,
"domain": {"data": "table", "field": "u"}
},
{
"name": "yscale",
"type": "linear",
"range": "height",
"nice": true,
"zero": false,
"domain": {"data": "table", "field": "v"}
}
],
"marks": [
{
"type": "line",
"from": {"data": "drop"},
"encode": {
"enter": {
"stroke": {"value": "#652c90"},
"strokeDash": {"value": [5, 5]},
"opacity": {"value": 0.5}
},
"update": {
"x": {"scale": "xscale", "field": "u"},
"y": {"scale": "yscale", "field": "v"},
"interpolate": {"signal": "interpolate"}
}
}
},
{
"type": "line",
"from": {"data": "table"},
"encode": {
"enter": {
"stroke": {"value": "#652c90"},
"opacity": {"value": 1}
},
"update": {
"x": {"scale": "xscale", "field": "u"},
"y": {"scale": "yscale", "field": "v"},
"defined": {"signal": "datum.v != null"},
"interpolate": {"signal": "interpolate"}
}
}
}
]
}
Not sure I understand your suggestion. Would the new issue be about "clipping paths" marks? It feels weird to rely on this astute but specific implementation rather than on the general description of the problem in a more declarative way.
There are possibly many ways to solve the problem. For instance, a different implementation for the same "descriptive problem" could be a (very targeted) dash-array. Or, linear lines that connect the endpoints around holes (we know where they are, even with curves).
I'll experiment a bit keep you posted :)
Here's my first attempt. I create a derived dataset "dataholes" with the following js code:
const dataholes = [];
let hole = -1;
let holeSeries = null;
let prevNumber = -1;
let prevNaN = -1;
data.forEach((d, i) => {
if (isNaN(d.value)) {
if (hole < 0 && prevNumber >= 0) {
hole = prevNumber;
holeSeries = d.series;
}
prevNaN = i;
} else {
if (hole >= 0 && prevNaN >= 0) {
if (d.series === holeSeries) {
dataholes.push(data[hole]);
dataholes.push(data[i]);
dataholes.push({ series: data[i].series, hole: true });
}
hole = -1;
}
prevNumber = i;
}
});
then I plot this dataset with a linear dashed "line"
object, where "defined"
is {"signal": "!datum.hole"}
.
It's a bit complex but I couldn't find how to do it with a proper "transform"
.
ANything on this issue? I have similar need. Hope attached file can help...
@jheer Wonderful example! I was wondering if this is able to be translated over to Vega-lite? I've been spinning my wheels trying to figure this out.
I'm doing a line chart where some data might be missing.
To avoid showing the missing data points I create a derivative data table:
To break the line where data is missing, I can use
encode.enter.defined = {"signal": "!isNaN(datum.value)"}
Now what I would want is to have a dashed line in between segments of the line, as in d3-line-chunked. Is this feasible with Vega (Vega 3)?
The d3-line-chunk plugin is not ported to Vega (see issue #15). But maybe there is a simple way to "connect the dots".