Commit 2d36923872e1ffeae5c312a4e3c755749c24c0e6

Authored by fengwotao
1 parent f1813b3b

feat(src/packages): 图表折线图新增实时折线图

  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 22 import { OverrideILoadConfigurationframeConfig } from '@/packages/components/external/Informations/Mores/OverrideILoadConfigurationframe'
23 23 import { OverrideVideoConfig } from '@/packages/components/external/Informations/Mores/OverrideVideo'
24 24 import { OverrideWaterPoloConfig } from '@/packages/components/external/Charts/Mores/OverrideWaterPolo'
  25 +import { OverrideLineRealTimeConfig } from '@/packages/components/external/Charts/Lines/OverrideLineRealTime'
25 26
26 27 export function useInjectLib(packagesList: EPackagesType) {
27 28 packagesList[EPackagesCategoryEnum.COMPOSES] = ComposesList
... ... @@ -51,6 +52,7 @@ export function useInjectLib(packagesList: EPackagesType) {
51 52 )
52 53 addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.INFORMATIONS, OverrideVideoConfig)
53 54 addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideWaterPoloConfig)
  55 + addWidgetToCategoryByCategoryName(packagesList, PackagesCategoryEnum.CHARTS, OverrideLineRealTimeConfig)
54 56 }
55 57
56 58 /**
... ...