From: Igor Lukanin Date: Fri, 21 Oct 2022 12:21:08 +0000 (+0400) Subject: Introduce Colors plugin (#10764) X-Git-Tag: v4.0.0-alpha.3~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1c2f66a00e91ad6e4449c8dc2f4e69c6ede7079e;p=thirdparty%2FChart.js.git Introduce Colors plugin (#10764) Introduces a colors plugin that provides a color palette Co-authored-by: Dan Onoshko Co-authored-by: Jacco van den Berg --- diff --git a/docs/general/colors-plugin-palette.png b/docs/general/colors-plugin-palette.png new file mode 100644 index 000000000..ac6994ee2 Binary files /dev/null and b/docs/general/colors-plugin-palette.png differ diff --git a/docs/general/colors.md b/docs/general/colors.md index c9b101693..913cf5482 100644 --- a/docs/general/colors.md +++ b/docs/general/colors.md @@ -1,55 +1,142 @@ # Colors -When supplying colors to Chart options, you can use a number of formats. You can specify the color as a string in hexadecimal, RGB, or HSL notations. If a color is needed, but not specified, Chart.js will use the global default color. There are 3 color options, stored at `Chart.defaults`, to set: +Charts support three color options: +* for geometric elements, you can change *background* and *border* colors; +* for textual elements, you can change the *font* color. -| Name | Type | Default | Description -| ---- | ---- | ------- | ----------- -| `backgroundColor` | `Color` | `rgba(0, 0, 0, 0.1)` | Background color. -| `borderColor` | `Color` | `rgba(0, 0, 0, 0.1)` | Border color. -| `color` | `Color` | `#666` | Font color. +Also, you can change the whole [canvas background](.../configuration/canvas-background.html). -You can also pass a [CanvasGradient](https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient) object. You will need to create this before passing to the chart, but using it you can achieve some interesting effects. +## Default colors -## Patterns and Gradients +If a color is not specified, a global default color from `Chart.defaults` is used: + +| Name | Type | Description | Default value +| ---- | ---- | ----------- | ------------- +| `backgroundColor` | [`Color`](../api/#color) | Background color | `rgba(0, 0, 0, 0.1)` +| `borderColor` | [`Color`](../api/#color) | Border color | `rgba(0, 0, 0, 0.1)` +| `color` | [`Color`](../api/#color) | Font color | `#666` + +You can reset default colors by updating these properties of `Chart.defaults`: + +```javascript +Chart.defaults.backgroundColor = '#9BD0F5'; +Chart.defaults.borderColor = '#36A2EB'; +Chart.defaults.color = '#000'; +``` + +### Per-dataset color settings + +If your chart has multiple datasets, using default colors would make individual datasets indistiguishable. In that case, you can set `backgroundColor` and `borderColor` for each dataset: + +```javascript +const data = { + labels: ['A', 'B', 'C'], + datasets: [ + { + label: 'Dataset 1', + data: [1, 2, 3], + borderColor: '#36A2EB', + backgroundColor: '#9BD0F5', + }, + { + label: 'Dataset 2', + data: [2, 3, 4], + borderColor: '#FF6384', + backgroundColor: '#FFB1C1', + } + ] +}; +``` + +However, setting colors for each dataset might require additional work that you'd rather not do. In that case, consider using the following plugins with pre-defined or generated palettes. + +### Default color palette + +If you don't have any preference for colors, you can use the built-in `Colors` plugin. It will cycle through a palette of seven Chart.js brand colors: + +
+ +![Colors plugin palette](./colors-plugin-palette.png) + +
+ +All you need is to import and register the plugin: -An alternative option is to pass a [CanvasPattern](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) or [CanvasGradient](https://developer.mozilla.org/en/docs/Web/API/CanvasGradient) object instead of a string colour. +```javascript +import { Colors } from 'chart.js'; + +Chart.register(Colors); +``` + +:::tip Note + +If you are using the UMD version of Chart.js, this plugin will be enabled by default. You can disable it by setting the `enabled` option to `false`: + +```js +const options = { + plugins: { + colors: { + enabled: false + } + } +}; +``` + +::: + +### Advanced color palettes -For example, if you wanted to fill a dataset with a pattern from an image you could do the following. +See the [awesome list](https://github.com/chartjs/awesome#plugins) for plugins that would give you more flexibility defining color palettes. + +## Color formats + +You can specify the color as a string in either of the following notations: + +| Notation | Example | Example with transparency +| -------- | ------- | ------------------------- +| [Hexademical](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) | `#36A2EB` | `#36A2EB80` +| [RGB](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb) or [RGBA](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgba) | `rgb(54, 162, 235)` | `rgba(54, 162, 235, 0.5)` +| [HSL](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) or [HSLA](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsla) | `hsl(204, 82%, 57%)` | `hsla(204, 82%, 57%, 0.5)` + +Alternatively, you can pass a [CanvasPattern](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) or [CanvasGradient](https://developer.mozilla.org/en/docs/Web/API/CanvasGradient) object instead of a string color to achieve some interesting effects. + +## Patterns and Gradients + +For example, you can fill a dataset with a pattern from an image. ```javascript const img = new Image(); img.src = 'https://example.com/my_image.png'; -img.onload = function() { - const ctx = document.getElementById('canvas').getContext('2d'); - const fillPattern = ctx.createPattern(img, 'repeat'); - - const chart = new Chart(ctx, { - data: { - labels: ['Item 1', 'Item 2', 'Item 3'], - datasets: [{ - data: [10, 20, 30], - backgroundColor: fillPattern - }] - } - }); +img.onload = () => { + const ctx = document.getElementById('canvas').getContext('2d'); + const fillPattern = ctx.createPattern(img, 'repeat'); + + const chart = new Chart(ctx, { + data: { + labels: ['Item 1', 'Item 2', 'Item 3'], + datasets: [{ + data: [10, 20, 30], + backgroundColor: fillPattern + }] + } + }); }; ``` +Pattern fills can help viewers with vision deficiencies (e.g., color-blindness or partial sight) [more easily understand your data](http://betweentwobrackets.com/data-graphics-and-colour-vision/). -Using pattern fills for data graphics can help viewers with vision deficiencies (e.g. color-blindness or partial sight) to [more easily understand your data](http://betweentwobrackets.com/data-graphics-and-colour-vision/). - -Using the [Patternomaly](https://github.com/ashiguruma/patternomaly) library you can generate patterns to fill datasets. +You can use the [Patternomaly](https://github.com/ashiguruma/patternomaly) library to generate patterns to fill datasets: ```javascript const chartData = { - datasets: [{ - data: [45, 25, 20, 10], - backgroundColor: [ - pattern.draw('square', '#ff6384'), - pattern.draw('circle', '#36a2eb'), - pattern.draw('diamond', '#cc65fe'), - pattern.draw('triangle', '#ffce56') - ] - }], - labels: ['Red', 'Blue', 'Purple', 'Yellow'] + datasets: [{ + data: [45, 25, 20, 10], + backgroundColor: [ + pattern.draw('square', '#ff6384'), + pattern.draw('circle', '#36a2eb'), + pattern.draw('diamond', '#cc65fe'), + pattern.draw('triangle', '#ffce56') + ] + }], + labels: ['Red', 'Blue', 'Purple', 'Yellow'] }; ``` diff --git a/src/plugins/index.js b/src/plugins/index.js index eb76545ed..e9a0be35a 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,3 +1,4 @@ +export {default as Colors} from './plugin.colors'; export {default as Decimation} from './plugin.decimation'; export {default as Filler} from './plugin.filler'; export {default as Legend} from './plugin.legend'; diff --git a/src/plugins/plugin.colors.ts b/src/plugins/plugin.colors.ts new file mode 100644 index 000000000..8d4766667 --- /dev/null +++ b/src/plugins/plugin.colors.ts @@ -0,0 +1,106 @@ +import type {Chart, ChartDataset} from '../types'; + +export interface ColorsPluginOptions { + enabled?: boolean; +} + +type DatasetColorizer = (dataset: ChartDataset, i: number) => void; + +interface ColorsDescriptor { + backgroundColor?: unknown; + borderColor?: unknown; +} + +const BORDER_COLORS = [ + 'rgb(54, 162, 235)', // blue + 'rgb(255, 99, 132)', // red + 'rgb(255, 159, 64)', // orange + 'rgb(255, 205, 86)', // yellow + 'rgb(75, 192, 192)', // green + 'rgb(153, 102, 255)', // purple + 'rgb(201, 203, 207)' // grey +]; + +// Border colors with 50% transparency +const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map(color => color.replace('rgb(', 'rgba(').replace(')', ', 0.5)')); + +function getBorderColor(i: number) { + return BORDER_COLORS[i % BORDER_COLORS.length]; +} + +function getBackgroundColor(i: number) { + return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length]; +} + +function createDefaultDatasetColorizer() { + return (dataset: ChartDataset, i: number) => { + dataset.borderColor = getBorderColor(i); + dataset.backgroundColor = getBackgroundColor(i); + }; +} + +function createDoughnutDatasetColorizer() { + let i = 0; + + return (dataset: ChartDataset) => { + dataset.backgroundColor = dataset.data.map(() => getBorderColor(i++)); + }; +} + +function createPolarAreaDatasetColorizer() { + let i = 0; + + return (dataset: ChartDataset) => { + dataset.backgroundColor = dataset.data.map(() => getBackgroundColor(i++)); + }; +} + +function containsColorsDefinitions( + descriptors: ColorsDescriptor[] | Record +) { + let k: number | string; + + for (k in descriptors) { + if (descriptors[k].borderColor || descriptors[k].backgroundColor) { + return true; + } + } + + return false; +} + +export default { + id: 'colors', + + defaults: { + enabled: true, + }, + + beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) { + if (!options.enabled) { + return; + } + + const { + type, + options: {elements}, + data: {datasets} + } = chart.config; + + if (containsColorsDefinitions(datasets) || elements && containsColorsDefinitions(elements)) { + return; + } + + let colorizer: DatasetColorizer; + + if (type === 'doughnut') { + colorizer = createDoughnutDatasetColorizer(); + } else if (type === 'polarArea') { + colorizer = createPolarAreaDatasetColorizer(); + } else { + colorizer = createDefaultDatasetColorizer(); + } + + datasets.forEach(colorizer); + } +}; diff --git a/test/fixtures/plugin.colors/bar.js b/test/fixtures/plugin.colors/bar.js new file mode 100644 index 000000000..0ca99e3b9 --- /dev/null +++ b/test/fixtures/plugin.colors/bar.js @@ -0,0 +1,36 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + data: [0, 5, 10, null, -10, -5], + }, + { + data: [10, 2, 3, null, 10, 5] + } + ] + }, + options: { + scales: { + x: { + ticks: { + display: false, + } + }, + y: { + ticks: { + display: false, + } + } + }, + plugins: { + legend: false, + colors: { + enabled: true + } + } + } + } +}; diff --git a/test/fixtures/plugin.colors/bar.png b/test/fixtures/plugin.colors/bar.png new file mode 100644 index 000000000..72c10f05d Binary files /dev/null and b/test/fixtures/plugin.colors/bar.png differ diff --git a/test/fixtures/plugin.colors/bubble.js b/test/fixtures/plugin.colors/bubble.js new file mode 100644 index 000000000..3e7c25a81 --- /dev/null +++ b/test/fixtures/plugin.colors/bubble.js @@ -0,0 +1,32 @@ +module.exports = { + config: { + type: 'bubble', + data: { + datasets: [{ + data: [{x: 12, y: 54, r: 22.4}] + }, { + data: [{x: 18, y: 38, r: 25}] + }] + }, + options: { + scales: { + x: { + ticks: { + display: false, + } + }, + y: { + ticks: { + display: false, + } + } + }, + plugins: { + legend: false, + colors: { + enabled: true + } + } + } + } +}; diff --git a/test/fixtures/plugin.colors/bubble.png b/test/fixtures/plugin.colors/bubble.png new file mode 100644 index 000000000..539eed1cc Binary files /dev/null and b/test/fixtures/plugin.colors/bubble.png differ diff --git a/test/fixtures/plugin.colors/doughnut.js b/test/fixtures/plugin.colors/doughnut.js new file mode 100644 index 000000000..077ad624a --- /dev/null +++ b/test/fixtures/plugin.colors/doughnut.js @@ -0,0 +1,23 @@ +module.exports = { + config: { + type: 'doughnut', + data: { + datasets: [ + { + data: [0, 2, 4, null, 6, 8] + }, + { + data: [5, 1, 6, 2, null, 9] + } + ] + }, + options: { + plugins: { + legend: false, + colors: { + enabled: true + } + } + } + } +}; diff --git a/test/fixtures/plugin.colors/doughnut.png b/test/fixtures/plugin.colors/doughnut.png new file mode 100644 index 000000000..d6c28c6c1 Binary files /dev/null and b/test/fixtures/plugin.colors/doughnut.png differ diff --git a/test/fixtures/plugin.colors/line.js b/test/fixtures/plugin.colors/line.js new file mode 100644 index 000000000..9658956a4 --- /dev/null +++ b/test/fixtures/plugin.colors/line.js @@ -0,0 +1,36 @@ +module.exports = { + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + data: [0, 5, 10, null, -10, -5], + }, + { + data: [10, 2, 3, null, 10, 5] + } + ] + }, + options: { + scales: { + x: { + ticks: { + display: false, + } + }, + y: { + ticks: { + display: false, + } + } + }, + plugins: { + legend: false, + colors: { + enabled: true + } + } + } + } +}; diff --git a/test/fixtures/plugin.colors/line.png b/test/fixtures/plugin.colors/line.png new file mode 100644 index 000000000..b9ce7585e Binary files /dev/null and b/test/fixtures/plugin.colors/line.png differ diff --git a/test/fixtures/plugin.colors/polarArea.js b/test/fixtures/plugin.colors/polarArea.js new file mode 100644 index 000000000..37e4d4af3 --- /dev/null +++ b/test/fixtures/plugin.colors/polarArea.js @@ -0,0 +1,28 @@ +module.exports = { + config: { + type: 'polarArea', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + data: [0, 2, 4, null, 6, 8] + } + ] + }, + options: { + scales: { + r: { + ticks: { + display: false + } + } + }, + plugins: { + legend: false, + colors: { + enabled: true + } + } + } + } +}; diff --git a/test/fixtures/plugin.colors/polarArea.png b/test/fixtures/plugin.colors/polarArea.png new file mode 100644 index 000000000..8671ff67a Binary files /dev/null and b/test/fixtures/plugin.colors/polarArea.png differ diff --git a/test/fixtures/plugin.colors/radar.js b/test/fixtures/plugin.colors/radar.js new file mode 100644 index 000000000..d5fd4318e --- /dev/null +++ b/test/fixtures/plugin.colors/radar.js @@ -0,0 +1,34 @@ +module.exports = { + config: { + type: 'radar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + data: [0, 5, 10, null, -10, -5] + }, + { + data: [4, -5, -10, null, 10, 5] + } + ] + }, + options: { + scales: { + r: { + ticks: { + display: false + }, + pointLabels: { + display: false, + } + } + }, + plugins: { + legend: false, + colors: { + enabled: true + } + } + } + } +}; diff --git a/test/fixtures/plugin.colors/radar.png b/test/fixtures/plugin.colors/radar.png new file mode 100644 index 000000000..5a39a08eb Binary files /dev/null and b/test/fixtures/plugin.colors/radar.png differ diff --git a/test/fixtures/plugin.colors/scatter.js b/test/fixtures/plugin.colors/scatter.js new file mode 100644 index 000000000..8822de1d7 --- /dev/null +++ b/test/fixtures/plugin.colors/scatter.js @@ -0,0 +1,37 @@ +module.exports = { + config: { + type: 'scatter', + data: { + datasets: [{ + data: [{x: 10, y: 15}, {x: 15, y: 10}], + pointRadius: 10, + showLine: true, + label: 'dataset1' + }, { + data: [{x: 20, y: 45}, {x: 5, y: 15}], + pointRadius: 20, + label: 'dataset2' + }], + }, + options: { + scales: { + x: { + ticks: { + display: false, + } + }, + y: { + ticks: { + display: false, + } + } + }, + plugins: { + legend: false, + colors: { + enabled: true + }, + } + } + } +}; diff --git a/test/fixtures/plugin.colors/scatter.png b/test/fixtures/plugin.colors/scatter.png new file mode 100644 index 000000000..12534c720 Binary files /dev/null and b/test/fixtures/plugin.colors/scatter.png differ diff --git a/test/index.js b/test/index.js index 8f1b7ced3..f250fb6a8 100644 --- a/test/index.js +++ b/test/index.js @@ -22,10 +22,15 @@ jasmine.triggerMouseEvent = triggerMouseEvent; // more stable test results. window.moment.tz.setDefault('Etc/UTC'); -beforeEach(function() { +beforeAll(() => { + // Disable colors plugin for tests. + window.Chart.defaults.plugins.colors.enabled = false; +}); + +beforeEach(() => { addMatchers(); }); -afterEach(function() { +afterEach(() => { releaseCharts(); }); diff --git a/test/specs/plugin.colors.tests.js b/test/specs/plugin.colors.tests.js new file mode 100644 index 000000000..df5ff8fb9 --- /dev/null +++ b/test/specs/plugin.colors.tests.js @@ -0,0 +1,3 @@ +describe('Plugin.colors', () => { + describe('auto', jasmine.fixture.specs('plugin.colors')); +}); diff --git a/types/index.d.ts b/types/index.d.ts index 2e0369a09..3c55cf010 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -3651,6 +3651,7 @@ export interface ChartConfigurationCustomTypesPerDataset< TData = DefaultDataPoint, TLabel = unknown > { + type: TType; data: ChartDataCustomTypesPerDataset; options?: ChartOptions; plugins?: Plugin[];