This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
cyber-narrator-cn-ui/src/views/charts/Chart.vue

571 lines
22 KiB
Vue
Raw Normal View History

<template>
2021-07-01 21:39:10 +08:00
<!-- 标题 -->
2021-06-25 19:11:00 +08:00
<div
v-if="isTitle"
class="cn-chart cn-chart__title"
:style="computePosition">{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</div>
2021-07-01 21:39:10 +08:00
<!-- Tabs -->
2021-07-05 17:40:43 +08:00
<el-tabs
2021-07-01 21:39:10 +08:00
v-else-if="isTabs"
class="cn-chart cn-chart__tabs"
2021-07-05 17:40:43 +08:00
v-model="activeTab"
@tab-click="changeTab"
:style="computePosition"
>
<el-tab-pane
2021-07-05 22:58:12 +08:00
v-for="tab in chartInfo.children"
2021-07-05 17:40:43 +08:00
:label="tab.i18n ? $t(tab.i18n) : tab.name" :name="`${tab.id}`"
:key="tab.id"
>
2021-07-05 22:58:12 +08:00
<template v-for="(chart, index) in tab.children">
<chart v-if="activeTab == tab.id" :key="index" :chart="chart" :start-time="startTime" :end-time="endTime"></chart>
</template>
2021-07-05 17:40:43 +08:00
</el-tab-pane>
</el-tabs>
2021-06-24 17:59:51 +08:00
<!-- 地图 -->
<chart-map
2021-06-25 19:11:00 +08:00
v-else-if="isMap"
2021-06-24 17:59:51 +08:00
:style="computePosition"
>
<template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template>
<template #operations>
<i class="cn-icon cn-icon-more-light"></i>
2021-07-10 11:18:40 +08:00
<!-- <i class="cn-icon cn-icon-refresh" @click="loadMap"></i>-->
2021-06-24 17:59:51 +08:00
</template>
<template #default>
<div class="chart-drawing" :id="`chart${chartInfo.id}`"></div>
</template>
</chart-map>
<!-- echarts类的图如饼图柱状图折线图等 -->
<echarts-frame
2021-06-24 17:59:51 +08:00
v-else-if="isEcharts"
:layout="layout"
:style="computePosition"
2021-06-23 15:57:34 +08:00
:chartInfo="chartInfo"
>
<template #title v-if="layout.indexOf(layoutConstant.HEADER) > -1">
{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}
</template>
<template #operations v-if="layout.indexOf(layoutConstant.HEADER) > -1">
<div class="header__operation header__operation--table" v-if="chart.type === 31">
<el-select
size="mini"
v-model="orderPieTable"
class="option__select select-topn"
placeholder=""
popper-class="option-popper"
@change="orderPieTableChange"
>
<el-option v-for="item in chartPieTableTopOptions" :key="item.value" :value="item.value">&nbsp{{item.name}}</el-option>
<template #prefix>TOP&nbsp;</template>
</el-select>
</div>
<i class="cn-icon cn-icon-more-light margin-l-10"></i>
</template>
<template #default>
<div class="chart-drawing" :id="`chart${chartInfo.id}`"></div>
</template>
2021-06-23 15:57:34 +08:00
<template #footer v-if="layout.indexOf(layoutConstant.FOOTER) > -1" :class="{}">
<!-- 带Table的饼图展示Table -->
<template v-if="isEchartsWithTable">
<pie-table :tableData="pieTableData" ref="pieTable" :chartInfo="chartInfo" :start-time="startTime" :end-time="endTime" :order="orderPieTable"/>
2021-07-05 22:58:12 +08:00
</template>
<template v-else-if="isEchartsWithStatistics">
<statistics-legend :data="statisticsData"></statistics-legend>
</template>
</template>
</echarts-frame>
<!-- 单值图 -->
<single-value
v-else-if="isSingleValue"
:type="chartInfo.type"
:style="computePosition"
:icon="singleValue.icon"
>
2021-07-06 18:29:46 +08:00
<template #title><span :title="chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name">{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</span></template>
2021-07-13 18:38:32 +08:00
<template #data>
<span>{{handleSingleValue(singleValue.value)}}</span>
<span v-if="chartInfo.params && chartInfo.params.unit" class="single-value__unit">{{chartInfo.params.unit}}</span>
</template>
</single-value>
<!-- 表格 -->
<chart-table
v-else-if="isTable"
2021-06-21 20:33:39 +08:00
:table-columns="table.tableColumns"
:table-data="table.currentPageData"
:style="computePosition"
>
<template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template>
<template #operations>
<!-- <div class="header__operation header__operation&#45;&#45;table">
<span class="option__button"><i class="cn-icon cn-icon-download"></i></span>
</div>-->
2021-06-21 20:33:39 +08:00
<div class="header__operation header__operation--table">
<el-select
size="mini"
v-model="table.limit"
class="option__select select-topn"
2021-06-21 20:33:39 +08:00
placeholder=""
popper-class="option-popper"
2021-07-09 21:58:49 +08:00
@change="tableLimitChange"
2021-06-21 20:33:39 +08:00
>
<el-option v-for="item in chartTableTopOptions" :key="item" :value="item">TOP&nbsp;{{item}}</el-option>
<template #prefix>TOP&nbsp;</template>
</el-select>
</div>
<div class="header__operation header__operation--table">
<el-select
size="mini"
v-model="table.orderBy"
class="option__select select-column"
2021-07-05 22:58:12 +08:00
:placeholder="$t('common.field')"
popper-class="option-popper"
2021-07-09 21:58:49 +08:00
@change="tableLimitChange"
2021-06-21 20:33:39 +08:00
>
<el-option v-for="item in table.tableColumns" :key="item.prop" :value="item.prop">{{item.prop}}</el-option>
2021-06-21 20:33:39 +08:00
</el-select>
</div>
<!-- <div class="header__operation header__operation&#45;&#45;table">
<span class="option__button"><i class="cn-icon cn-icon-style"></i></span>
<div class="icon-group-divide"></div>
<span class="option__button"><i class="cn-icon cn-icon-dropdown"></i></span>
</div>-->
<div class="header__operation header__operation--table">
<span class="option__button"><i class="cn-icon cn-icon-full-screen"></i></span>
</div>
</template>
2021-06-21 20:33:39 +08:00
<template #footer>
<chart-table-pagination
:total="table.tableData.length"
@pageJump="pageJump"
></chart-table-pagination>
</template>
</chart-table>
</template>
<script>
import * as echarts from 'echarts'
2021-06-24 17:59:51 +08:00
import * as am4Core from '@amcharts/amcharts4/core'
import * as am4Maps from '@amcharts/amcharts4/maps'
2021-07-05 22:58:12 +08:00
import { shallowRef } from 'vue'
2021-07-08 10:43:48 +08:00
import { allTableTitle } from '@/components/charts/chartTableTitle'
2021-06-24 17:59:51 +08:00
import {
isEcharts,
isSingleValue,
isTable,
2021-06-25 19:11:00 +08:00
isTitle,
2021-06-24 17:59:51 +08:00
isMap,
getOption,
getTypeCategory,
getLayout,
layoutConstant,
isEchartsWithTable,
2021-07-05 22:58:12 +08:00
isEchartsWithStatistics,
2021-07-01 21:39:10 +08:00
isMapLine,
2021-07-10 12:11:59 +08:00
isMapBlock,
2021-07-06 10:55:09 +08:00
isTabs,
getChartColor
2021-06-24 17:59:51 +08:00
} from '@/components/charts/chart-options'
import EchartsFrame from '@/components/charts/EchartsFrame'
import SingleValue from '@/components/charts/ChartSingleValue'
2021-06-24 17:59:51 +08:00
import ChartTable from '@/components/charts/ChartTable'
import ChartMap from '@/components/charts/ChartMap'
2021-06-23 15:57:34 +08:00
import PieTable from '@/components/charts/PieTable'
2021-07-05 22:58:12 +08:00
import StatisticsLegend from '@/components/charts/StatisticsLegend'
2021-06-21 20:33:39 +08:00
import ChartTablePagination from '@/components/charts/ChartTablePagination'
import { chartTableDefaultPageSize, chartTableTopOptions, storageKey, chartPieTableTopOptions } from '@/utils/constants'
import { get } from '@/utils/http'
import { replaceUrlPlaceholder, getCapitalGeo, getGeoData } from '@/utils/tools'
export default {
name: 'Chart',
props: {
2021-06-23 15:57:34 +08:00
chart: Object, // 图表对象包括id、name、type等数据
startTime: {
type: Number
},
endTime: {
type: Number
}
},
components: {
EchartsFrame,
SingleValue,
2021-06-21 20:33:39 +08:00
ChartTablePagination,
2021-06-24 17:59:51 +08:00
ChartTable,
PieTable,
2021-07-05 22:58:12 +08:00
StatisticsLegend,
2021-07-05 17:40:43 +08:00
ChartMap
},
data () {
return {
2021-06-21 20:33:39 +08:00
table: {
pageSize: chartTableDefaultPageSize,
limit: chartTableTopOptions[0], // top-n
2021-07-07 18:47:52 +08:00
orderBy: 'sessions',
2021-06-21 20:33:39 +08:00
tableColumns: [], // table字段
tableData: [], // table的所有数据
currentPageData: [] // table当前页的数据
2021-06-23 15:57:34 +08:00
},
2021-06-25 19:11:00 +08:00
pieTableData: [],
singleValue: {
value: '-',
icon: ''
},
2021-07-05 17:40:43 +08:00
activeTab: '',
statisticsData: [],
orderPieTable: chartPieTableTopOptions[0].value,
selectPieChartName: ''
}
},
methods: {
initChart () {
2021-07-13 18:38:32 +08:00
const chartParams = this.chartInfo.params
2021-06-24 17:59:51 +08:00
if (this.isMap) {
2021-07-10 12:11:59 +08:00
const { chart, polygonSeries } = this.initMap(`chart${this.chartInfo.id}`)
this.myChart = chart
2021-06-24 17:59:51 +08:00
if (chartParams) {
2021-07-10 12:11:59 +08:00
this.loadMap(polygonSeries)
2021-07-01 15:39:48 +08:00
}
2021-06-24 17:59:51 +08:00
} else if (this.isEcharts) {
2021-06-25 10:10:35 +08:00
const dom = document.getElementById(`chart${this.chartInfo.id}`)
2021-06-25 19:11:00 +08:00
this.myChart = echarts.init(dom)
if (chartParams) {
if (this.isEchartsWithTable) {
2021-07-10 11:18:40 +08:00
this.initEchartsWithPieTable(chartParams)
2021-07-05 22:58:12 +08:00
} else if (this.isEchartsWithStatistics) {
2021-07-10 11:18:40 +08:00
this.initEchartsWithStatistics(chartParams)
2021-07-06 09:55:51 +08:00
} else {
2021-07-10 11:18:40 +08:00
this.initECharts(chartParams)
2021-06-23 15:57:34 +08:00
}
}
} else if (this.isTable) {
2021-06-24 17:59:51 +08:00
if (chartParams) {
2021-07-07 18:47:52 +08:00
this.initChartTable(chartParams)
}
2021-07-01 21:39:10 +08:00
} else if (this.isSingleValue) {
if (chartParams) {
this.singleValue.icon = chartParams.icon
2021-07-05 17:40:43 +08:00
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000) }
2021-07-01 21:39:10 +08:00
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
this.singleValue.value = response.data.result
2021-07-01 21:39:10 +08:00
}
})
}
2021-07-05 17:40:43 +08:00
} else if (this.isTabs) {
if (!this.$_.isEmpty(this.chartInfo.children)) {
this.activeTab = `${this.chartInfo.children[0].id}`
}
}
2021-06-21 20:33:39 +08:00
},
2021-07-01 15:39:48 +08:00
getTitle (data) {
return `Server: ${data.serverRegion ? data.serverRegion : data.serverCountry}
Client: ${data.clientRegion ? data.clientRegion : data.clientCountry}
Sessions: ${data.sessions}
Bytes: ${data.bytes}`
},
2021-07-05 17:40:43 +08:00
changeTab (tab) {
this.activeTab = tab.paneName
},
2021-06-24 17:59:51 +08:00
initMap (id) {
2021-06-29 19:45:44 +08:00
const chart = am4Core.create(id, am4Maps.MapChart)
chart.geodata = getGeoData(storageKey.iso36112WorldLow)
2021-06-29 19:45:44 +08:00
chart.projection = new am4Maps.projections.Miller()
const polygonSeries = chart.series.push(new am4Maps.MapPolygonSeries())
polygonSeries.useGeodata = true
polygonSeries.exclude = ['AQ'] // 排除南极洲
// 鼠标悬停提示
/* const polygonTemplate = polygonSeries.mapPolygons.template
2021-07-01 15:39:48 +08:00
polygonTemplate.tooltipText = '{name}'
polygonTemplate.fontSize = '12px'
2021-06-29 19:45:44 +08:00
const hs = polygonTemplate.states.create('hover')
hs.properties.fill = am4Core.color('#ccc') */
2021-07-10 12:11:59 +08:00
return {
chart,
polygonSeries
}
2021-06-24 17:59:51 +08:00
},
2021-07-10 12:11:59 +08:00
loadMap (polygonSeries) {
2021-07-13 18:38:32 +08:00
const chartParams = this.chartInfo.params
2021-07-08 18:02:57 +08:00
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), country: '', region: '' } // 统计数据的查询参数
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
const data = response.data.result
data.forEach(r => {
const serverCountryCapital = r.serverId && getCapitalGeo(r.serverId)
const clientCountryCapital = r.clientId && getCapitalGeo(r.clientId)
serverCountryCapital && (r.serverLongitude = serverCountryCapital.capitalLongitude)
serverCountryCapital && (r.serverLatitude = serverCountryCapital.capitalLatitude)
clientCountryCapital && (r.clientLongitude = clientCountryCapital.capitalLongitude)
clientCountryCapital && (r.clientLatitude = clientCountryCapital.capitalLatitude)
})
if (this.isMapLine) {
const lineSeries = this.myChart.series.push(new am4Maps.MapLineSeries())
const lineTemplate = lineSeries.mapLines.template
lineTemplate.stroke = am4Core.color('#A258EC')
lineTemplate.line.nonScalingStroke = true
lineTemplate.line.strokeDasharray = '4 3'
lineTemplate.nonScalingStroke = true
lineTemplate.arrow.nonScaling = true
lineTemplate.arrow.width = 4
lineTemplate.arrow.height = 6
lineSeries.data = [
{
multiGeoLine: data.map(d => {
return [
{
latitude: parseFloat(d.serverLatitude),
longitude: parseFloat(d.serverLongitude)
},
{
latitude: parseFloat(d.clientLatitude),
longitude: parseFloat(d.clientLongitude)
}
]
})
}
]
const imageSeries = this.myChart.series.push(new am4Maps.MapImageSeries())
imageSeries.dataFields.value = 'sessions'
const imageSeriesTemplate = imageSeries.mapImages.template
const circle = imageSeriesTemplate.createChild(am4Core.Circle)
circle.fillOpacity = 0.7
circle.nonScaling = true
circle.tooltipText = '{title}'
const radiusHeat = imageSeries.heatRules.push({
target: circle,
property: 'radius',
min: 8,
max: 30
})
const colorHeat = imageSeries.heatRules.push({
target: circle,
property: 'fill',
min: am4Core.color('#D2A8FF'),
max: am4Core.color('#A258EC')
})
imageSeriesTemplate.propertyFields.latitude = 'latitude'
imageSeriesTemplate.propertyFields.longitude = 'longitude'
const pointData = []
data.forEach(d => {
pointData.push({
...d,
latitude: parseFloat(d.serverLatitude),
longitude: parseFloat(d.serverLongitude),
title: this.getTitle(d)
})
pointData.push({
...d,
latitude: parseFloat(d.clientLatitude),
longitude: parseFloat(d.clientLongitude),
title: this.getTitle(d)
})
})
imageSeries.data = pointData
2021-07-10 12:11:59 +08:00
} else if (this.isMapBlock) {
polygonSeries.heatRules.push({
property: 'fill',
target: polygonSeries.mapPolygons.template,
min: am4Core.color('#FFFF00'),
max: am4Core.color('#E66767')
})
polygonSeries.data = response.data.result.map(r => {
return {
id: r.serverId,
value: r.establishLatency || r.httpResponseLatency || r.sslConLatency || r.sequenceGapLossPercent || r.pktRetransPercent
}
})
const polygonTemplate = polygonSeries.mapPolygons.template
polygonTemplate.tooltipText = '{name}: {value}'
polygonTemplate.nonScalingStroke = true
polygonTemplate.strokeWidth = 0.5
const hs = polygonTemplate.states.create('hover')
// hs.properties.fill = am4Core.color('#3c5bdc')
2021-07-08 18:02:57 +08:00
}
}
})
},
2021-06-21 20:33:39 +08:00
pageJump (val) {
this.table.currentPageData = this.getTargetPageData(val, this.table.pageSize, this.table.tableData)
},
getTargetPageData (pageNum, pageSize, tableData) {
return this.$_.slice(tableData, (pageNum - 1) * pageSize, pageNum * pageSize)
},
orderPieTableChange () {
if (this.chart.type === 31) {
const chartParams = this.chartInfo.params ? JSON.parse(this.chartInfo.params) : null // 图表参数
this.myChart.off('click')
2021-07-10 11:18:40 +08:00
this.initEchartsWithPieTable(chartParams)
}
},
2021-07-10 11:18:40 +08:00
initECharts (chartParams) {
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000) }
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
this.statisticsData = response.data.result
const seriesTemplate = this.chartOption.series[0]
this.chartOption.series = response.data.result.map(r => {
return {
...seriesTemplate,
name: r.legend,
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1])])
}
})
}
this.myChart.setOption(this.chartOption)
this.$nextTick(() => {
this.myChart.resize()
})
})
},
initEchartsWithStatistics (chartParams) {
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000) }
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
this.statisticsData = response.data.result
const seriesTemplate = this.chartOption.series[0]
this.chartOption.series = response.data.result.map((r, i) => {
return {
...seriesTemplate,
name: r.legend,
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1])]),
lineStyle: {
color: getChartColor[i]
}
}
})
}
this.myChart.setOption(this.chartOption)
this.$nextTick(() => {
this.myChart.resize()
})
})
},
initEchartsWithPieTable (chartParams) {
2021-07-07 18:33:05 +08:00
const self = this
chartParams.valueColumn = this.orderPieTable
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: 10, order: this.orderPieTable } // 统计数据的查询参数
2021-07-08 14:27:27 +08:00
const tableQueryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: 10, order: this.orderPieTable } // 统计数据的查询参数
tableQueryParams[chartParams.nameColumn] = [] // 处理两个图表不一样的地方
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
const data = response.data.result
this.chartOption.series[0].data = data.map(d => {
tableQueryParams[chartParams.nameColumn].push(d[chartParams.nameColumn])
return {
data: d,
name: d[chartParams.nameColumn],
value: parseInt(d[chartParams.valueColumn])
}
})
if (this.chartOption.series[0].data && this.chartOption.series[0].data.length > 10) { // pieWithTable 图例超过10个改为滚动显示
this.chartOption.legend.type = 'scroll'
}
this.myChart.setOption(this.chartOption)
this.$nextTick(() => {
this.myChart.resize()
})
get(replaceUrlPlaceholder(chartParams.urlTable, tableQueryParams)).then(response2 => {
if (response2.code === 200) {
this.pieTableData = response2.data.result
}
})
}
})
2021-07-10 12:30:06 +08:00
this.myChart.on('legendselectchanged', function (params) {
self.myChart.setOption({
legend: { selected: { [params.name]: true } }
})
})
this.myChart.on('click', function (echartParams) {
const childrenParams = { startTime: parseInt(self.startTime / 1000), endTime: parseInt(self.endTime / 1000), limit: 10, order: self.orderPieTable }
childrenParams[chartParams.nameColumn] = echartParams.name
if (self.selectPieChartName === echartParams.name) {
self.selectPieChartName = ''
childrenParams[chartParams.nameColumn] = tableQueryParams[chartParams.nameColumn]
} else {
self.selectPieChartName = echartParams.name
}
get(replaceUrlPlaceholder(chartParams.urlTable, childrenParams)).then(response2 => {
if (response2.code === 200) {
self.pieTableData = response2.data.result
}
})
})
},
2021-07-09 21:58:49 +08:00
tableLimitChange () {
2021-07-07 18:47:52 +08:00
const chartParams = this.chartInfo.params ? JSON.parse(this.chartInfo.params) : null // 图表参数
this.initChartTable(chartParams)
},
initChartTable (chartParams) {
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: this.table.limit, order: this.table.orderBy }
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
2021-07-08 10:43:48 +08:00
this.table.tableColumns = allTableTitle['tableTitles' + this.chart.id]
2021-07-07 18:47:52 +08:00
this.table.tableData = response.data.result
this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
}
})
}
},
computed: {
computePosition () {
const gridColumn = `${this.chartInfo.x} / ${this.chartInfo.x + this.chartInfo.w}`
const gridRow = `${this.chartInfo.y} / ${this.chartInfo.y + this.chartInfo.h}`
return {
gridColumn,
gridRow
}
2021-07-13 18:38:32 +08:00
},
handleSingleValue () {
return function (value) {
return Number(value) < 0.01 ? '< 0.01' : this.$_.round(value, 2)
}
}
},
mounted () {
this.initChart()
},
2021-07-09 21:58:49 +08:00
watch: {
chart: {
immediate: true,
deep: true,
handler (n, o) {
if (o) {
this.initChart()
}
}
}
},
setup (props) {
const chartInfo = JSON.parse(JSON.stringify(props.chart))
chartInfo.category = getTypeCategory(props.chart.type)
2021-07-13 18:38:32 +08:00
chartInfo.params = chartInfo.params ? JSON.parse(chartInfo.params) : null
return {
chartInfo,
layoutConstant,
2021-06-21 20:33:39 +08:00
chartTableTopOptions,
chartPieTableTopOptions,
chartOption: getOption(props.chart.type),
isEcharts: isEcharts(props.chart.type),
isEchartsWithTable: isEchartsWithTable(props.chart.type),
2021-07-05 22:58:12 +08:00
isEchartsWithStatistics: isEchartsWithStatistics(props.chart.type),
isSingleValue: isSingleValue(props.chart.type),
isTable: isTable(props.chart.type),
2021-06-24 17:59:51 +08:00
isMap: isMap(props.chart.type),
2021-06-25 19:11:00 +08:00
isTitle: isTitle(props.chart.type),
2021-06-24 17:59:51 +08:00
isMapLine: isMapLine(props.chart.type),
2021-07-10 12:11:59 +08:00
isMapBlock: isMapBlock(props.chart.type),
2021-07-01 21:39:10 +08:00
isTabs: isTabs(props.chart.type),
2021-07-05 22:58:12 +08:00
layout: getLayout(props.chart.type),
myChart: shallowRef({})
}
}
}
</script>