NEZ-2037 feat:新增chart类型bubble

This commit is contained in:
zyh
2022-07-21 10:33:20 +08:00
parent 199d9be7b5
commit c3faf9ae21
10 changed files with 324 additions and 4 deletions

View File

@@ -420,6 +420,7 @@ td .nz-icon-gear:before {
.chart-time-series,
.chart-treemap,
.chart-pie,
.chart-bubble,
.chart-canvas-tooltip,
.line-chart-block-Zindex,
.alert-label,

View File

@@ -26,6 +26,15 @@
:is-fullscreen="isFullscreen"
@chartIsNoData="chartIsNoData"
></chart-pie>
<chart-bubble
:ref="'chart' + chartInfo.id"
v-if="isChartBubble(chartInfo.type)"
:chart-data="chartData"
:chart-info="chartInfo"
:chart-option="chartOption"
:is-fullscreen="isFullscreen"
@chartIsNoData="chartIsNoData"
></chart-bubble>
<chart-bar
:ref="'chart' + chartInfo.id"
v-if="isChartBar(chartInfo.type)"
@@ -200,6 +209,7 @@ import chartGroup from './chart/chartGroup'
import chartLog from './chart/chartLog'
import chartNoData from './chart/chartNoData'
import chartPie from './chart/chartPie'
import chartBubble from './chart/chartBubble'
import chartStat from './chart/chartStat'
import chartTable from './chart/chartTable'
import chartText from './chart/chartText'
@@ -210,7 +220,7 @@ import chartValue from './chart/chartValue'
import chartHexagonD3 from './chart/chartHexagonD3'
import chartMap from './chart/chartMap'
import chartTopology from './chart/chartTopology'
import { getOption, isTimeSeries, isHexagon, isUrl, isText, isChartPie, isChartBar, isTreemap, isLog, isStat, isDiagram, isGroup, isAutotopology, isMap, isAssetInfo, isEndpointInfo, isTable, isGauge, isClock, isTopology } from './chart/tools'
import { getOption, isTimeSeries, isHexagon, isUrl, isText, isChartPie, isChartBubble, isChartBar, isTreemap, isLog, isStat, isDiagram, isGroup, isAutotopology, isMap, isAssetInfo, isEndpointInfo, isTable, isGauge, isClock, isTopology } from './chart/tools'
import lodash from 'lodash'
export default {
@@ -228,6 +238,7 @@ export default {
chartLog,
chartNoData,
chartPie,
chartBubble,
chartStat,
chartTable,
chartText,
@@ -295,6 +306,7 @@ export default {
isTimeSeries,
isHexagon,
isChartPie,
isChartBubble,
isChartBar,
isUrl,
isText,

View File

@@ -0,0 +1,249 @@
<template>
<div
ref="pie-chart-box"
class="nz-chart__component nz-chart__component--time-series" @mouseenter="mouseEnterChart"
@mouseleave="mouseLeaveChart"
>
<div :id="`chart-canvas-${chartId}`" class="chart__canvas"></div>
</div>
</template>
<script>
import chartMixin from '@/components/chart/chartMixin'
import chartFormat from '@/components/chart/chartFormat'
import * as echarts from 'echarts'
import * as d3 from 'd3'
import { getChart, setChart } from '@/components/common/js/common'
import { getMetricTypeValue } from '@/components/common/js/tools'
import chartDataFormat from '@/components/chart/chartDataFormat'
import { initColor } from '@/components/chart/chart/tools'
import lodash from 'lodash'
export default {
name: 'chart-bubble',
components: {
},
mixins: [chartMixin, chartFormat],
props: {
chartInfo: Object,
chartData: Array,
chartOption: Object,
isFullscreen: Boolean
},
computed: {
},
data () {
return {
colorList: [],
chartDot: 2,
isInit: true, // 是否是初始化初始化时为true图表初始化结束后设为false
chartId: ''
}
},
methods: {
initChart (chartOption = this.chartOption) {
this.legends = []
const parentNode = {
id: 'parentNode',
name: 'parentNode',
depth: -1
}
chartOption.dataset.source = [parentNode, ...this.initBubbleData(this.chartInfo, [], this.chartData)]
if (this.isNoData) {
return
}
chartOption.series[0].renderItem = this.renderItem
chartOption.tooltip.formatter = this.formatterFunc
chartOption.tooltip.position = this.tooltipPosition
/* 使用setTimeout延迟渲染图表避免样式错乱 */
setTimeout(() => {
const myChart = this.isInit ? echarts.init(document.getElementById(`chart-canvas-${this.chartId}`)) : getChart(this.chartId)
if (!myChart) {
return
}
myChart.setOption(chartOption)
this.isInit && setChart(this.chartId, myChart) // 缓存不使用vue的data是为避免整个chart被监听导致卡顿
this.isInit = false
}, 200)
},
initBubbleData (chartInfo, seriesTemplate, originalDatas) {
let colorIndex = 0
const decimals = this.chartInfo.param.decimals || 2
const s = lodash.cloneDeep(seriesTemplate)
this.isNoData = true
originalDatas.forEach((originalData, expressionIndex) => {
originalData.forEach((data, dataIndex) => {
this.isNoData = false
if (s) {
const value = getMetricTypeValue(data.values, chartInfo.param.statistics)
const showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(value, null, -1, decimals)
const mapping = this.selectMapping(value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
// eslint-disable-next-line vue/no-mutating-props
mapping && this.chartOption.color && (this.chartOption.color[colorIndex] = mapping.color.bac)
const legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
s.push({
value: value,
realValue: value,
showValue: showValue,
name: legend.name,
alias: legend.alias,
labels: data.metric,
seriesIndex: expressionIndex,
dataIndex: dataIndex,
mapping: mapping,
id: colorIndex, // 气泡id
background: mapping ? mapping.color.bac : this.colorList[colorIndex] // 气泡颜色
})
colorIndex++
}
})
})
this.$emit('chartIsNoData', this.isNoData)
return s
},
formatterFunc: function (params, ticket, callback) {
const self = this
return `<div>
<div style="white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis; min-width: 150px; max-width: 600px; line-height: 18px; font-size: 14px;">
<div class="tooltip-title" style="max-width: 500px;white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis;margin-bottom: 5px">${params.data.alias}</div>
<div style="font-size:12px;display:flex;justify-content: space-between;">
<div>value</div>
<div>
<div style="display: ${params.data.mapping && params.data.mapping.icon ? 'inline-block' : 'none'}">
<i class="${params.data.mapping && params.data.mapping.icon}" style="color: ${params.data.mapping && params.data.mapping.color && params.data.mapping.color.icon}"></i>
</div>
<div style="display: ${params.data.mapping && params.data.mapping.display ? 'none' : 'inline-block'}">${params.data.showValue}</div>
<div style="display: ${params.data.mapping && params.data.mapping.display ? 'inline-block' : 'none'}">${self.handleDisplay(params.data.mapping.display, { ...params.data.labels, value: params.data.showValue })}</div>
</div>
</div>
</div>
</div>
`
},
renderItem (params, api) {
// 如果数据全为0 则设置默认值(否则图表不显示)
let seriesData = lodash.cloneDeep(this.chartOption.dataset.source)
if (seriesData.every(item => !item.value || item.value == 0)) {
seriesData = seriesData.map(item => {
if (item.id !== 'parentNode') {
return {
...item,
value: 100
}
} else {
return {
...item
}
}
})
}
const displayRoot = stratify()
function stratify () {
return d3
.stratify()
.parentId(function (d) {
// 判断是否是父节点
if (d.id !== 'parentNode') {
return 'parentNode'
}
})(seriesData)
.sum(function (d) {
return d.value || 0
})
.sort(function (a, b) {
return b.value - a.value
})
}
function overallLayout (params, api) {
const context = params.context
d3
.pack()
.size([api.getWidth() - 2, api.getHeight() - 2])
.padding(6)(displayRoot)
context.nodes = {}
displayRoot.descendants().forEach(function (node) {
context.nodes[node.id] = node
})
}
const context = params.context
// Only do that layout once in each time `setOption` called.
// 每次调用“setOption”时只能进行一次布局。
if (!context.layout) {
context.layout = true
overallLayout(params, api)
}
const nodePath = api.value('id')
// const nodeName = nodePath
// .slice(nodePath.lastIndexOf('.') + 1)
// .split(/(?=[A-Z][^A-Z])/g)
// .join('')
const node = context.nodes[nodePath]
if (node.id === 'parentNode') {
node.r = 0
}
if (!node) {
// Reder nothing.
return
}
const z2 = api.value('depth') * 2
return {
type: 'circle',
shape: {
cx: node.x,
cy: node.y,
r: node.r
},
transition: ['shape'],
z2: z2,
textContent: {
type: 'text',
style: {
// transition: isLeaf ? 'fontSize' : null,
text: this.pieFormatterLabel(node),
fill: node.data.mapping ? node.data.mapping.color.text : '#000000',
width: node.r * 1.3,
overflow: 'truncate',
fontSize: node.r / 3,
lineHeight: node.r / 2.2
},
emphasis: {
style: {
// overflow: null,
// fontSize: Math.max(node.r / 3, 12)
}
}
},
textConfig: {
position: 'inside'
},
style: {
fill: node.data.background
},
emphasis: {
style: {
// shadowBlur: 20,
// shadowOffsetX: 3,
// shadowOffsetY: 5,
// shadowColor: 'rgba(0,0,0,0.3)'
}
}
}
}
},
mounted () {
// eslint-disable-next-line vue/no-mutating-props
this.chartOption.color || (this.chartOption.color = initColor(20))
this.colorList = this.chartOption.color
try {
this.isStack = this.chartInfo.param.stack
} catch (e) {}
this.chartInfo.loaded && this.initChart(this.chartOption)
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
const chartBubble = {
dataset: {
source: []
},
tooltip: {
show: true,
trigger: 'item',
confine: false,
extraCssText: 'z-index:1000;',
z: 9,
animation: false,
appendToBody: true,
className: 'chart-bubble'
},
hoverLayerThreshold: Infinity,
series: [{
type: 'custom',
renderItem: undefined,
progressive: 0,
coordinateSystem: 'none'
}]
}
export default chartBubble

View File

@@ -1,6 +1,7 @@
import { chartType } from '@/components/common/js/constants'
import chartBarOption from './options/chartBar'
import chartPieOption from './options/chartPie'
import chartBubbleOption from './options/chartBubble'
import lodash from 'lodash'
import {
chartTimeSeriesLineOption,
@@ -39,6 +40,10 @@ export function getOption (type) {
chartOption = lodash.cloneDeep(chartPieOption)
break
}
case chartType.bubble: {
chartOption = lodash.cloneDeep(chartBubbleOption)
break
}
case chartType.treemap: {
chartOption = lodash.cloneDeep(chartTreemapOption)
break
@@ -68,6 +73,9 @@ export function isHexagon (type) {
export function isChartPie (type) {
return type === chartType.pie
}
export function isChartBubble (type) {
return type === chartType.bubble
}
export function isChartBar (type) {
return type === chartType.bar
}

View File

@@ -189,7 +189,7 @@ export const endpoint = {
{ value: 1, label: i18n.t('endpoint.metricLabel') },
{ value: 2, label: i18n.t('endpoint.metricEnable') },
{ value: 3, label: i18n.t('endpoint.logEnable') }
],
]
}
export const alertMessage = {
severityData: [
@@ -407,6 +407,7 @@ export const chartType = {
stat: 'stat',
gauge: 'gauge',
pie: 'pie',
bubble: 'bubble',
treemap: 'treemap',
log: 'log',
text: 'text',

View File

@@ -910,6 +910,10 @@ export default {
id: 'pie',
name: this.$t('dashboard.panel.chartForm.typeVal.pie.label')
},
{
id: 'bubble',
name: this.$t('dashboard.panel.chartForm.typeVal.bubble.label')
},
{
id: 'log',
name: this.$t('dashboard.panel.chartForm.typeVal.log.label')
@@ -980,7 +984,8 @@ export default {
case 'stat':
case 'hexagon':
case 'gauge':
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'hexagon') {
case 'bubble':
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'hexagon' || this.oldType === 'bubble') {
break
}
this.chartConfig.param = {

View File

@@ -55,6 +55,7 @@ export default {
case 'treemap':
case 'gauge':
case 'pie':
case 'bubble':
return false
default: return false
}
@@ -72,6 +73,7 @@ export default {
case 'treemap':
case 'gauge':
case 'pie':
case 'bubble':
return true
default: return false
}
@@ -89,6 +91,7 @@ export default {
case 'stat':
case 'hexagon':
case 'gauge':
case 'bubble':
return false
default: return false
}
@@ -105,6 +108,7 @@ export default {
case 'gauge':
case 'treemap':
case 'pie':
case 'bubble':
case 'bar':
return false
default: return false
@@ -132,6 +136,7 @@ export default {
case 'gauge':
case 'treemap':
case 'pie':
case 'bubble':
return true
default: return false
}
@@ -142,6 +147,7 @@ export default {
case 'bar':
case 'treemap':
case 'pie':
case 'bubble':
case 'stat':
case 'hexagon':
case 'gauge':
@@ -159,6 +165,7 @@ export default {
case 'bar':
case 'treemap':
case 'pie':
case 'bubble':
case 'stat':
case 'hexagon':
case 'gauge':
@@ -190,6 +197,7 @@ export default {
case 'bar':
case 'treemap':
case 'pie':
case 'bubble':
case 'stat':
case 'hexagon':
case 'gauge':

View File

@@ -243,6 +243,10 @@ export default {
id: 'pie',
name: this.$t('dashboard.panel.chartForm.typeVal.pie.label')
},
{
id: 'bubble',
name: this.$t('dashboard.panel.chartForm.typeVal.bubble.label')
},
{
id: 'table',
name: this.$t('dashboard.panel.chartForm.typeVal.table.label')
@@ -286,6 +290,10 @@ export default {
id: 'pie',
name: this.$t('dashboard.panel.chartForm.typeVal.pie.label')
},
{
id: 'bubble',
name: this.$t('dashboard.panel.chartForm.typeVal.bubble.label')
},
{
id: 'log',
name: this.$t('dashboard.panel.chartForm.typeVal.log.label')

View File

@@ -834,6 +834,10 @@ export default {
id: 'pie',
name: this.$t('dashboard.panel.chartForm.typeVal.pie.label')
},
{
id: 'bubble',
name: this.$t('dashboard.panel.chartForm.typeVal.bubble.label')
},
{
id: 'gauge',
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
@@ -899,7 +903,8 @@ export default {
case 'stat':
case 'hexagon':
case 'gauge':
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'hexagon') {
case 'bubble':
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'hexagon' || this.oldType === 'bubble') {
break
}
this.chartConfig.param = {