diff --git a/plugins/panel/radar/Editor.tsx b/plugins/panel/radar/Editor.tsx new file mode 100644 index 000000000..b64c89690 --- /dev/null +++ b/plugins/panel/radar/Editor.tsx @@ -0,0 +1,112 @@ +// Copyright 2023 Datav.io Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { Select, Switch, Textarea } from "@chakra-ui/react" +import PanelAccordion from "src/views/dashboard/edit-panel/Accordion" +import PanelEditItem from "src/views/dashboard/edit-panel/PanelEditItem" +import { Panel, PanelEditorProps } from "types/dashboard" +import React, { memo } from "react"; +import { useStore } from "@nanostores/react" +import { commonMsg } from "src/i18n/locales/en" +import { PluginSettings, initSettings, } from "./types" +import { dispatch } from "use-bus"; +import { PanelForceRebuildEvent } from "src/data/bus-events"; +import { defaultsDeep } from "lodash"; +import RadionButtons from "components/RadioButtons"; +import { EditorNumberItem } from "components/editor/EditorItem"; + + +const PanelEditor = memo(({ panel, onChange }: PanelEditorProps) => { + const t = useStore(commonMsg) + panel.plugins[panel.type] = defaultsDeep(panel.plugins[panel.type], initSettings) + const options: PluginSettings = panel.plugins[panel.type] + return ( + <> + + + onChange((panel: Panel) => { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.animation = e.currentTarget.checked + // force the panel to rebuild to avoid some problems + dispatch(PanelForceRebuildEvent + panel.id) + })} /> + + + + + + + + onChange((panel: Panel) => { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.mode = v + dispatch(PanelForceRebuildEvent + panel.id) + })} /> + + + + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.top = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.bottom = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.left = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.right = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.itemGap = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + + ) +}) + +export default PanelEditor \ No newline at end of file diff --git a/plugins/panel/radar/OverrideEditor.tsx b/plugins/panel/radar/OverrideEditor.tsx new file mode 100644 index 000000000..79146b874 --- /dev/null +++ b/plugins/panel/radar/OverrideEditor.tsx @@ -0,0 +1,38 @@ +// Copyright 2023 Datav.io Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { OverrideRule } from "types/dashboard"; +import React from "react"; + +interface Props { + override: OverrideRule + onChange: any +} + + +const OverrideEditor = (props: Props) => { + return <> +} + +export default OverrideEditor + +export enum OverrideRules { + // basic +} + +// The above example will get targets from SeriesData, Table and Graph panels are using this method to get targets +// If return [] or null or undefined, Datav will use the default function to get override targets +export const getOverrideTargets = (panel, data) => { + // for demonstration purpose, we just return a hard coded targets list + return [] +} \ No newline at end of file diff --git a/plugins/panel/radar/Panel.tsx b/plugins/panel/radar/Panel.tsx new file mode 100644 index 000000000..6f5745268 --- /dev/null +++ b/plugins/panel/radar/Panel.tsx @@ -0,0 +1,69 @@ +// Copyright 2023 Datav.io Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { Box, Center, Text, useColorMode } from "@chakra-ui/react"; +import ChartComponent from "src/components/charts/Chart"; +import { memo, useMemo, useState } from "react"; +import { PanelProps } from "types/dashboard" +import { FieldType, SeriesData } from "types/seriesData"; +import React from "react"; +import { isEmpty } from "utils/validate"; +import NoData from "src/views/dashboard/components/PanelNoData"; +import { defaultsDeep } from "lodash"; +import { PluginSettings, initSettings } from "./types"; +import { buildOptions } from "./buildOptions"; +import mockData from './mockData.json' +import { isSeriesData } from "utils/seriesData"; + +interface Props extends PanelProps { + data: SeriesData[][] +} + +const PanelComponentWrapper = memo((props: Props) => { + const d: SeriesData[] = props.data.flat() + if (isEmpty(d)) { + return
+ } + + return (<> + + + ) +}) + +export default PanelComponentWrapper + +const PanelComponent = (props: Props) => { + const { panel, height, width } = props + const [chart, setChart] = useState(null) + const { colorMode } = useColorMode() + + + // init panel plugin settings + props.panel.plugins[panel.type] = defaultsDeep(props.panel.plugins[panel.type], initSettings) + // give plugin settings a name for easy access + const options: PluginSettings = props.panel.plugins[panel.type] + + const echartOptions = useMemo(() => { + // transform SeriesData to candlestick data format + const option = buildOptions(panel, props.data.flat(), colorMode) + return option + }, [props.data, panel.overrides, colorMode, options]) + + return (<> + {options && setChart(c)} onChartEvents={null} />} + ) +} + +export const mockDataForTestDataDs = () => { + return mockData +} diff --git a/plugins/panel/radar/buildOptions.ts b/plugins/panel/radar/buildOptions.ts new file mode 100644 index 000000000..335102695 --- /dev/null +++ b/plugins/panel/radar/buildOptions.ts @@ -0,0 +1,50 @@ +import { Panel } from "types/dashboard"; +import { SeriesData } from "types/seriesData"; +import { PluginSettings } from "./types"; + + +export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light" | "dark") => { + const options: PluginSettings = panel.plugins[panel.type] + + const legend = data.map(item => item.name) + const seriesData = legend.map(i => ({ name: i, value: [] })) + const indicator = {} + data.forEach(item => { + item.fields.forEach(field => { + const total = field.values.reduce((a, b) => a + b) + if (indicator[field.name] && indicator[field.name] > total) { + indicator[field.name] = field.values.reduce((a, b) => a + b) + } else { + indicator[field.name] = field.values.reduce((a, b) => a + b) + } + const idx = seriesData.findIndex(i => i.name === item.name) + seriesData[idx].value.push(total) + }) + }) + const opts = { + darkMode: colorMode === 'dark', + legend: { + data: legend, + show: options.graph.legend.mode, + top: options.graph.legend.top, + bottom: options.graph.legend.bottom, + left: options.graph.legend.left, + right: options.graph.legend.right, + orient: options.graph.legend.orient, + itemGap: options.graph.legend.itemGap, + }, + animation: options.animation, + radar: { + shape: options.radar.shape, + indicator: Object.keys(indicator).map(key => ({ name: key, value: indicator[key] * 1.25 })), + }, + series: [ + { + type: 'radar', + data: seriesData, + } + ] + } + + return opts +} \ No newline at end of file diff --git a/plugins/panel/radar/icon.svg b/plugins/panel/radar/icon.svg new file mode 100644 index 000000000..345057093 --- /dev/null +++ b/plugins/panel/radar/icon.svg @@ -0,0 +1,1593 @@ + + + + diff --git a/plugins/panel/radar/index.ts b/plugins/panel/radar/index.ts new file mode 100644 index 000000000..26efca75c --- /dev/null +++ b/plugins/panel/radar/index.ts @@ -0,0 +1,23 @@ +import { PanelPluginComponents } from "types/plugin"; +import PanelComponent, { mockDataForTestDataDs } from "./Panel"; +import PanelEditor from "./Editor"; +import OverrideEditor, { OverrideRules, getOverrideTargets } from "./OverrideEditor"; +import icon from './icon.svg' +import { PanelTypeRadar, initSettings } from "./types"; + + +const panelComponents: PanelPluginComponents = { + panel: PanelComponent, + editor: PanelEditor, + overrideEditor: OverrideEditor, + overrideRules: OverrideRules, + getOverrideTargets: getOverrideTargets, + mockDataForTestDataDs: mockDataForTestDataDs, + settings: { + type: PanelTypeRadar, + icon, + initOptions: initSettings + } +} + +export default panelComponents \ No newline at end of file diff --git a/plugins/panel/radar/mockData.json b/plugins/panel/radar/mockData.json new file mode 100644 index 000000000..e91d4e7d2 --- /dev/null +++ b/plugins/panel/radar/mockData.json @@ -0,0 +1,125 @@ +[ + { + "name": "Beijing", + "fields": [ + { + "name": "PM2.5", + "type": "number", + "values": [ + 10, + 20, + 30 + ] + }, + { + "name": "PM10", + "type": "number", + "values": [ + 15, + 25, + 35 + ] + }, + { + "name": "CO", + "type": "number", + "values": [ + 15, + 25, + 35 + ] + }, + { + "name": "NO2", + "type": "number", + "values": [ + 25, + 27, + 30 + ] + } + ] + }, + { + "name": "Shanghai", + "fields": [ + { + "name": "PM2.5", + "type": "number", + "values": [ + 13, + 17, + 20 + ] + }, + { + "name": "PM10", + "type": "number", + "values": [ + 15, + 27, + 40 + ] + }, + { + "name": "CO", + "type": "number", + "values": [ + 35, + 47, + 60 + ] + }, + { + "name": "NO2", + "type": "number", + "values": [ + 35, + 47, + 60 + ] + } + ] + }, + { + "name": "Shenzhen", + "fields": [ + { + "name": "PM2.5", + "type": "number", + "values": [ + 5, + 7, + 13 + ] + }, + { + "name": "PM10", + "type": "number", + "values": [ + 19, + 26, + 50 + ] + }, + { + "name": "CO", + "type": "number", + "values": [ + 15, + 27, + 30 + ] + }, + { + "name": "NO2", + "type": "number", + "values": [ + 42, + 56, + 60 + ] + } + ] + } +] \ No newline at end of file diff --git a/plugins/panel/radar/types.ts b/plugins/panel/radar/types.ts new file mode 100644 index 000000000..a1874741e --- /dev/null +++ b/plugins/panel/radar/types.ts @@ -0,0 +1,36 @@ +export const PanelTypeRadar = "radar" + +export interface PluginSettings { + animation: boolean + graph: { + legend: { + mode: boolean + left?: number + right?: number + top?: number + bottom?: number + orient?: string + itemGap?: number + } + } + radar: { + shape?: string + } +} + + +export const initSettings: PluginSettings = { + animation: false, + radar: { + shape: 'polygon' + }, + graph: { + legend: { + mode: true, + right: 0, + top: 0, + orient: 'vertical', + itemGap: 20 + } + } +} \ No newline at end of file diff --git a/ui/src/views/dashboard/grid/PanelGrid/PanelGrid.tsx b/ui/src/views/dashboard/grid/PanelGrid/PanelGrid.tsx index 91f48176a..6380a4394 100644 --- a/ui/src/views/dashboard/grid/PanelGrid/PanelGrid.tsx +++ b/ui/src/views/dashboard/grid/PanelGrid/PanelGrid.tsx @@ -195,7 +195,7 @@ export const PanelComponent = ({ dashboard, panel, variables, onRemovePanel, onH if (!plugin) { setQueryError("Datasource plugin not found: " + datasource.type) setPanelData([]) - return + return } setLoading(true) @@ -206,6 +206,7 @@ export const PanelComponent = ({ dashboard, panel, variables, onRemovePanel, onH } else { const promises = [] for (const q0 of ds.queries) { + console.log('===>', ds.queries) if (!q0.visible) { continue } diff --git a/ui/src/views/dashboard/plugins/built-in/panel/echarts/Echarts.tsx b/ui/src/views/dashboard/plugins/built-in/panel/echarts/Echarts.tsx index 29c68ebc9..73c94a1f1 100644 --- a/ui/src/views/dashboard/plugins/built-in/panel/echarts/Echarts.tsx +++ b/ui/src/views/dashboard/plugins/built-in/panel/echarts/Echarts.tsx @@ -20,7 +20,7 @@ import { cloneDeep, isEmpty, isFunction } from "lodash"; import { useSearchParam } from "react-use"; import React from "react"; import moment from "moment"; -import { paletteMap, palettes } from "utils/colors"; +import { paletteMap, palettes } from "utils/colors"; import loadash from "lodash" import 'echarts/extension/bmap/bmap'; import { genDynamicFunction } from "utils/dashboard/dynamicCall"; @@ -86,7 +86,7 @@ const EchartsPanel = memo((props: PanelProps) => { if (options) { options.animation = panel.plugins.echarts.animation } - + return [options, onEvents] }, [panel.plugins.echarts, props.data, chart]) @@ -147,7 +147,7 @@ export const EchartsComponent = ({ options, theme, width, height, onChartCreated useEffect(() => { if (onChartEvents && chart) { onChartEvents(options, chart, gnavigate, (k, v) => setVariable(k, v), setDateTime, $variables) - } + } }, [onChartEvents]) useEffect(() => { diff --git a/ui/src/views/dashboard/plugins/built-in/panel/echarts/index.ts b/ui/src/views/dashboard/plugins/built-in/panel/echarts/index.ts index f7aa73e49..63ad3a4da 100644 --- a/ui/src/views/dashboard/plugins/built-in/panel/echarts/index.ts +++ b/ui/src/views/dashboard/plugins/built-in/panel/echarts/index.ts @@ -10,7 +10,7 @@ import { PanelTypeEcharts } from "./types"; const panelComponents: PanelPluginComponents = { panel: EchartsPanel, editor: PanelEditor, - mockDataForTestDataDs: mockEchartsDataForTestDataDs, + mockDataForTestDataDs: mockEchartsDataForTestDataDs, settings: { type: PanelTypeEcharts, icon, @@ -43,4 +43,4 @@ function registerEvents(options, chart, navigate, setVariable, setDateTime, $var } } -export default panelComponents \ No newline at end of file +export default panelComponents \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/candlestick/Editor.tsx b/ui/src/views/dashboard/plugins/external/panel/candlestick/Editor.tsx index e2b53796c..d27d1c2a5 100644 --- a/ui/src/views/dashboard/plugins/external/panel/candlestick/Editor.tsx +++ b/ui/src/views/dashboard/plugins/external/panel/candlestick/Editor.tsx @@ -55,12 +55,12 @@ const PanelEditor = memo(({ panel, onChange }: CandleEditorProps) => { })} /> - onChange((panel: Panel) => { + onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].kChart.upColor = v })} /> - onChange((panel: Panel) => { + onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].kChart.downColor = v })} /> @@ -110,7 +110,7 @@ const PanelEditor = memo(({ panel, onChange }: CandleEditorProps) => { } /> - onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].volumeChart.value.decimal = v })} /> + onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].volumeChart.value.decimal = v })} /> @@ -145,7 +145,7 @@ const PanelEditor = memo(({ panel, onChange }: CandleEditorProps) => { } /> - onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].value.decimal = v })} /> + onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].value.decimal = v })} /> {/* { @@ -160,7 +160,7 @@ const PanelEditor = memo(({ panel, onChange }: CandleEditorProps) => { })} /> - onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].maLine.lineWidth = v })} /> + onChange((panel: Panel) => { panel.plugins[PanelTypeCandle].maLine.lineWidth = v })} /> onChange((panel: Panel) => { diff --git a/ui/src/views/dashboard/plugins/external/panel/candlestick/buildOptions.ts b/ui/src/views/dashboard/plugins/external/panel/candlestick/buildOptions.ts index f06ef672e..918c222fc 100644 --- a/ui/src/views/dashboard/plugins/external/panel/candlestick/buildOptions.ts +++ b/ui/src/views/dashboard/plugins/external/panel/candlestick/buildOptions.ts @@ -13,10 +13,10 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light const upColor = paletteColorNameToHex(options.kChart.upColor, colorMode) const downColor = paletteColorNameToHex(options.kChart.downColor, colorMode) - + // Each item: open,close,lowest,highest const series = cloneDeep(data[0]) - const kChartName = isEmpty(options.kChart.displayName) ? (isEmpty(series.name) ? "K" : series.name) : options.kChart.displayName + const kChartName = isEmpty(options.kChart.displayName) ? (isEmpty(series.name) ? "K" : series.name) : options.kChart.displayName const data0 = splitData(transformSeriesDataToCandlestickFormat(series)); function splitData(rawData) { let categoryData = []; @@ -26,7 +26,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light categoryData.push(rawData[i].splice(0, 1)[0]); values.push(rawData[i]); if (rawData[i][4]) { - volumes.push([i, rawData[i][4], rawData[i][0] > rawData[i][1] ? 1 : -1]); + volumes.push([i, rawData[i][4], rawData[i][0] > rawData[i][1] ? 1 : -1]); } } @@ -95,7 +95,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light left: '5%', top: '65%', height: '13%' - }]): [] + }]) : [] const volumeOverride = findOverride(panel, "Volume") const ma5verride = findOverride(panel, "MA5") @@ -103,7 +103,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light const ma20verride = findOverride(panel, "MA20") const ma30verride = findOverride(panel, "MA30") - return { + return { animation: options.animation, tooltip: [{ trigger: 'axis', @@ -236,7 +236,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light gridIndex: 1, splitNumber: 2, position: "right", - axisLabel: { + axisLabel: { show: options.volumeChart.showYAxisLabel, formatter: (function (value) { return formatUnit(value, options.volumeChart.value.units, options.volumeChart.value.decimal) @@ -308,7 +308,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light data: data0.volumes, itemStyle: { opacity: options.chartOpacity, - color: paletteColorNameToHex(findRuleInOverride(volumeOverride, OverrideRules.SeriesColor),colorMode) ?? null, + color: paletteColorNameToHex(findRuleInOverride(volumeOverride, OverrideRules.SeriesColor), colorMode) ?? null, }, tooltip: { valueFormatter: (function (value) { @@ -329,7 +329,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light width: options.maLine.lineWidth }, itemStyle: { - color: paletteColorNameToHex(findRuleInOverride(ma5verride, OverrideRules.SeriesColor),colorMode) ?? null, + color: paletteColorNameToHex(findRuleInOverride(ma5verride, OverrideRules.SeriesColor), colorMode) ?? null, }, symbol: options.maLine.lineSymbol }, @@ -343,7 +343,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light width: options.maLine.lineWidth }, itemStyle: { - color: paletteColorNameToHex(findRuleInOverride(ma10verride, OverrideRules.SeriesColor),colorMode) ?? null, + color: paletteColorNameToHex(findRuleInOverride(ma10verride, OverrideRules.SeriesColor), colorMode) ?? null, }, symbol: options.maLine.lineSymbol }, @@ -357,7 +357,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light width: options.maLine.lineWidth }, itemStyle: { - color: paletteColorNameToHex(findRuleInOverride(ma20verride, OverrideRules.SeriesColor),colorMode) ?? null, + color: paletteColorNameToHex(findRuleInOverride(ma20verride, OverrideRules.SeriesColor), colorMode) ?? null, }, symbol: options.maLine.lineSymbol }, @@ -371,7 +371,7 @@ export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light width: options.maLine.lineWidth }, itemStyle: { - color: paletteColorNameToHex(findRuleInOverride(ma30verride, OverrideRules.SeriesColor),colorMode) ?? null, + color: paletteColorNameToHex(findRuleInOverride(ma30verride, OverrideRules.SeriesColor), colorMode) ?? null, }, symbol: options.maLine.lineSymbol } @@ -390,7 +390,7 @@ const transformSeriesDataToCandlestickFormat = (s: SeriesData) => { const volumeField = s.fields.find(f => f.name == "volume") // [time, open, close, lowest, highest, volume] - timeField.values.forEach((t,i) => { + timeField.values.forEach((t, i) => { const v = [ t, openField.values[i], @@ -404,7 +404,7 @@ const transformSeriesDataToCandlestickFormat = (s: SeriesData) => { } res.push(v) - }) + }) return res } \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/Editor.tsx b/ui/src/views/dashboard/plugins/external/panel/radar/Editor.tsx new file mode 100644 index 000000000..b64c89690 --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/Editor.tsx @@ -0,0 +1,112 @@ +// Copyright 2023 Datav.io Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { Select, Switch, Textarea } from "@chakra-ui/react" +import PanelAccordion from "src/views/dashboard/edit-panel/Accordion" +import PanelEditItem from "src/views/dashboard/edit-panel/PanelEditItem" +import { Panel, PanelEditorProps } from "types/dashboard" +import React, { memo } from "react"; +import { useStore } from "@nanostores/react" +import { commonMsg } from "src/i18n/locales/en" +import { PluginSettings, initSettings, } from "./types" +import { dispatch } from "use-bus"; +import { PanelForceRebuildEvent } from "src/data/bus-events"; +import { defaultsDeep } from "lodash"; +import RadionButtons from "components/RadioButtons"; +import { EditorNumberItem } from "components/editor/EditorItem"; + + +const PanelEditor = memo(({ panel, onChange }: PanelEditorProps) => { + const t = useStore(commonMsg) + panel.plugins[panel.type] = defaultsDeep(panel.plugins[panel.type], initSettings) + const options: PluginSettings = panel.plugins[panel.type] + return ( + <> + + + onChange((panel: Panel) => { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.animation = e.currentTarget.checked + // force the panel to rebuild to avoid some problems + dispatch(PanelForceRebuildEvent + panel.id) + })} /> + + + + + + + + onChange((panel: Panel) => { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.mode = v + dispatch(PanelForceRebuildEvent + panel.id) + })} /> + + + + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.top = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.bottom = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.left = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.right = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + { + const plugin: PluginSettings = panel.plugins[panel.type] + plugin.graph.legend.itemGap = e + dispatch(PanelForceRebuildEvent + panel.id) + }} /> + + + + ) +}) + +export default PanelEditor \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/OverrideEditor.tsx b/ui/src/views/dashboard/plugins/external/panel/radar/OverrideEditor.tsx new file mode 100644 index 000000000..79146b874 --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/OverrideEditor.tsx @@ -0,0 +1,38 @@ +// Copyright 2023 Datav.io Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { OverrideRule } from "types/dashboard"; +import React from "react"; + +interface Props { + override: OverrideRule + onChange: any +} + + +const OverrideEditor = (props: Props) => { + return <> +} + +export default OverrideEditor + +export enum OverrideRules { + // basic +} + +// The above example will get targets from SeriesData, Table and Graph panels are using this method to get targets +// If return [] or null or undefined, Datav will use the default function to get override targets +export const getOverrideTargets = (panel, data) => { + // for demonstration purpose, we just return a hard coded targets list + return [] +} \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/Panel.tsx b/ui/src/views/dashboard/plugins/external/panel/radar/Panel.tsx new file mode 100644 index 000000000..6f5745268 --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/Panel.tsx @@ -0,0 +1,69 @@ +// Copyright 2023 Datav.io Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { Box, Center, Text, useColorMode } from "@chakra-ui/react"; +import ChartComponent from "src/components/charts/Chart"; +import { memo, useMemo, useState } from "react"; +import { PanelProps } from "types/dashboard" +import { FieldType, SeriesData } from "types/seriesData"; +import React from "react"; +import { isEmpty } from "utils/validate"; +import NoData from "src/views/dashboard/components/PanelNoData"; +import { defaultsDeep } from "lodash"; +import { PluginSettings, initSettings } from "./types"; +import { buildOptions } from "./buildOptions"; +import mockData from './mockData.json' +import { isSeriesData } from "utils/seriesData"; + +interface Props extends PanelProps { + data: SeriesData[][] +} + +const PanelComponentWrapper = memo((props: Props) => { + const d: SeriesData[] = props.data.flat() + if (isEmpty(d)) { + return
+ } + + return (<> + + + ) +}) + +export default PanelComponentWrapper + +const PanelComponent = (props: Props) => { + const { panel, height, width } = props + const [chart, setChart] = useState(null) + const { colorMode } = useColorMode() + + + // init panel plugin settings + props.panel.plugins[panel.type] = defaultsDeep(props.panel.plugins[panel.type], initSettings) + // give plugin settings a name for easy access + const options: PluginSettings = props.panel.plugins[panel.type] + + const echartOptions = useMemo(() => { + // transform SeriesData to candlestick data format + const option = buildOptions(panel, props.data.flat(), colorMode) + return option + }, [props.data, panel.overrides, colorMode, options]) + + return (<> + {options && setChart(c)} onChartEvents={null} />} + ) +} + +export const mockDataForTestDataDs = () => { + return mockData +} diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/buildOptions.ts b/ui/src/views/dashboard/plugins/external/panel/radar/buildOptions.ts new file mode 100644 index 000000000..335102695 --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/buildOptions.ts @@ -0,0 +1,50 @@ +import { Panel } from "types/dashboard"; +import { SeriesData } from "types/seriesData"; +import { PluginSettings } from "./types"; + + +export const buildOptions = (panel: Panel, data: SeriesData[], colorMode: "light" | "dark") => { + const options: PluginSettings = panel.plugins[panel.type] + + const legend = data.map(item => item.name) + const seriesData = legend.map(i => ({ name: i, value: [] })) + const indicator = {} + data.forEach(item => { + item.fields.forEach(field => { + const total = field.values.reduce((a, b) => a + b) + if (indicator[field.name] && indicator[field.name] > total) { + indicator[field.name] = field.values.reduce((a, b) => a + b) + } else { + indicator[field.name] = field.values.reduce((a, b) => a + b) + } + const idx = seriesData.findIndex(i => i.name === item.name) + seriesData[idx].value.push(total) + }) + }) + const opts = { + darkMode: colorMode === 'dark', + legend: { + data: legend, + show: options.graph.legend.mode, + top: options.graph.legend.top, + bottom: options.graph.legend.bottom, + left: options.graph.legend.left, + right: options.graph.legend.right, + orient: options.graph.legend.orient, + itemGap: options.graph.legend.itemGap, + }, + animation: options.animation, + radar: { + shape: options.radar.shape, + indicator: Object.keys(indicator).map(key => ({ name: key, value: indicator[key] * 1.25 })), + }, + series: [ + { + type: 'radar', + data: seriesData, + } + ] + } + + return opts +} \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/icon.svg b/ui/src/views/dashboard/plugins/external/panel/radar/icon.svg new file mode 100644 index 000000000..345057093 --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/icon.svg @@ -0,0 +1,1593 @@ + + + + diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/index.ts b/ui/src/views/dashboard/plugins/external/panel/radar/index.ts new file mode 100644 index 000000000..26efca75c --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/index.ts @@ -0,0 +1,23 @@ +import { PanelPluginComponents } from "types/plugin"; +import PanelComponent, { mockDataForTestDataDs } from "./Panel"; +import PanelEditor from "./Editor"; +import OverrideEditor, { OverrideRules, getOverrideTargets } from "./OverrideEditor"; +import icon from './icon.svg' +import { PanelTypeRadar, initSettings } from "./types"; + + +const panelComponents: PanelPluginComponents = { + panel: PanelComponent, + editor: PanelEditor, + overrideEditor: OverrideEditor, + overrideRules: OverrideRules, + getOverrideTargets: getOverrideTargets, + mockDataForTestDataDs: mockDataForTestDataDs, + settings: { + type: PanelTypeRadar, + icon, + initOptions: initSettings + } +} + +export default panelComponents \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/mockData.json b/ui/src/views/dashboard/plugins/external/panel/radar/mockData.json new file mode 100644 index 000000000..e91d4e7d2 --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/mockData.json @@ -0,0 +1,125 @@ +[ + { + "name": "Beijing", + "fields": [ + { + "name": "PM2.5", + "type": "number", + "values": [ + 10, + 20, + 30 + ] + }, + { + "name": "PM10", + "type": "number", + "values": [ + 15, + 25, + 35 + ] + }, + { + "name": "CO", + "type": "number", + "values": [ + 15, + 25, + 35 + ] + }, + { + "name": "NO2", + "type": "number", + "values": [ + 25, + 27, + 30 + ] + } + ] + }, + { + "name": "Shanghai", + "fields": [ + { + "name": "PM2.5", + "type": "number", + "values": [ + 13, + 17, + 20 + ] + }, + { + "name": "PM10", + "type": "number", + "values": [ + 15, + 27, + 40 + ] + }, + { + "name": "CO", + "type": "number", + "values": [ + 35, + 47, + 60 + ] + }, + { + "name": "NO2", + "type": "number", + "values": [ + 35, + 47, + 60 + ] + } + ] + }, + { + "name": "Shenzhen", + "fields": [ + { + "name": "PM2.5", + "type": "number", + "values": [ + 5, + 7, + 13 + ] + }, + { + "name": "PM10", + "type": "number", + "values": [ + 19, + 26, + 50 + ] + }, + { + "name": "CO", + "type": "number", + "values": [ + 15, + 27, + 30 + ] + }, + { + "name": "NO2", + "type": "number", + "values": [ + 42, + 56, + 60 + ] + } + ] + } +] \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/panel/radar/types.ts b/ui/src/views/dashboard/plugins/external/panel/radar/types.ts new file mode 100644 index 000000000..a1874741e --- /dev/null +++ b/ui/src/views/dashboard/plugins/external/panel/radar/types.ts @@ -0,0 +1,36 @@ +export const PanelTypeRadar = "radar" + +export interface PluginSettings { + animation: boolean + graph: { + legend: { + mode: boolean + left?: number + right?: number + top?: number + bottom?: number + orient?: string + itemGap?: number + } + } + radar: { + shape?: string + } +} + + +export const initSettings: PluginSettings = { + animation: false, + radar: { + shape: 'polygon' + }, + graph: { + legend: { + mode: true, + right: 0, + top: 0, + orient: 'vertical', + itemGap: 20 + } + } +} \ No newline at end of file diff --git a/ui/src/views/dashboard/plugins/external/plugins.ts b/ui/src/views/dashboard/plugins/external/plugins.ts index e2c39aa1c..08356b274 100644 --- a/ui/src/views/dashboard/plugins/external/plugins.ts +++ b/ui/src/views/dashboard/plugins/external/plugins.ts @@ -3,10 +3,12 @@ import { DatasourcePluginComponents, PanelPluginComponents } from "types/plugin" import candlestickPanel from "./panel/candlestick" +import radarPanel from "./panel/radar" import victoriaMetricsDatasrouce from "./datasource/victoriaMetrics" -export const externalPanelPlugins: Record = { +export const externalPanelPlugins: Record = { [candlestickPanel.settings.type]: candlestickPanel, + [radarPanel.settings.type]: radarPanel, } -export const externalDatasourcePlugins: Record = { +export const externalDatasourcePlugins: Record = { [victoriaMetricsDatasrouce.settings.type]: victoriaMetricsDatasrouce, } \ No newline at end of file