博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WPF仿百度Echarts人口迁移图
阅读量:6714 次
发布时间:2019-06-25

本文共 10214 字,大约阅读时间需要 34 分钟。

原文:

GitHub地址:

关于大名鼎鼎的百度Echarts我就不多说了 不了解的朋友直接看官方的例子吧 

效果图:

 

关于可行性:以前常听人说wpf动画开多了会很卡,而我也没有写过含有大量动画的项目,不知道实际怎样,这个地图显然全是动画,所以我写了个测试动画性能的小程序,生成100个点和线跑动画,发现完全没有什么问题.

所以wpf做这个东西肯定是完全没有问题的.附上这个小程序  有兴趣的朋友可以开点动画 看看windows任务管理器里的cpu和内存的消耗情况

 

先说下大体的思路吧:

  1. 如果你没有搞设计的帮你做地图的话,基本得去网上找矢量地图,转后转换成path
  2. 找到省会城市的坐标,这就是运动轨迹的起点和终点
  3. 根据起点终点生成运动轨迹的path和跑动的点,在点上做路径动画,生成一个圆,中心放到到达城市的坐标处
  4. 初始化过程的动画

布局

最初的最初,我们得先考虑布局,为了防止一旦做成用户控件的话,设置尺寸时地图走形.

  1. 最外层肯定要用Viewbox,按比例缩放.需要注意的是,Viewbox内部放的控件是必须有具体的尺寸的,它才能进行缩放,当然不一定必须要显式的去设置内部的Width和Height,只要内部有实际意义上的尺寸就行.
  2. Viewbox内先放一个Grid,分成两列Width全部设置成auto,这样能根据内部控件的实际大小来决定列宽.
  3. 0列放一个StackPanel.这个是左侧当菜单用的RadioButton的容器,每个RadioButton都有具体的宽度,所以0列就有了具体的宽度
  4. 1列再放一个Grid,这个Grid一定要设置HorizontalAlignment="Left" VerticalAlignment="Top",就是靠左上角布局,这样他内部的控件就会给它撑起来,也就有了具体的尺寸,这样Viewbox才能够缩放
  5. 把地图的path全部放到这个Grid里,path的Stretch必须是None,这样path就会把这个Grid给撑起来,在这个Grid里面所有path的下面再放一个Grid,用来做生成的动画用的图形的容器,他的坐标是和父级Grid的坐标重合的

地图

关于找地图,不好找,我没有什么好的心得.反正目的就是找一个带有省会标记的地图矢量图,只要是矢量图,我们就应该有办法把他转换成Xmal.

我是在百度文库里找到一个ppt版的矢量地图,0下载券  下载下来后用ppt打开,要用微软的,别的可能保存不了源文件,右键地图=>另存为=>选.emf格式,然后用Microsoft Expression Design打开,然后右键=>导出

这样就得到了我们要的path,然后找到每个省会所对应的path,取他们的Canvas.Left+Width/2 Canvas.Top+Height/2 就是对应坐标点的(x,y).(我算的没这么精细,就是大概加了下.这个工作太枯燥,这不是重点.)

先吐槽下我找的这个地图,北京和天津是连在一起的,廊坊也消失不见了,3个城市整个合成了一个path.所以建议大家自己再去找找

注意wpf的坐标都是以左上角开始的(0,0) 向右加x值 向下加y值 后面我们生成的图形定位时都要 x值-自身Width/2 y值-自身Height/2 这样才能让图形的中心对准需要定位的坐标点

有了地图和坐标,我们就可以做下面的工作了

生成动画所需要的跑动的点,运动轨迹的path,表示到达城市的圆圈

跑动的点

跑动的点,我用了一个Grid里面套了一个path和一个Ellipse.

椭圆做阴影,颜色和轨迹一样,加一个透明掩码OpacityMask,里面是一个放射型的渐变画刷RadialGradientBrush.原点GradientOrigin(0.8,0.5) offset0处设置为不透明,offset1处不透明度设置为2/16.

水滴型的path我就用blend里的钢笔随意画了一个,得到了它的Data. Fill给一个线型渐变画刷,StartPoint(0,0),EndPoint(1,0),offset0给一个半透明的轨迹色,offset1给个不透明的纯白.

这个Grid的IsHitTestVisible可以设置成false,不参与命中测试,这样鼠标在轨迹上时,点经过时,不会打断轨迹ToolTip的显示.

代码控,想自己写path的话,思路可以参考我的另一篇博客

城市的圆

他就是个圆圈,没什么好说的,注意一下中心的定位就行了 Ellipse 颜色和轨迹一样 ToolTip写上你想显示的东西

运动轨迹

我用的是弧线ArcSegment 两个城市的点确定了,那么可以通过两个点的x,y,根据勾股定理计算出线段的长度.给一个点,连接这两个城市的点,可以组成一个三角形,两个城市组成的线段对面的那个角可以设置成一个角度参数,

这个线段固定,对角的角度固定,那么他所对应的外接圆的圆弧就是固定的.我们可以根据正弦定理a/sinA=2r求出外接圆的半径.就可以画出这个弧线来了.然后可以给这个path的ToolTip附上鼠标移上去想显示的文字.改下ToolTip的样式就行了

动画

点沿着轨迹跑的动画

这部分动画,我就不说了,参考周银辉的博客 

 

城市的圆的动画

给Ellipse的透明掩码OpacityMask加一个放射型的渐变画刷RadialGradientBrush,加三个节点,offset0,offset1都是不透明的,在他们中间加一个完全透明的节点,然后动画控制offset值由0到1或由1到0,效果不同.

初始化过程的动画

这部分动画其实就是计算时间,在合适的时间开始合适的动画.

运动轨迹的呈现:就是给运动轨迹path的透明掩码给一个线型渐变画刷,根据向左,右,上,下运动,设置好StartPoint和EndPoint,然后两个节点一个透明,一个不透明,同时从0向1做动画,需要注意的是如果一前一后运动,一定要透明的那个节点在前面运动,

不然会出现很怪异的行为,把这个动画的时间设置成跑动的点的一半的时间.这样轨迹比点跑的快,不至于点跑过去了,路径还没有呈现到那

关于城市的圆,这部分加的比较多,首先可以用一个DoubleAnimation来控制Ellipse的透明度,开始时间是轨迹呈现的时间,也就是点的时间/2,这样刚好轨迹呈现到圆时,圆开始呈现,动画时间也设置成轨迹呈现时间,这样刚好点运动到圆的时候,圆已经完全呈现完.

然后加一个ColorAnimation,来控制圆透明掩码里放射画刷的第二个节点,也就是控制点,让他变为透明,用时0就可以,这样就可以继续圆的放射型动画了.开始时间就是点运动到圆的时间.

接下来就是一些RadioButton,ToolTip,Path的样式问题了.这部分大家看心情,做个自己喜欢的样式就可以了.

 

2016-08-01更新:

将名称注册动画改为对象注册动画

1         private void AddPointToStoryboard(Grid runPoint, Ellipse toEll, Storyboard sb, Path particlePath, double l, ProvincialCapital from, MapToItem toItem)  2         {  3             double pointTime = l / m_Speed;//点运动所需的时间  4             double particleTime = pointTime / 2;//轨迹呈现所需时间(跑的比点快两倍)  5             ////生成为控件注册名称的guid  6             //string name = Guid.NewGuid().ToString().Replace("-", "");  7   8             #region 运动的点  9             TransformGroup tfg = new TransformGroup(); 10             MatrixTransform mtf = new MatrixTransform(); 11             tfg.Children.Add(mtf); 12             TranslateTransform ttf = new TranslateTransform(-runPoint.Width / 2, -runPoint.Height / 2);//纠正最上角沿path运动到中心沿path运动 13             tfg.Children.Add(ttf); 14             runPoint.RenderTransform = tfg; 15             //this.RegisterName("m" + name, mtf); 16  17             MatrixAnimationUsingPath maup = new MatrixAnimationUsingPath(); 18             maup.PathGeometry = particlePath.Data.GetFlattenedPathGeometry(); 19             maup.Duration = new Duration(TimeSpan.FromSeconds(pointTime)); 20             maup.RepeatBehavior = RepeatBehavior.Forever; 21             maup.AutoReverse = false; 22             maup.IsOffsetCumulative = false; 23             maup.DoesRotateWithTangent = true; 24             //Storyboard.SetTargetName(maup, "m" + name); 25             //Storyboard.SetTargetProperty(maup, new PropertyPath(MatrixTransform.MatrixProperty)); 26             Storyboard.SetTarget(maup, runPoint); 27             Storyboard.SetTargetProperty(maup, new PropertyPath("(Grid.RenderTransform).Children[0].(MatrixTransform.Matrix)")); 28             sb.Children.Add(maup); 29             #endregion 30  31             #region 达到城市的圆 32             //this.RegisterName("ell" + name, toEll); 33             //轨迹到达圆时 圆呈现 34             DoubleAnimation ellda = new DoubleAnimation(); 35             ellda.From = 0.2;//此处值设置0-1会有不同的呈现效果 36             ellda.To = 1; 37             ellda.Duration = new Duration(TimeSpan.FromSeconds(particleTime)); 38             ellda.BeginTime = TimeSpan.FromSeconds(particleTime);//推迟动画开始时间 等轨迹连接到圆时 开始播放圆的呈现动画 39             ellda.FillBehavior = FillBehavior.HoldEnd; 40             //Storyboard.SetTargetName(ellda, "ell" + name); 41             //Storyboard.SetTargetProperty(ellda, new PropertyPath(Ellipse.OpacityProperty)); 42             Storyboard.SetTarget(ellda, toEll); 43             Storyboard.SetTargetProperty(ellda, new PropertyPath(Ellipse.OpacityProperty)); 44             sb.Children.Add(ellda); 45             //圆呈放射状 46             RadialGradientBrush rgBrush = new RadialGradientBrush(); 47             GradientStop gStop0 = new GradientStop(Color.FromArgb(255, 0, 0, 0), 0); 48             //此为控制点 color的a值设为0 off值走0-1 透明部分向外放射 初始设为255是为了初始化效果 开始不呈放射状 等跑动的点运动到城市的圆后 color的a值才设为0开始呈现放射动画 49             GradientStop gStopT = new GradientStop(Color.FromArgb(255, 0, 0, 0), 0); 50             GradientStop gStop1 = new GradientStop(Color.FromArgb(255, 0, 0, 0), 1); 51             rgBrush.GradientStops.Add(gStop0); 52             rgBrush.GradientStops.Add(gStopT); 53             rgBrush.GradientStops.Add(gStop1); 54             toEll.OpacityMask = rgBrush; 55             //this.RegisterName("e" + name, gStopT); 56             //跑动的点达到城市的圆时 控制点由不透明变为透明 color的a值设为0 动画时间为0 57             ColorAnimation ca = new ColorAnimation(); 58             ca.To = Color.FromArgb(0, 0, 0, 0); 59             ca.Duration = new Duration(TimeSpan.FromSeconds(0)); 60             ca.BeginTime = TimeSpan.FromSeconds(pointTime); 61             ca.FillBehavior = FillBehavior.HoldEnd; 62             //Storyboard.SetTargetName(ca, "e" + name); 63             //Storyboard.SetTargetProperty(ca, new PropertyPath(GradientStop.ColorProperty)); 64             Storyboard.SetTarget(ca, toEll); 65             Storyboard.SetTargetProperty(ca, new PropertyPath("(Ellipse.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Color)")); 66             sb.Children.Add(ca); 67             //点达到城市的圆时 呈现放射状动画 控制点的off值走0-1 透明部分向外放射 68             DoubleAnimation eda = new DoubleAnimation(); 69             eda.To = 1; 70             eda.Duration = new Duration(TimeSpan.FromSeconds(2)); 71             eda.RepeatBehavior = RepeatBehavior.Forever; 72             eda.BeginTime = TimeSpan.FromSeconds(particleTime); 73             //Storyboard.SetTargetName(eda, "e" + name); 74             //Storyboard.SetTargetProperty(eda, new PropertyPath(GradientStop.OffsetProperty)); 75             Storyboard.SetTarget(eda, toEll); 76             Storyboard.SetTargetProperty(eda, new PropertyPath("(Ellipse.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Offset)")); 77             sb.Children.Add(eda); 78             #endregion 79  80             #region 运动轨迹 81             //找到渐变的起点和终点 82             Point startPoint = GetProvincialCapitalPoint(from); 83             Point endPoint = GetProvincialCapitalPoint(toItem.To); 84             Point start = new Point(0, 0); 85             Point end = new Point(1, 1); 86             if (startPoint.X > endPoint.X) 87             { 88                 start.X = 1; 89                 end.X = 0; 90             } 91             if (startPoint.Y > endPoint.Y) 92             { 93                 start.Y = 1; 94                 end.Y = 0; 95             } 96             LinearGradientBrush lgBrush = new LinearGradientBrush(); 97             lgBrush.StartPoint = start; 98             lgBrush.EndPoint = end; 99             GradientStop lgStop0 = new GradientStop(Color.FromArgb(255, 0, 0, 0), 0);100             GradientStop lgStop1 = new GradientStop(Color.FromArgb(0, 0, 0, 0), 0);101             lgBrush.GradientStops.Add(lgStop0);102             lgBrush.GradientStops.Add(lgStop1);103             particlePath.OpacityMask = lgBrush;104             //this.RegisterName("p0" + name, lgStop0);105             //this.RegisterName("p1" + name, lgStop1);106             //运动轨迹呈现107             DoubleAnimation pda0 = new DoubleAnimation();108             pda0.To = 1;109             pda0.Duration = new Duration(TimeSpan.FromSeconds(particleTime));110             pda0.FillBehavior = FillBehavior.HoldEnd;111             //Storyboard.SetTargetName(pda0, "p0" + name);112             //Storyboard.SetTargetProperty(pda0, new PropertyPath(GradientStop.OffsetProperty));113             Storyboard.SetTarget(pda0, particlePath);114             Storyboard.SetTargetProperty(pda0, new PropertyPath("(Path.OpacityMask).(GradientBrush.GradientStops)[0].(GradientStop.Offset)"));115             sb.Children.Add(pda0);116             DoubleAnimation pda1 = new DoubleAnimation();117             //pda1.From = 0.5; //此处解开注释 值设为0-1 会有不同的轨迹呈现效果118             pda1.To = 1;119             pda1.Duration = new Duration(TimeSpan.FromSeconds(particleTime));120             pda1.FillBehavior = FillBehavior.HoldEnd;121             //Storyboard.SetTargetName(pda1, "p1" + name);122             //Storyboard.SetTargetProperty(pda1, new PropertyPath(GradientStop.OffsetProperty));123             Storyboard.SetTarget(pda1, particlePath);124             Storyboard.SetTargetProperty(pda1, new PropertyPath("(Path.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Offset)"));125             sb.Children.Add(pda1);126             #endregion127         }
View Code

2016-12-19更新:

发布到GitHub,地址:

 

源码下载:

转载地址:http://ewelo.baihongyu.com/

你可能感兴趣的文章
Thinkpad 笔记本 装win7 64 位操作系统热键驱动装不上问题解决!
查看>>
【演讲实录】下一代企业级应用架构管理体系
查看>>
1.11考试
查看>>
变量和数据类型 .py
查看>>
最小生成树专题总结
查看>>
BOM属性对象方法
查看>>
02 求1-100所有整数的和
查看>>
springboot pom.xml记
查看>>
Generating Sankey Diagrams from rCharts
查看>>
数学计划
查看>>
第0周---python网络爬虫前奏
查看>>
LinqPad使用教程
查看>>
各种排序算法比较
查看>>
限制html文本框input只能输入数字和小数点
查看>>
hdu 1251 统计难题 (字典树)
查看>>
HDU 3486 Interviewe【二分+rmq】
查看>>
HDU 2614 Beat【深搜】
查看>>
c#实现redis哈希槽CRC16方法
查看>>
javascript数据结构与算法--二叉树(插入节点、生成二叉树)
查看>>
qcharts编译
查看>>