]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Introduce Colors plugin (#10764)
authorIgor Lukanin <me@igorlukanin.io>
Fri, 21 Oct 2022 12:21:08 +0000 (16:21 +0400)
committerGitHub <noreply@github.com>
Fri, 21 Oct 2022 12:21:08 +0000 (08:21 -0400)
Introduces a colors plugin that provides a color palette

Co-authored-by: Dan Onoshko <danon0404@gmail.com>
Co-authored-by: Jacco van den Berg <jaccoberg2281@gmail.com>
21 files changed:
docs/general/colors-plugin-palette.png [new file with mode: 0644]
docs/general/colors.md
src/plugins/index.js
src/plugins/plugin.colors.ts [new file with mode: 0644]
test/fixtures/plugin.colors/bar.js [new file with mode: 0644]
test/fixtures/plugin.colors/bar.png [new file with mode: 0644]
test/fixtures/plugin.colors/bubble.js [new file with mode: 0644]
test/fixtures/plugin.colors/bubble.png [new file with mode: 0644]
test/fixtures/plugin.colors/doughnut.js [new file with mode: 0644]
test/fixtures/plugin.colors/doughnut.png [new file with mode: 0644]
test/fixtures/plugin.colors/line.js [new file with mode: 0644]
test/fixtures/plugin.colors/line.png [new file with mode: 0644]
test/fixtures/plugin.colors/polarArea.js [new file with mode: 0644]
test/fixtures/plugin.colors/polarArea.png [new file with mode: 0644]
test/fixtures/plugin.colors/radar.js [new file with mode: 0644]
test/fixtures/plugin.colors/radar.png [new file with mode: 0644]
test/fixtures/plugin.colors/scatter.js [new file with mode: 0644]
test/fixtures/plugin.colors/scatter.png [new file with mode: 0644]
test/index.js
test/specs/plugin.colors.tests.js [new file with mode: 0644]
types/index.d.ts

diff --git a/docs/general/colors-plugin-palette.png b/docs/general/colors-plugin-palette.png
new file mode 100644 (file)
index 0000000..ac6994e
Binary files /dev/null and b/docs/general/colors-plugin-palette.png differ
index c9b101693ca8b1f886e88c2eaa3bbcc63fa8d732..913cf548207d8d7db92d86e2cf38a98c62bad76a 100644 (file)
 # 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:
+
+<div style="max-width: 500px;">
+
+![Colors plugin palette](./colors-plugin-palette.png)
+
+</div>
+
+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']
 };
 ```
index eb76545ed4f62b6d4ebb20cdb38f77c42040e48a..e9a0be35aa666043c4062344f5c5201cb0523af1 100644 (file)
@@ -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 (file)
index 0000000..8d47666
--- /dev/null
@@ -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<string, ColorsDescriptor>
+) {
+  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 (file)
index 0000000..0ca99e3
--- /dev/null
@@ -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 (file)
index 0000000..72c10f0
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 (file)
index 0000000..3e7c25a
--- /dev/null
@@ -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 (file)
index 0000000..539eed1
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 (file)
index 0000000..077ad62
--- /dev/null
@@ -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 (file)
index 0000000..d6c28c6
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 (file)
index 0000000..9658956
--- /dev/null
@@ -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 (file)
index 0000000..b9ce758
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 (file)
index 0000000..37e4d4a
--- /dev/null
@@ -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 (file)
index 0000000..8671ff6
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 (file)
index 0000000..d5fd431
--- /dev/null
@@ -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 (file)
index 0000000..5a39a08
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 (file)
index 0000000..8822de1
--- /dev/null
@@ -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 (file)
index 0000000..12534c7
Binary files /dev/null and b/test/fixtures/plugin.colors/scatter.png differ
index 8f1b7ced3c6173621a61434ce5d1e96dc88b6d3b..f250fb6a889a3e7a263d332a1254e8476fe6788a 100644 (file)
@@ -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 (file)
index 0000000..df5ff8f
--- /dev/null
@@ -0,0 +1,3 @@
+describe('Plugin.colors', () => {
+  describe('auto', jasmine.fixture.specs('plugin.colors'));
+});
index 2e0369a093a5ba18a1fda50f8f9f4dc16bf4b79d..3c55cf0104db1c12c8da10705f5ec6cdcada2e64 100644 (file)
@@ -3651,6 +3651,7 @@ export interface ChartConfigurationCustomTypesPerDataset<
   TData = DefaultDataPoint<TType>,
   TLabel = unknown
 > {
+  type: TType;
   data: ChartDataCustomTypesPerDataset<TType, TData, TLabel>;
   options?: ChartOptions<TType>;
   plugins?: Plugin<TType>[];