NEZ-2133 feat:新增 sankey 图表类型
This commit is contained in:
24
nezha-fronted/package-lock.json
generated
24
nezha-fronted/package-lock.json
generated
@@ -5943,6 +5943,30 @@
|
|||||||
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
|
||||||
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
|
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
|
||||||
},
|
},
|
||||||
|
"d3-sankey": {
|
||||||
|
"version": "0.12.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
|
||||||
|
"integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
|
||||||
|
"requires": {
|
||||||
|
"d3-array": "1 - 2",
|
||||||
|
"d3-shape": "^1.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||||
|
},
|
||||||
|
"d3-shape": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
|
||||||
|
"requires": {
|
||||||
|
"d3-path": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"d3-scale": {
|
"d3-scale": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"cytoscape": "^3.15.2",
|
"cytoscape": "^3.15.2",
|
||||||
"d3": "^6.7.0",
|
"d3": "^6.7.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
|
"d3-sankey": "^0.12.3",
|
||||||
"d3-zoom": "^3.0.0",
|
"d3-zoom": "^3.0.0",
|
||||||
"echarts": "^5.2.2",
|
"echarts": "^5.2.2",
|
||||||
"element-ui": "^2.15.3",
|
"element-ui": "^2.15.3",
|
||||||
|
|||||||
@@ -427,6 +427,7 @@ td .nz-icon-gear:before {
|
|||||||
}
|
}
|
||||||
.chart-bar,
|
.chart-bar,
|
||||||
.chart-gauge,
|
.chart-gauge,
|
||||||
|
.chart-sankey,
|
||||||
.chart-time-series,
|
.chart-time-series,
|
||||||
.chart-treemap,
|
.chart-treemap,
|
||||||
.chart-pie,
|
.chart-pie,
|
||||||
|
|||||||
@@ -134,7 +134,9 @@ export default {
|
|||||||
case 'table' :
|
case 'table' :
|
||||||
case 'stat' :
|
case 'stat' :
|
||||||
case 'gauge' :
|
case 'gauge' :
|
||||||
|
case 'sankey' :
|
||||||
case 'pie' :
|
case 'pie' :
|
||||||
|
case 'bubble' :
|
||||||
case 'treemap' :
|
case 'treemap' :
|
||||||
case 'log' :
|
case 'log' :
|
||||||
case 'hexagon' :
|
case 'hexagon' :
|
||||||
|
|||||||
@@ -108,6 +108,15 @@
|
|||||||
:is-fullscreen="isFullscreen"
|
:is-fullscreen="isFullscreen"
|
||||||
@chartIsNoData="chartIsNoData"
|
@chartIsNoData="chartIsNoData"
|
||||||
></chart-gauge>
|
></chart-gauge>
|
||||||
|
<chart-sankey
|
||||||
|
:ref="'chart' + chartInfo.id"
|
||||||
|
v-if="isSankey(chartInfo.type)"
|
||||||
|
:chart-data="chartData"
|
||||||
|
:chart-info="chartInfo"
|
||||||
|
:chart-option="chartOption"
|
||||||
|
:is-fullscreen="isFullscreen"
|
||||||
|
@chartIsNoData="chartIsNoData"
|
||||||
|
></chart-sankey>
|
||||||
<chart-diagram
|
<chart-diagram
|
||||||
:ref="'chart' + chartInfo.id"
|
:ref="'chart' + chartInfo.id"
|
||||||
v-if="isDiagram(chartInfo.type)"
|
v-if="isDiagram(chartInfo.type)"
|
||||||
@@ -205,6 +214,7 @@ import chartClock from './chart/chartClock'
|
|||||||
import chartDiagram from './chart/chartDiagram'
|
import chartDiagram from './chart/chartDiagram'
|
||||||
import chartEndpointInfo from './chart/chartEndpointInfo'
|
import chartEndpointInfo from './chart/chartEndpointInfo'
|
||||||
import chartGauge from './chart/chartGauge'
|
import chartGauge from './chart/chartGauge'
|
||||||
|
import chartSankey from './chart/chartSankey'
|
||||||
import chartGroup from './chart/chartGroup'
|
import chartGroup from './chart/chartGroup'
|
||||||
import chartLog from './chart/chartLog'
|
import chartLog from './chart/chartLog'
|
||||||
import chartNoData from './chart/chartNoData'
|
import chartNoData from './chart/chartNoData'
|
||||||
@@ -220,7 +230,7 @@ import chartValue from './chart/chartValue'
|
|||||||
import chartHexagonD3 from './chart/chartHexagonD3'
|
import chartHexagonD3 from './chart/chartHexagonD3'
|
||||||
import chartMap from './chart/chartMap'
|
import chartMap from './chart/chartMap'
|
||||||
import chartTopology from './chart/chartTopology'
|
import chartTopology from './chart/chartTopology'
|
||||||
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 { getOption, isTimeSeries, isHexagon, isUrl, isText, isChartPie, isChartBubble, isChartBar, isTreemap, isLog, isStat, isDiagram, isGroup, isAutotopology, isMap, isAssetInfo, isEndpointInfo, isTable, isGauge, isSankey, isClock, isTopology } from './chart/tools'
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -234,6 +244,7 @@ export default {
|
|||||||
chartDiagram,
|
chartDiagram,
|
||||||
chartEndpointInfo,
|
chartEndpointInfo,
|
||||||
chartGauge,
|
chartGauge,
|
||||||
|
chartSankey,
|
||||||
chartGroup,
|
chartGroup,
|
||||||
chartLog,
|
chartLog,
|
||||||
chartNoData,
|
chartNoData,
|
||||||
@@ -321,6 +332,7 @@ export default {
|
|||||||
isMap,
|
isMap,
|
||||||
isTable,
|
isTable,
|
||||||
isGauge,
|
isGauge,
|
||||||
|
isSankey,
|
||||||
isClock,
|
isClock,
|
||||||
isTopology,
|
isTopology,
|
||||||
chartIsNoData (flag) {
|
chartIsNoData (flag) {
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
colorList: [],
|
colorList: [],
|
||||||
chartDot: 2,
|
|
||||||
isInit: true, // 是否是初始化,初始化时为true,图表初始化结束后设为false
|
isInit: true, // 是否是初始化,初始化时为true,图表初始化结束后设为false
|
||||||
chartId: '',
|
chartId: '',
|
||||||
bubbleData: [],
|
bubbleData: [],
|
||||||
@@ -276,11 +275,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.colorList = initColor(20)
|
this.colorList = initColor(20)
|
||||||
this.chartInfo.loaded && this.initChart(this.chartOption)
|
this.chartInfo.loaded && this.initChart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
387
nezha-fronted/src/components/chart/chart/chartSankey.vue
Normal file
387
nezha-fronted/src/components/chart/chart/chartSankey.vue
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="pie-chart-box"
|
||||||
|
class="nz-chart__component nz-chart__component--time-series" @mouseenter="mouseEnterChart"
|
||||||
|
@mouseleave="mouseLeaveChart"
|
||||||
|
index="300"
|
||||||
|
>
|
||||||
|
<div :id="`chart-canvas-${chartId}`" class="chart__canvas">
|
||||||
|
<svg :id="`sankey-svg-${chartId}`" width="100%" height="100%"></svg>
|
||||||
|
</div>
|
||||||
|
<div :class="`chart-canvas-tooltip-${chartId}`" :id="`chart-canvas-tooltip-${chartId}`" class="chart-canvas-tooltip" :style="{left:tooltip.x+'px',top:tooltip.y+'px'}" v-if="tooltip.show">
|
||||||
|
<div class="chart-canvas-tooltip-title tooltip-title">
|
||||||
|
{{tooltip.title}}
|
||||||
|
</div>
|
||||||
|
<div class="chart-canvas-tooltip-content">
|
||||||
|
<div>value</div>
|
||||||
|
<div>
|
||||||
|
<div v-if="tooltip.mapping && tooltip.mapping.icon" style="display: inline-block">
|
||||||
|
<i :class="tooltip.mapping.icon" :style="{color: tooltip.mapping.color.icon}"></i>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block">{{tooltip.value}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import chartMixin from '@/components/chart/chartMixin'
|
||||||
|
import chartFormat from '@/components/chart/chartFormat'
|
||||||
|
import * as d3 from 'd3'
|
||||||
|
import * as d3Sankey from 'd3-sankey'
|
||||||
|
import { getMetricTypeValue } from '@/components/common/js/tools'
|
||||||
|
import chartDataFormat from '@/components/chart/chartDataFormat'
|
||||||
|
import { initColor } from '@/components/chart/chart/tools'
|
||||||
|
import { randomcolor } from '@/components/common/js/radomcolor/randomcolor'
|
||||||
|
import lodash from 'lodash'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'chart-sankey',
|
||||||
|
mixins: [chartMixin, chartFormat],
|
||||||
|
props: {
|
||||||
|
chartInfo: Object,
|
||||||
|
chartData: Array,
|
||||||
|
chartOption: Object,
|
||||||
|
isFullscreen: Boolean
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
colorList: [],
|
||||||
|
isInit: true, // 是否是初始化,初始化时为true,图表初始化结束后设为false
|
||||||
|
chartId: '',
|
||||||
|
linksData: [],
|
||||||
|
nodesData: [],
|
||||||
|
tooltip: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
title: 0,
|
||||||
|
value: 0,
|
||||||
|
mapping: {},
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initChart () {
|
||||||
|
this.linksData = this.initsankeyData(this.chartInfo, this.chartData) // 生成links
|
||||||
|
this.isNoData = !this.linksData.length
|
||||||
|
this.$emit('chartIsNoData', this.isNoData)
|
||||||
|
if (this.isNoData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 根据link获取node
|
||||||
|
this.linksData.forEach(item => {
|
||||||
|
this.nodesData.push({ node: item.source })
|
||||||
|
this.nodesData.push({ node: item.target })
|
||||||
|
})
|
||||||
|
// 去重相同的node
|
||||||
|
for (let i = 0; i < this.nodesData.length; i++) {
|
||||||
|
for (let j = i + 1; j < this.nodesData.length; j++) {
|
||||||
|
if (this.nodesData[i].node === this.nodesData[j].node) {
|
||||||
|
this.nodesData.splice(j, 1)
|
||||||
|
j = j - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 使用setTimeout延迟渲染图表,避免样式错乱 */
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawSankeyChart()
|
||||||
|
this.isInit = false
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
initsankeyData (chartInfo, originalDatas) {
|
||||||
|
this.linksData = []
|
||||||
|
this.nodesData = []
|
||||||
|
const sankeyData = []
|
||||||
|
const decimals = this.chartInfo.param.decimals || 2
|
||||||
|
originalDatas.forEach((originalData) => {
|
||||||
|
originalData.forEach((data, dataIndex) => {
|
||||||
|
this.isNoData = false
|
||||||
|
const value = getMetricTypeValue(data.values, chartInfo.param.statistics)
|
||||||
|
const obj = {
|
||||||
|
value: value,
|
||||||
|
realValue: value,
|
||||||
|
labels: data.metric,
|
||||||
|
dataIndex: dataIndex
|
||||||
|
}
|
||||||
|
if (data.metric[chartInfo.param.sourceLabel] && data.metric[chartInfo.param.targetLabel]) {
|
||||||
|
obj.source = data.metric[chartInfo.param.sourceLabel]
|
||||||
|
obj.target = data.metric[chartInfo.param.targetLabel]
|
||||||
|
sankeyData.push(obj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 汇总 source,target 相同的数据
|
||||||
|
const links = []
|
||||||
|
const tempObj = {}
|
||||||
|
sankeyData.forEach((item) => {
|
||||||
|
const key = item.source + '-' + item.target
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(tempObj, key)) {
|
||||||
|
tempObj[key] = item
|
||||||
|
tempObj[key].showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(tempObj[key].value, null, -1, decimals)
|
||||||
|
} else {
|
||||||
|
const num1 = parseFloat(tempObj[key].value)
|
||||||
|
const num2 = parseFloat(item.value)
|
||||||
|
tempObj[key].value = num1 + num2
|
||||||
|
tempObj[key].showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(tempObj[key].value, null, -1, decimals)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const key in tempObj) {
|
||||||
|
links.push(tempObj[key])
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
},
|
||||||
|
|
||||||
|
drawSankeyChart () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// 清空作图区域
|
||||||
|
d3.select(`#sankey-svg-${this.chartId}`).selectAll('g').remove()
|
||||||
|
|
||||||
|
// 获取svg宽高 初始化画布
|
||||||
|
const svgDom = document.getElementById(`sankey-svg-${this.chartId}`)
|
||||||
|
const width = svgDom && svgDom.getBoundingClientRect().width
|
||||||
|
const height = svgDom && svgDom.getBoundingClientRect().height
|
||||||
|
const margin1 = 100
|
||||||
|
const margin2 = 50
|
||||||
|
const svg = d3.select(`#sankey-svg-${this.chartId}`)
|
||||||
|
const chart = svg.append('g').attr('transform', `translate(${margin2}, ${margin2})`)
|
||||||
|
|
||||||
|
// 创建桑基图生成器
|
||||||
|
const sankey = d3Sankey
|
||||||
|
.sankey()
|
||||||
|
.nodeWidth(20)
|
||||||
|
.nodePadding(20)
|
||||||
|
.size([width - 2 * margin1, height - 2 * margin2])
|
||||||
|
.nodeId((d) => d.node)
|
||||||
|
const nodesData = lodash.cloneDeep(this.nodesData)
|
||||||
|
const linksData = lodash.cloneDeep(this.linksData)
|
||||||
|
const { nodes, links } = sankey({
|
||||||
|
nodes: nodesData,
|
||||||
|
links: linksData
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置节点颜色
|
||||||
|
nodes.forEach((item, index) => {
|
||||||
|
if (index >= 20) {
|
||||||
|
const colorRandom = randomcolor()
|
||||||
|
this.colorList.push(colorRandom)
|
||||||
|
}
|
||||||
|
const mapping = this.selectMapping(item.value, this.chartInfo.param.valueMapping, this.chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
|
||||||
|
item.mapping = mapping
|
||||||
|
item.background = mapping ? mapping.color.bac : this.colorList[index]
|
||||||
|
const decimals = this.chartInfo.param.decimals || 2
|
||||||
|
item.showValue = chartDataFormat.getUnit(this.chartInfo.unit ? this.chartInfo.unit : 2).compute(item.value, null, -1, decimals)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建一个连线绘制组,绑定连线数据(links)
|
||||||
|
chart
|
||||||
|
.append('g')
|
||||||
|
.attr('fill', 'none')
|
||||||
|
.selectAll()
|
||||||
|
.data(links)
|
||||||
|
.join('path')
|
||||||
|
.attr('linkNodes', (d) => { // 设置与当前连线相连的节点(必须以字母开头)
|
||||||
|
return 'i-' + d.source.index + ' ' + 'i-' + d.target.index
|
||||||
|
})
|
||||||
|
.attr('d', d3Sankey.sankeyLinkHorizontal())
|
||||||
|
.attr('stroke', (d, i) => {
|
||||||
|
return d.source.background
|
||||||
|
})
|
||||||
|
.attr('stroke-width', (d) => d.width)
|
||||||
|
.style('stroke-opacity', '0.5')
|
||||||
|
.attr('cursor', 'pointer')
|
||||||
|
.style('transition', 'all 0.3s')
|
||||||
|
|
||||||
|
// 创建一个节点绘制组,绑定节点数据(nodes)。
|
||||||
|
chart
|
||||||
|
.append('g')
|
||||||
|
.selectAll()
|
||||||
|
.data(nodes)
|
||||||
|
.join('g')
|
||||||
|
.attr('class', 'node')
|
||||||
|
.attr('linkNodes', (d) => { // 设置与当前节点相连的节点(必须以字母开头)
|
||||||
|
let nodeStr = ''
|
||||||
|
d.targetLinks.forEach(link => {
|
||||||
|
nodeStr += 'i-' + link.source.index + ' '
|
||||||
|
})
|
||||||
|
nodeStr += 'i-' + d.index
|
||||||
|
d.sourceLinks.forEach(link => {
|
||||||
|
nodeStr += ' ' + 'i-' + link.target.index
|
||||||
|
})
|
||||||
|
|
||||||
|
return nodeStr
|
||||||
|
})
|
||||||
|
.attr('index', (d) => { // 设置标识(必须以字母开头)
|
||||||
|
return 'i-' + d.index
|
||||||
|
})
|
||||||
|
.append('rect')
|
||||||
|
.attr('fill', (d, i) => {
|
||||||
|
return d.background
|
||||||
|
})
|
||||||
|
.attr('x', (d) => d.x0)
|
||||||
|
.attr('y', (d) => d.y0)
|
||||||
|
.attr('height', (d) => d.y1 - d.y0)
|
||||||
|
.attr('width', (d) => d.x1 - d.x0)
|
||||||
|
.attr('cursor', 'pointer')
|
||||||
|
.style('transition', 'all 0.3s')
|
||||||
|
|
||||||
|
// 节点添加文字
|
||||||
|
chart
|
||||||
|
.selectAll('.node')
|
||||||
|
.append('foreignObject')
|
||||||
|
// .attr('width', 20)
|
||||||
|
.attr('height', function (d) { return d.y1 - d.y0 })
|
||||||
|
.attr('x', function (d) { return d.x0 + 30 })
|
||||||
|
.attr('y', function (d) { return d.y0 })
|
||||||
|
.style('overflow', 'visible')
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.style('transition', 'all 0.3s')
|
||||||
|
.html((d) => {
|
||||||
|
return this.sankeyFormatterLabel(d)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 划过连线
|
||||||
|
chart.selectAll('path')
|
||||||
|
.on('mouseover', (e, d) => {
|
||||||
|
chart.selectAll('.node, path').style('fill-opacity', '0.1').style('stroke-opacity', '0.1')
|
||||||
|
chart.selectAll('.node').selectAll('foreignObject').style('opacity', '0.1')
|
||||||
|
const hoverNodes = d3.select(e.target).style('stroke-opacity', '0.8').attr('linkNodes').split(' ')
|
||||||
|
hoverNodes.forEach((index) => {
|
||||||
|
chart.selectAll('[index=' + index + ']').style('fill-opacity', '1').selectAll('foreignObject').style('opacity', '1')
|
||||||
|
})
|
||||||
|
// 显示悬浮框
|
||||||
|
this.tooltip.title = d.source.node + ' ——> ' + d.target.node
|
||||||
|
this.tooltip.value = d.showValue
|
||||||
|
this.tooltip.mapping = ''
|
||||||
|
this.tooltip.show = true
|
||||||
|
this.setPosition(e)
|
||||||
|
})
|
||||||
|
.on('mousemove', (e) => {
|
||||||
|
if (this.tooltip.show) {
|
||||||
|
this.setPosition(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
chart.selectAll('.node, path').style('fill-opacity', '1').style('stroke-opacity', '0.5')
|
||||||
|
chart.selectAll('.node').selectAll('foreignObject').style('opacity', '1')
|
||||||
|
// 隐藏悬浮框
|
||||||
|
this.tooltip.show = false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 划过节点
|
||||||
|
chart.selectAll('.node')
|
||||||
|
.on('mouseover', (e, d) => {
|
||||||
|
chart.selectAll('.node, path').style('fill-opacity', '0.1').style('stroke-opacity', '0.1')
|
||||||
|
chart.selectAll('.node').selectAll('foreignObject').style('opacity', '0.1')
|
||||||
|
chart.selectAll('[linkNodes~=' + 'i-' + d.index + ']')
|
||||||
|
.style('fill-opacity', '1')
|
||||||
|
.style('stroke-opacity', '0.8')
|
||||||
|
.selectAll('foreignObject')
|
||||||
|
.style('opacity', '1')
|
||||||
|
// 显示悬浮框
|
||||||
|
this.tooltip.title = d.node
|
||||||
|
this.tooltip.value = d.showValue
|
||||||
|
this.tooltip.mapping = d.mapping
|
||||||
|
this.tooltip.show = true
|
||||||
|
this.setPosition(e)
|
||||||
|
})
|
||||||
|
.on('mousemove', (e) => {
|
||||||
|
if (this.tooltip.show) {
|
||||||
|
this.setPosition(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
chart.selectAll('.node, path').style('fill-opacity', '1').style('stroke-opacity', '0.5')
|
||||||
|
chart.selectAll('.node').selectAll('foreignObject').style('opacity', '1')
|
||||||
|
// 隐藏悬浮框
|
||||||
|
this.tooltip.show = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
setPosition (e) {
|
||||||
|
const windowWidth = window.innerWidth// 窗口宽度
|
||||||
|
const windowHeight = window.innerHeight// 窗口高度
|
||||||
|
const box = document.getElementById(`chart-canvas-tooltip-${this.chartId}`)
|
||||||
|
if (box) {
|
||||||
|
const boxWidth = box.offsetWidth
|
||||||
|
const boxHeight = box.offsetHeight
|
||||||
|
if (e.pageX < (windowWidth / 2)) { // 说明鼠标在左边放不下提示框
|
||||||
|
this.tooltip.x = e.pageX + 15
|
||||||
|
} else {
|
||||||
|
this.tooltip.x = e.pageX - boxWidth - 15
|
||||||
|
}
|
||||||
|
if (e.pageY + 50 + boxHeight < windowHeight) { // 说明鼠标上面放不下提示框
|
||||||
|
this.tooltip.y = e.pageY + 15
|
||||||
|
} else {
|
||||||
|
this.tooltip.y = e.pageY - boxHeight - 10
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.tooltip.y = e.pageY + 15
|
||||||
|
this.tooltip.x = e.pageX + 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理label
|
||||||
|
sankeyFormatterLabel (data) {
|
||||||
|
let str = ''
|
||||||
|
let valueStr = ''
|
||||||
|
if (this.chartInfo.param.text === 'all') {
|
||||||
|
str += data.node
|
||||||
|
valueStr = data.mapping && data.mapping.display ? this.handleDisplay(data.mapping.display, { value: data.showValue }) : data.showValue
|
||||||
|
}
|
||||||
|
if (this.chartInfo.param.text === 'value' || !this.chartInfo.param.text) {
|
||||||
|
valueStr = data.mapping && data.mapping.display ? this.handleDisplay(data.mapping.display, { value: data.showValue }) : data.showValue
|
||||||
|
}
|
||||||
|
if (this.chartInfo.param.text === 'legend') {
|
||||||
|
str += data.node
|
||||||
|
}
|
||||||
|
if (this.chartInfo.param.text === 'none') {
|
||||||
|
str += ''
|
||||||
|
}
|
||||||
|
if (str && valueStr) {
|
||||||
|
return `
|
||||||
|
<div style="width:auto;height: 100%;display: flex;justify-content: center;flex-direction: column;color:#000000">
|
||||||
|
<p style="cursor:pointer;white-space: nowrap;color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
|
<span>${str}</span>
|
||||||
|
</p>
|
||||||
|
<p style="cursor:pointer;white-space: nowrap;color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
|
<i class="${data.mapping && data.mapping.icon}" style="color: ${data.mapping && data.mapping.color && data.mapping.color.icon};font-size:1em;"></i>
|
||||||
|
<span>${valueStr}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
} else if (str) {
|
||||||
|
return `
|
||||||
|
<div style="width:auto;height: 100%;display: flex;justify-content: center;flex-direction: column;color:#000000">
|
||||||
|
<p style="cursor:pointer;white-space: nowrap;color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
|
<i class="${data.mapping && data.mapping.icon}" style="color: ${data.mapping && data.mapping.color && data.mapping.color.icon};font-size:1em;"></i>
|
||||||
|
<span>${str}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
} else if (valueStr) {
|
||||||
|
return `
|
||||||
|
<div style="width:auto;height: 100%;display: flex;justify-content: center;flex-direction: column;color:#000000">
|
||||||
|
<p style="cursor:pointer;white-space: nowrap;color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
|
<i class="${data.mapping && data.mapping.icon}" style="color: ${data.mapping && data.mapping.color && data.mapping.color.icon};font-size:1em;"></i>
|
||||||
|
<span>${valueStr}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resize () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawSankeyChart()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.colorList = initColor(20)
|
||||||
|
this.chartInfo.loaded && this.initChart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -114,6 +114,9 @@ export function isTable (type) {
|
|||||||
export function isGauge (type) {
|
export function isGauge (type) {
|
||||||
return type === chartType.gauge
|
return type === chartType.gauge
|
||||||
}
|
}
|
||||||
|
export function isSankey (type) {
|
||||||
|
return type === chartType.sankey
|
||||||
|
}
|
||||||
export function isClock (type) {
|
export function isClock (type) {
|
||||||
return type === chartType.clock
|
return type === chartType.clock
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,6 +408,7 @@ export const chartType = {
|
|||||||
table: 'table',
|
table: 'table',
|
||||||
stat: 'stat',
|
stat: 'stat',
|
||||||
gauge: 'gauge',
|
gauge: 'gauge',
|
||||||
|
sankey: 'sankey',
|
||||||
pie: 'pie',
|
pie: 'pie',
|
||||||
bubble: 'bubble',
|
bubble: 'bubble',
|
||||||
treemap: 'treemap',
|
treemap: 'treemap',
|
||||||
|
|||||||
@@ -340,6 +340,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-items--half-width-group" v-if="isGauge(chartConfig.type)">
|
<div class="form-items--half-width-group" v-if="isGauge(chartConfig.type)">
|
||||||
<!--min-->
|
<!--min-->
|
||||||
<el-form-item :label="$t('dashboard.panel.chartForm.min')" class="form-item--half-width">
|
<el-form-item :label="$t('dashboard.panel.chartForm.min')" class="form-item--half-width">
|
||||||
@@ -360,6 +361,40 @@
|
|||||||
show-word-limit v-model="chartConfig.param.max"/>
|
show-word-limit v-model="chartConfig.param.max"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-items--half-width-group" v-if="isSankey(chartConfig.type)" key="sankey">
|
||||||
|
<!--Source label-->
|
||||||
|
<el-form-item class="form-item--half-width" :label="$t('dashboard.panel.chartForm.sourceLabel')" prop="param.sourceLabel"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: $t('validate.required'), trigger: 'blur'},
|
||||||
|
{ pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: $t('dashboard.panel.matchRegex'), trigger: 'blur'},
|
||||||
|
{ validator: labelValidator,trigger: 'blur'},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
size="small"
|
||||||
|
style="margin-top: 2px"
|
||||||
|
:placeholder="$t('overall.placeHolder')"
|
||||||
|
@change="change"
|
||||||
|
v-model="chartConfig.param.sourceLabel"/>
|
||||||
|
</el-form-item>
|
||||||
|
<!--Target label-->
|
||||||
|
<el-form-item class="form-item--half-width" :label="$t('dashboard.panel.chartForm.targetLabel')" prop="param.targetLabel"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: $t('validate.required'), trigger: 'blur'},
|
||||||
|
{ pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: $t('dashboard.panel.matchRegex'), trigger: 'blur'},
|
||||||
|
{ validator: labelValidator,trigger: 'blur'},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
size="small"
|
||||||
|
style="margin-top: 2px"
|
||||||
|
:placeholder="$t('overall.placeHolder')"
|
||||||
|
@change="change"
|
||||||
|
v-model="chartConfig.param.targetLabel"/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-items--half-width-group" v-if="isShowDecimals(chartConfig.type)">
|
<div class="form-items--half-width-group" v-if="isShowDecimals(chartConfig.type)">
|
||||||
<!--decimals-->
|
<!--decimals-->
|
||||||
<el-form-item :label="$t('overall.decimal')" class="form-item--half-width">
|
<el-form-item :label="$t('overall.decimal')" class="form-item--half-width">
|
||||||
@@ -898,7 +933,7 @@ import chartTypeShow from '@/components/common/rightBox/chart/chartTypeShow'
|
|||||||
import VueTagsInput from '@johmun/vue-tags-input'
|
import VueTagsInput from '@johmun/vue-tags-input'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { randomcolor, ColorReverse } from '@/components/common/js/radomcolor/randomcolor'
|
import { randomcolor, ColorReverse } from '@/components/common/js/radomcolor/randomcolor'
|
||||||
import { isGauge } from '@/components/chart/chart/tools'
|
import { isGauge, isSankey } from '@/components/chart/chart/tools'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'chartConfig',
|
name: 'chartConfig',
|
||||||
@@ -969,6 +1004,10 @@ export default {
|
|||||||
id: 'gauge',
|
id: 'gauge',
|
||||||
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
|
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'sankey',
|
||||||
|
name: this.$t('dashboard.panel.chartForm.typeVal.sankey.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'treemap',
|
id: 'treemap',
|
||||||
name: this.$t('dashboard.panel.chartForm.typeVal.treemap.label')
|
name: this.$t('dashboard.panel.chartForm.typeVal.treemap.label')
|
||||||
@@ -995,6 +1034,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isGauge,
|
isGauge,
|
||||||
|
isSankey,
|
||||||
beforeInit () {
|
beforeInit () {
|
||||||
this.promqlType = this.type
|
this.promqlType = this.type
|
||||||
this.chartTypeList = this[this.type + 'ChartTypeList']
|
this.chartTypeList = this[this.type + 'ChartTypeList']
|
||||||
@@ -1017,6 +1057,18 @@ export default {
|
|||||||
this.expressionChange()
|
this.expressionChange()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 变量名校验 防止重复
|
||||||
|
labelValidator (rule, value, callback) {
|
||||||
|
const sourceLabel = this.chartConfig.param.sourceLabel
|
||||||
|
const targetLabel = this.chartConfig.param.targetLabel
|
||||||
|
setTimeout(() => {
|
||||||
|
if (sourceLabel === targetLabel) {
|
||||||
|
callback(new Error(this.$t('error.labelEqual')))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
chartTypeChange (type) {
|
chartTypeChange (type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'line':
|
case 'line':
|
||||||
@@ -1053,8 +1105,9 @@ export default {
|
|||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'hexagon' || this.oldType === 'bubble') {
|
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'sankey' || this.oldType === 'hexagon' || this.oldType === 'bubble') {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.chartConfig.param = {
|
this.chartConfig.param = {
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export default {
|
|||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
|
case 'sankey':
|
||||||
return true
|
return true
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
@@ -91,6 +92,7 @@ export default {
|
|||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
return false
|
return false
|
||||||
default: return false
|
default: return false
|
||||||
@@ -106,6 +108,7 @@ export default {
|
|||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
@@ -134,6 +137,7 @@ export default {
|
|||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'bar':
|
case 'bar':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
@@ -151,6 +155,7 @@ export default {
|
|||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
return false
|
return false
|
||||||
case 'line':
|
case 'line':
|
||||||
case 'area':
|
case 'area':
|
||||||
@@ -169,6 +174,7 @@ export default {
|
|||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
return true
|
return true
|
||||||
case 'line':
|
case 'line':
|
||||||
case 'area':
|
case 'area':
|
||||||
@@ -201,6 +207,7 @@ export default {
|
|||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
case 'sankey':
|
||||||
return true
|
return true
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,6 +247,10 @@ export default {
|
|||||||
id: 'gauge',
|
id: 'gauge',
|
||||||
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
|
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'sankey',
|
||||||
|
name: this.$t('dashboard.panel.chartForm.typeVal.sankey.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'treemap',
|
id: 'treemap',
|
||||||
name: this.$t('dashboard.panel.chartForm.typeVal.treemap.label')
|
name: this.$t('dashboard.panel.chartForm.typeVal.treemap.label')
|
||||||
@@ -294,6 +298,10 @@ export default {
|
|||||||
id: 'gauge',
|
id: 'gauge',
|
||||||
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
|
name: this.$t('dashboard.panel.chartForm.typeVal.gauge.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'sankey',
|
||||||
|
name: this.$t('dashboard.panel.chartForm.typeVal.sankey.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'treemap',
|
id: 'treemap',
|
||||||
name: this.$t('dashboard.panel.chartForm.typeVal.treemap.label')
|
name: this.$t('dashboard.panel.chartForm.typeVal.treemap.label')
|
||||||
|
|||||||
Reference in New Issue
Block a user