From 00d88b21980987f32ad3594414f52bd9d90b4e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Holeczek?= Date: Wed, 4 Aug 2021 12:13:56 +0200 Subject: [PATCH] release: v2.0.0-alpha.0 --- src/CChart.ts | 261 ---------------------------------------- src/CChartComponent.ts | 264 +++++++++++++++++++++++++++++++++++++++++ src/CCharts.ts | 55 --------- src/index.ts | 41 ++++++- 4 files changed, 303 insertions(+), 318 deletions(-) delete mode 100644 src/CChart.ts create mode 100644 src/CChartComponent.ts delete mode 100644 src/CCharts.ts diff --git a/src/CChart.ts b/src/CChart.ts deleted file mode 100644 index 063828d..0000000 --- a/src/CChart.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { defineComponent, h, onMounted, onUnmounted, onUpdated, PropType, ref, Ref } from 'vue' - -import Chart, { /*ChartData,*/ ChartOptions, ChartType, Plugin } from 'chart.js/auto' -import * as chartjs from 'chart.js' -import { customTooltips as cuiCustomTooltips } from '@coreui/chartjs' - -import assign from 'lodash/assign' -import find from 'lodash/find' -import merge from 'lodash/merge' - -export const CChart = defineComponent({ - name: 'CChart', - props: { - /** - * Enables custom html based tooltips instead of standard tooltips. - * - * @default true - */ - customTooltips: { - type: Boolean, - default: true, - required: false, - }, - /** - * The data object that is passed into the Chart.js chart (more info). - */ - data: { - type: [Object, Function], - // type: [ - // Object as PropType | - // Function as PropType<(canvas: HTMLCanvasElement) => ChartData>, - // ], - required: true, - }, - /** - * Height attribute applied to the rendered canvas. - * - * @default 150 - */ - height: { - type: Number, - default: 150, - required: false, - }, - /** - * ID attribute applied to the rendered canvas. - */ - id: { - type: String, - default: undefined, - required: false, - }, - /** - * The options object that is passed into the Chart.js chart. - * - * {@link https://www.chartjs.org/docs/latest/general/options.html More Info} - */ - options: { - type: Object as PropType, - default: undefined, - }, - // /** - // * The plugins array that is passed into the Chart.js chart (more info) - // * - // * {@link https://www.chartjs.org/docs/latest/developers/plugins.html More Info} - // */ - plugins: { - type: Array as PropType, - default: undefined, - }, - /** - * If true, will tear down and redraw chart on all updates. - */ - redraw: Boolean, - /** - * Chart.js chart type. - * - * @type {'line' | 'bar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie' | 'scatter'} - */ - type: { - type: String as PropType, - required: true, - }, - /** - * Width attribute applied to the rendered canvas. - * - * @default 300 - */ - width: { - type: Number, - default: 300, - required: false, - }, - /** - * Put the chart into the wrapper div element. - * - * @default true - */ - wrapper: { - type: Boolean, - default: true, - required: false, - }, - }, - emits: [ - /** - * Proxy for Chart.js getDatasetAtEvent. Calls with dataset and triggering event. - */ - 'getDatasetAtEvent', - /** - * Proxy for Chart.js getElementAtEvent. Calls with single element array and triggering event. - */ - 'getElementAtEvent', - /** - * Proxy for Chart.js getElementsAtEvent. Calls with element array and triggering event. - */ - 'getElementsAtEvent', - ], - setup(props, { emit, slots }) { - const canvasRef = ref() - const chart = ref(null) - - // const computedData = ref(merge({}, props.data)) - const computedData = ref( - typeof props.data === 'function' - ? canvasRef.value - ? props.data(canvasRef.value) - : { datasets: [] } - : merge({}, props.data), - ) - - const renderChart = () => { - if (!canvasRef.value) return - - if (props.customTooltips) { - chartjs.defaults.plugins.tooltip.enabled = false - chartjs.defaults.plugins.tooltip.mode = 'index' - chartjs.defaults.plugins.tooltip.position = 'nearest' - chartjs.defaults.plugins.tooltip.external = cuiCustomTooltips - } - - chart.value = new Chart(canvasRef.value, { - type: props.type, - data: computedData.value, - options: props.options, - plugins: props.plugins, - }) - } - - const handleOnClick = (e: Event) => { - console.log(chart.value) - if (!chart.value) return - - emit( - 'getDatasetAtEvent', - chart.value.getElementsAtEventForMode(e, 'dataset', { intersect: true }, false), - e, - ) - emit( - 'getElementAtEvent', - chart.value.getElementsAtEventForMode(e, 'nearest', { intersect: true }, false), - e, - ) - emit( - 'getElementsAtEvent', - chart.value.getElementsAtEventForMode(e, 'index', { intersect: true }, false), - e, - ) - } - - const updateChart = () => { - if (!chart.value) return - - if (props.options) { - chart.value.options = { ...props.options } - } - - if (!chart.value.config.data) { - chart.value.config.data = computedData - chart.value.update() - return - } - - const { datasets: newDataSets = [], ...newChartData } = computedData - const { datasets: currentDataSets = [] } = chart.value.config.data - - // copy values - assign(chart.value.config.data, newChartData) - chart.value.config.data.datasets = newDataSets.map((newDataSet: any) => { - // given the new set, find it's current match - const currentDataSet = find( - currentDataSets, - (d: any) => d.label === newDataSet.label && d.type === newDataSet.type, - ) - - // There is no original to update, so simply add new one - if (!currentDataSet || !newDataSet.data) return newDataSet - - if (!currentDataSet.data) { - currentDataSet.data = [] - } else { - currentDataSet.data.length = newDataSet.data.length - } - - // copy in values - assign(currentDataSet.data, newDataSet.data) - - // apply dataset changes, but keep copied data - return { - ...currentDataSet, - ...newDataSet, - data: currentDataSet.data, - } - }) - - chart.value && chart.value.update() - } - - const destroyChart = () => { - if (chart.value) chart.value.destroy() - } - - onMounted(() => { - renderChart() - }) - - onUnmounted(() => { - destroyChart() - }) - - onUpdated(() => { - if (props.redraw) { - destroyChart() - setTimeout(() => { - renderChart() - }, 0) - } else { - updateChart() - } - }) - - const canvas = (ref: Ref) => - h( - 'canvas', - { - id: props.id, - height: props.height, - width: props.width, - onClick: (e: Event) => handleOnClick(e), - role: 'img', - ref: ref, - }, - { - fallbackContent: () => slots.fallback && slots.fallback(), - }, - ) - - return () => - props.wrapper ? h('div', { class: 'chart-wrapper' }, canvas(canvasRef)) : canvas(canvasRef) - }, -}) diff --git a/src/CChartComponent.ts b/src/CChartComponent.ts new file mode 100644 index 0000000..058a309 --- /dev/null +++ b/src/CChartComponent.ts @@ -0,0 +1,264 @@ +import { defineComponent, h, onMounted, onUnmounted, onUpdated, PropType, ref, Ref } from 'vue' + +import Chart, { /*ChartData,*/ ChartOptions, ChartType, Plugin } from 'chart.js/auto' +import * as chartjs from 'chart.js' +import { customTooltips as cuiCustomTooltips } from '@coreui/chartjs' + +import assign from 'lodash/assign' +import find from 'lodash/find' +import merge from 'lodash/merge' + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const defineCChartComponent = (name: string, type: ChartType | undefined) => { + return defineComponent({ + name: name, + props: { + /** + * Enables custom html based tooltips instead of standard tooltips. + * + * @default true + */ + customTooltips: { + type: Boolean, + default: true, + required: false, + }, + /** + * The data object that is passed into the Chart.js chart (more info). + */ + data: { + type: [Object, Function], + // type: [ + // Object as PropType | + // Function as PropType<(canvas: HTMLCanvasElement) => ChartData>, + // ], + required: true, + }, + /** + * Height attribute applied to the rendered canvas. + * + * @default 150 + */ + height: { + type: Number, + default: 150, + required: false, + }, + /** + * ID attribute applied to the rendered canvas. + */ + id: { + type: String, + default: undefined, + required: false, + }, + /** + * The options object that is passed into the Chart.js chart. + * + * {@link https://www.chartjs.org/docs/latest/general/options.html More Info} + */ + options: { + type: Object as PropType, + default: undefined, + required: false, + }, + // /** + // * The plugins array that is passed into the Chart.js chart (more info) + // * + // * {@link https://www.chartjs.org/docs/latest/developers/plugins.html More Info} + // */ + plugins: { + type: Array as PropType, + default: undefined, + }, + /** + * If true, will tear down and redraw chart on all updates. + */ + redraw: Boolean, + /** + * Chart.js chart type. + * + * @type {'line' | 'bar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie' | 'scatter'} + */ + type: { + type: String as PropType, + required: true, + }, + /** + * Width attribute applied to the rendered canvas. + * + * @default 300 + */ + width: { + type: Number, + default: 300, + required: false, + }, + /** + * Put the chart into the wrapper div element. + * + * @default true + */ + wrapper: { + type: Boolean, + default: true, + required: false, + }, + }, + emits: [ + /** + * Proxy for Chart.js getDatasetAtEvent. Calls with dataset and triggering event. + */ + 'getDatasetAtEvent', + /** + * Proxy for Chart.js getElementAtEvent. Calls with single element array and triggering event. + */ + 'getElementAtEvent', + /** + * Proxy for Chart.js getElementsAtEvent. Calls with element array and triggering event. + */ + 'getElementsAtEvent', + ], + setup(props, { emit, slots }) { + const canvasRef = ref() + const chart = ref(null) + + const computedData = ref( + typeof props.data === 'function' + ? canvasRef.value + ? props.data(canvasRef.value) + : { datasets: [] } + : merge({}, props.data), + ) + + const renderChart = () => { + if (!canvasRef.value) return + + if (props.customTooltips) { + chartjs.defaults.plugins.tooltip.enabled = false + chartjs.defaults.plugins.tooltip.mode = 'index' + chartjs.defaults.plugins.tooltip.position = 'nearest' + chartjs.defaults.plugins.tooltip.external = cuiCustomTooltips + } + + chart.value = new Chart(canvasRef.value, { + type: typeof type === 'undefined' ? props.type : type, + data: computedData.value, + options: props.options as ChartOptions, + plugins: props.plugins, + }) + } + + const handleOnClick = (e: Event) => { + console.log(chart.value) + if (!chart.value) return + + emit( + 'getDatasetAtEvent', + chart.value.getElementsAtEventForMode(e, 'dataset', { intersect: true }, false), + e, + ) + emit( + 'getElementAtEvent', + chart.value.getElementsAtEventForMode(e, 'nearest', { intersect: true }, false), + e, + ) + emit( + 'getElementsAtEvent', + chart.value.getElementsAtEventForMode(e, 'index', { intersect: true }, false), + e, + ) + } + + const updateChart = () => { + if (!chart.value) return + + if (props.options) { + chart.value.options = { ...props.options } + } + + if (!chart.value.config.data) { + chart.value.config.data = computedData + chart.value.update() + return + } + + const { datasets: newDataSets = [], ...newChartData } = computedData + const { datasets: currentDataSets = [] } = chart.value.config.data + + // copy values + assign(chart.value.config.data, newChartData) + chart.value.config.data.datasets = newDataSets.map((newDataSet: any) => { + // given the new set, find it's current match + const currentDataSet = find( + currentDataSets, + (d: any) => d.label === newDataSet.label && d.type === newDataSet.type, + ) + + // There is no original to update, so simply add new one + if (!currentDataSet || !newDataSet.data) return newDataSet + + if (!currentDataSet.data) { + currentDataSet.data = [] + } else { + currentDataSet.data.length = newDataSet.data.length + } + + // copy in values + assign(currentDataSet.data, newDataSet.data) + + // apply dataset changes, but keep copied data + return { + ...currentDataSet, + ...newDataSet, + data: currentDataSet.data, + } + }) + + chart.value && chart.value.update() + } + + const destroyChart = () => { + if (chart.value) chart.value.destroy() + } + + onMounted(() => { + renderChart() + }) + + onUnmounted(() => { + destroyChart() + }) + + onUpdated(() => { + if (props.redraw) { + destroyChart() + setTimeout(() => { + renderChart() + }, 0) + } else { + updateChart() + } + }) + + const canvas = (ref: Ref) => + h( + 'canvas', + { + id: props.id, + height: props.height, + width: props.width, + onClick: (e: Event) => handleOnClick(e), + role: 'img', + ref: ref, + }, + { + fallbackContent: () => slots.fallback && slots.fallback(), + }, + ) + + return () => + props.wrapper ? h('div', { class: 'chart-wrapper' }, canvas(canvasRef)) : canvas(canvasRef) + }, + }) +} diff --git a/src/CCharts.ts b/src/CCharts.ts deleted file mode 100644 index 66b0e01..0000000 --- a/src/CCharts.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Chart from './Chart' - -const CChartBar = Object.assign({}, Chart, { - name: 'CChartBar', - type: 'bar' -}) -const CChartHorizontalBar = Object.assign({}, Chart, { - name: 'CChartHorizontalBar', - type: 'horizontalBar' -}) -const CChartLine = Object.assign({}, Chart, { - name: 'CChartLine', - type: 'line' -}) -const CChartDoughnut = Object.assign({}, Chart, { - name: 'CChartDoughnut', - type: 'doughnut' -}) -const CChartRadar = Object.assign({}, Chart, { - name: 'CChartRadar', - type: 'radar' -}) -const CChartPie = Object.assign({}, Chart, { - name: 'CChartPie', - type: 'pie' -}) -const CChartPolarArea = Object.assign({}, Chart, { - name: 'CChartPolarArea', - type: 'polarArea' -}) - -export { - CChartBar, - CChartHorizontalBar, - CChartLine, - CChartDoughnut, - CChartRadar, - CChartPie, - CChartPolarArea -} - -const CoreuiVueCharts = { - install (Vue) { - Vue.component('CChartBar', CChartBar) - Vue.component('CChartHorizontalBar', CChartHorizontalBar) - Vue.component('CChartLine', CChartLine) - Vue.component('CChartDoughnut', CChartDoughnut) - Vue.component('CChartRadar', CChartRadar) - Vue.component('CChartPie', CChartPie) - Vue.component('CChartPolarArea', CChartPolarArea) - } -} - -// Export library -export default CoreuiVueCharts diff --git a/src/index.ts b/src/index.ts index cc5320e..608d6e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,40 @@ -import { CChart } from './CChart' +import { App } from 'vue' +import { defineCChartComponent } from './CChartComponent' -export { CChart } +export const CChart = defineCChartComponent('CChart', undefined) +export const CChartBar = defineCChartComponent('CChartBar', 'bar') +export const CChartBubble = defineCChartComponent('CChartBubble', 'bubble') +export const CChartDoughnut = defineCChartComponent('CChartDoughnut', 'doughnut') +export const CChartLine = defineCChartComponent('CChartLine', 'line') +export const CChartPie = defineCChartComponent('CChartPie', 'pie') +export const CChartPolarArea = defineCChartComponent('CChartPolarArea', 'polarArea') +export const CChartRadar = defineCChartComponent('CChartRadar', 'radar') +export const CChartScatter = defineCChartComponent('CChartScatter', 'scatter') + + +const CChartPlugin = { + install: (app: App): void => { + app.component('CChart', CChart) + app.component('CChartBar', CChartBar) + app.component('CChartBubble', CChartBubble) + app.component('CChartDoughnut', CChartDoughnut) + app.component('CChartLine', CChartLine) + app.component('CChartPie', CChartPie) + app.component('CChartPolarArea', CChartPolarArea) + app.component('CChartRadar', CChartRadar) + app.component('CChartScatter', CChartScatter) + }, +} + +export default { + CChart, + CChartBar, + CChartBubble, + CChartDoughnut, + CChartLine, + CChartPie, + CChartPolarArea, + CChartRadar, + CChartScatter, + CChartPlugin, +}