|
|
const e="be616a00-e2bc-4849-8b28-8cdd35cd198b",a="custom-bar-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="1694685702418" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4058" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M290.15 503.877l-149.461-2.114v333.16H290.15z" fill="#D73949" p-id="4059"></path><path d="M497.316 227.066l-149.462-3.883v611.74h149.462z" fill="#0CA294" p-id="4060"></path><path d="M704.481 368.468l-149.461-2.98v469.435h149.461z" fill="#35A0D6" p-id="4061"></path><path d="M911.647 638.005l-149.462-1.258v198.176h149.462z" fill="#D97B24" p-id="4062"></path><path d="M102.139 885.365V98.152H65.063v830.142h895.855v-42.929z" fill="#819292" p-id="4063"></path></svg>',i="svg",l="动态",o="图表组件",u=!1,c=!0,s="时序",d=`{"type":"page","id":"u:270584784ce1","name":"page1","asideResizor":false,"style":{"boxShadow":" 0px 0px 0px 0px transparent"},"pullRefresh":{"disabled":true},"body":[{"type":"tabs","name":"tab","tabs":[{"title":"样式","icon":"fa fa-th-large","body":[{"type":"form","title":"","name":"basicPropForm","body":[{"type":"input-text","label":"名称","name":"nodeAlias","id":"u:6b126f0520cb","size":"full","mode":"horizontal","inputControlClassName":"w-100","className":"m-b"},{"type":"input-text","label":"ID ","name":"id","id":"u:6232710ac003","size":"full","mode":"horizontal","inputControlClassName":"w-100","className":"m-b"},{"type":"grid","id":"u:c605398a724c","className":"m-b","columns":[{"body":[{"type":"input-number","label":"宽度","name":"width","keyboard":true,"id":"u:dcc0c21d16f6","step":1,"suffix":"px","placeholder":"组件左边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:14cc19d6ffb0","md":6},{"body":[{"type":"input-number","label":"高度","name":"height","keyboard":true,"id":"u:cd6fdff9ca88","step":1,"suffix":"px","placeholder":"组件上边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:4931801ca9b8","md":6}]},{"type":"grid","id":"u:da449a94908a","className":"m-b","columns":[{"body":[{"type":"input-number","label":"X 轴","name":"x","keyboard":true,"id":"u:29852d093d9d","step":1,"suffix":"px","placeholder":"组件左边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:1b561d652acc","md":6},{"body":[{"type":"input-number","label":"Y 轴","name":"y","keyboard":true,"id":"u:dc8c1daed8ed","step":1,"suffix":"px","placeholder":"组件上边距","size":"full","mode":"horizontal","className":"m-b","value":200,"labelAlign":"left","precision":2,"inputClassName":"w-full","labelClassName":"w-8"}],"id":"u:9672575193ac","md":6}]},{"type":"grid","id":"u:a332a7bf83c1","className":"m-b","columns":[{"body":[{"type":"input-number","label":"旋转","name":"rotation","id":"u:f6a2dbb518f9","placeholder":"组件旋转角度","mode":"horizontal","size":"full","className":"","keyboard":true,"step":1,"suffix":"deg","value":0,"labelAlign":"left","inputClassName":"w-full"}],"id":"u:646cd98b7955","md":6},{"body":[{"type":"input-number","label":"透明","name":"opacity","id":"u:cf80f59d8d42","placeholder":"组件透明度","mode":"horizontal","size":"full","className":"m-b","keyboard":true,"step":0,"suffix":"","value":1,"inputClassName":"w-full","precision":2}],"id":"u:51ddf54ac749","md":6}],"gap":""},{"type":"grid","columns":[],"id":"u:235f153e5ad5","className":"m-b"},{"type":"radios","label":"渲染模式","name":"renderMode","options":[{"label":"canvas","value":"canvas"},{"label":"svg","value":"svg"}],"id":"u:047a1336613a","mode":"horizontal"},{"type":"fieldset","id":"u:2a93d8eee7a9","className":"","title":"布局","collapsable":true,"body":[{"type":"co
"nodes": [ { "id": "a3c0bb09-4bc9-4717-8adc-ff83e5eee086", "type": "custom-bar-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": "15", "bottom": "20" }, "title": { "show": true, "padding": "", "left": "30", "right": "2", "top": "2", "bottom": "2", "text": "kWh", "textAlign": "auto", "textVerticalAlign ": "middle" }, "legend": { "show": true, "orient": "horizontal" }, "tooltip": { "show": true, "showContent": true, "trigger": "axis", "backgroundColor": "rgba(0, 0, 0, 0.5)", "textStyle": { "color": "rgba(255, 255, 255, 1)" } }, "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})\\r\\n\\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, "timeAxisFormatter": "", "dataShowTypes": "oneThingManyAttr", "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", thingCode: 'A001' }) });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 BarChart = { template: '<div :id="lineId" :style="getStyle" class="custom-bar-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) { if (!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: 'bar', data: prevValues.filter(Boolean) }) series.push({ name: legendCurr, type: 'bar', 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: 'bar', data: serieValues }) } } else { const serieValues = serieData.map((point) => { xAxisData.push(+point.ts); return point.val; }) series.push({ name: legendKey, type: 'bar', data: serieValues }) } } } } else { legends = ['测试图例'] xAxisData = [1, 2, 3, 4, 5, 6, 7, 8] series = [{ type: 'bar', 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 CustomBarChartNode 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, apiid, renderMode } = properties; const { normalData } = properties.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); } }
const el = document.createElement('div'); rootEl.innerHTML = ''; const instance = createVNode(BarChart, { apiid, name: nodeAlias, lineId: \`line-\${properties.id}\`,
historyDatas: this.historyDatas, width, height, grid, title, legend, tooltip, xAxis, yAxis, codeConfig, timeAxisFormatter, completeDatas, totalTimes, timeCompare, dataShowTypes, legendNameType, renderMode }) instance.appContext = app._context render(instance, el) rootEl.appendChild(el); }
sameProps(properties) { const isSame = window._.isEqual(this.oldProperties, properties); if (isSame) return true; this.oldProperties = 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 CustomBarChartModel 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-bar-chart', view: CustomBarChartNode, model: CustomBarChartModel,})`,css:`.custom-bar-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,c as isDefault,u as isRemote,a as name,s as sectionType};
|