From 6f8fa5173f4b027e23e1ae933b0a50714466c540 Mon Sep 17 00:00:00 2001 From: Slava Terekhov Date: Tue, 10 May 2022 10:15:06 +0400 Subject: [PATCH] feat: add reactivity to options (#840) --- README.md | 225 ++----------------------------------- legacy/src/Charts.js | 32 +++++- src/BaseCharts.ts | 43 ++++++- src/utils.ts | 13 +++ website/src/guide/index.md | 35 +++++- 5 files changed, 119 insertions(+), 229 deletions(-) diff --git a/README.md b/README.md index 73a0e501..8cf95237 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,9 @@ Supports Chart.js v3 and v2.
Install   •   -Docs -  •   How to use   •   -Migration to v4 -  •   -Demo +Docs   •   Slack   •   @@ -57,10 +53,6 @@ Need an API to fetch data? Consider [Cube](https://cube.dev/?ref=eco-vue-chartjs [![supported by Cube](https://user-images.githubusercontent.com/986756/154330861-d79ab8ec-aacb-4af8-9e17-1b28f1eccb01.svg)](https://cube.dev/?ref=eco-vue-chartjs) -## Docs - -- 📖 [Docs](http://vue-chartjs.org/) - ## How to use This package works with version 2.x and 3.x of Vue. @@ -228,216 +220,13 @@ export default { ``` -## Reactivity - -vue-chartjs will update or re-render the chart if new data is passed. - -## Migration to v4 - -With v4, this library introduces a number of breaking changes. In order to improve performance, offer new features, and improve maintainability, it was necessary to break backwards compatibility, but we aimed to do so only when worth the benefit. - -v4 is fully compatible with Chart.js v3. - -### Tree-shaking - -v4 of this library, [just like Chart.js v3](https://www.chartjs.org/docs/latest/getting-started/v3-migration.html#setup-and-installation), is tree-shakable. It means that you need to import and register the controllers, elements, scales, and plugins you want to use. - -For a list of all the available items to import, see [Chart.js docs](https://www.chartjs.org/docs/latest/getting-started/integration.html#bundlers-webpack-rollup-etc). - -v3: - -```javascript -import { Bar } from 'vue-chartjs' -``` - -v4 — lazy way: - -```javascript -import 'chart.js/auto'; -import { Bar } from 'vue-chartjs' -``` - -v4 — tree-shakable way: - -```javascript -import { Bar } from 'vue-chartjs' -import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js' - -ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale) -``` - -Using the "lazy way" is okay to simplify the migration, but please consider using the tree-shakable way to decrease the bundle size. - -Please note that typed chart components register their controllers by default, so you don't need to register them by yourself. For example, when using the Pie component, you don't need to register PieController explicitly. - -```javascript -import { Pie } from 'vue-chartjs' -import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js' - -ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale) -``` - -### Changing the creation of Charts - -In v3, you needed to import the component, and then either use extends or mixins and add it. - -v3: - -```javascript -// BarChart.js -import { Bar } from 'vue-chartjs' - -export default { - extends: Bar, - mounted () { - // Overwriting base render method with actual data. - this.renderChart({ - labels: ['January', 'February', 'March'], - datasets: [ - { - label: 'GitHub Commits', - backgroundColor: '#f87979', - data: [40, 20, 12] - } - ] - }) - } -} -``` - -```vue - - - -``` - -In v4, you need to import the component, pass props to it, and use Chart component as a standard Vue component. - -```vue - - - -``` - -### New reactivity system - -v3 does not update or re-render the chart if new data is passed. You needed to use `reactiveProp` and `reactiveData` mixins for that. - -v3: - -```javascript -import { Line, mixins } from 'vue-chartjs' - -export default { - extends: Line, - mixins: [mixins.reactiveProp], - props: ['chartData', 'options'], - mounted () { - this.renderChart(this.chartData, this.options) - } -} -``` - -v4 charts have data change watcher by default. v4 will update or re-render the chart if new data is passed. Mixins have been removed. - -v4: - -```vue - - - -``` - -## Demo - -- 📺 [Demo](http://demo.vue-chartjs.org/) - -## Available Charts - -### Bar Chart - -![Bar](assets/bar.png) - -### Line Chart - -![Line](assets/line.png) - -### Doughnut - -![Doughnut](assets/doughnut.png) - -### Pie - -![Pie](assets/pie.png) - -### Radar - -![Pie](assets/radar.png) - -### Polar Area - -![Pie](assets/polar.png) - -### Bubble - -![Bubble](assets/bubble.png) - -### Scatter +## Docs -![Scatter](assets/scatter.png) +- [Reactivity](https://vue-chartjs.org/guide/#updating-charts) +- [Access to Chart instance](https://vue-chartjs.org/guide/#access-to-chart-instance) +- [Migration to v4](https://vue-chartjs.org/migration-to-v4/) +- [API](https://vue-chartjs.org/api/) +- [Examples](https://vue-chartjs.org/examples/) ## Build Setup diff --git a/legacy/src/Charts.js b/legacy/src/Charts.js index de6d71f2..7a858811 100644 --- a/legacy/src/Charts.js +++ b/legacy/src/Charts.js @@ -19,7 +19,8 @@ import { setChartDatasets, compareData, templateError, - ChartEmits + ChartEmits, + setChartOptions } from '../../src/utils' const ANNOTATION_PLUGIN_KEY = 'annotation' @@ -91,8 +92,17 @@ export function generateChart(chartId, chartType, chartController) { } }, watch: { - chartData(newValue, oldValue) { - this.chartDataHandler(newValue, oldValue) + chartData: { + handler: function (newValue, oldValue) { + this.chartDataHandler(newValue, oldValue) + }, + deep: true + }, + chartOptions: { + handler: function (newValue) { + this.chartOptionsHandler(newValue) + }, + deep: true } }, methods: { @@ -139,7 +149,7 @@ export function generateChart(chartId, chartType, chartController) { this.$emit(ChartEmits.LabelsUpdated) } - chartUpdate(currentChart) + this.updateChart() this.$emit(ChartEmits.ChartUpdated) } else { if (currentChart !== null) { @@ -160,6 +170,20 @@ export function generateChart(chartId, chartType, chartController) { this.$emit(ChartEmits.ChartRendered) } }, + chartOptionsHandler(options) { + const currentChart = this.getCurrentChart() + + if (currentChart !== null) { + setChartOptions(currentChart, options) + this.updateChart() + } else { + chartCreate(this.renderChart, this.chartData, this.chartOptions) + } + }, + updateChart() { + const currentChart = this.getCurrentChart() + chartUpdate(currentChart) + }, getCurrentChart() { return this.hasAnnotationPlugin ? _chartRef.current : this.$data._chart }, diff --git a/src/BaseCharts.ts b/src/BaseCharts.ts index 22945fdd..75ca33a9 100644 --- a/src/BaseCharts.ts +++ b/src/BaseCharts.ts @@ -39,7 +39,9 @@ import { setChartLabels, setChartDatasets, compareData, - templateError + templateError, + chartUpdateError, + setChartOptions } from './utils' import type { @@ -165,7 +167,7 @@ export const generateChart = < ) } - chartUpdate(chart, context) + updateChart() } else { if (chart !== null) { chartDestroy(chart, context) @@ -192,6 +194,32 @@ export const generateChart = < } } + function chartOptionsHandler(options: TChartOptions): void { + const chart = toRaw(_chart.value) + + if (chart !== null) { + setChartOptions(chart, options) + updateChart() + } else { + chartCreate( + renderChart, + props.chartData, + props.chartOptions as ChartOptions, + context + ) + } + } + + function updateChart(): void { + const chart = toRaw(_chart.value) + + if (chart !== null) { + chartUpdate(chart, context) + } else { + console.error(chartUpdateError) + } + } + watch( () => props.chartData, ( @@ -201,6 +229,12 @@ export const generateChart = < { deep: true } ) + watch( + () => props.chartOptions, + newValue => chartOptionsHandler(newValue as ChartOptions), + { deep: true } + ) + onMounted(() => { if ( 'datasets' in props.chartData && @@ -221,6 +255,11 @@ export const generateChart = < } }) + context.expose({ + chart: _chart, + updateChart + }) + return () => h('div', { style: props.styles, class: props.cssClasses }, [ h('canvas', { diff --git a/src/utils.ts b/src/utils.ts index 20768908..cb43eb38 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -125,6 +125,17 @@ export function setChartLabels< } } +export function setChartOptions< + TType extends ChartType, + TData = DefaultDataPoint, + TLabel = unknown +>( + chart: TypedChartJS, + options: TChartOptions +): void { + chart.options = { ...options } +} + export function compareData< TType extends ChartType = ChartType, TData = DefaultDataPoint, @@ -151,3 +162,5 @@ export function compareData< export const templateError = 'Please remove the tags from your chart component. See https://vue-chartjs.org/guide/#vue-single-file-components' + +export const chartUpdateError = 'Update ERROR: chart instance not found' diff --git a/website/src/guide/index.md b/website/src/guide/index.md index 06a57d62..5dfabd6f 100644 --- a/website/src/guide/index.md +++ b/website/src/guide/index.md @@ -206,11 +206,11 @@ export default { ## Updating Charts -vue-chartjs charts have data change watcher by default. vue-chartjs will update or re-render the chart if new data is passed. +v4 charts have data change watcher and options change watcher by default. v4 will update or re-render the chart if new data or new options is passed. Mixins have been removed. ```vue ``` -### Options +## Access to Chart instance + +You can get access to chart instance via template refs. + +```vue + +``` + +In Vue3 projects: -The `options` object is not currently implemented reactively. Therefore, if you dynamically change the chart options, they will not be recognized by the update data handler. +```javascript +const chartInstance = this.$refs.bar.chart +``` + +In Vue2 projects: + +```javascript +const chartInstance = this.$refs.bar.getCurrentChart +``` + +Also you can get access to **updateChart** function + +```javascript +this.$refs.bar.updateChart() +``` ### Events