NEZ-2769 feat:dashboard增加 Rose chart(玫瑰图)图表类型
This commit is contained in:
@@ -721,7 +721,6 @@
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.foreign{
|
.foreign{
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
.foreign-label-wrap{
|
.foreign-label-wrap{
|
||||||
@@ -733,15 +732,13 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.funnel-label{
|
.funnel-label{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: $--color-text-primary;
|
color: $--color-text-primary;
|
||||||
}
|
}
|
||||||
|
.doughnut-label,.rose-label{
|
||||||
.doughnut-label{
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export default {
|
|||||||
case 'gauge' :
|
case 'gauge' :
|
||||||
case 'pie' :
|
case 'pie' :
|
||||||
case 'doughnut' :
|
case 'doughnut' :
|
||||||
|
case 'rose' :
|
||||||
case 'treemap' :
|
case 'treemap' :
|
||||||
case 'log' :
|
case 'log' :
|
||||||
case 'hexagon' :
|
case 'hexagon' :
|
||||||
|
|||||||
@@ -36,6 +36,15 @@
|
|||||||
:is-fullscreen="isFullscreen"
|
:is-fullscreen="isFullscreen"
|
||||||
@chartIsNoData="chartIsNoData"
|
@chartIsNoData="chartIsNoData"
|
||||||
></chart-doughnut>
|
></chart-doughnut>
|
||||||
|
<chart-rose
|
||||||
|
:ref="'chart' + chartInfo.id"
|
||||||
|
v-if="isRose(chartInfo.type)"
|
||||||
|
:chart-data="chartData"
|
||||||
|
:chart-info="chartInfo"
|
||||||
|
:chart-option="chartOption"
|
||||||
|
:is-fullscreen="isFullscreen"
|
||||||
|
@chartIsNoData="chartIsNoData"
|
||||||
|
></chart-rose>
|
||||||
<chart-bar
|
<chart-bar
|
||||||
:ref="'chart' + chartInfo.id"
|
:ref="'chart' + chartInfo.id"
|
||||||
v-if="isChartBar(chartInfo.type)"
|
v-if="isChartBar(chartInfo.type)"
|
||||||
@@ -249,6 +258,7 @@ import chartLog from './chart/chartLog'
|
|||||||
import chartNoData from './chart/chartNoData'
|
import chartNoData from './chart/chartNoData'
|
||||||
import chartPie from './chart/chartPie'
|
import chartPie from './chart/chartPie'
|
||||||
import chartDoughnut from './chart/chartDoughnut'
|
import chartDoughnut from './chart/chartDoughnut'
|
||||||
|
import chartRose from './chart/chartRose'
|
||||||
import chartStat from './chart/chartStat'
|
import chartStat from './chart/chartStat'
|
||||||
import chartTable from './chart/chartTable'
|
import chartTable from './chart/chartTable'
|
||||||
import chartText from './chart/chartText'
|
import chartText from './chart/chartText'
|
||||||
@@ -263,7 +273,7 @@ import chartBubble from './chart/chartBubble'
|
|||||||
import chartRank from './chart/chartRank'
|
import chartRank from './chart/chartRank'
|
||||||
import chartSankey from './chart/chartSankey'
|
import chartSankey from './chart/chartSankey'
|
||||||
import chartFunnel from './chart/chartFunnelNew'
|
import chartFunnel from './chart/chartFunnelNew'
|
||||||
import { getOption, isTimeSeries, isHexagon, isUrl, isText, isChartPie, isDoughnut, isChartBar, isTreemap, isLog, isStat, isDiagram, isGroup, isAutotopology, isMap, isAssetInfo, isEndpointInfo, isTable, isGauge, isClock, isTopology, isChartBubble, isChartRank, isSankey, isFunnel } from './chart/tools'
|
import { getOption, isTimeSeries, isHexagon, isUrl, isText, isChartPie, isDoughnut, isRose, isChartBar, isTreemap, isLog, isStat, isDiagram, isGroup, isAutotopology, isMap, isAssetInfo, isEndpointInfo, isTable, isGauge, isClock, isTopology, isChartBubble, isChartRank, isSankey, isFunnel } from './chart/tools'
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -282,6 +292,7 @@ export default {
|
|||||||
chartNoData,
|
chartNoData,
|
||||||
chartPie,
|
chartPie,
|
||||||
chartDoughnut,
|
chartDoughnut,
|
||||||
|
chartRose,
|
||||||
chartStat,
|
chartStat,
|
||||||
chartTable,
|
chartTable,
|
||||||
chartText,
|
chartText,
|
||||||
@@ -354,6 +365,7 @@ export default {
|
|||||||
isHexagon,
|
isHexagon,
|
||||||
isChartPie,
|
isChartPie,
|
||||||
isDoughnut,
|
isDoughnut,
|
||||||
|
isRose,
|
||||||
isChartBar,
|
isChartBar,
|
||||||
isUrl,
|
isUrl,
|
||||||
isText,
|
isText,
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
<div
|
<div
|
||||||
:class="legendPlacement"
|
:class="legendPlacement"
|
||||||
ref="doughnut-chart-box"
|
ref="doughnut-chart-box"
|
||||||
class="nz-chart__component nz-chart__component--time-series"
|
class="nz-chart__component"
|
||||||
>
|
>
|
||||||
<div :id="`chart-canvas-${chartId}`" class="chart__canvas"></div>
|
<div :id="`chart-canvas-${chartId}`" class="chart__canvas" style="overflow: hidden;"></div>
|
||||||
<chart-legend
|
<chart-legend
|
||||||
v-if="hasLegend"
|
v-if="hasLegend"
|
||||||
:chart-data="chartData"
|
:chart-data="chartData"
|
||||||
:chart-info="chartInfo"
|
:chart-info="chartInfo"
|
||||||
:legends="legends"
|
:legends="legends"
|
||||||
:is-fullscreen="isFullscreen"
|
:is-fullscreen="isFullscreen"
|
||||||
@clickLegendDoughnut="clickLegendDoughnut"
|
@clickLegendD3="clickLegendD3"
|
||||||
></chart-legend>
|
></chart-legend>
|
||||||
<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-${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">
|
<div class="chart-canvas-tooltip-title tooltip-title">
|
||||||
@@ -120,7 +120,7 @@ export default {
|
|||||||
const mapping = this.selectMapping(value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
|
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)
|
const legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
|
||||||
this.doughnutData.push({
|
this.doughnutData.push({
|
||||||
value: value,
|
value: Number(value),
|
||||||
realValue: value,
|
realValue: value,
|
||||||
showValue: showValue,
|
showValue: showValue,
|
||||||
name: legend.name,
|
name: legend.name,
|
||||||
@@ -141,7 +141,6 @@ export default {
|
|||||||
this.$emit('chartIsNoData', this.isNoData)
|
this.$emit('chartIsNoData', this.isNoData)
|
||||||
},
|
},
|
||||||
drawDoughnutChart (animate) {
|
drawDoughnutChart (animate) {
|
||||||
this.dispose()
|
|
||||||
const svgDom = document.getElementById(`chart-canvas-${this.chartId}`)
|
const svgDom = document.getElementById(`chart-canvas-${this.chartId}`)
|
||||||
if (!svgDom) {
|
if (!svgDom) {
|
||||||
return false
|
return false
|
||||||
@@ -160,8 +159,17 @@ export default {
|
|||||||
|
|
||||||
const width = svgDom.getBoundingClientRect().width
|
const width = svgDom.getBoundingClientRect().width
|
||||||
const height = svgDom.getBoundingClientRect().height
|
const height = svgDom.getBoundingClientRect().height
|
||||||
this.svg = d3.select(`#chart-canvas-${this.chartId}`).append('svg').attr('height', height).attr('width', width).attr('viewBox', [-width / 2, -height / 2, width, height]).style('display', 'block')
|
let chart
|
||||||
const chart = this.svg.append('g')
|
if (this.svg) {
|
||||||
|
chart = this.svg.select('g')
|
||||||
|
} else {
|
||||||
|
this.svg = d3.select(`#chart-canvas-${this.chartId}`).append('svg').style('display', 'block')
|
||||||
|
chart = this.svg.append('g')
|
||||||
|
}
|
||||||
|
this.svg
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', height)
|
||||||
|
.attr('viewBox', [-width / 2, -height / 2, width, height])
|
||||||
|
|
||||||
const outerRadius = (Math.min(width, height) / 2) * 0.6 // outer radius of pie, in pixels
|
const outerRadius = (Math.min(width, height) / 2) * 0.6 // outer radius of pie, in pixels
|
||||||
const innerRadius = outerRadius * 0.67 // inner radius of pie, in pixels (non-zero for donut)
|
const innerRadius = outerRadius * 0.67 // inner radius of pie, in pixels (non-zero for donut)
|
||||||
@@ -174,15 +182,15 @@ export default {
|
|||||||
const arcs = pie(doughnutData)
|
const arcs = pie(doughnutData)
|
||||||
|
|
||||||
function doughnutOver (e, d) {
|
function doughnutOver (e, d) {
|
||||||
d3.select(chart.selectAll('path').nodes()[d.index])
|
chart.select('.path-' + d.index)
|
||||||
.transition()
|
.transition()
|
||||||
.attr('d', d3.arc()
|
.attr('d', d3.arc()
|
||||||
.innerRadius(innerRadius)
|
.innerRadius(innerRadius)
|
||||||
.outerRadius(outerRadius * 1.05)
|
.outerRadius(outerRadius * 1.06)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
function doughnutOut (e, d) {
|
function doughnutOut (e, d) {
|
||||||
d3.select(chart.selectAll('path').nodes()[d.index])
|
chart.select('.path-' + d.index)
|
||||||
.transition()
|
.transition()
|
||||||
.attr('d', d3.arc()
|
.attr('d', d3.arc()
|
||||||
.innerRadius(innerRadius)
|
.innerRadius(innerRadius)
|
||||||
@@ -190,28 +198,30 @@ export default {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const that = this
|
|
||||||
// 图形
|
// 图形
|
||||||
chart.selectAll('path')
|
chart.selectAll('path')
|
||||||
.data(arcs)
|
.data(arcs.filter(function (d) {
|
||||||
|
return d.data.value
|
||||||
|
}))
|
||||||
.join('path')
|
.join('path')
|
||||||
.attr('fill', d => d.data.background)
|
.attr('fill', d => d.data.background)
|
||||||
.attr('d', arc)
|
.attr('d', arc)
|
||||||
|
.attr('class', (d) => 'path-' + d.index)
|
||||||
.style('cursor', 'pointer')
|
.style('cursor', 'pointer')
|
||||||
.classed('no-events', true)
|
.classed('no-events', true)
|
||||||
.on('mouseover', doughnutOver)
|
.on('mouseover', doughnutOver)
|
||||||
.on('mouseout', doughnutOut)
|
.on('mouseout', doughnutOut)
|
||||||
.on('mouseenter', that.doughnutEnter)
|
.on('mouseenter', this.doughnutEnter)
|
||||||
.on('mousemove', that.doughnutMove)
|
.on('mousemove', this.doughnutMove)
|
||||||
.on('mouseleave', that.doughnutLeave)
|
.on('mouseleave', this.doughnutLeave)
|
||||||
.transition().duration(animate === true ? 600 : 0)
|
.transition().duration(animate === true ? 600 : 0)
|
||||||
.attrTween('d', (d) => {
|
.attrTween('d', function (d) {
|
||||||
let currentArc = this._current
|
let currentArc = this._current
|
||||||
if (!currentArc) {
|
if (!currentArc) {
|
||||||
currentArc = { startAngle: 0, endAngle: 0 }
|
currentArc = { startAngle: 0, endAngle: 0 }
|
||||||
}
|
}
|
||||||
const i = d3.interpolate(currentArc, d)
|
const i = d3.interpolate(currentArc, d)
|
||||||
this._current = i(0) // 当饼图更新时,从当前角度过渡到新角度
|
this._current = i(1) // 当饼图更新时,从当前角度过渡到新角度
|
||||||
return function (t) {
|
return function (t) {
|
||||||
return arc(i(t))
|
return arc(i(t))
|
||||||
}
|
}
|
||||||
@@ -243,8 +253,9 @@ export default {
|
|||||||
d3.select(this).classed('no-events', false)
|
d3.select(this).classed('no-events', false)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理label
|
// 处理label
|
||||||
formatterLabel ({ data }) {
|
formatterLabel ({ data }) {
|
||||||
let str = ''
|
let str = ''
|
||||||
let valueStr = ''
|
let valueStr = ''
|
||||||
if (this.chartInfo.param.text === 'all') {
|
if (this.chartInfo.param.text === 'all') {
|
||||||
@@ -327,7 +338,7 @@ export default {
|
|||||||
this.tooltip.x = e.pageX + 15
|
this.tooltip.x = e.pageX + 15
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clickLegendDoughnut (isGrey) {
|
clickLegendD3 (isGrey) {
|
||||||
const data = this.doughnutData.filter((item, i) => !isGrey[i])
|
const data = this.doughnutData.filter((item, i) => !isGrey[i])
|
||||||
this.selectData = this.$loadsh.cloneDeep(data)
|
this.selectData = this.$loadsh.cloneDeep(data)
|
||||||
this.drawDoughnutChart(true)
|
this.drawDoughnutChart(true)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
:class="legendPlacement"
|
:class="legendPlacement"
|
||||||
ref="pie-chart-box"
|
ref="pie-chart-box"
|
||||||
class="nz-chart__component nz-chart__component--time-series" @mouseenter="mouseEnterChart"
|
class="nz-chart__component" @mouseenter="mouseEnterChart"
|
||||||
@mouseleave="mouseLeaveChart"
|
@mouseleave="mouseLeaveChart"
|
||||||
>
|
>
|
||||||
<div :id="`chart-canvas-${chartId}`" class="chart__canvas"></div>
|
<div :id="`chart-canvas-${chartId}`" class="chart__canvas"></div>
|
||||||
|
|||||||
395
nezha-fronted/src/components/chart/chart/chartRose.vue
Normal file
395
nezha-fronted/src/components/chart/chart/chartRose.vue
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="legendPlacement"
|
||||||
|
ref="rose-chart-box"
|
||||||
|
class="nz-chart__component"
|
||||||
|
>
|
||||||
|
<div :id="`chart-canvas-${chartId}`" class="chart__canvas" style="overflow: hidden;"></div>
|
||||||
|
<chart-legend
|
||||||
|
v-if="hasLegend"
|
||||||
|
:chart-data="chartData"
|
||||||
|
:chart-info="chartInfo"
|
||||||
|
:legends="legends"
|
||||||
|
:is-fullscreen="isFullscreen"
|
||||||
|
@clickLegendD3="clickLegendD3"
|
||||||
|
></chart-legend>
|
||||||
|
<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 lodash from 'lodash'
|
||||||
|
import chartFormat from '@/components/chart/chartFormat'
|
||||||
|
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 {
|
||||||
|
name: 'chart-rose',
|
||||||
|
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: [],
|
||||||
|
isInit: true, // 是否是初始化,初始化时为true,图表初始化结束后设为false
|
||||||
|
chartId: '',
|
||||||
|
roseData: [],
|
||||||
|
selectData: [],
|
||||||
|
tooltip: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
title: 0,
|
||||||
|
value: 0,
|
||||||
|
mapping: {},
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
svg: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initChart (animate) {
|
||||||
|
this.legends = []
|
||||||
|
this.initRoseData(this.chartInfo, this.chartData)
|
||||||
|
this.selectData = this.$loadsh.cloneDeep(this.roseData)
|
||||||
|
if (this.isNoData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/* 使用setTimeout延迟渲染图表,避免样式错乱 */
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawRoseChart(animate)
|
||||||
|
this.isInit = false
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
initRoseData (chartInfo, originalDatas) {
|
||||||
|
this.roseData = []
|
||||||
|
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.roseData.push({
|
||||||
|
value: Number(value),
|
||||||
|
realValue: value,
|
||||||
|
showValue: showValue,
|
||||||
|
name: legend.name,
|
||||||
|
alias: legend.alias,
|
||||||
|
labels: {
|
||||||
|
...data.metric,
|
||||||
|
legend: legend.alias
|
||||||
|
},
|
||||||
|
seriesIndex: expressionIndex,
|
||||||
|
dataIndex: dataIndex,
|
||||||
|
mapping: mapping,
|
||||||
|
background: mapping ? mapping.color.bac : this.colorList[colorIndex]
|
||||||
|
|
||||||
|
})
|
||||||
|
colorIndex++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.$emit('chartIsNoData', this.isNoData)
|
||||||
|
},
|
||||||
|
drawRoseChart (animate) {
|
||||||
|
const svgDom = document.getElementById(`chart-canvas-${this.chartId}`)
|
||||||
|
if (!svgDom) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果数据全为0 则设置默认值(否则图表不显示)
|
||||||
|
let roseData = lodash.cloneDeep(this.selectData)
|
||||||
|
if (roseData.every(item => item.value == 0)) {
|
||||||
|
roseData = roseData.map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = svgDom.getBoundingClientRect().width
|
||||||
|
const height = svgDom.getBoundingClientRect().height
|
||||||
|
let chart
|
||||||
|
if (this.svg) {
|
||||||
|
chart = this.svg.select('g')
|
||||||
|
} else {
|
||||||
|
this.svg = d3.select(`#chart-canvas-${this.chartId}`).append('svg').style('display', 'block')
|
||||||
|
chart = this.svg.append('g')
|
||||||
|
}
|
||||||
|
this.svg.attr('width', width)
|
||||||
|
.attr('height', height)
|
||||||
|
.attr('viewBox', [-width / 2, -height / 2, width, height])
|
||||||
|
|
||||||
|
const outerRadius = (Math.min(width, height) / 2) * 0.6 // outer radius of pie, in pixels
|
||||||
|
const innerRadius = 20 // inner radius of pie, in pixels (non-zero for donut)
|
||||||
|
const padAngle = 0 // angular separation between wedges
|
||||||
|
const radius = 4
|
||||||
|
|
||||||
|
const scale = d3.scaleRadial()
|
||||||
|
.domain([0, d3.max(roseData, d => d.value)])
|
||||||
|
.range([innerRadius, outerRadius])
|
||||||
|
|
||||||
|
const arc = d3.arc()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(d => {
|
||||||
|
return scale(d.data.value)
|
||||||
|
})
|
||||||
|
.cornerRadius(radius)
|
||||||
|
|
||||||
|
const pie = d3.pie()
|
||||||
|
.padAngle(padAngle)
|
||||||
|
.sort((a, b) => b.value - a.value)
|
||||||
|
.value(d => d.value)
|
||||||
|
|
||||||
|
const arcs = pie(roseData)
|
||||||
|
|
||||||
|
function roseOver (e, d) {
|
||||||
|
chart.select('.path-' + d.index)
|
||||||
|
.transition()
|
||||||
|
.attr('d', d3.arc()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(d => {
|
||||||
|
return scale(d.data.value) * 1.06
|
||||||
|
})
|
||||||
|
.cornerRadius(radius)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function roseOut (e, d) {
|
||||||
|
chart.select('.path-' + d.index)
|
||||||
|
.transition()
|
||||||
|
.attr('d', d3.arc()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(d => scale(d.data.value))
|
||||||
|
.cornerRadius(radius)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 图形
|
||||||
|
chart.selectAll('path')
|
||||||
|
.data(arcs.filter(function (d) {
|
||||||
|
return d.data.value
|
||||||
|
}))
|
||||||
|
.join('path')
|
||||||
|
.attr('fill', d => d.data.background)
|
||||||
|
.attr('d', arc)
|
||||||
|
.attr('class', (d) => 'path-' + d.index)
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.classed('no-events', true)
|
||||||
|
.on('mouseover', roseOver)
|
||||||
|
.on('mouseout', roseOut)
|
||||||
|
.on('mouseenter', this.roseEnter)
|
||||||
|
.on('mousemove', this.roseMove)
|
||||||
|
.on('mouseleave', this.roseLeave)
|
||||||
|
.transition().duration(animate === true ? 600 : 0)
|
||||||
|
.attrTween('d', function (d) {
|
||||||
|
let currentArc = this._current
|
||||||
|
if (!currentArc) {
|
||||||
|
currentArc = { startAngle: 0, endAngle: 0 }
|
||||||
|
}
|
||||||
|
const i = d3.interpolate(currentArc, d)
|
||||||
|
this._current = i(1) // 当饼图更新时,从当前角度过渡到新角度
|
||||||
|
const arc = d3.arc()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(scale(d.value))
|
||||||
|
.cornerRadius(radius)
|
||||||
|
return function (t) {
|
||||||
|
return arc(i(t))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('end', function () {
|
||||||
|
d3.select(this).classed('no-events', false)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 文本
|
||||||
|
chart
|
||||||
|
.selectAll('foreignObject')
|
||||||
|
.data(arcs)
|
||||||
|
.join('foreignObject')
|
||||||
|
.attr('transform', d => `translate(${arc.centroid(d)})`)
|
||||||
|
.attr('class', 'foreign')
|
||||||
|
.classed('no-events', true)
|
||||||
|
.on('mouseover', roseOver)
|
||||||
|
.on('mouseout', roseOut)
|
||||||
|
.on('mouseenter', this.roseEnter)
|
||||||
|
.on('mousemove', this.roseMove)
|
||||||
|
.on('mouseleave', this.roseLeave)
|
||||||
|
.style('opacity', 0)
|
||||||
|
.html((d) => {
|
||||||
|
return d.endAngle - d.startAngle > 0.25 ? this.formatterLabel(d) : ''
|
||||||
|
})
|
||||||
|
.transition('opacity').duration(animate === true ? 600 : 0)
|
||||||
|
.style('opacity', 1)
|
||||||
|
.on('end', function () {
|
||||||
|
d3.select(this).classed('no-events', false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理label
|
||||||
|
formatterLabel ({ data }) {
|
||||||
|
let str = ''
|
||||||
|
let valueStr = ''
|
||||||
|
if (this.chartInfo.param.text === 'all') {
|
||||||
|
str += data.alias
|
||||||
|
valueStr = data.mapping && data.mapping.display ? this.handleDisplay(data.mapping.display, { ...data.labels, 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, { ...data.labels, value: data.showValue }) : data.showValue
|
||||||
|
}
|
||||||
|
if (this.chartInfo.param.text === 'legend') {
|
||||||
|
str += data.alias
|
||||||
|
}
|
||||||
|
if (this.chartInfo.param.text === 'none') {
|
||||||
|
str += ''
|
||||||
|
}
|
||||||
|
if (str && valueStr) {
|
||||||
|
return `<div class="foreign-label-wrap">
|
||||||
|
<p class="rose-label" style="color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
|
<span>${str}</span>
|
||||||
|
</p>
|
||||||
|
<p class="rose-label" style="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 class="foreign-label-wrap">
|
||||||
|
<p class="rose-label" style="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 class="foreign-label-wrap">
|
||||||
|
<p class="rose-label" style="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>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
roseEnter (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)
|
||||||
|
},
|
||||||
|
roseMove (e) { // 气泡内移动
|
||||||
|
if (this.tooltip.show) {
|
||||||
|
this.tooltip.show = true
|
||||||
|
this.setPosition(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
roseLeave () { // 移出气泡
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickLegendD3 (isGrey) {
|
||||||
|
const data = this.roseData.filter((item, i) => !isGrey[i])
|
||||||
|
this.selectData = this.$loadsh.cloneDeep(data)
|
||||||
|
this.drawRoseChart(true)
|
||||||
|
},
|
||||||
|
resize () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawRoseChart(false)
|
||||||
|
}, 50)
|
||||||
|
},
|
||||||
|
dispose () {
|
||||||
|
if (this.svg) {
|
||||||
|
this.svg.selectAll('path').on('mouseover', null)
|
||||||
|
this.svg.selectAll('path').on('mouseout', null)
|
||||||
|
this.svg.selectAll('path').on('mouseenter', null)
|
||||||
|
this.svg.selectAll('path').on('mousemove', null)
|
||||||
|
this.svg.selectAll('path').on('mouseleave', null)
|
||||||
|
this.svg.selectAll('foreignObject').on('mouseover', null)
|
||||||
|
this.svg.selectAll('foreignObject').on('mouseout', null)
|
||||||
|
this.svg.selectAll('foreignObject').on('mouseenter', null)
|
||||||
|
this.svg.selectAll('foreignObject').on('mousemove', null)
|
||||||
|
this.svg.selectAll('foreignObject').on('mouseleave', null)
|
||||||
|
this.svg.remove()
|
||||||
|
this.svg = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.colorList = initColor(20)
|
||||||
|
this.chartInfo.loaded && this.initChart(true)
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -139,8 +139,8 @@ export default {
|
|||||||
this.clickLegendTreemap(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
this.clickLegendTreemap(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.chartInfo.type === 'doughnut') {
|
if (this.chartInfo.type === 'doughnut' || this.chartInfo.type === 'rose') {
|
||||||
this.clickLegendDoughnut(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
this.clickLegendD3(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clickLegendDoughnut (legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight) {
|
clickLegendD3 (legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight) {
|
||||||
if (!hasGrey) { // 1.除当前legend外全置灰
|
if (!hasGrey) { // 1.除当前legend外全置灰
|
||||||
this.isGrey = this.isGrey.map((g, i) => i !== index)
|
this.isGrey = this.isGrey.map((g, i) => i !== index)
|
||||||
} else if (currentIsTheOnlyOneHighlight) { // 2.全高亮
|
} else if (currentIsTheOnlyOneHighlight) { // 2.全高亮
|
||||||
@@ -240,7 +240,7 @@ export default {
|
|||||||
} else { // 对应高亮
|
} else { // 对应高亮
|
||||||
this.$set(this.isGrey, index, !this.isGrey[index])
|
this.$set(this.isGrey, index, !this.isGrey[index])
|
||||||
}
|
}
|
||||||
this.$emit('clickLegendDoughnut', this.isGrey)
|
this.$emit('clickLegendD3', this.isGrey)
|
||||||
},
|
},
|
||||||
// 四舍五入保留2位小数(不够位数,则用0替补)
|
// 四舍五入保留2位小数(不够位数,则用0替补)
|
||||||
keepTwoDecimalFull (num) {
|
keepTwoDecimalFull (num) {
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ export function isChartPie (type) {
|
|||||||
export function isDoughnut (type) {
|
export function isDoughnut (type) {
|
||||||
return type === chartType.doughnut
|
return type === chartType.doughnut
|
||||||
}
|
}
|
||||||
|
export function isRose (type) {
|
||||||
|
return type === chartType.rose
|
||||||
|
}
|
||||||
export function isChartBar (type) {
|
export function isChartBar (type) {
|
||||||
return type === chartType.bar
|
return type === chartType.bar
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,6 +289,10 @@ export const chart = {
|
|||||||
value: 'doughnut',
|
value: 'doughnut',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'rose',
|
||||||
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.rose.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'table',
|
value: 'table',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.table.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.table.label')
|
||||||
@@ -475,6 +479,7 @@ export const chartType = {
|
|||||||
gauge: 'gauge',
|
gauge: 'gauge',
|
||||||
pie: 'pie',
|
pie: 'pie',
|
||||||
doughnut: 'doughnut',
|
doughnut: 'doughnut',
|
||||||
|
rose: 'rose',
|
||||||
treemap: 'treemap',
|
treemap: 'treemap',
|
||||||
log: 'log',
|
log: 'log',
|
||||||
text: 'text',
|
text: 'text',
|
||||||
|
|||||||
@@ -1272,7 +1272,8 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie' || this.oldType === 'doughnut') {
|
case 'rose':
|
||||||
|
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie' || this.oldType === 'doughnut' || this.oldType === 'rose') {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.chartConfig.param = {
|
this.chartConfig.param = {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export default {
|
|||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
return false
|
return false
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
@@ -74,6 +75,7 @@ export default {
|
|||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
case 'rank':
|
case 'rank':
|
||||||
case 'sankey':
|
case 'sankey':
|
||||||
@@ -90,6 +92,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'bar':
|
case 'bar':
|
||||||
return true
|
return true
|
||||||
case 'table':
|
case 'table':
|
||||||
@@ -113,6 +116,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'bar':
|
case 'bar':
|
||||||
return false
|
return false
|
||||||
default: return false
|
default: return false
|
||||||
@@ -141,6 +145,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
case 'rank':
|
case 'rank':
|
||||||
case 'sankey':
|
case 'sankey':
|
||||||
@@ -156,6 +161,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
@@ -174,6 +180,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
@@ -210,6 +217,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
|
case 'rose':
|
||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
|||||||
@@ -251,6 +251,10 @@ export default {
|
|||||||
id: 'doughnut',
|
id: 'doughnut',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'rose',
|
||||||
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.rose.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bubble',
|
id: 'bubble',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
||||||
@@ -314,6 +318,10 @@ export default {
|
|||||||
id: 'doughnut',
|
id: 'doughnut',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'rose',
|
||||||
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.rose.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bubble',
|
id: 'bubble',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
||||||
|
|||||||
@@ -894,6 +894,10 @@ export default {
|
|||||||
id: 'doughnut',
|
id: 'doughnut',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'rose',
|
||||||
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.rose.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bubble',
|
id: 'bubble',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
||||||
@@ -1006,7 +1010,8 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
case 'doughnut':
|
case 'doughnut':
|
||||||
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie' || this.oldType === 'doughnut') {
|
case 'rose':
|
||||||
|
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie' || this.oldType === 'doughnut' || this.oldType === 'rose') {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.chartConfig.param = {
|
this.chartConfig.param = {
|
||||||
|
|||||||
Reference in New Issue
Block a user