]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Implement routing of defaults (#7453)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Mon, 8 Jun 2020 21:49:17 +0000 (00:49 +0300)
committerGitHub <noreply@github.com>
Mon, 8 Jun 2020 21:49:17 +0000 (17:49 -0400)
Implement routing of defaults

docs/docs/configuration/elements.md
src/core/core.defaults.js
src/elements/element.arc.js
src/elements/element.line.js
src/elements/element.point.js
src/elements/element.rectangle.js
test/specs/core.datasetController.tests.js

index ccd50c090f06dd162152d6ebfcfbc64f8089122d..d07213813ed85f30d83f37206fce40da6f5340a4 100644 (file)
@@ -21,11 +21,11 @@ Global point options: `Chart.defaults.elements.point`.
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
 | `radius` | `number` | `3` | Point radius.
-| [`pointStyle`](#point-styles) | <code>string&#124;Image</code> | `'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` | <code>boolean&#124;string</code> | `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.
index caacea7292c04aba5e804271446812f260e146c4..652ee34c55531d7093e4e4268beaec774bdb58c6 100644 (file)
@@ -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;
+                                       }
+                               }
+                       });
+               });
        }
 }
 
index 3c0ee72c9308c257e6e54d1195b5fb12bc038572..243524468989043a5532b2a0c97eef164186203c 100644 (file)
@@ -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;
index 76c72e685ff8442593e0184cd98c96ae45c0a6e0..9e45e22d54b7e8702bf16c58ef5e216f82c0fbbc 100644 (file)
@@ -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);
index ec2468eb4f8727ba1eee36f1a5d74893f21a966e..93e4948bbc52a18d831c0ba00168167c6bdb474b 100644 (file)
@@ -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) {
index 739de83e1319c2e76c8c99bc624215ffafef5f61..94d338ea0ec074aec742d65a0fd3f5678597f46f 100644 (file)
@@ -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
index 4c21f1a0a9627b54fd6ea0058adcf32f20c8a574..0e2d83a5f70fb4fe719b90a09a7f75a386ab5b05 100644 (file)
@@ -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;
+       });
 });