Preserve scriptable context (#7981)

* Preserve scriptable context
* CC, utilize `index` in tests
* Update example to utilize context
This commit is contained in:
Jukka Kurkela 2020-11-01 14:39:08 +02:00 committed by GitHub
parent 72dc37581c
commit 23bf7c0c89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 159 additions and 35 deletions

View File

@ -37,14 +37,52 @@ color: [
## Option Context
The option context is used to give contextual information when resolving options and currently only applies to [scriptable options](#scriptable-options).
The object is preserved, so it can be used to store and pass information between calls.
There are multiple levels of context objects:
- `chart`
- `dataset`
- `data`
- `scale`
- `tick`
Each level inherits its parent(s) and any contextual information stored in the parent is available through the child.
The context object contains the following properties:
### chart
- `chart`: the associated chart
- `dataPoint`: the parsed data values for the given `dataIndex` and `datasetIndex`
- `dataIndex`: index of the current data
### dataset
In addition to [chart](#chart)
- `active`: true if element is active (hovered)
- `dataset`: dataset at index `datasetIndex`
- `datasetIndex`: index of the current dataset
- `active`: true if element is active (hovered)
- `index`: getter for `datasetIndex`
**Important**: since the context can represent different types of entities (dataset, data, ticks, etc.), some properties may be `undefined` so be sure to test any context property before using it.
### data
In addition to [dataset](#dataset)
- `active`: true if element is active (hovered)
- `dataIndex`: index of the current data
- `dataPoint`: the parsed data values for the given `dataIndex` and `datasetIndex`
- `element`: the element (point, arc, bar, etc.) for this data
- `index`: getter for `dataIndex`
### scale
In addition to [chart](#chart)
- `scale`: the associated scale
### tick
In addition to [scale](#scale)
- `tick`: the associated tick object
- `index`: tick index

View File

@ -63,7 +63,6 @@
};
window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
var started = {};
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
@ -77,9 +76,9 @@
var delay = 0;
var dsIndex = context.datasetIndex;
var index = context.dataIndex;
if (!started[index + dsIndex * 1000]) {
if (context.dataPoint && !context.delayed) {
delay = index * 300 + dsIndex * 100;
started[index + dsIndex * 1000] = true;
context.delayed = true;
}
return {
easing: 'linear',

View File

@ -116,6 +116,7 @@ class Chart {
this._hiddenIndices = {};
this.attached = false;
this._animationsDisabled = undefined;
this.$context = undefined;
// Add the chart instance to the global namespace
Chart.instances[me.id] = me;
@ -712,6 +713,14 @@ class Chart {
return meta;
}
getContext() {
return this.$context || (this.$context = Object.create(null, {
chart: {
value: this
}
}));
}
getVisibleDatasetCount() {
return this.getSortedVisibleDatasetMetas().length;
}

View File

@ -148,6 +148,49 @@ function getFirstScaleId(chart, axis) {
return Object.keys(scales).filter(key => scales[key].axis === axis).shift();
}
function createDatasetContext(parent, index, dataset) {
return Object.create(parent, {
active: {
writable: true,
value: false
},
dataset: {
value: dataset
},
datasetIndex: {
value: index
},
index: {
get() {
return this.datasetIndex;
}
}
});
}
function createDataContext(parent, index, point, element) {
return Object.create(parent, {
active: {
writable: true,
value: false
},
dataIndex: {
value: index
},
dataPoint: {
value: point
},
element: {
value: element
},
index: {
get() {
return this.dataIndex;
}
}
});
}
const optionKeys = (optionNames) => isArray(optionNames) ? optionNames : Object.keys(optionNames);
const optionKey = (key, active) => active ? 'hover' + _capitalize(key) : key;
const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
@ -177,6 +220,7 @@ export default class DatasetController {
this._drawStart = undefined;
this._drawCount = undefined;
this.enableOptionSharing = false;
this.$context = undefined;
this.initialize();
}
@ -491,6 +535,13 @@ export default class DatasetController {
return this._cachedMeta._parsed[index];
}
/**
* @protected
*/
getDataElement(index) {
return this._cachedMeta.data[index];
}
/**
* @protected
*/
@ -698,14 +749,18 @@ export default class DatasetController {
* @protected
*/
getContext(index, active) {
return {
chart: this.chart,
dataPoint: this.getParsed(index),
dataIndex: index,
dataset: this.getDataset(),
datasetIndex: this.index,
active
};
const me = this;
let context;
if (index >= 0 && index < me._cachedMeta.data.length) {
const element = me._cachedMeta.data[index];
context = element.$context ||
(element.$context = createDataContext(me.getContext(), index, me.getParsed(index), element));
} else {
context = me.$context || (me.$context = createDatasetContext(me.chart.getContext(), me.index, me.getDataset()));
}
context.active = !!active;
return context;
}
/**

View File

@ -8,7 +8,7 @@ import Ticks from './core.ticks';
/**
* @typedef { import("./core.controller").default } Chart
* @typedef {{value:any, label?:string, major?:boolean}} Tick
* @typedef {{value:any, label?:string, major?:boolean, $context?:any}} Tick
*/
defaults.set('scale', {
@ -269,6 +269,25 @@ function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
}
}
function createScaleContext(parent, scale) {
return Object.create(parent, {
scale: {
value: scale
},
});
}
function createTickContext(parent, index, tick) {
return Object.create(parent, {
tick: {
value: tick
},
index: {
value: index
}
});
}
export default class Scale extends Element {
// eslint-disable-next-line max-statements
@ -345,6 +364,7 @@ export default class Scale extends Element {
this._ticksLength = 0;
this._borderValue = 0;
this._cache = {};
this.$context = undefined;
}
/**
@ -1043,13 +1063,16 @@ export default class Scale extends Element {
* @protected
*/
getContext(index) {
const ticks = this.ticks || [];
return {
chart: this.chart,
scale: this,
tick: ticks[index],
index
};
const me = this;
const ticks = me.ticks || [];
if (index >= 0 && index < ticks.length) {
const tick = ticks[index];
return tick.$context ||
(tick.$context = createTickContext(me.getContext(), index, tick));
}
return me.$context ||
(me.$context = createScaleContext(me.chart.getContext(), me));
}
/**

View File

@ -8,7 +8,7 @@ module.exports = {
// option in dataset
data: [4, 5, 10, null, -10, -5],
backgroundColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#ff00ff';
@ -26,7 +26,7 @@ module.exports = {
elements: {
line: {
backgroundColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#ff00ff';

View File

@ -8,7 +8,7 @@ module.exports = {
// option in dataset
data: [4, 5, 10, null, -10, -5],
borderColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#0000ff';
@ -26,7 +26,7 @@ module.exports = {
elements: {
line: {
borderColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#0000ff';

View File

@ -9,7 +9,7 @@ module.exports = {
data: [4, 5, 10, null, -10, -5],
borderColor: '#0000ff',
borderWidth: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index % 2 ? 10 : 20;
},
pointBorderColor: '#00ff00'
@ -27,7 +27,7 @@ module.exports = {
line: {
borderColor: '#ff0000',
borderWidth: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index % 2 ? 10 : 20;
},
fill: false,

View File

@ -8,7 +8,7 @@ module.exports = {
// option in dataset
data: [0, 5, 10, null, -10, -5],
backgroundColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#ff00ff';
@ -26,7 +26,7 @@ module.exports = {
elements: {
line: {
backgroundColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#ff00ff';

View File

@ -8,7 +8,7 @@ module.exports = {
// option in dataset
data: [0, 5, 10, null, -10, -5],
borderColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#0000ff';
@ -26,7 +26,7 @@ module.exports = {
elements: {
line: {
borderColor: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index === 0 ? '#ff0000'
: index === 1 ? '#00ff00'
: '#0000ff';

View File

@ -9,7 +9,7 @@ module.exports = {
data: [0, 5, 10, null, -10, -5],
borderColor: '#0000ff',
borderWidth: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index % 2 ? 10 : 20;
},
pointBorderColor: '#00ff00'
@ -27,7 +27,7 @@ module.exports = {
line: {
borderColor: '#ff0000',
borderWidth: function(ctx) {
var index = (ctx.dataIndex === undefined ? ctx.datasetIndex : ctx.dataIndex);
var index = ctx.index;
return index % 2 ? 10 : 20;
},
fill: false