]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Vuepress samples (#8756)
authorEvert Timberg <evert.timberg+github@gmail.com>
Fri, 2 Apr 2021 12:04:39 +0000 (08:04 -0400)
committerGitHub <noreply@github.com>
Fri, 2 Apr 2021 12:04:39 +0000 (08:04 -0400)
* Generate API docs with vuepress-plugin-typedoc

* Links, fixes, cleanup

* Convert bar samples to Vuepress

* Some line chart samples moved over

* Fix lint issues

* Derived axis type sample

* LineAreaStacked chart created in vuepress

* added radar area axample

* Line dataset added sample

* final area example added

* Add derived-chart-type

* Bar scriptable sample

* Scriptable samples

* Clean lint errors

* added linear axis samples to vuepress

* change tab to spaces to fix lint error

* Convert the rest of the scale samples

* Scale option samples

* Fix typo

* Fixes

* Legend samples

* Title samples

* Change the title of the tip block to Note (#8758)

* Convert bar samples to Vuepress

* Some line chart samples moved over

* Fix lint issues

* Derived axis type sample

* LineAreaStacked chart created in vuepress

* added radar area axample

* Line dataset added sample

* final area example added

* Add derived-chart-type

* Bar scriptable sample

* Scriptable samples

* Clean lint errors

* added linear axis samples to vuepress

* change tab to spaces to fix lint error

* Convert the rest of the scale samples

* Scale option samples

* Fix typo

* Fixes

* Legend samples

* Advanced samples

* Remove extra section

* Animation samples

* Hide legend from progressive line

* Add a comment on what `from` does

* Tooltip samples

* Ädd other charts to vuepress samples

* enable plugin again since all samples have been converted

* fix skip radar example, middle skip was not calculated correctly

* lint error

* Progressive-line: add 2nd line

* Fix lint errors

Co-authored-by: Jukka Kurkela <jukka.kurkela@gmail.com>
Co-authored-by: Jacco van den Berg <jaccoberg2281@gmail.com>
Co-authored-by: Jacco van den Berg <39033624+LeeLenaleee@users.noreply.github.com>
72 files changed:
docs/.vuepress/config.js
docs/samples/advanced/derived-axis-type.md [new file with mode: 0644]
docs/samples/advanced/derived-chart-type.md [new file with mode: 0644]
docs/samples/advanced/linear-gradient.md [new file with mode: 0644]
docs/samples/advanced/programmatic-events.md [new file with mode: 0644]
docs/samples/advanced/progress-bar.md [new file with mode: 0644]
docs/samples/advanced/radial-gradient.md [new file with mode: 0644]
docs/samples/animations/delay.md [new file with mode: 0644]
docs/samples/animations/drop.md [new file with mode: 0644]
docs/samples/animations/loop.md [new file with mode: 0644]
docs/samples/animations/progressive-line.md [new file with mode: 0644]
docs/samples/area/line-boundaries.md [new file with mode: 0644]
docs/samples/area/line-datasets.md [new file with mode: 0644]
docs/samples/area/line-stacked.md [new file with mode: 0644]
docs/samples/area/radar.md [new file with mode: 0644]
docs/samples/bar/border-radius.md [new file with mode: 0644]
docs/samples/bar/floating.md [new file with mode: 0644]
docs/samples/bar/horizontal.md [new file with mode: 0644]
docs/samples/bar/stacked-groups.md [new file with mode: 0644]
docs/samples/bar/stacked.md [new file with mode: 0644]
docs/samples/bar/vertical.md [new file with mode: 0644]
docs/samples/index.md [new file with mode: 0644]
docs/samples/legend/point-style.md [new file with mode: 0644]
docs/samples/legend/position.md [new file with mode: 0644]
docs/samples/legend/title.md [new file with mode: 0644]
docs/samples/line/interpolation.md [new file with mode: 0644]
docs/samples/line/line.md [new file with mode: 0644]
docs/samples/line/multi-axis.md [new file with mode: 0644]
docs/samples/line/stepped.md [new file with mode: 0644]
docs/samples/line/styling.md [new file with mode: 0644]
docs/samples/other-charts/bubble.md [new file with mode: 0644]
docs/samples/other-charts/combo-bar-line.md [new file with mode: 0644]
docs/samples/other-charts/doughnut.md [new file with mode: 0644]
docs/samples/other-charts/multi-series-pie.md [new file with mode: 0644]
docs/samples/other-charts/pie.md [new file with mode: 0644]
docs/samples/other-charts/polar-area.md [new file with mode: 0644]
docs/samples/other-charts/radar-skip-points.md [new file with mode: 0644]
docs/samples/other-charts/radar.md [new file with mode: 0644]
docs/samples/other-charts/scatter-multi-axis.md [new file with mode: 0644]
docs/samples/other-charts/scatter.md [new file with mode: 0644]
docs/samples/scale-options/center.md [new file with mode: 0644]
docs/samples/scale-options/grid.md [new file with mode: 0644]
docs/samples/scale-options/ticks.md [new file with mode: 0644]
docs/samples/scale-options/titles.md [new file with mode: 0644]
docs/samples/scales/linear-min-max-suggested.md [new file with mode: 0644]
docs/samples/scales/linear-min-max.md [new file with mode: 0644]
docs/samples/scales/linear-step-size.md [new file with mode: 0644]
docs/samples/scales/log.md [new file with mode: 0644]
docs/samples/scales/time-combo.md [new file with mode: 0644]
docs/samples/scales/time-line.md [new file with mode: 0644]
docs/samples/scales/time-max-span.md [new file with mode: 0644]
docs/samples/scriptable/bar.md [new file with mode: 0644]
docs/samples/scriptable/bubble.md [new file with mode: 0644]
docs/samples/scriptable/line.md [new file with mode: 0644]
docs/samples/scriptable/pie.md [new file with mode: 0644]
docs/samples/scriptable/polar.md [new file with mode: 0644]
docs/samples/scriptable/radar.md [new file with mode: 0644]
docs/samples/title/alignment.md [new file with mode: 0644]
docs/samples/tooltip/content.md [new file with mode: 0644]
docs/samples/tooltip/html.md [new file with mode: 0644]
docs/samples/tooltip/interactions.md [new file with mode: 0644]
docs/samples/tooltip/point-style.md [new file with mode: 0644]
docs/samples/tooltip/position.md [new file with mode: 0644]
docs/scripts/analyzer.js [new file with mode: 0644]
docs/scripts/components.js [new file with mode: 0644]
docs/scripts/derived-bubble.js [new file with mode: 0644]
docs/scripts/helpers.js [new file with mode: 0644]
docs/scripts/log2.js [new file with mode: 0644]
docs/scripts/register.js
docs/scripts/utils.js
package-lock.json
package.json

index 48d3fbc41cca50af713f6b2f025e6f061167bfb4..f538e291fd7eb9e2cc91500eea2e1a27f11e24d5 100644 (file)
@@ -21,7 +21,6 @@ module.exports = {
     ],
     [
       'vuepress-plugin-typedoc',
-
       {
         entryPoints: ['../../types/index.esm.d.ts'],
         hideInPageTOC: true,
@@ -58,13 +57,15 @@ module.exports = {
       imports: [
         ['scripts/register.js'],
         ['scripts/utils.js', 'Utils'],
+        ['scripts/helpers.js', 'helpers'],
+        ['scripts/components.js', 'components']
       ]
     },
     nav: [
       {text: 'Home', link: '/'},
       {text: 'API', link: '/api/'},
       // TODO: Make local when samples moved to vuepress
-      {text: 'Samples', link: `https://www.chartjs.org/samples/${docsVersion}/`},
+      {text: 'Samples', link: `/samples/`},
       {
         text: 'Ecosystem',
         ariaLabel: 'Community Menu',
@@ -79,6 +80,131 @@ module.exports = {
       '/api/': {
         title: 'API'
       },
+      '/samples/': [
+        '',
+        {
+          title: 'Bar Charts',
+          children: [
+            'bar/vertical',
+            'bar/horizontal',
+            'bar/stacked',
+            'bar/stacked-groups',
+            'bar/floating',
+            'bar/border-radius',
+          ]
+        },
+        {
+          title: 'Line Charts',
+          children: [
+            'line/line',
+            'line/multi-axis',
+            'line/stepped',
+            'line/interpolation',
+            'line/styling',
+            // 'line/point-styling',
+          ]
+        },
+        {
+          title: 'Other charts',
+          children: [
+            'other-charts/bubble',
+            'other-charts/scatter',
+            'other-charts/scatter-multi-axis',
+            'other-charts/doughnut',
+            'other-charts/pie',
+            'other-charts/multi-series-pie',
+            'other-charts/polar-area',
+            'other-charts/radar',
+            'other-charts/radar-skip-points',
+            'other-charts/combo-bar-line',
+          ]
+        },
+        {
+          title: 'Area charts',
+          children: [
+            'area/line-boundaries',
+            'area/line-datasets',
+            'area/line-stacked',
+            'area/radar'
+          ]
+        },
+        {
+          title: 'Scales',
+          children: [
+            'scales/linear-min-max',
+            'scales/linear-min-max-suggested',
+            'scales/linear-step-size',
+            'scales/log',
+            'scales/time-line',
+            'scales/time-max-span',
+            'scales/time-combo',
+          ]
+        },
+        {
+          title: 'Scale Options',
+          children: [
+            'scale-options/grid',
+            'scale-options/ticks',
+            'scale-options/titles',
+            'scale-options/center'
+          ]
+        },
+        {
+          title: 'Legend',
+          children: [
+            'legend/position',
+            'legend/title',
+            'legend/point-style',
+          ]
+        },
+        {
+          title: 'Title',
+          children: [
+            'title/alignment',
+          ]
+        },
+        {
+          title: 'Tooltip',
+          children: [
+            'tooltip/position',
+            'tooltip/interactions',
+            'tooltip/point-style',
+            'tooltip/content',
+            'tooltip/html',
+          ]
+        },
+        {
+          title: 'Scriptable Options',
+          children: [
+            'scriptable/bar',
+            'scriptable/bubble',
+            'scriptable/pie',
+            'scriptable/line',
+            'scriptable/polar',
+            'scriptable/radar',
+          ]
+        },
+        {
+          title: 'Animations',
+          children: [
+            'animations/delay',
+            'animations/drop',
+            'animations/loop',
+            'animations/progressive-line',
+          ]
+        },
+        {
+          title: 'Advanced',
+          children: [
+            'advanced/progress-bar',
+            'advanced/radial-gradient',
+            'advanced/linear-gradient',
+            'advanced/programmatic-events',
+            'advanced/derived-axis-type',
+            'advanced/derived-chart-type',
+          ]
+        },
+      ],
       '/': [
         '',
         {
diff --git a/docs/samples/advanced/derived-axis-type.md b/docs/samples/advanced/derived-axis-type.md
new file mode 100644 (file)
index 0000000..178cce7
--- /dev/null
@@ -0,0 +1,50 @@
+# Derived Axis Type
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 12;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 1000};
+const labels = Utils.months({count: DATA_COUNT});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'My First dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      fill: false,
+    }
+  ],
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data,
+  options: {
+    responsive: true,
+    scales: {
+      x: {
+        display: true,
+      },
+      y: {
+        display: true,
+        type: 'log2',
+      }
+    }
+  }
+};
+
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
+
+## Log2 axis implementation
+
+<<< @/docs/scripts/log2.js
diff --git a/docs/samples/advanced/derived-chart-type.md b/docs/samples/advanced/derived-chart-type.md
new file mode 100644 (file)
index 0000000..9633f07
--- /dev/null
@@ -0,0 +1,46 @@
+# Derived Chart Type
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100, rmin: 1, rmax: 20};
+const data = {
+  datasets: [
+    {
+      label: 'My First dataset',
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      borderColor: Utils.CHART_COLORS.blue,
+      borderWidth: 1,
+      boxStrokeStyle: 'red',
+      data: Utils.bubbles(NUMBER_CFG)
+    }
+  ],
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'derivedBubble',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Derived Chart Type'
+      },
+    }
+  }
+};
+
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
+
+## DerivedBubble Implementation
+
+<<< @/docs/scripts/derived-bubble.js
diff --git a/docs/samples/advanced/linear-gradient.md b/docs/samples/advanced/linear-gradient.md
new file mode 100644 (file)
index 0000000..47fa7b5
--- /dev/null
@@ -0,0 +1,110 @@
+# Linear Gradient
+
+```js chart-editor
+// <block:actions:3>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:getGradient:0>
+let width, height, gradient;
+function getGradient(ctx, chartArea) {
+  const chartWidth = chartArea.right - chartArea.left;
+  const chartHeight = chartArea.bottom - chartArea.top;
+  if (gradient === null || width !== chartWidth || height !== chartHeight) {
+    // Create the gradient because this is either the first render
+    // or the size of the chart has changed
+    width = chartWidth;
+    height = chartHeight;
+    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
+    gradient.addColorStop(0, Utils.CHART_COLORS.blue);
+    gradient.addColorStop(0.5, Utils.CHART_COLORS.yellow);
+    gradient.addColorStop(1, Utils.CHART_COLORS.red);
+  }
+
+  return gradient;
+}
+// </block:getGradient>
+
+// <block:setup:2>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const labels = Utils.months({count: 7});
+
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: function(context) {
+        const chart = context.chart;
+        const {ctx, chartArea} = chart;
+
+        if (!chartArea) {
+          // This case happens on initial chart load
+          return null;
+        }
+        return getGradient(ctx, chartArea);
+      },
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:1>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/advanced/programmatic-events.md b/docs/samples/advanced/programmatic-events.md
new file mode 100644 (file)
index 0000000..63a29f0
--- /dev/null
@@ -0,0 +1,103 @@
+# Programmatic Event Triggers
+
+```js chart-editor
+// <block:hover:0>
+function triggerHover(chart) {
+  if (chart.getActiveElements().length > 0) {
+    chart.setActiveElements([]);
+  } else {
+    chart.setActiveElements([
+      {
+        datasetIndex: 0,
+        index: 0,
+      }, {
+        datasetIndex: 1,
+        index: 0,
+      }
+    ]);
+  }
+  chart.update();
+}
+// </block:hover>
+
+// <block:tooltip:1>
+function triggerTooltip(chart) {
+  const tooltip = chart.tooltip;
+  if (tooltip.getActiveElements().length > 0) {
+    tooltip.setActiveElements([], {x: 0, y: 0});
+  } else {
+    const chartArea = chart.chartArea;
+    tooltip.setActiveElements([
+      {
+        datasetIndex: 0,
+        index: 2,
+      }, {
+        datasetIndex: 1,
+        index: 2,
+      }
+    ],
+    {
+      x: (chartArea.left + chartArea.right) / 2,
+      y: (chartArea.top + chartArea.bottom) / 2,
+    });
+  }
+
+  chart.update();
+}
+// </block:tooltip>
+
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Trigger Hover',
+    handler: triggerHover
+  },
+  {
+    name: 'Trigger Tooltip',
+    handler: triggerTooltip
+  }
+];
+// </block:actions>
+
+// <block:setup:4>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      hoverBorderWidth: 5,
+      hoverBorderColor: 'green',
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      hoverBorderWidth: 5,
+      hoverBorderColor: 'green',
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:3>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/advanced/progress-bar.md b/docs/samples/advanced/progress-bar.md
new file mode 100644 (file)
index 0000000..ff86c75
--- /dev/null
@@ -0,0 +1,129 @@
+# Animation Progress Bar
+
+<progress id="animationProgress" max="1" value="0" style="width: 100%"></progress>
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const progress = document.getElementById('animationProgress');
+
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    animation: {
+      duration: 2000,
+      onProgress: function(animation) {
+        progress.value = animation.currentStep / animation.numSteps;
+      },
+      onComplete: function() {
+        //
+      }
+    },
+    interaction: {
+      mode: 'nearest',
+      axis: 'x',
+      intersect: false
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - Animation Progress Bar'
+      }
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/advanced/radial-gradient.md b/docs/samples/advanced/radial-gradient.md
new file mode 100644 (file)
index 0000000..cf8d0d6
--- /dev/null
@@ -0,0 +1,116 @@
+# Radial Gradient
+
+```js chart-editor
+// <block:setup:3>
+const DATA_COUNT = 5;
+Utils.srand(110);
+
+const chartColors = Utils.CHART_COLORS;
+const colors = [chartColors.red, chartColors.orange, chartColors.yellow, chartColors.green, chartColors.blue];
+
+const cache = new Map();
+let width = null;
+let height = null;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+];
+// </block:setup>
+
+// <block:createRadialGradient3:0>
+function createRadialGradient3(context, c1, c2, c3) {
+  const chartArea = context.chart.chartArea;
+  if (!chartArea) {
+    // This case happens on initial chart load
+    return null;
+  }
+
+  const chartWidth = chartArea.right - chartArea.left;
+  const chartHeight = chartArea.bottom - chartArea.top;
+  if (width !== chartWidth || height !== chartHeight) {
+    cache.clear();
+  }
+  var gradient = cache.get(c1 + c2 + c3);
+  if (!gradient) {
+    // Create the gradient because this is either the first render
+    // or the size of the chart has changed
+    width = chartWidth;
+    height = chartHeight;
+    const centerX = (chartArea.left + chartArea.right) / 2;
+    const centerY = (chartArea.top + chartArea.bottom) / 2;
+    const r = Math.min(
+      (chartArea.right - chartArea.left) / 2,
+      (chartArea.bottom - chartArea.top) / 2
+    );
+    var ctx = context.chart.ctx;
+    gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, r);
+    gradient.addColorStop(0, c1);
+    gradient.addColorStop(0.5, c2);
+    gradient.addColorStop(1, c3);
+    cache.set(c1 + c2 + c3, gradient);
+  }
+
+  return gradient;
+}
+// </block:createRadialGradient3>
+
+// <block:data:2>
+function generateData() {
+  return Utils.numbers({
+    count: DATA_COUNT,
+    min: 0,
+    max: 100
+  });
+}
+
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [{
+    data: generateData()
+  }]
+};
+// </block:data>
+
+// <block:config:1>
+const config = {
+  type: 'polarArea',
+  data: data,
+  options: {
+    plugins: {
+      legend: false,
+      tooltip: false,
+    },
+    elements: {
+      arc: {
+        backgroundColor: function(context) {
+          let c = colors[context.dataIndex];
+          if (!c) {
+            return;
+          }
+          if (context.active) {
+            c = helpers.getHoverColor(c);
+          }
+          const mid = helpers.color(c).desaturate(0.2).darken(0.2).rgbString();
+          const start = helpers.color(c).lighten(0.2).rotate(270).rgbString();
+          const end = helpers.color(c).lighten(0.1).rgbString();
+          return createRadialGradient3(context, start, mid, end);
+        },
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/animations/delay.md b/docs/samples/animations/delay.md
new file mode 100644 (file)
index 0000000..3e7a8d4
--- /dev/null
@@ -0,0 +1,79 @@
+# Delay
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.red,
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.blue,
+    },
+    {
+      label: 'Dataset 3',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.green,
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+var delayed;
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    animation: {
+      onComplete: () => {
+        delayed = true;
+      },
+      delay: (context) => {
+        let delay = 0;
+        if (context.type === 'data' && context.mode === 'default' && !delayed) {
+          delay = context.dataIndex * 300 + context.datasetIndex * 100;
+        }
+        return delay;
+      },
+    },
+    scales: {
+      x: {
+        stacked: true,
+      },
+      y: {
+        stacked: true
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/animations/drop.md b/docs/samples/animations/drop.md
new file mode 100644 (file)
index 0000000..ced9517
--- /dev/null
@@ -0,0 +1,126 @@
+# Drop
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      animations: {
+        y: {
+          duration: 2000,
+          delay: 500
+        }
+      },
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      fill: 1,
+      tension: 0.5
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    animations: {
+      y: {
+        easing: 'easeInOutElastic',
+        from: (ctx) => {
+          if (ctx.type === 'data') {
+            if (ctx.mode === 'default' && !ctx.dropped) {
+              ctx.dropped = true;
+              return 0;
+            }
+          }
+        }
+      }
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/animations/loop.md b/docs/samples/animations/loop.md
new file mode 100644 (file)
index 0000000..943962c
--- /dev/null
@@ -0,0 +1,126 @@
+# Loop
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: DATA_COUNT});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      tension: 0.4,
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      tension: 0.2,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    animations: {
+      radius: {
+        duration: 400,
+        easing: 'linear',
+        loop: (context) => context.active
+      }
+    },
+    hoverRadius: 12,
+    hoverBackgroundColor: 'yellow',
+    interaction: {
+      mode: 'nearest',
+      intersect: false,
+      axis: 'x'
+    },
+    plugins: {
+      tooltip: {
+        enabled: false
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/animations/progressive-line.md b/docs/samples/animations/progressive-line.md
new file mode 100644 (file)
index 0000000..6323555
--- /dev/null
@@ -0,0 +1,90 @@
+# Progressive Line
+
+```js chart-editor
+
+// <block:data:2>
+const data = [];
+const data2 = [];
+let prev = 100;
+let prev2 = 80;
+for (let i = 0; i < 1000; i++) {
+  prev += 5 - Math.random() * 10;
+  data.push({x: i, y: prev});
+  prev2 += 5 - Math.random() * 10;
+  data2.push({x: i, y: prev2});
+}
+// </block:data>
+
+// <block:animation:1>
+const totalDuration = 10000;
+const delayBetweenPoints = totalDuration / data.length;
+const previousY = (ctx) => ctx.index === 0 ? ctx.chart.scales.y.getPixelForValue(100) : ctx.chart.getDatasetMeta(ctx.datasetIndex).data[ctx.index - 1].getProps(['y'], true).y;
+const animation = {
+  x: {
+    type: 'number',
+    easing: 'linear',
+    duration: delayBetweenPoints,
+    from: NaN, // the point is initially skipped
+    delay(ctx) {
+      if (ctx.type !== 'data' || ctx.xStarted) {
+        return 0;
+      }
+      ctx.xStarted = true;
+      return ctx.index * delayBetweenPoints;
+    }
+  },
+  y: {
+    type: 'number',
+    easing: 'linear',
+    duration: delayBetweenPoints,
+    from: previousY,
+    delay(ctx) {
+      if (ctx.type !== 'data' || ctx.yStarted) {
+        return 0;
+      }
+      ctx.yStarted = true;
+      return ctx.index * delayBetweenPoints;
+    }
+  }
+};
+// </block:animation>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: {
+    datasets: [{
+      borderColor: Utils.CHART_COLORS.red,
+      borderWidth: 1,
+      radius: 0,
+      data: data,
+    },
+    {
+      borderColor: Utils.CHART_COLORS.blue,
+      borderWidth: 1,
+      radius: 0,
+      data: data2,
+    }]
+  },
+  options: {
+    animation,
+    interaction: {
+      intersect: false
+    },
+    plugins: {
+      legend: false
+    },
+    scales: {
+      x: {
+        type: 'linear'
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  config
+};
+
+```
diff --git a/docs/samples/area/line-boundaries.md b/docs/samples/area/line-boundaries.md
new file mode 100644 (file)
index 0000000..1f2c2fc
--- /dev/null
@@ -0,0 +1,341 @@
+# Line Chart Boundaries
+
+:::: tabs
+
+::: tab "Fill: false"
+
+```js chart-editor
+// <block:setup:2>
+const inputs = {
+  min: -100,
+  max: 100,
+  count: 8,
+  decimals: 2,
+  continuity: 1
+};
+
+const generateLabels = () => {
+  return Utils.months({count: inputs.count});
+};
+
+const generateData = () => (Utils.numbers(inputs));
+// </block:setup>
+
+// <block:data:0>
+const data = {
+  labels: generateLabels(),
+  datasets: [
+    {
+      label: 'Dataset',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+      fill: false
+    }
+  ]
+};
+// </block:data>
+
+// <block:actions:3>
+let smooth = false;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      // Utils.srand(Utils.rand())
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Smooth',
+    handler(chart) {
+      smooth = !smooth;
+      chart.options.elements.line.tension = smooth ? 0.4 : 0;
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:config:1>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      filler: {
+        propagate: false,
+      }
+    },
+    interaction: {
+      intersect: false,
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
+
+:::
+
+::: tab "Fill: origin"
+
+```js chart-editor
+// <block:setup:2>
+const inputs = {
+  min: -100,
+  max: 100,
+  count: 8,
+  decimals: 2,
+  continuity: 1
+};
+
+const generateLabels = () => {
+  return Utils.months({count: inputs.count});
+};
+
+const generateData = () => (Utils.numbers(inputs));
+// </block:setup>
+
+// <block:data:0>
+const data = {
+  labels: generateLabels(),
+  datasets: [
+    {
+      label: 'Dataset',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+      fill: 'origin'
+    }
+  ]
+};
+// </block:data>
+
+// <block:actions:3>
+let smooth = false;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      // Utils.srand(Utils.rand())
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Smooth',
+    handler(chart) {
+      smooth = !smooth;
+      chart.options.elements.line.tension = smooth ? 0.4 : 0;
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:config:1>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      filler: {
+        propagate: false,
+      }
+    },
+    interaction: {
+      intersect: false
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
+
+:::
+
+::: tab "Fill: start"
+
+```js chart-editor
+// <block:setup:1>
+const inputs = {
+  min: -100,
+  max: 100,
+  count: 8,
+  decimals: 2,
+  continuity: 1
+};
+
+const generateLabels = () => {
+  return Utils.months({count: inputs.count});
+};
+
+const generateData = () => (Utils.numbers(inputs));
+// </block:setup>
+
+// <block:data:0>
+const data = {
+  labels: generateLabels(),
+  datasets: [
+    {
+      label: 'Dataset',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+      fill: 'start'
+    }
+  ]
+};
+// </block:data>
+
+// <block:actions:2>
+let smooth = false;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      // Utils.srand(Utils.rand())
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Smooth',
+    handler(chart) {
+      smooth = !smooth;
+      chart.options.elements.line.tension = smooth ? 0.4 : 0;
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      filler: {
+        propagate: false,
+      }
+    },
+    interaction: {
+      intersect: false,
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
+
+:::
+
+::: tab "Fill: end"
+
+```js chart-editor
+// <block:setup:2>
+const inputs = {
+  min: -100,
+  max: 100,
+  count: 8,
+  decimals: 2,
+  continuity: 1
+};
+
+const generateLabels = () => {
+  return Utils.months({count: inputs.count});
+};
+
+const generateData = () => (Utils.numbers(inputs));
+// </block:setup>
+
+// <block:data:0>
+const data = {
+  labels: generateLabels(),
+  datasets: [
+    {
+      label: 'Dataset',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+      fill: 'end'
+    }
+  ]
+};
+// </block:data>
+
+// <block:actions:3>
+let smooth = false;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      // Utils.srand(Utils.rand())
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Smooth',
+    handler(chart) {
+      smooth = !smooth;
+      chart.options.elements.line.tension = smooth ? 0.4 : 0;
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:config:1>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      filler: {
+        propagate: false,
+      }
+    },
+    interaction: {
+      intersect: false,
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
+
+:::
+
+::::
diff --git a/docs/samples/area/line-datasets.md b/docs/samples/area/line-datasets.md
new file mode 100644 (file)
index 0000000..12d89b6
--- /dev/null
@@ -0,0 +1,166 @@
+# Line Chart Datasets
+
+```js chart-editor
+// <block:setup:2>
+const inputs = {
+  min: 20,
+  max: 80,
+  count: 8,
+  decimals: 2,
+  continuity: 1
+};
+
+const generateLabels = () => {
+  return Utils.months({count: inputs.count});
+};
+
+const generateData = () => (Utils.numbers(inputs));
+
+Utils.srand(42);
+// </block:setup>
+
+// <block:data:0>
+const data = {
+  labels: generateLabels(),
+  datasets: [
+    {
+      label: 'D0',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+      hidden: true
+    },
+    {
+      label: 'D1',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.orange,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange),
+      fill: '-1'
+    },
+    {
+      label: 'D2',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.yellow,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.yellow),
+      hidden: true,
+      fill: 1
+    },
+    {
+      label: 'D3',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.green,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green),
+      fill: '-1'
+    },
+    {
+      label: 'D4',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue),
+      fill: '-1'
+    },
+    {
+      label: 'D5',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.grey,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.grey),
+      fill: '+2'
+    },
+    {
+      label: 'D6',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.purple,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.purple),
+      fill: false
+    },
+    {
+      label: 'D7',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+      fill: 8
+    },
+    {
+      label: 'D8',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.orange,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange),
+      fill: 'end',
+      hidden: true
+    },
+    {
+      label: 'D9',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.yellow,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.yellow),
+      fill: {above: 'blue', below: 'red', target: {value: 350}}
+    }
+  ]
+};
+// </block:data>
+
+// <block:actions:3>
+let smooth = false;
+let propagate = false;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Propagate',
+    handler(chart) {
+      propagate = !propagate;
+      chart.options.plugins.filler.propagate = propagate;
+      chart.update();
+    }
+  },
+  {
+    name: 'Smooth',
+    handler(chart) {
+      smooth = !smooth;
+      chart.options.elements.line.tension = smooth ? 0.4 : 0;
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:config:1>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    scales: {
+      y: {
+        stacked: true
+      }
+    },
+    plugins: {
+      filler: {
+        propagate: false
+      },
+      'samples-filler-analyser': {
+        target: 'chart-analyser'
+      }
+    },
+    interaction: {
+      intersect: false,
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
+
+<div id="chart-analyser" class="analyser"></div>
diff --git a/docs/samples/area/line-stacked.md b/docs/samples/area/line-stacked.md
new file mode 100644 (file)
index 0000000..5f59249
--- /dev/null
@@ -0,0 +1,313 @@
+# Line Chart Stacked
+
+:::: tabs
+
+::: tab Stacked
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: dsColor,
+        borderColor: dsColor,
+        fill: true,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'My First dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.CHART_COLORS.red,
+      fill: true
+    },
+    {
+      label: 'My Second dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.CHART_COLORS.blue,
+      fill: true
+    },
+    {
+      label: 'My Third dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.green,
+      backgroundColor: Utils.CHART_COLORS.green,
+      fill: true
+    },
+    {
+      label: 'My Fourth dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.yellow,
+      backgroundColor: Utils.CHART_COLORS.yellow,
+      fill: true
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - stacked=true'
+      },
+      tooltip: {
+        mode: 'index'
+      },
+    },
+    interaction: {
+      mode: 'nearest',
+      axis: 'x',
+      intersect: false
+    },
+    scales: {
+      x: {
+        title: {
+          display: true,
+          text: 'Month'
+        }
+      },
+      y: {
+        stacked: true,
+        title: {
+          display: true,
+          text: 'Value'
+        }
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config
+};
+```
+
+:::
+
+::: tab "Stacked single"
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: dsColor,
+        borderColor: dsColor,
+        fill: true,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'My First dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.CHART_COLORS.red,
+      fill: true
+    },
+    {
+      label: 'My Second dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.CHART_COLORS.blue,
+      fill: true
+    },
+    {
+      label: 'My Third dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.green,
+      backgroundColor: Utils.CHART_COLORS.green,
+      fill: true
+    },
+    {
+      label: 'My Fourth dataset',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.yellow,
+      backgroundColor: Utils.CHART_COLORS.yellow,
+      fill: true
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - stacked=single'
+      },
+      tooltip: {
+        mode: 'index'
+      },
+    },
+    interaction: {
+      mode: 'nearest',
+      axis: 'x',
+      intersect: false
+    },
+    scales: {
+      x: {
+        title: {
+          display: true,
+          text: 'Month'
+        }
+      },
+      y: {
+        stacked: 'single',
+        title: {
+          display: true,
+          text: 'Value'
+        }
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config
+};
+```
+
+:::
+
+::::
diff --git a/docs/samples/area/radar.md b/docs/samples/area/radar.md
new file mode 100644 (file)
index 0000000..48011d0
--- /dev/null
@@ -0,0 +1,141 @@
+# Radar Chart Stacked
+
+```js chart-editor
+// <block:setup:1>
+const inputs = {
+  min: 8,
+  max: 16,
+  count: 8,
+  decimals: 2,
+  continuity: 1
+};
+
+const generateLabels = () => {
+  return Utils.months({count: inputs.count});
+};
+
+const generateData = () => {
+  const values = Utils.numbers(inputs);
+  inputs.from = values;
+  return values;
+};
+
+const labels = Utils.months({count: 8});
+const data = {
+  labels: generateLabels(),
+  datasets: [
+    {
+      label: 'D0',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),
+    },
+    {
+      label: 'D1',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.orange,
+      hidden: true,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange),
+      fill: '-1'
+    },
+    {
+      label: 'D2',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.yellow,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.yellow),
+      fill: 1
+    },
+    {
+      label: 'D3',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.green,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green),
+      fill: false
+    },
+    {
+      label: 'D4',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue),
+      fill: '-1'
+    },
+    {
+      label: 'D5',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.purple,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.purple),
+      fill: '-1'
+    },
+    {
+      label: 'D6',
+      data: generateData(),
+      borderColor: Utils.CHART_COLORS.grey,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.grey),
+      fill: {value: 85}
+    }
+  ]
+};
+// </block:setup>
+
+// <block:actions:2>
+let smooth = false;
+let propagate = false;
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      inputs.from = [];
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Propagate',
+    handler(chart) {
+      propagate = !propagate;
+      chart.options.plugins.filler.propagate = propagate;
+      chart.update();
+
+    }
+  },
+  {
+    name: 'Smooth',
+    handler(chart) {
+      smooth = !smooth;
+      chart.options.elements.line.tension = smooth ? 0.4 : 0;
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:config:0>
+const config = {
+  type: 'radar',
+  data: data,
+  options: {
+    plugins: {
+      filler: {
+        propagate: false
+      },
+      'samples-filler-analyser': {
+        target: 'chart-analyser'
+      }
+    },
+    interaction: {
+      intersect: false
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config
+};
+```
+
+<div id="chart-analyser" class="analyser"></div>
diff --git a/docs/samples/bar/border-radius.md b/docs/samples/bar/border-radius.md
new file mode 100644 (file)
index 0000000..110a97b
--- /dev/null
@@ -0,0 +1,71 @@
+# Bar Chart Border Radius
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Fully Rounded',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      borderWidth: 2,
+      borderRadius: Number.MAX_VALUE,
+      borderSkipped: false,
+    },
+    {
+      label: 'Small Radius',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      borderWidth: 2,
+      borderRadius: 5,
+      borderSkipped: false,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Bar Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/bar/floating.md b/docs/samples/bar/floating.md
new file mode 100644 (file)
index 0000000..b0fb7db
--- /dev/null
@@ -0,0 +1,69 @@
+# Floating Bars
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = chart.data.labels.map(() => {
+          return [Utils.rand(-100, 100), Utils.rand(-100, 100)];
+        });
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: labels.map(() => {
+        return [Utils.rand(-100, 100), Utils.rand(-100, 100)];
+      }),
+      backgroundColor: Utils.CHART_COLORS.red,
+    },
+    {
+      label: 'Dataset 2',
+      data: labels.map(() => {
+        return [Utils.rand(-100, 100), Utils.rand(-100, 100)];
+      }),
+      backgroundColor: Utils.CHART_COLORS.blue,
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Floating Bar Chart'
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/bar/horizontal.md b/docs/samples/bar/horizontal.md
new file mode 100644 (file)
index 0000000..a56eaa0
--- /dev/null
@@ -0,0 +1,123 @@
+# Horizontal Bar Chart
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    indexAxis: 'y',
+    // Elements options apply to all of the options unless overridden in a dataset
+    // In this case, we are setting the border of each horizontal bar to be 2px wide
+    elements: {
+      bar: {
+        borderWidth: 2,
+      }
+    },
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'right',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Horizontal Bar Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/bar/stacked-groups.md b/docs/samples/bar/stacked-groups.md
new file mode 100644 (file)
index 0000000..515900b
--- /dev/null
@@ -0,0 +1,79 @@
+# Stacked Bar Chart with Groups
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.red,
+      stack: 'Stack 0',
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.blue,
+      stack: 'Stack 0',
+    },
+    {
+      label: 'Dataset 3',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.green,
+      stack: 'Stack 1',
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Bar Chart - Stacked'
+      },
+    },
+    responsive: true,
+    interaction: {
+      intersect: false,
+    },
+    scales: {
+      x: {
+        stacked: true,
+      },
+      y: {
+        stacked: true
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/bar/stacked.md b/docs/samples/bar/stacked.md
new file mode 100644 (file)
index 0000000..855a557
--- /dev/null
@@ -0,0 +1,73 @@
+# Stacked Bar Chart
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.red,
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.blue,
+    },
+    {
+      label: 'Dataset 3',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Utils.CHART_COLORS.green,
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Bar Chart - Stacked'
+      },
+    },
+    responsive: true,
+    scales: {
+      x: {
+        stacked: true,
+      },
+      y: {
+        stacked: true
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/bar/vertical.md b/docs/samples/bar/vertical.md
new file mode 100644 (file)
index 0000000..e554056
--- /dev/null
@@ -0,0 +1,115 @@
+# Vertical Bar Chart
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Bar Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/index.md b/docs/samples/index.md
new file mode 100644 (file)
index 0000000..9a7f330
--- /dev/null
@@ -0,0 +1 @@
+# Samples
diff --git a/docs/samples/legend/point-style.md b/docs/samples/legend/point-style.md
new file mode 100644 (file)
index 0000000..c8f4d6f
--- /dev/null
@@ -0,0 +1,59 @@
+# Point Style
+
+This sample show how to use the dataset point style in the legend instead of a rectangle to identify each dataset..
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Toggle Point Style',
+    handler(chart) {
+      chart.options.plugins.legend.labels.usePointStyle = !chart.options.plugins.legend.labels.usePointStyle;
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      borderWidth: 1,
+      pointStyle: 'rectRot',
+      pointRadius: 5,
+      pointBorderColor: 'rgb(0, 0, 0)'
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      legend: {
+        labels: {
+          usePointStyle: true,
+        },
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/legend/position.md b/docs/samples/legend/position.md
new file mode 100644 (file)
index 0000000..a729c77
--- /dev/null
@@ -0,0 +1,68 @@
+# Position
+
+This sample show how to change the position of the chart legend.
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Position: top',
+    handler(chart) {
+      chart.options.plugins.legend.position = 'top';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: right',
+    handler(chart) {
+      chart.options.plugins.legend.position = 'right';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: bottom',
+    handler(chart) {
+      chart.options.plugins.legend.position = 'bottom';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: left',
+    handler(chart) {
+      chart.options.plugins.legend.position = 'left';
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/legend/title.md b/docs/samples/legend/title.md
new file mode 100644 (file)
index 0000000..ebc6856
--- /dev/null
@@ -0,0 +1,74 @@
+# Alignment and Title Position
+
+This sample show how to configure the alignment and title position of the chart legend.
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Title Position: start',
+    handler(chart) {
+      chart.options.plugins.legend.align = 'start';
+      chart.options.plugins.legend.title.position = 'start';
+      chart.update();
+    }
+  },
+  {
+    name: 'Title Position: center (default)',
+    handler(chart) {
+      chart.options.plugins.legend.align = 'center';
+      chart.options.plugins.legend.title.position = 'center';
+      chart.update();
+    }
+  },
+  {
+    name: 'Title Position: end',
+    handler(chart) {
+      chart.options.plugins.legend.align = 'end';
+      chart.options.plugins.legend.title.position = 'end';
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      legend: {
+        title: {
+          display: true,
+          text: 'Legend Title',
+        }
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/line/interpolation.md b/docs/samples/line/interpolation.md
new file mode 100644 (file)
index 0000000..77f287b
--- /dev/null
@@ -0,0 +1,77 @@
+# Interpolation Modes
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 12;
+const labels = [];
+for (let i = 0; i < DATA_COUNT; ++i) {
+  labels.push(i.toString());
+}
+const datapoints = [0, 20, 20, 60, 60, 120, NaN, 180, 120, 125, 105, 110, 170];
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Cubic interpolation (monotone)',
+      data: datapoints,
+      borderColor: Utils.CHART_COLORS.red,
+      fill: false,
+      cubicInterpolationMode: 'monotone',
+      tension: 0.4
+    }, {
+      label: 'Cubic interpolation',
+      data: datapoints,
+      borderColor: Utils.CHART_COLORS.blue,
+      fill: false,
+      tension: 0.4
+    }, {
+      label: 'Linear interpolation (default)',
+      data: datapoints,
+      borderColor: Utils.CHART_COLORS.green,
+      fill: false
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - Cubic interpolation mode'
+      },
+    },
+    interaction: {
+      intersect: false,
+    },
+    scales: {
+      x: {
+        display: true,
+        title: {
+          display: true
+        }
+      },
+      y: {
+        display: true,
+        title: {
+          display: true,
+          text: 'Value'
+        },
+        suggestedMin: -10,
+        suggestedMax: 200
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
diff --git a/docs/samples/line/line.md b/docs/samples/line/line.md
new file mode 100644 (file)
index 0000000..9510ccf
--- /dev/null
@@ -0,0 +1,115 @@
+# Line Chart
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/line/multi-axis.md b/docs/samples/line/multi-axis.md
new file mode 100644 (file)
index 0000000..b9850c2
--- /dev/null
@@ -0,0 +1,86 @@
+# Multi Axis Line Chart
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      yAxisID: 'y',
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      yAxisID: 'y1',
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    interaction: {
+      mode: 'index',
+      intersect: false,
+    },
+    stacked: false,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - Multi Axis'
+      }
+    },
+    scales: {
+      y: {
+        type: 'linear',
+        display: true,
+        position: 'left',
+      },
+      y1: {
+        type: 'linear',
+        display: true,
+        position: 'right',
+
+        // grid line settings
+        grid: {
+          drawOnChartArea: false, // only want the grid lines for one axis to show up
+        },
+      },
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/line/stepped.md b/docs/samples/line/stepped.md
new file mode 100644 (file)
index 0000000..9c5c6ac
--- /dev/null
@@ -0,0 +1,50 @@
+# Stepped Line Charts
+
+```js chart-editor
+// <block:setup:1>
+const data = {
+  labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6'],
+  datasets: [
+    {
+      label: 'Dataset',
+      data: Utils.numbers({count: 6, min: -100, max: 100}),
+      borderColor: Utils.CHART_COLORS.red,
+      fill: false,
+
+      // Change the stepped mode to explore different stepped chart options
+      // false: no stepping
+      // true: stepped before interpolation
+      // 'before': step before interpolation
+      // 'after': step after interpolation
+      // 'middle': step middle interpolation
+      stepped: true,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    interaction: {
+      intersect: false,
+      axis: 'x'
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: (ctx) => 'Step ' + ctx.chart.data.datasets[0].stepped + ' Interpolation',
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
diff --git a/docs/samples/line/styling.md b/docs/samples/line/styling.md
new file mode 100644 (file)
index 0000000..c506166
--- /dev/null
@@ -0,0 +1,76 @@
+# Line Styling
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: DATA_COUNT});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Unfilled',
+      fill: false,
+      backgroundColor: Utils.CHART_COLORS.blue,
+      borderColor: Utils.CHART_COLORS.blue,
+      data: Utils.numbers(NUMBER_CFG),
+    }, {
+      label: 'Dashed',
+      fill: false,
+      backgroundColor: Utils.CHART_COLORS.green,
+      borderColor: Utils.CHART_COLORS.green,
+      borderDash: [5, 5],
+      data: Utils.numbers(NUMBER_CFG),
+    }, {
+      label: 'Filled',
+      backgroundColor: Utils.CHART_COLORS.red,
+      borderColor: Utils.CHART_COLORS.red,
+      data: Utils.numbers(NUMBER_CFG),
+      fill: true,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart'
+      },
+    },
+    interaction: {
+      mode: 'index',
+      intersect: false
+    },
+    scales: {
+      x: {
+        display: true,
+        title: {
+          display: true,
+          text: 'Month'
+        }
+      },
+      y: {
+        display: true,
+        title: {
+          display: true,
+          text: 'Value'
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/bubble.md b/docs/samples/other-charts/bubble.md
new file mode 100644 (file)
index 0000000..3d330fb
--- /dev/null
@@ -0,0 +1,113 @@
+# Bubble
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.bubbles({count: chart.data.labels.length, rmin: 5, rmax: 15, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        data: Utils.bubbles({count: data.labels.length, rmin: 5, rmax: 15, min: 0, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.bubbles({count: 1, rmin: 5, rmax: 15, min: 0, max: 100})[0]);
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, rmin: 5, rmax: 15, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.bubbles(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.bubbles(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.orange,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bubble',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Bubble Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/combo-bar-line.md b/docs/samples/other-charts/combo-bar-line.md
new file mode 100644 (file)
index 0000000..16013d9
--- /dev/null
@@ -0,0 +1,118 @@
+# Combo bar/line
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        borderWidth: 1,
+        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(-100, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      order: 1
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      type: 'line',
+      order: 0
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Combined Line/Bar Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/doughnut.md b/docs/samples/other-charts/doughnut.md
new file mode 100644 (file)
index 0000000..bbe7d9f
--- /dev/null
@@ -0,0 +1,112 @@
+# Doughnut
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: [],
+        data: [],
+      };
+
+      for (let i = 0; i < data.labels.length; i++) {
+        newDataset.data.push(Utils.numbers({count: 1, min: 0, max: 100}));
+
+        const colorIndex = i % Object.keys(Utils.CHART_COLORS).length;
+        newDataset.backgroundColor.push(Object.values(Utils.CHART_COLORS)[colorIndex]);
+      }
+
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels.push('data #' + (data.labels.length + 1));
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(0, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 5;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const data = {
+  labels: ['Red', 'Orange', 'Yellow', 'Green', 'Blue'],
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Object.values(Utils.CHART_COLORS),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'doughnut',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Doughnut Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/multi-series-pie.md b/docs/samples/other-charts/multi-series-pie.md
new file mode 100644 (file)
index 0000000..4a7d11e
--- /dev/null
@@ -0,0 +1,91 @@
+# Multi Series Pie
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 5;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: ['Overall Yay', 'Overall Nay', 'Group A Yay', 'Group A Nay', 'Group B Yay', 'Group B Nay', 'Group C Yay', 'Group C Nay'],
+  datasets: [
+    {
+      backgroundColor: ['#AAA', '#777'],
+      data: [21, 79]
+    },
+    {
+      backgroundColor: ['hsl(0, 100%, 60%)', 'hsl(0, 100%, 35%)'],
+      data: [33, 67]
+    },
+    {
+      backgroundColor: ['hsl(100, 100%, 60%)', 'hsl(100, 100%, 35%)'],
+      data: [20, 80]
+    },
+    {
+      backgroundColor: ['hsl(180, 100%, 60%)', 'hsl(180, 100%, 35%)'],
+      data: [10, 90]
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'pie',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        labels: {
+          generateLabels: function(chart) {
+            // Get the default label list
+            const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
+            const labelsOriginal = original.call(this, chart);
+
+            // Build an array of colors used in the datasets of the chart
+            var datasetColors = chart.data.datasets.map(function(e) {
+              return e.backgroundColor;
+            });
+            datasetColors = datasetColors.flat();
+
+            // Modify the color and hide state of each label
+            labelsOriginal.forEach(label => {
+              // There are twice as many labels as there are datasets. This converts the label index into the corresponding dataset index
+              label.datasetIndex = (label.index - label.index % 2) / 2;
+
+              // The hidden state must match the dataset's hidden state
+              label.hidden = !chart.isDatasetVisible(label.datasetIndex);
+
+              // Change the color to match the dataset
+              label.fillStyle = datasetColors[label.index];
+            });
+
+            return labelsOriginal;
+          }
+        },
+        onClick: function(mouseEvent, legendItem, legend) {
+          // toggle the visibility of the dataset from what it currently is
+          legend.chart.getDatasetMeta(
+            legendItem.datasetIndex
+          ).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);
+          legend.chart.update();
+        }
+      },
+      tooltip: {
+        callbacks: {
+          label: function(context) {
+            const labelIndex = (context.datasetIndex * 2) + context.dataIndex;
+            return context.chart.data.labels[labelIndex] + ': ' + context.formattedValue;
+          }
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/pie.md b/docs/samples/other-charts/pie.md
new file mode 100644 (file)
index 0000000..3578625
--- /dev/null
@@ -0,0 +1,112 @@
+# Pie
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: [],
+        data: [],
+      };
+
+      for (let i = 0; i < data.labels.length; i++) {
+        newDataset.data.push(Utils.numbers({count: 1, min: 0, max: 100}));
+
+        const colorIndex = i % Object.keys(Utils.CHART_COLORS).length;
+        newDataset.backgroundColor.push(Object.values(Utils.CHART_COLORS)[colorIndex]);
+      }
+
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels.push('data #' + (data.labels.length + 1));
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(0, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 5;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const data = {
+  labels: ['Red', 'Orange', 'Yellow', 'Green', 'Blue'],
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: Object.values(Utils.CHART_COLORS),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'pie',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Pie Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/polar-area.md b/docs/samples/other-charts/polar-area.md
new file mode 100644 (file)
index 0000000..826a399
--- /dev/null
@@ -0,0 +1,91 @@
+# Polar area
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels.push('data #' + (data.labels.length + 1));
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(0, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 5;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = ['Red', 'Orange', 'Yellow', 'Green', 'Blue'];
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      backgroundColor: [
+        Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+        Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),
+        Utils.transparentize(Utils.CHART_COLORS.yellow, 0.5),
+        Utils.transparentize(Utils.CHART_COLORS.green, 0.5),
+        Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      ]
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'polarArea',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Polar Area Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/radar-skip-points.md b/docs/samples/other-charts/radar-skip-points.md
new file mode 100644 (file)
index 0000000..33f0bf1
--- /dev/null
@@ -0,0 +1,87 @@
+# Radar skip points
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach((dataset, i) => {
+        const data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+
+        if (i === 0) {
+          data[0] = null;
+        } else if (i === 1) {
+          data[Number.parseInt(data.length / 2, 10)] = null;
+        } else {
+          data[data.length - 1] = null;
+        }
+
+        dataset.data = data;
+      });
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const dataFirstSkip = Utils.numbers(NUMBER_CFG);
+const dataMiddleSkip = Utils.numbers(NUMBER_CFG);
+const dataLastSkip = Utils.numbers(NUMBER_CFG);
+
+dataFirstSkip[0] = null;
+dataMiddleSkip[Number.parseInt(dataMiddleSkip.length / 2, 10)] = null;
+dataLastSkip[dataLastSkip.length - 1] = null;
+
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Skip first dataset',
+      data: dataFirstSkip,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Skip mid dataset',
+      data: dataMiddleSkip,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    },
+    {
+      label: 'Skip last dataset',
+      data: dataLastSkip,
+      borderColor: Utils.CHART_COLORS.green,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'radar',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Radar Skip Points Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config
+};
+```
+
diff --git a/docs/samples/other-charts/radar.md b/docs/samples/other-charts/radar.md
new file mode 100644 (file)
index 0000000..e6fc7a8
--- /dev/null
@@ -0,0 +1,111 @@
+# Radar
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        data: Utils.numbers({count: data.labels.length, min: 0, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(0, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'radar',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Radar Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/scatter-multi-axis.md b/docs/samples/other-charts/scatter-multi-axis.md
new file mode 100644 (file)
index 0000000..9241d72
--- /dev/null
@@ -0,0 +1,135 @@
+# Scatter - Multi axis
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.bubbles({count: chart.data.labels.length, rmin: 1, rmax: 1, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        data: Utils.bubbles({count: data.labels.length, rmin: 1, rmax: 1, min: -100, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.bubbles({count: 1, rmin: 1, rmax: 1, min: -100, max: 100})[0]);
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, rmin: 1, rmax: 1, min: -100, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.bubbles(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      yAxisID: 'y',
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.bubbles(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.orange,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),
+      yAxisID: 'y2',
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'scatter',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Scatter Multi Axis Chart'
+      }
+    },
+    scales: {
+      y: {
+        type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+        position: 'left',
+        ticks: {
+          color: Utils.CHART_COLORS.red
+        }
+      },
+      y2: {
+        type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+        position: 'right',
+        reverse: true,
+        ticks: {
+          color: Utils.CHART_COLORS.blue
+        },
+        grid: {
+          drawOnChartArea: false // only want the grid lines for one axis to show up
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/other-charts/scatter.md b/docs/samples/other-charts/scatter.md
new file mode 100644 (file)
index 0000000..d669057
--- /dev/null
@@ -0,0 +1,113 @@
+# Scatter
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.bubbles({count: chart.data.labels.length, rmin: 1, rmax: 1, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: Utils.transparentize(dsColor, 0.5),
+        borderColor: dsColor,
+        data: Utils.bubbles({count: data.labels.length, rmin: 1, rmax: 1, min: 0, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.bubbles({count: 1, rmin: 1, rmax: 1, min: 0, max: 100})[0]);
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, rmin: 1, rmax: 1, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.bubbles(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.bubbles(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.orange,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'scatter',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: 'top',
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Scatter Chart'
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scale-options/center.md b/docs/samples/scale-options/center.md
new file mode 100644 (file)
index 0000000..19a509c
--- /dev/null
@@ -0,0 +1,89 @@
+# Center Positioning
+
+This sample show how to place the axis in the center of the chart area, instead of at the edges.
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Default Positions',
+    handler(chart) {
+      chart.options.scales.x.position = 'bottom';
+      chart.options.scales.y.position = 'left';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: center',
+    handler(chart) {
+      chart.options.scales.x.position = 'center';
+      chart.options.scales.y.position = 'center';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: Vertical: x=-60, Horizontal: y=30',
+    handler(chart) {
+      chart.options.scales.x.position = {y: 30};
+      chart.options.scales.y.position = {x: -60};
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+
+// <block:setup:1>
+const DATA_COUNT = 6;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.points(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.points(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'scatter',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Axis Center Positioning'
+      }
+    },
+    scales: {
+      x: {
+        min: -100,
+        max: 100,
+      },
+      y: {
+        min: -100,
+        max: 100,
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/scale-options/grid.md b/docs/samples/scale-options/grid.md
new file mode 100644 (file)
index 0000000..68c030a
--- /dev/null
@@ -0,0 +1,94 @@
+# Grid Configuration
+
+This sample shows how to use scriptable grid options for an axis to control styling. In this case, the Y axis grid lines are colored based on their value. In addition, booleans are provided to toggle different parts of the X axis grid visibility.
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: [10, 30, 39, 20, 25, 34, -10],
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: [18, 33, 22, 19, 11, -39, 30],
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+// Change these settings to change the display for different parts of the X axis
+// grid configuiration
+const DISPLAY = true;
+const BORDER = true;
+const CHART_AREA = true;
+const TICKS = true;
+
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Grid Line Settings'
+      }
+    },
+    scales: {
+      x: {
+        grid: {
+          display: DISPLAY,
+          drawBorder: BORDER,
+          drawOnChartArea: CHART_AREA,
+          drawTicks: TICKS,
+        }
+      },
+      y: {
+        grid: {
+          drawBorder: false,
+          color: function(context) {
+            if (context.tick.value > 0) {
+              return Utils.CHART_COLORS.green;
+            } else if (context.tick.value < 0) {
+              return Utils.CHART_COLORS.red;
+            }
+
+            return '#000000';
+          },
+        },
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scale-options/ticks.md b/docs/samples/scale-options/ticks.md
new file mode 100644 (file)
index 0000000..110047e
--- /dev/null
@@ -0,0 +1,94 @@
+# Tick Configuration
+
+This sample shows how to use different tick features to control how tick labels are shown on the X axis. These features include:
+
+* Multi-line labels
+* Filtering labels
+* Changing the tick color
+* Changing the tick alignment for the X axis
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Alignment: start',
+    handler(chart) {
+      chart.options.scales.x.ticks.align = 'start';
+      chart.update();
+    }
+  },
+  {
+    name: 'Alignment: center (default)',
+    handler(chart) {
+      chart.options.scales.x.ticks.align = 'center';
+      chart.update();
+    }
+  },
+  {
+    name: 'Alignment: end',
+    handler(chart) {
+      chart.options.scales.x.ticks.align = 'end';
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+
+// <block:setup:1>
+const DATA_COUNT = 12;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+const data = {
+  labels: [['June', '2015'], 'July', 'August', 'September', 'October', 'November', 'December', ['January', '2016'], 'February', 'March', 'April', 'May'],
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart with Tick Configuration'
+      }
+    },
+    scales: {
+      x: {
+        ticks: {
+          // For a category axis, the val is the index so the lookup via getLabelForValue is needed
+          callback: function(val, index) {
+            // Hide the label of every 2nd dataset
+            return index % 2 === 0 ? this.getLabelForValue(val) : '';
+          },
+          color: 'red',
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scale-options/titles.md b/docs/samples/scale-options/titles.md
new file mode 100644 (file)
index 0000000..fdb1ea9
--- /dev/null
@@ -0,0 +1,76 @@
+# Title Configuration
+
+This sample shows how to configure the title of an axis including alignment, font, and color.
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    scales: {
+      x: {
+        display: true,
+        title: {
+          display: true,
+          text: 'Month',
+          color: '#911',
+          font: {
+            family: 'Comic Sans MS',
+            size: 20,
+            style: 'bold',
+            lineHeight: 1.2,
+          },
+          padding: {top: 20, left: 0, right: 0, bottom: 0}
+        }
+      },
+      y: {
+        display: true,
+        title: {
+          display: true,
+          text: 'Value',
+          color: '#191',
+          font: {
+            family: 'Times',
+            size: 20,
+            style: 'normal',
+            lineHeight: 1.2,
+          },
+          padding: {top: 30, left: 0, right: 0, bottom: 0}
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
diff --git a/docs/samples/scales/linear-min-max-suggested.md b/docs/samples/scales/linear-min-max-suggested.md
new file mode 100644 (file)
index 0000000..a297fd9
--- /dev/null
@@ -0,0 +1,56 @@
+# Linear Scale - Suggested Min-Max
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: [10, 30, 39, 20, 25, 34, -10],
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.CHART_COLORS.red,
+    },
+    {
+      label: 'Dataset 2',
+      data: [18, 33, 22, 19, 11, 39, 30],
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.CHART_COLORS.blue,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Suggested Min and Max Settings'
+      }
+    },
+    scales: {
+      y: {
+        // the data minimum used for determining the ticks is Math.min(dataMin, suggestedMin)
+        suggestedMin: 30,
+
+        // the data maximum used for determining the ticks is Math.max(dataMax, suggestedMax)
+        suggestedMax: 50,
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  config: config,
+};
+```
diff --git a/docs/samples/scales/linear-min-max.md b/docs/samples/scales/linear-min-max.md
new file mode 100644 (file)
index 0000000..6a8910b
--- /dev/null
@@ -0,0 +1,53 @@
+# Linear Scale - Min-Max
+
+```js chart-editor
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: [10, 30, 50, 20, 25, 44, -10],
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.CHART_COLORS.red,
+    },
+    {
+      label: 'Dataset 2',
+      data: [100, 33, 22, 19, 11, 49, 30],
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.CHART_COLORS.blue,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Min and Max Settings'
+      }
+    },
+    scales: {
+      y: {
+        min: 10,
+        max: 50,
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  config: config,
+};
+```
diff --git a/docs/samples/scales/linear-step-size.md b/docs/samples/scales/linear-step-size.md
new file mode 100644 (file)
index 0000000..049b0ae
--- /dev/null
@@ -0,0 +1,139 @@
+# Linear Scale - Step Size
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Dataset',
+    handler(chart) {
+      const data = chart.data;
+      const dsColor = Utils.namedColor(chart.data.datasets.length);
+      const newDataset = {
+        label: 'Dataset ' + (data.datasets.length + 1),
+        backgroundColor: dsColor,
+        borderColor: dsColor,
+        data: Utils.numbers({count: data.labels.length, min: 0, max: 100}),
+      };
+      chart.data.datasets.push(newDataset);
+      chart.update();
+    }
+  },
+  {
+    name: 'Add Data',
+    handler(chart) {
+      const data = chart.data;
+      if (data.datasets.length > 0) {
+        data.labels = Utils.months({count: data.labels.length + 1});
+
+        for (var index = 0; index < data.datasets.length; ++index) {
+          data.datasets[index].data.push(Utils.rand(0, 100));
+        }
+
+        chart.update();
+      }
+    }
+  },
+  {
+    name: 'Remove Dataset',
+    handler(chart) {
+      chart.data.datasets.pop();
+      chart.update();
+    }
+  },
+  {
+    name: 'Remove Data',
+    handler(chart) {
+      chart.data.labels.splice(-1, 1); // remove the label first
+
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.pop();
+      });
+
+      chart.update();
+    }
+  }
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.CHART_COLORS.red,
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.CHART_COLORS.blue,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      tooltip: {
+        mode: 'index',
+        intersect: false
+      },
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart'
+      }
+    },
+    hover: {
+      mode: 'index',
+      intersec: false
+    },
+    scales: {
+      x: {
+        title: {
+          display: true,
+          text: 'Month'
+        }
+      },
+      y: {
+        title: {
+          display: true,
+          text: 'Value'
+        },
+        min: 0,
+        max: 100,
+        ticks: {
+          // forces step size to be 50 units
+          stepSize: 50
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scales/log.md b/docs/samples/scales/log.md
new file mode 100644 (file)
index 0000000..d08f55e
--- /dev/null
@@ -0,0 +1,76 @@
+# Log Scale
+
+```js chart-editor
+// <block:actions:2>
+const logNumbers = (num) => {
+  const data = [];
+
+  for (let i = 0; i < num; ++i) {
+    data.push(Math.ceil(Math.random() * 10.0) * Math.pow(10, Math.ceil(Math.random() * 5)));
+  }
+
+  return data;
+};
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = logNumbers(chart.data.labels.length);
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = Utils.months({count: 7});
+const data = {
+  labels: labels,
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: logNumbers(DATA_COUNT),
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.CHART_COLORS.red,
+      fill: false,
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    responsive: true,
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - Logarithmic'
+      }
+    },
+    scales: {
+      x: {
+        display: true,
+      },
+      y: {
+        display: true,
+        type: 'logarithmic',
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scales/time-combo.md b/docs/samples/scales/time-combo.md
new file mode 100644 (file)
index 0000000..abd726a
--- /dev/null
@@ -0,0 +1,82 @@
+# Time Scale - Combo Chart
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const labels = [];
+
+for (let i = 0; i < DATA_COUNT; ++i) {
+  labels.push(Utils.newDate(i));
+}
+
+const data = {
+  labels: labels,
+  datasets: [{
+    type: 'bar',
+    label: 'Dataset 1',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    borderColor: Utils.CHART_COLORS.red,
+    data: Utils.numbers(NUMBER_CFG),
+  }, {
+    type: 'bar',
+    label: 'Dataset 2',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    borderColor: Utils.CHART_COLORS.blue,
+    data: Utils.numbers(NUMBER_CFG),
+  }, {
+    type: 'line',
+    label: 'Dataset 3',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),
+    borderColor: Utils.CHART_COLORS.green,
+    fill: false,
+    data: Utils.numbers(NUMBER_CFG),
+  }]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      title: {
+        text: 'Chart.js Combo Time Scale',
+        display: true
+      }
+    },
+    scales: {
+      x: {
+        type: 'time',
+        display: true,
+        offset: true,
+        time: {
+          unit: 'day'
+        }
+      },
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scales/time-line.md b/docs/samples/scales/time-line.md
new file mode 100644 (file)
index 0000000..a981b96
--- /dev/null
@@ -0,0 +1,112 @@
+# Time Scale
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.forEach(function(dataObj, j) {
+          const newVal = Utils.rand(0, 100);
+
+          if (typeof dataObj === 'object') {
+            dataObj.y = newVal;
+          } else {
+            dataset.data[j] = newVal;
+          }
+        });
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};
+
+const data = {
+  labels: [ // Date Objects
+    Utils.newDate(0),
+    Utils.newDate(1),
+    Utils.newDate(2),
+    Utils.newDate(3),
+    Utils.newDate(4),
+    Utils.newDate(5),
+    Utils.newDate(6)
+  ],
+  datasets: [{
+    label: 'My First dataset',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    borderColor: Utils.CHART_COLORS.red,
+    fill: false,
+    data: Utils.numbers(NUMBER_CFG),
+  }, {
+    label: 'My Second dataset',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    borderColor: Utils.CHART_COLORS.blue,
+    fill: false,
+    data: Utils.numbers(NUMBER_CFG),
+  }, {
+    label: 'Dataset with point data',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),
+    borderColor: Utils.CHART_COLORS.green,
+    fill: false,
+    data: [{
+      x: Utils.newDateString(0),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDateString(5),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDateString(7),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDateString(15),
+      y: Utils.rand(0, 100)
+    }],
+  }]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      title: {
+        text: 'Chart.js Time Scale',
+        display: true
+      }
+    },
+    scales: {
+      x: {
+        type: 'time',
+        time: {
+          // Luxon format string
+          tooltipFormat: 'DD T'
+        },
+        title: {
+          display: true,
+          text: 'Date'
+        }
+      },
+      y: {
+        title: {
+          display: true,
+          text: 'value'
+        }
+      }
+    },
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
diff --git a/docs/samples/scales/time-max-span.md b/docs/samples/scales/time-max-span.md
new file mode 100644 (file)
index 0000000..dc144c9
--- /dev/null
@@ -0,0 +1,126 @@
+# Time Scale - Max Span
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data.forEach(function(dataObj, j) {
+          const newVal = Utils.rand(0, 100);
+
+          if (typeof dataObj === 'object') {
+            dataObj.y = newVal;
+          } else {
+            dataset.data[j] = newVal;
+          }
+        });
+      });
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const data = {
+  datasets: [{
+    label: 'Dataset with string point data',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    borderColor: Utils.CHART_COLORS.red,
+    fill: false,
+    data: [{
+      x: Utils.newDateString(0),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDateString(2),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDateString(4),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDateString(6),
+      y: Utils.rand(0, 100)
+    }],
+  }, {
+    label: 'Dataset with date object point data',
+    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    borderColor: Utils.CHART_COLORS.blue,
+    fill: false,
+    data: [{
+      x: Utils.newDate(0),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDate(2),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDate(5),
+      y: Utils.rand(0, 100)
+    }, {
+      x: Utils.newDate(6),
+      y: Utils.rand(0, 100)
+    }]
+  }]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    spanGaps: 1000 * 60 * 60 * 24 * 2, // 2 days
+    responsive: true,
+    interaction: {
+      mode: 'nearest',
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Time - spanGaps: 172800000 (2 days in ms)'
+      },
+    },
+    scales: {
+      x: {
+        type: 'time',
+        display: true,
+        title: {
+          display: true,
+          text: 'Date'
+        },
+        ticks: {
+          autoSkip: false,
+          maxRotation: 0,
+          major: {
+            enabled: true
+          },
+          // color: function(context) {
+          //   return context.tick && context.tick.major ? '#FF0000' : 'rgba(0,0,0,0.1)';
+          // },
+          font: function(context) {
+            if (context.tick && context.tick.major) {
+              return {
+                style: 'bold',
+              };
+            }
+          }
+        }
+      },
+      y: {
+        display: true,
+        title: {
+          display: true,
+          text: 'value'
+        }
+      }
+    }
+  },
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
diff --git a/docs/samples/scriptable/bar.md b/docs/samples/scriptable/bar.md
new file mode 100644 (file)
index 0000000..8842c6a
--- /dev/null
@@ -0,0 +1,73 @@
+# Bar Chart
+
+```js chart-editor
+// <block:setup:2>
+var DATA_COUNT = 16;
+Utils.srand(110);
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+];
+// </block:setup>
+
+// <block:data:1>
+function generateData() {
+  return Utils.numbers({
+    count: DATA_COUNT,
+    min: -100,
+    max: 100
+  });
+}
+
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [{
+    data: generateData(),
+  }]
+};
+// </block:data>
+
+// <block:options:0>
+function colorize(opaque) {
+  return (ctx) => {
+    var v = ctx.parsed.y;
+    var c = v < -50 ? '#D60000'
+      : v < 0 ? '#F46300'
+      : v < 50 ? '#0358B6'
+      : '#44DE28';
+
+    return opaque ? c : Utils.transparentize(c, 1 - Math.abs(v / 150));
+  };
+}
+
+const config = {
+  type: 'bar',
+  data: data,
+  options: {
+    plugins: {
+      legend: false,
+    },
+    elements: {
+      bar: {
+        backgroundColor: colorize(false),
+        borderColor: colorize(true),
+        borderWidth: 2
+      }
+    }
+  }
+};
+// </block:options>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/scriptable/bubble.md b/docs/samples/scriptable/bubble.md
new file mode 100644 (file)
index 0000000..d466172
--- /dev/null
@@ -0,0 +1,109 @@
+# Bubble Chart
+
+```js chart-editor
+// <block:setup:2>
+const DATA_COUNT = 16;
+const MIN_XY = -150;
+const MAX_XY = 100;
+Utils.srand(110);
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+];
+// </block:setup>
+
+// <block:data:1>
+function generateData() {
+  var data = [];
+  var i;
+
+  for (i = 0; i < DATA_COUNT; ++i) {
+    data.push({
+      x: Utils.rand(MIN_XY, MAX_XY),
+      y: Utils.rand(MIN_XY, MAX_XY),
+      v: Utils.rand(0, 1000)
+    });
+  }
+
+  return data;
+}
+
+const data = {
+  datasets: [{
+    data: generateData()
+  }, {
+    data: generateData()
+  }]
+};
+// </block:data>
+
+// <block:options:0>
+function channelValue(x, y, values) {
+  return x < 0 && y < 0 ? values[0] : x < 0 ? values[1] : y < 0 ? values[2] : values[3];
+}
+
+function colorize(opaque, context) {
+  var value = context.raw;
+  var x = value.x / 100;
+  var y = value.y / 100;
+  var r = channelValue(x, y, [250, 150, 50, 0]);
+  var g = channelValue(x, y, [0, 50, 150, 250]);
+  var b = channelValue(x, y, [0, 150, 150, 250]);
+  var a = opaque ? 1 : 0.5 * value.v / 1000;
+
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
+}
+
+const config = {
+  type: 'bubble',
+  data: data,
+  options: {
+    aspectRatio: 1,
+    plugins: {
+      legend: false,
+      tooltip: false,
+    },
+    elements: {
+      point: {
+        backgroundColor: colorize.bind(null, false),
+
+        borderColor: colorize.bind(null, true),
+
+        borderWidth: function(context) {
+          return Math.min(Math.max(1, context.datasetIndex + 1), 8);
+        },
+
+        hoverBackgroundColor: 'transparent',
+
+        hoverBorderColor: function(context) {
+          return Utils.color(context.datasetIndex);
+        },
+
+        hoverBorderWidth: function(context) {
+          return Math.round(8 * context.raw.v / 1000);
+        },
+
+        radius: function(context) {
+          var size = context.chart.width;
+          var base = Math.abs(context.raw.v) / 1000;
+          return (size / 24) * base;
+        }
+      }
+    }
+  }
+};
+// </block:options>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/scriptable/line.md b/docs/samples/scriptable/line.md
new file mode 100644 (file)
index 0000000..beeff9e
--- /dev/null
@@ -0,0 +1,91 @@
+# Line Chart
+
+```js chart-editor
+// <block:setup:2>
+const DATA_COUNT = 12;
+Utils.srand(110);
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+];
+// </block:setup>
+
+// <block:data:1>
+function generateData() {
+  return Utils.numbers({
+    count: DATA_COUNT,
+    min: 0,
+    max: 100
+  });
+}
+
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [{
+    data: generateData()
+  }]
+};
+// </block:data>
+
+// <block:options:0>
+function getLineColor(ctx) {
+  return Utils.color(ctx.datasetIndex);
+}
+
+function alternatePointStyles(ctx) {
+  var index = ctx.dataIndex;
+  return index % 2 === 0 ? 'circle' : 'rect';
+}
+
+function makeHalfAsOpaque(ctx) {
+  return Utils.transparentize(getLineColor(ctx));
+}
+
+function adjustRadiusBasedOnData(ctx) {
+  var v = ctx.parsed.y;
+  return v < 10 ? 5
+    : v < 25 ? 7
+    : v < 50 ? 9
+    : v < 75 ? 11
+    : 15;
+}
+
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      legend: false,
+      tooltip: true,
+    },
+    elements: {
+      line: {
+        fill: false,
+        backgroundColor: getLineColor,
+        borderColor: getLineColor,
+      },
+      point: {
+        backgroundColor: getLineColor,
+        hoverBackgroundColor: makeHalfAsOpaque,
+        radius: adjustRadiusBasedOnData,
+        pointStyle: alternatePointStyles,
+        hoverRadius: 15,
+      }
+    }
+  }
+};
+// </block:options>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/scriptable/pie.md b/docs/samples/scriptable/pie.md
new file mode 100644 (file)
index 0000000..423e213
--- /dev/null
@@ -0,0 +1,87 @@
+# Pie Chart
+
+```js chart-editor
+// <block:setup:2>
+const DATA_COUNT = 5;
+Utils.srand(110);
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+  {
+    name: 'Toggle Doughnut View',
+    handler(chart) {
+      if (chart.options.cutout) {
+        chart.options.cutout = 0;
+      } else {
+        chart.options.cutout = '50%';
+      }
+      chart.update();
+    }
+  }
+];
+// </block:setup>
+
+// <block:data:1>
+function generateData() {
+  return Utils.numbers({
+    count: DATA_COUNT,
+    min: -100,
+    max: 100
+  });
+}
+
+const data = {
+  datasets: [{
+    data: generateData()
+  }]
+};
+// </block:data>
+
+// <block:options:0>
+function colorize(opaque, hover, ctx) {
+  var v = ctx.parsed;
+  var c = v < -50 ? '#D60000'
+    : v < 0 ? '#F46300'
+    : v < 50 ? '#0358B6'
+    : '#44DE28';
+
+  var opacity = hover ? 1 - Math.abs(v / 150) - 0.2 : 1 - Math.abs(v / 150);
+
+  return opaque ? c : Utils.transparentize(c, opacity);
+}
+
+function hoverColorize(ctx) {
+  return colorize(false, true, ctx);
+}
+
+const config = {
+  type: 'pie',
+  data: data,
+  options: {
+    plugins: {
+      legend: false,
+      tooltip: false,
+    },
+    elements: {
+      arc: {
+        backgroundColor: colorize.bind(null, false, false),
+        hoverBackgroundColor: hoverColorize
+      }
+    }
+  }
+};
+// </block:options>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/scriptable/polar.md b/docs/samples/scriptable/polar.md
new file mode 100644 (file)
index 0000000..4fe6e04
--- /dev/null
@@ -0,0 +1,77 @@
+# Polar Area Chart
+
+```js chart-editor
+// <block:setup:2>
+const DATA_COUNT = 7;
+Utils.srand(110);
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+];
+// </block:setup>
+
+// <block:data:1>
+function generateData() {
+  return Utils.numbers({
+    count: DATA_COUNT,
+    min: 0,
+    max: 100
+  });
+}
+
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [{
+    data: generateData()
+  }]
+};
+// </block:data>
+
+// <block:options:0>
+function colorize(opaque, hover, ctx) {
+  var v = ctx.raw;
+  var c = v < 35 ? '#D60000'
+    : v < 55 ? '#F46300'
+    : v < 75 ? '#0358B6'
+    : '#44DE28';
+
+  var opacity = hover ? 1 - Math.abs(v / 150) - 0.2 : 1 - Math.abs(v / 150);
+
+  return opaque ? c : Utils.transparentize(c, opacity);
+}
+
+function hoverColorize(ctx) {
+  return colorize(false, true, ctx);
+}
+
+const config = {
+  type: 'polarArea',
+  data: data,
+  options: {
+    plugins: {
+      legend: false,
+      tooltip: false,
+    },
+    elements: {
+      arc: {
+        backgroundColor: colorize.bind(null, false, false),
+        hoverBackgroundColor: hoverColorize
+      }
+    }
+  }
+};
+// </block:options>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/scriptable/radar.md b/docs/samples/scriptable/radar.md
new file mode 100644 (file)
index 0000000..fc3b686
--- /dev/null
@@ -0,0 +1,94 @@
+# Radar Chart
+
+```js chart-editor
+// <block:setup:2>
+const DATA_COUNT = 7;
+Utils.srand(110);
+
+const actions = [
+  {
+    name: 'Randomize',
+    handler(chart) {
+      chart.data.datasets.forEach(dataset => {
+        dataset.data = generateData();
+      });
+      chart.update();
+    }
+  },
+];
+// </block:setup>
+
+// <block:data:1>
+function generateData() {
+  return Utils.numbers({
+    count: DATA_COUNT,
+    min: 0,
+    max: 100
+  });
+}
+
+const data = {
+  labels: [['Eating', 'Dinner'], ['Drinking', 'Water'], 'Sleeping', ['Designing', 'Graphics'], 'Coding', 'Cycling', 'Running'],
+  datasets: [{
+    data: generateData()
+  }]
+};
+// </block:data>
+
+// <block:options:0>
+function getLineColor(ctx) {
+  return Utils.color(ctx.datasetIndex);
+}
+
+function alternatePointStyles(ctx) {
+  var index = ctx.dataIndex;
+  return index % 2 === 0 ? 'circle' : 'rect';
+}
+
+function makeHalfAsOpaque(ctx) {
+  return Utils.transparentize(getLineColor(ctx));
+}
+
+function make20PercentOpaque(ctx) {
+  return Utils.transparentize(getLineColor(ctx), 0.8);
+}
+
+function adjustRadiusBasedOnData(ctx) {
+  var v = ctx.parsed.y;
+  return v < 10 ? 5
+    : v < 25 ? 7
+    : v < 50 ? 9
+    : v < 75 ? 11
+    : 15;
+}
+
+const config = {
+  type: 'radar',
+  data: data,
+  options: {
+    plugins: {
+      legend: false,
+      tooltip: false,
+    },
+    elements: {
+      line: {
+        backgroundColor: make20PercentOpaque,
+        borderColor: getLineColor,
+      },
+      point: {
+        backgroundColor: getLineColor,
+        hoverBackgroundColor: makeHalfAsOpaque,
+        radius: adjustRadiusBasedOnData,
+        pointStyle: alternatePointStyles,
+        hoverRadius: 15,
+      }
+    }
+  }
+};
+// </block:options>
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/docs/samples/title/alignment.md b/docs/samples/title/alignment.md
new file mode 100644 (file)
index 0000000..791a503
--- /dev/null
@@ -0,0 +1,69 @@
+# Alignment
+
+This sample show how to configure the alignment of the chart title
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Title Alignment: start',
+    handler(chart) {
+      chart.options.plugins.title.align = 'start';
+      chart.update();
+    }
+  },
+  {
+    name: 'Title Alignment: center (default)',
+    handler(chart) {
+      chart.options.plugins.title.align = 'center';
+      chart.update();
+    }
+  },
+  {
+    name: 'Title Alignment: end',
+    handler(chart) {
+      chart.options.plugins.title.align = 'end';
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart Title',
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/tooltip/content.md b/docs/samples/tooltip/content.md
new file mode 100644 (file)
index 0000000..26a3a88
--- /dev/null
@@ -0,0 +1,66 @@
+# Custom Tooltip Content
+
+This sample shows how to use the tooltip callbacks to add additional content to the tooltip.
+
+```js chart-editor
+// <block:footer:2>
+const footer = (tooltipItems) => {
+  let sum = 0;
+
+  tooltipItems.forEach(function(tooltipItem) {
+    sum += tooltipItem.parsed.y;
+  });
+  return 'Sum: ' + sum;
+};
+
+// </block:footer>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100, decimals: 0};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    interaction: {
+      intersect: false,
+      mode: 'index',
+    },
+    plugins: {
+      tooltip: {
+        callbacks: {
+          footer: footer,
+        }
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/tooltip/html.md b/docs/samples/tooltip/html.md
new file mode 100644 (file)
index 0000000..2a7cb9e
--- /dev/null
@@ -0,0 +1,165 @@
+# External HTML Tooltip
+
+This sample shows how to use the external tooltip functionality to generate an HTML tooltip.
+
+```js chart-editor
+// <block:external:2>
+const getOrCreateTooltip = (chart) => {
+  let tooltipEl = chart.canvas.parentNode.querySelector('div');
+
+  if (!tooltipEl) {
+    tooltipEl = document.createElement('div');
+    tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)';
+    tooltipEl.style.borderRadius = '3px';
+    tooltipEl.style.color = 'white';
+    tooltipEl.style.opacity = 1;
+    tooltipEl.style.pointerEvents = 'none';
+    tooltipEl.style.position = 'absolute';
+    tooltipEl.style.transform = 'translate(-50%, 0)';
+    tooltipEl.style.transition = 'all .1s ease';
+
+    const table = document.createElement('table');
+    table.style.margin = '0px';
+
+    tooltipEl.appendChild(table);
+    chart.canvas.parentNode.appendChild(tooltipEl);
+  }
+
+  return tooltipEl;
+};
+
+const externalTooltipHandler = (context) => {
+  // Tooltip Element
+  const {chart, tooltip} = context;
+  const tooltipEl = getOrCreateTooltip(chart);
+
+  // Hide if no tooltip
+  if (tooltip.opacity === 0) {
+    tooltipEl.style.opacity = 0;
+    return;
+  }
+
+  // Set Text
+  if (tooltip.body) {
+    const titleLines = tooltip.title || [];
+    const bodyLines = tooltip.body.map(b => b.lines);
+
+    const tableHead = document.createElement('thead');
+
+    titleLines.forEach(title => {
+      const tr = document.createElement('tr');
+      tr.style.borderWidth = 0;
+
+      const th = document.createElement('th');
+      th.style.borderWidth = 0;
+      const text = document.createTextNode(title);
+
+      th.appendChild(text);
+      tr.appendChild(th);
+      tableHead.appendChild(tr);
+    });
+
+    const tableBody = document.createElement('tbody');
+    bodyLines.forEach((body, i) => {
+      const colors = tooltip.labelColors[i];
+
+      const span = document.createElement('span');
+      span.style.background = colors.backgroundColor;
+      span.style.borderColor = colors.borderColor;
+      span.style.borderWidth = '2px';
+      span.style.marginRight = '10px';
+      span.style.height = '10px';
+      span.style.width = '10px';
+      span.style.display = 'inline-block';
+
+      const tr = document.createElement('tr');
+      tr.style.backgroundColor = 'inherit';
+      tr.style.borderWidth = 0;
+
+      const td = document.createElement('td');
+      td.style.borderWidth = 0;
+
+      const text = document.createTextNode(body);
+
+      td.appendChild(span);
+      td.appendChild(text);
+      tr.appendChild(td);
+      tableBody.appendChild(tr);
+    });
+
+    const tableRoot = tooltipEl.querySelector('table');
+
+    // Remove old children
+    while (tableRoot.firstChild) {
+      tableRoot.firstChild.remove();
+    }
+
+    // Add new children
+    tableRoot.appendChild(tableHead);
+    tableRoot.appendChild(tableBody);
+  }
+
+  const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;
+
+  // Display, position, and set styles for font
+  tooltipEl.style.opacity = 1;
+  tooltipEl.style.left = positionX + tooltip.caretX + 'px';
+  tooltipEl.style.top = positionY + tooltip.caretY + 'px';
+  tooltipEl.style.font = tooltip.options.bodyFont.string;
+  tooltipEl.style.padding = tooltip.padding + 'px ' + tooltip.padding + 'px';
+};
+// </block:external>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100, decimals: 0};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    interaction: {
+      mode: 'index',
+      intersect: false,
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: 'Chart.js Line Chart - External Tooltips'
+      },
+      tooltip: {
+        enabled: false,
+        position: 'nearest',
+        external: externalTooltipHandler
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: [],
+  config: config,
+};
+```
diff --git a/docs/samples/tooltip/interactions.md b/docs/samples/tooltip/interactions.md
new file mode 100644 (file)
index 0000000..0b1a995
--- /dev/null
@@ -0,0 +1,110 @@
+# Interaction Modes
+
+This sample shows how to use the tooltip position mode setting.
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Mode: index',
+    handler(chart) {
+      chart.options.interaction.mode = 'index';
+      chart.update();
+    }
+  },
+  {
+    name: 'Mode: dataset',
+    handler(chart) {
+      chart.options.interaction.mode = 'dataset';
+      chart.update();
+    }
+  },
+  {
+    name: 'Mode: point',
+    handler(chart) {
+      chart.options.interaction.mode = 'point';
+      chart.update();
+    }
+  },
+  {
+    name: 'Mode: nearest',
+    handler(chart) {
+      chart.options.interaction.mode = 'nearest';
+      chart.update();
+    }
+  },
+  {
+    name: 'Mode: x',
+    handler(chart) {
+      chart.options.interaction.mode = 'x';
+      chart.update();
+    }
+  },
+  {
+    name: 'Mode: y',
+    handler(chart) {
+      chart.options.interaction.mode = 'y';
+      chart.update();
+    }
+  },
+  {
+    name: 'Toggle Intersect',
+    handler(chart) {
+      chart.options.interaction.intersect = !chart.options.interaction.intersect;
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    interaction: {
+      intersect: false,
+      mode: 'index',
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: (ctx) => {
+          const {intersect, mode} = ctx.chart.options.interaction;
+          return 'Mode: ' + mode + ', intersect: ' + intersect;
+        }
+      },
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/tooltip/point-style.md b/docs/samples/tooltip/point-style.md
new file mode 100644 (file)
index 0000000..cdeba46
--- /dev/null
@@ -0,0 +1,80 @@
+# Point Style
+
+This sample shows how to use the dataset point style in the tooltip instead of a rectangle to identify each dataset.
+
+```js chart-editor
+// <block:actions:2>
+const actions = [
+  {
+    name: 'Toggle Tooltip Point Style',
+    handler(chart) {
+      chart.options.plugins.tooltip.usePointStyle = !chart.options.plugins.tooltip.usePointStyle;
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:1>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Triangles',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+      pointStyle: 'triangle',
+      pointRadius: 6,
+    },
+    {
+      label: 'Circles',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+      pointStyle: 'circle',
+      pointRadius: 6,
+    },
+    {
+      label: 'Stars',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.green,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),
+      pointStyle: 'star',
+      pointRadius: 6,
+    }
+  ]
+};
+// </block:setup>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    interaction: {
+      mode: 'index',
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: (ctx) => 'Tooltip point style: ' + ctx.chart.options.plugins.tooltip.usePointStyle,
+      },
+      tooltip: {
+        usePointStyle: true,
+      }
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/samples/tooltip/position.md b/docs/samples/tooltip/position.md
new file mode 100644 (file)
index 0000000..e5e1d5f
--- /dev/null
@@ -0,0 +1,93 @@
+# Position
+
+This sample shows how to use the tooltip position mode setting.
+
+```js chart-editor
+// <block:actions:3>
+const actions = [
+  {
+    name: 'Position: average',
+    handler(chart) {
+      chart.options.plugins.tooltip.position = 'average';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: nearest',
+    handler(chart) {
+      chart.options.plugins.tooltip.position = 'nearest';
+      chart.update();
+    }
+  },
+  {
+    name: 'Position: bottom (custom)',
+    handler(chart) {
+      chart.options.plugins.tooltip.position = 'bottom';
+      chart.update();
+    }
+  },
+];
+// </block:actions>
+
+// <block:setup:2>
+const DATA_COUNT = 7;
+const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};
+const data = {
+  labels: Utils.months({count: DATA_COUNT}),
+  datasets: [
+    {
+      label: 'Dataset 1',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.red,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
+    },
+    {
+      label: 'Dataset 2',
+      data: Utils.numbers(NUMBER_CFG),
+      fill: false,
+      borderColor: Utils.CHART_COLORS.blue,
+      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
+    },
+  ]
+};
+// </block:setup>
+
+// <block:positioner:1>
+// Create a custom tooltip positioner to put at the bottom of the chart area
+components.Tooltip.positioners.bottom = function(items) {
+  const pos = components.Tooltip.positioners.average(items);
+  const chart = this._chart;
+
+  return {
+    x: pos.x,
+    y: chart.chartArea.bottom,
+  };
+};
+
+// </block:positioner>
+
+// <block:config:0>
+const config = {
+  type: 'line',
+  data: data,
+  options: {
+    interaction: {
+      intersect: false,
+      mode: 'index',
+    },
+    plugins: {
+      title: {
+        display: true,
+        text: (ctx) => 'Tooltip position mode: ' + ctx.chart.options.plugins.tooltip.position,
+      },
+    }
+  }
+};
+// </block:config>
+
+module.exports = {
+  actions: actions,
+  config: config,
+};
+```
\ No newline at end of file
diff --git a/docs/scripts/analyzer.js b/docs/scripts/analyzer.js
new file mode 100644 (file)
index 0000000..8d71e8a
--- /dev/null
@@ -0,0 +1,60 @@
+export default {
+  id: 'samples-filler-analyser',
+
+  beforeInit: function(chart, args, options) {
+    this.element = document.getElementById(options.target);
+  },
+
+  afterUpdate: function(chart) {
+    var datasets = chart.data.datasets;
+    var element = this.element;
+    var stats = [];
+    var meta, i, ilen, dataset;
+
+    if (!element) {
+      return;
+    }
+
+    for (i = 0, ilen = datasets.length; i < ilen; ++i) {
+      meta = chart.getDatasetMeta(i).$filler;
+      if (meta) {
+        dataset = datasets[i];
+        stats.push({
+          fill: dataset.fill,
+          target: meta.fill,
+          visible: meta.visible,
+          index: i
+        });
+      }
+    }
+
+    this.element.innerHTML = '<table>' +
+      '<tr>' +
+        '<th>Dataset</th>' +
+        '<th>Fill</th>' +
+        '<th>Target (visibility)</th>' +
+      '</tr>' +
+      stats.map(function(stat) {
+        var target = stat.target;
+        var row =
+          '<td><b>' + stat.index + '</b></td>' +
+          '<td>' + JSON.stringify(stat.fill) + '</td>';
+
+        if (target === false) {
+          target = 'none';
+        } else if (isFinite(target)) {
+          target = 'dataset ' + target;
+        } else {
+          target = 'boundary "' + target + '"';
+        }
+
+        if (stat.visible) {
+          row += '<td>' + target + '</td>';
+        } else {
+          row += '<td>(hidden)</td>';
+        }
+
+        return '<tr>' + row + '</tr>';
+      }).join('') + '</table>';
+  }
+};
diff --git a/docs/scripts/components.js b/docs/scripts/components.js
new file mode 100644 (file)
index 0000000..5ed77b6
--- /dev/null
@@ -0,0 +1,3 @@
+// Add Chart components needed in samples here.
+// Usable through `components[name]`.
+export {Tooltip} from '../../dist/chart.esm';
diff --git a/docs/scripts/derived-bubble.js b/docs/scripts/derived-bubble.js
new file mode 100644 (file)
index 0000000..2111ba9
--- /dev/null
@@ -0,0 +1,34 @@
+import {Chart, BubbleController} from 'chart.js';
+
+class Custom extends BubbleController {
+  draw() {
+    // Call bubble controller method to draw all the points
+    super.draw(arguments);
+
+    // Now we can do some custom drawing for this dataset.
+    // Here we'll draw a box around the first point in each dataset,
+    // using `boxStrokeStyle` dataset option for color
+    var meta = this.getMeta();
+    var pt0 = meta.data[0];
+
+    const {x, y} = pt0.getProps(['x', 'y']);
+    const {radius} = pt0.options;
+
+    var ctx = this.chart.ctx;
+    ctx.save();
+    ctx.strokeStyle = this.options.boxStrokeStyle;
+    ctx.lineWidth = 1;
+    ctx.strokeRect(x - radius, y - radius, 2 * radius, 2 * radius);
+    ctx.restore();
+  }
+}
+Custom.id = 'derivedBubble';
+Custom.defaults = {
+  // Custom defaults. Bubble defaults are inherited.
+  boxStrokeStyle: 'red'
+};
+// Overrides are only inherited, but not merged if defined
+// Custom.overrides = Chart.overrides.bubble;
+
+// Stores the controller so that the chart initialization routine can look it up
+Chart.register(Custom);
diff --git a/docs/scripts/helpers.js b/docs/scripts/helpers.js
new file mode 100644 (file)
index 0000000..a7c6036
--- /dev/null
@@ -0,0 +1,4 @@
+// Add helpers needed in samples here.
+// Usable through `helpers[name]`.
+export {color, getHoverColor} from '../../dist/helpers.esm';
+
diff --git a/docs/scripts/log2.js b/docs/scripts/log2.js
new file mode 100644 (file)
index 0000000..8aec32a
--- /dev/null
@@ -0,0 +1,67 @@
+import {Scale, LinearScale} from 'chart.js';
+
+export default class Log2Axis extends Scale {
+  constructor(cfg) {
+    super(cfg);
+    this._startValue = undefined;
+    this._valueRange = 0;
+  }
+
+  parse(raw, index) {
+    const value = LinearScale.prototype.parse.apply(this, [raw, index]);
+    return isFinite(value) && value > 0 ? value : null;
+  }
+
+  determineDataLimits() {
+    const {min, max} = this.getMinMax(true);
+    this.min = isFinite(min) ? Math.max(0, min) : null;
+    this.max = isFinite(max) ? Math.max(0, max) : null;
+  }
+
+  buildTicks() {
+    const ticks = [];
+
+    let power = Math.floor(Math.log2(this.min));
+    let maxPower = Math.ceil(Math.log2(this.max));
+    while (power <= maxPower) {
+      ticks.push({value: Math.pow(2, power)});
+      power += 1;
+    }
+
+    this.min = ticks[0].value;
+    this.max = ticks[ticks.length - 1].value;
+    return ticks;
+  }
+
+  /**
+   * @protected
+   */
+  configure() {
+    const start = this.min;
+
+    super.configure();
+
+    this._startValue = Math.log2(start);
+    this._valueRange = Math.log2(this.max) - Math.log2(start);
+  }
+
+  getPixelForValue(value) {
+    if (value === undefined || value === 0) {
+      value = this.min;
+    }
+
+    return this.getPixelForDecimal(value === this.min ? 0
+      : (Math.log2(value) - this._startValue) / this._valueRange);
+  }
+
+  getValueForPixel(pixel) {
+    const decimal = this.getDecimalForPixel(pixel);
+    return Math.pow(2, this._startValue + decimal * this._valueRange);
+  }
+}
+
+Log2Axis.id = 'log2';
+Log2Axis.defaults = {};
+
+// The derived axis is registered like this:
+// Chart.register(Log2Axis);
index 6ffea88942d99c174d3fd493db305f8eceeedb13..43f0ebcdba7d57512ae91f83432d7e1fff3563a9 100644 (file)
@@ -1,3 +1,8 @@
 import {Chart, registerables} from '../../dist/chart.esm';
+import Log2Axis from './log2';
+import './derived-bubble';
+import analyzer from './analyzer';
 
 Chart.register(...registerables);
+Chart.register(Log2Axis);
+Chart.register(analyzer);
index 9cb8a73cee980f95dbfc926021a12a980e5ad2dd..772b27505f77872c186ccb9bc23b8bc7cca41df2 100644 (file)
@@ -1,3 +1,6 @@
+import colorLib from '@kurkle/color';
+import {DateTime} from 'luxon';
+import 'chartjs-adapter-luxon';
 import {valueOrDefault} from '../../dist/helpers.esm';
 
 // Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
@@ -44,6 +47,13 @@ export function points(config) {
   return xs.map((x, i) => ({x, y: ys[i]}));
 }
 
+export function bubbles(config) {
+  return this.points(config).map(pt => {
+    pt.r = this.rand(config.rmin, config.rmax);
+    return pt;
+  });
+}
+
 export function labels(config) {
   var cfg = config || {};
   var min = cfg.min || 0;
@@ -109,12 +119,12 @@ export function color(index) {
   return COLORS[index % COLORS.length];
 }
 
-export function transparentize(color, opacity) {
+export function transparentize(value, opacity) {
   var alpha = opacity === undefined ? 0.5 : 1 - opacity;
-  return Color(color).alpha(alpha).rgbString();
+  return colorLib(value).alpha(alpha).rgbString();
 }
 
-export const chartColors = {
+export const CHART_COLORS = {
   red: 'rgb(255, 99, 132)',
   orange: 'rgb(255, 159, 64)',
   yellow: 'rgb(255, 205, 86)',
@@ -123,3 +133,25 @@ export const chartColors = {
   purple: 'rgb(153, 102, 255)',
   grey: 'rgb(201, 203, 207)'
 };
+
+const NAMED_COLORS = [
+  CHART_COLORS.red,
+  CHART_COLORS.orange,
+  CHART_COLORS.yellow,
+  CHART_COLORS.green,
+  CHART_COLORS.blue,
+  CHART_COLORS.purple,
+  CHART_COLORS.grey,
+];
+
+export function namedColor(index) {
+  return NAMED_COLORS[index % NAMED_COLORS.length];
+}
+
+export function newDate(days) {
+  return DateTime.now().plus({days}).toJSDate();
+}
+
+export function newDateString(days) {
+  return DateTime.now().plus({days}).toISO();
+}
index 9ddc90a56954014ed0d7c885502233de337a19f8..6d36a3754a67bfdf1cfe1d3922ce96cd0783dc84 100644 (file)
       "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
       "dev": true
     },
+    "chartjs-adapter-luxon": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.0.0-beta.2.tgz",
+      "integrity": "sha512-1DKxf5jMXs5b1n+NPtzkeM7iAWCSCwCOVI6mmh1/U4rTzprxDRyWQcA9SOdScdZQNFHuij/VbmerQpnrKEHUJA==",
+      "dev": true
+    },
     "chartjs-adapter-moment": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-0.1.2.tgz",
       "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
       "dev": true
     },
+    "luxon": {
+      "version": "1.26.0",
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.26.0.tgz",
+      "integrity": "sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A==",
+      "dev": true
+    },
     "magic-string": {
       "version": "0.25.7",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
index dd10d2cc6b4b29da2d42e4e8110405eba6caef06..9dee681f37281cb75b90d58d4425081464a0b830 100644 (file)
@@ -57,6 +57,7 @@
     "@typescript-eslint/eslint-plugin": "^4.18.0",
     "@typescript-eslint/parser": "^4.18.0",
     "@vuepress/plugin-google-analytics": "1.8.2",
+    "chartjs-adapter-luxon": "^1.0.0-beta.2",
     "chartjs-adapter-moment": "^0.1.2",
     "chartjs-test-utils": "^0.2.2",
     "concurrently": "^6.0.0",
@@ -80,6 +81,7 @@
     "karma-rollup-preprocessor": "^7.0.7",
     "karma-safari-private-launcher": "^1.0.0",
     "karma-spec-reporter": "0.0.32",
+    "luxon": "^1.26.0",
     "markdown-it-include": "^2.0.0",
     "moment": "^2.29.1",
     "pixelmatch": "^5.2.1",