重构treemap矩形树状图
This commit is contained in:
@@ -71,7 +71,7 @@
|
||||
:is-fullscreen="isFullscreen"
|
||||
@chartIsNoData="chartIsNoData"
|
||||
></chart-text>
|
||||
<chart-treemap
|
||||
<!-- <chart-treemap
|
||||
:ref="'chart' + chartInfo.id"
|
||||
v-if="isTreemap(chartInfo.type)"
|
||||
:chart-data="chartData"
|
||||
@@ -79,7 +79,16 @@
|
||||
:chart-option="chartOption"
|
||||
:is-fullscreen="isFullscreen"
|
||||
@chartIsNoData="chartIsNoData"
|
||||
></chart-treemap>
|
||||
></chart-treemap> -->
|
||||
<chart-treemapD3
|
||||
:ref="'chart' + chartInfo.id"
|
||||
v-if="isTreemap(chartInfo.type)"
|
||||
:chart-data="chartData"
|
||||
:chart-info="chartInfo"
|
||||
:chart-option="chartOption"
|
||||
:is-fullscreen="isFullscreen"
|
||||
@chartIsNoData="chartIsNoData"
|
||||
></chart-treemapD3>
|
||||
<chart-log
|
||||
:ref="'chart' + chartInfo.id"
|
||||
v-if="isLog(chartInfo.type)"
|
||||
@@ -214,7 +223,8 @@ import chartStat from './chart/chartStat'
|
||||
import chartTable from './chart/chartTable'
|
||||
import chartText from './chart/chartText'
|
||||
import chartTimeSeries from './chart/chartTimeSeries'
|
||||
import chartTreemap from './chart/chartTreemap'
|
||||
// import chartTreemap from './chart/chartTreemap'
|
||||
import chartTreemapD3 from './chart/chartTreemapD3'
|
||||
import chartUrl from './chart/chartUrl'
|
||||
import chartValue from './chart/chartValue'
|
||||
import chartHexagonD3 from './chart/chartHexagonD3'
|
||||
@@ -243,7 +253,8 @@ export default {
|
||||
chartTable,
|
||||
chartText,
|
||||
chartTimeSeries,
|
||||
chartTreemap,
|
||||
// chartTreemap,
|
||||
chartTreemapD3,
|
||||
chartUrl,
|
||||
chartValue,
|
||||
chartHexagonD3,
|
||||
|
||||
376
nezha-fronted/src/components/chart/chart/chartTreemapD3.vue
Normal file
376
nezha-fronted/src/components/chart/chart/chartTreemapD3.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<div
|
||||
ref="pie-chart-box"
|
||||
class="nz-chart__component"
|
||||
>
|
||||
<div :id="`chart-canvas-${chartId}`" class="chart__canvas" >
|
||||
<svg :id="`treemap-svg-${chartId}`" width="100%" height="100%">
|
||||
</svg>
|
||||
<chart-legend
|
||||
v-if="hasLegend"
|
||||
:chart-data="chartData"
|
||||
:chart-info="chartInfo"
|
||||
:legends="legends"
|
||||
:series="series"
|
||||
:is-fullscreen="isFullscreen"
|
||||
></chart-legend>
|
||||
</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 * as d3 from 'd3'
|
||||
import legend from '@/components/chart/chart/legend'
|
||||
import hexbin from '@/components/chart/chart/options/chartHexagonD3'
|
||||
import { chartLegendPlacement } from '@/components/common/js/constants'
|
||||
import chartMixin from '@/components/chart/chartMixin'
|
||||
import chartFormat from '@/components/chart/chartFormat'
|
||||
import { initColor } from '@/components/chart/chart/tools'
|
||||
import '@svgdotjs/svg.panzoom.js'
|
||||
import { getMetricTypeValue } from '@/components/common/js/tools'
|
||||
import chartDataFormat from '@/components/chart/chartDataFormat'
|
||||
import lodash from 'lodash'
|
||||
export default {
|
||||
name: 'chartTreemapD3',
|
||||
components: {
|
||||
chartLegend: legend
|
||||
},
|
||||
mixins: [chartMixin, chartFormat],
|
||||
props: {
|
||||
chartInfo: Object,
|
||||
chartData: Array,
|
||||
chartOption: Object,
|
||||
isFullscreen: Boolean
|
||||
},
|
||||
computed: {
|
||||
hasLegend () {
|
||||
try {
|
||||
return [chartLegendPlacement.bottom, chartLegendPlacement.left, chartLegendPlacement.right].indexOf(this.chartInfo.param.legend.placement) > -1
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
legendPlacement () {
|
||||
try {
|
||||
switch (this.chartInfo.param.legend.placement) {
|
||||
case 'left':
|
||||
case 'right':
|
||||
case 'bottom': {
|
||||
return `nz-chart__component--${this.chartInfo.param.legend.placement}`
|
||||
}
|
||||
default: return ''
|
||||
}
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
colorList: [],
|
||||
chartDot: 2,
|
||||
isInit: true, // 是否是初始化,初始化时为true,图表初始化结束后设为false
|
||||
chartId: '',
|
||||
treemapData: [],
|
||||
legends: [], // { name, alias, color, statistics: [{type: min, value: xxx}, ...] }
|
||||
tooltip: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
title: 0,
|
||||
value: 0,
|
||||
mapping: {},
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hexbin,
|
||||
initChart () {
|
||||
this.legends = []
|
||||
this.initTreemapData(this.chartInfo, this.chartData)
|
||||
if (this.isNoData) {
|
||||
return
|
||||
}
|
||||
/* 使用setTimeout延迟渲染图表,避免样式错乱 */
|
||||
setTimeout(() => {
|
||||
this.drawTreemapChart()
|
||||
this.isInit = false
|
||||
}, 200)
|
||||
},
|
||||
initTreemapData (chartInfo, originalDatas) {
|
||||
this.treemapData = []
|
||||
let colorIndex = 0
|
||||
const decimals = this.chartInfo.param.decimals || 2
|
||||
this.isNoData = true
|
||||
originalDatas.forEach((originalData, expressionIndex) => {
|
||||
originalData.forEach((data, dataIndex) => {
|
||||
this.isNoData = false
|
||||
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)
|
||||
const legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
|
||||
this.treemapData.push({
|
||||
value: value,
|
||||
realValue: value,
|
||||
showValue: showValue,
|
||||
name: legend.name,
|
||||
alias: legend.alias,
|
||||
labels: data.metric,
|
||||
seriesIndex: expressionIndex,
|
||||
dataIndex: dataIndex,
|
||||
mapping: mapping,
|
||||
background: mapping ? mapping.color.bac : this.colorList[colorIndex] // 气泡颜色
|
||||
})
|
||||
colorIndex++
|
||||
})
|
||||
})
|
||||
this.$emit('chartIsNoData', this.isNoData)
|
||||
},
|
||||
drawTreemapChart () {
|
||||
this.$nextTick(() => {
|
||||
d3.select(`#treemap-svg-${this.chartId}`).selectAll('g').remove()// 清空作图区域
|
||||
let width
|
||||
let height
|
||||
try {
|
||||
const chartWrap = document.getElementById(`chart-canvas-${this.chartId}`)
|
||||
width = chartWrap.clientWidth
|
||||
height = chartWrap.clientHeight
|
||||
} catch (error) {
|
||||
}
|
||||
// 定义布局方式
|
||||
const treemap = d3.treemap()
|
||||
.size([width, height])
|
||||
.padding(6)
|
||||
// 如果数据全为0 则设置默认值(否则图表不显示)
|
||||
let treemapData = lodash.cloneDeep(this.treemapData)
|
||||
if (treemapData.every(item => item.value == 0)) {
|
||||
treemapData = treemapData.map(item => {
|
||||
return {
|
||||
...item,
|
||||
value: 100 // 目的是显示气泡 与值大小无关
|
||||
}
|
||||
})
|
||||
}
|
||||
const data = d3.hierarchy({ children: treemapData })
|
||||
.sum(function (d) {
|
||||
return d.value || 0
|
||||
})
|
||||
.sort(function (a, b) {
|
||||
return b.value - a.value
|
||||
})
|
||||
const nodes = treemap(data).descendants()
|
||||
const treemaps = d3.select(`#treemap-svg-${this.chartId}`).selectAll('.treemap')
|
||||
.data(nodes)
|
||||
.enter()
|
||||
.filter(function (d) {
|
||||
return !d.children
|
||||
})
|
||||
.append('g')
|
||||
.attr('class', 'treemap')
|
||||
treemaps.append('rect')// 模块在画布当中的位置
|
||||
.style('fill', function (d) {
|
||||
return d.data.background
|
||||
})
|
||||
.attr('x', function (d) { return d.x0 })
|
||||
.attr('y', function (d) { return d.y0 })
|
||||
.attr('width', function (d) { return d.x1 - d.x0 })
|
||||
.attr('height', function (d) { return d.y1 - d.y0 })
|
||||
.attr('fill', function (d) { return '#594' })
|
||||
treemaps.append('foreignObject') // 文字
|
||||
.attr('width', function (d) { return d.x1 - d.x0 })
|
||||
.attr('height', function (d) { return d.y1 - d.y0 })
|
||||
.attr('x', function (d) { return d.x0 })
|
||||
.attr('y', function (d) { return d.y0 })
|
||||
.attr('dx', '0.5em')
|
||||
.attr('dy', '1.5em')
|
||||
.attr('fill', 'red')
|
||||
.attr('font-size', 30)
|
||||
.html((d) => {
|
||||
return this.treemapFormatterLabel(d)
|
||||
})
|
||||
treemaps.on('mouseenter', this.treemapEnter)
|
||||
treemaps.on('mousemove', this.treemapMove)
|
||||
treemaps.on('mouseleave', this.treemapLeave)
|
||||
})
|
||||
},
|
||||
// 处理label
|
||||
treemapFormatterLabel (node) {
|
||||
console.log(1100)
|
||||
let str = ''
|
||||
let valueStr = ''
|
||||
if (this.chartInfo.param.text === 'all') {
|
||||
str += node.data.alias
|
||||
valueStr = node.data.mapping && node.data.mapping.display ? this.handleDisplay(node.data.mapping.display, { ...node.data.labels, value: node.data.showValue }) : node.data.showValue
|
||||
}
|
||||
if (this.chartInfo.param.text === 'value' || !this.chartInfo.param.text) {
|
||||
valueStr = node.data.mapping && node.data.mapping.display ? this.handleDisplay(node.data.mapping.display, { ...node.data.labels, value: node.data.showValue }) : node.data.showValue
|
||||
}
|
||||
if (this.chartInfo.param.text === 'legend') {
|
||||
str += node.data.alias
|
||||
}
|
||||
if (this.chartInfo.param.text === 'none') {
|
||||
str += ''
|
||||
}
|
||||
if (str && valueStr) {
|
||||
console.log(1)
|
||||
return `
|
||||
<div style="width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer;">
|
||||
<p style="width: 80%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center">
|
||||
<span>${str}</span>
|
||||
</p>
|
||||
<p style="width: 80%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center">
|
||||
<i class="${node.data.mapping && node.data.mapping.icon}" style="color: ${node.data.mapping && node.data.mapping.color && node.data.mapping.color.icon};font-size:1em;"></i>
|
||||
<span>${valueStr}</span>
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
} else if (str) {
|
||||
console.log(2)
|
||||
return `
|
||||
<div style="width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer;">
|
||||
<p style="width: 80%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center">
|
||||
<i class="${node.data.mapping && node.data.mapping.icon}" style="color: ${node.data.mapping && node.data.mapping.color && node.data.mapping.color.icon};font-size:1em;"></i>
|
||||
<span>${str}</span>
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
} else if (valueStr) {
|
||||
console.log(3)
|
||||
return `
|
||||
<div style="width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer;">
|
||||
<p style="width: 80%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center">
|
||||
<i class="${node.data.mapping && node.data.mapping.icon}" style="color: ${node.data.mapping && node.data.mapping.color && node.data.mapping.color.icon};font-size:1em;"></i>
|
||||
<span>${valueStr}</span>
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
},
|
||||
resize () {
|
||||
setTimeout(() => {
|
||||
this.drawTreemapChart()
|
||||
}, 50)
|
||||
},
|
||||
initTreemap (layout) {
|
||||
this.isInit = false
|
||||
let rowIndex = 0
|
||||
let colIndex = -1
|
||||
const data = this.treemapData.map(item => {
|
||||
colIndex++
|
||||
if (colIndex > layout.col) {
|
||||
rowIndex++
|
||||
colIndex = 0
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
x: colIndex,
|
||||
y: rowIndex,
|
||||
metrics: item.label,
|
||||
label: item.legend
|
||||
}
|
||||
})
|
||||
const dom = document.getElementById(`chart-canvas-${this.chartId}`)
|
||||
const child = document.getElementById('svgHex' + this.chartId)
|
||||
if (dom && child) {
|
||||
dom.removeChild(child)
|
||||
}
|
||||
if (dom) {
|
||||
const hexaRadius = layout.radius
|
||||
dom.append(this.showtreemaps(data, hexaRadius, layout.col, layout.row))
|
||||
setTimeout(() => {
|
||||
const svg = document.getElementById('svgHex' + this.chartId)
|
||||
const bbox = svg.getBBox()
|
||||
svg.setAttribute('viewBox', (bbox.x - 10) + ' ' + (bbox.y - 10) + ' ' + (bbox.width + 20) + ' ' + (bbox.height + 20))
|
||||
svg.setAttribute('width', (bbox.width + 20) + 'px')
|
||||
svg.setAttribute('height', (bbox.height + 20) + 'px')
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
treemapEnter (e, node) { // 移入六边形
|
||||
this.tooltip.title = node.data.alias
|
||||
this.tooltip.value = node.data.showValue
|
||||
this.tooltip.mapping = node.data.mapping
|
||||
this.tooltip.show = true
|
||||
this.setPosition(e)
|
||||
},
|
||||
treemapMove (e) { // 六边形内移动
|
||||
if (this.tooltip.show) {
|
||||
this.setPosition(e)
|
||||
}
|
||||
},
|
||||
treemapLeave () { // 移出六边形
|
||||
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
|
||||
}
|
||||
},
|
||||
getCenter (hexaRadius, row, col) {
|
||||
let x = hexaRadius * col * Math.sqrt(3)
|
||||
// Offset each uneven row by half of a "hex-width" to the right
|
||||
if (row % 2 === 1) x += (hexaRadius * Math.sqrt(3)) / 2
|
||||
const y = hexaRadius * row * 1.5
|
||||
return [x + hexaRadius, y + hexaRadius]
|
||||
},
|
||||
invertColor (hex) {
|
||||
if (hex.indexOf('#') === 0) {
|
||||
hex = hex.slice(1)
|
||||
}
|
||||
// convert 3-digit hex to 6-digits.
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
|
||||
}
|
||||
if (hex.length !== 6) {
|
||||
// throw new Error('Invalid HEX color.');
|
||||
}
|
||||
const r = parseInt(hex.slice(0, 2), 16)
|
||||
const g = parseInt(hex.slice(2, 4), 16)
|
||||
const b = parseInt(hex.slice(4, 6), 16)
|
||||
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
|
||||
? '#000000'
|
||||
: '#FFFFFF'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
mounted () {
|
||||
this.colorList = initColor(20)
|
||||
this.chartInfo.loaded && this.initChart(this.chartOption)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
Reference in New Issue
Block a user