NEZ-2543 feat:stat chart支持sparkline mode页面开发

This commit is contained in:
zyh
2023-02-13 15:27:30 +08:00
parent a93bf5a881
commit 8353046835
9 changed files with 297 additions and 160 deletions

View File

@@ -283,6 +283,36 @@
min-height: unset;
}
}
.legend-box{
width: 100%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.rightYAxis-legend{
display: flex;
justify-content: flex-end;
flex-grow: 1;
}
.rightYAxis-name{
position: absolute;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
line-height: 100%;
margin-left: calc(50% - 18px);
font-size: 12px;
color:#6E7079;
pointer-events:none;
span{
transform: rotate(90deg);
font-family: sans-serif;
}
}
}
.chart-detail {
width: 100%;
@@ -498,7 +528,16 @@
//padding: 2px;
overflow: hidden;
color: $--color-text-regular;
flex-grow: 1
flex-grow: 1;
position: relative;
.sparkline{
width: 100%;
height: 40%;
position: absolute;
right: 0;
bottom: 0;
z-index: 1;
}
}
}
.chart-gauge-box{

View File

@@ -14,38 +14,47 @@
fontSize: item.fontSize,
}"
>
<div>
<span v-if="chartInfo.param.text==='all'">
<span v-if="item.mapping" :style="{color:item.mapping.color.text}">
{{item.legend}}<br/>
<div style="position: relative;z-index: 10;">
<!-- all -->
<template v-if="chartInfo.param.text==='all'">
<div v-if="item.mapping" :style="{color:item.mapping.color.text}">
<p>{{item.legend}}</p>
<template v-if="item.mapping && item.mapping.icon">
<i :class="item.mapping.icon" :style="{color: item.mapping.color.icon,fontSize:'1em'}"></i>
</template>
<span>{{handleDisplay(item.mapping.display, { ...item.label, value: item.showValue })}}</span>
</span>
<span v-else>{{item.legend}}<br/><span>{{item.showValue}}</span></span>
</span>
<span v-if="chartInfo.param.text==='legend'">
<span v-if="item.mapping" :style="{color:item.mapping.color.text}">
{{item.legend}}<br/>
<template v-if="item.mapping && item.mapping.icon">
<i :class="item.mapping.icon" :style="{color: item.mapping.color.icon,fontSize:'1em'}"></i>
</template>
<span>{{handleDisplay(item.mapping.display, { ...item.label, value: item.showValue })}}</span>
</span>
<span v-else>{{item.legend}}</span>
</span>
<span v-if="chartInfo.param.text==='value'|| !chartInfo.param.text">
<span v-if="item.mapping" :style="{color:item.mapping.color.text}">
<template v-if="item.mapping && item.mapping.icon">
<i :class="item.mapping.icon" :style="{color: item.mapping.color.icon,fontSize:'1em'}"></i>
</template>
{{handleDisplay(item.mapping.display, { ...item.label, value: item.showValue })}}
</span>
<span v-else>{{item.showValue}}</span>
</span>
<span v-if="chartInfo.param.text==='none'"></span>
</div>
<div v-else>
<p>{{item.legend}}</p>
<span>{{item.showValue}}</span>
</div>
</template>
<!-- legend -->
<template v-if="chartInfo.param.text==='legend'">
<div v-if="item.mapping" :style="{color:item.mapping.color.text}">
<template v-if="item.mapping && item.mapping.icon">
<i :class="item.mapping.icon" :style="{color: item.mapping.color.icon,fontSize:'1em'}"></i>
</template>
<span>{{item.legend}}</span>
</div>
<div v-else>{{item.legend}}</div>
</template>
<!-- value -->
<template v-if="chartInfo.param.text==='value'|| !chartInfo.param.text">
<div v-if="item.mapping" :style="{color:item.mapping.color.text}">
<template v-if="item.mapping && item.mapping.icon">
<i :class="item.mapping.icon" :style="{color: item.mapping.color.icon,fontSize:'1em'}"></i>
</template>
<span>{{handleDisplay(item.mapping.display, { ...item.label, value: item.showValue })}}</span>
</div>
<div v-else>{{item.showValue}}</div>
</template>
<!-- none -->
<template v-if="chartInfo.param.text==='none'"></template>
</div>
<!-- sparkline -->
<div class="sparkline" :id="`chart-canvas-${chartId}-${index}`" v-if="chartInfo.param.sparklineMode && chartInfo.param.sparklineMode !== 'none'"></div>
</div>
<div :class="`chart-canvas-tooltip-${chartId}`" :id="`chart-canvas-tooltip-${chartId}`" class="chart-canvas-tooltip" :style="{left:tooltip.x+'px',top:tooltip.y+'px'}" v-if="tooltip.show">
<div class="chart-canvas-tooltip-title tooltip-title">
@@ -73,7 +82,9 @@ import chartDataFormat from '@/components/chart/chartDataFormat'
import { randomcolor } from '@/components/common/js/radomcolor/randomcolor'
import { initColor } from '@/components/chart/chart/tools'
// import fontWidth from '@/components/chart/chart/options/fontWidth'
import * as echarts from 'echarts'
import chartSparklineOption from './options/chartSparkline'
import lodash from 'lodash'
export default {
name: 'chart-stat',
mixins: [chartMixin, chartFormat],
@@ -95,7 +106,9 @@ export default {
fontSize: 12,
customFontSize: '',
minFontSzie: 12,
defaultUnit: 60 // 根据stat的长宽取 需要的字体 = (取最短的边 / defaultUnit) * fontSize 因为 需要的字体/fontSize = 实际宽 / defaultUnit
defaultUnit: 60, // 根据stat的长宽取 需要的字体 = (取最短的边 / defaultUnit) * fontSize 因为 需要的字体/fontSize = 实际宽 / defaultUnit
sparkline: {},
maxValue: 0
}
},
methods: {
@@ -107,6 +120,11 @@ export default {
}
this.getLayout().then(layout => {
this.renderStat(layout)
if (this.chartInfo.param.sparklineMode && this.chartInfo.param.sparklineMode !== 'none') {
this.$nextTick(() => {
this.drawChart()
})
}
})
})
},
@@ -127,9 +145,8 @@ export default {
height: '',
legend: '',
name: '',
mapping: {
}
mapping: {},
data: {}
}
stat.value = getMetricTypeValue(data.values, chartInfo.param.statistics)
stat.label = data.metric
@@ -138,67 +155,28 @@ export default {
stat.name = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex).name
stat.showValue = chartDataFormat.getUnit(chartInfo.unit ? chartInfo.unit : 2).compute(stat.value, null, -1, decimals)
stat.mapping = this.selectMapping(stat.value, chartInfo.param.valueMapping, chartInfo.param.enable && this.chartInfo.param.enable.valueMapping)
stat.data = data.values
this.statData.push(stat)
colorIndex++
})
})
let maxValue = 0
if (this.statData.length > 0) {
maxValue = 0
for (let j = 0; j < this.statData.length; j++) {
for (let i = 0; i < this.statData[j].data.length; i++) {
if (!isNaN(this.statData[j].data[i][1])) {
maxValue = (maxValue < Number(this.statData[j].data[i][1]) ? Number(this.statData[j].data[i][1]) : maxValue)
}
}
}
}
this.maxValue = maxValue
this.$emit('chartIsNoData', this.isNoData)
resolve()
})
},
statMouseMove (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
}
},
statMouseEnter (that, e) {
this.tooltip.title = this.$loadsh.cloneDeep(that.legend)
this.tooltip.value = this.$loadsh.cloneDeep(that.showValue)
this.tooltip.mapping = this.$loadsh.cloneDeep(that.mapping)
this.tooltip.show = true
this.$nextTick(() => {
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
}
})
},
statMouseleave (taht) {
this.tooltip.show = false
},
getLayout () {
try {
this.boxWidth = this.$refs['chart-stat-box'].offsetWidth - 3 * this.boxPadding
@@ -327,6 +305,34 @@ export default {
})
this.isInit = false
},
// 绘制图表
drawChart () {
this.statData.forEach((item, index) => {
const chart = this.sparkline[index] ? this.sparkline[index] : echarts.init(document.getElementById(`chart-canvas-${this.chartId}-${index}`))
const chartOption = lodash.cloneDeep(chartSparklineOption)
// sparkline 颜色和 字体颜色 保持一致
const theme = localStorage.getItem(`nz-user-${localStorage.getItem('nz-user-id')}-theme`) || 'light'
let color = theme === 'light' ? '#666666' : '#BEBEBE'
if (item.mapping) {
color = item.mapping.color.text
}
chartOption.series = [{
...chartOption.series[0],
data: item.data,
lineStyle: {
color
}
}]
if (this.chartInfo.param.sparklineMode === 'area') {
chartOption.series[0].areaStyle = {
color
}
}
chartOption.yAxis.max = this.maxValue
chart.setOption(chartOption)
this.sparkline[index] = chart
})
},
resize () {
if (this.statTimer) {
clearTimeout(this.statTimer)
@@ -335,9 +341,68 @@ export default {
this.statTimer = setTimeout(() => {
this.getLayout().then(layout => {
this.renderStat(layout)
this.$nextTick(() => {
for (const key in this.sparkline) {
this.sparkline[key].resize()
}
})
})
}, 50)
},
statMouseMove (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
}
},
statMouseEnter (that, e) {
this.tooltip.title = this.$loadsh.cloneDeep(that.legend)
this.tooltip.value = this.$loadsh.cloneDeep(that.showValue)
this.tooltip.mapping = this.$loadsh.cloneDeep(that.mapping)
this.tooltip.show = true
this.$nextTick(() => {
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
}
})
},
statMouseleave () {
this.tooltip.show = false
},
setFontSize (item) {
let fontSize = ''
@@ -362,10 +427,12 @@ export default {
mounted () {
this.colorList = initColor(20)
this.chartInfo.loaded && this.initChart()
},
beforeDestroy () {
for (const key in this.sparkline) {
this.sparkline[key].dispose()
this.sparkline[key] = null
}
}
}
</script>
<style scoped>
</style>

View File

@@ -529,25 +529,3 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.rightYAxis-name{
position: absolute;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
line-height: 100%;
margin-left: calc(50% - 18px);
font-size: 12px;
color:#666666;
pointer-events:none;
span{
transform: rotate(90deg);
font-family: sans-serif;
}
}
</style>

View File

@@ -14,13 +14,13 @@
<div
v-for="(item, index) in legends"
:key="index"
v-show="series[index].yAxisIndex!=1"
v-show="!isTimeSeries||series[index].yAxisIndex!=1"
:class="{'row--inactive': isGrey[index]}"
class="legend--table-row"
@click="clickLegend(item.name, index)"
>
<div :title="item.alias ? item.alias : item.name" class="legend--table-cell">
<i v-if="series[index].yAxisIndex==0" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-zuozongzhou"></i>
<i v-if="isTimeSeries&&series[index].yAxisIndex==0" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-zuozongzhou"></i>
<span v-else :style="{background: item.color}" class="legend-shape"></span>
<span>{{item.alias ? item.alias : item.name}}</span>
</div>
@@ -30,13 +30,13 @@
<div
v-for="(item, index) in legends"
:key="index+'right'"
v-show="series[index].yAxisIndex==1"
v-show="isTimeSeries&&series[index].yAxisIndex==1"
:class="{'row--inactive': isGrey[index]}"
class="legend--table-row"
@click="clickLegend(item.name, index)"
>
<div :title="item.alias ? item.alias : item.name" class="legend--table-cell">
<i v-if="series[index].yAxisIndex==1" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-youzongzhou"></i>
<i v-if="isTimeSeries&&series[index].yAxisIndex==1" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-youzongzhou"></i>
<span v-else :style="{background: item.color}" class="legend-shape"></span>
<span>{{item.alias ? item.alias : item.name}}</span>
</div>
@@ -53,31 +53,31 @@
<li
v-for="(item, index) in legends"
:key="index"
v-show="series[index].yAxisIndex!=1"
v-show="!isTimeSeries||series[index].yAxisIndex!=1"
:class="{'legend-item--inactive': isGrey[index]}"
:title="item.alias ? item.alias : item.name"
class="legend-item"
@click="clickLegend(item.name, index)"
>
<i v-if="series[index].yAxisIndex==0" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-zuozongzhou"></i>
<i v-if="isTimeSeries&&series[index].yAxisIndex==0" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-zuozongzhou"></i>
<span v-else :style="{background: item.color}" class="legend-shape"></span>
<span>{{item.alias ? item.alias : item.name.split('-')[0]}}</span>
</li>
</ul>
</div>
<!-- 右y轴legend -->
<div class="rightYAxis">
<div class="rightYAxis-legend">
<ul>
<li
v-for="(item, index) in legends"
:key="index+'right'"
v-show="series[index].yAxisIndex==1"
v-show="isTimeSeries&&series[index].yAxisIndex==1"
:class="{'legend-item--inactive': isGrey[index]}"
:title="item.alias ? item.alias : item.name"
class="legend-item"
@click="clickLegend(item.name, index)"
>
<i v-if="series[index].yAxisIndex==1" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-youzongzhou"></i>
<i v-if="isTimeSeries&&series[index].yAxisIndex==1" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-youzongzhou"></i>
<span v-else :style="{background: item.color}" class="legend-shape"></span>
<span>{{item.alias ? item.alias : item.name.split('-')[0]}}</span>
</li>
@@ -91,13 +91,13 @@
<div
v-for="(item, index) in legends"
:key="index"
v-show="series[index].yAxisIndex!=1"
v-show="!isTimeSeries||series[index].yAxisIndex!=1"
:class="{'legend-item--inactive': isGrey[index]}"
:title="item.alias ? item.alias : item.name"
class="legend-item"
@click="clickLegend(item.name, index)"
>
<i v-if="series[index].yAxisIndex==0" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-zuozongzhou"></i>
<i v-if="isTimeSeries&&series[index].yAxisIndex==0" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-zuozongzhou"></i>
<span v-else :style="{background: item.color}" class="legend-shape"></span>
<span>{{item.alias ? item.alias : item.name.split('-')[0]}}</span>
</div>
@@ -105,13 +105,13 @@
<div
v-for="(item, index) in legends"
:key="index+'right'"
v-show="series[index].yAxisIndex==1"
v-show="isTimeSeries&&series[index].yAxisIndex==1"
:class="{'legend-item--inactive': isGrey[index]}"
:title="item.alias ? item.alias : item.name"
class="legend-item"
@click="clickLegend(item.name, index)"
>
<i v-if="series[index].yAxisIndex==1" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-youzongzhou"></i>
<i v-if="isTimeSeries&&series[index].yAxisIndex==1" :style="{color: item.color}" class="yAxis-icon nz-icon nz-icon-youzongzhou"></i>
<span v-else :style="{background: item.color}" class="legend-shape"></span>
<span>{{item.alias ? item.alias : item.name.split('-')[0]}}</span>
</div>
@@ -125,7 +125,6 @@ import { getChart } from '@/components/common/js/common'
import chartDataFormat from '@/components/chart/chartDataFormat'
import { statisticsList } from '@/components/common/js/constants'
import * as chart from 'echarts'
import { isTimeSeries } from './tools'
export default {
name: 'chartLegend',
props: {
@@ -151,6 +150,9 @@ export default {
// timeSeries类型图表联动
isConnect () {
return this.$store.state.panel.isConnect
},
isTimeSeries () {
return this.chartInfo.type === 'line' || this.chartInfo.type === 'area' || this.chartInfo.type === 'point'
}
},
methods: {
@@ -181,7 +183,7 @@ export default {
if (echarts) {
// 判断timeSeries类型图表 先取消多表联动
if (isTimeSeries(this.chartInfo.type) && (this.isConnect && this.isConnect !== 'none')) {
if (this.isTimeSeries && (this.isConnect && this.isConnect !== 'none')) {
chart.disconnect('timeSeriesGroup')
}
if (!hasGrey) { // 1.除当前legend外全置灰
@@ -206,11 +208,11 @@ export default {
})
this.$set(this.isGrey, index, !this.isGrey[index])
}
if (isTimeSeries(this.chartInfo.type)) {
if (this.isTimeSeries) {
this.$parent.legendChange(this.isGrey)
}
// 判断timeSeries类型图表 建立多表联动
if (isTimeSeries(this.chartInfo.type) && (this.isConnect && this.isConnect !== 'none')) {
if (this.isTimeSeries && (this.isConnect && this.isConnect !== 'none')) {
chart.connect('timeSeriesGroup')
}
}
@@ -350,17 +352,3 @@ export default {
}
}
</script>
<style scoped>
.legend-box{
width: 100%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.rightYAxis{
display: flex;
justify-content: flex-end;
flex-grow: 1;
}
</style>

View File

@@ -0,0 +1,29 @@
const chartSparklineOption = {
xAxis: {
show: false,
type: 'time'
},
yAxis: {
show: false,
type: 'value'
},
grid: {
left: 0,
right: 0,
top: 0,
bottom: 0
},
animation: false,
axisLabel: {},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
silent: true,
data: []
}
]
}
export default chartSparklineOption

View File

@@ -398,9 +398,9 @@
</el-form-item>
</div>
<div class="form-items--half-width-group" v-if="isShowDecimals(chartConfig.type)">
<div class="form-items--half-width-group">
<!--decimals-->
<el-form-item :label="$t('overall.decimal')" class="form-item--half-width">
<el-form-item :label="$t('overall.decimal')" class="form-item--half-width" v-if="isShowDecimals(chartConfig.type)">
<el-input-number
size="small"
style="margin-top: 2px"
@@ -409,6 +409,23 @@
:placeholder="'Default 2'"
show-word-limit v-model="chartConfig.param.decimals"/>
</el-form-item>
<!-- Sparkline mode -->
<el-form-item :label="$t('dashboard.panel.chartForm.sparklineMode')" class="form-item--half-width" v-if="isShowSparkline(chartConfig.type)">
<el-select
v-model="chartConfig.param.sparklineMode"
placeholder=""
popper-class="right-box-select-top prevent-clickoutside"
size="small"
@change="change"
>
<el-option
v-for="item in sparklineTypeList"
:key="item.value"
:label="$t(item.name)"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</div>
<!-- Right Y Axis -->
@@ -1198,6 +1215,9 @@ export default {
case 'bubble':
case 'rank':
case 'funnel':
if (this.oldType === 'stat') {
this.chartConfig.param.sparklineMode = 'line'
}
if (this.oldType === 'stat' || this.oldType === 'gauge' || this.oldType === 'sankey' || this.oldType === 'hexagon' || this.oldType === 'bubble' || this.oldType === 'rank' || this.oldType === 'funnel') {
break
}
@@ -1221,7 +1241,8 @@ export default {
operator: 'equal',
varValue: '',
result: 'show'
}
},
sparklineMode: 'line'
}
break
case 'bar':

View File

@@ -251,6 +251,9 @@ export default {
delete params.param.min
delete params.param.max
}
if (!this.isShowSparkline(params.type)) {
delete params.param.sparklineMode
}
if (!params.x && !params.y && this.from === 'endpointQuery') { // endpointQuery 新增 放在最后
params.x = 0
params.y = 999

View File

@@ -231,6 +231,13 @@ export default {
return true
default: return false
}
},
isShowSparkline (type) {
switch (type) {
case 'stat':
return true
default: return false
}
}
}
}

View File

@@ -378,20 +378,25 @@ export default {
name: this.$t('dashboard.panel.chartForm.typeVal.point.label')
}
],
isChoose: []
sparklineTypeList: [
{
id: 'line',
name: this.$t('dashboard.panel.chartForm.typeVal.line.label')
},
{
id: 'area',
name: this.$t('dashboard.panel.chartForm.typeVal.stackArea.label')
},
{
id: 'none',
name: this.$t('project.topology.none')
}
]
}
},
mixins: [rz],
methods: {
isStat,
// 隐藏icon列表
closeChoose (index) {
this.$set(this.isChoose, index, false)
},
// 显示icon列表
chooseIcon (value, index) {
this.$set(this.isChoose, index, !value)
},
// icon点击
iconActive (item, subItem, index) {
if (item.icon === subItem.value) {