本文在C1Chart实现一个自定义的序列,它用来背景高亮呈现颜色带---Alarm Zone区域。 用途适用于需要用背景颜色区分的场景,本文适用于WPF、SilverLight,且代码可用于Windows Store和Windows Phone apps上。
什么是Alarm Zone区域?
用数据绘制的Alarm Zone区域定义: 放在后面的一系列带或形状,同时,可作为前面的图表背景。 Alarm Zone也被称为更简单的彩色条纹或地区。
自定义的实现
虽然在XAML C1Chart的并没有内建支持Alarm Zone, 但完成同样的功能是有办法的:每个XAML版本的C1Chart支持多边形的图表类型,它们允许你在一个单一的场景下结合任何数量的图表类型。 如,可以很容易地创建一个Alarm Zone,通过直接添加一个你喜欢PolygonFilled图表。
1: <c1:C1Chart Name="c1Chart1"
2: ChartType="XYPlot"
3: Palette="Standard">
4: <c1:C1Chart.Data>
5: <c1:ChartData>
6: <c1:XYDataSeries ChartType="PolygonFilled"
7: XValues="3 3 7 6 5"
8: Values="8 17 17 15 8"/>
9: <c1:XYDataSeries Label="S1"
10: Values="5 12 18 14 6 7 21 15 12 19"
11: XValues="5 4 3 1 2 6 7 9 8 5" />
12: </c1:ChartData>
13: </c1:C1Chart.Data>
14: <c1:C1ChartLegend />
15: </c1:C1Chart>
问题是,你必须在你的脑海绘制出的X,Y坐标的每个点。另外,如果你想要一个区域扩展到“无限”,你必须知道的轴的最大值是什么。 为此,我们可以按写一个定制的的类,AlarmZone,它继承自XYDataSeries类。
请看看下面的AlarmZone.cs的WPF定义。在这篇博客文章的底部,可下载完整的demo。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Windows.Media;
6: using System.Windows.Controls;
7: using C1.WPF.C1Chart;
8:
9: namespace Chart_AlarmZones_WPF_CS
10: {
11: public class AlarmZone : XYDataSeries
12: {
13: #region "constructor"
14:
15: public AlarmZone()
16: {
17: this.LayoutUpdated += new EventHandler(AlarmZone_LayoutUpdated);
18: this.ChartType = C1.WPF.C1Chart.ChartType.PolygonFilled;
19: this.XValues = new DoubleCollection();
20: this.Values = new DoubleCollection();
21: UpdateLegend();
22: Update();
23: }
24:
25: #endregion
26:
27: #region "members"
28:
29: private C1Chart _chart;
30: public C1Chart Chart
31: {
32: get { return _chart; }
33: set { _chart = value; }
34: }
35:
36: private double? _lowerExtent = null;
37: public double? LowerExtent
38: {
39: get { return _lowerExtent; }
40: set
41: {
42: _lowerExtent = value;
43: Update();
44: }
45: }
46:
47: private double? _upperExtent = null;
48: public double? UpperExtent
49: {
50: get { return _upperExtent; }
51: set
52: {
53: _upperExtent = value;
54: Update();
55: }
56: }
57:
58: private double? _near = null;
59: public double? Near
60: {
61: get { return _near; }
62: set
63: {
64: _near = value;
65: Update();
66: }
67: }
68:
69: private double? _far = null;
70: public double? Far
71: {
72: get { return _far; }
73: set
74: {
75: _far = value;
76: Update();
77: }
78: }
79:
80: private bool _showInLegend = false;
81: public bool ShowInLegend
82: {
83: get { return _showInLegend; }
84: set
85: {
86: _showInLegend = value;
87: UpdateLegend();
88: }
89: }
90:
91: #endregion
92:
93: #region "implementation"
94:
95: public void Update()
96: {
97: if (_near != null && _far != null)
98: {
99: this.XValues.Clear();
100: this.XValues.Add((double)_near);
101: this.XValues.Add((double)_far);
102: this.XValues.Add((double)_far);
103: this.XValues.Add((double)_near);
104: }
105:
106: if(_lowerExtent != null && _upperExtent != null)
107: {
108: this.Values.Clear();
109: this.Values.Add((double)_lowerExtent);
110: this.Values.Add((double)_lowerExtent);
111: this.Values.Add((double)_upperExtent);
112: this.Values.Add((double)_upperExtent);
113: }
114: }
115:
116: public void UpdateLegend()
117: {
118: if (_showInLegend)
119: {
120: this.Display = SeriesDisplay.SkipNaN;
121: }
122: else
123: {
124: this.Display = SeriesDisplay.HideLegend;
125: }
126: }
127:
128: private void chart_LayoutUpdated(object sender, EventArgs e)
129: {
130: if (this.Chart != null)
131: {
132: if (this.Chart.View != null)
133: {
134: // if extent is null, set to axis bounds
135: if (_near == null)
136: {
137: _near = Chart.View.AxisX.ActualMin;
138: Update();
139: }
140: if (_far == null)
141: {
142: _far = Chart.View.AxisX.ActualMax;
143: Update();
144: }
145: if (_upperExtent == null)
146: {
147: _upperExtent = Chart.View.AxisY.ActualMax;
148: Update();
149: }
150: if (_lowerExtent == null)
151: {
152: _lowerExtent = Chart.View.AxisY.ActualMin;
153: Update();
154: }
155:
156: }
157: }
158: }
159:
160: // obtains the parent chart control so we can later get axis bounds
161: void AlarmZone_LayoutUpdated(object sender, EventArgs e)
162: {
163: if (this.Parent != null && this.Chart == null)
164: {
165: Canvas c = this.Parent as Canvas;
166: if (c != null)
167: {
168: Canvas cv = c.Parent as Canvas;
169: if (cv != null)
170: {
171: Border b = cv.Parent as Border;
172: if (b != null)
173: {
174: Grid g = b.Parent as Grid;
175: if (g != null)
176: {
177: C1Chart chart = g.Parent as C1Chart;
178: if (chart != null)
179: {
180: this.Chart = chart;
181: this.Chart.LayoutUpdated += chart_LayoutUpdated;
182: }
183: }
184: }
185:
186: }
187: }
188:
189: }
190: }
191:
192: #endregion
193:
194: }
195: }
AlarmZone类公开4个关键的属性:Near, Far, UpperExtent,LowerExtent。 这些属性换算成多边形系列的数据点。 正如你可以看到下图中,这4个属性,形成我们的Alarm Zone的边界。
我们的区域的颜色,是由从XYDataSeries继承的ConnectionFill属性设置的。我们可以复用很多基类的东西,如外观和图例属性。
下列代码添加到您的项目的AlarmZone类,建立本地XML命名空间,并添加到您的XAML文件。
1: xmlns:local="clr-namespace:<Project Namespace>"
现在你可以增加一些AlarmZones,如数据序列将它们添加到您的图表。 创建一个红块,像在上面的图表,你将所有四个的属性,Near, Far, LowerExtent, UpperExtent的,如在下面的设置。
1: <c1:C1Chart Name="c1Chart1"
2: ChartType="XYPlot"
3: Palette="Standard">
4: <c1:C1Chart.Data>
5: <c1:ChartData>
6: <local:AlarmZone Near="3"
7: Far="7"
8: LowerExtent="10"
9: UpperExtent="18"
10: ShowInLegend="True"
11: Label="Alarm"
12: ConnectionFill="#79FF0000"/>
13: <c1:XYDataSeries Label="S1"
14: Values="5 12 18 14 6 7 21 15 12 19"
15: XValues="5 4 3 1 2 6 7 9 8 5" />
16: </c1:ChartData>
17: </c1:C1Chart.Data>
18: <c1:C1ChartLegend />
19: </c1:C1Chart>
我们还增加了ShowInLegend的属性,可让您在图例标记区域。此属性只是显示属性XYDataSeries序列在图例中显示。
这个自定义AlarmZone实现的是,它也可以扩展区轴的界限,不管实际的图表中的数据多少。如,你已经注意到,Near, Far, LowerExtent, UpperExtent的属性默认都为空值(如果没有明确设置这些值), 区域将延伸到在该方向的轴边界。例如,要创建一些垂直条纹延伸至底部的Y轴的顶端,你可以只是简单地为每个区域设置了Near, Far属性。
1: <c1:C1Chart Name="c1Chart1" ChartType="XYPlot">
2: <c1:C1Chart.Data>
3: <c1:ChartData>
4: <local:AlarmZone Far="3"
5: ConnectionFill="#96FF0000"
6: ShowInLegend="True"
7: Label="Poor"/>
8: <local:AlarmZone Near="3"
9: Far="6"
10: ConnectionFill="#96FFFF00"
11: ShowInLegend="True"
12: Label="Medium"/>
13: <local:AlarmZone Near="6"
14: ConnectionFill="#96008000"
15: ShowInLegend="True"
16: Label="Good"/>
17: <c1:XYDataSeries Label="S1"
18: Values="5 12 18 14 6 7 21 15 12 19"
19: XValues="5 4 3 1 2 6 7 9 8 5" />
20: </c1:ChartData>
21: </c1:C1Chart.Data>
22: <c1:C1ChartLegend />
23: </c1:C1Chart>
如不设置UpperExtent LowerExtent属性,区域将延长。请注意,我们甚至没有设置近属性为红色区域,绿色区域或Far属性,因为这些将沿x轴延伸到“无限”。
如果任何这些值是null,Alarm Zone从父图表得到实际轴的最小值或最大值,并使用该值。(AlarmZone的初始化,可获得父图表对象。)
1: if (_upperExtent == null)
2: {
3: _upperExtent = Chart.View.AxisY.ActualMax;
4: Update();
5: }
这是其中的代码之间稍有不同,因为的WPF和其他XAML平台的可视化树层次结构是一个有点不同。因此,请确保使用正确版本的AlarmZone。
结论
此示例演示,在C1Chart--WPF、SilverLight下,我们如何能够按使用现有的功能扩展和添加这些缺少的功能。通过使用多边形数据为基础,我们可以创建我们自己的'Alarm Zone定义, 可以添加到任何现有图表解决方案系列。