Flex4 用LineChart实现实时曲线图,并在其中添加自定义的虚线水平线

本示例首先是为了显示实时曲线。然后呢,要显示个水平线,比如我要显示一个变量的变化过程,但它有一个标准值,就可以用该种方式。

示例:

[flash=600,500]https://dl.dropbox.com/u/38216791/flex/samples/runtimeChart/samples.swf[/flash]

然后是代码:
先看Application:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:chartClasses="lip.charts.chartClasses.*">

<fx:Script>
<![CDATA[
import lip.utils.DateFormatter;

import mx.collections.ArrayCollection;
import mx.utils.ObjectProxy;

private static const DELAY:int = 1000;

private static const MAX_COUNT:int = 10;

[Bindable]
private var ac:ArrayCollection;

private var timer:Timer;

protected function startCalc(event:MouseEvent):void
{
if(!timer)
{
timer = new Timer(DELAY);
timer.addEventListener(TimerEvent.TIMER, timer_timerHandler);
}
timer.start();
}

protected function stopCalc(event:MouseEvent):void
{
if(timer)
timer.stop();

ac = null;
}

protected function timer_timerHandler(event:TimerEvent):void
{
var now:Date = new Date();
var nowTime:String;

if(!ac)
{
ac = new ArrayCollection();
now.setTime(now.time - 1000 * MAX_COUNT);
for (var i:int = 0; i < MAX_COUNT; i++)
{
nowTime = DateFormatter.getInstance().formatTime(now);
ac.addItem(new ObjectProxy({time:nowTime, gas:0}));
now.setTime(now.time + 1000);
}

}

nowTime = DateFormatter.getInstance().formatTime(now);
var item:ObjectProxy = new ObjectProxy({time:nowTime, gas:Math.round(Math.random() * 100) * .01});

if(ac.length < 10)
{
ac.addItem(item);
}
else
{
ac.removeItemAt(0);
ac.addItem(item);
}

trace(item.time, ":", item.gas);
}

protected function changeDashedLine(event:MouseEvent):void
{
nn = Math.round((Math.random() / 5 + 0.8) * 100) * .01;
}

]]>
</fx:Script>

<fx:Declarations>
<!-- 将非可视元素(例如服务、值对象)放在此处 -->
<mx:SeriesInterpolate id="effect" duration="1000" />
<fx:Number id="nn">0.3</fx:Number>
</fx:Declarations>

<s:Panel width="600" height="500" title="测试实时曲线">
<s:controlBarContent>
<s:HGroup width="100%" height="20" horizontalAlign="center" verticalAlign="middle">
<s:Button label="start" click="startCalc(event)"/>
<s:Button label="stop" click="stopCalc(event)"/>
<s:Button label="change dashed line" click="changeDashedLine(event)"/>
</s:HGroup>
</s:controlBarContent>
<mx:LineChart id="chart" width="500" height="400" horizontalCenter="0" showDataTips="true"
verticalCenter="0" dataProvider="{ac}" >
<mx:backgroundElements>
<mx:GridLines gridDirection="both"/>
<chartClasses:DashedLines lineColor="0xFF0000" yValue="{nn}"/>
</mx:backgroundElements>

<mx:horizontalAxis>
<mx:CategoryAxis categoryField="time" displayName="Time" title="时间" />
</mx:horizontalAxis>

<mx:verticalAxis>
<mx:LinearAxis minimum="0" maximum="1"/>
</mx:verticalAxis>

<mx:series>
<mx:LineSeries displayName="瓦斯" xField="time" yField="gas" form="curve"/>
</mx:series>
</mx:LineChart>

</s:Panel>
</s:Application>


接下来是里面的DashedLines:

package lip.charts.chartClasses
{
import flash.display.Graphics;
import flash.geom.Point;

import lip.utils.GraphicUtils;

import mx.charts.chartClasses.CartesianChart;
import mx.charts.chartClasses.CartesianTransform;
import mx.charts.chartClasses.ChartElement;
import mx.charts.chartClasses.ChartState;
import mx.charts.chartClasses.IAxis;

public class DashedLines extends ChartElement
{
public function DashedLines()
{
super();
}

private var _yValue:Number = NaN;

/**
* 该线对应的y值
*/
public function get yValue():Number
{
return _yValue;
}

/**
* @private
*/
public function set yValue(value:Number):void
{
_yValue = value;
invalidateDisplayList();
}


/**
* 实线部分的长度
* @default 10
*/
public var length:Number = 10;

/**
* 空白部分的长度
* @default 5
*/
public var gap:Number = 5;

/**
* 线条的宽度
* @default 1
*/
public var lineThickness:Number = 1;

/**
* 线条的颜色
* @default 黑色
*/
public var lineColor:uint = 0;

private var _displayName:String;

/**
* 该线所对应的数值名称(平均值,最大值等等)
* @default
*/
public function get displayName():String
{
return _displayName;
}

/**
* @private
*/
public function set displayName(value:String):void
{
_displayName = value;
invalidateDisplayList();
}


protected var label:TextField;

override protected function createChildren():void
{
// TODO Auto Generated method stub
super.createChildren();

if(!label)
{
label = new TextField();
label.mouseEnabled = false;
addChild(label);
}
}


override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);

if (!chart||
chart.chartState == ChartState.PREPARING_TO_HIDE_DATA ||
chart.chartState == ChartState.HIDING_DATA)
{
return;
}

var g:Graphics = this.graphics;
g.clear();

// 如果没有设置数据,不显示
if(isNaN(yValue))
{
return;
}

var w:Number = unscaledWidth;
var h:Number = unscaledHeight;
var vAxis:IAxis = CartesianChart(this.chart).verticalAxis;

var y:Number = dataToLocal(0, yValue).y;

var pFrom:Point = new Point(0, y);
var pTo:Point = new Point(w, y);

GraphicUtils.drawDashed(g, pFrom, pTo, this.length, this.gap, this.lineThickness, this.lineColor);

label.text = (displayName ? (displayName + " : ") : "") + yValue;
label.x = 1;
label.y = y > 21 ? y - 21 : y + 1;
}


// 这个方法复制自LineSeries
override public function dataToLocal(... dataValues):Point
{
var data:Object = {};
var da:Array /* of Object */ = [ data ];
var n:int = dataValues.length;

if (n > 0)
{
data["d0"] = dataValues[0];
dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).
mapCache(da, "d0", "v0");
}

if (n > 1)
{
data["d1"] = dataValues[1];
dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).
mapCache(da, "d1", "v1");
}

dataTransform.transformCache(da,"v0","s0","v1","s1");

return new Point(data.s0 + this.x,
data.s1 + this.y);
}
}
}


还有其中用到的GraphicUtils.drawDashed()方法:


package lip.utils
{
import flash.display.Graphics;
import flash.geom.Point;

/**
* 一些绘图相关的方法
* @author lip
*/
public class GraphicUtils
{
public function GraphicUtils()
{
}


/**
* 画虚线
* @param graphics 你懂的
* @param pFrom 起点
* @param pTo 终点
* @param length 实线段的长度
* @param gap 实线段的间距
* @param thickness 线的宽度
* @param color 线的颜色
*/
public static function drawDashed(graphics:Graphics, pFrom:Point, pTo:Point, length:Number = 5, gap:Number = 5, thickness:Number = 1, color:uint = 0):void
{
var max:Number = Point.distance(pFrom, pTo);
var l:Number = 0;
var p3:Point;
var p4:Point;
graphics.lineStyle(thickness, color);
while (l < max)
{
p3 = Point.interpolate(pTo, pFrom, l / max);
l += length;
if (l > max)
l = max;
p4 = Point.interpolate(pTo, pFrom, l / max);
graphics.moveTo(p3.x, p3.y)
graphics.lineTo(p4.x, p4.y)
l += gap;
}
}
}
}


代码不太多,就不详细解释了。
<template> <div class="full_content"> <div class="header"> <!-- <div class="header_desc">{{ $t("跑步能力") }}</div> --> <div class="header_icon" @click="fullScreenClick"> <el-icon><TopRight /></el-icon> </div> </div> <div class="footer"> <div class="chart1" ref="chartRef" style="width: 100%; height: 100%" ></div> </div> <echartsBox :title="$t('跑步能力')" v-model:visible="dialogVisible"> <template #content> <div class="footer" style="width: 750px; height: 400px"> <div class="chart2" ref="chartDialog" style="width: 100%; height: 90%" ></div> </div> </template> </echartsBox> </div> </template> <script setup> import { ref, onMounted, onUnmounted, computed } from "vue"; import * as echarts from "echarts"; const chartRef = ref(null); let myChart = null; // 全屏按钮触发 const dialogVisible = ref(false); const fullScreenClick = () => { console.log(123); dialogVisible.value = true; }; const props = defineProps({ xAxisData: { type: Array, default: [ "00:00", "02:00", "04:00", "06:00", "08:00", "10:00", "12:00", "14:00", "16:00", "18:00", "20:00", "22:00", ], }, title: { type: String, default() { return "跑步能力"; }, }, seriesBinding: { type: Object, default: {}, }, }); // 常用清晰区分的颜色(适合折线图) const COLOR_PALETTE = [ "#4895ef", // 蓝 "#eb4d4b", // 红 "#f0932b", // 橙 "#36cbcb", // 青 "#9b59b6", // 紫 "#00b894", // 绿松石 ]; // 控制每个系列绑定到哪个 Y 轴 const seriesBinding = ref({ 单次跑力: "left", 跑步指数: "right", 单次跑力111: "left", // 同样绑定到左边 跑步指数222: "right", // 绑定到右边 }); const yAxisData = ref([ { name: "单次跑力", data: [70, 10, 75, 39, 60, 80, 75, 65, 55, 70, 75, 70], type: "value", }, { name: "跑步指数", data: [55, 40, 50, 55, 50, 60, 50, 30, 55, 81, 50, 55], type: "value", }, { name: "单次", data: [ "70", "101", "9", "39", "60", "10", "75", "65", "55", "70", "75", "70", ], type: "category", }, { name: "跑步", data: ["03:30", "04:30", "02:30", "06:30", "02:30", "09:30"], type: "category", }, ]); const legendData = yAxisData.value.map((item) => { return item.name; }); const seriesWithColor = computed(() => { return yAxisData.value.map((series, index) => ({ ...series, lineColor: COLOR_PALETTE[index % COLOR_PALETTE.length], // 循环使用颜色 })); }); const chartSeries = computed(() => { return seriesWithColor.value.map((series, index) => ({ name: series.name, type: "line", data: series.data, yAxisIndex: index, // 每条线绑定到自己的 Y 轴 showSymbol: false, lineStyle: { color: series.lineColor, width: 2.5, }, itemStyle: { color: series.lineColor, }, })); }); const chartYAxis = computed(() => { return seriesWithColor.value.map((series, index) => { // 左右交替放置 Y 轴,避免全部挤在一起 const isLeft = index % 2 === 0; const position = isLeft ? "left" : "right"; // 根据 index 计算偏移量,每多一个同侧轴就向外推 30px const offset = Math.floor(index / 2) * 30; const getNiceMax = (data, step = 10) => Math.ceil(Math.max(...data) / step) * step + step; return { type: series.type, position, offset, axisLabel: { color: series.lineColor, }, nameTextStyle: { color: series.lineColor, }, splitLine: { lineStyle: { color: "#444" } }, min: 0, max: getNiceMax(series.data, 10), }; }); }); // 初始化图表的函数 const initChart = () => { myChart = echarts.init(chartRef.value); const option = { title: { text: props.title, left: "center", textStyle: { color: "#0d7ee7", }, }, xAxis: { type: "category", data: props.xAxisData.length > 0 ? props.xAxisData : xAxisData, axisLine: { lineStyle: { color: "#7a7b7c", }, }, axisLabel: { color: "#7a7b7c", }, }, yAxis: chartYAxis.value, series: chartSeries.value, legend: { data: legendData, top: "10%", right: "8%", icon: "circle", textStyle: { color: "#fff", }, }, tooltip: { trigger: "axis", }, grid: { left: "3%", right: "4%", bottom: "3%", containLabel: true, }, }; myChart.setOption(option); }; // 窗口大小变化时调整图表大小的函数 const handleResize = () => { if (myChart) { myChart.resize(); } }; onMounted(() => { initChart(); window.addEventListener("resize", handleResize); }); onUnmounted(() => { if (myChart) { myChart.dispose(); myChart = null; } window.removeEventListener("resize", handleResize); }); </script> <style scoped lang="scss"> .full_content { position: relative; width: 100vw; height: 40vh; width: 600px; min-height: 400px; background-color: rgb(42, 40, 40); .header { width: 100%; height: 10%; display: flex; justify-content: center; position: relative; font-size: 24px; font-weight: bolder; color: #0d7ee7; align-items: center; padding-top: 3%; .header_icon { margin-left: 85%; position: absolute; top: 18px; width: 30px; height: 30px; font-size: 25px; cursor: pointer; color: #ffffff5e; background-size: contain; z-index: 999; } } .footer { position: absolute; top: 20px; width: 100%; height: 85%; } } </style> 我这样改完后 出现了一个像刻度尺一样的东西
最新发布
10-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值