NEZ-1254 feat: chart架构、时序图定义

This commit is contained in:
chenjinsong
2021-11-26 20:13:54 +08:00
parent ed9bf789d1
commit d892f4e645
17 changed files with 987 additions and 125 deletions

View File

@@ -74,6 +74,7 @@
"generate-asset-webpack-plugin": "^0.3.0",
"git-revision-webpack-plugin": "^3.0.6",
"html-webpack-plugin": "^2.30.1",
"lodash": "^4.17.21",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",

View File

@@ -1,6 +1,8 @@
.panel-chart {
border: 1px solid $--chart-box-border-color;
height: 100%;
display: flex;
flex-direction: column;
.chart-header {
display: flex;
@@ -68,3 +70,102 @@
}
}
}
.nz-chart {
height: calc(100% - 39px);
.nz-chart__component {
display: flex;
height: 100%;
.chart__canvas {
flex: 1;
}
&.nz-chart__component--bottom {
flex-direction: column;
.legend-container {
width: 100%;
max-height: 80px;
min-height: 25px;
}
}
&.nz-chart__component--right {
flex-direction: row;
}
&.nz-chart__component--left {
flex-direction: row-reverse;
}
&.nz-chart__component--right, &.nz-chart__component--left {
.legend-container {
flex-direction: column;
width: unset;
max-width: 50%;
max-height: unset;
min-height: unset;
}
}
}
}
.legend-container {
display: flex;
flex-wrap: wrap;
overflow: auto;
font-size: 12px;
text-align: left;
line-height: 18px;
padding: 0 10px 3px 10px;
box-sizing: border-box;
.legend-item {
white-space: nowrap;
margin-right: 10px;
cursor: pointer;
display: inline-block;
line-height: 20px;
}
.legend-item, .legend--table-row {
&.legend-item--inactive, &.row--inactive {
color: $--color-text-secondary;
.legend-shape {
background-color: $--background-color-1 !important;
}
}
}
// 表格类型
.legend--table {
display: table;
width: 100%;
.legend--table-row {
display: table-row;
width: 100%;
}
.legend--table-row:not(:first-of-type):hover {
background-color: $--background-color-1;
cursor: pointer;
}
.legend--table-row:first-of-type {
color:#33a2e5;
font-weight: bold;
font-size: 12px;
.legend--table-cell:not(:first-of-type) {
cursor: pointer;
}
}
.legend--table-cell:first-of-type {
max-width: 600px;
width: 90%;
}
.legend--table-cell {
display: table-cell;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
padding: 1px 5px;
box-sizing: border-box;
min-width: 50px;
}
}
}

View File

@@ -23,7 +23,7 @@
.ft-gr{
color:lightgray;
}
.legend-container{
/*.legend-container{
width: calc(100% - 15px);
overflow: auto;
max-height:80px;
@@ -48,7 +48,7 @@
.legend-container table tr:hover{
background-color: $--background-color-1;
}
}*/
.nz-icon-warning{
color: #e6a23c;
}
@@ -56,7 +56,7 @@
max-height: 80px;
min-height:25px;
}
.legend-item{
/*.legend-item{
text-overflow:ellipsis;
white-space:nowrap;
margin-right:10px;
@@ -65,7 +65,7 @@
display:inline-block;
float:left;
line-height: 20px;
}
}*/
.nz-chart-dropdown {
position: absolute;
top: 30px;

View File

@@ -775,7 +775,7 @@ export default {
if (!params.panelId) {
this.finshGetData = false
return
} // 没有panelId不调用接口
}
this.$get('visual/panel/chart?panelId=' + params.panelId + '&groupId=0' + '&pageSize=-1').then(response => {
response = chartData
console.log(chartData)

View File

@@ -1,10 +1,85 @@
<template>
<div class="nz-chart">
<chart-no-data v-if="isNoData"></chart-no-data>
<template v-else>
<chart-time-series
v-if="isTimeSeries(chartInfo.type)"
:chart-data="chartData"
:chart-info="chartInfo"
:chart-option="chartOption"
></chart-time-series>
</template>
</div>
</template>
<script>
import chartAssetInfo from './chart/chartAssetInfo'
import chartBar from './chart/chartBar'
import chartClock from './chart/chartClock'
import chartDiagram from './chart/chartDiagram'
import chartEndpointInfo from './chart/chartEndpointInfo'
import chartGauge from './chart/chartGauge'
import chartGroup from './chart/chartGroup'
import chartLog from './chart/chartLog'
import chartNoData from './chart/chartNoData'
import chartPie from './chart/chartPie'
import chartStat from './chart/chartStat'
import chartTable from './chart/chartTable'
import chartText from './chart/chartText'
import chartTimeSeries from './chart/chartTimeSeries'
import chartTreemap from './chart/chartTreemap'
import chartUrl from './chart/chartUrl'
import chartValue from './chart/chartValue'
import { getOption, isTimeSeries } from './chart/tools'
import lodash from 'lodash'
export default {
name: 'chart'
name: 'chart',
components: {
chartAssetInfo,
chartBar,
chartClock,
chartDiagram,
chartEndpointInfo,
chartGauge,
chartGroup,
chartLog,
chartNoData,
chartPie,
chartStat,
chartTable,
chartText,
chartTimeSeries,
chartTreemap,
chartUrl,
chartValue
},
props: {
chartInfo: Object,
chartData: [Object, Array, String], // 数据查询后传入chart组件chart组件内不查询只根据接传递的数据来渲染
customChartOption: Object // 需要自定义echarts的option时传入非必须传入该值时仍需传对应格式的chartData
},
data () {
return {
}
},
computed: {
isNoData () {
return !this.chartData || this.chartData.length === 0
},
chartOption () {
if (this.customChartOption) {
return lodash.cloneDeep(this.customChartOption)
} else {
return getOption(this.chartInfo.type)
}
}
},
methods: {
isTimeSeries
},
mounted () {
}
}
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div class="nz-chart__no-data"
</template>
<script>
export default {
name: 'chartNoData'
}
</script>
<style scoped>
</style>

View File

@@ -1,13 +1,209 @@
<template>
<div :class="legendPlacement" class="nz-chart__component nz-chart__component--time-series">
<div :id="`chart-canvas-${chartInfo.id}`" class="chart__canvas"></div>
<chart-legend
v-if="hasLegend"
:chart-data="chartData"
:chart-info="chartInfo"
:legends="legends"
></chart-legend>
</div>
</template>
<script>
import legend from './legend'
import chartMixin from '@/components/chart/chartMixin'
import * as echarts from 'echarts'
import lodash from 'lodash'
import moment from 'moment-timezone'
import bus from '@/libs/bus'
import { formatScientificNotation } from '@/components/common/js/tools'
import chartDataFormat from '@/components/charts/chartDataFormat'
import { randomcolor } from '@/components/common/js/radomcolor/randomcolor'
import { chartType, chartLegendPlacement } from '@/components/common/js/constants'
import { setChart } from '@/components/common/js/common'
import { initColor } from '@/components/chart/chart/tools'
let myChart = null
export default {
name: 'chart-time-series' // x轴是时间的图包括折线、柱状、堆叠、散点
name: 'chart-time-series', // x轴是时间的图包括折线、柱状、堆叠、散点
components: {
chartLegend: legend
},
mixins: [chartMixin],
data () {
return {
stackTotalColor: null
}
},
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 ''
}
}
},
methods: {
initChart (chartOption) {
this.legends = []
chartOption.series = this.handleTimeSeries(this.chartInfo, chartOption.series[0], this.chartData) // 生成series和legends
const { minTime, maxTime, minValue, maxValue } = this.getMinMaxFromData(this.chartData[0])
chartOption.xAxis.axisLabel.formatter = this.xAxisLabelFormatter(minTime, maxTime)
chartOption.tooltip.formatter = this.tooltipFormatter()
chartOption.yAxis.axisLabel.formatter = this.yAxisLabelFormatter(minValue, maxValue)
setTimeout(() => {
myChart = echarts.init(document.getElementById(`chart-canvas-${this.chartInfo.id}`))
setChart(this.chartInfo.id, myChart) // 缓存不使用vue的data是为避免整个chart被监听导致卡顿
myChart.setOption(chartOption)
})
},
getMinMaxFromData (originalData) {
let minTime = null
let maxTime = null
let minValue = null
let maxValue = null
if (!lodash.isEmpty(originalData)) {
minTime = originalData[0][0]
maxTime = originalData[originalData.length - 1][0]
const sorted = originalData.sort((a, b) => {
return a[1] - b[1]
})
minValue = sorted[0][1]
maxValue = sorted[sorted.length - 1][1]
}
return { minTime, maxTime, minValue, maxValue }
},
xAxisLabelFormatter (minTime, maxTime) {
return function (value, index) {
let offset = localStorage.getItem('nz-sys-timezone')
offset = moment.tz(offset).format('Z')
offset = Number.parseInt(offset)
const localOffset = new Date().getTimezoneOffset() * 60 * 1000 * -1 // 默认 一分钟显示时区偏移的结果
const tData = new Date(value - localOffset + offset * 60 * 60 * 1000)
let hour = tData.getHours()
hour = hour > 9 ? hour : '0' + hour // 加0补充为两位数字
let minute = tData.getMinutes()
minute = minute > 9 ? minute : '0' + minute // 如果分钟小于10,则在前面加0补充为两位数字
if (minTime !== null && maxTime !== null) {
const diffSec = (maxTime - minTime) / 1000
const secOneDay = 24 * 60 * 60// 1天的秒数
const secOneMonth = secOneDay * 30// 30天的秒数
if (diffSec <= secOneDay) { // 同一天
return [hour, minute].join(':')
} else if (diffSec < secOneMonth) { // 大于1天小于30天
return [tData.getMonth() + 1, tData.getDate()].join('/') + ' ' + [hour, minute].join(':')
} else { // 大于等于30天
return [tData.getMonth() + 1, tData.getDate()].join('/')
}
} else {
return [tData.getFullYear(), tData.getMonth() + 1, tData.getDate()].join('/') + '\n' +
[hour, minute].join(':')
}
}
},
tooltipFormatter () {
const self = this
return function (params) {
let str = '<div>'
let sum = 0
params.forEach((item, i) => {
const color = self.colorList[item.seriesIndex]
const previousItem = params.find((series) => ('Previous ' + item.seriesName) === series.seriesName)
let paramsDot = bus.countDecimals(item.data[1])
if (paramsDot < self.chartDot) {
paramsDot = self.chartDot
} else if (paramsDot > 6) {
paramsDot = 6
}
const val = formatScientificNotation(item.data[1], paramsDot)
sum += self.numberWithEConvent(val)
// TODO 改成class
str += '<div style="white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis;display: flex; justify-content: space-between; min-width: 150px; max-width: 600px; line-height: 18px; font-size: 12px;">'
str += `<div style="max-width: 500px;white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis;"><span style='display:inline-block;margin-right:5px;border-radius:10px;width:15px;height:5px;background-color: ${color};}'></span>${item.seriesName} </div>`
str += '<div style="padding-left: 10px;min-width: 75px;text-align: right">'
str += chartDataFormat.getUnit(self.chartInfo.unit ? self.chartInfo.unit : 2).compute(val, null, -1, paramsDot)
if (previousItem) {
str += '<span style="padding-left: 10px; display: inline-block;width: 65px;text-align: right">'
const previousval = formatScientificNotation(item.data[1], paramsDot)
let minusVal = 0
if (previousval <= val) {
minusVal = val - previousval
str += '+'
} else {
minusVal = previousval - val
str += '-'
}
str += chartDataFormat.getUnit(self.chartInfo.unit ? self.chartInfo.unit : 2).compute(minusVal, null, -1, paramsDot)
str += '</span>'
}
str += '</div>'
str += '</div>'
})
if (self.chartInfo.type === chartType.stackArea) {
if (!self.stackTotalColor || self.stackTotalColor == '') {
self.stackTotalColor = randomcolor()
}
sum = parseFloat(Number(sum).toFixed(2))
// TODO 改成class
str += '<div style="white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis;display: flex; justify-content: space-between; min-width: 150px; max-width: 600px; line-height: 18px; font-size: 12px;">'
str += '<div style="line-height: 18px; font-size: 12px;padding-left:0px;">'
str += `<span style='display:inline-block;margin-right:5px;border-radius:10px;width:15px;height:5px;background-color: ${self.stackTotalColor};}'></span>`
str += self.$t('dashboard.panel.chartTotal')
str += '</div>'
str += '<div style="padding-left: 10px;">'
str += chartDataFormat.getUnit(self.chartInfo.unit ? self.chartInfo.unit : 2).compute(sum, null, self.chartDot)
str += '</div>'
str += '</div>'
}
str += '</div>'
return str
}
},
yAxisLabelFormatter (minValue, maxValue) {
const self = this
return function (val, index) {
const value = formatScientificNotation(val, 2)
let chartUnit = self.chartInfo.unit
chartUnit = chartUnit || 2
const unit = chartDataFormat.getUnit(chartUnit)
// TODO 弄清楚dot逻辑
/* if (chartDataFormat.Interval(maxValue, copies, unit.type, 'min') < 1 && dot < 2) {
dot = 2
}
if (dot == 0) {
dot = 1
}
dot = bus.countDecimals(value)
if (dot < self.chartDot) {
dot = self.chartDot
} */
return unit.compute(value, index, -1, 2)
}
}
},
mounted () {
this.chartOption.color || (this.chartOption.color = initColor(20))
this.colorList = this.chartOption.color
this.initChart(this.chartOption)
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div ref="legendArea" class='legend-container'>
<!-- 带统计的是table形式 -->
<template v-if="isStatistics">
<div class="legend--table">
<div class="legend--table-row table-header">
<div class="legend--table-cell"></div>
<div v-for="statistics in chartInfo.param.legend.values" :key="statistics" class="legend--table-cell">{{statistics}}</div>
</div>
<div v-for="(item, index) in legends"
:key="index"
: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">
<span :style="{background: item.color}" class="legend-shape"></span>{{item.alias ? item.alias : item.name}}
</div>
<div v-for="(statistics, index) in item.statistics" :key="index" :class="{'legend-item--inactive': isGrey[index]}" class="legend--table-cell">{{statistics.value}}</div>
</div>
</div>
</template>
<!-- 否则是普通形式 -->
<template v-else>
<div
v-for="(item, index) in legends"
:key="index"
:class="{'legend-item--inactive': isGrey[index]}"
:title="item.alias ? item.alias : item.name"
class="legend-item"
@click="clickLegend(item.name, index)"
>
<span :style="{background: item.color}" class="legend-shape"></span>{{item.alias ? item.alias : item.name}}
</div>
</template>
</div>
</template>
<script>
import lodash from 'lodash'
import { getChart } from '@/components/common/js/common'
export default {
name: 'chartLegend',
props: {
chartInfo: Object,
chartData: Array,
legends: Array
},
data () {
return {
isGrey: [],
legendDefaultCount: 20, // 初始显示的legend条数
showLegends: [] // 要显示的legend若legend数量过多初始时这个数据里只有前20条legend
}
},
computed: {
isStatistics () {
return !lodash.isEmpty(this.chartInfo.param.legend.values)
}
},
methods: {
clickLegend (legendName, index) {
/* 点击legend
* 1.当前如果是全高亮状态则全部置灰只留被点击的legend高亮
* 2.如果点击的是唯一高亮的legend则变为全高亮状态
* 3.否则只改变被点击的legend状态
* */
let highlightNum = 0 // 高亮数量
this.isGrey.forEach(g => {
if (!g) {
highlightNum++
}
})
const hasGrey = highlightNum < this.isGrey.length // 是否有置灰的
const curIsGrey = this.isGrey[index] // 当前legend的状态
const currentIsTheOnlyOneHighlight = !curIsGrey && highlightNum === 1 // 当前legend是否是目前唯一高亮的
const echarts = getChart(this.chartInfo.id)
if (echarts) {
if (!hasGrey) { // 1.除当前legend外全置灰
echarts.dispatchAction({
type: 'legendInverseSelect'
})
echarts.dispatchAction({
type: 'legendSelect',
name: legendName
})
this.isGrey = this.isGrey.map((g, i) => i !== index)
} else if (currentIsTheOnlyOneHighlight) { // 2.全高亮
echarts.dispatchAction({
type: 'legendAllSelect'
})
this.isGrey = this.isGrey.map(() => false)
} else {
const type = curIsGrey ? 'legendSelect' : 'legendUnSelect'
echarts.dispatchAction({
type: type,
name: legendName
})
this.$set(this.isGrey, index, !this.isGrey[index])
}
}
}
},
watch: {
legends (n) {
this.isGrey = n.map(() => false)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,7 +1,97 @@
import { initColor } from '@/components/chart/chart/tools'
export const chartTimeSeriesLineOption = {
title: {
show: false
},
legend: {
show: false
},
toolbox: {
show: false
},
tooltip: {
trigger: 'axis',
confine: false,
extraCssText: 'z-index:1000;'
// formatter: 动态生成
},
color: initColor(),
grid: {
left: 20,
right: 20,
top: 20,
bottom: 10,
containLabel: true
},
xAxis: {
type: 'time',
animation: false,
showAllSymbol: false,
axisLabel: {
interval: '0',
showMaxLabel: false,
rotate: 0,
show: true,
fontSize: 10
// formatter: 动态生成
},
axisPointer: { // y轴上显示指针对应的值
show: true
},
splitLine: {
show: true,
lineStyle: {
color: '#d9d9d9',
opacity: 0.8,
width: 1
}
},
axisLine: {
show: false
},
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
splitLine: {
show: true,
lineStyle: {
color: '#d9d9d9',
opacity: 0.8,
width: 1
}
},
// 去掉y轴
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: true,
fontSize: 10
// formatter: 动态生成
}
},
series: [{
name: '',
type: 'line',
symbol: 'emptyCircle', // 去掉点
symbolSize: [2, 2],
smooth: 0.2, // 曲线变平滑
showSymbol: false,
data: [],
lineStyle: {
width: 1,
opacity: 0.9
}
}],
useUTC: false // 使用本地时间
}
export const chartTimeSeriesBarOption = {
export const chartTimeSeriesAreaOption = {
}
export const chartTimeSeriesScatterOption = {

View File

@@ -1,7 +0,0 @@
import chartBarOption from './chartBar'
import chartGaugeOption from './chartGauge'
import chartPieOption from './chartPie'
import { chartTimeSeriesLineOption, chartTimeSeriesBarOption, chartTimeSeriesScatterOption } from './chartTimeSeries'
import chartTreemapOption from './chartTreemap'
export default { chartBarOption, chartGaugeOption, chartPieOption, chartTimeSeriesLineOption, chartTimeSeriesBarOption, chartTimeSeriesScatterOption, chartTreemapOption }

View File

@@ -0,0 +1,66 @@
import { chartType } from '@/components/common/js/constants'
import chartBarOption from './options/chartBar'
import chartGaugeOption from './options/chartGauge'
import chartPieOption from './options/chartPie'
import lodash from 'lodash'
import {
chartTimeSeriesLineOption,
chartTimeSeriesScatterOption,
chartTimeSeriesAreaOption
} from './options/chartTimeSeries'
import chartTreemapOption from './options/chartTreemap'
import {randomcolor} from "@/components/common/js/radomcolor/randomcolor";
export function getOption (type) {
let chartOption = null
switch (type) {
case chartType.stackArea: {
chartOption = lodash.cloneDeep(chartTimeSeriesAreaOption)
break
}
case chartType.point: {
chartOption = lodash.cloneDeep(chartTimeSeriesScatterOption)
break
}
case chartType.line: {
chartOption = lodash.cloneDeep(chartTimeSeriesLineOption)
break
}
case chartType.bar: {
chartOption = lodash.cloneDeep(chartBarOption)
break
}
case chartType.gauge: {
chartOption = lodash.cloneDeep(chartGaugeOption)
break
}
case chartType.pie: {
chartOption = lodash.cloneDeep(chartPieOption)
break
}
case chartType.treemap: {
chartOption = lodash.cloneDeep(chartTreemapOption)
break
}
default: break
}
return chartOption
}
export function isTimeSeries (type) {
return type === chartType.line || type === chartType.stackArea || type === chartType.point
}
export function initColor (colorNum = 20) {
const colorList = [
'#FF5200', '#3685FF', '#FF8D00', '#00DCA2',
'#954Eff', '#FFCB01', '#f65A96', '#00BFD0',
'#FF8BEA', '#4D7693', '#72577C', '#99D750',
'#DD8270', '#C475EE', '#7E83FB', '#7EB090',
'#FF9094', '#00CCF5', '#CF6684', '#4E55FF'
]
for (let i = 0; i < colorNum - 20; i++) {
colorList.push(randomcolor())
}
return colorList
}

View File

@@ -11,7 +11,7 @@
:vertical-compact="true"
>
<grid-item
v-for="(item, index) in copyDataList"
v-for="item in copyDataList"
:key="item.id"
:h="item.h"
:i="item.i"
@@ -27,6 +27,7 @@
<panel-chart
:ref="'chart' + item.id"
:chart-info="item"
:time-range="timeRange"
></panel-chart>
</grid-item>
</grid-layout>
@@ -43,10 +44,6 @@
<script>
import VueGridLayout from 'vue-grid-layout'
import { fromRoute } from '@/components/common/js/constants'
import bus from '@/libs/bus'
import chartTempData from '@/components/charts/chartTempData'
import axios from 'axios'
import chartDataFormat from '@/components/charts/chartDataFormat'
import panelChart from '@/components/chart/panelChart'
export default {
@@ -55,6 +52,7 @@ export default {
// TODO isModel
from: { type: String },
obj: Object,
timeRange: Array, // 时间范围
panelLock: { type: Boolean, default: true },
dataList: Array // 看板中所有图表信息
},
@@ -104,9 +102,16 @@ export default {
handler (n, o) {
this.noData = !n || n.length < 1
this.copyDataList = n.map(item => {
let param = {}
try {
param = JSON.parse(item.param)
} catch (e) {
console.info(e)
}
return {
...item,
i: item.id
i: item.id,
param
}
})
}

View File

@@ -0,0 +1,92 @@
import lodash from 'lodash'
import { getMetricTypeValue } from '@/components/common/js/tools'
export default {
data () {
return {
colorList: [],
chartDot: 2,
legends: [] // { name, alias, color, statistics: [{type: min, value: xxx}, ...] }
}
},
props: {
chartInfo: Object,
chartData: Array,
chartOption: Object
},
methods: {
handleTimeSeries (chartInfo, seriesTemplate, originalDatas) {
const series = []
let colorIndex = 0
originalDatas.forEach((originalData, expressionIndex) => {
originalData.forEach((data, dataIndex) => {
const s = lodash.cloneDeep(seriesTemplate)
s.data = data.values
s.name = this.handleLegend(chartInfo, data, expressionIndex, dataIndex, colorIndex)
if (chartInfo.param.stack) { // 堆叠
s.stack = 'Total'
s.areaStyle = {}
}
series.push(s)
colorIndex++
})
})
return series
},
// 单个legend
handleLegend (chartInfo, data, expressionIndex, dataIndex, colorIndex) {
let legend = '' // up
if (data.metric.__name__) {
legend += `${data.metric.__name__}{`
}
const tagKeysArr = Object.keys(data.metric)
tagKeysArr.forEach(tagKey => {
if (tagKey !== '__name__') {
legend += `${tagKey}="${data.metric[tagKey]}",`
}
if (legend.endsWith(',')) {
legend = legend.substr(0, legend.length - 1)
}
if (data.metric.__name__) {
legend += '}'
}
})
if (!legend) {
legend = chartInfo.elements[expressionIndex].expression
}
// 处理legend别名
let alias = this.handleLegendAlias(legend, chartInfo.elements[expressionIndex].legend)
if (!alias) {
alias = legend
}
const name = legend + '-' + chartInfo.elements[expressionIndex].id + '-' + dataIndex
// 若需要统计,处理统计数据
const statisticsTypes = chartInfo.param.legend.values
let statistics = []
if (!lodash.isEmpty(statisticsTypes)) {
statistics = statisticsTypes.map(type => {
return { type, value: getMetricTypeValue(data.values, type) }
})
}
this.legends.push({ name, alias, statistics, color: this.colorList[colorIndex] })
return name
},
handleLegendAlias (legend, aliasExpression) {
if (/\{\{.+\}\}/.test(aliasExpression)) {
const labelValue = aliasExpression.replace(/(\{\{.+?\}\})/g, function (i) {
const label = i.substr(i.indexOf('{{') + 2, i.indexOf('}}') - i.indexOf('{{') - 2)
const reg = new RegExp(label + '=".+?"')
let value = null
if (reg.test(legend)) {
const find = legend.match(reg)[0]
value = find.substr(find.indexOf('"') + 1, find.lastIndexOf('"') - find.indexOf('"') - 1)
}
return value || label
})
return labelValue
} else {
return aliasExpression
}
}
}
}

View File

@@ -6,19 +6,23 @@
:chart-info="chartInfo"
></chart-header>
<!-- chart -->
<div class="chart-container">
<!-- 数据获取后传入chart组件chart组件内不发送查询请求只根据接传递的数据来渲染 -->
<!-- 数据查询后传入chart组件chart组件内不查询只根据接传递的数据来渲染 -->
<chart
:chart-data="chartData"
:chart-info="chartInfo"
></chart>
</div>
</div>
</template>
<script>
import chartHeader from '@/components/chart/chartHeader'
import chart from '@/components/chart/chart'
import { isTimeSeries } from './chart/tools'
import { chartType, fromRoute } from '@/components/common/js/constants'
import bus from '@/libs/bus'
import axios from 'axios'
import chartTempData from '@/components/charts/chartTempData'
export default {
name: 'panelChart',
components: {
@@ -26,16 +30,83 @@ export default {
chart
},
props: {
chartInfo: Object
chartInfo: Object, // 其中的param json串已转化为对象
timeRange: Array // 时间范围
},
data () {
return {
chartData: null
chartData: []
}
},
methods: {
getChartData () {
getChartData (isRefresh) {
// TODO assetInfo、endpointInfo、echarts等进行不同的处理
let startTime = ''
let endTime = ''
if (isRefresh) { // 刷新则视情况更新时间范围
const now = new Date(bus.computeTimezone(new Date().getTime()))
const origin = new Date(this.timeRange[1])
const numInterval = now.getTime() - origin.getTime()
if (numInterval >= 60000) { // 大于1分钟则start、end均往后移numInterval否则时间不变
startTime = this.getNewTime(this.timeRange[0], numInterval)
endTime = bus.timeFormate(now, 'yyyy-MM-dd hh:mm:ss')
} else {
startTime = this.timeRange[0]
endTime = this.timeRange[1]
}
} else {
startTime = this.timeRange[0]
endTime = this.timeRange[1]
}
const step = bus.getStep(startTime, endTime)
startTime = this.$stringTimeParseToUnix(startTime)
endTime = this.$stringTimeParseToUnix(endTime)
const elements = this.chartInfo.elements || []
this.query(elements, startTime, endTime, step)
},
query (elements, startTime, endTime, step) {
switch (this.chartInfo.dataSource) {
case 1:
case 2: {
let urlPre = ''
if (this.chartInfo.dataSource === 1) {
urlPre = '/prom'
} else if (this.chartInfo.dataSource === 2) {
urlPre = '/logs/loki'
}
const requests = elements.map((element) => {
if (this.from === fromRoute.chartTemp) {
return chartTempData
}
let query = `${urlPre}/api/v1/query_range?start=${startTime}&end=${endTime}&step=${step}`
if (isTimeSeries(this.chartInfo.type)) {
query += `&nullType=${this.chartInfo.param.nullType || 'null'}`
}
query += `&query=${element.expression}`
return this.$get(query)
})
const chartData = []
axios.all(requests).then(res => {
res.forEach(r => {
if (r.status === 'success') {
chartData.push(r.data.result)
} else {
chartData.push({ error: r.msg || r.error || r })
}
})
this.chartData = chartData
})
break
}
case 3: {
break
}
case 4: {
break
}
}
}
},
mounted () {

View File

@@ -12,6 +12,7 @@ const chartData = {
name: '123',
panelId: 1243,
groupId: 0,
dataSource: 1,
span: 12,
height: 6,
updateBy: 1,
@@ -19,33 +20,22 @@ const chartData = {
type: 'line',
unit: 2,
weight: 0,
param: {
last: 0,
legendValue: {
total: 'off',
min: 'off',
avg: 'off',
last: 'off',
max: 'off'
},
threshold: '',
valueMapping: {
mapping: [
{
color: {
bac: '#fff',
text: '#000'
},
text: '',
value: ''
}
],
type: 'text'
},
state: '1',
url: '',
nullType: 'null'
},
param: '{' +
' "style":"line",' +
' "stack":false,' +
' "legend":{' +
' "placement":"bottom",' +
' "values":["min","max","avg","first","last","total"]' +
' },' +
' "thresholds":[{' +
' "color":"#eee",' +
' "val":"10.1"' +
' },{' +
' "color":"#aaa",' +
' "val":"base"' +
' }],' +
' "nullType":"zero"' +
'}',
pid: null,
buildIn: 0,
remark: '123',
@@ -59,7 +49,17 @@ const chartData = {
{
id: 68527,
chartId: 690483,
expression: '123',
// expression: 'up{asset="44.37"}',
expression: 'label_replace(up{instance=~\'.*10090\',job=\'\'},"asset","$1","instance","(.*)")',
type: 'expert',
legend: '{{asset}}',
buildIn: 0,
seq: null
},
{
id: 68528,
chartId: 690483,
expression: 'Hadoop_HBase_Healthy',
type: 'expert',
legend: '',
buildIn: 0,
@@ -125,6 +125,7 @@ const chartData = {
name: '233',
panelId: 1243,
groupId: 0,
dataSource: 1,
span: 6,
height: 4,
updateBy: 1,
@@ -132,32 +133,22 @@ const chartData = {
type: 'line',
unit: 2,
weight: 1,
param: {
last: 0,
legendValue: {
total: 'off',
min: 'off',
avg: 'off',
last: 'off',
max: 'off'
},
threshold: '',
valueMapping: {
mapping: [
{
color: {
bac: '#fff',
text: '#000'
},
text: '',
value: ''
}
],
type: 'text'
},
url: '',
nullType: 'null'
},
param: '{' +
' "style":"line",' +
' "stack":true,' +
' "legend":{' +
' "placement":"right"' +
// ' "values":["min","max","avg","first","last","total"]' +
' },' +
' "thresholds":[{' +
' "color":"#eee",' +
' "val":"10.1"' +
' },{' +
' "color":"#aaa",' +
' "val":"base"' +
' }],' +
' "nullType":"zero"' +
'}',
pid: null,
buildIn: 0,
remark: '',
@@ -205,7 +196,6 @@ const chartData = {
type: null,
unit: null,
weight: null,
param: null,
pid: null,
buildIn: null,
remark: null,
@@ -241,35 +231,26 @@ const chartData = {
height: 4,
updateBy: 1,
updateAt: '2021-11-10 09:51:06',
type: 'group',
type: 'line',
unit: 2,
weight: 2,
param: {
last: 0,
legendValue: {
total: 'off',
min: 'off',
avg: 'off',
last: 'off',
max: 'off'
},
threshold: '',
valueMapping: {
mapping: [
{
color: {
bac: '#fff',
text: '#000'
},
text: '',
value: ''
}
],
type: 'text'
},
url: '',
nullType: 'null'
},
dataSource: 1,
param: '{' +
' "style":"line",' +
' "stack":true,' +
' "legend":{' +
' "placement":"left"' +
// ' "values":["min","max","avg","first","last","total"]' +
' },' +
' "thresholds":[{' +
' "color":"#eee",' +
' "val":"10.1"' +
' },{' +
' "color":"#aaa",' +
' "val":"base"' +
' }],' +
' "nullType":"zero"' +
'}',
pid: null,
buildIn: 0,
remark: '123',
@@ -279,7 +260,26 @@ const chartData = {
w: 6,
h: 4,
i: 690485,
elements: null,
elements: [
{
id: 68527,
chartId: 690483,
expression: 'up{asset="44.37"}',
type: 'expert',
legend: '',
buildIn: 0,
seq: null
},
{
id: 68528,
chartId: 690483,
expression: 'Hadoop_HBase_Healthy',
type: 'expert',
legend: '',
buildIn: 0,
seq: null
}
],
sync: null,
panel: {
id: 1243,

View File

@@ -366,3 +366,50 @@ export const fromRoute = {
apiKey: 'apiKey',
chartTemp: 'chartTemp'
}
export const chartDataSource = [
{
label: 'metrics',
value: 1
},
{
label: 'logs',
value: 2
},
{
label: 'misc',
value: 4
},
{
label: 'system',
value: 3
}
]
export const chartType = {
line: 'line',
stackArea: 'stackArea',
point: 'point',
bar: 'bar',
table: 'table',
singleStat: 'singleStat',
gauge: 'gauge',
pie: 'pie',
treemap: 'treemap',
log: 'log',
text: 'text',
url: 'url',
group: 'group',
diagram: 'diagram',
assetInfo: 'assetInfo',
endpointInfo: 'endpointInfo',
topology: 'topology',
map: 'map'
}
export const chartLegendPlacement = {
hidden: 'hidden',
left: 'left',
right: 'right',
bottom: 'bottom'
}

View File

@@ -80,7 +80,7 @@
</template>
</div>
<div id="tableList" class="table-list">
<div id="dashboardScrollbar" ref="dashboardScrollbar" class="table-list-box" style="overflow: auto;">
<div id="dashboardScrollbar" ref="dashboardScrollbar" class="table-list-box">
<div class="box-content" v-loading="chartListLoading">
<chart-list
ref="chartList"
@@ -88,6 +88,7 @@
:data-list="dataList"
:from="fromRoute.panel"
:panel-lock="panelLock"
:time-range="searchTime"
@on-edit-chart="editChart"
@on-refresh-time="refreshTime"
@on-remove-chart="delChart"
@@ -357,7 +358,6 @@ export default {
this.$get('visual/panel/chart/' + data.id).then(res => {
if (res.code === 200) {
const chartData = res.data.data
// console.log(typeof chartData.param)
if (typeof chartData.param === 'string') {
chartData.param = chartData.param ? JSON.parse(chartData.param) : {}
}
@@ -456,7 +456,6 @@ export default {
},
// 获取数据,用在子页面
getData (params) {
console.info(0)
if (!this.hasButton('panel_view')) {
return
}
@@ -470,7 +469,6 @@ export default {
}
this.$get('visual/panel/chart?panelId=' + params.panelId + '&groupId=0' + '&pageSize=-1').then(response => {
if (response.code === 200) {
console.info(1)
this.chartListLoading = false
this.dataList = chartData.data.list.map(item => {
return {