|
|
const e="be616a00-e2bc-4849-8b28-8cdd35cd190b",a="custom-line-chart",t="折线图",n='<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694569114379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1510" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M240.8 803.68l-26.933333-25.813333 248.053333-258.613334 158.186667 158.186667 232-232 26.4 26.4-258.4 258.4-157.653334-157.653333-221.653333 231.093333z" fill="#23B8BA" p-id="1511"></path><path d="M852.106667 456.746667l15.093333 15.093333-247.093333 247.093333-152-152-5.76-5.76-5.653334 5.866667-216.16 225.333333-15.36-14.773333 236.853334-246.933333 152.426666 152.426666 5.653334 5.653334 5.653333-5.653334 226.346667-226.346666m0-11.306667L620.106667 677.333333 461.866667 519.253333l-248 258.613334 26.666666 25.813333 221.653334-231.093333 157.653333 157.653333 258.4-258.4-26.4-26.666667z" fill="#14A59E" p-id="1512"></path><path d="M240.8 537.44l-26.933333-25.866667 248.053333-258.613333 158.186667 158.186667 232-232 26.4 26.4-258.4 258.4-157.653334-157.6-221.653333 231.093333z" fill="#FFA800" p-id="1513"></path><path d="M852.106667 190.453333l15.093333 15.093334-247.093333 247.093333-152-151.946667-5.76-5.813333-5.653334 5.92L240.533333 526.08l-15.36-14.72 236.853334-246.933333 152.426666 152.373333 5.653334 5.653333 5.653333-5.653333 226.346667-226.346667m0-11.306666l-232 232L461.866667 252.96 213.866667 511.573333l26.666666 25.866667 221.92-231.093333L620.106667 464l258.4-258.4-26.4-26.4z" fill="#E29103" p-id="1514"></path><path d="M144 880V106.666667h-37.333333v810.666666h810.666666v-37.333333H144z" fill="#0985F6" p-id="1515"></path><path d="M136 114.666667v773.333333h773.333333v21.333333h-794.666666v-794.666666h21.333333M144 106.666667h-37.333333v810.666666h810.666666v-37.333333H144V106.666667z" fill="#006FC1" p-id="1516"></path><path d="M462.293333 294.56m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#FFA800" p-id="1517"></path><path d="M462.293333 254.56a40 40 0 1 1-40 40 40 40 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#E29103" p-id="1518"></path><path d="M227.306667 524.48m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#FFA800" p-id="1519"></path><path d="M227.306667 484.48a40 40 0 1 1-40 40 40.053333 40.053333 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#E29103" p-id="1520"></path><path d="M619.253333 432.426667m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#FFA800" p-id="1521"></path><path d="M619.253333 392.426667a40 40 0 1 1-40 40 40 40 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#E29103" p-id="1522"></path><path d="M861.333333 192.8m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#FFA800" p-id="1523"></path><path d="M861.333333 152.8a40 40 0 1 1-40 40 40 40 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#E29103" p-id="1524"></path><path d="M462.293333 560.8m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#23B8BA" p-id="1525"></path><path d="M462.293333 520.8a40 40 0 1 1-40 40 40.053333 40.053333 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#14A59E" p-id="1526"></path><path d="M227.306667 790.773333m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#23B8BA" p-id="1527"></path><path d="M227.306667 750.773333a40 40 0 1 1-40 40 40 40 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#14A59E" p-id="1528"></path><path d="M619.253333 698.666667m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#23B8BA" p-id="1529"></path><path d="M619.253333 658.666667a40 40 0 1 1-40 40 40.053333 40.053333 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#14A59E" p-id="1530"></path><path d="M861.333333 457.6m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#23B8BA" p-id="1531"></path><path d="M861.333333 417.6a40 40 0 1 1-40 40 40 40 0 0 1 40-40m0-8a48 48 0 1 0 48 48 48 48 0 0 0-48-48z" fill="#14A59E" p-id="1532"></path></svg>',i="svg",l="动态",o="图表组件",u=!1,p=!0,s="时序",d=`{"type":"page","id":"u:270584784c
"nodes": [ { "id": "a3c0bb09-4bc9-4717-8adc-ff83e5eee086", "type": "custom-line-chart", "x": 200, "y": 200, "text": { "value": "", "x": 200, "y": 200 }, "properties": { "id": "a3c0bb09-4bc9-4717-8adc-ff83e5eee086", "width": 350, "height": 250, "x": 200, "y": 200, "rotation": 0, "opacity": 1, "grid": { "show": false, "top": "25", "left": "30", "right": "5", "bottom": "20", "backgroundColor": "" }, "title": { "show": true, "padding": "", "left": "30", "right": "2", "top": "2", "bottom": "2", "text": "kWh", "textAlign": "auto", "textVerticalAlign ": "middle" }, "legend": { "show": true, "orient": "horizontal", "left": "center", "top": "" }, "tooltip": { "show": true, "showContent": true, "trigger": "axis" }, "xAxis": { "show": true, "axisLine": { "show": true }, "axisTick": { "show": true }, "axisLabel": { "show": true, "fontSize": 12 }, "splitLine": { "show": false }, "type ": "category", "name": "", "nameColor": "" }, "yAxis": { "show": true, "axisLine": { "show": true }, "axisTick": { "show": true }, "axisLabel": { "show": true, "fontSize": 12 }, "splitLine": { "show": false }, "type ": "value" }, "codeConfig": "option.color = ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'];\\r\\n\\r\\n// 值保留为两位小数。\\r\\noption.valueFormatter = (value) => {\\r\\n if (typeof value === 'number') {\\r\\n return value.toFixed(2);\\r\\n } else {\\r\\n return value\\r\\n }\\r\\n};\\r\\n\\r\\noption.series.forEach((item) => {\\r\\n // item.stack = \\"total\\";\\r\\n item.smooth = \\"true\\";\\r\\n // 区域风格,可配置\\r\\n // item.areaStyle = {};\\r\\n // 线条风格, 可自定义\\r\\n item.lineStyle = {\\r\\n width: 1\\r\\n };\\r\\n});\\r\\nreturn option;", "fontSize": 0, "showDefaultValue": false, "showUnit": false, "nodeAlias": "折线图", "renderMode": "canvas", "dynamic": { "normalData": { "dataPoint": "", "compareType": "", "conditionVariables": [], "defaultValue": "", "unit": "", "renderIntervalEnabled": true, "completeDatas": false, "legendNameType": "attrName", "customDatasource": false, "dataShowTypes": "oneThingManyAttr", "timeAxisFormatter": "HH", "requestMethod": "get", "requestParams": "return {};", "dataFilterFn": "// datas 数据处理\\n// ....\\n" } } } } ]}`,javascript:`const { createApp, createVNode, render } = Vue;const app = createApp({})
const timeArr = new Array(24).fill('');const totals = [];timeArr.forEach((i, index) => { const t = window.dayjs().hour(index).valueOf(); totals.push({ val: Math.random(1000) * 100, ts: t, attrKey: "A29" }) });timeArr.forEach((i, index) => { const t = window.dayjs().hour(index).valueOf(); totals.push({ val: Math.random(1000) * 100, ts: t, attrKey: "A8" }) });const defaultSocketValue = totals;
const xAxisLabelFormatter = function (val) { if (this.timeCompare) return; if (this.timeAxisFormatter) { return dayjs(+val).format(this.timeAxisFormatter); } const { months, days } = this; if (months === 0 && days === 0) { return dayjs(+val).format("HH:mm"); } else if (months === 0 && days > 0) { return dayjs(+val).format("DD HH:mm"); } else if (months > 0 && days >= 0) { return dayjs(+val).format("MM-DD HH:mm"); }};
// 工具提示格式化
const tooltipFormatter = function (params) { let strs = ""; const timeFormatterMap = { "HH": '时', "DD": '日', 'MM': '月' } const time = this.timeCompare ? params[0].name : window.dayjs.unix(+params[0].name / 1000).format(this.timeAxisFormatter || "YYYY-MM-DD HH:mm:ss"); strs += \`<div>\${time} \${timeFormatterMap[this.timeAxisFormatter] || ''}</div>\`;
params.forEach((param) => { strs += param.marker + " " + param.seriesName + ":  " + Number(param.value).toFixed(2) + "<br/>"; }) return strs;};
// 图例格式化
const assembleLegend = (api, thingKey, attrkey, dataShowTypes, legendNameType, datas) => { if (api) { const infos = window.totalDeviceInfos[api]; if (!infos) return; if (!datas[0]) return; const thing = infos[datas[0].thingCode]; if (!thing) return; const thingName = thing.entityName; const attrName = thing.attrs[attrkey].name; if (dataShowTypes.value === 'oneThingOneAttr') { switch (legendNameType.value) { case "thingName": return thing.entityName; case "thingCode": return thingKey; case "attrName": return attrName; case "attrCode": return attrkey; case "thingNameAttrName": return thingName + '-' + attrName; case "thingCodeAttrCode": return thingKey + '-' + attrkey; } } else if (dataShowTypes.value === 'oneThingManyAttr') { switch (legendNameType.value) { case "attrName": return thing.attrs[attrkey].name; case "attrCode": return attrkey; case "thingNameAttrName": return thingName + '-' + attrName; case "thingCodeAttrCode": return thingKey + '-' + thingKey } } else if (dataShowTypes.value === 'manyThingManyAttr') { switch (legendNameType.value) { case "thingNameAttrName": const attrName = thing.attrs[attrKey].name return thingName + '-' + attrName; case "thingCodeAttrCode": return thingKey + '-' + thingKey; } } else if (dataShowTypes.value === 'manyThingOneAttr') { switch (legendNameType.value) { case "thingName": return thingName; case "thingCode": return thingKey; case "thingNameAttrName": const attrName = thing.attrs[attrKey].name return thingName + '-' + attrName; case "thingCodeAttrCode": return thingKey + '-' + thingKey; } } } else { return attrkey; }}
const LineChart = { template: '<div :id="lineId" :style="getStyle" class="custom-line-chart"></div>', props: { lineId: { type: String, default: '' }, historyDatas: { type: Array, default: () => [] }, width: { type: Number, default: 350 }, height: { type: Number, default: 150 }, grid: { type: Object, default: () => { } }, title: { type: Object, default: () => { } }, legend: { type: Object, default: () => { } }, tooltip: { type: Object, default: () => { } }, xAxis: { type: Object, default: () => { } }, yAxis: { type: Object, default: () => { } }, codeConfig: { type: String, default: '' }, timeAxisFormatter: { type: String, default: '' }, completeDatas: { type: Boolean, default: false }, totalTimes: { type: Array, default: () => [] }, timeCompare: { type: String, default: '', }, dataShowTypes: { type: String, default: 'oneThingManyAttr', }, legendNameType: { type: String, default: 'attrName', }, apiid: { type: String, default: '', }, renderMode: { type: String, default: 'canvas', } }, computed: { getStyle() { return { width: \`\${this.width}px\`,
height: \`\${this.height}px\`
} } }, setup(props) { const { onMounted, nextTick, toRefs, watch } = Vue; const { grid, title, legend, tooltip, xAxis, yAxis, historyDatas, codeConfig, timeAxisFormatter, completeDatas, totalTimes, timeCompare, dataShowTypes, legendNameType, apiid, renderMode } = toRefs(props)
const tooltipFormatterBound = tooltipFormatter.bind({ timeCompare: timeCompare.value, timeAxisFormatter: timeAxisFormatter.value }); let myChart = null; let timeCompareMap = { 'day': { prev: '昨日', curr: '今日' }, 'month': { prev: '上月', curr: '当月' }, 'year': { prev: '去年', curr: '今年' }, } const initChart = (datas) => { // 基于准备好的dom,初始化echarts实例
const dom = document.getElementById(props.lineId); if (dom && !myChart) { myChart = echarts.init(dom, null, { renderer: renderMode.value }); if (datas) { let series = []; let legends = []; let xAxisData = []; let xAxisLabelFormatterBound = null; if (datas.length > 0) { const thingGrouped = window._.groupBy(datas, 'thingCode'); for (const thingKey in thingGrouped) { const attrGrouped = window._.groupBy(thingGrouped[thingKey], 'attrKey') const keysLen = Object.keys(attrGrouped).length; for (const key in attrGrouped) { let serieData = attrGrouped[key]; const legendKey = assembleLegend(apiid.value, thingKey, key, dataShowTypes, legendNameType, serieData); legends.push(legendKey); if (!xAxisLabelFormatterBound) { const first = serieData[0]; const last = serieData[serieData.length - 1]; if (first.ts > last.ts) { serieData = serieData.reverse() } const dayjs = window.dayjs; const firstDay = dayjs(+first.ts); const lastDay = dayjs(+last.ts); const days = firstDay.diff(lastDay, 'day'); const months = firstDay.diff(lastDay, 'month'); xAxisLabelFormatterBound = xAxisLabelFormatter.bind({ months, days, timeAxisFormatter: timeAxisFormatter.value, timeCompare: completeDatas.value && timeCompare.value, }) } if (completeDatas.value && totalTimes.value.length > 0) { // 如果需要补全一个区间内的缺失数据
// console.log('serieData', serieData);
if (timeCompare.value) { const splitTimes = window.splitTimes(totalTimes.value, timeCompare.value); const realIndexs = {}; // 正确数据索引
const prevValues = splitTimes.prev.map((t, index) => { let tVal = null; if (timeCompare.value === 'day') { tVal = dayjs(t).format(timeAxisFormatter.value || 'HH:mm'); } else if (timeCompare.value === 'month') { tVal = dayjs(t).format(timeAxisFormatter.value || 'DD'); } else if (timeCompare.value === 'year') { tVal = dayjs(t).format(timeAxisFormatter.value || 'MM'); } if (!xAxisData.includes(tVal)) { xAxisData.push(tVal); realIndexs[index] = true; const point = serieData.find(d => +d.ts === t); return (point && point.val) || 0 } }) const currValues = splitTimes.curr.map((t, index) => { if (xAxisData.length === 0) { let tVal = null; if (timeCompare.value === 'day') { tVal = dayjs(t).format(timeAxisFormatter.value || 'HH:mm'); } else if (timeCompare.value === 'month') { tVal = dayjs(t).format(timeAxisFormatter.value || 'DD'); } else if (timeCompare.value === 'year') { tVal = dayjs(t).format(timeAxisFormatter.value || 'MM'); } if (!xAxisData.includes(tVal)) { xAxisData.push(tVal); const point = serieData.find(d => +d.ts === t); return (point && point.val) || 0 } } else { if (realIndexs[index]) { const point = serieData.find(d => +d.ts === t); return (point && point.val) || 0 } } }) let legendPrev = ''; let legendCurr = ''; if (keysLen > 1) { legendPrev = \`\${legendKey}(\${timeCompareMap[timeCompare.value].prev})\`;
legendCurr = \`\${legendKey}(\${timeCompareMap[timeCompare.value].curr})\`;
} else if (keysLen === 1) { legendPrev = \`\${timeCompareMap[timeCompare.value].prev}\`;
legendCurr = \`\${timeCompareMap[timeCompare.value].curr}\`;
} legends.push(legendPrev); legends.push(legendCurr); series.push({ name: legendPrev, type: 'line', data: prevValues.filter(Boolean) }) series.push({ name: legendCurr, type: 'line', data: currValues.filter(Boolean) }) } else { const serieValues = totalTimes.value.map(t => { xAxisData.push(t); const point = serieData.find(d => +d.ts === t); return (point && point.val) || 0 }) series.push({ name: legendKey, type: 'line', data: serieValues }) } } else { const serieValues = serieData.map((point) => { xAxisData.push(+point.ts); return point.val; }) series.push({ name: legendKey, type: 'line', data: serieValues }) } } } } else { legends = ['测试图例'] xAxisData = [1, 2, 3, 4, 5, 6, 7, 8] series = [{ type: 'line', name: '测试图例', data: [], }] }
// xAxisData 去重
xAxisData = [...new Set(xAxisData)].sort();
const legendConfig = { ...legend.value, data: legends.filter(Boolean) }
const xAxisConfig = { ...xAxis.value, axisLabel: { ...xAxis.value.axisLabel, formatter: (completeDatas.value && timeCompare.value) ? null : xAxisLabelFormatterBound }, nameLocation: 'end', nameGap: 5, nameTextStyle: { color: xAxis.value.nameColor, verticalAlign: 'top', lineHeight: 28 }, data: xAxisData }
// 指定图表的配置项和数据
var option = { grid: grid.value, title: title.value, tooltip: { ...tooltip.value, formatter: tooltipFormatterBound, borderColor: tooltip.value.backgroundColor, textStyle: { ...tooltip.value.textStyle, align: 'left' } }, legend: legendConfig, xAxis: xAxisConfig, yAxis: yAxis.value, series: series, }; const func = new Function('option', 'datas', 'instance', codeConfig.value); const opt = func(window._.cloneDeep(option), datas, myChart); // 使用刚指定的配置项和数据显示图表。
myChart.setOption(opt); } } }
watch(historyDatas, (val) => { if (val) { nextTick(() => { initChart(val) }) } }, { immediate: true }) }}
class CustomLineChartNode extends HtmlResize.view { chartRendered = false historyDatas = [] oldProperties = "{}"
setHtml(rootEl) { if (!rootEl) return; const { properties, width, height, } = this.props.model; const { nodeAlias, grid, title, legend, tooltip, xAxis, yAxis, codeConfig, dynamic, apiid, renderMode } = properties; const { normalData } = dynamic || {}; const { timeAxisFormatter, completeDatas, timeCompare, dataShowTypes, legendNameType } = normalData || {};
let totalTimes = []; if (completeDatas) { if (apiid) { const param = window.totalApiParams[apiid]; const info = totalDeviceInfos[apiid]; totalTimes = window.completeTimesForChart(param, info, this.historyDatas, apiid); } }
const el = document.createElement('div'); rootEl.innerHTML = ''; const instance = createVNode(LineChart, { name: nodeAlias, lineId: \`line-\${properties.id}\`,
historyDatas: this.historyDatas, width, height, grid, title, legend, tooltip, xAxis, yAxis, codeConfig, timeAxisFormatter, completeDatas, totalTimes, timeCompare, apiid, dataShowTypes, legendNameType, renderMode }) instance.appContext = app._context render(instance, el) rootEl.appendChild(el); }
sameProps(properties) { const isSame = window._.isEqual(JSON.parse(this.oldProperties), properties); if (isSame) return true; this.oldProperties = JSON.stringify(properties); return false }
// 过滤历史数据
filterHistoryData(thingCodeArr, dataPointArr, apiid, renderIntervalEnabled) { if (dataPointArr && dataPointArr.length > 0) { let datas = [] if (renderIntervalEnabled) { datas = window.totalHistoryDatas[apiid]; } else { if (window.globalDashboardDatas[apiid]) { datas = window.globalDashboardDatas[apiid].values; } } if (datas && datas.length > 0) { const gotValues = datas.filter((val) => thingCodeArr.includes(val.thingCode) && dataPointArr.includes(val.attrKey)) this.historyDatas = gotValues this.chartRendered = true; } } }
// 生命周期 支持重写内容, 但格式需一致
shouldUpdate() { const { properties } = this.props.model; const { apiid } = properties; const { normalData } = properties.dynamic || {}; const { dataPointArr, defaultValue, thingCodeArr, customApiDatas } = normalData || {}
if (normalData && !normalData.dataPoint && !normalData.defaultValue) { this.historyDatas = defaultSocketValue; return true } else if (normalData && !normalData.dataPoint && normalData.defaultValue) { this.historyDatas = JSON.parse(defaultValue); return true }
// 如果采用来自自定义数据源的数据
if (customApiDatas) { this.historyDatas = customApiDatas; return true }
const propertiesBack = window._.cloneDeep(properties); if (propertiesBack.dynamic.normalData) { propertiesBack.dynamic.normalData.defaultValue = ''; if (this.sameProps(propertiesBack) && this.chartRendered) { return false }
if (dataPointArr && apiid && !this.chartRendered) { this.filterHistoryData(thingCodeArr, dataPointArr, apiid, normalData.renderIntervalEnabled); return true; } } return true; }
updateHtml() { this.setHtml(this.rootEl); }
componentDidMount() { // 防止拖动时候频繁渲染图表
this.updateHtmlDebounced = window._.debounce(this.updateHtml.bind(this), 500); const { properties } = this.props.model; const { normalData } = properties.dynamic || {}; const { renderInterval, dataPointArr, thingCodeArr } = normalData || {}; if (this.shouldUpdate()) { this.setHtml(this.rootEl); } const initRender = () => { // 第一次历史数据返回可能比较慢,轮询判断
let times = 0 const inter = setInterval(() => { if (window.totalHistoryDatas && window.totalHistoryDatas[properties.apiid]) { this.filterHistoryData(thingCodeArr, dataPointArr, properties.apiid, normalData.renderIntervalEnabled); this.setHtml(this.rootEl); clearInterval(inter); } if (times > 20) { clearInterval(inter) } times++; }, 1000) } initRender();
let inters = parseInt(renderInterval || '300000') if (normalData && !normalData.renderIntervalEnabled) { inters = 1000 } setInterval(() => { if (window.totalHistoryDatas[properties.apiid]) { this.filterHistoryData(thingCodeArr, dataPointArr, properties.apiid, normalData.renderIntervalEnabled); this.setHtml(this.rootEl); } }, inters) }
componentDidUpdate() { if (this.shouldUpdate()) { this.updateHtmlDebounced(); } }}
class CustomLineChartModel extends HtmlResize.model { initNodeData(data) { // 自定义组件,需最开始重置一下text 。
data.text = { value: "", x: data.x, y: data.y, };
super.initNodeData(data); const { properties } = this; this.width = properties.width || 80; this.height = properties.height || 35; this.text.editable = false; // 不允许文本被编辑
}
setAttributes() { // 自定义组件需重置 text
const { x, y, properties } = this; const { textHorizontalMove = 0, textVerticalMove = 0 } = properties; this.text = { ...this.text, x: x + textHorizontalMove, y: y + textVerticalMove, value: "", } }}
lf.register({ type: 'custom-line-chart', view: CustomLineChartNode, model: CustomLineChartModel,})`,css:`.custom-line-chart svg {\r stroke: none;\r}`,fakeData:""},m={id:e,name:a,aliasName:t,image:n,imageType:i,groupName:l,groupType:o,isRemote:!1,isDefault:!0,sectionType:s,config:d,files:r};export{t as aliasName,d as config,m as default,r as files,l as groupName,o as groupType,e as id,n as image,i as imageType,p as isDefault,u as isRemote,a as name,s as sectionType};
|