Skip to content

Commit

Permalink
Preserve scriptable context (#7981)
Browse files Browse the repository at this point in the history
* Preserve scriptable context
* CC, utilize `index` in tests
* Update example to utilize context
  • Loading branch information
kurkle committed Nov 1, 2020
1 parent 72dc375 commit 23bf7c0
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 34 deletions.
44 changes: 41 additions & 3 deletions docs/docs/general/options.md
Expand Up @@ -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
- `index`: getter for `datasetIndex`

### 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)

**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.
- `tick`: the associated tick object
- `index`: tick index
5 changes: 2 additions & 3 deletions samples/animations/delay.html
Expand Up @@ -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,
Expand All @@ -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',
Expand Down
9 changes: 9 additions & 0 deletions src/core/core.controller.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
71 changes: 63 additions & 8 deletions src/core/core.datasetController.js
Expand Up @@ -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';
Expand Down Expand Up @@ -177,6 +220,7 @@ export default class DatasetController {
this._drawStart = undefined;
this._drawCount = undefined;
this.enableOptionSharing = false;
this.$context = undefined;

this.initialize();
}
Expand Down Expand Up @@ -491,6 +535,13 @@ export default class DatasetController {
return this._cachedMeta._parsed[index];
}

/**
* @protected
*/
getDataElement(index) {
return this._cachedMeta.data[index];
}

/**
* @protected
*/
Expand Down Expand Up @@ -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;
}

/**
Expand Down
39 changes: 31 additions & 8 deletions src/core/core.scale.js
Expand Up @@ -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', {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -345,6 +364,7 @@ export default class Scale extends Element {
this._ticksLength = 0;
this._borderValue = 0;
this._cache = {};
this.$context = undefined;
}

/**
Expand Down Expand Up @@ -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));
}

/**
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/controller.line/backgroundColor/scriptable.js
Expand Up @@ -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';
Expand All @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/controller.line/borderColor/scriptable.js
Expand Up @@ -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';
Expand All @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/controller.line/borderWidth/scriptable.js
Expand Up @@ -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'
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/controller.radar/backgroundColor/scriptable.js
Expand Up @@ -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';
Expand All @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/controller.radar/borderColor/scriptable.js
Expand Up @@ -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';
Expand All @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/controller.radar/borderWidth/scriptable.js
Expand Up @@ -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'
Expand All @@ -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
Expand Down

0 comments on commit 23bf7c0

Please sign in to comment.