过往案例
绘制geojson格式的数据
import * as echarts from "echarts"
export interface DrawPolygonConfig {
title?: string
max_len?: number
range?: number[]
}
/**
* 保留数组的第一个元素、最后一个元素以及所有奇数索引(从0开始计数)的元素。
* @param {T[]} arr 输入的数组。
* @return {T[]} 包含指定元素的数组。
*/
export function retainFirstLastAndOddIndexed<T>(arr: T[]): T[] {
if (arr.length === 0) return []
let result = [arr[0]] // 始终包含第一个元素
// 遍历原数组(从索引1开始,因为第一个元素已经添加到结果中)
for (let i = 1; i < arr.length - 1; i++) {
if (i % 2 !== 0) {
// 如果是奇数索引,则将当前元素添加到结果数组中
result.push(arr[i])
}
}
// 添加最后一个元素
result.push(arr[arr.length - 1])
return result
}
使用折线来绘制
function drawPolygon(geojson: any, customConfig: DrawPolygonConfig) {
if (!geojson) return console.warn("have no geojsonData")
if (!myChart) return console.warn("echarts not init")
const defaultConfig = {
title: "图片标题",
max_len: 200,
range: [],
}
const config = Object.assign(defaultConfig, customConfig)
// 对折现的折点进行稀释
let polygon = retainFirstLastAndOddIndexed<number>(geojson.features[0].geometry.coordinates[0])
while (polygon.length > config.max_len) polygon = retainFirstLastAndOddIndexed(polygon)
// xy坐标轴的余量
const axisOffset = 0.05
const bounds = {
maxx: Math.max(...polygon.map((item) => item[0])),
minx: Math.min(...polygon.map((item) => item[0])),
maxy: Math.max(...polygon.map((item) => item[1])),
miny: Math.min(...polygon.map((item) => item[1])),
}
const shap = {
w: bounds.maxx - bounds.minx,
h: bounds.maxy - bounds.miny,
}
// 计算中心点
centerCoords.value = [
bounds.minx + (bounds.maxx - bounds.minx) / 2,
bounds.miny + (bounds.maxy - bounds.miny) / 2,
]
const option = {
xAxis: {
show: false,
min: bounds.minx - shap.w * axisOffset,
max: bounds.maxx + shap.w * axisOffset,
type: "value",
axisLine: { onZero: false },
},
yAxis: {
show: false,
min: bounds.miny - shap.h * axisOffset,
max: bounds.maxy + shap.h * axisOffset,
type: "value",
axisLine: { onZero: false },
},
tooltip: {
triggerOn: "none",
formatter: function (params) {
return "X: " + params.data[0].toFixed(2) + "<br>Y: " + params.data[1].toFixed(2)
},
},
grid: {
show: false, // 设置边距
left: "5%",
right: "5%",
top: "5%",
bottom: "5%",
},
// 开启鼠标中键滚动缩放功能
dataZoom: [
{
type: "inside",
xAxisIndex: 0,
filterMode: "none",
},
{
type: "inside",
yAxisIndex: 0,
filterMode: "none",
},
],
series: [
{
id: "polygon_base",
type: "line",
data: polygon,
Symbol: false,
symbolSize: 0,
},
],
}
myChart.setOption(option)
myChart.on("finished", () => {
drawPolygonCount.value++
})
}
绘制矩形
绘制一个矩形,不过无法拉伸缩放,只能移动,如果需要更高级的特性可以使用interactJS配合使用
public drawRect(w: number = 297 / 2, h: number = 210 / 2, x?: number, y?: number) {
if (!this.chart) return console.log("this.chart is null")
// 中心坐标在上一步绘制折线后已经提前生成
let position
if (x && y) {
position = [x, y]
} else {
const centerXY = this.chart.convertToPixel({ seriesId: "polygon_base" }, [
...this.centerCoords,
])
position = [centerXY[0] - w / 2, centerXY[1] - h / 2]
}
this.recordBounds([position[0], position[1], w, h])
const graphic = [
{
id: "sel_rect",
z: 10,
type: "rect",
draggable: true,
position,
shape: {
x: 0,
y: 0,
width: w,
height: h,
},
style: {
fill: "transparent", // 设置为透明填充
stroke: "#ff0000", // 设置边框颜色为红色
lineWidth: 2, // 设置边框宽度为2
},
onmouseup: (e) => {
this.recordBounds([e.target.x, e.target.y, e.target.shape.width, e.target.shape.height])
},
},
]
this.chart.setOption({ graphic })
this.drawRectCount++
this.isDraw = true
}
多边形(拖动和点击添加)
<template>
<div class="" style="width: 800px; height: 600px">
<div ref="testRef" class="w-full h-full"></div>
</div>
</template>
<script setup lang="ts">
import * as echarts from "echarts"
const testRef = ref()
const symbolSize = 20
let myChart
const data = [
[40, -10],
[-30, -5],
[-76.5, 20],
[-63.5, 40],
[-22.1, 50],
]
const poloyDataList: number[][] = []
function draggable() {
setTimeout(function () {
// Add shadow circles (which is not visible) to enable drag.
// 视图绘制完成后再绘制拖拽点
myChart.setOption({
graphic: data.map(function (item, dataIndex) {
return {
type: "circle",
position: myChart.convertToPixel("grid", item),
shape: {
cx: 0,
cy: 0,
r: symbolSize / 2,
},
invisible: true,
draggable: true,
ondrag: function (event) {
onPointDragging(dataIndex, [event.offsetX, event.offsetY])
},
onmousemove: function () {
showTooltip(dataIndex)
},
onmouseout: function () {
hideTooltip(dataIndex)
},
z: 100,
}
}),
})
}, 100)
}
function updatePosition() {
myChart.setOption({
graphic: data.map(function (item, _dataIndex) {
return {
position: myChart.convertToPixel("grid", item),
}
}),
})
}
function showTooltip(dataIndex: number) {
myChart.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: dataIndex,
})
}
function hideTooltip(_dataIndex: number) {
myChart.dispatchAction({ type: "hideTip" })
}
function onPointDragging(dataIndex: number, pos: [number, number]) {
data[dataIndex] = myChart.convertFromPixel("grid", pos)
// Update data
myChart.setOption({
series: [
{
id: "a",
data: data,
},
],
})
}
function drawPolygon(poloyData: number[]) {
// 绘制多边形
poloyDataList.push(poloyData)
myChart.setOption({
series: [
{
id: "a",
data: poloyDataList,
},
],
})
}
function onPointClick(params: any) {
const pointInPixel = [params.offsetX, params.offsetY]
const pointInGrid = myChart.convertFromPixel("grid", pointInPixel)
// 检查新点是否在图表区域内
if (myChart.containPixel("grid", pointInPixel)) {
// 检查新点附近是否已有点
const existingPoints = data.map((item) => myChart.convertToPixel("grid", item))
const pointDistanceSquared = (p1, p2) => (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
let isPointNearExisting
existingPoints.some((existingPoint, idx) => {
const distanceSquared = pointDistanceSquared(pointInPixel, existingPoint)
// symbolSize 是半径,所以需要比较距离的平方和 symbolSize^2
if (distanceSquared <= symbolSize ** 2) {
isPointNearExisting = data[idx]
}
return distanceSquared <= (symbolSize / 2) ** 2
})
if (isPointNearExisting) {
// 使用临近的点
data.push(isPointNearExisting)
// 绘制多边形
} else {
// 创建新点
data.push(pointInGrid)
}
myChart.setOption({
series: [
{
id: "a",
data: data,
},
],
})
draggable() // 重新添加拖拽功能(如果需要)
}
}
onMounted(() => {
myChart = echarts.init(testRef.value)
const option = {
tooltip: {
triggerOn: "none",
formatter: function (params) {
return "X: " + params.data[0].toFixed(2) + "<br>Y: " + params.data[1].toFixed(2)
},
},
xAxis: {
min: -100,
max: 70,
type: "value",
axisLine: { onZero: false },
},
yAxis: {
min: -30,
max: 60,
type: "value",
axisLine: { onZero: false },
},
series: [
{
id: "a",
type: "line",
smooth: false,
symbolSize: symbolSize,
data: data,
},
{
id: "b",
type: "area",
data: data, // 闭合数据
areaStyle: {},
lineStyle: {
opacity: 0, // 隐藏线条
},
},
],
}
draggable()
myChart.on("dataZoom", updatePosition)
myChart.setOption(option)
myChart.getZr().on("click", onPointClick)
window.addEventListener("resize", updatePosition)
})
</script>
水位对比图
function generateTimeAxis(
startTimeStr: string,
endTimeStr: string,
stepMs: number = 3600000
): string[] {
// 解析日期字符串为 Date 对象
const parseDateTime = (str: string): Date => {
const [datePart, timePart] = str.split(' ');
const [year, month, day] = datePart.split('-').map(Number);
const [hours, minutes, seconds] = timePart.split(':').map(Number);
return new Date(year, month - 1, day, hours, minutes, seconds);
};
// 格式化 Date 对象为字符串
const formatDate = (date: Date): string => {
const pad = (n: number) => n.toString().padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
};
// 参数校验
const start = parseDateTime(startTimeStr);
const end = parseDateTime(endTimeStr);
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
throw new Error('Invalid date format');
}
if (start > end) return [];
if (stepMs <= 0) throw new Error('Step must be positive');
// 生成时间序列
const result: string[] = [];
let current = start.getTime();
const endTime = end.getTime();
while (current <= endTime) {
result.push(formatDate(new Date(current)));
current += stepMs;
}
return result;
}
function generateFluctuatedValue(
originalValue: number,
minMultiplier: number = 0.9,
maxMultiplier: number = 1.03
): number {
// 计算实际浮动边界
const minVal = originalValue * minMultiplier;
const maxVal = originalValue * maxMultiplier;
// 智能确定数值范围(自动处理正负值情况)
const [lowerBound, upperBound] = minVal <= maxVal
? [minVal, maxVal]
: [maxVal, minVal];
// 生成区间内随机值并限制精度
const randomValue = lowerBound + Math.random() * (upperBound - lowerBound);
return parseFloat(randomValue.toFixed(10));
}
const baseData = [
0.62, 0.5, 0.48, 0.6, 0.88, 1.12, 1.2, 1.1, 0.96, 0.8, 0.68, 0.54, 0.4, 0.3,
0.28, 0.48, 0.88, 1.28, 1.54, 1.58, 1.44, 1.3, 1.12, 0.92, 0.72, 0.54, 0.4,
0.32, 0.5, 0.84, 1.06, 1.12, 1.04, 0.9, 0.78, 0.66, 0.52, 0.4, 0.34, 0.42,
0.74, 1.22, 1.56, 1.7, 1.6, 1.38, 1.22, 1.02, 0.76, 0.54, 0.34, 0.2, 0.14,
0.28, 0.66, 0.96, 1.08, 1.04, 0.92, 0.8, 0.64, 0.5, 0.38, 0.34, 0.54, 1, 1.48,
1.78, 1.8, 1.6, 1.34, 1.1, 0.86, 0.6, 0.38, 0.18, 0.02, -0.04, 0.16, 0.6,
0.94, 1.06, 1, 0.9, 0.74, 0.58, 0.44, 0.36, 0.4, 0.78, 1.28, 1.7, 1.9, 1.82,
1.56, 1.3, 1.06, 0.8, 0.54, 0.3, 0.08, -0.06, -0.06, 0.32, 0.76, 1.02, 1.12,
1.04, 0.94, 0.78, 0.64, 0.5, 0.44, 0.62, 1.12, 1.64, 2, 2.08, 1.86, 1.6, 1.34,
1.04, 0.72, 0.46, 0.22, 0, -0.12, 0.02, 0.48, 0.84, 1.04, 1.08, 1.02, 0.9,
0.76, 0.62, 0.52, 0.54, 0.92, 1.44, 1.88, 2.12, 2.04, 1.8, 1.54, 1.24, 0.92,
0.64, 0.36, 0.12, -0.06, -0.16, 0.04, 0.54, 0.94, 1.08, 1.02, 0.92, 0.82,
0.68, 0.58, 0.52, 0.62, 1.1, 1.66, 2.04, 2.14, 1.96, 1.72, 1.44, 1.14, 0.82,
0.52, 0.26, 0.02, -0.14, -0.18, 0.22, 0.68, 0.96, 1.08, 1.04, 0.94, 0.8, 0.66,
0.52, 0.46, 0.78, 1.34, 1.82, 2.12, 2.1, 1.84, 1.54, 1.28, 1, 0.7, 0.42, 0.16,
-0.04, -0.2, -0.16, 0.34, 0.78, 1, 1.04, 1, 0.92, 0.76, 0.64, 0.54, 0.54, 0.92
];
const timeList = generateTimeAxis(
'2020-06-16 00:00:00',
'2020-06-25 00:00:00'
);
const data2 = []
const data1 = []
timeList.forEach((eachTime, i)=>{
data1.push([eachTime, baseData[i]])
data2.push([eachTime, generateFluctuatedValue(baseData[i])])
})
option = {
xAxis: {
type:'time',
data:timeList,
},
yAxis: {
min:-1,
max:3,
},
series: [
{
name:'实测数据',
symbol:'none',
symbolSize: 8,
data: data1,
type: 'line',
smooth:true,
},
{
name:'模拟数据',
symbolSize: 8,
data: data2,
type: 'scatter',
}
]
};