From c22d3bedea902deaaf5021a28d3c113fcc4fa439 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sat, 17 Oct 2020 15:46:56 -0400 Subject: [PATCH] Ability to fill a line from a specified value along an axis (#7905) --- docs/docs/charts/area.md | 7 ++- samples/charts/area/analyser.js | 2 +- samples/charts/area/line-datasets.html | 6 +++ samples/charts/area/radar.html | 6 +++ src/plugins/plugin.filler.js | 21 +++++++-- .../plugin.filler/fill-line-value.json | 42 ++++++++++++++++++ .../plugin.filler/fill-line-value.png | Bin 0 -> 5227 bytes .../plugin.filler/fill-radar-value.json | 39 ++++++++++++++++ .../plugin.filler/fill-radar-value.png | Bin 0 -> 8389 bytes types/plugins/index.d.ts | 2 +- 10 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/plugin.filler/fill-line-value.json create mode 100644 test/fixtures/plugin.filler/fill-line-value.png create mode 100644 test/fixtures/plugin.filler/fill-radar-value.json create mode 100644 test/fixtures/plugin.filler/fill-radar-value.png diff --git a/docs/docs/charts/area.md b/docs/docs/charts/area.md index 4ec8edb75..477ece36f 100644 --- a/docs/docs/charts/area.md +++ b/docs/docs/charts/area.md @@ -15,11 +15,13 @@ Both [line](./line.mdx) and [radar](./radar.mdx) charts support a `fill` option | Boundary 2 | `string` | `'start'`, `'end'`, `'origin'` | | Disabled 3 | `boolean` | `false` | | Stacked value below 4 | `string` | `'stack'` | +| Axis value 5 | `object` | `{ value: number; }` | > 1 dataset filling modes have been introduced in version 2.6.0
> 2 prior version 2.6.0, boundary values was `'zero'`, `'top'`, `'bottom'` (not supported anymore)
> 3 for backward compatibility, `fill: true` (default) is equivalent to `fill: 'origin'`
> 4 stack mode has been introduced in version 3.0.0
+> 5 axis value mode has been introduced in version 3.0.0
**Example** @@ -31,7 +33,8 @@ new Chart(ctx, { {fill: '+2'}, // 1: fill to dataset 3 {fill: 1}, // 2: fill to dataset 1 {fill: false}, // 3: no fill - {fill: '-2'} // 4: fill to dataset 2 + {fill: '-2'}, // 4: fill to dataset 2 + {fill: {value: 25}} // 5: fill to axis value 25 ] } }); @@ -41,7 +44,7 @@ If you need to support multiple colors when filling from one dataset to another, | Param | Type | Description | | :--- | :--- | :--- | -| `target` | `number`, `string`, `boolean` | The accepted values are the same as the filling mode values, so you may use absolute and relative dataset indexes and/or boundaries. | +| `target` | `number`, `string`, `boolean`, `object` | The accepted values are the same as the filling mode values, so you may use absolute and relative dataset indexes and/or boundaries. | | `above` | `Color` | If no color is set, the default color will be the background color of the chart. | | `below` | `Color` | Same as the above. | diff --git a/samples/charts/area/analyser.js b/samples/charts/area/analyser.js index 5c1005418..592dcbb9d 100644 --- a/samples/charts/area/analyser.js +++ b/samples/charts/area/analyser.js @@ -48,7 +48,7 @@ } else if (isFinite(target)) { target = 'dataset ' + target; } else { - target = 'boundary "' + target + '"'; + target = 'boundary "' + (typeof target === 'object' ? JSON.stringify(target) : target) + '"'; } if (stat.visible) { diff --git a/samples/charts/area/line-datasets.html b/samples/charts/area/line-datasets.html index 9a6636161..1d1d2706e 100644 --- a/samples/charts/area/line-datasets.html +++ b/samples/charts/area/line-datasets.html @@ -102,6 +102,12 @@ hidden: true, label: 'D8', fill: 'end' + }, { + backgroundColor: utils.transparentize(presets.yellow), + borderColor: presets.yellow, + data: generateData(), + label: 'D9', + fill: {above: 'blue', below: 'red', target: {value: 350}} }] }; diff --git a/samples/charts/area/radar.html b/samples/charts/area/radar.html index de30630ed..3c36d670f 100644 --- a/samples/charts/area/radar.html +++ b/samples/charts/area/radar.html @@ -85,6 +85,12 @@ data: generateData(), label: 'D5', fill: '-1' + }, { + backgroundColor: utils.transparentize(presets.grey), + borderColor: presets.grey, + data: generateData(), + label: 'D6', + fill: {value: 85}, }] }; diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index c27e4d377..49d2d9ec9 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -7,7 +7,7 @@ import Line from '../elements/element.line'; import {_boundSegment, _boundSegments} from '../helpers/helpers.segment'; import {clipArea, unclipArea} from '../helpers/helpers.canvas'; -import {isArray, isFinite, valueOrDefault} from '../helpers/helpers.core'; +import {isArray, isFinite, isObject, valueOrDefault} from '../helpers/helpers.core'; import {TAU, _normalizeAngle} from '../helpers/helpers.math'; /** @@ -57,7 +57,9 @@ function decodeFill(line, index, count) { const fill = parseFillOption(line); let target = parseFloat(fill); - if (isFinite(target) && Math.floor(target) === target) { + if (isObject(fill)) { + return isNaN(fill.value) ? false : fill; + } else if (isFinite(target) && Math.floor(target) === target) { if (fill[0] === '-' || fill[0] === '+') { target = index + target; } @@ -81,6 +83,8 @@ function computeLinearBoundary(source) { target = scale.bottom; } else if (fill === 'end') { target = scale.top; + } else if (isObject(fill)) { + target = scale.getPixelForValue(fill.value); } else if (scale.getBasePixel) { target = scale.getBasePixel(); } @@ -135,8 +139,17 @@ function computeCircularBoundary(source) { const target = []; const start = options.reverse ? scale.max : scale.min; const end = options.reverse ? scale.min : scale.max; - const value = fill === 'start' ? start : fill === 'end' ? end : scale.getBaseValue(); - let i, center; + let i, center, value; + + if (fill === 'start') { + value = start; + } else if (fill === 'end') { + value = end; + } else if (isObject(fill)) { + value = fill.value; + } else { + value = scale.getBaseValue(); + } if (options.gridLines.circular) { center = scale.getPointPositionForValue(0, start); diff --git a/test/fixtures/plugin.filler/fill-line-value.json b/test/fixtures/plugin.filler/fill-line-value.json new file mode 100644 index 000000000..f8520ea6e --- /dev/null +++ b/test/fixtures/plugin.filler/fill-line-value.json @@ -0,0 +1,42 @@ +{ + "config": { + "type": "line", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(255, 0, 0, 0.25)", + "data": [-4, 4, 0, -1, 0, 1, 0, -1, 0], + "fill": { "value": 2 } + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scales": { + "x": { + "display": false + }, + "y": { + "display": false + } + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "tension": 0 + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/plugin.filler/fill-line-value.png b/test/fixtures/plugin.filler/fill-line-value.png new file mode 100644 index 0000000000000000000000000000000000000000..758b1c1324b2b9a148fb267bd1119714048b16e2 GIT binary patch literal 5227 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/plugin.filler/fill-radar-value.json b/test/fixtures/plugin.filler/fill-radar-value.json new file mode 100644 index 000000000..f56be396e --- /dev/null +++ b/test/fixtures/plugin.filler/fill-radar-value.json @@ -0,0 +1,39 @@ +{ + "config": { + "type": "radar", + "data": { + "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"], + "datasets": [{ + "backgroundColor": "rgba(0, 0, 192, 0.25)", + "data": [0, -4, 2, 4, 2, 1, -1, 1, 2] + }] + }, + "options": { + "responsive": false, + "spanGaps": false, + "legend": false, + "title": false, + "scale": { + "display": false, + "gridLines": { + "circular": true + } + }, + "elements": { + "point": { + "radius": 0 + }, + "line": { + "borderColor": "transparent", + "fill": { "value": 3 } + } + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 256 + } + } +} diff --git a/test/fixtures/plugin.filler/fill-radar-value.png b/test/fixtures/plugin.filler/fill-radar-value.png new file mode 100644 index 0000000000000000000000000000000000000000..f74ee51e4c404adbf12eda7480d6af7078223745 GIT binary patch literal 8389 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