From: Evert Timberg Date: Thu, 11 Jun 2020 12:49:54 +0000 (-0400) Subject: Radial scale scriptable options (#7471) X-Git-Tag: v3.0.0-beta.2~92 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d3159345132f4746c77afc7a316c474937ddb501;p=thirdparty%2FChart.js.git Radial scale scriptable options (#7471) Scriptable radialLinear options --- diff --git a/docs/docs/axes/radial/linear.md b/docs/docs/axes/radial/linear.md index eb7fdb10d..05aaee292 100644 --- a/docs/docs/axes/radial/linear.md +++ b/docs/docs/axes/radial/linear.md @@ -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 diff --git a/docs/docs/getting-started/v3-migration.md b/docs/docs/getting-started/v3-migration.md index ed19e0585..81548f4fe 100644 --- a/docs/docs/getting-started/v3-migration.md +++ b/docs/docs/getting-started/v3-migration.md @@ -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` diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 01fa51f34..a283ffba3 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -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. diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index bb1497295..888c5c570 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -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 index 000000000..f7f9b768f --- /dev/null +++ b/test/fixtures/scale.radialLinear/anglelines-indexable.js @@ -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 index 000000000..dfbb15bb1 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 index 000000000..b07b08a39 --- /dev/null +++ b/test/fixtures/scale.radialLinear/anglelines-scriptable.js @@ -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 index 000000000..dfbb15bb1 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 index 000000000..5d37593fe --- /dev/null +++ b/test/fixtures/scale.radialLinear/gridlines-scriptable.js @@ -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 index 000000000..c25ff9483 Binary files /dev/null and b/test/fixtures/scale.radialLinear/gridlines-scriptable.png differ diff --git a/test/specs/helpers.core.tests.js b/test/specs/helpers.core.tests.js index 9b613d142..fc20ae63d 100644 --- a/test/specs/helpers.core.tests.js +++ b/test/specs/helpers.core.tests.js @@ -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();