NEZ-2768 feat:新增圆环图 图表类型
This commit is contained in:
@@ -706,3 +706,9 @@ textarea {
|
|||||||
.message-zindex{
|
.message-zindex{
|
||||||
z-index: 5000 !important;
|
z-index: 5000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-ellipsis{
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -715,25 +715,40 @@
|
|||||||
stroke: $--color-text-primary;;
|
stroke: $--color-text-primary;;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-ellipsis{
|
.no-events{
|
||||||
|
pointer-events: none !important;
|
||||||
|
*{
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.foreign{
|
||||||
|
overflow: visible;
|
||||||
|
.foreign-label-wrap{
|
||||||
|
white-space: nowrap;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.funnel-label{
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
line-height: 16px;
|
||||||
|
color: $--color-text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doughnut-label{
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
color: #000000;
|
||||||
|
max-width: 200px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.funnel-label-wrap{
|
|
||||||
white-space: nowrap;
|
|
||||||
position: absolute;
|
|
||||||
transform: translate(-50%,-50%);
|
|
||||||
pointer-events: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
.funnel-label{
|
|
||||||
cursor: pointer;
|
|
||||||
width: min-content;
|
|
||||||
pointer-events: auto;
|
|
||||||
line-height: 16px;
|
|
||||||
color: $--color-text-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -135,6 +135,7 @@ export default {
|
|||||||
case 'stat' :
|
case 'stat' :
|
||||||
case 'gauge' :
|
case 'gauge' :
|
||||||
case 'pie' :
|
case 'pie' :
|
||||||
|
case 'doughnut' :
|
||||||
case 'treemap' :
|
case 'treemap' :
|
||||||
case 'log' :
|
case 'log' :
|
||||||
case 'hexagon' :
|
case 'hexagon' :
|
||||||
|
|||||||
@@ -27,6 +27,15 @@
|
|||||||
:is-fullscreen="isFullscreen"
|
:is-fullscreen="isFullscreen"
|
||||||
@chartIsNoData="chartIsNoData"
|
@chartIsNoData="chartIsNoData"
|
||||||
></chart-pie>
|
></chart-pie>
|
||||||
|
<chart-doughnut
|
||||||
|
:ref="'chart' + chartInfo.id"
|
||||||
|
v-if="isDoughnut(chartInfo.type)"
|
||||||
|
:chart-data="chartData"
|
||||||
|
:chart-info="chartInfo"
|
||||||
|
:chart-option="chartOption"
|
||||||
|
:is-fullscreen="isFullscreen"
|
||||||
|
@chartIsNoData="chartIsNoData"
|
||||||
|
></chart-doughnut>
|
||||||
<chart-bar
|
<chart-bar
|
||||||
:ref="'chart' + chartInfo.id"
|
:ref="'chart' + chartInfo.id"
|
||||||
v-if="isChartBar(chartInfo.type)"
|
v-if="isChartBar(chartInfo.type)"
|
||||||
@@ -239,6 +248,7 @@ 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'
|
||||||
import chartPie from './chart/chartPie'
|
import chartPie from './chart/chartPie'
|
||||||
|
import chartDoughnut from './chart/chartDoughnut'
|
||||||
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'
|
||||||
@@ -253,7 +263,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, 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, 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 {
|
||||||
@@ -271,6 +281,7 @@ export default {
|
|||||||
chartLog,
|
chartLog,
|
||||||
chartNoData,
|
chartNoData,
|
||||||
chartPie,
|
chartPie,
|
||||||
|
chartDoughnut,
|
||||||
chartStat,
|
chartStat,
|
||||||
chartTable,
|
chartTable,
|
||||||
chartText,
|
chartText,
|
||||||
@@ -342,6 +353,7 @@ export default {
|
|||||||
isTimeSeries,
|
isTimeSeries,
|
||||||
isHexagon,
|
isHexagon,
|
||||||
isChartPie,
|
isChartPie,
|
||||||
|
isDoughnut,
|
||||||
isChartBar,
|
isChartBar,
|
||||||
isUrl,
|
isUrl,
|
||||||
isText,
|
isText,
|
||||||
|
|||||||
@@ -108,86 +108,84 @@ export default {
|
|||||||
this.$emit('chartIsNoData', this.isNoData)
|
this.$emit('chartIsNoData', this.isNoData)
|
||||||
},
|
},
|
||||||
drawBubbleChart () {
|
drawBubbleChart () {
|
||||||
this.$nextTick(() => {
|
this.dispose()
|
||||||
this.dispose()
|
this.svg = d3.select(`#bubble-svg-${this.chartId}`)
|
||||||
this.svg = d3.select(`#bubble-svg-${this.chartId}`)
|
const svgDom = document.getElementById(`bubble-svg-${this.chartId}`)
|
||||||
const svgDom = document.getElementById(`bubble-svg-${this.chartId}`)
|
if (!svgDom) {
|
||||||
if (!svgDom) {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
const width = svgDom.getBoundingClientRect().width
|
||||||
const width = svgDom.getBoundingClientRect().width
|
const height = svgDom.getBoundingClientRect().height
|
||||||
const height = svgDom.getBoundingClientRect().height
|
// 定义布局方式
|
||||||
// 定义布局方式
|
const pack = d3.pack()
|
||||||
const pack = d3.pack()
|
.size([width, height])
|
||||||
.size([width, height])
|
.padding(6)
|
||||||
.padding(6)
|
|
||||||
// 如果数据全为0 则设置默认值(否则图表不显示)
|
// 如果数据全为0 则设置默认值(否则图表不显示)
|
||||||
let bubbleData = lodash.cloneDeep(this.bubbleData)
|
let bubbleData = lodash.cloneDeep(this.bubbleData)
|
||||||
if (bubbleData.every(item => item.value == 0)) {
|
if (bubbleData.every(item => item.value == 0)) {
|
||||||
bubbleData = bubbleData.map(item => {
|
bubbleData = bubbleData.map(item => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
value: 100 // 目的是显示气泡 与值大小无关
|
value: 100 // 目的是显示气泡 与值大小无关
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const data = d3.hierarchy({ children: bubbleData })
|
const data = d3.hierarchy({ children: bubbleData })
|
||||||
.sum(function (d) {
|
.sum(function (d) {
|
||||||
return d.value || 0
|
return d.value || 0
|
||||||
})
|
})
|
||||||
.sort(function (a, b) {
|
.sort(function (a, b) {
|
||||||
return b.value - a.value
|
return b.value - a.value
|
||||||
})
|
})
|
||||||
const nodes = pack(data).descendants()
|
const nodes = pack(data).descendants()
|
||||||
const bubbles = this.svg.selectAll('.bubble')
|
const bubbles = this.svg.selectAll('.bubble')
|
||||||
.data(nodes)
|
.data(nodes)
|
||||||
.enter()
|
.enter()
|
||||||
.filter(function (d) {
|
.filter(function (d) {
|
||||||
return !d.children
|
return !d.children
|
||||||
})
|
})
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('class', 'bubble')
|
.attr('class', 'bubble')
|
||||||
|
|
||||||
bubbles.append('circle')
|
bubbles.append('circle')
|
||||||
.style('fill', function (d) {
|
.style('fill', function (d) {
|
||||||
return d.data.background
|
return d.data.background
|
||||||
})
|
})
|
||||||
.attr('cx', function (d) {
|
.attr('cx', function (d) {
|
||||||
return d.x
|
return d.x
|
||||||
})
|
})
|
||||||
.attr('cy', function (d) {
|
.attr('cy', function (d) {
|
||||||
return d.y
|
return d.y
|
||||||
})
|
})
|
||||||
.attr('r', function (d) {
|
.attr('r', function (d) {
|
||||||
return d.r
|
return d.r
|
||||||
})
|
})
|
||||||
bubbles.append('foreignObject')
|
bubbles.append('foreignObject')
|
||||||
.attr('width', function (d) {
|
.attr('width', function (d) {
|
||||||
return d.r * 2
|
return d.r * 2
|
||||||
})
|
})
|
||||||
.attr('height', function (d) {
|
.attr('height', function (d) {
|
||||||
return d.r * 2
|
return d.r * 2
|
||||||
})
|
})
|
||||||
.attr('x', function (d) {
|
.attr('x', function (d) {
|
||||||
return d.x - d.r
|
return d.x - d.r
|
||||||
})
|
})
|
||||||
.attr('y', function (d) {
|
.attr('y', function (d) {
|
||||||
return d.y - d.r
|
return d.y - d.r
|
||||||
})
|
})
|
||||||
.style('font-size', function (d) {
|
.style('font-size', function (d) {
|
||||||
let fontSize
|
let fontSize
|
||||||
fontSize = d.r / 4 > 10 ? d.r / 4 : 0
|
fontSize = d.r / 4 > 10 ? d.r / 4 : 0
|
||||||
fontSize = fontSize > 30 ? 30 : fontSize
|
fontSize = fontSize > 30 ? 30 : fontSize
|
||||||
return fontSize
|
return fontSize
|
||||||
})
|
})
|
||||||
.style('border-radius', '50%')
|
.style('border-radius', '50%')
|
||||||
.html((d) => {
|
.html((d) => {
|
||||||
return this.bubbleFormatterLabel(d)
|
return this.bubbleFormatterLabel(d)
|
||||||
})
|
})
|
||||||
bubbles.on('mouseenter', this.bubbleEnter)
|
bubbles.on('mouseenter', this.bubbleEnter)
|
||||||
bubbles.on('mousemove', this.bubbleMove)
|
bubbles.on('mousemove', this.bubbleMove)
|
||||||
bubbles.on('mouseleave', this.bubbleLeave)
|
bubbles.on('mouseleave', this.bubbleLeave)
|
||||||
})
|
|
||||||
},
|
},
|
||||||
// 处理label
|
// 处理label
|
||||||
bubbleFormatterLabel (node) {
|
bubbleFormatterLabel (node) {
|
||||||
|
|||||||
365
nezha-fronted/src/components/chart/chart/chartDoughnut.vue
Normal file
365
nezha-fronted/src/components/chart/chart/chartDoughnut.vue
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="legendPlacement"
|
||||||
|
ref="doughnut-chart-box"
|
||||||
|
class="nz-chart__component nz-chart__component--time-series"
|
||||||
|
>
|
||||||
|
<div :id="`chart-canvas-${chartId}`" class="chart__canvas"></div>
|
||||||
|
<chart-legend
|
||||||
|
v-if="hasLegend"
|
||||||
|
:chart-data="chartData"
|
||||||
|
:chart-info="chartInfo"
|
||||||
|
:legends="legends"
|
||||||
|
:is-fullscreen="isFullscreen"
|
||||||
|
@clickLegendDoughnut="clickLegendDoughnut"
|
||||||
|
></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-doughnut',
|
||||||
|
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: '',
|
||||||
|
doughnutData: [],
|
||||||
|
selectData: [],
|
||||||
|
tooltip: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
title: 0,
|
||||||
|
value: 0,
|
||||||
|
mapping: {},
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
svg: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initChart (animate) {
|
||||||
|
this.legends = []
|
||||||
|
this.initDoughnutData(this.chartInfo, this.chartData)
|
||||||
|
this.selectData = this.$loadsh.cloneDeep(this.doughnutData)
|
||||||
|
if (this.isNoData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/* 使用setTimeout延迟渲染图表,避免样式错乱 */
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawDoughnutChart(animate)
|
||||||
|
this.isInit = false
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
initDoughnutData (chartInfo, originalDatas) {
|
||||||
|
this.doughnutData = []
|
||||||
|
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.doughnutData.push({
|
||||||
|
value: 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)
|
||||||
|
},
|
||||||
|
drawDoughnutChart (animate) {
|
||||||
|
this.dispose()
|
||||||
|
const svgDom = document.getElementById(`chart-canvas-${this.chartId}`)
|
||||||
|
if (!svgDom) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果数据全为0 则设置默认值(否则图表不显示)
|
||||||
|
let doughnutData = lodash.cloneDeep(this.selectData)
|
||||||
|
if (doughnutData.every(item => item.value == 0)) {
|
||||||
|
doughnutData = doughnutData.map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = svgDom.getBoundingClientRect().width
|
||||||
|
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')
|
||||||
|
const chart = this.svg.append('g')
|
||||||
|
|
||||||
|
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 padAngle = 0 // angular separation between wedges
|
||||||
|
const arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius)
|
||||||
|
const pie = d3.pie()
|
||||||
|
.padAngle(padAngle)
|
||||||
|
.sort(null)
|
||||||
|
.value(d => d.value)
|
||||||
|
const arcs = pie(doughnutData)
|
||||||
|
|
||||||
|
function doughnutOver (e, d) {
|
||||||
|
d3.select(chart.selectAll('path').nodes()[d.index])
|
||||||
|
.transition()
|
||||||
|
.attr('d', d3.arc()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(outerRadius * 1.05)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function doughnutOut (e, d) {
|
||||||
|
d3.select(chart.selectAll('path').nodes()[d.index])
|
||||||
|
.transition()
|
||||||
|
.attr('d', d3.arc()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(outerRadius)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const that = this
|
||||||
|
// 图形
|
||||||
|
chart.selectAll('path')
|
||||||
|
.data(arcs)
|
||||||
|
.join('path')
|
||||||
|
.attr('fill', d => d.data.background)
|
||||||
|
.attr('d', arc)
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.classed('no-events', true)
|
||||||
|
.on('mouseover', doughnutOver)
|
||||||
|
.on('mouseout', doughnutOut)
|
||||||
|
.on('mouseenter', that.doughnutEnter)
|
||||||
|
.on('mousemove', that.doughnutMove)
|
||||||
|
.on('mouseleave', that.doughnutLeave)
|
||||||
|
.transition().duration(animate === true ? 600 : 0)
|
||||||
|
.attrTween('d', (d) => {
|
||||||
|
let currentArc = this._current
|
||||||
|
if (!currentArc) {
|
||||||
|
currentArc = { startAngle: 0, endAngle: 0 }
|
||||||
|
}
|
||||||
|
const i = d3.interpolate(currentArc, d)
|
||||||
|
this._current = i(0) // 当饼图更新时,从当前角度过渡到新角度
|
||||||
|
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', doughnutOver)
|
||||||
|
.on('mouseout', doughnutOut)
|
||||||
|
.on('mouseenter', this.doughnutEnter)
|
||||||
|
.on('mousemove', this.doughnutMove)
|
||||||
|
.on('mouseleave', this.doughnutLeave)
|
||||||
|
.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="doughnut-label" style="color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
|
<span>${str}</span>
|
||||||
|
</p>
|
||||||
|
<p class="doughnut-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="doughnut-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="doughnut-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>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doughnutEnter (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)
|
||||||
|
},
|
||||||
|
doughnutMove (e) { // 气泡内移动
|
||||||
|
if (this.tooltip.show) {
|
||||||
|
this.tooltip.show = true
|
||||||
|
this.setPosition(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doughnutLeave () { // 移出气泡
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickLegendDoughnut (isGrey) {
|
||||||
|
const data = this.doughnutData.filter((item, i) => !isGrey[i])
|
||||||
|
this.selectData = this.$loadsh.cloneDeep(data)
|
||||||
|
this.drawDoughnutChart(true)
|
||||||
|
},
|
||||||
|
resize () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawDoughnutChart(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>
|
||||||
@@ -122,119 +122,115 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
funnelData = funnelData.filter(item => item.value !== 0)
|
funnelData = funnelData.filter(item => item.value !== 0)
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
this.dispose()
|
||||||
this.dispose()
|
// 获取svg宽高 初始化画布
|
||||||
// 获取svg宽高 初始化画布
|
const svgDom = document.getElementById(`funnel-svg-${this.chartId}`)
|
||||||
const svgDom = document.getElementById(`funnel-svg-${this.chartId}`)
|
if (!svgDom) {
|
||||||
if (!svgDom) {
|
return false
|
||||||
return false
|
}
|
||||||
|
const width = svgDom.getBoundingClientRect().width
|
||||||
|
const height = svgDom.getBoundingClientRect().height
|
||||||
|
const margin = 20
|
||||||
|
const chartWidth = width - margin * 2
|
||||||
|
const chartHeight = height - margin * 2
|
||||||
|
|
||||||
|
// 每个块的高度
|
||||||
|
const trapezoidPadding = 2
|
||||||
|
const trapezoidsHeight = (chartHeight - trapezoidPadding * (funnelData.length - 1)) / funnelData.length
|
||||||
|
|
||||||
|
const scale = d3.scaleLinear()
|
||||||
|
.domain([0, d3.max(funnelData, (d) => d.value)])
|
||||||
|
.range([0, chartWidth * 0.8])
|
||||||
|
|
||||||
|
// 数据处理
|
||||||
|
funnelData = funnelData.map((d, i, array) => {
|
||||||
|
d.index = i
|
||||||
|
if (i !== array.length - 1) {
|
||||||
|
d.nextValue = array[i + 1].value
|
||||||
|
} else {
|
||||||
|
d.nextValue = 0
|
||||||
}
|
}
|
||||||
const width = svgDom.getBoundingClientRect().width
|
return d
|
||||||
const height = svgDom.getBoundingClientRect().height
|
}
|
||||||
const margin = 20
|
)
|
||||||
const chartWidth = width - margin * 2
|
this.svg = d3.select(`#funnel-svg-${this.chartId}`)
|
||||||
const chartHeight = height - margin * 2
|
const chart = this.svg.append('g').attr('width', chartWidth).attr('height', chartHeight).attr('transform', `translate(${margin}, ${margin})`)
|
||||||
|
|
||||||
// 每个块的高度
|
// 渲染梯形
|
||||||
const trapezoidPadding = 2
|
const trapezoids = chart
|
||||||
const trapezoidsHeight = (chartHeight - trapezoidPadding * (funnelData.length - 1)) / funnelData.length
|
.append('g')
|
||||||
|
.attr('class', 'traps')
|
||||||
|
.attr('transform', 'translate(' + chartWidth / 2 + ',0)')
|
||||||
|
.selectAll('.trap')
|
||||||
|
.data(funnelData)
|
||||||
|
trapezoids.enter()
|
||||||
|
.append('polygon')
|
||||||
|
.attr('class', (d) => 'trap trap-' + d.index)
|
||||||
|
.merge(trapezoids)
|
||||||
|
.attr('points', (d) => getPoints(scale(d.value), scale(d.nextValue), trapezoidsHeight))
|
||||||
|
.attr('transform', (d, i) => 'translate(0,' + i * (trapezoidPadding + trapezoidsHeight) + ')')
|
||||||
|
.attr('fill', (d) => d.background)
|
||||||
|
.style('opacity', 0)
|
||||||
|
.transition('opacity').duration(animate === true ? 600 : 0)
|
||||||
|
.style('opacity', 1)
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
trapezoids.exit()
|
||||||
|
.remove()
|
||||||
|
|
||||||
const scale = d3.scaleLinear()
|
// 绑定交互事件
|
||||||
.domain([0, d3.max(funnelData, (d) => d.value)])
|
chart.selectAll('.trap')
|
||||||
.range([0, chartWidth * 0.8])
|
.on('mouseover', (e, d) => {
|
||||||
|
// 划过变色
|
||||||
|
d3.select(e.target).transition('fill').attr('fill', this.shade(d.background, 0.2))
|
||||||
|
this.chartEnter(e, d)
|
||||||
|
})
|
||||||
|
.on('mousemove', this.chartMove)
|
||||||
|
.on('mouseleave', (e, d) => {
|
||||||
|
d3.select(e.target).transition('fill').attr('fill', d.background)
|
||||||
|
this.chartLeave()
|
||||||
|
})
|
||||||
|
|
||||||
// 数据处理
|
function getPoints (topWidth, bottomWidth, height) {
|
||||||
funnelData = funnelData.map((d, i, array) => {
|
const points = []
|
||||||
d.index = i
|
points.push(-topWidth / 2 + ',' + 0)
|
||||||
if (i !== array.length - 1) {
|
points.push(topWidth / 2 + ',' + 0)
|
||||||
d.nextValue = array[i + 1].value
|
if (bottomWidth === 0) {
|
||||||
} else {
|
points.push(0 + ',' + height)
|
||||||
d.nextValue = 0
|
} else {
|
||||||
}
|
points.push(bottomWidth / 2 + ',' + height)
|
||||||
return d
|
points.push(-bottomWidth / 2 + ',' + height)
|
||||||
}
|
}
|
||||||
)
|
return points.join(' ')
|
||||||
this.svg = d3.select(`#funnel-svg-${this.chartId}`)
|
}
|
||||||
const chart = this.svg.append('g').attr('width', chartWidth).attr('height', chartHeight).attr('transform', `translate(${margin}, ${margin})`)
|
|
||||||
|
|
||||||
// 渲染梯形
|
// 渲染文本标签
|
||||||
const trapezoids = chart
|
const texts = chart.select('.traps')
|
||||||
.append('g')
|
.selectAll('.foreign')
|
||||||
.attr('class', 'traps')
|
.data(funnelData)
|
||||||
.attr('transform', 'translate(' + chartWidth / 2 + ',0)')
|
texts.enter()
|
||||||
.selectAll('.trap')
|
.append('foreignObject')
|
||||||
.data(funnelData)
|
.attr('class', 'foreign')
|
||||||
trapezoids.enter()
|
.merge(texts)
|
||||||
.append('polygon')
|
.html((d) => this.formatterLabel(d, trapezoidsHeight))
|
||||||
.attr('class', (d) => 'trap trap-' + d.index)
|
.attr('x', 0)
|
||||||
.merge(trapezoids)
|
.attr('y', (d, i) => i * (trapezoidPadding + trapezoidsHeight) + trapezoidsHeight / 2)
|
||||||
.attr('points', (d) => getPoints(scale(d.value), scale(d.nextValue), trapezoidsHeight))
|
// 绑定交互事件
|
||||||
.attr('transform', (d, i) => 'translate(0,' + i * (trapezoidPadding + trapezoidsHeight) + ')')
|
.on('mouseover', (e, d) => {
|
||||||
.attr('fill', (d) => d.background)
|
chart.select('.trap-' + d.index).transition('fill').attr('fill', this.shade(d.background, 0.2))
|
||||||
.style('opacity', 0)
|
this.chartEnter(e, d)
|
||||||
.transition('opacity').duration(animate === true || this.isInit ? 600 : 0)
|
})
|
||||||
.style('opacity', 1)
|
.on('mousemove', this.chartMove)
|
||||||
.style('cursor', 'pointer')
|
.on('mouseleave', (e, d) => {
|
||||||
trapezoids.exit()
|
chart.select('.trap-' + d.index).transition('fill').attr('fill', d.background)
|
||||||
.remove()
|
this.chartLeave()
|
||||||
|
})
|
||||||
|
.style('opacity', 0)
|
||||||
|
.transition('opacity').duration(animate === true ? 600 : 0)
|
||||||
|
.style('opacity', 1)
|
||||||
|
texts.exit()
|
||||||
|
.remove()
|
||||||
|
|
||||||
// 绑定交互事件
|
this.isInit = false
|
||||||
chart.selectAll('.trap')
|
|
||||||
.on('mouseover', (e, d) => {
|
|
||||||
// 划过变色
|
|
||||||
d3.select(e.target).transition('fill').attr('fill', this.shade(d.background, 0.2))
|
|
||||||
this.chartEnter(e, d)
|
|
||||||
})
|
|
||||||
.on('mousemove', this.chartMove)
|
|
||||||
.on('mouseleave', (e, d) => {
|
|
||||||
d3.select(e.target).transition('fill').attr('fill', d.background)
|
|
||||||
this.chartLeave()
|
|
||||||
})
|
|
||||||
|
|
||||||
function getPoints (topWidth, bottomWidth, height) {
|
|
||||||
const points = []
|
|
||||||
points.push(-topWidth / 2 + ',' + 0)
|
|
||||||
points.push(topWidth / 2 + ',' + 0)
|
|
||||||
if (bottomWidth === 0) {
|
|
||||||
points.push(0 + ',' + height)
|
|
||||||
} else {
|
|
||||||
points.push(bottomWidth / 2 + ',' + height)
|
|
||||||
points.push(-bottomWidth / 2 + ',' + height)
|
|
||||||
}
|
|
||||||
return points.join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染文本标签
|
|
||||||
const texts = chart.select('.traps')
|
|
||||||
.selectAll('.label')
|
|
||||||
.data(funnelData)
|
|
||||||
texts.enter()
|
|
||||||
.append('foreignObject')
|
|
||||||
.attr('class', 'label')
|
|
||||||
.merge(texts)
|
|
||||||
.html((d) => this.formatterLabel(d, trapezoidsHeight))
|
|
||||||
.attr('x', 0)
|
|
||||||
.attr('y', (d, i) => i * (trapezoidPadding + trapezoidsHeight) + trapezoidsHeight / 2)
|
|
||||||
// 绑定交互事件
|
|
||||||
.on('mouseover', (e, d) => {
|
|
||||||
chart.select('.trap-' + d.index).transition('fill').attr('fill', this.shade(d.background, 0.2))
|
|
||||||
this.chartEnter(e, d)
|
|
||||||
})
|
|
||||||
.on('mousemove', this.chartMove)
|
|
||||||
.on('mouseleave', (e, d) => {
|
|
||||||
chart.select('.trap-' + d.index).transition('fill').attr('fill', d.background)
|
|
||||||
this.chartLeave()
|
|
||||||
})
|
|
||||||
.style('overflow', 'visible')
|
|
||||||
.style('position', 'relative')
|
|
||||||
.style('opacity', 0)
|
|
||||||
.transition('opacity').duration(animate === true || this.isInit ? 600 : 0)
|
|
||||||
.style('opacity', 1)
|
|
||||||
texts.exit()
|
|
||||||
.remove()
|
|
||||||
|
|
||||||
this.isInit = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
// 处理label
|
// 处理label
|
||||||
formatterLabel (data, height) {
|
formatterLabel (data, height) {
|
||||||
@@ -257,7 +253,7 @@ export default {
|
|||||||
if (height < 32) {
|
if (height < 32) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return `<div class="funnel-label-wrap">
|
return `<div class="foreign-label-wrap">
|
||||||
<p class="funnel-label" style="color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
<p class="funnel-label" style="color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
||||||
<span>${str}</span>
|
<span>${str}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -271,7 +267,7 @@ export default {
|
|||||||
if (height < 16) {
|
if (height < 16) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return `<div class="funnel-label-wrap">
|
return `<div class="foreign-label-wrap">
|
||||||
<p class="funnel-label" style="color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
<p class="funnel-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>
|
<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>
|
<span>${str}</span>
|
||||||
@@ -282,7 +278,7 @@ export default {
|
|||||||
if (height < 16) {
|
if (height < 16) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return `<div class="funnel-label-wrap">
|
return `<div class="foreign-label-wrap">
|
||||||
<p class="funnel-label" style="color: ${data.mapping && data.mapping.color && data.mapping.color.text};">
|
<p class="funnel-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>
|
<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>
|
<span>${valueStr}</span>
|
||||||
|
|||||||
@@ -115,114 +115,112 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
drawRankChart () {
|
drawRankChart () {
|
||||||
this.$nextTick(() => {
|
this.dispose()
|
||||||
this.dispose()
|
// 获取svg宽高 初始化画布
|
||||||
// 获取svg宽高 初始化画布
|
const svgDom = document.getElementById(`rank-svg-${this.chartId}`)
|
||||||
const svgDom = document.getElementById(`rank-svg-${this.chartId}`)
|
if (!svgDom) {
|
||||||
if (!svgDom) {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
const width = svgDom.getBoundingClientRect().width
|
||||||
const width = svgDom.getBoundingClientRect().width
|
// 柱子高度
|
||||||
// 柱子高度
|
const barHeight = 24
|
||||||
const barHeight = 24
|
// 柱子间隔
|
||||||
// 柱子间隔
|
const margin = 40
|
||||||
const margin = 40
|
// 计算svg高度
|
||||||
// 计算svg高度
|
const height = this.rankData.length * (barHeight + margin) + margin
|
||||||
const height = this.rankData.length * (barHeight + margin) + margin
|
this.svg = d3.select(`#rank-svg-${this.chartId}`).attr('height', height)
|
||||||
this.svg = d3.select(`#rank-svg-${this.chartId}`).attr('height', height)
|
const bodyX = 50
|
||||||
const bodyX = 50
|
const bodyWidth = width - 3 * bodyX
|
||||||
const bodyWidth = width - 3 * bodyX
|
|
||||||
|
|
||||||
// 从大到小排序
|
// 从大到小排序
|
||||||
let rankData = lodash.cloneDeep(this.rankData)
|
let rankData = lodash.cloneDeep(this.rankData)
|
||||||
rankData.sort((a, b) => b.value - a.value)
|
rankData.sort((a, b) => b.value - a.value)
|
||||||
rankData = rankData.map((item, index) => {
|
rankData = rankData.map((item, index) => {
|
||||||
return {
|
return {
|
||||||
rank: index,
|
rank: index,
|
||||||
background: item.mapping ? item.mapping.color.bac : this.colorList[index],
|
background: item.mapping ? item.mapping.color.bac : this.colorList[index],
|
||||||
...item
|
...item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 尺度转换
|
||||||
|
const scaleX = d3.scaleLinear()
|
||||||
|
.domain([0, d3.max(rankData, (d) => parseFloat(d.value))])
|
||||||
|
.range([0, bodyWidth])
|
||||||
|
|
||||||
|
// 柱子最小宽度
|
||||||
|
const minWidth = 2
|
||||||
|
// 渲染柱形
|
||||||
|
const bars = this.svg.append('g')
|
||||||
|
.attr('transform', `translate(${bodyX})`)
|
||||||
|
.selectAll()
|
||||||
|
.data(rankData)
|
||||||
|
bars.enter()
|
||||||
|
.append('rect')
|
||||||
|
.merge(bars)
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr('y', (d) => {
|
||||||
|
return (d.rank * barHeight) + (d.rank + 1) * margin
|
||||||
|
})
|
||||||
|
.attr('height', barHeight)
|
||||||
|
.attr('fill', (d) => d.background)
|
||||||
|
.attr('width', (d) => {
|
||||||
|
return scaleX(d.value) > minWidth ? scaleX(d.value) : minWidth
|
||||||
|
})
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.on('mouseenter', this.rankEnter)
|
||||||
|
.on('mousemove', this.rankMove)
|
||||||
|
.on('mouseleave', this.rankLeave)
|
||||||
|
bars.exit().remove()
|
||||||
|
|
||||||
|
// 文本标签
|
||||||
|
this.svg.append('g')
|
||||||
|
.attr('transform', `translate(${bodyX})`)
|
||||||
|
.selectAll()
|
||||||
|
.data(rankData)
|
||||||
|
.enter()
|
||||||
|
.append('foreignObject')
|
||||||
|
.on('mouseenter', this.rankEnter)
|
||||||
|
.on('mousemove', this.rankMove)
|
||||||
|
.on('mouseleave', this.rankLeave)
|
||||||
|
.html((d) => {
|
||||||
|
return this.rankFormatterLabel(d)
|
||||||
|
})
|
||||||
|
.attr('x', (d) => {
|
||||||
|
let x = 0
|
||||||
|
if (scaleX(d.value) > minWidth) {
|
||||||
|
x = scaleX(d.value) + 10
|
||||||
|
} else {
|
||||||
|
x = minWidth + 10
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
})
|
||||||
|
.attr('y', (d) => {
|
||||||
|
return (d.rank * barHeight) + (d.rank + 1) * margin
|
||||||
|
})
|
||||||
|
.attr('height', barHeight)
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.style('overflow', 'visible')
|
||||||
|
|
||||||
|
// 生成标签和矩形
|
||||||
|
this.svg.append('g')
|
||||||
|
.attr('transform', `translate(${bodyX})`)
|
||||||
|
.selectAll('text')
|
||||||
|
.data(rankData)
|
||||||
|
.enter()
|
||||||
|
.append('text')
|
||||||
|
.attr('class', 'chart-label-text')
|
||||||
|
.attr('x', 4)
|
||||||
|
.attr('y', d => {
|
||||||
|
return (d.rank * barHeight) + (d.rank + 1) * margin - 12
|
||||||
|
})
|
||||||
|
.text(d => {
|
||||||
|
if (this.chartInfo.param.text !== 'all' && this.chartInfo.param.text !== 'legend') {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return d.alias
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 尺度转换
|
|
||||||
const scaleX = d3.scaleLinear()
|
|
||||||
.domain([0, d3.max(rankData, (d) => parseFloat(d.value))])
|
|
||||||
.range([0, bodyWidth])
|
|
||||||
|
|
||||||
// 柱子最小宽度
|
|
||||||
const minWidth = 2
|
|
||||||
// 渲染柱形
|
|
||||||
const bars = this.svg.append('g')
|
|
||||||
.attr('transform', `translate(${bodyX})`)
|
|
||||||
.selectAll()
|
|
||||||
.data(rankData)
|
|
||||||
bars.enter()
|
|
||||||
.append('rect')
|
|
||||||
.merge(bars)
|
|
||||||
.attr('x', 0)
|
|
||||||
.attr('y', (d) => {
|
|
||||||
return (d.rank * barHeight) + (d.rank + 1) * margin
|
|
||||||
})
|
|
||||||
.attr('height', barHeight)
|
|
||||||
.attr('fill', (d) => d.background)
|
|
||||||
.attr('width', (d) => {
|
|
||||||
return scaleX(d.value) > minWidth ? scaleX(d.value) : minWidth
|
|
||||||
})
|
|
||||||
.style('cursor', 'pointer')
|
|
||||||
.on('mouseenter', this.rankEnter)
|
|
||||||
.on('mousemove', this.rankMove)
|
|
||||||
.on('mouseleave', this.rankLeave)
|
|
||||||
bars.exit().remove()
|
|
||||||
|
|
||||||
// 文本标签
|
|
||||||
this.svg.append('g')
|
|
||||||
.attr('transform', `translate(${bodyX})`)
|
|
||||||
.selectAll()
|
|
||||||
.data(rankData)
|
|
||||||
.enter()
|
|
||||||
.append('foreignObject')
|
|
||||||
.on('mouseenter', this.rankEnter)
|
|
||||||
.on('mousemove', this.rankMove)
|
|
||||||
.on('mouseleave', this.rankLeave)
|
|
||||||
.html((d) => {
|
|
||||||
return this.rankFormatterLabel(d)
|
|
||||||
})
|
|
||||||
.attr('x', (d) => {
|
|
||||||
let x = 0
|
|
||||||
if (scaleX(d.value) > minWidth) {
|
|
||||||
x = scaleX(d.value) + 10
|
|
||||||
} else {
|
|
||||||
x = minWidth + 10
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
})
|
|
||||||
.attr('y', (d) => {
|
|
||||||
return (d.rank * barHeight) + (d.rank + 1) * margin
|
|
||||||
})
|
|
||||||
.attr('height', barHeight)
|
|
||||||
.style('cursor', 'pointer')
|
|
||||||
.style('overflow', 'visible')
|
|
||||||
|
|
||||||
// 生成标签和矩形
|
|
||||||
this.svg.append('g')
|
|
||||||
.attr('transform', `translate(${bodyX})`)
|
|
||||||
.selectAll('text')
|
|
||||||
.data(rankData)
|
|
||||||
.enter()
|
|
||||||
.append('text')
|
|
||||||
.attr('class', 'chart-label-text')
|
|
||||||
.attr('x', 4)
|
|
||||||
.attr('y', d => {
|
|
||||||
return (d.rank * barHeight) + (d.rank + 1) * margin - 12
|
|
||||||
})
|
|
||||||
.text(d => {
|
|
||||||
if (this.chartInfo.param.text !== 'all' && this.chartInfo.param.text !== 'legend') {
|
|
||||||
return ''
|
|
||||||
} else {
|
|
||||||
return d.alias
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 处理label
|
// 处理label
|
||||||
|
|||||||
@@ -136,181 +136,179 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
drawSankeyChart () {
|
drawSankeyChart () {
|
||||||
this.$nextTick(() => {
|
this.dispose()
|
||||||
this.dispose()
|
// 获取svg宽高 初始化画布
|
||||||
// 获取svg宽高 初始化画布
|
const svgDom = document.getElementById(`sankey-svg-${this.chartId}`)
|
||||||
const svgDom = document.getElementById(`sankey-svg-${this.chartId}`)
|
if (!svgDom) {
|
||||||
if (!svgDom) {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
const width = svgDom.getBoundingClientRect().width
|
||||||
const width = svgDom.getBoundingClientRect().width
|
const height = svgDom.getBoundingClientRect().height
|
||||||
const height = svgDom.getBoundingClientRect().height
|
const margin1 = 100
|
||||||
const margin1 = 100
|
const margin2 = 50
|
||||||
const margin2 = 50
|
this.svg = d3.select(`#sankey-svg-${this.chartId}`)
|
||||||
this.svg = d3.select(`#sankey-svg-${this.chartId}`)
|
const chart = this.svg.append('g').attr('transform', `translate(${margin2}, ${margin2})`)
|
||||||
const chart = this.svg.append('g').attr('transform', `translate(${margin2}, ${margin2})`)
|
|
||||||
|
|
||||||
// 创建桑基图生成器
|
// 创建桑基图生成器
|
||||||
const sankey = d3Sankey
|
const sankey = d3Sankey
|
||||||
.sankey()
|
.sankey()
|
||||||
.nodeWidth(20)
|
.nodeWidth(20)
|
||||||
.nodePadding(20)
|
.nodePadding(20)
|
||||||
.size([width - 2 * margin1, height - 2 * margin2])
|
.size([width - 2 * margin1, height - 2 * margin2])
|
||||||
.nodeId((d) => d.node)
|
.nodeId((d) => d.node)
|
||||||
const nodesData = lodash.cloneDeep(this.nodesData)
|
const nodesData = lodash.cloneDeep(this.nodesData)
|
||||||
const linksData = lodash.cloneDeep(this.linksData)
|
const linksData = lodash.cloneDeep(this.linksData)
|
||||||
|
|
||||||
// 判断数据是否全部为0
|
// 判断数据是否全部为0
|
||||||
let allZero = false
|
let allZero = false
|
||||||
if (linksData.every(item => item.value == 0)) {
|
if (linksData.every(item => item.value == 0)) {
|
||||||
linksData.forEach(item => {
|
linksData.forEach(item => {
|
||||||
item.value = 100 // 目的是显示图表 与值大小无关
|
item.value = 100 // 目的是显示图表 与值大小无关
|
||||||
})
|
|
||||||
allZero = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const { nodes, links } = sankey({
|
|
||||||
nodes: nodesData,
|
|
||||||
links: linksData
|
|
||||||
})
|
})
|
||||||
|
allZero = true
|
||||||
|
}
|
||||||
|
|
||||||
// 设置节点颜色
|
const { nodes, links } = sankey({
|
||||||
nodes.forEach((item, index) => {
|
nodes: nodesData,
|
||||||
if (index >= 20) {
|
links: linksData
|
||||||
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(!allZero ? item.value : 0, 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) => {
|
|
||||||
return d.source.background
|
|
||||||
})
|
|
||||||
.attr('stroke-width', (d) => d.width || 1)
|
|
||||||
.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 || 2)
|
|
||||||
.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
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 设置节点颜色
|
||||||
|
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(!allZero ? item.value : 0, 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) => {
|
||||||
|
return d.source.background
|
||||||
|
})
|
||||||
|
.attr('stroke-width', (d) => d.width || 1)
|
||||||
|
.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 || 2)
|
||||||
|
.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) {
|
setPosition (e) {
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
class Colorizer {
|
|
||||||
/**
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
constructor () {
|
|
||||||
this.hexExpression = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i
|
|
||||||
this.instanceId = null
|
|
||||||
this.labelFill = null
|
|
||||||
this.scale = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} instanceId
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
setInstanceId (instanceId) {
|
|
||||||
this.instanceId = instanceId
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} fill
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
setLabelFill (fill) {
|
|
||||||
this.labelFill = fill
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {function|Array} scale
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
setScale (scale) {
|
|
||||||
this.scale = scale
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a raw data block, return an appropriate color for the block.
|
|
||||||
*
|
|
||||||
* @param {string} fill
|
|
||||||
* @param {Number} index
|
|
||||||
* @param {string} fillType
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
getBlockFill (fill, index, fillType) {
|
|
||||||
const raw = this.getBlockRawFill(fill, index)
|
|
||||||
|
|
||||||
return {
|
|
||||||
raw,
|
|
||||||
actual: this.getBlockActualFill(raw, index, fillType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the raw hex color for the block.
|
|
||||||
*
|
|
||||||
* @param {string} fill
|
|
||||||
* @param {Number} index
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getBlockRawFill (fill, index) {
|
|
||||||
// Use the block's color, if set and valid
|
|
||||||
if (this.hexExpression.test(fill)) {
|
|
||||||
return fill
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, attempt to use the array scale
|
|
||||||
if (Array.isArray(this.scale)) {
|
|
||||||
return this.scale[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, use a functional scale
|
|
||||||
return this.scale(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the actual background for the block.
|
|
||||||
*
|
|
||||||
* @param {string} raw
|
|
||||||
* @param {Number} index
|
|
||||||
* @param {string} fillType
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getBlockActualFill (raw, index, fillType) {
|
|
||||||
if (fillType === 'solid') {
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
|
|
||||||
return `url(#${this.getGradientId(index)})`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the gradient ID for the given index.
|
|
||||||
*
|
|
||||||
* @param {Number} index
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getGradientId (index) {
|
|
||||||
return `${this.instanceId}-gradient-${index}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a raw data block, return an appropriate label color.
|
|
||||||
*
|
|
||||||
* @param {string} labelFill
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getLabelColor (labelFill) {
|
|
||||||
return this.hexExpression.test(labelFill) ? labelFill : this.labelFill
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shade a color to the given percentage.
|
|
||||||
*
|
|
||||||
* @param {string} color A hex color.
|
|
||||||
* @param {number} shade The shade adjustment. Can be positive or negative.
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
shade (color, shade) {
|
|
||||||
const { R, G, B } = this.hexToRgb(color)
|
|
||||||
const t = shade < 0 ? 0 : 255
|
|
||||||
const p = shade < 0 ? shade * -1 : shade
|
|
||||||
|
|
||||||
const converted = 0x1000000 +
|
|
||||||
((Math.round((t - R) * p) + R) * 0x10000) +
|
|
||||||
((Math.round((t - G) * p) + G) * 0x100) +
|
|
||||||
(Math.round((t - B) * p) + B)
|
|
||||||
|
|
||||||
return `#${converted.toString(16).slice(1)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a hex color to an RGB object.
|
|
||||||
*
|
|
||||||
* @param {string} color
|
|
||||||
*
|
|
||||||
* @returns {{R: Number, G: number, B: number}}
|
|
||||||
*/
|
|
||||||
hexToRgb (color) {
|
|
||||||
let hex = color.slice(1)
|
|
||||||
|
|
||||||
if (hex.length === 3) {
|
|
||||||
hex = this.expandHex(hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
const f = parseInt(hex, 16)
|
|
||||||
|
|
||||||
/* eslint-disable no-bitwise */
|
|
||||||
const R = f >> 16
|
|
||||||
const G = (f >> 8) & 0x00FF
|
|
||||||
const B = f & 0x0000FF
|
|
||||||
/* eslint-enable */
|
|
||||||
|
|
||||||
return { R, G, B }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expands a three character hex code to six characters.
|
|
||||||
*
|
|
||||||
* @param {string} hex
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
expandHex (hex) {
|
|
||||||
return hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Colorizer
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,75 +0,0 @@
|
|||||||
class Formatter {
|
|
||||||
/**
|
|
||||||
* Register the format function.
|
|
||||||
*
|
|
||||||
* @param {string|function} format
|
|
||||||
*
|
|
||||||
* @return {function}
|
|
||||||
*/
|
|
||||||
getFormatter (format) {
|
|
||||||
if (typeof format === 'function') {
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
return (label, value, formattedValue) => (
|
|
||||||
this.stringFormatter(label, value, formattedValue, format)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the given value according to the data point or the format.
|
|
||||||
*
|
|
||||||
* @param {string} label
|
|
||||||
* @param {number} value
|
|
||||||
* @param {*} formattedValue
|
|
||||||
* @param {function} formatter
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
format ({ label, value, formattedValue = null }, formatter) {
|
|
||||||
return formatter(label, value, formattedValue, arguments[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the string according to a simple expression.
|
|
||||||
*
|
|
||||||
* {l}: label
|
|
||||||
* {v}: raw value
|
|
||||||
* {f}: formatted value
|
|
||||||
*
|
|
||||||
* @param {string} label
|
|
||||||
* @param {number} value
|
|
||||||
* @param {*} formattedValue
|
|
||||||
* @param {string} expression
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
stringFormatter (label, value, formattedValue, expression) {
|
|
||||||
let formatted = formattedValue
|
|
||||||
|
|
||||||
// Attempt to use supplied formatted value
|
|
||||||
// Otherwise, use the default
|
|
||||||
if (formattedValue === null) {
|
|
||||||
formatted = this.getDefaultFormattedValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression
|
|
||||||
.split('{l}')
|
|
||||||
.join(label)
|
|
||||||
.split('{v}')
|
|
||||||
.join(value)
|
|
||||||
.split('{f}')
|
|
||||||
.join(formatted)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getDefaultFormattedValue (value) {
|
|
||||||
return value.toLocaleString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Formatter
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
class Navigator {
|
|
||||||
/**
|
|
||||||
* Given a list of path commands, returns the compiled description.
|
|
||||||
*
|
|
||||||
* @param {Array} commands
|
|
||||||
*
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
plot (commands) {
|
|
||||||
let path = ''
|
|
||||||
|
|
||||||
commands.forEach((command) => {
|
|
||||||
path += `${command[0]}${command[1]},${command[2]} `
|
|
||||||
})
|
|
||||||
|
|
||||||
return path.replace(/ +/g, ' ').trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} dimensions
|
|
||||||
* @param {boolean} isValueOverlay
|
|
||||||
*
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
makeCurvedPaths (dimensions, isValueOverlay = false) {
|
|
||||||
const points = this.makeBezierPoints(dimensions)
|
|
||||||
|
|
||||||
if (isValueOverlay) {
|
|
||||||
return this.makeBezierPath(points, dimensions.ratio)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.makeBezierPath(points)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Number} centerX
|
|
||||||
* @param {Number} prevLeftX
|
|
||||||
* @param {Number} prevRightX
|
|
||||||
* @param {Number} prevHeight
|
|
||||||
* @param {Number} nextLeftX
|
|
||||||
* @param {Number} nextRightX
|
|
||||||
* @param {Number} nextHeight
|
|
||||||
* @param {Number} curveHeight
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
makeBezierPoints ({
|
|
||||||
centerX,
|
|
||||||
prevLeftX,
|
|
||||||
prevRightX,
|
|
||||||
prevHeight,
|
|
||||||
nextLeftX,
|
|
||||||
nextRightX,
|
|
||||||
nextHeight,
|
|
||||||
curveHeight
|
|
||||||
}) {
|
|
||||||
return {
|
|
||||||
p00: {
|
|
||||||
x: prevLeftX,
|
|
||||||
y: prevHeight
|
|
||||||
},
|
|
||||||
p01: {
|
|
||||||
x: centerX,
|
|
||||||
y: prevHeight + (curveHeight / 2)
|
|
||||||
},
|
|
||||||
p02: {
|
|
||||||
x: prevRightX,
|
|
||||||
y: prevHeight
|
|
||||||
},
|
|
||||||
|
|
||||||
p10: {
|
|
||||||
x: nextLeftX,
|
|
||||||
y: nextHeight
|
|
||||||
},
|
|
||||||
p11: {
|
|
||||||
x: centerX,
|
|
||||||
y: nextHeight + curveHeight
|
|
||||||
},
|
|
||||||
p12: {
|
|
||||||
x: nextRightX,
|
|
||||||
y: nextHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} p00
|
|
||||||
* @param {Object} p01
|
|
||||||
* @param {Object} p02
|
|
||||||
* @param {Object} p10
|
|
||||||
* @param {Object} p11
|
|
||||||
* @param {Object} p12
|
|
||||||
* @param {Number} ratio
|
|
||||||
*
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
makeBezierPath ({
|
|
||||||
p00,
|
|
||||||
p01,
|
|
||||||
p02,
|
|
||||||
p10,
|
|
||||||
p11,
|
|
||||||
p12
|
|
||||||
}, ratio = 1) {
|
|
||||||
const curve0 = this.getQuadraticBezierCurve(p00, p01, p02, ratio)
|
|
||||||
const curve1 = this.getQuadraticBezierCurve(p10, p11, p12, ratio)
|
|
||||||
|
|
||||||
return [
|
|
||||||
// Top Bezier curve
|
|
||||||
[curve0.p0.x, curve0.p0.y, 'M'],
|
|
||||||
[curve0.p1.x, curve0.p1.y, 'Q'],
|
|
||||||
[curve0.p2.x, curve0.p2.y, ''],
|
|
||||||
// Right line
|
|
||||||
[curve1.p2.x, curve1.p2.y, 'L'],
|
|
||||||
// Bottom Bezier curve
|
|
||||||
[curve1.p2.x, curve1.p2.y, 'M'],
|
|
||||||
[curve1.p1.x, curve1.p1.y, 'Q'],
|
|
||||||
[curve1.p0.x, curve1.p0.y, ''],
|
|
||||||
// Left line
|
|
||||||
[curve0.p0.x, curve0.p0.y, 'L']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} p0
|
|
||||||
* @param {Object} p1
|
|
||||||
* @param {Object} p2
|
|
||||||
* @param {Number} t
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
getQuadraticBezierCurve (p0, p1, p2, t = 1) {
|
|
||||||
// Quadratic Bezier curve syntax: M(P0) Q(P1) P2
|
|
||||||
// Where P0, P2 are the curve endpoints and P1 is the control point
|
|
||||||
|
|
||||||
// More generally, at 0 <= t <= 1, we have the following:
|
|
||||||
// Q0(t), which varies linearly from P0 to P1
|
|
||||||
// Q1(t), which varies linearly from P1 to P2
|
|
||||||
// B(t), which is interpolated linearly between Q0(t) and Q1(t)
|
|
||||||
|
|
||||||
// For an intermediate curve at 0 <= t <= 1:
|
|
||||||
// P1(t) = Q0(t)
|
|
||||||
// P2(t) = B(t)
|
|
||||||
|
|
||||||
return {
|
|
||||||
p0,
|
|
||||||
p1: {
|
|
||||||
x: this.getLinearInterpolation(p0, p1, t, 'x'),
|
|
||||||
y: this.getLinearInterpolation(p0, p1, t, 'y')
|
|
||||||
},
|
|
||||||
p2: {
|
|
||||||
x: this.getQuadraticInterpolation(p0, p1, p2, t, 'x'),
|
|
||||||
y: this.getQuadraticInterpolation(p0, p1, p2, t, 'y')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} p0
|
|
||||||
* @param {Object} p1
|
|
||||||
* @param {Number} t
|
|
||||||
* @param {string} axis
|
|
||||||
*
|
|
||||||
* @return {Number}
|
|
||||||
*/
|
|
||||||
getLinearInterpolation (p0, p1, t, axis) {
|
|
||||||
return p0[axis] + (t * (p1[axis] - p0[axis]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} p0
|
|
||||||
* @param {Object} p1
|
|
||||||
* @param {Object} p2
|
|
||||||
* @param {Number} t
|
|
||||||
* @param {string} axis
|
|
||||||
*
|
|
||||||
* @return {Number}
|
|
||||||
*/
|
|
||||||
getQuadraticInterpolation (p0, p1, p2, t, axis) {
|
|
||||||
return (((1 - t) ** 2) * p0[axis]) +
|
|
||||||
(2 * (1 - t) * t * p1[axis]) +
|
|
||||||
((t ** 2) * p2[axis])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Number} prevLeftX
|
|
||||||
* @param {Number} prevRightX
|
|
||||||
* @param {Number} prevHeight
|
|
||||||
* @param {Number} nextLeftX
|
|
||||||
* @param {Number} nextRightX
|
|
||||||
* @param {Number} nextHeight
|
|
||||||
* @param {Number} ratio
|
|
||||||
* @param {boolean} isValueOverlay
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
makeStraightPaths ({
|
|
||||||
prevLeftX,
|
|
||||||
prevRightX,
|
|
||||||
prevHeight,
|
|
||||||
nextLeftX,
|
|
||||||
nextRightX,
|
|
||||||
nextHeight,
|
|
||||||
ratio
|
|
||||||
}, isValueOverlay = false) {
|
|
||||||
if (isValueOverlay) {
|
|
||||||
const lengthTop = (prevRightX - prevLeftX)
|
|
||||||
const lengthBtm = (nextRightX - nextLeftX)
|
|
||||||
let rightSideTop = (lengthTop * (ratio || 0)) + prevLeftX
|
|
||||||
let rightSideBtm = (lengthBtm * (ratio || 0)) + nextLeftX
|
|
||||||
|
|
||||||
// Overlay should not be longer than the max length of the path
|
|
||||||
rightSideTop = Math.min(rightSideTop, lengthTop)
|
|
||||||
rightSideBtm = Math.min(rightSideBtm, lengthBtm)
|
|
||||||
|
|
||||||
return [
|
|
||||||
// Start position
|
|
||||||
[prevLeftX, prevHeight, 'M'],
|
|
||||||
// Move to right
|
|
||||||
[rightSideTop, prevHeight, 'L'],
|
|
||||||
// Move down
|
|
||||||
[rightSideBtm, nextHeight, 'L'],
|
|
||||||
// Move to left
|
|
||||||
[nextLeftX, nextHeight, 'L'],
|
|
||||||
// Wrap back to top
|
|
||||||
[prevLeftX, prevHeight, 'L']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
// Start position
|
|
||||||
[prevLeftX, prevHeight, 'M'],
|
|
||||||
// Move to right
|
|
||||||
[prevRightX, prevHeight, 'L'],
|
|
||||||
// Move down
|
|
||||||
[nextRightX, nextHeight, 'L'],
|
|
||||||
// Move to left
|
|
||||||
[nextLeftX, nextHeight, 'L'],
|
|
||||||
// Wrap back to top
|
|
||||||
[prevLeftX, prevHeight, 'L']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Navigator
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
class Utils {
|
|
||||||
/**
|
|
||||||
* Determine whether the given parameter is an extendable object.
|
|
||||||
*
|
|
||||||
* @param {*} a
|
|
||||||
*
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
static isExtendableObject (a) {
|
|
||||||
return typeof a === 'object' && a !== null && !Array.isArray(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends an object with the members of another.
|
|
||||||
*
|
|
||||||
* @param {Object} a The object to be extended.
|
|
||||||
* @param {Object} b The object to clone from.
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
static extend (a, b) {
|
|
||||||
let result = {}
|
|
||||||
|
|
||||||
// If a is non-trivial, extend the result with it
|
|
||||||
if (Object.keys(a).length > 0) {
|
|
||||||
result = Utils.extend({}, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy over the properties in b into a
|
|
||||||
Object.keys(b).forEach((prop) => {
|
|
||||||
if (Utils.isExtendableObject(b[prop])) {
|
|
||||||
if (Utils.isExtendableObject(a[prop])) {
|
|
||||||
result[prop] = Utils.extend(a[prop], b[prop])
|
|
||||||
} else {
|
|
||||||
result[prop] = Utils.extend({}, b[prop])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result[prop] = b[prop]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert the legacy block array to a block object.
|
|
||||||
*
|
|
||||||
* @param {Array} block
|
|
||||||
*
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
static convertLegacyBlock (block) {
|
|
||||||
return {
|
|
||||||
label: block[0],
|
|
||||||
value: Utils.getRawBlockCount(block),
|
|
||||||
formattedValue: Array.isArray(block[1]) ? block[1][1] : null,
|
|
||||||
backgroundColor: block[2],
|
|
||||||
labelColor: block[3]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a raw data block, return its count.
|
|
||||||
*
|
|
||||||
* @param {Array} block
|
|
||||||
*
|
|
||||||
* @return {Number}
|
|
||||||
*/
|
|
||||||
static getRawBlockCount (block) {
|
|
||||||
if (Array.isArray(block)) {
|
|
||||||
return Array.isArray(block[1]) ? block[1][0] : block[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return block.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Utils
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// Export default to provide support for non-ES6 solutions
|
|
||||||
module.exports = require('./d3-funnel/D3Funnel').default
|
|
||||||
@@ -139,6 +139,10 @@ export default {
|
|||||||
this.clickLegendTreemap(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
this.clickLegendTreemap(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (this.chartInfo.type === 'doughnut') {
|
||||||
|
this.clickLegendDoughnut(legendName, index, hasGrey, curIsGrey, currentIsTheOnlyOneHighlight)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (echarts) {
|
if (echarts) {
|
||||||
// 判断timeSeries类型图表 先取消多表联动
|
// 判断timeSeries类型图表 先取消多表联动
|
||||||
@@ -228,6 +232,16 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
clickLegendDoughnut (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])
|
||||||
|
}
|
||||||
|
this.$emit('clickLegendDoughnut', this.isGrey)
|
||||||
|
},
|
||||||
// 四舍五入保留2位小数(不够位数,则用0替补)
|
// 四舍五入保留2位小数(不够位数,则用0替补)
|
||||||
keepTwoDecimalFull (num) {
|
keepTwoDecimalFull (num) {
|
||||||
let result = parseFloat(num)
|
let result = parseFloat(num)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const chartPieOption = {
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: '55%',
|
radius: '60%',
|
||||||
center: ['50%', '50%'],
|
center: ['50%', '50%'],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
zlevel: 1,
|
zlevel: 1,
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ export function isHexagon (type) {
|
|||||||
export function isChartPie (type) {
|
export function isChartPie (type) {
|
||||||
return type === chartType.pie
|
return type === chartType.pie
|
||||||
}
|
}
|
||||||
|
export function isDoughnut (type) {
|
||||||
|
return type === chartType.doughnut
|
||||||
|
}
|
||||||
export function isChartBar (type) {
|
export function isChartBar (type) {
|
||||||
return type === chartType.bar
|
return type === chartType.bar
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,6 +285,10 @@ export const chart = {
|
|||||||
value: 'pie',
|
value: 'pie',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'doughnut',
|
||||||
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'table',
|
value: 'table',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.table.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.table.label')
|
||||||
@@ -317,18 +321,10 @@ export const chart = {
|
|||||||
value: 'treemap',
|
value: 'treemap',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.treemap.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.treemap.label')
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: 'pie',
|
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.log.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.log.label')
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: 'table',
|
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.table.label')
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: 'map',
|
value: 'map',
|
||||||
label: i18n.t('dashboard.dashboard.chartForm.typeVal.map.label')
|
label: i18n.t('dashboard.dashboard.chartForm.typeVal.map.label')
|
||||||
@@ -478,6 +474,7 @@ export const chartType = {
|
|||||||
stat: 'stat',
|
stat: 'stat',
|
||||||
gauge: 'gauge',
|
gauge: 'gauge',
|
||||||
pie: 'pie',
|
pie: 'pie',
|
||||||
|
doughnut: 'doughnut',
|
||||||
treemap: 'treemap',
|
treemap: 'treemap',
|
||||||
log: 'log',
|
log: 'log',
|
||||||
text: 'text',
|
text: 'text',
|
||||||
|
|||||||
@@ -1271,7 +1271,8 @@ export default {
|
|||||||
case 'bar':
|
case 'bar':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie') {
|
case 'doughnut':
|
||||||
|
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie' || this.oldType === 'doughnut') {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.chartConfig.param = {
|
this.chartConfig.param = {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
return false
|
return false
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
@@ -72,6 +73,7 @@ export default {
|
|||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
case 'rank':
|
case 'rank':
|
||||||
case 'sankey':
|
case 'sankey':
|
||||||
@@ -87,6 +89,7 @@ export default {
|
|||||||
case 'point':
|
case 'point':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'bar':
|
case 'bar':
|
||||||
return true
|
return true
|
||||||
case 'table':
|
case 'table':
|
||||||
@@ -109,6 +112,7 @@ export default {
|
|||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'bar':
|
case 'bar':
|
||||||
return false
|
return false
|
||||||
default: return false
|
default: return false
|
||||||
@@ -136,6 +140,7 @@ export default {
|
|||||||
case 'gauge':
|
case 'gauge':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'bubble':
|
case 'bubble':
|
||||||
case 'rank':
|
case 'rank':
|
||||||
case 'sankey':
|
case 'sankey':
|
||||||
@@ -150,6 +155,7 @@ export default {
|
|||||||
case 'bar':
|
case 'bar':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
@@ -167,6 +173,7 @@ export default {
|
|||||||
case 'bar':
|
case 'bar':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
@@ -202,6 +209,7 @@ export default {
|
|||||||
case 'bar':
|
case 'bar':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
|
case 'doughnut':
|
||||||
case 'stat':
|
case 'stat':
|
||||||
case 'hexagon':
|
case 'hexagon':
|
||||||
case 'gauge':
|
case 'gauge':
|
||||||
|
|||||||
@@ -247,6 +247,10 @@ export default {
|
|||||||
id: 'pie',
|
id: 'pie',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'doughnut',
|
||||||
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bubble',
|
id: 'bubble',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
||||||
@@ -306,6 +310,10 @@ export default {
|
|||||||
id: 'pie',
|
id: 'pie',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'doughnut',
|
||||||
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bubble',
|
id: 'bubble',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
||||||
|
|||||||
@@ -890,6 +890,10 @@ export default {
|
|||||||
id: 'pie',
|
id: 'pie',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.pie.label')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'doughnut',
|
||||||
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.doughnut.label')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bubble',
|
id: 'bubble',
|
||||||
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
name: this.$t('dashboard.dashboard.chartForm.typeVal.bubble.label')
|
||||||
@@ -1001,7 +1005,8 @@ export default {
|
|||||||
case 'bar':
|
case 'bar':
|
||||||
case 'treemap':
|
case 'treemap':
|
||||||
case 'pie':
|
case 'pie':
|
||||||
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie') {
|
case 'doughnut':
|
||||||
|
if (this.oldType === 'bar' || this.oldType === 'treemap' || this.oldType === 'pie' || this.oldType === 'doughnut') {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.chartConfig.param = {
|
this.chartConfig.param = {
|
||||||
|
|||||||
Reference in New Issue
Block a user