Commit 38c2b3403cbf5ae92b1e83e831dd393349110a3a
Merge branch 'ft' into 'main_dev'
feat(src/packages): 图表折线图新增实时折线图 See merge request yunteng/thingskit-view!86
Showing
6 changed files
with
421 additions
and
0 deletions
| 1 | +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public' | ||
| 2 | +import { OverrideLineRealTimeConfig } from './index' | ||
| 3 | +import { CreateComponentType } from '@/packages/index.d' | ||
| 4 | +import cloneDeep from 'lodash/cloneDeep' | ||
| 5 | +import dataJson from './data.json' | ||
| 6 | + | ||
| 7 | +export const includes = ['legend', 'xAxis', 'yAxis', 'grid'] | ||
| 8 | +export const seriesItem = { | ||
| 9 | + type: 'line', | ||
| 10 | + label: { | ||
| 11 | + show: true, | ||
| 12 | + position: 'top', | ||
| 13 | + color: '#fff', | ||
| 14 | + fontSize: 12 | ||
| 15 | + }, | ||
| 16 | + symbolSize: 5, //设定实心点的大小 | ||
| 17 | + itemStyle: { | ||
| 18 | + color: null, | ||
| 19 | + borderRadius: 0 | ||
| 20 | + }, | ||
| 21 | + lineStyle: { | ||
| 22 | + type: 'solid', | ||
| 23 | + width: 3, | ||
| 24 | + color: null | ||
| 25 | + } | ||
| 26 | +} | ||
| 27 | +// 其它配置项比如新增(动画) | ||
| 28 | +const otherConfig = { | ||
| 29 | + // 轮播动画 | ||
| 30 | + isCarousel: false, | ||
| 31 | + //超过最大数据点则删除 | ||
| 32 | + chartMaxDataPoint: 10, | ||
| 33 | +} | ||
| 34 | +export const option = { | ||
| 35 | + ...otherConfig, | ||
| 36 | + tooltip: { | ||
| 37 | + show: true, | ||
| 38 | + trigger: 'axis', | ||
| 39 | + axisPointer: { | ||
| 40 | + type: 'line' | ||
| 41 | + } | ||
| 42 | + }, | ||
| 43 | + xAxis: { | ||
| 44 | + show: true, | ||
| 45 | + type: 'category' | ||
| 46 | + }, | ||
| 47 | + yAxis: { | ||
| 48 | + show: true, | ||
| 49 | + type: 'value' | ||
| 50 | + }, | ||
| 51 | + dataset: { ...dataJson }, | ||
| 52 | + series: [seriesItem, seriesItem], | ||
| 53 | + dataZoom: { | ||
| 54 | + show: false, | ||
| 55 | + min: 0, | ||
| 56 | + max: 100 | ||
| 57 | + } | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +export default class Config extends PublicConfigClass implements CreateComponentType { | ||
| 61 | + public key: string = OverrideLineRealTimeConfig.key | ||
| 62 | + public chartConfig = cloneDeep(OverrideLineRealTimeConfig) | ||
| 63 | + // 图表配置项 | ||
| 64 | + public option = echartOptionProfixHandle(option, includes) | ||
| 65 | +} |
| 1 | +<template> | ||
| 2 | + <!-- Echarts 全局设置 --> | ||
| 3 | + <global-setting :optionData="optionData"></global-setting> | ||
| 4 | + <CollapseItem :expanded="true" name="数值个数"> | ||
| 5 | + <SettingItemBox name="数值个数"> | ||
| 6 | + <SettingItem name="最大值"> | ||
| 7 | + <n-input-number | ||
| 8 | + v-model:value="optionData.chartMaxDataPoint" | ||
| 9 | + :min="1" | ||
| 10 | + :max="100" | ||
| 11 | + size="small" | ||
| 12 | + placeholder="最大值" | ||
| 13 | + ></n-input-number> | ||
| 14 | + </SettingItem> | ||
| 15 | + </SettingItemBox> | ||
| 16 | + </CollapseItem> | ||
| 17 | + <CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true"> | ||
| 18 | + <SettingItemBox name="线条"> | ||
| 19 | + <SettingItem name="宽度"> | ||
| 20 | + <n-input-number | ||
| 21 | + v-model:value="item.lineStyle.width" | ||
| 22 | + :min="1" | ||
| 23 | + :max="100" | ||
| 24 | + size="small" | ||
| 25 | + placeholder="自动计算" | ||
| 26 | + ></n-input-number> | ||
| 27 | + </SettingItem> | ||
| 28 | + <SettingItem name="类型"> | ||
| 29 | + <n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select> | ||
| 30 | + </SettingItem> | ||
| 31 | + </SettingItemBox> | ||
| 32 | + <SettingItemBox name="动画" :alone="true"> | ||
| 33 | + <SettingItem> | ||
| 34 | + <n-space> | ||
| 35 | + <n-switch v-model:value="optionData.isCarousel" size="small"></n-switch> | ||
| 36 | + <n-text>开启<n-text :depth="3">(将自动隐藏图例)</n-text></n-text> | ||
| 37 | + </n-space> | ||
| 38 | + </SettingItem> | ||
| 39 | + <SettingItem> | ||
| 40 | + <n-text :depth="3">无鼠标点击图例场景时,可强行打开图例</n-text> | ||
| 41 | + </SettingItem> | ||
| 42 | + </SettingItemBox> | ||
| 43 | + <SettingItemBox name="实心点"> | ||
| 44 | + <SettingItem name="大小"> | ||
| 45 | + <n-input-number | ||
| 46 | + v-model:value="item.symbolSize" | ||
| 47 | + :min="1" | ||
| 48 | + :max="100" | ||
| 49 | + size="small" | ||
| 50 | + placeholder="自动计算" | ||
| 51 | + ></n-input-number> | ||
| 52 | + </SettingItem> | ||
| 53 | + </SettingItemBox> | ||
| 54 | + <setting-item-box name="标签"> | ||
| 55 | + <setting-item> | ||
| 56 | + <n-space> | ||
| 57 | + <n-switch v-model:value="item.label.show" size="small" /> | ||
| 58 | + <n-text>展示标签</n-text> | ||
| 59 | + </n-space> | ||
| 60 | + </setting-item> | ||
| 61 | + <setting-item name="大小"> | ||
| 62 | + <n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number> | ||
| 63 | + </setting-item> | ||
| 64 | + <setting-item name="颜色"> | ||
| 65 | + <n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker> | ||
| 66 | + </setting-item> | ||
| 67 | + <setting-item name="位置"> | ||
| 68 | + <n-select | ||
| 69 | + v-model:value="item.label.position" | ||
| 70 | + :options="[ | ||
| 71 | + { label: 'top', value: 'top' }, | ||
| 72 | + { label: 'left', value: 'left' }, | ||
| 73 | + { label: 'right', value: 'right' }, | ||
| 74 | + { label: 'bottom', value: 'bottom' } | ||
| 75 | + ]" | ||
| 76 | + /> | ||
| 77 | + </setting-item> | ||
| 78 | + </setting-item-box> | ||
| 79 | + </CollapseItem> | ||
| 80 | +</template> | ||
| 81 | + | ||
| 82 | +<script setup lang="ts"> | ||
| 83 | +import { PropType, computed } from 'vue' | ||
| 84 | +import { lineConf } from '@/packages/chartConfiguration/echarts/index' | ||
| 85 | +import { GlobalThemeJsonType } from '@/settings/chartThemes/index' | ||
| 86 | +import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | ||
| 87 | + | ||
| 88 | +const props = defineProps({ | ||
| 89 | + optionData: { | ||
| 90 | + type: Object as PropType<GlobalThemeJsonType>, | ||
| 91 | + required: true | ||
| 92 | + } | ||
| 93 | +}) | ||
| 94 | +const seriesList = computed(() => { | ||
| 95 | + return props.optionData.series | ||
| 96 | +}) | ||
| 97 | +</script> |
| 1 | +{ | ||
| 2 | + "dimensions": ["product", "data1", "data2"], | ||
| 3 | + "source": [ | ||
| 4 | + { | ||
| 5 | + "product": "Mon", | ||
| 6 | + "data1": 120, | ||
| 7 | + "data2": 130 | ||
| 8 | + }, | ||
| 9 | + { | ||
| 10 | + "product": "Tue", | ||
| 11 | + "data1": 200, | ||
| 12 | + "data2": 130 | ||
| 13 | + }, | ||
| 14 | + { | ||
| 15 | + "product": "Wed", | ||
| 16 | + "data1": 150, | ||
| 17 | + "data2": 312 | ||
| 18 | + }, | ||
| 19 | + { | ||
| 20 | + "product": "Thu", | ||
| 21 | + "data1": 80, | ||
| 22 | + "data2": 268 | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + "product": "Fri", | ||
| 26 | + "data1": 70, | ||
| 27 | + "data2": 155 | ||
| 28 | + }, | ||
| 29 | + { | ||
| 30 | + "product": "Sat", | ||
| 31 | + "data1": 110, | ||
| 32 | + "data2": 117 | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + "product": "Sun", | ||
| 36 | + "data1": 130, | ||
| 37 | + "data2": 160 | ||
| 38 | + } | ||
| 39 | + ] | ||
| 40 | +} |
| 1 | +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d' | ||
| 2 | +import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/Charts/index.d' | ||
| 3 | +import { useWidgetKey } from '@/packages/external/useWidgetKey' | ||
| 4 | + | ||
| 5 | +const { key, conKey, chartKey } = useWidgetKey('OverrideLineRealTime', true) | ||
| 6 | + | ||
| 7 | +export const OverrideLineRealTimeConfig: ConfigType = { | ||
| 8 | + key, | ||
| 9 | + chartKey, | ||
| 10 | + conKey, | ||
| 11 | + title: '实时折线图', | ||
| 12 | + category: ChatCategoryEnum.LINE, | ||
| 13 | + categoryName: ChatCategoryEnumName.LINE, | ||
| 14 | + package: PackagesCategoryEnum.CHARTS, | ||
| 15 | + chartFrame: ChartFrameEnum.ECHARTS, | ||
| 16 | + image: 'line.png' | ||
| 17 | +} |
| 1 | +<template> | ||
| 2 | + <v-chart | ||
| 3 | + ref="vChartRef" | ||
| 4 | + :init-options="initOptions" | ||
| 5 | + :theme="themeColor" | ||
| 6 | + :option="option" | ||
| 7 | + :manual-update="isPreview()" | ||
| 8 | + :update-options="{ | ||
| 9 | + replaceMerge: replaceMergeArr | ||
| 10 | + }" | ||
| 11 | + autoresize | ||
| 12 | + @mouseover="handleHighlight" | ||
| 13 | + @mouseout="handleDownplay" | ||
| 14 | + > | ||
| 15 | + </v-chart> | ||
| 16 | +</template> | ||
| 17 | + | ||
| 18 | +<script setup lang="ts"> | ||
| 19 | +import { PropType, computed, watch, ref, onMounted, unref, toRaw, toRefs } from 'vue' | ||
| 20 | +import VChart from 'vue-echarts' | ||
| 21 | +import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' | ||
| 22 | +import { use } from 'echarts/core' | ||
| 23 | +import { CanvasRenderer } from 'echarts/renderers' | ||
| 24 | +import { LineChart } from 'echarts/charts' | ||
| 25 | +import config, { includes } from './config' | ||
| 26 | +import { mergeTheme } from '@/packages/public/chart' | ||
| 27 | +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' | ||
| 28 | +import { useChartDataFetch } from '@/hooks' | ||
| 29 | +import { isPreview } from '@/utils' | ||
| 30 | +import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' | ||
| 31 | +import dataJson from './data.json' | ||
| 32 | +import { useFullScreen } from '../../utls/fullScreen' | ||
| 33 | +import { SocketReceiveMessageType } from '@/store/external/modules/socketStore.d' | ||
| 34 | +import dayjs from 'dayjs' | ||
| 35 | + | ||
| 36 | +const props = defineProps({ | ||
| 37 | + themeSetting: { | ||
| 38 | + type: Object, | ||
| 39 | + required: true | ||
| 40 | + }, | ||
| 41 | + themeColor: { | ||
| 42 | + type: Object, | ||
| 43 | + required: true | ||
| 44 | + }, | ||
| 45 | + chartConfig: { | ||
| 46 | + type: Object as PropType<config>, | ||
| 47 | + required: true | ||
| 48 | + } | ||
| 49 | +}) | ||
| 50 | + | ||
| 51 | +interface RealTimeItemType { | ||
| 52 | + [key: string]: number | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +const { chartMaxDataPoint } = toRefs(props.chartConfig.option) | ||
| 56 | + | ||
| 57 | +const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting) | ||
| 58 | + | ||
| 59 | +use([DatasetComponent, CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent]) | ||
| 60 | + | ||
| 61 | +const replaceMergeArr = ref<string[]>() | ||
| 62 | + | ||
| 63 | +const option = computed(() => { | ||
| 64 | + return mergeTheme(props.chartConfig.option, props.themeSetting, includes) | ||
| 65 | +}) | ||
| 66 | + | ||
| 67 | +const toolBoxOption = { | ||
| 68 | + show: true, | ||
| 69 | + right: 20, | ||
| 70 | + feature: { | ||
| 71 | + myFullButton: { | ||
| 72 | + show: true, | ||
| 73 | + title: '全屏查看', | ||
| 74 | + icon: 'path://M733.549304 0l116.434359 116.23452-226.402521 226.40252 57.053835 57.068109 226.459617-226.445342 120.616689 120.41685V0H733.549304zM689.513507 619.855586l-57.068108 57.068109 224.232847 224.232847-122.64362 122.843458h293.676657V729.838022l-114.007751 114.207588-224.190025-224.190024zM338.197775 404.144414l57.068109-57.068109L171.033037 122.843458 293.676657 0H0v294.161978l114.022025-114.207588 224.17575 224.190024zM347.076305 624.294851L120.616689 850.754468 0 730.323343v293.676657h294.161978l-116.420084-116.23452 226.40252-226.40252-57.068109-57.068109z', | ||
| 75 | + onclick: () => { | ||
| 76 | + const getEchartDom = vChartRef.value?.getDom() | ||
| 77 | + const domName = document.getElementById(getEchartDom.id) as HTMLElement | ||
| 78 | + const htmlName = document.querySelector('html') as HTMLHtmlElement | ||
| 79 | + useFullScreen(domName, htmlName) | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +props.chartConfig.option = { | ||
| 86 | + ...props.chartConfig.option, | ||
| 87 | + ...{ toolbox: toolBoxOption } | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +const realTimeList = ref<RealTimeItemType[]>([]) | ||
| 91 | + | ||
| 92 | +let seriesDataNum = -1 | ||
| 93 | +let seriesDataMaxLength = 0 | ||
| 94 | +let intervalInstance: any = null | ||
| 95 | +const duration = 1500 | ||
| 96 | + | ||
| 97 | +// 会重新选择需要选中和展示的数据 | ||
| 98 | +const handleSeriesData = () => { | ||
| 99 | + if (seriesDataNum > -1) { | ||
| 100 | + vChartRef.value?.dispatchAction({ | ||
| 101 | + type: 'downplay', | ||
| 102 | + dataIndex: seriesDataNum | ||
| 103 | + }) | ||
| 104 | + } | ||
| 105 | + seriesDataNum = seriesDataNum >= seriesDataMaxLength - 1 ? 0 : seriesDataNum + 1 | ||
| 106 | + vChartRef.value?.dispatchAction({ | ||
| 107 | + type: 'showTip', | ||
| 108 | + seriesIndex: 0, | ||
| 109 | + dataIndex: seriesDataNum | ||
| 110 | + }) | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +// 新增轮播 | ||
| 114 | +const addPieInterval = (newData?: typeof dataJson, skipPre = false) => { | ||
| 115 | + if (!skipPre && !Array.isArray(newData?.source)) return | ||
| 116 | + if (!skipPre) seriesDataMaxLength = newData?.source.length || 0 | ||
| 117 | + clearInterval(intervalInstance) | ||
| 118 | + intervalInstance = setInterval(() => { | ||
| 119 | + handleSeriesData() | ||
| 120 | + }, duration) | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +// 取消轮播 | ||
| 124 | +const clearPieInterval = () => { | ||
| 125 | + vChartRef.value?.dispatchAction({ | ||
| 126 | + type: 'hideTip', | ||
| 127 | + seriesIndex: 0, | ||
| 128 | + dataIndex: seriesDataNum | ||
| 129 | + }) | ||
| 130 | + vChartRef.value?.dispatchAction({ | ||
| 131 | + type: 'downplay', | ||
| 132 | + dataIndex: seriesDataNum | ||
| 133 | + }) | ||
| 134 | + clearInterval(intervalInstance) | ||
| 135 | + intervalInstance = null | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +// 处理鼠标聚焦高亮内容 | ||
| 139 | +const handleHighlight = () => { | ||
| 140 | + clearPieInterval() | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +// 处理鼠标取消悬浮 | ||
| 144 | +const handleDownplay = () => { | ||
| 145 | + if (props.chartConfig.option.isCarousel && !intervalInstance) { | ||
| 146 | + // 恢复轮播 | ||
| 147 | + addPieInterval(undefined, true) | ||
| 148 | + } | ||
| 149 | +} | ||
| 150 | + | ||
| 151 | +watch( | ||
| 152 | + () => props.chartConfig.option.isCarousel, | ||
| 153 | + newData => { | ||
| 154 | + if (newData) { | ||
| 155 | + addPieInterval(undefined, true) | ||
| 156 | + props.chartConfig.option.legend.show = false | ||
| 157 | + } else { | ||
| 158 | + props.chartConfig.option.legend.show = true | ||
| 159 | + clearPieInterval() | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | +) | ||
| 163 | + | ||
| 164 | +const formatValueToNumber = (value: string | number, defaultValue = 0) => | ||
| 165 | + isNaN(value as number) ? defaultValue : Number(value) | ||
| 166 | + | ||
| 167 | +//fix 修复v-chart图表绑定联动组件视图不更新问题 | ||
| 168 | +const updateVChart = (newData: SocketReceiveMessageType) => { | ||
| 169 | + if (!newData) return | ||
| 170 | + const { data } = newData | ||
| 171 | + const keys = Object.keys(data) | ||
| 172 | + const record = keys.reduce((prev, next) => { | ||
| 173 | + const [latest] = data[next] || [] | ||
| 174 | + const [ts, value] = latest as unknown as string[] | ||
| 175 | + return { ...prev, ts: dayjs(ts).format('YYYY-MM-DD HH:mm:ss'), [next]: formatValueToNumber(value) } | ||
| 176 | + // return { ...prev, ts, [next]: formatValueToNumber(value) } | ||
| 177 | + }, {}) | ||
| 178 | + if (unref(realTimeList).length >= chartMaxDataPoint.value) { | ||
| 179 | + unref(realTimeList).splice(0, 1) | ||
| 180 | + } | ||
| 181 | + realTimeList.value.push(record) | ||
| 182 | + vChartRef.value?.setOption({ | ||
| 183 | + dataset: { | ||
| 184 | + dimensions: ['ts', ...keys], | ||
| 185 | + source: toRaw(unref(realTimeList)) | ||
| 186 | + } | ||
| 187 | + }) | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, newData => { | ||
| 191 | + updateVChart(newData) | ||
| 192 | +}) | ||
| 193 | + | ||
| 194 | +onMounted(() => { | ||
| 195 | + seriesDataMaxLength = dataJson.source.length | ||
| 196 | + if (props.chartConfig.option.isCarousel) { | ||
| 197 | + addPieInterval(undefined, true) | ||
| 198 | + } | ||
| 199 | +}) | ||
| 200 | +</script> |
| @@ -22,6 +22,7 @@ import { OverrideMapBaseConfig } from '@/packages/components/external/Charts/Map | @@ -22,6 +22,7 @@ import { OverrideMapBaseConfig } from '@/packages/components/external/Charts/Map | ||
| 22 | import { OverrideILoadConfigurationframeConfig } from '@/packages/components/external/Informations/Mores/OverrideILoadConfigurationframe' | 22 | import { OverrideILoadConfigurationframeConfig } from '@/packages/components/external/Informations/Mores/OverrideILoadConfigurationframe' |
| 23 | import { OverrideVideoConfig } from '@/packages/components/external/Informations/Mores/OverrideVideo' | 23 | import { OverrideVideoConfig } from '@/packages/components/external/Informations/Mores/OverrideVideo' |
| 24 | import { OverrideWaterPoloConfig } from '@/packages/components/external/Charts/Mores/OverrideWaterPolo' | 24 | import { OverrideWaterPoloConfig } from '@/packages/components/external/Charts/Mores/OverrideWaterPolo' |
| 25 | +import { OverrideLineRealTimeConfig } from '@/packages/components/external/Charts/Lines/OverrideLineRealTime' | ||
| 25 | 26 | ||
| 26 | export function useInjectLib(packagesList: EPackagesType) { | 27 | export function useInjectLib(packagesList: EPackagesType) { |
| 27 | packagesList[EPackagesCategoryEnum.COMPOSES] = ComposesList | 28 | packagesList[EPackagesCategoryEnum.COMPOSES] = ComposesList |
| @@ -51,6 +52,7 @@ export function useInjectLib(packagesList: EPackagesType) { | @@ -51,6 +52,7 @@ export function useInjectLib(packagesList: EPackagesType) { | ||
| 51 | ) | 52 | ) |
| 52 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideVideoConfig) | 53 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideVideoConfig) |
| 53 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideWaterPoloConfig) | 54 | addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideWaterPoloConfig) |
| 55 | + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideLineRealTimeConfig) | ||
| 54 | } | 56 | } |
| 55 | 57 | ||
| 56 | /** | 58 | /** |