visifire今天登陆他们官网的时候,发现好像是挂掉了,不知道是不再运营了,还是单纯服务器出了问题。
visifire今天登陆他们官网的时候,发现好像是挂掉了,不知道是不再运营了,还是单纯服务器出了问题。
0、小叙闲言
除了仪表盘控件比较常用外,还有图表也经常使用,同样网上也有非常强大的图表控件,有收费的(DEVexpress),也有免费的。但我们平时在使用时,只想简单地绘一个图,控件库里面的许多功能我们都用不到,没必要使用那么功能丰富的控件,以提高程序运行的效率和减小程序的占用空间。同时,我们自己如果能够绘制图表出来,对于程序的移植,也非常方便。对于大部分平台,相信设计方法是不会变的。废话少讲,直接上图看效果,如果觉得还不错,可以支持一下,继续往下看。源码下载地址:
VisifireChart的效果不炫,但是对于一些项目,感觉够用的,所以,今天大概看了几篇博客,学习了一下
VisifireChart的效果不炫,但是对于一些项目,感觉够用的,所以,今天大概看了几篇博客,学习了一下
1、图表整体设计
简单来看一个图表的组成,一般由4个部分组成,坐标轴,刻度和刻度值,绘图区域(添加数据点和绘制曲线)。后面如果要做到数据动态显示,还要花一点点功夫,不过也是很容易的,耐心研究,不会比高等数学难。
2、坐标轴绘制
先作出两根垂直的直线出来,为x轴和y轴,XAML代码如下。2,3行代码即为两个数轴。4~23行,是作出两个小三角形,以形成箭头的形状。
1 <Canvas Margin="5">
2 <Line x:Name="x_axis" Stroke="Black" StrokeThickness="3" X1="40" Y1="280" X2="480" Y2="280" StrokeStartLineCap="Round"/>
3 <Line x:Name="y_axis" Stroke="Black" StrokeThickness="3" X1="40" Y1="280" X2="40" Y2="30" StrokeStartLineCap="Round"/>
4 <Path x:Name="x_axisArrow" Fill="Black">
5 <Path.Data>
6 <PathGeometry>
7 <PathFigure StartPoint="480,276" IsClosed="True">
8 <LineSegment Point="480,284"/>
9 <LineSegment Point="490,280"/>
10 </PathFigure>
11 </PathGeometry>
12 </Path.Data>
13 </Path>
14 <Path x:Name="y_axisArrow" Fill="Black">
15 <Path.Data>
16 <PathGeometry>
17 <PathFigure StartPoint="36,30" IsClosed="True">
18 <LineSegment Point="44,30"/>
19 <LineSegment Point="40,20"/>
20 </PathFigure>
21 </PathGeometry>
22 </Path.Data>
23 </Path>
24 </Canvas>
C#作出两个小箭头的后台代码如下:
1 /// <summary>
2 /// 作出箭头
3 /// </summary>
4 private void DrawArrow()
5 {
6 Path x_axisArrow = new Path();//x轴箭头
7 Path y_axisArrow = new Path();//y轴箭头
8
9 x_axisArrow.Fill = new SolidColorBrush(Color.FromRgb(0xff, 0, 0));
10 y_axisArrow.Fill = new SolidColorBrush(Color.FromRgb(0xff, 0, 0));
11
12 PathFigure x_axisFigure = new PathFigure();
13 x_axisFigure.IsClosed = true;
14 x_axisFigure.StartPoint = new Point(480, 276); //路径的起点
15 x_axisFigure.Segments.Add(new LineSegment(new Point(480, 284), false)); //第2个点
16 x_axisFigure.Segments.Add(new LineSegment(new Point(490, 280), false)); //第3个点
17
18 PathFigure y_axisFigure = new PathFigure();
19 y_axisFigure.IsClosed = true;
20 y_axisFigure.StartPoint = new Point(36, 30); //路径的起点
21 y_axisFigure.Segments.Add(new LineSegment(new Point(44, 30), false)); //第2个点
22 y_axisFigure.Segments.Add(new LineSegment(new Point(40, 20), false)); //第3个点
23
24 PathGeometry x_axisGeometry = new PathGeometry();
25 PathGeometry y_axisGeometry = new PathGeometry();
26
27 x_axisGeometry.Figures.Add(x_axisFigure);
28 y_axisGeometry.Figures.Add(y_axisFigure);
29
30 x_axisArrow.Data = x_axisGeometry;
31 y_axisArrow.Data = y_axisGeometry;
32
33 this.chartCanvas.Children.Add(x_axisArrow);
34 this.chartCanvas.Children.Add(y_axisArrow);
35 }
两个小箭头
WPF中没有画带箭头直线的函数,这个必需要自己写了,最好的方法当然还是在XAML中,用Path来绘制出一个三角形在线的末端。当然这种用绝对坐标绘出来的小三角形,它不方便绘制到别的画布中,当前单纯为了做出效果,后面可以用C#动态生成箭头,在后台完成绘制。上述代码的效果如下所示。
然后给坐标轴添加上x,y标签,
使用TextBlock表示出,对于标签的样式,是可以定义一个样式资源的,来统一风格,不必要每一个标签进行设置。但是目前,主要是为了实现功能,暂且不做得过于复杂。
1 <TextBlock x:Name="x_label" Text="x" Canvas.Left="477" Canvas.Top="279"
2 FontFamily="Arial" FontStyle="Italic" FontSize="20"/>
3 <TextBlock x:Name="y_label" Text="y" Canvas.Left="20" Canvas.Top="20"
4 FontFamily="Arial" FontStyle="Italic" FontSize="20"/>
5 <TextBlock x:Name="o_label" Text="o" Canvas.Left="20" Canvas.Top="272"
6 FontFamily="Arial" FontStyle="Italic" FontSize="20"/>
添加X,Y,O标签
3、坐标轴刻度和标签添加
刻度就是一系列的小直线,控制好每一条小直线的位置,就可以轻松作出标签。同样,先用XAML语言,静态画一个小线段,看一看效果,然后用C#语言,在后台动态作出所有的小线段。XAML代码如下
1 <Line x:Name="x_scale1" Stroke="Black" StrokeThickness="1" X1="50" Y1="280" X2="50" Y2="276" StrokeEndLineCap="Triangle"/>
2 <Line x:Name="y_scale1" Stroke="Black" StrokeThickness="1" X1="40" Y1="270" X2="44" Y2="270" StrokeEndLineCap="Triangle"/>
原点坐标O=(40,280)(这是相对于窗口的坐标),第一个x_scale1刻度,离原点10px距离,即打算每10px作一个刻度,故刻度的起点为(50,280);在轴上的刻度,终点的坐标相同,故为(50,276);|Y2-Y1|=4px,即为小刻度的长度。y_scale1也是同样的原理。作出两个小刻度后,效果如下
①坐标轴刻度线添加
每一个坐标轴的刻度线都很多,不可能一个个都用XAML语言都描述出来,还是要发挥一下C#代码的功力。在上面已经明白如何作出x轴上和y轴上的刻度,并且已经作出来一个,多作几个,无非是循环处理的问题,比较容易。C#代码如下。关键部分在14、15行和31、32行,在窗口的坐标系统中,x轴的方向是向右,而y轴的方向是向下,因此,x轴方向与我们所作的图表x轴方向一致,而y轴方向与图表y轴方向相反,所以,14行代码上相加,而31行代码上相减,这样就正确绘制了所有刻度了。
1 /// <summary>
2 /// 作出x轴和y轴的刻度线
3 /// </summary>
4 private void DrawScale()
5 {
6 for (int i = 0; i < 45; i += 1)//作480个刻度,因为当前x轴长 480px,每10px作一个小刻度,还预留了一些小空间
7 {
8 //原点 O=(40,280)
9 Line x_scale = new Line();
10 x_scale.StrokeEndLineCap = PenLineCap.Triangle;
11 x_scale.StrokeThickness = 1;
12 x_scale.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0));
13
14 x_scale.X1 = 40 + i * 10; //原点x=40,每10px作1个刻度
15 x_scale.X2 = x_scale.X1; //在x轴上的刻度线,起点和终点相同
16
17 x_scale.Y1 = 280; //与原点坐标的y=280,相同
18 x_scale.Y2 = x_scale.Y1 - 4;//刻度线长度为4px
19
20 if (i < 25)//由于y轴短一些,所以在此作出判断,只作25个刻度
21 {
22 //作出Y轴的刻度
23 Line y_scale = new Line();
24 y_scale.StrokeEndLineCap = PenLineCap.Triangle;
25 y_scale.StrokeThickness = 1;
26 y_scale.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0));
27
28 y_scale.X1 = 40; //原点x=40,在y轴上的刻度线的起点与原点相同
29 y_scale.X2 = y_scale.X1 + 4;//刻度线长度为4px
30
31 y_scale.Y1 = 280 - i * 10; //每10px作一个刻度
32 y_scale.Y2 = y_scale.Y1; //起点和终点y坐标相同
33 this.chartCanvas.Children.Add(y_scale);
34 }
35 this.chartCanvas.Children.Add(x_scale);
36 }
37 }
上述代码执行后效果如下(左图):
为了表达更加清楚,平时所用的图表都有一个大刻度,在此,我也添加一个,其实也非常容易,无非就是在for循环里面添加一个判断,到了所需要的位置的时候,将刻度线加粗,加长一些,也就是改变它的样式。添加大刻度线的C#代码如下,代码只是对上面的代码作了点小修改,其效果如上图(右图)。
1 /// <summary>
2 /// 作出x轴和y轴的标尺
3 /// </summary>
4 private void DrawScale()
5 {
6 for (int i = 0; i < 45; i += 1)//作480个刻度,因为当前x轴长 480px,每10px作一个小刻度,还预留了一些小空间
7 {
8 //原点 O=(40,280)
9 Line x_scale = new Line();
10 x_scale.StrokeEndLineCap = PenLineCap.Triangle;
11 x_scale.StrokeThickness = 1;
12 x_scale.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0));
13
14 x_scale.X1 = 40 + i * 10; //原点x=40,每10px作1个刻度
15 x_scale.X2 = x_scale.X1; //在x轴上的刻度线,起点和终点相同
16
17 x_scale.Y1 = 280; //与原点坐标的y=280,相同
18 if (i % 5 == 0)//每5个刻度添加一个大刻度
19 {
20 x_scale.StrokeThickness = 3;//把刻度线加粗一点
21 x_scale.Y2 = x_scale.Y1 - 8;//刻度线长度为8px
22 }
23 else
24 {
25 x_scale.Y2 = x_scale.Y1 - 4;//刻度线长度为4px
26 }
27
28 if (i < 25)//由于y轴短一些,所以在此作出判断,只作25个刻度
29 {
30 //作出Y轴的刻度
31 Line y_scale = new Line();
32 y_scale.StrokeEndLineCap = PenLineCap.Triangle;
33 y_scale.StrokeThickness = 1;
34 y_scale.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0));
35
36 y_scale.X1 = 40; //原点x=40,在y轴上的刻度线的起点与原点相同
37 if (i % 5 == 0)
38 {
39 y_scale.StrokeThickness = 3;
40 y_scale.X2 = y_scale.X1 + 8;//刻度线长度为4px
41 }
42 else
43 {
44 y_scale.X2 = y_scale.X1 + 4;//刻度线长度为8px
45 }
46
47 y_scale.Y1 = 280 - i * 10; //每10px作一个刻度
48 y_scale.Y2 = y_scale.Y1; //起点和终点y坐标相同
49 this.chartCanvas.Children.Add(y_scale);
50 }
51 this.chartCanvas.Children.Add(x_scale);
52 }
53 }
添加大刻度
②坐标轴标签添加
标签的显示还是使用文本块(TextBlock控件)来实现。为了让标签的显示位置刚刚好,对坐标值做了一些偏移,下面程序中已经解释得比较清楚了。
1 /// <summary>
2 /// 添加刻度标签
3 /// </summary>
4 private void DrawScaleLabel()
5 {
6 for (int i = 1; i < 7; i++)//7 个标签,一共
7 {
8 TextBlock x_ScaleLabel = new TextBlock();
9 TextBlock y_ScaleLabel = new TextBlock();
10
11 x_ScaleLabel.Text = (i * 50).ToString();//只给大刻度添加标签,每50px添加一个标签
12
13 Canvas.SetLeft(x_ScaleLabel, 40 + 5 * 10 * i - 12);//40是原点的坐标,-12是为了让标签看的位置剧中一点
14 Canvas.SetTop(x_ScaleLabel, 280 + 2);//让标签字往下移一点
15
16 y_ScaleLabel.Text = (i * 50).ToString();
17 Canvas.SetLeft(y_ScaleLabel, 40 - 25); //-25px是字体大小的偏移
18 Canvas.SetTop(y_ScaleLabel, 280 - 5 * 10 * i - 6); //280px是原点的坐标,同样-6是为了让标签不要上坐标轴叠上
19
20 this.chartCanvas.Children.Add(x_ScaleLabel);
21 this.chartCanvas.Children.Add(y_ScaleLabel);
22 }
23 }
添加刻度标签
运行此代码后,效果如下:
自己也尝试写了写效果,VisifireChart支持单组数据的对比,和多组数据的对比。
自己也尝试写了写效果,VisifireChart支持单组数据的对比,和多组数据的对比。
4、数据点添加和曲线绘制
①数据点添加显示
对于数据点的显示,用ellipse作出一个个小圆点,画在坐标轴中。为了解数据点显示的方法,还是先用XAML语言静态作出两个数据点P1=(60,80);P2=(180,100)。由于坐标系的不同,两个数据点在canvas画布里面的坐标要做一个转换。由于原点O=(40,280),且y轴是相反的方向,故x1=40+60=100;y1=280-80=200;可得P1_Canvas=(100,200);同样的方法,P2_Canvas=(220,180)。XAML代码如下
1 <Ellipse Fill="Blue" Height="8" Width="8" Canvas.Left="100" Canvas.Top="200"/>
2 <Ellipse Fill="Blue" Height="8" Width="8" Canvas.Left="220" Canvas.Top="180"/>
由于小圆点有一定的直径,为了让其中心与数据点重合,还要做一个小的调整,x轴值+半径=x中心;y轴值-半径=y中心。因此,两个数据点调整后的位置为P1_Canvas=(96,196),P2_Canvas=(216,176)。
先给程序定义一个数据点集合,后面生成8个随机点,X轴坐标是每隔50px出现一次,Y轴坐标的大小是随机生成的。C#代码如下
1 private void DrawPoint()
2 {
3 //随机生成8个点
4 Random rPoint = new Random();
5 for (int i = 0; i < 8; i++)
6 {
7 int x_point = i * 50;
8 int y_point = rPoint.Next(250);
9 dataPoints.Add(new Point(x_point, y_point));
10 }
11
12 for (int i = 0; i < dataPoints.Count; i++)
13 {
14 Ellipse dataEllipse = new Ellipse();
15 dataEllipse.Fill = new SolidColorBrush(Color.FromRgb(0, 0, 0xff));
16 dataEllipse.Width = 8;
17 dataEllipse.Height = 8;
18
19 Canvas.SetLeft(dataEllipse, 40 + dataPoints[i].X - 4);//-4是为了补偿圆点的大小,到精确的位置
20 Canvas.SetTop(dataEllipse, 280 - dataPoints[i].Y - 4);
21
22 chartCanvas.Children.Add(dataEllipse);
23 }
24 }
数据点集合声明如下
private List<Point> dataPoints = new List<Point>();
上述代码的运行效果如下(左图):
②曲线绘制
将所有点用折线描绘出来,C#代码如下,效果如上图(右图)。由于前后两次的代码是不同时间运行的,生成的点是随机的,不一样,所以两副图点不相同。
1 private void DrawCurve()
2 {
3 Polyline curvePolyline = new Polyline();
4
5 curvePolyline.Stroke = Brushes.Green;
6 curvePolyline.StrokeThickness = 2;
7
8 curvePolyline.Points = coordinatePoints;
9 chartCanvas.Children.Add(curvePolyline);
10 }
对coordinatePoints的定义和初始化如下。
//这行代码在程序的开始部分
private PointCollection coordinatePoints = new PointCollection();
----------------------------------------------------------------------------
//将数据点在画布中的位置保存下来
coordinatePoints.Add(new Point(40 + dataPoints[i].X, 280 - dataPoints[i].Y));
数据是从网上找的,然后自己弄成了XML格式文件,读取到了List列表里。
数据是从网上找的,然后自己弄成了XML格式文件,读取到了List列表里。
5、让图表动起来
首先,思考要让线动起来,表中两类元素需要移,一个线,另一个是小圆点。而线是随数据点动的,这一部分WPF已经帮我们做好了;而点动,我们要不停地改变它在canvas中的位置,这样就可以看到动的效果,每次单击一下鼠标,就添加一个点,这个点的Y轴坐标还是随机生成的,C#代码如下。这个函数主要就做了2件事,一个是移除集合中的第0个点、在最后位置添加一个点,二是将所有数据都向左移50px,很容易。
1 private void AddCurvePoint(Point dataPoint)
2 {
3 dataPoints.RemoveAt(0);
4 dataPoints.Add(dataPoint);
5 for (int i = 0; i < dataPoints.Count; i++)
6 {
7 //每一个点的X数据都要向左移动50px
8 dataPoints[i] = new Point(dataPoints[i].X - 50, dataPoints[i].Y);
9 coordinatePoints[i] = new Point(40 + dataPoints[i].X, 280 - dataPoints[i].Y);
10
11 Canvas.SetLeft(pointEllipses[i], 40 + dataPoints[i].X - 4);//-4是为了补偿圆点的大小,到精确的位置
12 Canvas.SetTop(pointEllipses[i], 280 - dataPoints[i].Y - 4);
13 }
14 }
鼠标单击时的程序如下:
1 private void chartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
2 {
3 //随机生成Y坐标
4 Point dataPoint = new Point(400, (new Random()).Next(250));
5
6 AddCurvePoint(dataPoint);
7 }
上面程序中用到的几个数据结构声明如下:
private List<Point> dataPoints = new List<Point>();
private PointCollection coordinatePoints = new PointCollection();
private List<Ellipse> pointEllipses = new List<Ellipse>();
终于做出来了,效果还不错的,来欣赏一下自己的成果,花了大半天了。
单组数据的对比,效果还是明显好看一些的。
单组数据的对比,效果还是明显好看一些的。
心得体会
合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。一个图表,虽然复杂,但一步一步来,将每一步用心构思,然后慢慢实现,虽然做不到完美,但是起码可以实现基本目标。
图标还有改多可以改进的地方,功能还很弱小。可以给图表加上网格,横轴的坐标的标签可以实时变化,以形成示波器的功能。
下一个目标,给图表添加更多功能。
下下一目标,完成窗体的美化,做出类似360安全卫士11的界面。
图表的样式是通过一个枚举参数设置的RenderAs,下设很多项:点,线,柱状图等,基本都是比较常用的。
图表的样式是通过一个枚举参数设置的RenderAs,下设很多项:点,线,柱状图等,基本都是比较常用的。
单组数据的时候,用哪种样式是都没有问题的,但是当多组数据组合时,如果是同一种样式也是OK的,但是如果是不同的样式,则有的是不兼容的。
单组数据的时候,用哪种样式是都没有问题的,但是当多组数据组合时,如果是同一种样式也是OK的,但是如果是不同的样式,则有的是不兼容的。
不兼容的,直接在设计页面,直接就会抛错,如图
不兼容的,直接在设计页面,直接就会抛错,如图
两组数据,一个设置的是Doughnut(圈),一个设置的是Line(线),这两种无法在一个X轴和Y轴上进行相关的实现显示,所以,会冲突。
两组数据,一个设置的是Doughnut(圈),一个设置的是Line(线),这两种无法在一个X轴和Y轴上进行相关的实现显示,所以,会冲突。
当两个都设置成Doughnut时,不冲突,可以显示,但是很乱,所以具体的效果,需要开发人员自己根据业务要求,进行相关的组装。
当两个都设置成Doughnut时,不冲突,可以显示,但是很乱,所以具体的效果,需要开发人员自己根据业务要求,进行相关的组装。
上面两篇帖子基本都讲述了如何去用VisifireChart,但是提供的代码一般都是CS的代码,个人更喜欢用XAML代码去实现,所以,我的代码除了Binding以外,页面的设计,是用XAML写的。
上面两篇帖子基本都讲述了如何去用VisifireChart,但是提供的代码一般都是CS的代码,个人更喜欢用XAML代码去实现,所以,我的代码除了Binding以外,页面的设计,是用XAML写的。
<Grid>
<chart:Chart Name="chart" AnimatedUpdate="True" AnimationEnabled="True" ThemeEnabled="True" View3D="True" ScrollingEnabled="True" ShadowEnabled="True" >
<chart:Chart.Titles>
<chart:Title FontSize="16" Text="部分省平均薪资"/>
</chart:Chart.Titles>
<chart:Chart.AxesY>
<chart:Axis Suffix="元"/>
</chart:Chart.AxesY>
<chart:Chart.Series>
<chart:DataSeries DataSource="{Binding WargeList,UpdateSourceTrigger=PropertyChanged}" LegendText="非城镇收入" RenderAs="Doughnut">
<chart:DataSeries.DataMappings>
<chart:DataMapping MemberName="AxisXLabel" Path="CityName"/>
<chart:DataMapping MemberName="YValue" Path="NpoWage"/>
<chart:DataMapping MemberName="Tag" Path="CityName"/>
</chart:DataSeries.DataMappings>
</chart:DataSeries>
<chart:DataSeries DataSource="{Binding WargeList,UpdateSourceTrigger=PropertyChanged}" LegendText="城镇收入" RenderAs="Doughnut">
<chart:DataSeries.DataMappings>
<chart:DataMapping MemberName="AxisXLabel" Path="CityName"/>
<chart:DataMapping MemberName="YValue" Path="PoWage"/>
<chart:DataMapping MemberName="Tag" Path="CityName"/>
</chart:DataSeries.DataMappings>
</chart:DataSeries>
</chart:Chart.Series>
</chart:Chart>
</Grid>
<Grid>
<chart:Chart Name="chart" AnimatedUpdate="True" AnimationEnabled="True" ThemeEnabled="True" View3D="True" ScrollingEnabled="True" ShadowEnabled="True" >
<chart:Chart.Titles>
<chart:Title FontSize="16" Text="部分省平均薪资"/>
</chart:Chart.Titles>
<chart:Chart.AxesY>
<chart:Axis Suffix="元"/>
</chart:Chart.AxesY>
<chart:Chart.Series>
<chart:DataSeries DataSource="{Binding WargeList,UpdateSourceTrigger=PropertyChanged}" LegendText="非城镇收入" RenderAs="Doughnut">
<chart:DataSeries.DataMappings>
<chart:DataMapping MemberName="AxisXLabel" Path="CityName"/>
<chart:DataMapping MemberName="YValue" Path="NpoWage"/>
<chart:DataMapping MemberName="Tag" Path="CityName"/>
</chart:DataSeries.DataMappings>
</chart:DataSeries>
<chart:DataSeries DataSource="{Binding WargeList,UpdateSourceTrigger=PropertyChanged}" LegendText="城镇收入" RenderAs="Doughnut">
<chart:DataSeries.DataMappings>
<chart:DataMapping MemberName="AxisXLabel" Path="CityName"/>
<chart:DataMapping MemberName="YValue" Path="PoWage"/>
<chart:DataMapping MemberName="Tag" Path="CityName"/>
</chart:DataSeries.DataMappings>
</chart:DataSeries>
</chart:Chart.Series>
</chart:Chart>
</Grid>
只需要将相关的数据绑定到对应的字段上,剩下的就交给程序自己吧。
只需要将相关的数据绑定到对应的字段上,剩下的就交给程序自己吧。
代码
代码