【总结】Echarts工艺大屏中的技术点

共 39280字,需浏览 79分钟

 ·

2023-07-15 08:42



93fcaa4f13afb0da3e784edd81ca6ef6.webp



大屏制作中主要包含了Echarts的象形图、3D的柱状图、自定义图形。





1、象形图






05a425fc4ae4f7b6f5c7621e1c0201fa.webp



1.1、自定义图形,可以是svg或者图片;



1.2、隐藏X、Y轴;



1.3、series为两个部分,一个是背景部分,一部分为数据填充部分,具体看代码;




const spirit = 'path://M150 0 L160 0 L170 10 L160 10 Z';


var maxData = 100;


option = {


tooltip: {


show: false


},


xAxis: {


max: maxData,


splitLine: { show: false },


offset: 10,


axisLine: { show: false },


axisLabel: { show: false }


},


yAxis: {


data: [],


inverse: true,


axisTick: { show: false },


axisLine: { show: false },


axisLabel: { show: false }


},


grid: {


top: 'center',


height: 200,


left: 70,


right: 100


},


series: [


{


// current data


type: 'pictorialBar',


symbol: spirit,


symbolRepeat: 'fixed',


symbolMargin: '5%',


symbolClip: true,


color: 'red',


symbolSize: 10,


symbolBoundingData: maxData,


data: [32],


z: 10


},


{


// full data


type: 'pictorialBar',


itemStyle: {


opacity: 0.2


},


label: {


show: false


},


animationDuration: 0,


symbolRepeat: 'fixed',


symbolMargin: '5%',


symbol: spirit,


symbolSize: 10,


symbolBoundingData: maxData,


data: [100],


z: 5


}


]



};











0aa19b6f759cc5992687e7834e6c1893.webp





2、3D柱状图




bd7e5e514bdcf2dd36aba3e2124f2751.webp




2.1、使用pictorialBar+bar绘制



主要是一个bar柱子,使用渐变色,然后增加一个pictorialBar盖子,最终结合为一个柱状图。




option = {


tooltip: {


trigger: 'axis',


axisPointer: {


type: 'shadow'


}


},


grid: {


left: '5%',


right: '5%',


bottom: '5%',


top: '10%',


containLabel: true


},


xAxis: {


type: 'category',


data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', 'aaa'],


nameTextStyle: {


color: "#fff"



    },




},



yAxis: {


type: 'value',


name: "人数:个",


splitLine: {


show: false,


},



},



series: [{


type: 'bar',


barWidth: 25,


stack: '1',


barCategoryGap: 8,


data: [1,2,3,4,5,6,7,8],


color: '#fff',


itemStyle: { // 柱体渐变色


color: {


type: 'linear',


x: 0,


x2: 1,


y: 0,


y2: 0,


colorStops: [


{ offset: 0, color: 'rgba(1, 180, 255, 0.7)' },


{ offset: 0.5, color: 'rgba(1, 180, 255, 0.7)' },


{ offset: 0.5, color: 'rgba(1, 180, 255, 0.3)' },


{ offset: 1, color: 'rgba(1, 180, 255, 0.3)' }


]


},


shadowColor: 'rgba(37, 179, 208, 0.6)',


shadowBlur: 10


},


label: {


normal: {


show: true,


position: [8, -25],


fontSize: 14,


},


},


},


{


type: 'pictorialBar',


symbolSize: [25, 8],


symbol: 'diamond',


// 这个属性很重要,直接决定了顶部跟柱子是否契合


symbolOffset: [0, -4],


z: 12,


itemStyle: { color: 'rgba(1, 180, 255, 1)' },


symbolPosition: 'end',


data: [1,2,3,4,5,6,7,8],



  }],




};




2.2、使用custom进行绘制



需要绘制柱状图,通过绘制3个面,最终结合形成3D柱状图。





<template>



<div ref="chartRef" :style="{ width, height }"></div>



</template>



<script setup lang="ts">


import { reactive, ref, watch, type Ref } from "vue"


import { useECharts } from '/@/hooks/web/useEcharts';


import { getDays } from '../utils';


import type { EChartsOption } from 'echarts';







const chartRef = ref<HTMLDivElement | null>(null);


const { setOptions, echarts } = useECharts(chartRef as Ref<HTMLDivElement>);







let linearArr = [


"#0079e4 ",


"#000af5 ",


"#36c9ff ",


"#003efa",


"#0099e4",


"#4a9df7",


"rgba(0, 31, 117, 0.3)",


"rgba(0, 153, 228, 0.3)",



]








const threeDimensionalLine = (offsetX = 20, sliderWidth = 12, offsetTick = 14) => {


// 绘制左侧面


const CubeLeft = echarts.graphic.extendShape({


shape: {


x: 0,


y: 0,


},


buildPath: function (ctx, shape) {


// 会canvas的应该都能看得懂,shape是从custom传入的


const xAxisPoint = shape.xAxisPoint


const c0 = [shape.x - offsetTick, shape.y]


const c1 = [shape.x - offsetTick + offsetX, shape.y]


const c2 = [xAxisPoint[0] - offsetTick + offsetX, xAxisPoint[1]]


const c3 = [xAxisPoint[0] - offsetTick, xAxisPoint[1]]


ctx


.moveTo(c0[0], c0[1])


.lineTo(c1[0], c1[1])


.lineTo(c2[0], c2[1])


.lineTo(c3[0], c3[1])


.closePath()


},


})


// 绘制右侧面


const CubeRight = echarts.graphic.extendShape({


shape: {


x: 0,


y: 0,


},


buildPath: function (ctx, shape) {


const xAxisPoint = shape.xAxisPoint


const c1 = [shape.x - offsetTick + offsetX, shape.y]


const c2 = [shape.x - offsetTick + offsetX + sliderWidth, shape.y - sliderWidth]


const c3 = [


xAxisPoint[0] - offsetTick + offsetX + sliderWidth,


xAxisPoint[1] - sliderWidth,


]


const c4 = [shape.x - offsetTick + offsetX, xAxisPoint[1]]


ctx


.moveTo(c1[0], c1[1])


.lineTo(c2[0], c2[1])


.lineTo(c3[0], c3[1])


.lineTo(c4[0], c4[1])


.closePath()


},


})


// 绘制顶面


const CubeTop = echarts.graphic.extendShape({


shape: {


x: 0,


y: 0,


},


buildPath: function (ctx, shape) {


const c1 = [shape.x - offsetTick, shape.y]


const c2 = [shape.x - offsetTick + offsetX, shape.y] // 右点


const c3 = [shape.x - offsetTick + offsetX + sliderWidth, shape.y - sliderWidth]


const c4 = [shape.x - offsetTick + sliderWidth, shape.y - sliderWidth]


ctx


.moveTo(c1[0], c1[1])


.lineTo(c2[0], c2[1])


.lineTo(c3[0], c3[1])


.lineTo(c4[0], c4[1])


.lineTo(c1[0], c1[1])


.closePath()


},


})







const CubeBottom = echarts.graphic.extendShape({


shape: {


x: 0,


y: 0,


},


buildPath: function (ctx, shape) {


const xAxisPoint = shape.xAxisPoint


const c1 = [xAxisPoint[0] - offsetTick, xAxisPoint[1]]


const c2 = [xAxisPoint[0] - offsetTick, xAxisPoint[1] + 6] // 右点


const c3 = [xAxisPoint[0] - offsetTick + offsetX + sliderWidth, xAxisPoint[1] + 6]


const c4 = [xAxisPoint[0] - offsetTick + offsetX + sliderWidth, xAxisPoint[1] - 6]


const c5 = [xAxisPoint[0] - offsetTick + offsetX, xAxisPoint[1]]


ctx


.moveTo(c1[0], c1[1])


.lineTo(c2[0], c2[1])


.lineTo(c3[0], c3[1])


.lineTo(c4[0], c4[1])


.lineTo(c5[0], c5[1])


.lineTo(c1[0], c1[1])


.closePath()


},


})


// 注册三个面图形


echarts.graphic.registerShape("CubeLeft", CubeLeft)


echarts.graphic.registerShape("CubeRight", CubeRight)


echarts.graphic.registerShape("CubeTop", CubeTop)


echarts.graphic.registerShape("CubeBottom", CubeBottom)



}








interface dataType {


xAxisData: string[] | number[];


barData: number[];



}



const dataList = reactive<dataType>({


xAxisData: [],


barData: []



});









/**



* @Description 近12个月成品库产量图


* @date 2023-07-04


* @param {any} dataList:{xAxisData:any;barData:any;}


* @returns {any}


*/


const getStaticsBarOptions = (dataList: { xAxisData: string[] | number[]; barData: number[]; }) => {


return {


textStyle: {


fontFamily: "PingFang",


},


grid: {


left: '5%',


right: '2%',


top: 50,


bottom: 30,


// containLabel: true,


},


xAxis: {


type: 'category',


data: dataList.xAxisData,


axisLabel: {


color: '#fff'


},


axisLine: {


lineStyle: {


color: '#fff'


}


},


axisTick: {


alignWithLabel: true


},


axisPointer: {


type: 'shadow',


label: {


color: '#000'


}


},


nameTextStyle: {


color: '#fff'


}


},


yAxis: {


type: 'value',


name: '数量(个)',


axisLabel: {


formatter: '{value}',


color: '#fff'


},


splitLine: {


show: false


},


nameTextStyle: {


color: '#fff'


}


},


tooltip: {


trigger: 'axis',


textStyle: {


fontSize: '22'


},


axisPointer: {


type: 'cross',


crossStyle: {


color: '#999'


}


},


formatter: function (params: any) {


return `


<div>${params[0].name}</div>


<div>${params[1].marker} ${params[0].value} 个</div>


`


},


},


series: [


{


type: "bar",


label: {


normal: {


show: true,


position: [22, -30],


fontSize: 14,


color: "#fff",


},


},


itemStyle: {


color: "transparent",


},


data: dataList.barData,


},


{


type: "custom",


renderItem: (params: any, api: { coord: (arg0: any[]) => any; value: (arg0: number) => any; }) => {


const location = api.coord([api.value(0), api.value(1)])


return {


type: "group",


children: [


{


type: "CubeLeft",


shape: {


api,


xValue: api.value(0),


yValue: api.value(1),


x: location[0],


y: location[1],


xAxisPoint: api.coord([api.value(0), 0]),


},


style: {


fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [


{


offset: 0,


color: linearArr[0],


},


{


offset: 1,


color: linearArr[1],


},


]),


},


},


{


type: "CubeRight",


shape: {


api,


xValue: api.value(0),


yValue: api.value(1),


x: location[0],


y: location[1],


xAxisPoint: api.coord([api.value(0), 0]),


},


style: {


fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [


{


offset: 0,


color: linearArr[2],


},


{


offset: 1,


color: linearArr[3],


},


]),


},


},


{


type: "CubeTop",


shape: {


api,


xValue: api.value(0),


yValue: api.value(1),


x: location[0],


y: location[1],


xAxisPoint: api.coord([api.value(0), 0]),


},


style: {


fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [


{


offset: 0,


color: linearArr[4],


},


{


offset: 1,


color: linearArr[5],


},


]),


},


},


{


type: "CubeBottom",


shape: {


api,


xValue: api.value(0),


yValue: api.value(1),


x: location[0],


y: location[1],


xAxisPoint: api.coord([api.value(0), 0]),


},


style: {


fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [


{


offset: 0,


color: linearArr[6],


},


{


offset: 1,


color: linearArr[7],


},


]),


},


},


],


}


},


data: dataList.barData,


},


],


}



};








const props = defineProps({


data: {


type: Array,


default: () => {


return [];


}


},


width: {


type: String,


default: '100%'


},


height: {


type: String,


default: '300px'


}



});









watch(



() => props.data,


async (newVal) => {


dataList.xAxisData = await getDays(12, 'month', 'YYYY-MM');


dataList.barData = newVal as unknown as number[];


await threeDimensionalLine();







const options = await getStaticsBarOptions(dataList);


setOptions(options as EChartsOption, false);


},


{ immediate: true }



);




</script>






faeb0ca7f03a12e3fbe390e5e351a71f.webp





3、工艺流程图




通过分析,可以看出来可以分为两个部分,工艺卡片和箭头指向,工艺卡片中又可以分为:底图、文字信息等。



1.1、组合数据




export const formatCraftData = (


list: {


id: string;


name: string;


inCount: number;


outCount: number;


complete: number;


rate: number;


}[]


) => {


const craft = [];


const arrow = [];


let rollback = true;


let yLoopIndex = -1;


for (let i = 0; i < list.length; i++) {


const element = list[i];


const remainder = i % RowIndex;


if (remainder === 0) {


yLoopIndex++;


rollback = !rollback;


}


const xLoopIndex = rollback ? RowIndex - remainder : remainder + 1;


const log = [


Math.round((halfGAPx + (CraftW + GAPx) * (xLoopIndex - 1)) * 1000) / 1000,


H - Math.round((halfGAPy + (CraftH + GAPy) * yLoopIndex) * 1000) / 1000


];







const item = [


'craft',


...log,


element.id,


element.name,


element.inCount,


element.outCount,


element,


element.complete,


element.rate


];


craft.push(item);







if (remainder === RowIndex - 1) {


const log = [


Math.round((halfGAPx + CraftW / 2 + (CraftW + GAPx) * (rollback ? 0 : remainder)) * 1000) /


1000,


H - Math.round((halfGAPy + CraftH + (CraftH + GAPy) * yLoopIndex) * 1000) / 1000


];


arrow.push(['arrow', ...log, 'B']);


} else {


if (yLoopIndex % 2 === 0) {


const log = [


Math.round((halfGAPx + CraftW + (CraftW + GAPx) * remainder) * 1000) / 1000,


H - Math.round((halfGAPy + CraftH / 2 + (CraftH + GAPy) * yLoopIndex) * 1000) / 1000


];


arrow.push(['arrow', ...log, 'R']);


} else {


const log = [


Math.round((halfGAPx + CraftW + GAPx + (CraftW + GAPx) * remainder) * 1000) / 1000,


H - Math.round((halfGAPy + CraftH / 2 + (CraftH + GAPy) * yLoopIndex) * 1000) / 1000


];


arrow.push(['arrow', ...log, 'L']);


}


}


}


arrow.pop();







return {


craft,


arrow


};


};




1.2
、绘制卡片





function renderCraftItem(_param, api) {


const [logX, logY, name, inCount, outCount, complete, rate] = [


api.value(1),


api.value(2),


api.value(4), // name


api.value(5), // 入库


api.value(6), // 出库


// api.value(7),


api.value(8), // 完成数


api.value(9) // 工序良率


];


const [x, y] = api.coord([logX, logY]);


const [w, h] = api.size([1, 1]);







const _W = CraftW * w;


const _H = CraftH * h;


const px = 2 * w;


const py = 5 * h;







// 创建虚线样式


const lineDash = [3, 8];


// 创建纹理图像


const textureImage = new Image();


textureImage.src = '/public/bg.png'; // 替换为你的纹理图像路径


if(dashData.includes(name)) {


textureImage.src = '/public/bg-no.png'; // 替换为你的纹理图像路径


}







// 创建纹理图案


const img = {


type: 'image',


style: {


image: textureImage,


x: x,


y: y,


width: _W,


height: _H


}


}


const rect = {


type: 'rect',


shape: {


x: x,


y: y,


width: _W,


height: _H


},


style: {


fill: 'rgba(255, 255, 255, 0)',


shadowBlur: 1 * w,


shadowOffsetX: 0,


shadowOffsetY: 0,


shadowColor: 'rgba(196, 200, 229, 1)'


}


};


if (dashData.includes(name)) {


rect.style.lineDash = lineDash;


}


let labelText = [];


let value: any[] = [];


if (craftClassifyA.includes(name)) {


labelText = ['完成数', '工序良率'];


value = [complete, rate || 0];


} else if (craftClassifyB.includes(name)) {


labelText = ['完成数'];


value = [complete];


} else {


labelText = ['入库', '出库'];


value = [inCount, outCount];


}







let children = [];


const color = craftClassifyA.includes(name) && rate <= WARNNUM ? 'red' : '#fff';


if (dashData.includes(name)) {


children = [


img,


rect,


{


type: 'text',


x: x + _W / 2,


y: y + 50,


style: {


text: name,


font: `bolder 22px sans-serif`,


fill: '#ffffff96',


stroke: '#fff',


textAlign: 'center'


}


}


];


} else {


children = [


img,


rect,


getText(x + _W / 2, y + py + 5, name, 20, 'center', color),


getText(x + px, y + 20 * h, labelText[0], 18, 'left', color),


getText(x + _W - px, y + 20 * h, value[0] || 0, 30, 'right', '#008BFF')


];


if (labelText[1]) {


const val = `${value[1] || 0}${craftClassifyA.includes(name) ? '%' : ''}`;


children = [


...children,


getText(x + px, y + 35 * h, labelText[1], 18, 'left', color),


getText(x + _W - px, y + 35 * h, val, 30, 'right', '#008BFF')


];


}


}







return {


type: 'group',


children: children


};


}








function getText(



x: number,


y: number,


text: string | number,


textSize: number,


textAlign: 'center' | 'left' | 'right' = 'center',


color: string


) {


return {


type: 'text',


x: x,


y: y,


style: {


text: text,


font: `bolder ${textSize}px PingFangSC-Semibold`,


fill: color,


stroke: color,


textAlign: textAlign


}


};


}



1.1

绘制箭头




function renderArrowItem(_param, api) {


const [X, Y, DIRECTION] = [


api.value(1) as number,


api.value(2) as number,


api.value(3) as 'R' | 'T' | 'L' | 'B'


];


const [x, y] = api.coord([X, Y]);


const newX = DIRECTION === 'L' ? x - 10 : x + 10


return {


type: 'path',


x: newX,


y: DIRECTION === 'R' ? y - 10 : y + 10,


rotation: (Math.PI / 2) * ArrowDire[DIRECTION],


shape: {


pathData: 'path://M0 0 L0 20 L10 10 Z'


},


style: {


fill: '#00EEDA'


}


};


}



1.1
、组合卡片,形成options




export const getOptions = (data: {


craft: (string | number | any)[][];


arrow: (string | number | any)[][];


}) => {


return {


tooltip: {


backgroundColor: 'rgba(255,255,255,0.9)',


extraCssText: 'min-width: 200px;',


textStyle: {


color: '#333'


}


},


geo: {


show: true, // 是否显示地理坐标系组件


left: 0,


right: 0,


top: 0,


bottom: 0,


roam: false, // 是否开启鼠标缩放和平移漫游


zoom: 1,


scaleLimit: {


max: 1,


min: 1


},


silent: true, // 图形是否不响应和触发鼠标事件


itemStyle: {


color: 'rgba(0,0,0,0)',


borderWidth: 0


},


map: 'CraftMap', // 使用 registerMap 注册的地图名称


center: [100, 100]


},


series: [


{


type: 'custom',


coordinateSystem: 'geo',


geoIndex: 0,


renderItem: renderCraftItem,


data: data.craft,


tooltip: {


show: false,


confine: true,


textStyle: {


fontSize: '22'


},


formatter: function (params: any) {


const [name, inCount, outCount, complete, rate, cuur] = [


params.value[4], // name


params.value[5], // 入库


params.value[6], // 出库


params.value[8], // 完成数


params.value[9], // 工序良率


params.value[7] // 当前


];







let labelText = [];


if (craftClassifyA.includes(name)) {


labelText = ['完成数', '工序良率'];


} else {


labelText = ['入库', '出库'];


}


let HTML = `<div>${name}</div>`;


if (inCount || inCount === 0) {


HTML = HTML + `<div>${labelText[0]}: ${inCount}</div>`;


}







if (outCount || outCount === 0) {


HTML = HTML + `<div>${labelText[1]}: ${outCount}</div>`;


}







if (complete || complete === 0) {


HTML = HTML + `<div>${labelText[0]}: ${complete}</div>`;


}







if (rate || rate === 0) {


HTML = HTML + `<div>${labelText[1]}: ${rate}%</div>`;


}


return HTML;


}


}


},


{


type: 'custom',


coordinateSystem: 'geo',


geoIndex: 0,


renderItem: renderArrowItem,


data: data.arrow,


tooltip: {


show: false


}


}


]


} as EChartsOption;


};




07423ae6350b934cf1f8ebfd3713f67f.webp



浏览 99
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报