NEZ-1292 feat: chart-hexagon 六边形组件开发

This commit is contained in:
zhangyu
2021-12-28 18:09:44 +08:00
parent 805ec979c6
commit bb5f780d9e
12 changed files with 502 additions and 34 deletions

View File

@@ -24,6 +24,8 @@
"@topology/sequence-diagram": "^0.3.0",
"axios": "^0.19.0",
"cytoscape": "^3.15.2",
"d3": "^6.7.0",
"d3-hexbin": "^0.2.2",
"echarts": "^5.2.2",
"element-ui": "^2.15.3",
"file-saver": "^2.0.2",

View File

@@ -476,6 +476,14 @@
text-align: center;
}
}
.chart-svg {
width: 100%;
height: 100%;
display: flex;
align-items:center;
justify-content:center;
overflow: hidden;
}
.chart-diagram{
height: 100%;
width: 100%;

View File

@@ -1,5 +1,5 @@
<template>
<div class="chart-gauge-box" ref="chart-gauge-box">
<div class="chart-gauge-box" :ref="'chart-gauge-box' + chartInfo.id">
<div
v-for="(item,index) in gaugeData" :key="index"
class="chart-gauge-item"
@@ -79,6 +79,7 @@ export default {
}
}
const legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
gauge.value = getMetricTypeValue(data.values, chartInfo.param.statistics || 'last')
gauge.max = chartInfo.param.max || 100
gauge.min = chartInfo.param.min || 0
@@ -86,9 +87,9 @@ export default {
gauge.min = gauge.max / 2
}
gauge.label = data.metric
gauge.legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex).alias
gauge.name = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex).name
gauge.alias = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex).alias
gauge.legend = legend.alias
gauge.name = legend.name
gauge.alias = legend.alias
gauge.showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(gauge.value, null, -1, 2)
// gauge.value = gauge.showValue
gauge.mapping = this.selectMapping(gauge.value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
@@ -101,8 +102,8 @@ export default {
})
},
getLayout () {
this.boxWidth = this.$refs['chart-gauge-box'].offsetWidth - 2 * this.boxPadding
this.boxHeight = this.$refs['chart-gauge-box'].offsetHeight - 2 * this.boxPadding
this.boxWidth = this.$refs['chart-gauge-box' + this.chartInfo.id].offsetWidth - 2 * this.boxPadding
this.boxHeight = this.$refs['chart-gauge-box' + this.chartInfo.id].offsetHeight - 2 * this.boxPadding
return new Promise(resolve => {
let rateMax = 0
let col = 0

View File

@@ -0,0 +1,332 @@
<template>
<div :ref="`chart-canvas-${chartId}`" style="height: 100%;width: 100%">
<div :id="`chart-canvas-${chartId}`" class="chart__canvas chart-svg"></div>
</div>
</template>
<script>
import * as d3 from 'd3'
import hexbin from '@/components/chart/chart/options/chartHexagonD3'
import chartMixin from '@/components/chart/chartMixin'
import chartFormat from '@/components/chart/chartFormat'
import { initColor } from '@/components/chart/chart/tools'
import '@svgdotjs/svg.panzoom.js'
import { getMetricTypeValue } from '@/components/common/js/tools'
import chartDataFormat from '@/components/charts/chartDataFormat'
export default {
name: 'chartHexagonD3',
mixins: [chartMixin, chartFormat],
data () {
return {
timer: null,
HexagonData: [],
boxWidth: 0,
boxHeight: 0,
boxPadding: 5
}
},
methods: {
hexbin,
initChart () {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.timer = setTimeout(() => {
this.initHexagonData(this.chartInfo, this.chartData).then(() => {
this.getLayout().then(layout => {
console.log(layout, this.boxWidth, this.boxHeight)
this.initHexagon(layout)
})
})
}, 200)
},
initHexagonData (chartInfo, originalDatas) {
this.HexagonData = []
this.isInit = false
return new Promise(resolve => {
let colorIndex = 0
console.log('init', 1)
originalDatas.forEach((originalData, expressionIndex) => {
originalData.forEach((data, dataIndex) => {
this.isNoData = false
const Hexagon = {
value: '',
showValue: '',
label: {},
width: '',
height: '',
legend: '',
oldValue: '',
mapping: {
}
}
const legend = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
Hexagon.value = getMetricTypeValue(data.values, chartInfo.param.statistics || 'last')
Hexagon.max = chartInfo.param.max || 100
Hexagon.min = chartInfo.param.min || 0
if (Hexagon.min === Hexagon.max) {
Hexagon.min = Hexagon.max / 2
}
Hexagon.label = data.metric
Hexagon.legend = legend.alias
Hexagon.name = legend.name
Hexagon.alias = legend.alias
Hexagon.showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(Hexagon.value, null, -1, 2)
// Hexagon.value = Hexagon.showValue
Hexagon.mapping = this.selectMapping(Hexagon.value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
this.HexagonData.push(Hexagon)
colorIndex++
})
})
this.$emit('chartIsNoData', this.isNoData)
resolve()
})
},
initHexagon (layout) {
this.isInit = false
console.log(layout, this.HexagonData)
let rowIndex = 0
let colIndex = -1
const data = this.HexagonData.map(item => {
colIndex++
if (colIndex > layout.col) {
rowIndex++
colIndex = 0
}
return {
...item,
x: colIndex,
y: rowIndex,
label: item.legend
}
})
const dom = document.getElementById(`chart-canvas-${this.chartId}`)
const child = document.getElementById('svgHex' + this.chartId)
if (dom && child) {
dom.removeChild(child)
}
if (dom) {
const hexaRadius = layout.radius
dom.append(this.showHexagons(data, hexaRadius, layout.col, layout.row))
setTimeout(() => {
const svg = document.getElementById('svgHex' + this.chartId)
const bbox = svg.getBBox()
svg.setAttribute('viewBox', (bbox.x - 10) + ' ' + (bbox.y - 10) + ' ' + (bbox.width + 20) + ' ' + (bbox.height + 20))
svg.setAttribute('width', (bbox.width + 20) + 'px')
svg.setAttribute('height', (bbox.height + 20) + 'px')
}, 100)
}
},
showHexagons (data, hexaRadiu = 60, row, col) {
// Initial Geometrical Calculations
const self = this
const hexaRadius = hexaRadiu
const spaceBetweenHexa = 0.5 // Number of pixels of space between consecutive hexagons
const hexaWidth = hexaRadius * 2 // Calculates Width of the Hexagons with specified radius
const hexaHeight = hexaRadius * Math.sqrt(3) // Calculates Width of the Hexagons ...
const rows = row // Number of desired Rows
const cols = col // Number of desired Columns
const height = (rows + 1 / 3) * 3 / 2 * hexaRadius // Calculates height of the window with given rows and radius
const width = (cols + 1 / 2) * Math.sqrt(3) * hexaRadius // Calculates width of ...
const hexbin = this.hexbin()
.radius(hexaRadius)
.extent([[0, 0], [width, height]])
const svg = d3.create('svg')
.attr('id', 'svgHex' + this.chartId)
.attr('width', width)
.attr('height', height)
for (let i = 0; i < data.length; i++) {
const point = data[i]
const col = point.x
const row = point.y
const color = point.mapping ? point.mapping.color.bac : this.colorList[i]
console.log(color)
const vals = self.getCenter(hexaRadius, row, col) // Gets the Center coordinates with given row and column
const x = vals[0]
const y = vals[1]
const hexa = self.drawHexagon(svg, hexaRadius, spaceBetweenHexa, x, y, hexbin) // Draws hexagon
hexa.attr('fill', color) // Paints hexagon
hexa.on('mouseenter', self.hexagonOver.bind(self, hexa))
hexa.on('mouseleave', self.hexagonOut.bind(self, hexa))
self.drawText(svg, vals, point, color) // 文本
data[i].fcolor = color
}
return svg.node()
},
hexagonOver (that, e) { // 移入六边形
// console.log(that, e)
},
hexagonMove (that, e) { // 六边形内移动
// console.log(that, e)
// this.$emit('assetMove',e)
},
hexagonOut (that) {
},
formatStr (d) {
const self = this
let str = ''
str += ''
return str
},
drawHexagon (svg, radius, space, x, y, hexbin) {
const hexagon = svg.append('path')
.attr('d', hexbin.hexagon(radius - space))
.attr('transform', 'translate(' + x + ',' + y + ')')
return hexagon
},
drawText (svg, vals, point, color) {
let str = ''
let valueStr = ''
const self = this
if (this.chartInfo.param.text === 'all') {
str += point.alias
let strLength = str
// eslint-disable-next-line no-unused-expressions
strLength = strLength.replace(/[\u0391-\uFFE5]/g, 'aa').length
if (strLength >= 15) {
str = str.slice(0, 12) + '...'
}
valueStr = point.mapping && point.mapping.display ? self.handleDisplay(point.mapping.display, { ...point.labels, value: point.showValue }) : point.showValue
}
if (this.chartInfo.param.text === 'value' || !this.chartInfo.param.text) {
valueStr = point.mapping && point.mapping.display ? self.handleDisplay(point.mapping.display, { ...point.labels, value: point.showValue }) : point.showValue
}
if (this.chartInfo.param.text === 'legend') {
str += point.alias
let strLength = str
// eslint-disable-next-line no-unused-expressions
strLength = strLength.replace(/[\u0391-\uFFE5]/g, 'aa').length
if (strLength >= 15) {
str = str.slice(0, 12) + '...'
}
}
if (this.chartInfo.param.text === 'none') {
str += ''
}
if (str && valueStr) {
svg.append('text')
.attr('x', vals[0])
.attr('y', vals[1] - 16)
.text(str)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'central')
.style('font-size', 16)
.style('fill', this.invertColor(color))
svg.append('text')
.attr('x', vals[0])
.attr('y', vals[1] + 16)
.text(valueStr)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'central')
.style('font-size', 16)
.style('fill', this.invertColor(color))
return
}
if (str) {
svg.append('text')
.attr('x', vals[0])
.attr('y', vals[1])
.text(str)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'central')
.style('font-size', 16)
.style('fill', this.invertColor(color))
return
}
if (valueStr) {
svg.append('text')
.attr('x', vals[0])
.attr('y', vals[1])
.text(valueStr)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'central')
.style('font-size', 16)
.style('fill', this.invertColor(color))
}
},
getCenter (hexaRadius, row, col) {
let x = hexaRadius * col * Math.sqrt(3)
// Offset each uneven row by half of a "hex-width" to the right
if (row % 2 === 1) x += (hexaRadius * Math.sqrt(3)) / 2
const y = hexaRadius * row * 1.5
return [x + hexaRadius, y + hexaRadius]
},
invertColor (hex) {
if (hex.indexOf('#') === 0) {
hex = hex.slice(1)
}
// convert 3-digit hex to 6-digits.
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
}
if (hex.length !== 6) {
// throw new Error('Invalid HEX color.');
}
const r = parseInt(hex.slice(0, 2), 16)
const g = parseInt(hex.slice(2, 4), 16)
const b = parseInt(hex.slice(4, 6), 16)
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
? '#000000'
: '#FFFFFF'
},
getLayout () {
this.boxWidth = this.$refs[`chart-canvas-${this.chartId}`].offsetWidth - 2 * this.boxPadding
this.boxHeight = this.$refs[`chart-canvas-${this.chartId}`].offsetHeight - 2 * this.boxPadding
return new Promise(resolve => {
let radius = 0
let col = 0
let row = 0
for (let i = 1; i <= this.HexagonData.length; i++) {
const cols = Math.ceil(this.HexagonData.length / i)
const hexaRadiusY = Math.ceil((this.boxHeight * 2) / (3 * i + 1))
let hexaRadiusX = ''
if (i === 1) {
hexaRadiusX = Math.ceil(this.boxWidth / (Math.sqrt(3) * cols))
} else {
hexaRadiusX = Math.ceil(this.boxWidth / (Math.sqrt(3) * cols + Math.sqrt(3) / 2))
}
const rateMax = hexaRadiusX > hexaRadiusY ? hexaRadiusY : hexaRadiusX
if (rateMax > radius) {
radius = rateMax
col = cols
row = i
}
}
if (this.HexagonData.length) {
while (col * row >= this.HexagonData.length) { // 避免出现空白
row--
}
}
row++
if (col === 1 || row === 1) { // 行 或 列有一个为1时 需要调换位置 显示才会好看
const temp = col
col = row
row = temp
}
resolve({ col, row, radius })
})
}
},
created () {
},
mounted () {
// eslint-disable-next-line vue/no-mutating-props
this.chartOption.color || (this.chartOption.color = initColor(20))
this.colorList = this.chartOption.color
try {
this.isStack = this.chartInfo.param.stack
} catch (e) {}
this.chartInfo.loaded && this.initChart(this.chartOption)
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,112 @@
const thirdPi = Math.PI / 3
const angles = [0, thirdPi, 2 * thirdPi, 3 * thirdPi, 4 * thirdPi, 5 * thirdPi]
function pointX (d) {
return d[0]
}
function pointY (d) {
return d[1]
}
export default function () {
let x0 = 0
let y0 = 0
let x1 = 1
let y1 = 1
let x = pointX
let y = pointY
let r
let dx
let dy
function hexbin (points) {
const binsById = {}; const bins = []; let i; const n = points.length
for (i = 0; i < n; ++i) {
if (isNaN(px = +x.call(null, point = points[i], i, points)) ||
isNaN(py = +y.call(null, point, i, points))) continue
var point
var px
var py
let pj = Math.round(py = py / dy)
let pi = Math.round(px = px / dx - (pj & 1) / 2)
const py1 = py - pj
if (Math.abs(py1) * 3 > 1) {
const px1 = px - pi
const pi2 = pi + (px < pi ? -1 : 1) / 2
const pj2 = pj + (py < pj ? -1 : 1)
const px2 = px - pi2
const py2 = py - pj2
if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) pi = pi2 + (pj & 1 ? 1 : -1) / 2, pj = pj2
}
const id = pi + '-' + pj; let bin = binsById[id]
if (bin) bin.push(point)
else {
bins.push(bin = binsById[id] = [point])
bin.x = (pi + (pj & 1) / 2) * dx
bin.y = pj * dy
}
}
return bins
}
function hexagon (radius) {
let x0 = 0; let y0 = 0
return angles.map(function (angle) {
const x1 = Math.sin(angle) * radius
const y1 = -Math.cos(angle) * radius
const dx = x1 - x0
const dy = y1 - y0
x0 = x1, y0 = y1
return [dx, dy]
})
}
hexbin.hexagon = function (radius) {
return 'm' + hexagon(radius == null ? r : +radius).join('l') + 'z'
}
hexbin.centers = function () {
const centers = []
let j = Math.round(y0 / dy)
const i = Math.round(x0 / dx)
for (let y = j * dy; y < y1 + r; y += dy, ++j) {
for (let x = i * dx + (j & 1) * dx / 2; x < x1 + dx / 2; x += dx) {
centers.push([x, y])
}
}
return centers
}
hexbin.mesh = function () {
const fragment = hexagon(r).slice(0, 4).join('l')
return hexbin.centers().map(function (p) { return 'M' + p + 'm' + fragment }).join('')
}
hexbin.x = function (_) {
return arguments.length ? (x = _, hexbin) : x
}
hexbin.y = function (_) {
return arguments.length ? (y = _, hexbin) : y
}
hexbin.radius = function (_) {
return arguments.length ? (r = +_, dx = r * 2 * Math.sin(thirdPi), dy = r * 1.5, hexbin) : r
}
hexbin.size = function (_) {
return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], hexbin) : [x1 - x0, y1 - y0]
}
hexbin.extent = function (_) {
return arguments.length ? (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1], hexbin) : [[x0, y0], [x1, y1]]
}
return hexbin.radius(1)
}

View File

@@ -128,10 +128,12 @@ export default {
return { type, value: getMetricTypeValue(data.values, type) }
})
}
if (colorIndex > 20) {
if (colorIndex >= 20) {
const colorRandom = randomcolor()
this.colorList.push(colorRandom)
this.chartOption.color.push(colorRandom)
// console.log(this.colorList.length, this.chartOption.color.length)
// this.chartOption.color.push(colorRandom)
// console.log(this.colorList.length, this.chartOption.color.length)
}
// console.log(name, alias, statistics)
this.legends.push({ name, alias, statistics, color: this.colorList[colorIndex] })
@@ -262,7 +264,10 @@ export default {
chartData: {
deep: true,
handler (n) {
console.log(this.isInit)
if (!this.isInit) {
this.colorList = this.colorList.slice(0, 20)
this.chartOption.color = this.chartOption.color.slice(0, 20)
this.initChart(this.chartOption)
}
}

View File

@@ -277,6 +277,7 @@ export default {
chartData.push(res.data.result)
} else {
chartData.push({ error: res.msg || res.error || res })
this.isError = true
}
this.chartData = chartData
this.loading = false

View File

@@ -728,6 +728,10 @@ export default {
id: 'stat',
name: this.$t('dashboard.panel.chartForm.typeVal.singleStat.label')
},
{
id: 'hexagon',
name: this.$t('dashboard.panel.chartForm.typeVal.hexagonFigure.label')
},
{
id: 'bar',
name: this.$t('dashboard.panel.chartForm.typeVal.bar.label')
@@ -803,8 +807,9 @@ export default {
})
break
case 'stat':
case 'hexagon':
case 'guage':
if (this.oldType === 'stat' || this.oldType === 'guage') {
if (this.oldType === 'stat' || this.oldType === 'guage' || this.oldType === 'hexagon') {
break
}
this.chartConfig.param = {

View File

@@ -50,6 +50,7 @@ export default {
return true
case 'table':
case 'stat':
case 'hexagon':
case 'bar':
case 'treemap':
case 'guage':
@@ -66,6 +67,7 @@ export default {
return false
case 'table':
case 'stat':
case 'hexagon':
case 'bar':
case 'treemap':
case 'guage':
@@ -85,6 +87,7 @@ export default {
return true
case 'table':
case 'stat':
case 'hexagon':
case 'guage':
return false
default: return false
@@ -107,6 +110,7 @@ export default {
case 'table':
return false
case 'stat':
case 'hexagon':
case 'bar':
case 'guage':
case 'treemap':
@@ -122,6 +126,7 @@ export default {
case 'treemap':
case 'pie':
case 'stat':
case 'hexagon':
case 'guage':
return false
case 'line':
@@ -138,6 +143,7 @@ export default {
case 'treemap':
case 'pie':
case 'stat':
case 'hexagon':
case 'guage':
return true
case 'line':

View File

@@ -81,6 +81,10 @@ export default {
id: 'stat',
name: this.$t('dashboard.panel.chartForm.typeVal.singleStat.label')
},
{
id: 'hexagon',
name: this.$t('dashboard.panel.chartForm.typeVal.hexagonFigure.label')
},
{
id: 'bar',
name: this.$t('dashboard.panel.chartForm.typeVal.bar.label')
@@ -120,6 +124,10 @@ export default {
id: 'stat',
name: this.$t('dashboard.panel.chartForm.typeVal.singleStat.label')
},
{
id: 'hexagon',
name: this.$t('dashboard.panel.chartForm.typeVal.hexagonFigure.label')
},
{
id: 'bar',
name: this.$t('dashboard.panel.chartForm.typeVal.bar.label')

View File

@@ -586,6 +586,10 @@ export default {
id: 'stat',
name: this.$t('dashboard.panel.chartForm.typeVal.singleStat.label')
},
{
id: 'hexagon',
name: this.$t('dashboard.panel.chartForm.typeVal.hexagonFigure.label')
},
{
id: 'bar',
name: this.$t('dashboard.panel.chartForm.typeVal.bar.label')
@@ -650,8 +654,9 @@ export default {
chartTypeChange (type) {
switch (type) {
case 'stat':
case 'hexagon':
case 'guage':
if (this.oldType === 'stat' || this.oldType === 'guage') {
if (this.oldType === 'stat' || this.oldType === 'guage' || this.oldType === 'hexagon') {
break
}
this.chartConfig.param = {
@@ -666,7 +671,8 @@ export default {
legend: true,
valueMapping: false,
thresholds: false
}
},
datasource: [...this.chartConfig.param.datasource]
}
break
case 'bar':
@@ -688,7 +694,8 @@ export default {
legend: true,
valueMapping: false,
thresholds: false
}
},
datasource: [...this.chartConfig.param.datasource]
}
break
case 'table':
@@ -707,30 +714,11 @@ export default {
valueMapping: false,
thresholds: false
},
datasource: [{
name: 'System',
type: '',
systemGroup: '',
systemSelect: '',
group: '',
select: '',
limit: 100,
sort: 'desc'
}
]
datasource: [...this.chartConfig.param.datasource]
}
break
}
this.chartConfig.param.datasource[0].type = this.systemData[0].name
this.groupList = this.systemData[0].group
this.selectList = this.systemData[0].select
this.oldType = type
this.systemNameShow = [{
error: false,
hideInput: true,
show: true,
oldName: this.chartConfig.param.datasource[0].name
}]
this.change()
},
changeSystem (item) {