NEZ-2064 feat : 通过d3js 实现 pie chart

This commit is contained in:
likexuan
2022-07-28 18:42:09 +08:00
parent e46e79da05
commit 260fbf2551
2 changed files with 123 additions and 27 deletions

View File

@@ -1,6 +1,20 @@
<template>
<div :ref="`chart-canvas-${chartId}`" style="height: 100%; width: 100%">
<div :id="`chart-canvas-${chartId}`" class="chart__canvas chart-svg"></div>
<div
:class="legendPlacement"
ref="pie-chart-box"
class="nz-chart__component nz-chart__component--time-series">
<div :ref="`chart-canvas-${chartId}`" style="height: 100%; width: 100%">
<div style="height: 85%; width: 100%">
<div :id="`chart-canvas-${chartId}`" class="chart__canvas chart-svg"></div>
</div>
<chart-legend
v-if="hasLegend"
:chart-data="chartData"
:chart-info="chartInfo"
:legends="dataset"
:is-fullscreen="isFullscreen"
@pieData="pieData"
></chart-legend>
<div
:class="`chart-canvas-tooltip-${chartId}`"
:id="`chart-canvas-tooltip-${chartId}`"
@@ -27,6 +41,7 @@
</div>
</div>
</div>
</div>
</div>
</template>
@@ -38,6 +53,8 @@ import chartMixin from '@/components/chart/chartMixin'
import chartDataFormat from '@/components/chart/chartDataFormat'
import { getMetricTypeValue } from '@/components/common/js/tools'
import { initColor } from '@/components/chart/chart/tools'
import legend from '@/components/chart/chart/legend'
import { chartLegendPlacement } from '@/components/common/js/constants'
export default {
data () {
return {
@@ -65,6 +82,9 @@ export default {
chartOption: Object,
isFullscreen: Boolean
},
components: {
chartLegend: legend
},
mounted () {
this.colorList = initColor(20)
this.initChart()
@@ -82,7 +102,7 @@ export default {
this.chartOption.series[0],
this.chartData
)[0].data
this.drawArcPie()
this.drawArcPie(this.dataset)
}, 200)
})
},
@@ -100,11 +120,15 @@ export default {
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 type = 'pie'
const color = this.colorList[colorIndex]
// 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.data.push({
value: value,
type: type,
color: color,
realValue: value,
showValue: showValue,
name: legend.name,
@@ -128,7 +152,7 @@ export default {
},
// 绘制饼图
drawArcPie () {
drawArcPie (data) {
this.$nextTick(() => {
d3.select(`#chart-canvas-${this.chartId}`).selectAll('svg').remove()// 清空作图区域
// 重新赋值宽高
@@ -139,7 +163,7 @@ export default {
} catch (error) {
}
// 如果数据全为0 则设置默认值(否则图表不显示)
let dataset = lodash.cloneDeep(this.dataset)
let dataset = lodash.cloneDeep(data)
if (dataset.every(item => item.value == 0)) {
dataset = dataset.map(item => {
return {
@@ -159,7 +183,7 @@ export default {
const pie = d3.pie().value(function (d) {
return d.value
})
const piedata = pie(this.dataset)
const piedata = pie(data)
const arc = d3
.arc()
.innerRadius(this.innerRadius)
@@ -184,34 +208,63 @@ export default {
// 边界线
// .attr('stroke', 'white')
// 颜色
.attr('fill', (d, i) => this.colorList[i])
.attr('fill', (d, i) => {
if (d.data.color) {
return d.data.color
} else {
return this.colorList[i]
}
})
// 文字
arcs
.append('foreignObject')
.attr('y', d => { // y轴居中减文字大小的一半
const y = arc.centroid(d)[1]
return y - Math.sqrt(2) / 2 * (this.outerRadius / 2) + Math.floor(this.outerRadius / 18)
return y - Math.sqrt(2) / 2 * (this.outerRadius / 2) + Math.ceil(this.outerRadius / 16)
})
.attr('transform', (d, i) => {
return 'translate(' + (arc.centroid(d)[0] - Math.abs(arc.centroid(d)[0]) - Math.floor(this.outerRadius / 18)) + ',' + (0) + ')' // x轴为最左侧 //x轴居中减文字大小的一半
return 'translate(' + (arc.centroid(d)[0] - Math.abs(arc.centroid(d)[0]) - Math.ceil(this.outerRadius / 16)) + ',' + (0) + ')' // x轴为最左侧 //x轴居中减文字大小的一半
})
.style('font-size', () => {
return Math.floor(this.outerRadius / 9)
.style('font-size', (i) => {
if (Math.ceil(this.outerRadius / 8) > 30) {
return 30
} else {
return Math.ceil(this.outerRadius / 8)
}
})
.attr('width', function (d, i) {
.attr('width', (d, i) => {
if ((d.endAngle - d.startAngle) < 0.25) {
return 0
} else {
return Math.abs(arc.centroid(d)[0]) * 2
}
})
.attr('height', function (d, i) {
return Math.abs(arc.centroid(d)[1])
.attr('height', (d, i) => {
if (Math.ceil(this.outerRadius / 8) > 30) {
return 90
} else {
return Math.ceil(this.outerRadius / 8) * 3
}
// return Math.abs(arc.centroid(d)[1]) - Math.ceil(this.outerRadius / 10)
})
// .attr('height', '20px')
.style('line-height', '20px')
.style('text-align', 'center')
.style('padding-left', () => {
if (this.isFullscreen) {
return 20
} else {
return 5
}
})
.style('padding-right', (d, i) => {
const path = document.getElementsByClassName('path')[i]
const w = path.getBoundingClientRect().width
if (((d.endAngle - d.startAngle) < 0.25) || (Math.abs(arc.centroid(d)[0]) * 2 < w / 2)) {
return 0
} else {
return (Math.abs(arc.centroid(d)[0]) * 2 - w / 3) / 2
}
})
.style('line-height', '16px')
.html((d) => {
return this.drawText(d)
})
@@ -253,7 +306,7 @@ export default {
if (str && valueStr) {
return `
<div style="width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer;">
<p style="width: 80%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;display: flex;align-items: center;justify-content: center;">
<p style="width: 100%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;line-height: 150%">
<span>${str}</span>
</p>
<p style="width: 80%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;display: flex;align-items: center;justify-content: center;">
@@ -264,14 +317,14 @@ export default {
node.data.mapping.color &&
node.data.mapping.color.icon
};font-size:1em;"></i>
<span style="color:#000">${valueStr}</span>
<span>${valueStr}</span>
</p>
</div>
`
} else if (str) {
return `
<div style="width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer;">
<p style="width: 80%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;display: flex;align-items: center;justify-content: center;">
<div style="color:#000;width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer">
<p style="width: 80%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;display: flex;align-items: center;justify-content: center;line-height: 150%">
<i class="${
node.data.mapping && node.data.mapping.icon
}" style="color: ${
@@ -279,14 +332,14 @@ export default {
node.data.mapping.color &&
node.data.mapping.color.icon
};font-size:1em;"></i>
<span style="color:#000">${str}</span>
<span style="color:#000;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;display:inline-block">${str}</span>
</p>
</div>
`
} else if (valueStr) {
return `
<div style="width:100%;height: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;cursor: pointer;">
<p style="width: 80%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;display: flex;align-items: center;justify-content: center;">
<p style="width: 80%;height: 100%;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;text-align:center;display: flex;align-items: center;justify-content: center;line-height: 150%">
<i class="${
node.data.mapping && node.data.mapping.icon
}" style="color: ${
@@ -294,7 +347,7 @@ export default {
node.data.mapping.color &&
node.data.mapping.color.icon
};font-size:1em;"></i>
<span style="color:#000">${valueStr}</span>
<span style="color:#000;text-overflow: ellipsis;white-space: nowrap;overflow:hidden;display:inline-block">${valueStr}</span>
</p>
</div>
`
@@ -346,7 +399,7 @@ export default {
},
resize () {
setTimeout(() => {
this.drawArcPie()
this.drawArcPie(this.dataset)
}, 50)
},
clearCache () {
@@ -356,10 +409,38 @@ export default {
},
beforeDestroy () {
this.clearCache()
},
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 ''
}
},
pieData (data) {
this.drawArcPie(data)
}
}
}
</script>
<style>
<style scoped>
.legend-container{
padding-left: 30px;
}
</style>>
</style>

View File

@@ -14,7 +14,7 @@
:key="index"
:class="{'row--inactive': isGrey[index]}"
class="legend--table-row"
@click="clickLegend(item.name, index)"
@click="clickLegend(item.name, index,item)"
>
<div :title="item.alias ? item.alias : item.name" class="legend--table-cell">
<span :style="{background: item.color}" class="legend-shape"></span>{{item.alias ? item.alias : item.name}}
@@ -98,6 +98,10 @@ export default {
this.clickLegendTreemap(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
return
}
if (this.chartInfo.type === 'pie') {
this.clickLegendPie(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
return
}
if (echarts) {
// 判断timeSeries类型图表 先取消多表联动
@@ -161,6 +165,17 @@ export default {
}
}
},
clickLegendPie (legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight) {
if (!hasGrey) { // 1.除当前legend外全置灰
this.isGrey = this.isGrey.map((g, i) => i !== index)
} else if (currentIsTheOnlyOneHighlight) { // 2.全高亮
this.isGrey = this.isGrey.map(() => false)
} else { // 对应高亮
this.$set(this.isGrey, index, !this.isGrey[index])
}
const data = this.legends.filter((item, i) => !this.isGrey[i])
this.$emit('pieData', data)
},
clickLegendBar (legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight) {
const echarts = getChart(this.chartId)
if (echarts) {