This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nezha-nezha-fronted/nezha-fronted/src/components/charts/chartPreview.vue
2020-12-16 11:26:51 +08:00

1412 lines
60 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style scoped>
.chart-preview-dialog{
}
.nz-preview-picker{
}
.v-modal{
}
.hidden{
visibility: hidden;
}
.visible{
visibility: visible;
}
.chart-title {
display: flex;
justify-content: space-between;
}
.chart-title-text{
max-width:calc(100% - 20px);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
</style>
<style>
.char-url-preview html{
}
#chartPreviewDailog .el-dialog__body {
padding-bottom:20px !important;
}
#chartPreviewDailog .el-dialog__header{
padding: 20px 20px 0px !important;
}
</style>
<template>
<el-dialog class="chart-preview-dialog nz-dialog" id="chartPreviewDailog"
:visible.sync="screenModal"
:width="dailogWidth"
:modal-append-to-body='false'
@close="handleClose"
@opened="initDialog">
<el-popover
v-if="isError"
placement="top-start"
:close-delay=10
trigger="hover"
popper-class="chart-error-popper">
<div >{{errorContent}}</div>
<span slot="reference" class="panel-info-corner panel-info-corner--error">
<i class="nz-icon nz-icon-warning fa-model" ></i>
<span class="panel-info-corner-inner"></span>
</span>
</el-popover>
<div slot="title" class="chart-title">
<span class="nz-dialog-title chart-title-text">{{chart.title}}</span>
<div class="float-right panel-calendar dialog-tool" v-if="chart.type!=='url' && chart.type !=='alertList'">
<time-picker ref="calendarPanel" class="nz-dashboard-picker" style="margin-top: -12px;" @change="dateChange" v-if="chart.type !='text'"></time-picker>
<!--
<el-date-picker ref="calendar" prefix-icon=" " size="mini" class="nz-preview-picker"
format="yyyy/MM/dd HH:mm"
@change="dateChange"
v-model="searchTime"
type="datetimerange"
:picker-options="pickerOptions"
:range-separator="$t('dashboard.panel.to')"
:start-placeholder="$t('dashboard.panel.startTime')"
:end-placeholder="$t('dashboard.panel.endTime')"
align="right">
</el-date-picker>-->
</div>
</div>
<template v-if="chart.type==='line'||chart.type==='bar'||chart.type==='stackArea' || chart.type == 'endpointInfo'">
<div id="chartEchartPreview" @mouseenter="mouseEnterFullChart" @mouseleave="mouseLeaveFullChart">
<div class="line-area" ref="screenShowArea" id="screenShowArea" style="margin-top:0px;"></div>
<div class="chart-no-data" v-show="noData">No Data</div>
<template v-if="!hasLegendOptions">
<div @mouseenter="mouseEnterFullChart" @mouseleave="mouseLeaveFullChart" class="legend-container legend-container-screen" ref="screenLegendArea" v-show="showLegend">
<div v-for="(item, index) in screenLegendList" :title="item.alias?item.alias:item.name" @click="clickScreenLegend(item.name,index)" class="legend-item" :class="{'ft-gr':isGreyScreen[index]}" :key="'legend_' + item.name+'_'+index">
<span class="legend-shape" :style="{background:(isGreyScreen[index]?'#D3D3D3':item.color)}"></span>{{item.alias?item.alias:item.name}}
<br/><!--bgColorList[index]-->
</div>
</div>
</template>
<template v-else>
<div @mouseenter="mouseEnterFullChart" @mouseleave="mouseLeaveFullChart" class="legend-container legend-container-screen" ref="screenLegendArea" v-show="showLegend">
<table style="width: 100%" border="0" cellpadding="0" cellspacing="0">
<th style="width: 100%"></th>
<template v-for="legendOption in screenLegendOptions">
<th v-if="legendOption.value == 'on'" class="option-th legend-option-cell" >
<span @click="legendValueSort(legendOption,screenLegendList,screenLegendOptions)">{{legendOption.option}}</span>
<span ><i style="font-size: 12px !important;" :class="{'nz-icon nz-icon-arrow-down':legendOption.sort =='asc','nz-icon nz-icon-arrow-up':legendOption.sort=='desc'}" ></i></span>
</th>
</template>
<tbody>
<tr v-for="(item, index) in screenLegendList" :key="'legend_' + item.name+'_'+index">
<td>
<div :title="item.alias?item.alias:item.name" @click="clickScreenLegend(item.name,index)" class="legend-item" :class="{'ft-gr':isGreyScreen[index]}" >
<span class="legend-shape" :style="{background:(isGreyScreen[index]?'#D3D3D3':item.color)}"></span>{{item.alias?item.alias:item.name}}
</div>
</td>
<template v-for="legendOption in screenLegendOptions">
<td v-if="legendOption.value == 'on'" class="legend-option-cell">{{formatLegendData(item[legendOption.option])}}</td>
</template>
</tr>
</tbody>
</table>
</div>
</template>
</div>
</template>
<template v-if="chart.type==='singleStat'">
<div id="chartSingleStatPreview" @mouseenter="mouseEnterFullChart" @mouseleave="mouseLeaveFullChart"
style=" height:100%;display:table;text-align:center;width:calc(100% - 16px);color:#000;" >
<div class="line-area" ref="singleStatArea" id="singleStatArea" :style="{color:mapping&&mapping.color?mapping.color.text:'#000',background:mapping&&mapping.color?mapping.color.bac:'#fff'}" style="margin-top:0px; text-align:center;
vertical-align: middle;
display:table-cell;
font-size:30px;">
{{serieSingleStat}}
<div class="chart-no-data" v-show="noData">No Data</div>
</div>
</div>
</template>
<template v-if="chart.type==='table'">
<div id="chartTablePreview" :class="{'visible':tableShow,'hidden':!tableShow}" >
<el-table :data="seriesItemScreen" border class="nz-table" height="95%" id="tableContainer" style="margin-top: 10px;" tooltip-effect="light">
<!-- <el-table-column sortable :show-overflow-tooltip="true" prop="metric" :label="$t('dashboard.panel.chartTableColumn.metric')" ></el-table-column>-->
<!-- <el-table-column sortable :show-overflow-tooltip="true" prop="label" :label="$t('dashboard.panel.chartTableColumn.label')" ></el-table-column>-->
<el-table-column sortable prop="time" :label="$t('dashboard.panel.chartTableColumn.time')" width="145" ></el-table-column>
<el-table-column sortable prop="element" :show-overflow-tooltip="true" :label="$t('dashboard.panel.chartTableColumn.element')" >
<template slot-scope="scope">
{{ scope.row.element.alias?scope.row.element.alias:scope.row.element.element}}
</template>
</el-table-column>
<el-table-column sortable prop="value" :label="$t('dashboard.panel.chartTableColumn.value')" width="90">
<template slot-scope="scope">
<div :style="{color:scope.row.mapping?scope.row.mapping.color.text:'#000',background:scope.row.mapping?scope.row.mapping.color.bac:'#fff',margin: '10px 0'}">{{ scope.row.value}}</div>
</template>
</el-table-column>
</el-table>
<Pagination :pageObj="screenPageObj" :post-page-sizes="pageSizes" @pageNo='screenPageNo' @pageSize='screenPageSize' ref="Pagination" ></Pagination>
</div>
</template>
<template v-if="chart.type==='url'">
<div id="chartUrlPreview" :class="{'visible':urlShow,'hidden':!urlShow}" class="char-url-preview">
<iframe id="chartUrlFrame" frameborder="0" width="100%" height="100%" name="showHere" scrolling=auto
style="z-index:5000;"
></iframe>
</div>
</template>
<chart-alert-list v-if="chart.type === 'alertList'" ref="alertListChart" id="chartAlertListPreview" :chart-info="chart"></chart-alert-list>
<template v-if="chart.type==='text'">
<div id="chartTextPreview" class="chart-text-preview">
<div style="height: 100%; overflow: auto;">
<div style="height: 100%;" v-html="chart.param.text" ></div>
</div>
</div>
</template>
<loading ref="loadingPreview"></loading>
</el-dialog>
</template>
<script>
import axios from 'axios';
import echarts from 'echarts';
import bus from '../../libs/bus';
import loading from "../common/loading";
import chartDataFormat from './chartDataFormat'
import {randomcolor} from '../common/js/radomcolor/randomcolor.js'
import timePicker from '../common/timePicker'
import chartAlertList from './chart-alert-list'
import chartConfig from "../page/dashboard/overview/chartConfig";
import textChart from './text-chart'
export default {
name: 'chartPreview',
components: {
'loading': loading,
'time-picker':timePicker,
'chart-alert-list':chartAlertList,
'text-chart':textChart
},
props: {
panelId:Number,
},
data() {
return {
chart:{},
noData:false,
//data: {}, // 该图表信息,chartItem
dailogWidth:'80%',
//seriesItem: [], // 保存信息
isError:false,
errorContent:'',
serieSingleStat:'',
seriesItemScreen:[],
pageSizes:[50,100,200],
screenPageObj:{
pageNo: 1,
pageSize: 50,
total: 0
},
unit:{},
//images: '',
//toolbox: false,
items: {
metric_name: [], // 每条数据列名称
xAxis: [],
theData: [], // series数据组
},
isStackArea:false,
echartModalStore: null, // 全屏查看时数据
chartType: 'line', // 图表类型
screenModal: false,
// 查询数据使用
stableFilter: {}, // 保存数据使用,初始化起止时间,单图or多图等
legend:[],
legendList:[],
screenLegendList:[],
isGrey:[],
isGreyScreen:[],
bgColorList: ['#7bbfea', '#b3424a', '#f05b72', '#596032', '#bd6758',
'#cd9a5b', '#918597', '#70a19f', '#005344', '#FF00FF',
'#f7acbc', '#5f5d46', '#66ffff', '#ccFF66', '#f47920',
'#769149', '#1d953f', '#abc88b', '#7f7522', '#9b95c9',
'#f3715c', '#ea66a6', '#d1c7b7', '#9d9087', '#77787b',
'#f58220', '#c37e00', '#00ae9d', '#f26522', '#76becc',
'#76624c', '#d71345', '#2468a2', '#ca8687', '#1b315e',
],
//firstShow: false, // 默认不显示操作按钮,
//dropdownMenuShow:false,
showLegend:true,
tableShow:true,
urlShow:true,
searchTime: bus.getTimezontDateRange(),
//oldSearchTime:[],
minHeight:200,
chartSpaceHeight:5,//top-border: 1,bottom-border: 1,padding-bottome:3
titleHeight:58,
legendHeight:80,
pageHeight:40,
hasLegendOptions:false,
screenLegendOptions:[],
mapping:{},
};
},
computed: {
},
watch: {},
methods: {
// 全屏查看
show(chartInfo) {
this.isError = false;
this.searchTime = bus.getTimezontDateRange();
this.chart = JSON.parse(JSON.stringify(chartInfo));
let chartType= chartInfo.type;
let chartContainerId = 'chartEchartPreview';
if(chartType==='table'){
this.tableShow = false;
chartContainerId = 'chartTablePreview';
}else if (chartType === 'line' || chartType === 'bar' || chartType === 'stackArea' || chartType === 4) {
this.isGreyScreen=[];
this.showLegend = false;
chartContainerId = 'chartEchartPreview';
}else if(chartType==='url'){
this.urlShow = false;
chartContainerId = 'chartUrlPreview';
}else if (chartType === 'singleStat') {
chartContainerId = 'chartSingleStatPreview';
}else if(chartType === 'alertList'){
chartContainerId = 'chartAlertListPreview';
}else if(chartType == 'text'){
chartContainerId = 'chartTextPreview'
}
//设置高度 chart-table
this.$nextTick(() => {
//const chartBox = document.getElementById('chartPreviewDailog');
//let height = this.chart.height;
let height = Math.round(this.chart.height/10)*10
if(height<this.minHeight){
height = this.minHeight;
}
let dailogBox = document.querySelector("#chartPreviewDailog .el-dialog");
dailogBox.style.height = `${height}px`;
const addChartBox = document.querySelector('.right-box-add-chart');
//addChartBox.style.cssText = 'z-index:1500 !important';
if(chartType==='table'){
const chartDiv = document.getElementById(chartContainerId);
chartDiv.style.height = `${height-this.chartSpaceHeight-this.titleHeight-25}px`;
const tableBox = document.getElementById('tableContainer');
tableBox.style.height = `${height-this.titleHeight-this.pageHeight-25}px`;//-75-32+25
}else{
const chartDiv = document.getElementById(chartContainerId);
chartDiv.style.height = `${height-this.chartSpaceHeight-this.titleHeight-15}px`;
}
});
this.screenModal = true;
},
setLoadFrame(){
if(this.chart.type==='url'){
let that = this;
this.$nextTick(() => {
let iframeBox = document.querySelector('#chartUrlPreview');
let iframe = document.querySelector('#chartUrlFrame');
// 处理兼容行问题
if (iframe.attachEvent) {
iframe.attachEvent('onload', function () {
// iframe加载完毕以后执行操作
that.$refs.loadingPreview.endLoading();
})
} else {
iframe.onload = function () {
// iframe加载完毕以后执行操作
that.$refs.loadingPreview.endLoading();
}
}
iframe.src = this.chart.param.url;
});
}
},
filterShowData(source,pageObj){
return source.slice((pageObj.pageNo-1)*pageObj.pageSize,pageObj.pageNo*pageObj.pageSize)
},
initDialog(){
//此时初始化才能获得screenShowArea对象否则此对象为undefined无法初始化图表
let chartType= this.chart.type;
if(chartType==='table'){
}else if (chartType === 'line' || chartType === 'bar' || chartType === 'stackArea' || chartType === 4 || chartType == "endpointInfo") {
if(this.echartModalStore){
this.echartModalStore.clear();
}
}else if(chartType==='url'){
}
//后台获得数据
if(this.chart.type!=='url'&&this.chart.type !=='text'){
this.$refs.loadingPreview.startLoading();
this.getChartData();
}else if(this.chart.type == 'url'){
this.$refs.loadingPreview.startLoading();
this.urlShow = true;
this.setLoadFrame();
}
},
getAlertListChartData:function(chartInfo,filterType){
this.$refs.alertListChart.getAlertList(filterType,true,this.chart);
this.$refs.loadingPreview.endLoading();
},
// 获取一个图表具体数据
getChartData() {
const chartItem = this.chart;
if(chartItem.type === 'alertList'){
this.getAlertListChartData(chartItem,null);
return;
}
const len = chartItem.elements.length;
// 没有数据的设置提示信息暂无数据-针对每一个图
if (len === 0) {
this.noData=true;
this.$nextTick(() => {
if (chartItem.type === 'table') {//表格
this.setTableData([]);
} else if (chartItem.type === 'line' || chartItem.type === 'bar' || chartItem.type === 'stackArea' || chartItem.type === 4 || chartItem.type === "endpointInfo") {
this.setData(chartItem, [], []);
}else if(chartItem.type ==='singleStat'){
this.serieSingleStat = "";
this.$refs.loadingPreview.endLoading();
}
});
} else {
let startTime = bus.timeFormate(this.searchTime[0], 'yyyy-MM-dd hh:mm:ss');
let endTime = bus.timeFormate(this.searchTime[1], 'yyyy-MM-dd hh:mm:ss');
if(!startTime || !endTime){//如果时间为空则默认取最近1小时
let now = new Date(bus.computeTimezone(new Date().getTime()));
startTime = bus.timeFormate(now, 'yyyy-MM-dd hh:mm:ss');
endTime = bus.timeFormate(now.setHours(now.getHours()-1), 'yyyy-MM-dd hh:mm:ss');
this.searchTime[0] = startTime;
this.searchTime[1] = endTime;
}
let step = bus.getStep(startTime,endTime);
this.$nextTick(() => {
const axiosArr = chartItem.elements.map((ele) => {
const filterItem = ele;
let query = encodeURIComponent(filterItem.expression);
if(chartItem.type === 'table'&&chartItem.param&&chartItem.param.last == 1){
return this.$get('/prom/api/v1/query_range?query=' + query + "&start=" + this.$stringTimeParseToUnix(endTime) + "&end=" + this.$stringTimeParseToUnix(endTime) + '&step=' + step);
}
return this.$get('/prom/api/v1/query_range?query='+query+"&start="+this.$stringTimeParseToUnix(startTime)+"&end="+this.$stringTimeParseToUnix(endTime)+'&step='+step);
});
// 一个图表的所有element单独获取数据
axios.all(axiosArr).then((res) => {
if (res.length > 0) {
const series = [];
let singleStatRlt = '';
const legend = [];
const tableData = [];
const sumData = {
name: 'sum',
data: [],
visible: true,
threshold: null,
};
res.forEach((response, innerPos) => {
if (response.status === 'success') {
this.isError = false;
this.errorContent = "";
if (response.data.result) {
if(chartItem.type==='singleStat'){
if(response.data.result.length===1){
let statistics = chartItem.param.statistics;
if(response.data.result[0].values){
singleStatRlt = bus.getSingleStatRlt(statistics,response.data.result[0].values);
this.noData=false
}else{
this.noData=true;
}
}else if(response.data.result.length > 1){
this.noData=true;
singleStatRlt = this.$t("dashboard.panel.singleStatErrorTip");
}
}else {
// 循环处理每个elements下获取的数据列
response.data.result.forEach((queryItem, resInnerPos) => {
const seriesItem = {
theData: {
name: '',
symbol: 'emptyCircle', //去掉点
symbolSize: [2, 2],
smooth: 0.2, //曲线变平滑
showSymbol: false,
data: [],
type: chartItem.type,
lineStyle: {
width: 1,
opacity: 0.9
},
},
metric_name: '',
};
if (chartItem.type === 'stackArea') {
seriesItem.theData.type = 'line';
seriesItem.theData.stack = chartItem.title;
seriesItem.theData.areaStyle = {"opacity": 0.3};
}
if (chartItem.type === "endpointInfo") {
seriesItem.theData.type = 'line';
}
// 图表中每条线的名字,后半部分
let host = '';//up,
if (queryItem.metric.__name__) {
host = `${queryItem.metric.__name__}{`;//up,
}
const tagsArr = Object.keys(queryItem.metric);//["__name__","asset","idc","instance","job","module","project"]
// 设置时间-数据格式对
const dpsArr = Object.entries(queryItem.values);//[ ["0",[1577959830.781,"0"]], ["1",[1577959845.781,"0"]] ]
// 判断是否有数据, && tagsArr.length > 0
if (dpsArr.length > 0) {
tagsArr.forEach((tag, i) => {
if (tag !== '__name__') {
host += `${tag}="${queryItem.metric[tag]}",`;
}
});
if (host.endsWith(',')) {
host = host.substr(0, host.length - 1);
}
if (queryItem.metric.__name__) {
host += "}";
}
if (!host || host === '') {
host = chartItem.elements[innerPos].expression;
}
//处理legend别名
let alias = this.dealLegendAlias(host, chartItem.elements[innerPos].legend);
if (!alias || alias === '') {
alias = host;
}
legend.push({name: host + resInnerPos, alias: alias});
// 图表中每条线的名字,去掉最后的逗号与空格:metric名称, 标签1=a,标签2=c
seriesItem.theData.name = host + resInnerPos;
seriesItem.metric_name = seriesItem.theData.name;
// 将秒改为毫秒
//alert('table=='+JSON.stringify(queryItem))
seriesItem.theData.data = queryItem.values.map((dpsItem, dpsIndex) => {
let t_date = new Date(dpsItem[0] * 1000);
let timeTmp = bus.timeFormate(t_date, 'yyyy-MM-dd hh:mm:ss');
tableData.push({//表格数据
// label: host.slice(host.indexOf('{') + 1,host.indexOf('}')),//label
// metric: queryItem.metric.__name__?queryItem.metric.__name__:'',//metric列
element: {element: host, alias: alias},
time: timeTmp,//采集时间
value: dpsItem[1],//数值
});
return [dpsItem[0] * 1000, dpsItem[1]];
});
series.push(seriesItem.theData);
} else if (chartItem.elements && chartItem.elements[innerPos]) {
// 无数据提示
/*
const currentInfo = chartItem.elements[innerPos];
const errorMsg = `图表 ${chartItem.title} 中 ${currentInfo.metric},${currentInfo.tags} 无数据`;
this.$message.warning({
duration: 15,
content: errorMsg,
closable: true,
});
*/
}
});
}
}
}else{
this.isError = true;
if(response.msg){
this.errorContent = response.msg;
}else if(response.error){
this.errorContent = response.error;
}else {
this.errorContent = response;
}
}
});
//if(this.$refs.editChart&&this.$refs.editChart[index]) {
if (chartItem.type === 'table') {//表格
this.setTableData(tableData);
} else if (chartItem.type === 'line' || chartItem.type === 'bar' || chartItem.type === 'stackArea' || chartItem.type === 4 || chartItem.from == "endpoint") {
if (series.length && chartItem.type === 4) {//曲线汇总
}
if(series.length<1){
this.noData=true;
}else{
this.noData=false;
}
let _chartItem = JSON.parse(JSON.stringify(chartItem));
if (chartItem.from == $CONSTANTS.fromRoute.endpoint) {
_chartItem.type = "line";
}
this.setData(_chartItem, series, legend);
}else if(chartItem.type ==='singleStat'){
if(Number(singleStatRlt)){
let singleStatTmp =parseFloat(Number(singleStatRlt).toFixed(2));//parseFloat 如果没有小数点或者小数点后都是零parseFloat() 会返回整数
if(chartItem.param.valueMapping && chartItem.param.valueMapping.type){
let type=chartItem.param.valueMapping.type;
let mappings=chartItem.param.valueMapping.mapping?chartItem.param.valueMapping.mapping:[];
let mapping;
if(type == 'value'){
mapping=mappings.find(item=>{return item.value == singleStatTmp})
}else{
mapping=mappings.find(item=>{return item.from <= singleStatTmp&& item.to >= singleStatTmp});
}
this.mapping=mapping;
if(this.mapping&&!this.mapping.color){
this.mapping.color={bac:'#fff',text:'#000'}
}
this.serieSingleStat = mapping?mapping.text.replace('{{value}}', singleStatTmp):chartDataFormat.getUnit(chartItem.unit?chartItem.unit:2).compute(singleStatTmp,null,2);
}else{
this.serieSingleStat = chartDataFormat.getUnit(chartItem.unit?chartItem.unit:2).compute(singleStatTmp,null,2);
}
}else {
this.serieSingleStat =singleStatRlt;
}
this.$refs.loadingPreview.endLoading();
}
//}
} else {
const type = chartItem.type;
//if(this.$refs.editChart[index]) {
if (type === 'table') {
this.setTableData([]);
} else if (type === 'line' || type === 'bar' || type === 'stackArea' || type === 4) {
this.setData(chartItem, []);
}else if(chartItem.type ==='singleStat'){
this.serieSingleStat = "";
this.$refs.loadingPreview.endLoading();
}
//}
}
}).catch((error) => {
if (error) {
this.$message.error(error.toString());
console.error(error)
}
});
});
}
},
clickScreenLegend(legendName,index){
//点击图表某一个legend图表只显示当前点击的曲线或柱状图其它隐藏再次点击已选中的legend ,显示全部
let curIsGrey=this.isGreyScreen[index];
if(this.echartModalStore){
this.screenLegendList.forEach((item,i)=>{
let isGrey = this.isGreyScreen[i];
if(index != i){
if(!curIsGrey && !isGrey){
this.echartModalStore.dispatchAction({
type: 'legendUnSelect',
name: item.name
});
}else if(!curIsGrey && isGrey){
this.echartModalStore.dispatchAction({
type: 'legendSelect',
name: item.name
});
}else{
this.echartModalStore.dispatchAction({
type: 'legendUnSelect',
name: item.name
});
}
}else {
this.echartModalStore.dispatchAction({
type: 'legendSelect',
name: item.name
});
}
});
this.isGreyScreen.forEach((item,i)=>{
if(index != i){
if(!curIsGrey && !item){
this.$set(this.isGreyScreen, i, true);
}else if(!curIsGrey && item){
this.$set(this.isGreyScreen, i, false);
}else{
this.$set(this.isGreyScreen, i, true);
}
}else{
if(item === true){
this.$set(this.isGreyScreen, i, false);
}
}
})
}
},
formatLegend(chartWidth,name){
if(!name){
return '';
}
//计算宽度
var span = document.createElement("span");
var result = {};
result.width = span.offsetWidth;
result.height = span.offsetHeight;
span.style.visibility = "hidden";
span.style.fontSize = 14;
span.style.fontFamily = "Arial";
span.style.display = "inline-block";
document.body.appendChild(span);
if(typeof span.textContent != "undefined"){
span.textContent = name;
}else{
span.innerText = name;
}
var txtWidth = parseFloat(window.getComputedStyle(span).width) - result.width;
document.body.removeChild(span);
if(txtWidth < chartWidth){
return name;
}else {
var charNum = `${(chartWidth-100)/(txtWidth/name.length)}`;
return name.slice(0,charNum)+'...';
}
},
// chartSite用于区分是全屏显示还是局部显示
initChart(chartInfo, dataArg, ele,legend) {
const self = this;
let minTime = null;
let maxTime = null
if(dataArg.length>0 && dataArg[0].data
&& dataArg[0].data.length>0 && dataArg[0].data[0].length>0){
let len = dataArg[0].data.length-1;
minTime = dataArg[0].data[0][0];
maxTime = dataArg[0].data[len][0];
//console.log('time==========',minTime,maxTime)
}
this.chartType = ''; // 图表类型
if ( chartInfo.type === 4) {//line,bar
this.chartType = 'line';
}
this.echartModalStore = echarts.init(ele);
var chartWidth = ele.clientWidth;
var title = {
show:false,
text: chartInfo.title || null,
textAlign: 'left',
useHTML: true,
textStyle: {
width: '60%',
fontStyle:'normal',
fontWeight:'bold',
color: "#333",
}
};
let stackIconBorderColor = (chartInfo.type==='stackArea'?'#53a3cb':'#7e7e7e');
let stackIconChooseBorderColor = (chartInfo.type==='stackArea'?'#7e7e7e':'#53a3cb');
var option = {
title:{
show:false,
},
color: this.bgColorList,
toolbox:{
show:false,
top:'0',
showTitle:false,
feature:{
dataZoom:{
yAxisIndex:false
},
magicType:{
type:['stack'],
iconStyle:{
borderColor:stackIconBorderColor,
},
emphasis:{
borderColor:stackIconChooseBorderColor,
}
}
}
},
tooltip: {
trigger: 'axis',
confine:false,
backgroundColor:'rgba(221,228,237,1)',
textStyle:{
color:'#000'
},
extraCssText:'z-index:1000;',
/*enterable:true, 导致tooltip不消失显示多个tooltip*/
position:function(point,params,dom,rect,size){
dom.style.transform = "translateZ(0)";
//提示框位置
var x=0;
var y=0;
//当前鼠标位置
var pointX = point[0];
var pointY = point[1];
//外层div大小
var viewWidth = size.viewSize[0];
var viewHeight = size.viewSize[1];
//提示框大小
var boxWidth = size.contentSize[0];
var boxHeight = size.contentSize[1];
if(pointX<(viewWidth/2)){//说明鼠标在左边放不下提示框
x=pointX+10;
}else{
x = pointX - boxWidth;
}
if(pointY<(viewHeight/2)){//说明鼠标上面放不下提示框
y = pointY+10;
}else {
y = pointY-boxHeight;
}
return [x,y];
},
formatter:function(params){
let str = `<div>`;
let sum = 0;
params.forEach((item, i) => {
let tip=legend[item.seriesIndex];
let color = self.bgColorList[item.seriesIndex];
if(i===0){
let t_date = new Date(item.data[0]);
str += [t_date.getFullYear(), t_date.getMonth() + 1, t_date.getDate()].join('-') + " "
+ [t_date.getHours(), t_date.getMinutes(),t_date.getSeconds()].join(':');
str +=`<br/>`;
}
let val = parseFloat(Number(item.data[1]).toFixed(2));
if(val===0){
val = Number(item.data[1]).toExponential(2);
}
sum +=val;
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>${tip?(tip.alias?tip.alias:tip.name):item.seriesName} </div>`;
str += `<div style="padding-left: 10px;">`;
str += function(){
if (chartInfo.from == 'endpoint') {
return val == 1 ? "up" : "down";
}
return chartDataFormat.getUnit(chartInfo.unit?chartInfo.unit:2).compute(val,null,2);
}();
str += `</div>`;
str += `</div>`;
});
if(chartInfo.type==='stackArea' || self.isStackArea){
sum = parseFloat(Number(sum).toFixed(2));
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:20px;">`
str +=self.$t("dashboard.panel.chartTotal");
str +=`</div>`;
str +=`<div style="padding-left: 10px;">`;
str +=chartDataFormat.getUnit(chartInfo.unit?chartInfo.unit:2).compute(sum,null,2);
str +=`</div>`;
str += `</div>`;
}
str +=`</div>`;
return str;
},
},
legend: {
show:false,
},
grid: {
top: 30,
left: 0,
right: 30,
containLabel: true,
bottom: 8,//156
},
xAxis: {
type: 'time',
// boundaryGap: false,//line-false; bar-true;
//data: ['20190101', '20190102', '周三', '周四', '周五', '周六', '周日']
axisLabel: {
interval: 0,
rotate: 0,
formatter: function (value) {
value = bus.computeTimezone(value);
let t_date = new Date(value);
let hour = t_date.getHours();
hour=hour>9?hour:"0"+hour; //加0补充为两位数字
let minute = t_date.getMinutes();
minute=minute>9?minute:"0"+minute; //如果分钟小于10,则在前面加0补充为两位数字
if(minTime!==null && maxTime!==null){
let diffSec = (maxTime-minTime)/1000;
let secOneDay = 24*60*60;//1天的秒数
let secOneMonth = secOneDay*30;//30天的秒数
if(diffSec<=secOneDay) {//同一天
return [hour,minute ].join(':');
}else if(diffSec<secOneMonth){//大于1天小于30天
return [t_date.getMonth() + 1, t_date.getDate()].join('/') +" "+ [hour, minute].join(':');
}else {//大于等于30天
return [t_date.getMonth() + 1, t_date.getDate()].join('/');
}
}else {
return [t_date.getFullYear(), t_date.getMonth() + 1, t_date.getDate()].join('/') + "\n"
+ [hour, minute].join(':');
}
}
},
axisPointer: {//y轴上显示指针对应的值
show: true,
},
splitLine:{
show:true
}
},
yAxis: {
type: 'value',
minInterval: 1,
splitLine:{
show:true
},
//去掉y轴--start
axisLine:{
show:false
},
axisTick: {
show: false,
},
//去掉y轴--end
axisLabel: {
formatter: function(value,index){
if (self.chart.from == 'endpoint') {
if (value == 1) {
return "up";
} else {
return "down";
}
}
let chartUnit=chartInfo.unit;
chartUnit=chartUnit?chartUnit:2;
let unit=chartDataFormat.getUnit(chartUnit);
return unit.compute(value,index);
},
},
//boundaryGap:[0,0.2]
},
useUTC: false,//使用本地时间
series: dataArg
};
if(this.echartModalStore){
this.echartModalStore.clear();
}
option.title = {};
//this.echartModalStore.setOption(option);//显示全屏界面
this.showLegend = true;
if(legend){
this.screenLegendList = [];
legend.forEach((item, i) => {
let _legend = {
name:item.name,
alias:item.alias,
color:item.color,
showText:this.formatLegend(chartWidth,item.alias?item.alias:item.name)
};
this.screenLegendList.push(_legend);
this.isGreyScreen.push(false);
});
}
if(this.hasLegendOptions){
this.computeLegendData(this.screenLegendList,dataArg,'screen');
}
this.$nextTick(() => {
let divHeight = this.$refs.screenLegendArea.offsetHeight;
this.echartModalStore.resize({height:(chartInfo.height-this.chartSpaceHeight-divHeight-this.titleHeight)});
this.echartModalStore.clear();
if(dataArg.length<1){
this.echartModalStore.setOption(chartConfig.getOption('noData'))
}else{
this.echartModalStore.setOption(option);//创建图表
}
this.$refs.loadingPreview.endLoading();
});
this.echartModalStore.on('magictypechanged', function (params) {
self.isStackArea = !self.isStackArea;
if(self.isStackArea){
this.setOption({
toolbox:{
feature:{
magicType:{
iconStyle:{
borderColor:'#7e7e7e',
},
},
}
},
tooltip: {
formatter:function(params){
let str = `<div>`;let sum = 0;
params.forEach((item, i) => {let tip=legend[item.seriesIndex];
let color = self.bgColorList[item.seriesIndex];
if(i===0){
let value=bus.computeTimezone(item.data[0]);
let t_date = new Date(value);
str += [t_date.getFullYear(), t_date.getMonth() + 1, t_date.getDate()].join('-') + " "
+ [t_date.getHours(), t_date.getMinutes(),t_date.getSeconds()].join(':');
str +=`<br/>`;
}
let val = parseFloat(Number(item.data[1]).toFixed(2));
if(val===0){
val = Number(item.data[1]).toExponential(2);
}
sum +=val;
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>${tip?(tip.alias?tip.alias:tip.name):item.seriesName} </div>`;
str += `<div style="padding-left: 10px;">`;
str += chartDataFormat.getUnit(chartInfo.unit?chartInfo.unit:2).compute(val,null,2);
str += `</div>`;
str += `</div>`;
});
if(chartInfo.type==='stackArea' || self.isStackArea){
sum = parseFloat(Number(sum).toFixed(2));
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:20px;">`
str +=self.$t("dashboard.panel.chartTotal");
str +=`</div>`;
str +=`<div style="padding-left: 10px;">`;
str +=chartDataFormat.getUnit(chartInfo.unit?chartInfo.unit:2).compute(sum,null,2);
str +=`</div>`;
str += `</div>`;
}
str +=`</div>`;
return str;
},
}
});
}
});
},
mouseEnterFullChart(){
if (this.echartModalStore) {
this.echartModalStore.setOption({
toolbox: {
show:true,
}
})
}
},
mouseLeaveFullChart(){
if (this.echartModalStore) {
this.echartModalStore.setOption({
toolbox: {
show:false,
}
})
}
},
setColor(colorNum){
this.bgColorList = [];
for(let i=0;i<colorNum;i++) {
this.bgColorList.push(randomcolor())
}
},
// 设置数据
setData(chartItem, seriesItem,legend) {
this.setColor(legend.length);
legend.forEach((t,i)=>{t.color = this.bgColorList[i]})
this.legend = legend;
//this.data = chartItem;
//this.seriesItem = seriesItem;
this.seriesItemScreen = seriesItem;
this.hasLegendOptions=this.findLegendOptions()
if(this.hasLegendOptions){
let sortedOptionKeys=['min','max','avg','last','total']
this.screenLegendOptions=sortedOptionKeys.map(item=>{ return {option:item,sort:'',value:chartItem.param.legendValue[item]}})
}
this.initChart(chartItem, seriesItem, this.$refs.screenShowArea,legend);
},
// 设置数据
setTableData(seriesItem) {
console.log('set table data',this.chart)
this.unit = chartDataFormat.getUnit(this.chart.unit);
seriesItem=seriesItem.map(item=>{
if(this.chart.param.valueMapping && this.chart.param.valueMapping.type){
let type=this.chart.param.valueMapping.type;
let mappings=this.chart.param.valueMapping.mapping?this.chart.param.valueMapping.mapping:[];
let value = item.value;
let mapping;
if(type == 'value'){
mapping=mappings.find(t=>{return t.value == value})
}else{
mapping=mappings.find(t=>{return t.from <= value&& t.to >= value});
}
this.mapping=mapping;
item.mapping=mapping;
item.value = mapping?mapping.text.replace('{{value}}', item.value):this.unit.compute(value,null,2);
}
return item;
})
this.storedScreanTableData=seriesItem;
this.storedScreanTableData=Object.assign([],this.storedScreanTableData.reverse());
this.screenPageObj.total=this.storedScreanTableData.length;
this.seriesItemScreen=this.filterShowData(this.storedScreanTableData,this.screenPageObj);
this.tableShow = true;
this.$refs.loadingPreview.endLoading();
},
clearChart(){
if(this.echartModalStore){
this.echartModalStore.clear();
}
this.chart = {};
//this.seriesItem = [];
this.seriesItemScreen = [];
this.searchTime[0] = new Date().setHours(new Date().getHours()-1);
this.searchTime[1] =new Date();
let iframe = document.querySelector('#chartUrlFrame');
if(iframe){
iframe.src="";
}
this.isStackArea = false;
this.serieSingleStat = '';
},
// 重新请求数据 刷新操作
/*
refreshChart() {
//this.dropdownMenuShow=false;
this.clearChart();
this.$refs['localLoading'+this.chartIndex].startLoading();
//this.firstShow = false;
this.$emit('on-refresh-data', this.data.id);
},
*/
dateChange(time) {
this.searchTime = [...time];
let chartType = this.chart.type;
if (chartType === 'table') {
this.seriesItemScreen = [];
for(let i=0;i<8;i++){
this.seriesItemScreen.push({//表格数据
// label: '',//label
// metric: '',//metric列
element:'',
time: '',//采集时间
value: '',//数值
});
}
this.tableShow = false;
this.$refs.loadingPreview.startLoading();
this.getChartData();
} else if (chartType === 'line' || chartType === 'bar' || chartType === 'stackArea' || chartType === 4 || this.chart.from == 'endpoint') {
this.echartModalStore.clear();
if (this.chart.from != $CONSTANTS.fromRoute.endpoint) {
this.showLegend = false;
}
this.$refs.loadingPreview.startLoading();
this.getQueryChart();
}else if (chartType === 'singleStat') {
this.serieSingleStat = "";
this.$refs.loadingPreview.startLoading();
this.getChartData();
}
},
// 查询数据,修改日期查询全屏数据
getQueryChart(type) {
let axiosArr = [];
const chartItem = this.chart;
console.log(chartItem);
this.$nextTick(() => {
let startTime = bus.timeFormate(this.searchTime[0], 'yyyy-MM-dd hh:mm:ss');
let endTime = bus.timeFormate(this.searchTime[1], 'yyyy-MM-dd hh:mm:ss');
if(!startTime || !endTime){//如果时间为空则默认取最近1小时
let now = new Date();
startTime = bus.timeFormate(now, 'yyyy-MM-dd hh:mm:ss');
endTime = bus.timeFormate(now.setHours(now.getHours()-1), 'yyyy-MM-dd hh:mm:ss');
this.searchTime[0] = startTime;
this.searchTime[1] = endTime;
}
let step = bus.getStep(startTime,endTime);
axiosArr = this.chart.elements.map((ele) => {
const filterItem = ele;
let query = encodeURIComponent(filterItem.expression);
if(chartItem.type === 'table'&&chartItem.param&&chartItem.param.last == 1){
return this.$get('/prom/api/v1/query_range?query=' + query + "&start=" + this.$stringTimeParseToUnix(endTime) + "&end=" + this.$stringTimeParseToUnix(endTime) + '&step=' + step);
}
return this.$get('/prom/api/v1/query_range?query='+query+"&start="+this.$stringTimeParseToUnix(startTime)+"&end="+this.$stringTimeParseToUnix(endTime)+'&step='+step);
});
// 一个图表
axios.all(axiosArr).then(res => {
if (res.length > 0) {
const series = [];
const legend = [];
const sumData = {
name: 'sum',
data: [],
visible: true,
threshold: null,
};
if(!this.chart.type || this.chart.from == 'endpoint'){
this.chart.type='line';
}
res.forEach((response,pos) => {
if (response.status === 'success') {
this.isError = false;
this.errorContent = "";
if (response.data.result) {
// 循环处理每个elements下获取的数据列
response.data.result.forEach((queryItem,innerPos) => {
const seriesItem = {
theData: {
name: '',
symbol:'emptyCircle', //去掉点
symbolSize:[2,2],
smooth:0.2, //曲线变平滑
showSymbol:false,
data: [],
type:this.chart.type,
lineStyle: {
width: 1,
opacity: 0.9
},
//visible: true,
//threshold: null,
},
metric_name: '',
};
if(this.chart.type === 'stackArea'){
seriesItem.theData.type='line';
seriesItem.theData.stack=this.chart.title;
seriesItem.theData.areaStyle={"opacity": 0.3};
}
// 图表中每条线的名字,后半部分
// let host = `${queryItem.metric.__name__}{`;//up,
let host = '';//up,
if(queryItem.metric.__name__){
host = `${queryItem.metric.__name__}{`;//up,
}
const tagsArr = Object.keys(queryItem.metric);//["__name__","asset","idc","instance","job","module","project"]
// 设置时间-数据格式对
const dpsArr = Object.entries(queryItem.values);//[ ["0",[1577959830.781,"0"]], ["1",[1577959845.781,"0"]] ]
// 判断是否有数据,&& tagsArr.length > 0
if (dpsArr.length > 0 ) {
tagsArr.forEach((tag, i) => {
if (tag !== '__name__') {
host += `${tag}="${queryItem.metric[tag]}",`;
}
});
if(host.endsWith(',')){host = host.substr(0,host.length-1);}
if(queryItem.metric.__name__){
host +="}";
}
if(!host || host===''){
host = this.chart.elements[pos].expression;
}
let alias=this.dealLegendAlias(host,this.chart.elements[pos].legend);
if(!alias || alias===''){
alias = host;
}
legend.push({name:host+innerPos,alias:alias});
// 图表中每条线的名字,去掉最后的逗号与空格:metric名称, 标签1=a,标签2=c
seriesItem.theData.name = host+innerPos;
seriesItem.metric_name = seriesItem.theData.name;
// 将秒改为毫秒
//alert('table=='+JSON.stringify(queryItem))
seriesItem.theData.data = queryItem.values.map((dpsItem, dpsIndex) => {
return [dpsItem[0] * 1000, dpsItem[1]];
});
series.push(seriesItem.theData);
} else if (this.chart.elements && this.chart.elements[innerPos]) {
// 无数据提示
}
});
}
}else{
this.isError = true;
if(response.msg){
this.errorContent = response.msg;
}else if(response.error){
this.errorContent = response.error;
}else {
this.errorContent = response;
}
}
});
this.setColor(legend.length);
this.initChart(this.chart,series, this.$refs.screenShowArea,legend);
}
}).catch(error => {
if (error) {
this.$message.error(error.toString());
}
});
});
},
dealLegendAlias:function(legend,expression){
if(/\{\{.+\}\}/.test(expression)){
let labelValue=expression.replace(/(\{\{.+?\}\})/g,function(i){
let label=i.substr(i.indexOf('{{')+2,i.indexOf('}}')-i.indexOf('{{')-2);
let reg=new RegExp(label+'=".+?"');
let value=null;
if(reg.test(legend)){
let find=legend.match(reg)[0];
value=find.substr(find.indexOf('"')+1,find.lastIndexOf('"')-find.indexOf('"')-1);
}
return value?value:label;
});
return labelValue
}else{
return expression;
}
},
screenPageNo(val) {
this.screenPageObj.pageNo = val;
this.seriesItemScreen=this.filterShowData(this.storedScreanTableData,this.screenPageObj)
},
screenPageSize(val) {
this.screenPageObj.pageSize = val;
this.seriesItemScreen=this.filterShowData(this.storedScreanTableData,this.screenPageObj)
},
handleClose(){
this.screenModal = false;
this.clearChart();
},
findLegendOptions:function(){
if((!this.chart.param) || (!this.chart.param.legendValue) || Object.keys(this.chart.param.legendValue)<1) return false;
let legendOptions= this.chart.param.legendValue;
let onVal=Object.keys(legendOptions).find(item=>{return legendOptions[item] == 'on'});
return onVal;
},
computeLegendData:function(legend,dataArr){
let options=this.screenLegendOptions;
let keys=options.filter(item=>{return item.value == 'on'}).map(item=>{return item.option});
let $self=this;
keys.forEach(item=>{
switch (item) {
case 'min':
$self.legendMinValue(legend,dataArr);
break;
case 'max':
$self.legendMaxValue(legend,dataArr);
break;
case 'avg':
$self.legendAvgValue(legend,dataArr);
break;
case 'last':
$self.legendLastValue(legend,dataArr);
break;
case 'total':
$self.legendTotalValue(legend,dataArr);
break;
}
})
},
legendMinValue:function(legend,dataArr){
return new Promise(resolve => {
legend.forEach(item=>{
let data=dataArr.find(t=>t.name == item.name)
if(data&&data.data){
let copy=JSON.parse(JSON.stringify(data.data));
let min =copy.sort((x,y)=>{return parseFloat(x[1]) - parseFloat(y[1])})[0][1]
item['min']=parseFloat(min)
}
})
resolve();
})
},
legendMaxValue:function(legend,dataArr){
return new Promise(resolve => {
legend.forEach(item=>{
let data=dataArr.find(t=>t.name == item.name)
if(data&&data.data){
let copy=JSON.parse(JSON.stringify(data.data));
let max =copy.sort((x,y)=>{return parseFloat(y[1]) - parseFloat(x[1])})[0][1]
item['max']=parseFloat(max)
}
})
resolve();
})
},
legendAvgValue:function(legend,dataArr){
return new Promise(resolve => {
legend.forEach(item=>{
let data=dataArr.find(t=>t.name == item.name)
if(data&&data.data){
let copy=JSON.parse(JSON.stringify(data.data));
copy = copy.map(t=>parseFloat(t[1]))
let sum = eval(copy.join('+'));
let avg = sum / copy.length;
item['avg'] = avg;
}
})
resolve();
})
},
legendLastValue:function(legend,dataArr){
return new Promise(resolve => {
legend.forEach(item=>{
let data=dataArr.find(t=>t.name == item.name)
if(data&&data.data){
let copy=JSON.parse(JSON.stringify(data.data));
let last =copy.sort((x,y)=>{return parseFloat(y[0]) - parseFloat(x[0])})[0][1]
item['last']=parseFloat(last)
}
})
resolve();
})
},
legendTotalValue:function(legend,dataArr){
return new Promise(resolve => {
legend.forEach(item=>{
let data=dataArr.find(t=>t.name == item.name)
if(data&&data.data){
let copy=JSON.parse(JSON.stringify(data.data));
copy = copy.map(t=>parseFloat(t[1]))
let sum = eval(copy.join('+'));
item['total'] = sum;
}
});
resolve();
})
},
formatLegendData:function(data){
return chartDataFormat.getUnit(this.chart.unit).compute(data,null,2)
},
legendValueSort:function(option,legend,options){
options.forEach(item=>{
if(item.option != option.option){
item.sort='';
}
})
if(!option.sort || option.sort == 'asc'){
legend.sort((x,y)=>x[option.option] - y[option.option])
option.sort = 'desc'
}else{
legend.sort((x,y)=>y[option.option] - x[option.option])
option.sort = 'asc'
}
}
},
beforeDestroy() {
this.clearChart();
try {
if(this.echartModalStore){
this.echartModalStore.off('magictypechanged');
}
} finally{}
},
};
</script>