]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Radial scale scriptable options (#7471)
authorEvert Timberg <evert.timberg+github@gmail.com>
Thu, 11 Jun 2020 12:49:54 +0000 (08:49 -0400)
committerGitHub <noreply@github.com>
Thu, 11 Jun 2020 12:49:54 +0000 (08:49 -0400)
Scriptable radialLinear options

docs/docs/axes/radial/linear.md
docs/docs/getting-started/v3-migration.md
src/helpers/helpers.core.js
src/scales/scale.radialLinear.js
test/fixtures/scale.radialLinear/anglelines-indexable.js [new file with mode: 0644]
test/fixtures/scale.radialLinear/anglelines-indexable.png [new file with mode: 0644]
test/fixtures/scale.radialLinear/anglelines-scriptable.js [new file with mode: 0644]
test/fixtures/scale.radialLinear/anglelines-scriptable.png [new file with mode: 0644]
test/fixtures/scale.radialLinear/gridlines-scriptable.js [new file with mode: 0644]
test/fixtures/scale.radialLinear/gridlines-scriptable.png [new file with mode: 0644]
test/specs/helpers.core.tests.js

index eb7fdb10d2f4570208dc45523524a8a74d88faee..05aaee292cb46e287de0db6f760bdb9a98e972a2 100644 (file)
@@ -26,16 +26,27 @@ The axis has configuration properties for ticks, angle lines (line that appear i
 
 The following options are provided by the linear radial scale. They are all located in the `ticks` sub options. The [common tick configuration](../styling.md#tick-configuration) options are supported by this axis.
 
-| Name | Type | Default | Description
-| ---- | ---- | ------- | -----------
-| `backdropColor` | `Color` | `'rgba(255, 255, 255, 0.75)'` | Color of label backdrops.
-| `backdropPaddingX` | `number` | `2` | Horizontal padding of label backdrop.
-| `backdropPaddingY` | `number` | `2` | Vertical padding of label backdrop.
-| `format` | `object` | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter
-| `maxTicksLimit` | `number` | `11` | Maximum number of ticks and gridlines to show.
-| `precision` | `number` | | if defined and `stepSize` is not specified, the step size will be rounded to this many decimal places.
-| `stepSize` | `number` | | User defined fixed step size for the scale. [more...](#step-size)
-| `showLabelBackdrop` | `boolean` | `true` | If true, draw a background behind the tick labels.
+| Name | Type | Scriptable | Default | Description
+| ---- | ---- | ------- | ------- | -----------
+| `backdropColor` | `Color` | Yes | `'rgba(255, 255, 255, 0.75)'` | Color of label backdrops.
+| `backdropPaddingX` | `number` | | `2` | Horizontal padding of label backdrop.
+| `backdropPaddingY` | `number` | | `2` | Vertical padding of label backdrop.
+| `format` | `object` | | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter
+| `maxTicksLimit` | `number` | | `11` | Maximum number of ticks and gridlines to show.
+| `precision` | `number` | | | if defined and `stepSize` is not specified, the step size will be rounded to this many decimal places.
+| `stepSize` | `number` | | | User defined fixed step size for the scale. [more...](#step-size)
+| `showLabelBackdrop` | `boolean` | Yes | `true` | If true, draw a background behind the tick labels.
+
+The scriptable context has the following form:
+
+```javascript
+{
+    chart,
+    scale,
+    index,
+    tick
+}
+```
 
 ## Axis Range Settings
 
@@ -93,23 +104,38 @@ let options = {
 
 The following options are used to configure angled lines that radiate from the center of the chart to the point labels. They can be found in the `angleLines` sub options.
 
-| Name | Type | Default | Description
-| ---- | ---- | ------- | -----------
-| `display` | `boolean` | `true` | if true, angle lines are shown.
-| `color` | `Color` | `'rgba(0, 0, 0, 0.1)'` | Color of angled lines.
-| `lineWidth` | `number` | `1` | Width of angled lines.
-| `borderDash` | `number[]` | `[]` | Length and spacing of dashes on angled lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
-| `borderDashOffset` | `number` | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).
+| Name | Type | Scriptable | Default | Description
+| ---- | ---- | ------- | ------- | -----------
+| `display` | `boolean` | | `true` | if true, angle lines are shown.
+| `color` | `Color` | Yes | `'rgba(0, 0, 0, 0.1)'` | Color of angled lines.
+| `lineWidth` | `number` | Yes | `1` | Width of angled lines.
+| `borderDash` | `number[]` | Yes* | `[]` | Length and spacing of dashes on angled lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
+| `borderDashOffset` | `number` | Yes | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).
+
+The `borderDash` setting only accepts a static value or a function. Passing an array of arrays is not supported.
+
+The scriptable context has the following form:
+
+```javascript
+{
+    chart,
+    scale,
+    index,
+    label
+}
+```
 
 ## Point Label Options
 
 The following options are used to configure the point labels that are shown on the perimeter of the scale. They can be found in the `pointLabels` sub options.
 
-| Name | Type | Default | Description
-| ---- | ---- | ------- | -----------
-| `display` | `boolean` | `true` | if true, point labels are shown.
-| `callback` | `function` | | Callback function to transform data labels to point labels. The default implementation simply returns the current string.
-| `font` | `Font` | `defaults.font` | See [Fonts](fonts.md)
+| Name | Type | Scriptable | Default | Description
+| ---- | ---- | ------- | ------- | -----------
+| `display` | `boolean` | | `true` | if true, point labels are shown.
+| `callback` | `function` | | | Callback function to transform data labels to point labels. The default implementation simply returns the current string.
+| `font` | `Font` | Yes | `defaults.font` | See [Fonts](fonts.md)
+
+The scriptable context is the same as for the [Angle Line Options](#angle-line-options).
 
 ## Internal data format
 
index ed19e0585b82910d8ae4d5b25ebe4cc5554aa8d7..81548f4fe5be0986e97debd9a8f8c9f0f0a386d8 100644 (file)
@@ -257,6 +257,7 @@ The following properties and methods were removed:
 * `helpers.findNextWhere`
 * `helpers.findPreviousWhere`
 * `helpers.extend`. Use `Object.assign` instead
+* `helpers.getValueAtIndexOrDefault`. Use `helpers.resolve` instead.
 * `helpers.indexOf`
 * `helpers.lineTo`
 * `helpers.longestText` was moved to the `helpers.canvas` namespace and made private
@@ -332,7 +333,6 @@ The following properties were renamed during v3 development:
 * `helpers.getMaximumWidth` was renamed to `helpers.dom.getMaximumWidth`
 * `helpers.getRelativePosition` was renamed to `helpers.dom.getRelativePosition`
 * `helpers.getStyle` was renamed to `helpers.dom.getStyle`
-* `helpers.getValueAtIndexOrDefault` was renamed to `helpers.valueAtIndexOrDefault`
 * `helpers.getValueOrDefault` was renamed to `helpers.valueOrDefault`
 * `helpers.easingEffects` was renamed to `helpers.easing.effects`
 * `helpers.log10` was renamed to `helpers.math.log10`
index 01fa51f349dc2feba31205f0b7ce52621a049a10..a283ffba36d2518fc1b1729d8595d928da80995d 100644 (file)
@@ -75,17 +75,6 @@ export function valueOrDefault(value, defaultValue) {
        return typeof value === 'undefined' ? defaultValue : value;
 }
 
-/**
- * Returns value at the given `index` in array if defined, else returns `defaultValue`.
- * @param {Array} value - The array to lookup for value at `index`.
- * @param {number} index - The index in `value` to lookup for value.
- * @param {*} defaultValue - The value to return if `value[index]` is undefined.
- * @returns {*}
- */
-export function valueAtIndexOrDefault(value, index, defaultValue) {
-       return valueOrDefault(isArray(value) ? value[index] : value, defaultValue);
-}
-
 /**
  * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
  * value returned by `fn`. If `fn` is not a function, this method returns undefined.
index bb1497295f1ef33417c56519780973ff0037eb93..888c5c570149ae73637451ae8b10611c46ec9d7b 100644 (file)
@@ -3,7 +3,7 @@ import {_longestText} from '../helpers/helpers.canvas';
 import {isNumber, toDegrees, toRadians, _normalizeAngle} from '../helpers/helpers.math';
 import LinearScaleBase from './scale.linearbase';
 import Ticks from '../core/core.ticks';
-import {valueOrDefault, isArray, valueAtIndexOrDefault, isFinite, callback as callCallback, isNullOrUndef} from '../helpers/helpers.core';
+import {valueOrDefault, isArray, isFinite, callback as callCallback, isNullOrUndef} from '../helpers/helpers.core';
 import {toFont, resolve} from '../helpers/helpers.options';
 
 
@@ -132,8 +132,6 @@ function fitWithPointLabels(scale) {
        //
        // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
 
-       const plFont = toFont(scale.options.pointLabels.font);
-
        // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
        // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
        const furthestLimits = {
@@ -145,12 +143,20 @@ function fitWithPointLabels(scale) {
        const furthestAngles = {};
        let i, textSize, pointPosition;
 
-       scale.ctx.font = plFont.string;
        scale._pointLabelSizes = [];
 
        const valueCount = scale.chart.data.labels.length;
        for (i = 0; i < valueCount; i++) {
                pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
+
+               const context = {
+                       chart: scale.chart,
+                       scale,
+                       index: i,
+                       label: scale.pointLabels[i]
+               };
+               const plFont = toFont(resolve([scale.options.pointLabels.font], context, i));
+               scale.ctx.font = plFont.string;
                textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]);
                scale._pointLabelSizes[i] = textSize;
 
@@ -222,7 +228,6 @@ function drawPointLabels(scale) {
        const pointLabelOpts = opts.pointLabels;
        const tickBackdropHeight = getTickBackdropHeight(opts);
        const outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
-       const plFont = toFont(pointLabelOpts.font);
 
        ctx.save();
 
@@ -233,12 +238,17 @@ function drawPointLabels(scale) {
                const extra = (i === 0 ? tickBackdropHeight / 2 : 0);
                const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
 
-               // Keep this in loop since we may support array properties here
+               const context = {
+                       chart: scale.chart,
+                       scale,
+                       index: i,
+                       label: scale.pointLabels[i],
+               };
+               const plFont = toFont(resolve([pointLabelOpts.font], context, i));
                ctx.font = plFont.string;
                ctx.fillStyle = plFont.color;
 
-               const angleRadians = scale.getIndexAngle(i);
-               const angle = toDegrees(angleRadians);
+               const angle = toDegrees(scale.getIndexAngle(i));
                ctx.textAlign = getTextAlignForAngle(angle);
                adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
                fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight);
@@ -250,8 +260,15 @@ function drawRadiusLine(scale, gridLineOpts, radius, index) {
        const ctx = scale.ctx;
        const circular = gridLineOpts.circular;
        const valueCount = scale.chart.data.labels.length;
-       const lineColor = valueAtIndexOrDefault(gridLineOpts.color, index - 1, undefined);
-       const lineWidth = valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1, undefined);
+
+       const context = {
+               chart: scale.chart,
+               scale,
+               index,
+               tick: scale.ticks[index],
+       };
+       const lineColor = resolve([gridLineOpts.color], context, index - 1);
+       const lineWidth = resolve([gridLineOpts.lineWidth], context, index - 1);
        let pointPosition;
 
        if ((!circular && !valueCount) || !lineColor || !lineWidth) {
@@ -262,8 +279,8 @@ function drawRadiusLine(scale, gridLineOpts, radius, index) {
        ctx.strokeStyle = lineColor;
        ctx.lineWidth = lineWidth;
        if (ctx.setLineDash) {
-               ctx.setLineDash(gridLineOpts.borderDash || []);
-               ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0;
+               ctx.setLineDash(resolve([gridLineOpts.borderDash, []], context));
+               ctx.lineDashOffset = resolve([gridLineOpts.borderDashOffset], context, index - 1);
        }
 
        ctx.beginPath();
@@ -457,8 +474,6 @@ class RadialLinearScale extends LinearScaleBase {
                const opts = me.options;
                const gridLineOpts = opts.gridLines;
                const angleLineOpts = opts.angleLines;
-               const lineWidth = valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
-               const lineColor = valueOrDefault(angleLineOpts.color, gridLineOpts.color);
                let i, offset, position;
 
                if (opts.pointLabels.display) {
@@ -474,16 +489,31 @@ class RadialLinearScale extends LinearScaleBase {
                        });
                }
 
-               if (angleLineOpts.display && lineWidth && lineColor) {
+               if (angleLineOpts.display) {
                        ctx.save();
-                       ctx.lineWidth = lineWidth;
-                       ctx.strokeStyle = lineColor;
-                       if (ctx.setLineDash) {
-                               ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
-                               ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
-                       }
 
                        for (i = me.chart.data.labels.length - 1; i >= 0; i--) {
+                               const context = {
+                                       chart: me.chart,
+                                       scale: me,
+                                       index: i,
+                                       label: me.pointLabels[i],
+                               };
+                               const lineWidth = resolve([angleLineOpts.lineWidth, gridLineOpts.lineWidth], context, i);
+                               const color = resolve([angleLineOpts.color, gridLineOpts.color], context, i);
+
+                               if (!lineWidth || !color) {
+                                       continue;
+                               }
+
+                               ctx.lineWidth = lineWidth;
+                               ctx.strokeStyle = color;
+
+                               if (ctx.setLineDash) {
+                                       ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []], context));
+                                       ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0], context, i);
+                               }
+
                                offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
                                position = me.getPointPosition(i, offset);
                                ctx.beginPath();
@@ -510,26 +540,35 @@ class RadialLinearScale extends LinearScaleBase {
                }
 
                const startAngle = me.getIndexAngle(0);
-               const tickFont = toFont(tickOpts.font);
                let offset, width;
 
                ctx.save();
-               ctx.font = tickFont.string;
                ctx.translate(me.xCenter, me.yCenter);
                ctx.rotate(startAngle);
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
 
                me.ticks.forEach((tick, index) => {
+                       const context = {
+                               chart: me.chart,
+                               scale: me,
+                               index,
+                               tick,
+                       };
+
                        if (index === 0 && !opts.reverse) {
                                return;
                        }
 
+                       const tickFont = me._resolveTickFontOptions(index);
+                       ctx.font = tickFont.string;
                        offset = me.getDistanceFromCenterForValue(me.ticks[index].value);
 
-                       if (tickOpts.showLabelBackdrop) {
+                       const showLabelBackdrop = resolve([tickOpts.showLabelBackdrop], context, index);
+
+                       if (showLabelBackdrop) {
                                width = ctx.measureText(tick.label).width;
-                               ctx.fillStyle = tickOpts.backdropColor;
+                               ctx.fillStyle = resolve([tickOpts.backdropColor], context, index);
 
                                ctx.fillRect(
                                        -width / 2 - tickOpts.backdropPaddingX,
diff --git a/test/fixtures/scale.radialLinear/anglelines-indexable.js b/test/fixtures/scale.radialLinear/anglelines-indexable.js
new file mode 100644 (file)
index 0000000..f7f9b76
--- /dev/null
@@ -0,0 +1,28 @@
+module.exports = {
+       config: {
+               type: 'radar',
+               data: {
+                       labels: ['A', 'B', 'C', 'D', 'E']
+               },
+               options: {
+                       responsive: false,
+                       legend: false,
+                       title: false,
+                       scale: {
+                               gridLines: {
+                                       display: true,
+                               },
+                               angleLines: {
+                                       color: ['red', 'green'],
+                                       lineWidth: [1, 5]
+                               },
+                               pointLabels: {
+                                       display: false
+                               },
+                               ticks: {
+                                       display: false
+                               }
+                       }
+               }
+       }
+};
diff --git a/test/fixtures/scale.radialLinear/anglelines-indexable.png b/test/fixtures/scale.radialLinear/anglelines-indexable.png
new file mode 100644 (file)
index 0000000..dfbb15b
Binary files /dev/null and b/test/fixtures/scale.radialLinear/anglelines-indexable.png differ
diff --git a/test/fixtures/scale.radialLinear/anglelines-scriptable.js b/test/fixtures/scale.radialLinear/anglelines-scriptable.js
new file mode 100644 (file)
index 0000000..b07b08a
--- /dev/null
@@ -0,0 +1,32 @@
+module.exports = {
+       config: {
+               type: 'radar',
+               data: {
+                       labels: ['A', 'B', 'C', 'D', 'E']
+               },
+               options: {
+                       responsive: false,
+                       legend: false,
+                       title: false,
+                       scale: {
+                               gridLines: {
+                                       display: true,
+                               },
+                               angleLines: {
+                                       color: function(context) {
+                                               return context.index % 2 === 0 ? 'red' : 'green';
+                                       },
+                                       lineWidth: function(context) {
+                                               return context.index % 2 === 0 ? 1 : 5;
+                                       },
+                               },
+                               pointLabels: {
+                                       display: false
+                               },
+                               ticks: {
+                                       display: false
+                               }
+                       }
+               }
+       }
+};
diff --git a/test/fixtures/scale.radialLinear/anglelines-scriptable.png b/test/fixtures/scale.radialLinear/anglelines-scriptable.png
new file mode 100644 (file)
index 0000000..dfbb15b
Binary files /dev/null and b/test/fixtures/scale.radialLinear/anglelines-scriptable.png differ
diff --git a/test/fixtures/scale.radialLinear/gridlines-scriptable.js b/test/fixtures/scale.radialLinear/gridlines-scriptable.js
new file mode 100644 (file)
index 0000000..5d37593
--- /dev/null
@@ -0,0 +1,34 @@
+module.exports = {
+       config: {
+               type: 'radar',
+               data: {
+                       labels: ['A', 'B', 'C', 'D', 'E']
+               },
+               options: {
+                       responsive: false,
+                       legend: false,
+                       title: false,
+                       scale: {
+                               gridLines: {
+                                       display: true,
+                                       color: function(context) {
+                                               return context.index % 2 === 0 ? 'red' : 'green';
+                                       },
+                                       lineWidth: function(context) {
+                                               return context.index % 2 === 0 ? 1 : 5;
+                                       },
+                               },
+                               angleLines: {
+                                       color: 'rgba(255, 255, 255, 0.5)',
+                                       lineWidth: 2
+                               },
+                               pointLabels: {
+                                       display: false
+                               },
+                               ticks: {
+                                       display: false
+                               }
+                       }
+               }
+       }
+};
diff --git a/test/fixtures/scale.radialLinear/gridlines-scriptable.png b/test/fixtures/scale.radialLinear/gridlines-scriptable.png
new file mode 100644 (file)
index 0000000..c25ff94
Binary files /dev/null and b/test/fixtures/scale.radialLinear/gridlines-scriptable.png differ
index 9b613d142322acee8b6e0150d89dc4d9c4c63731..fc20ae63d0a2f5f562d179e5afa322919db12438 100644 (file)
@@ -119,27 +119,6 @@ describe('Chart.helpers.core', function() {
                });
        });
 
-       describe('valueAtIndexOrDefault', function() {
-               it('should return the passed value if not an array', function() {
-                       expect(helpers.valueAtIndexOrDefault(0, 0, 42)).toBe(0);
-                       expect(helpers.valueAtIndexOrDefault('', 0, 42)).toBe('');
-                       expect(helpers.valueAtIndexOrDefault(null, 0, 42)).toBe(null);
-                       expect(helpers.valueAtIndexOrDefault(false, 0, 42)).toBe(false);
-                       expect(helpers.valueAtIndexOrDefault(98, 0, 42)).toBe(98);
-               });
-               it('should return the value at index if defined', function() {
-                       expect(helpers.valueAtIndexOrDefault([1, false, 'foo'], 1, 42)).toBe(false);
-                       expect(helpers.valueAtIndexOrDefault([1, false, 'foo'], 2, 42)).toBe('foo');
-               });
-               it('should return the default value if the passed value is undefined', function() {
-                       expect(helpers.valueAtIndexOrDefault(undefined, 0, 42)).toBe(42);
-               });
-               it('should return the default value if value at index is undefined', function() {
-                       expect(helpers.valueAtIndexOrDefault([1, false, 'foo'], 3, 42)).toBe(42);
-                       expect(helpers.valueAtIndexOrDefault([1, undefined, 'foo'], 1, 42)).toBe(42);
-               });
-       });
-
        describe('callback', function() {
                it('should return undefined if fn is not a function', function() {
                        expect(helpers.callback()).not.toBeDefined();