New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request] spanGaps with different style, eg. dashed line #5956
Comments
I don't believe there is any support for this at the moment. Line Gap Dash Image If that's what's being looked for then I'd like members to suggest what the options for the line would be in this case. This would be a setting in the main options. A thought:
The way I did this was to move the logic within element.line.js draw function into the loop so that I could modify each section as need be. |
@Madrussian still not sure how I would implement it but an idea come to my mind: I would allow the user to configure every line segment via scriptable options (not fully implemented yet). I think it's a much better approach because it would provide a generic and flexible solution instead of a rigid set of new options. For example (not tested but this is the idea): function hasGap(ctx) {
var idx = ctx.dataset.data[ctx.dataIndex]
var d0 = ctx.dataset.data[idx]
var d1 = ctx.dataset.data[idx + 1]
return d0 === null || d1 === null;
}
datasets: [{
// draw gap line dashed and in red
borderColor: function(ctx) {
return hasGap(ctx) ? 'red' : 'green';
},
borderDash: function(ctx) {
return hasGap(ctx) ? [1, 4] : false;
}
}] And a totally different use case: datasets: [{
// draw increasing line in green, decreasing in red
borderColor: function(ctx) {
var idx = ctx.dataset.data[ctx.dataIndex]
var d0 = ctx.dataset.data[idx]
var d1 = ctx.dataset.data[idx + 1]
return d0 < d1 ? 'red' : 'green';
}
}] |
I am missing this feature right now as well, I am able to provide two datasets with the calculated value for the missing point. But it is an overhead that would not be needed if I just can describe what segment should have different style. The proposed callback function from @simonbrunel make most sense to me. Also the user might want to have different borderWidth, pointBackgroundColor and so on, but for now the borderDash callback would make my day. |
I was more thinking about this and I might have a bit better proposal for the interface than @simonbrunel proposed. And right now I am pretty sure that this behaviour might be needed and useful also for the point styles. var points = [10, 30, null, null, null, null, 10, null, 15, 25]
// Since the line is defined by two points the developer will be more interested into the interface like this.
// From my point it make the developer life easier that he does not care about all the undefined checks.
function hasGap(lineSourceIndex, lineDestinationIndex, ctx) {
return hasMissingValue(lineSourceIndex) || hasMissingValue(lineDestinationIndex) === null;
}
function hasMissingValue(datasetIndex) {
return points[datasetIndex] === null;
}
datasets: [{
data: points,
borderColor: function(lineSourceIndex, lineDestinationIndex, ctx) {
return hasGap(lineSourceIndex, lineDestinationIndex, ctx) ? 'red' : 'green';
},
borderDash: function(lineSourceIndex, lineDestinationIndex, ctx) {
return hasGap(lineSourceIndex, lineDestinationIndex, ctx) ? [1, 4] : false;
},
backgroundColor: function(lineSourceIndex, lineDestinationIndex, ctx) {
return hasGap(lineSourceIndex, lineDestinationIndex, ctx) ? 'red' : 'green';
},
borderWidth: function(lineSourceIndex, lineDestinationIndex, ctx) {
return hasGap(lineSourceIndex, lineDestinationIndex, ctx) ? 3 : 1;
},
pointRadius: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 3 : 1;
},
pointBorderWidth: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 1 : 2;
},
pointHoverBorderWidth: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 0 : 2;
},
pointHoverRadius: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 0 : 2;
},
pointHoverRadius: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 0 : 15;
},
pointBackgroundColor: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 'red' : 'green';
},
pointHoverBackgroundColor: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 'red' : 'green';
},
pointHoverBorderColor: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 'white' : 'black';
},
pointBorderColor: function(datasetIndex, ctx) {
return hasMissingValue(datasetIndex) ? 'white' : 'black';
},
}] I am not 100% sure that I did not miss a some option, but those the options I am using right now. |
@simonbrunel scriptable options is what I thought of first. Right now, #5973 has the start of scriptable options for line charts. Perhaps @Madrussian you could fork from that branch and implement it for the line dash settings. |
@etimberg Sure, although as discussed in chat with @simonbrunel the PR will wait until #5973 is merged since its a requirement. |
Thanks for the effort. Looking forward to use it, so far I have graph like this image link and there was needed not complex but pretty long algorithm to get it work for all possible values. |
@Madrussian Just looking into the codepen now, would be hard to make the lines curved based on tension? Also it would be nice if there would be an option to fill the missing points with interpolated value so you can hover on top of those. |
Just an idea, why not use spanGaps to define dash (or color, line thickness, complete alternate border definition)? Unless something more complex - eh, universal - is needed ;) Example (dummy data): https://pasteboard.co/HYKmwH1.png I'm not sure how well it works with tension, but at least it renders. I don't intend to use tension in my charts. |
Unfortunately I have not had time to work on this. If someone wants to take over this or has already started then go for it, I'm not sure when I'll have availability at the present moment. What I have for this is linked below. Since its not complete and there are tests failing I did not create a PR for it. |
Any luck to have it in near feature? |
Here's a great implementation that might work for others: And here's an implementation of this for Chart.js v1 that I found on this answer. And here's the code: Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
var strokeColors = [];
data.datasets.forEach(function (dataset, i) {
if (dataset.dottedFromLabel) {
strokeColors.push(dataset.strokeColor);
dataset.strokeColor = "rgba(0,0,0,0)"
}
})
Chart.types.Line.prototype.initialize.apply(this, arguments);
var self = this;
data.datasets.forEach(function (dataset, i) {
if (dataset.dottedFromLabel) {
self.datasets[i].dottedFromIndex = data.labels.indexOf(dataset.dottedFromLabel) + 1;
self.datasets[i]._saved = {
strokeColor: strokeColors.shift()
}
}
})
},
draw: function () {
Chart.types.Line.prototype.draw.apply(this, arguments);
// from Chart.js library code
var hasValue = function (item) {
return item.value !== null;
},
nextPoint = function (point, collection, index) {
return Chart.helpers.findNextWhere(collection, hasValue, index) || point;
},
previousPoint = function (point, collection, index) {
return Chart.helpers.findPreviousWhere(collection, hasValue, index) || point;
};
var ctx = this.chart.ctx;
var self = this;
ctx.save();
this.datasets.forEach(function (dataset) {
if (dataset.dottedFromIndex) {
ctx.lineWidth = self.options.datasetStrokeWidth;
ctx.strokeStyle = dataset._saved.strokeColor;
// adapted from Chart.js library code
var pointsWithValues = Chart.helpers.where(dataset.points, hasValue);
Chart.helpers.each(pointsWithValues, function (point, index) {
if (index >= dataset.dottedFromIndex)
ctx.setLineDash([3, 3]);
else
ctx.setLineDash([]);
if (index === 0) {
ctx.moveTo(point.x, point.y);
}
else {
if (self.options.bezierCurve) {
var previous = previousPoint(point, pointsWithValues, index);
ctx.bezierCurveTo(
previous.controlPoints.outer.x,
previous.controlPoints.outer.y,
point.controlPoints.inner.x,
point.controlPoints.inner.y,
point.x,
point.y
);
}
else {
ctx.lineTo(point.x, point.y);
}
}
ctx.stroke();
}, this);
}
})
ctx.restore();
}
}); Unfortunately it doesn't seem to be working for the latest version. I've seen other suggesting using duplicated datasets with empty values except the last one, but that's far from an acceptable solution, specially when having multiple lines in a chart and dynamically dealing with them. Any chance of making this code work for the latest version? |
If we don't merge anything on some day, graphs are becoming rather ugly. With this option lines are drawn between points. No point is visually drawn in the missing day and also we indicate in label that there was no run. It would be nice if chartjs/Chart.js#5956 would be implemented, but in the meantime I guess we can just use this. (Other option is to create for each graph two data sets, but that is rather busy work)
If we don't merge anything on some day, graphs are becoming rather ugly. With this option lines are drawn between points. No point is visually drawn in the missing day and also we indicate in label that there was no run. It would be nice if chartjs/Chart.js#5956 would be implemented, but in the meantime I guess we can just use this. (Other option is to create for each graph two data sets, but that is rather busy work)
I'm looking to replace a home brewed charting solution in a PHP based web app but really, really need to span gaps in line charts with a dotted or dashed line. I want to keep the line running over gaps to show the trend but change its style to make it obvious there is a gap.
I thought I might be able to do what I want by duplicating the data series with the gaps - display it once with a solid line and spanGaps: false and again dashed with spanGaps: true but then the two interpolate differently - I'd like to use monotone interpolation.
Any ideas? I've seen SO questions about this but no answers.
The text was updated successfully, but these errors were encountered: