重构treemap矩形树状图

This commit is contained in:
wenzhijie
2022-08-11 10:01:04 +08:00
parent 8c246b1295
commit cdc702f689
2 changed files with 391 additions and 4 deletions

View File

@@ -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,

View 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>