From: Jukka Kurkela Date: Mon, 8 Jun 2020 21:49:17 +0000 (+0300) Subject: Implement routing of defaults (#7453) X-Git-Tag: v3.0.0-beta.2~100 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f3cfeb84205385655e8d492f3e6ceb4c24026874;p=thirdparty%2FChart.js.git Implement routing of defaults (#7453) Implement routing of defaults --- diff --git a/docs/docs/configuration/elements.md b/docs/docs/configuration/elements.md index ccd50c090..d07213813 100644 --- a/docs/docs/configuration/elements.md +++ b/docs/docs/configuration/elements.md @@ -21,11 +21,11 @@ Global point options: `Chart.defaults.elements.point`. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- | `radius` | `number` | `3` | Point radius. -| [`pointStyle`](#point-styles) | string|Image | `'circle'` | Point style. +| [`pointStyle`](#point-styles) | `string`\|`Image` | `'circle'` | Point style. | `rotation` | `number` | `0` | Point rotation (in degrees). -| `backgroundColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Point fill color. +| `backgroundColor` | `Color` | `Chart.defaults.color` | Point fill color. | `borderWidth` | `number` | `1` | Point stroke width. -| `borderColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Point stroke color. +| `borderColor` | `Color` | `Chart.defaults.color` | Point stroke color. | `hitRadius` | `number` | `1` | Extra radius added to point radius for hit detection. | `hoverRadius` | `number` | `4` | Point radius when hovered. | `hoverBorderWidth` | `number` | `1` | Stroke width when hovered. @@ -56,16 +56,16 @@ Global line options: `Chart.defaults.elements.line`. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- | `tension` | `number` | `0.4` | Bézier curve tension (`0` for no Bézier curves). -| `backgroundColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Line fill color. +| `backgroundColor` | `Color` | `Chart.defaults.color` | Line fill color. | `borderWidth` | `number` | `3` | Line stroke width. -| `borderColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Line stroke color. +| `borderColor` | `Color` | `Chart.defaults.color` | Line stroke color. | `borderCapStyle` | `string` | `'butt'` | Line cap style. See [MDN](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap). | `borderDash` | `number[]` | `[]` | Line dash. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). | `borderDashOffset` | `number` | `0.0` | Line dash offset. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). | `borderJoinStyle` | `string` | `'miter'` | Line join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). | `capBezierPoints` | `boolean` | `true` | `true` to keep Bézier control inside the chart, `false` for no restriction. | `cubicInterpolationMode` | `string` | `'default'` | Interpolation mode to apply. [See more...](../charts/line.md#cubicinterpolationmode) -| `fill` | boolean|string | `true` | How to fill the area under the line. See [area charts](../charts/area.md#filling-modes). +| `fill` | `boolean`\|`string` | `true` | How to fill the area under the line. See [area charts](../charts/area.md#filling-modes). | `stepped` | `boolean` | `false` | `true` to show the line as a stepped line (`tension` will be ignored). ## Rectangle Configuration @@ -76,9 +76,9 @@ Global rectangle options: `Chart.defaults.elements.rectangle`. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- -| `backgroundColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Bar fill color. +| `backgroundColor` | `Color` | `Chart.defaults.color` | Bar fill color. | `borderWidth` | `number` | `0` | Bar stroke width. -| `borderColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Bar stroke color. +| `borderColor` | `Color` | `Chart.defaults.color` | Bar stroke color. | `borderSkipped` | `string` | `'bottom'` | Skipped (excluded) border: `'bottom'`, `'left'`, `'top'` or `'right'`. ## Arc Configuration @@ -90,7 +90,7 @@ Global arc options: `Chart.defaults.elements.arc`. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- | `angle` - for polar only | `number` | `circumference / (arc count)` | Arc angle to cover. -| `backgroundColor` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Arc fill color. +| `backgroundColor` | `Color` | `Chart.defaults.color` | Arc fill color. | `borderAlign` | `string` | `'center'` | Arc stroke alignment. | `borderColor` | `Color` | `'#fff'` | Arc stroke color. | `borderWidth`| `number` | `2` | Arc stroke width. diff --git a/src/core/core.defaults.js b/src/core/core.defaults.js index caacea729..652ee34c5 100644 --- a/src/core/core.defaults.js +++ b/src/core/core.defaults.js @@ -1,7 +1,25 @@ -import {merge} from '../helpers/helpers.core'; +import {merge, isArray, valueOrDefault} from '../helpers/helpers.core'; + +/** + * @param {object} node + * @param {string} key + * @return {object} + */ +function getScope(node, key) { + if (!key) { + return node; + } + const keys = key.split('.'); + for (let i = 0, n = keys.length; i < n; ++i) { + const k = keys[i]; + node = node[k] || (node[k] = {}); + } + return node; +} /** * Please use the module's default export which provides a singleton instance + * Note: class is exported for typedoc */ export class Defaults { constructor() { @@ -39,13 +57,63 @@ export class Defaults { this.title = undefined; this.tooltips = undefined; this.doughnut = undefined; + this._routes = {}; } /** * @param {string} scope * @param {*} values */ set(scope, values) { - return merge(this[scope] || (this[scope] = {}), values); + return merge(getScope(this, scope), values); + } + + /** + * Routes the named defaults to fallback to another scope/name. + * This routing is useful when those target values, like defaults.color, are changed runtime. + * If the values would be copied, the runtime change would not take effect. By routing, the + * fallback is evaluated at each access, so its always up to date. + * + * Examples: + * + * defaults.route('elements.arc', 'backgroundColor', '', 'color') + * - reads the backgroundColor from defaults.color when undefined locally + * + * defaults.route('elements.line', ['backgroundColor', 'borderColor'], '', 'color') + * - reads the backgroundColor and borderColor from defaults.color when undefined locally + * + * defaults.route('elements.customLine', ['borderWidth', 'tension'], 'elements.line', ['borderWidth', 'tension']) + * - reads the borderWidth and tension from elements.line when those are not defined in elements.customLine + * + * @param {string} scope Scope this route applies to. + * @param {string[]} names Names of the properties that should be routed to different namespace when not defined here. + * @param {string} targetScope The namespace where those properties should be routed to. Empty string ('') is the root of defaults. + * @param {string|string[]} targetNames The target name/names in the target scope the properties should be routed to. + */ + route(scope, names, targetScope, targetNames) { + const scopeObject = getScope(this, scope); + const targetScopeObject = getScope(this, targetScope); + const targetNamesIsArray = isArray(targetNames); + names.forEach((name, index) => { + const privateName = '_' + name; + const targetName = targetNamesIsArray ? targetNames[index] : targetNames; + Object.defineProperties(scopeObject, { + // A private property is defined to hold the actual value, when this property is set in its scope (set in the setter) + [privateName]: { + writable: true + }, + // The actual property is defined as getter/setter so we can do the routing when value is not locally set. + [name]: { + enumerable: true, + get() { + // @ts-ignore + return valueOrDefault(this[privateName], targetScopeObject[targetName]); + }, + set(value) { + this[privateName] = value; + } + } + }); + }); } } diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index 3c0ee72c9..243524468 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -3,15 +3,15 @@ import Element from '../core/core.element'; import {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math'; const TAU = Math.PI * 2; -defaults.set('elements', { - arc: { - backgroundColor: defaults.color, - borderAlign: 'center', - borderColor: '#fff', - borderWidth: 2 - } +const scope = 'elements.arc'; +defaults.set(scope, { + borderAlign: 'center', + borderColor: '#fff', + borderWidth: 2 }); +defaults.route(scope, ['backgroundColor'], '', ['color']); + function clipArc(ctx, model) { const {startAngle, endAngle, pixelMargin, x, y} = model; let angleMargin = pixelMargin / model.outerRadius; diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 76c72e685..9e45e22d5 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -9,23 +9,20 @@ import {_updateBezierControlPoints} from '../helpers/helpers.curve'; * @typedef { import("./element.point").default } Point */ -const defaultColor = defaults.color; - -defaults.set('elements', { - line: { - backgroundColor: defaultColor, - borderCapStyle: 'butt', - borderColor: defaultColor, - borderDash: [], - borderDashOffset: 0, - borderJoinStyle: 'miter', - borderWidth: 3, - capBezierPoints: true, - fill: true, - tension: 0.4 - } +const scope = 'elements.line'; +defaults.set(scope, { + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 3, + capBezierPoints: true, + fill: true, + tension: 0.4 }); +defaults.route(scope, ['backgroundColor', 'borderColor'], '', 'color'); + function setStyle(ctx, vm) { ctx.lineCap = vm.borderCapStyle; ctx.setLineDash(vm.borderDash); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index ec2468eb4..93e4948bb 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -2,21 +2,18 @@ import defaults from '../core/core.defaults'; import Element from '../core/core.element'; import {_isPointInArea, drawPoint} from '../helpers/helpers.canvas'; -const defaultColor = defaults.color; - -defaults.set('elements', { - point: { - backgroundColor: defaultColor, - borderColor: defaultColor, - borderWidth: 1, - hitRadius: 1, - hoverBorderWidth: 1, - hoverRadius: 4, - pointStyle: 'circle', - radius: 3 - } +const scope = 'elements.point'; +defaults.set(scope, { + borderWidth: 1, + hitRadius: 1, + hoverBorderWidth: 1, + hoverRadius: 4, + pointStyle: 'circle', + radius: 3 }); +defaults.route(scope, ['backgroundColor', 'borderColor'], '', 'color'); + class Point extends Element { constructor(cfg) { diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 739de83e1..94d338ea0 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -2,17 +2,14 @@ import defaults from '../core/core.defaults'; import Element from '../core/core.element'; import {isObject} from '../helpers/helpers.core'; -const defaultColor = defaults.color; - -defaults.set('elements', { - rectangle: { - backgroundColor: defaultColor, - borderColor: defaultColor, - borderSkipped: 'bottom', - borderWidth: 0 - } +const scope = 'elements.rectangle'; +defaults.set(scope, { + borderSkipped: 'bottom', + borderWidth: 0 }); +defaults.route(scope, ['backgroundColor', 'borderColor'], '', 'color'); + /** * Helper function to get the bounds of the bar regardless of the orientation * @param {Rectangle} bar the bar diff --git a/test/specs/core.datasetController.tests.js b/test/specs/core.datasetController.tests.js index 4c21f1a0a..0e2d83a5f 100644 --- a/test/specs/core.datasetController.tests.js +++ b/test/specs/core.datasetController.tests.js @@ -502,4 +502,25 @@ describe('Chart.DatasetController', function() { expect(data1[hook]).toBe(Array.prototype[hook]); }); }); + + it('should resolve data element options to the default color', function() { + var data0 = [0, 1, 2, 3, 4, 5]; + var oldColor = Chart.defaults.color; + Chart.defaults.color = 'red'; + var chart = acquireChart({ + type: 'line', + data: { + datasets: [{ + data: data0 + }] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.dataset.options.borderColor).toBe('red'); + expect(meta.data[0].options.borderColor).toBe('red'); + + // Reset old shared state + Chart.defaults.color = oldColor; + }); });