Skip to content

Commit

Permalink
Enhance chartjs-plugin-annotation support
Browse files Browse the repository at this point in the history
  • Loading branch information
nagix committed May 10, 2021
1 parent 308f603 commit 0792037
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 99 deletions.
13 changes: 11 additions & 2 deletions samples/annotation.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,21 @@
}

function onRefresh(chart) {
var box = chart.options.annotation.annotations[1];
var now = Date.now();
chart.data.datasets.forEach(function(dataset) {
dataset.data.push({
x: now,
y: randomScalingFactor()
});
});
if (!(box.xMax >= now - 22000)) {
box.xMin = now - 2000;
box.xMax = now + 8000;
box.yMin = randomScalingFactor();
box.yMax = randomScalingFactor();
chart.update({duration: 0});
}
}

var color = Chart.helpers.color;
Expand Down Expand Up @@ -136,9 +144,10 @@
{
drawTime: 'beforeDatasetsDraw',
type: 'box',
xScaleID: 'x-axis-0',
yScaleID: 'y-axis-0',
yMin: randomScalingFactor(),
yMax: randomScalingFactor(),
xMin: 0,
xMax: 0,
backgroundColor: color(chartColors.purple).alpha(0.25).rgbString(),
borderColor: chartColors.purple,
borderWidth: 1,
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/helpers.streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ var cancelAnimFrame = (function() {

export default {

resolveOption(scale, key) {
var realtimeOpts = scale.options.realtime;
var streamingOpts = scale.chart.options.plugins.streaming;
return helpers.valueOrDefault(realtimeOpts[key], streamingOpts[key]);
},

startFrameRefreshTimer: function(context, func) {
if (!context.frameRequestID) {
var frameRefresh = function() {
Expand Down
119 changes: 119 additions & 0 deletions src/plugins/plugin.annotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Chart from 'chart.js';

function isValid(rawValue) {
if (rawValue === null || typeof rawValue === 'undefined') {
return false;
} else if (typeof rawValue === 'number') {
return isFinite(rawValue);
}
return !!rawValue;
}

function scaleValue(scale, value, fallback) {
return isValid(value) ?
{value: scale.getPixelForValue(value), transitionable: true} :
{value: fallback};
}

function updateBoxAnnotation(element) {
var chart = element.chartInstance;
var options = element.options;
var scales = chart.scales;
var chartArea = chart.chartArea;
var xScaleID = options.xScaleID;
var yScaleID = options.yScaleID;
var xScale = scales[xScaleID];
var yScale = scales[yScaleID];
var streaming = element._streaming = {};
var min, max, reverse;

if (xScale) {
min = scaleValue(xScale, options.xMin, chartArea.left);
max = scaleValue(xScale, options.xMax, chartArea.right);
reverse = min.value > max.value;

if (min.transitionable) {
streaming[reverse ? 'right' : 'left'] = {axisId: xScaleID};
}
if (max.transitionable) {
streaming[reverse ? 'left' : 'right'] = {axisId: xScaleID};
}
}

if (yScale) {
min = scaleValue(yScale, options.yMin, chartArea.top);
max = scaleValue(yScale, options.yMax, chartArea.bottom);
reverse = min.value > max.value;

if (min.transitionable) {
streaming[reverse ? 'bottom' : 'top'] = {axisId: yScaleID};
}
if (max.transitionable) {
streaming[reverse ? 'top' : 'bottom'] = {axisId: yScaleID};
}
}
}

function updateLineAnnotation(element) {
var chart = element.chartInstance;
var options = element.options;
var scaleID = options.scaleID;
var value = options.value;
var scale = chart.scales[scaleID];
var streaming = element._streaming = {};

if (scale) {
var isHorizontal = scale.isHorizontal();
var pixel = scaleValue(scale, value);

if (pixel.transitionable) {
streaming[isHorizontal ? 'x1' : 'y1'] = {axisId: scaleID};
streaming[isHorizontal ? 'x2' : 'y2'] = {axisId: scaleID};
streaming[isHorizontal ? 'labelX' : 'labelY'] = {axisId: scaleID};
}
}
}

function initAnnotationPlugin() {
var BoxAnnotation = Chart.Annotation.types.box;
var LineAnnotation = Chart.Annotation.types.line;
var configureBoxAnnotation = BoxAnnotation.prototype.configure;
var configureLineAnnotation = LineAnnotation.prototype.configure;

BoxAnnotation.prototype.configure = function() {
updateBoxAnnotation(this);
return configureBoxAnnotation.call(this);
};

LineAnnotation.prototype.configure = function() {
updateLineAnnotation(this);
return configureLineAnnotation.call(this);
};
}

export default {
attachChart(chart) {
var streaming = chart.streaming;

if (!streaming.annotationPlugin) {
initAnnotationPlugin();
streaming.annotationPlugin = true;
}
},

getElements(chart) {
var annotation = chart.annotation;

if (annotation) {
var elements = annotation.elements;
return Object.keys(elements).map(function(id) {
return elements[id];
});
}
return [];
},

detachChart(chart) {
delete chart.streaming.annotationPlugin;
}
};
102 changes: 94 additions & 8 deletions src/plugins/plugin.streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import Chart from 'chart.js';
import streamingHelpers from '../helpers/helpers.streaming';
import AnnotationPlugin from '../plugins/plugin.annotation';
import ZoomPlugin from '../plugins/plugin.zoom';
import RealTimeScale from '../scales/scale.realtime';

var helpers = Chart.helpers;
Expand All @@ -17,20 +19,70 @@ Chart.defaults.global.plugins.streaming = {
ttl: undefined
};

// Ported from Chart.js 2.9.4 d6a5ea0. Modified for realtime scale.
Chart.defaults.global.legend.onClick = function(e, legendItem) {
var index = legendItem.datasetIndex;
var ci = this.chart;
var meta = ci.getDatasetMeta(index);

meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
ci.update({duration: 0});
};

function getAxisMap(element, keys, meta) {
var axisMap = {};

helpers.each(keys.x, function(key) {
axisMap[key] = {axisId: meta.xAxisID};
});
helpers.each(keys.y, function(key) {
axisMap[key] = {axisId: meta.yAxisID};
});
return axisMap;
}

var transitionKeys = {
x: ['x', 'controlPointPreviousX', 'controlPointNextX', 'caretX'],
y: ['y', 'controlPointPreviousY', 'controlPointNextY', 'caretY']
};

function updateElements(chart) {
helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
var meta = chart.getDatasetMeta(datasetIndex);
var elements = meta.data || [];
var element = meta.dataset;
var i, ilen;

for (i = 0, ilen = elements.length; i < ilen; ++i) {
elements[i]._streaming = getAxisMap(elements[i], transitionKeys, meta);
}
if (element) {
element._streaming = getAxisMap(element, transitionKeys, meta);
}
});
}

/**
* Update the chart keeping the current animation but suppressing a new one
* @param {object} config - animation options
*/
function update(config) {
var me = this;
var preservation = config && config.preservation;
var tooltip, lastActive, tooltipLastActive, lastMouseEvent;
var tooltip, lastActive, tooltipLastActive, lastMouseEvent, legend, legendUpdate;

if (preservation) {
tooltip = me.tooltip;
lastActive = me.lastActive;
tooltipLastActive = tooltip._lastActive;
me._bufferedRender = true;
legend = me.legend;

// Skip legend update
if (legend) {
legendUpdate = legend.update;
legend.update = helpers.noop;
}
}

Chart.prototype.update.apply(me, arguments);
Expand All @@ -41,6 +93,10 @@ function update(config) {
me.lastActive = lastActive;
tooltip._lastActive = tooltipLastActive;

if (legend) {
legend.update = legendUpdate;
}

if (me.animating) {
// If the chart is animating, keep it until the duration is over
Chart.animationService.animations.forEach(function(animation) {
Expand Down Expand Up @@ -68,6 +124,21 @@ function update(config) {
}
}

function tooltipUpdate() {
var me = this;
var element = me._active && me._active[0];
var meta;

if (element) {
meta = me._chart.getDatasetMeta(element._datasetIndex);
me._streaming = getAxisMap(me, transitionKeys, meta);
} else {
me._streaming = {};
}

return Chart.Tooltip.prototype.update.apply(me, arguments);
}

// Draw chart at frameRate
function drawChart(chart) {
var streaming = chart.streaming;
Expand Down Expand Up @@ -112,35 +183,47 @@ export default {

afterInit: function(chart) {
chart.update = update;

if (chart.resetZoom) {
Chart.Zoom.updateResetZoom(chart);
}
chart.tooltip.update = tooltipUpdate;
},

beforeUpdate: function(chart) {
var chartOpts = chart.options;
var scalesOpts = chartOpts.scales;

if (scalesOpts) {
scalesOpts.xAxes.concat(scalesOpts.yAxes).forEach(function(scaleOpts) {
helpers.each(scalesOpts.xAxes.concat(scalesOpts.yAxes), function(scaleOpts) {
if (scaleOpts.type === 'realtime' || scaleOpts.type === 'time') {
// Allow Bézier control to be outside the chart
chartOpts.elements.line.capBezierPoints = false;
}
});
}

if (chart.annotation) {
AnnotationPlugin.attachChart(chart);
} else {
AnnotationPlugin.detachChart(chart);
}

if (chart.resetZoom) {
ZoomPlugin.attachChart(chart);
} else {
ZoomPlugin.detachChart(chart);
}

return true;
},

afterUpdate: function(chart, options) {
afterUpdate: function(chart) {
var streaming = chart.streaming;
var pause = true;

updateElements(chart);

// if all scales are paused, stop refreshing frames
helpers.each(chart.scales, function(scale) {
if (scale instanceof RealTimeScale) {
pause &= helpers.valueOrDefault(scale.options.realtime.pause, options.pause);
pause &= streamingHelpers.resolveOption(scale, 'pause');
}
});
if (pause) {
Expand Down Expand Up @@ -197,6 +280,9 @@ export default {

streamingHelpers.stopFrameRefreshTimer(streaming);

delete chart.update;
delete chart.tooltip.update;

canvas.removeEventListener('mousedown', mouseEventListener);
canvas.removeEventListener('mouseup', mouseEventListener);

Expand Down
Loading

0 comments on commit 0792037

Please sign in to comment.