WPF仿百度Echarts人口迁移图
GitHub地址:https://github.com/ptddqr/wpf-echarts-map/tree/master
关于大名鼎鼎的百度Echarts我就不多说了 不了解的朋友直接看官方的例子吧 http://echarts.baidu.com/examples.html
效果图:

关于可行性:以前常听人说wpf动画开多了会很卡,而我也没有写过含有大量动画的项目,不知道实际怎样,这个地图显然全是动画,所以我写了个测试动画性能的小程序,生成100个点和线跑动画,发现完全没有什么问题.
所以wpf做这个东西肯定是完全没有问题的.附上这个小程序 动画性能测试 有兴趣的朋友可以开点动画 看看windows任务管理器里的cpu和内存的消耗情况
先说下大体的思路吧:
- 如果你没有搞设计的帮你做地图的话,基本得去网上找矢量地图,转后转换成path
- 找到省会城市的坐标,这就是运动轨迹的起点和终点
- 根据起点终点生成运动轨迹的path和跑动的点,在点上做路径动画,生成一个圆,中心放到到达城市的坐标处
- 初始化过程的动画
布局
最初的最初,我们得先考虑布局,为了防止一旦做成用户控件的话,设置尺寸时地图走形.
- 最外层肯定要用Viewbox,按比例缩放.需要注意的是,Viewbox内部放的控件是必须有具体的尺寸的,它才能进行缩放,当然不一定必须要显式的去设置内部的Width和Height,只要内部有实际意义上的尺寸就行.
- Viewbox内先放一个Grid,分成两列Width全部设置成auto,这样能根据内部控件的实际大小来决定列宽.
- 0列放一个StackPanel.这个是左侧当菜单用的RadioButton的容器,每个RadioButton都有具体的宽度,所以0列就有了具体的宽度
- 1列再放一个Grid,这个Grid一定要设置HorizontalAlignment="Left" VerticalAlignment="Top",就是靠左上角布局,这样他内部的控件就会给它撑起来,也就有了具体的尺寸,这样Viewbox才能够缩放
- 把地图的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的话,思路可以参考我的另一篇博客 WPF绘制简单常用的Path
城市的圆
他就是个圆圈,没什么好说的,注意一下中心的定位就行了 Ellipse 颜色和轨迹一样 ToolTip写上你想显示的东西
运动轨迹
我用的是弧线ArcSegment 两个城市的点确定了,那么可以通过两个点的x,y,根据勾股定理计算出线段的长度.给一个点,连接这两个城市的点,可以组成一个三角形,两个城市组成的线段对面的那个角可以设置成一个角度参数,
这个线段固定,对角的角度固定,那么他所对应的外接圆的圆弧就是固定的.我们可以根据正弦定理a/sinA=2r求出外接圆的半径.就可以画出这个弧线来了.然后可以给这个path的ToolTip附上鼠标移上去想显示的文字.改下ToolTip的样式就行了
动画
点沿着轨迹跑的动画
这部分动画,我就不说了,参考周银辉的博客 http://www.cnblogs.com/zhouyinhui/archive/2007/07/31/837893.html
城市的圆的动画
给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更新:
将名称注册动画改为对象注册动画
private void AddPointToStoryboard(Grid runPoint, Ellipse toEll, Storyboard sb, Path particlePath, double l, ProvincialCapital from, MapToItem toItem)
{
double pointTime = l / m_Speed;//点运动所需的时间
double particleTime = pointTime / ;//轨迹呈现所需时间(跑的比点快两倍)
////生成为控件注册名称的guid
//string name = Guid.NewGuid().ToString().Replace("-", ""); #region 运动的点
TransformGroup tfg = new TransformGroup();
MatrixTransform mtf = new MatrixTransform();
tfg.Children.Add(mtf);
TranslateTransform ttf = new TranslateTransform(-runPoint.Width / , -runPoint.Height / );//纠正最上角沿path运动到中心沿path运动
tfg.Children.Add(ttf);
runPoint.RenderTransform = tfg;
//this.RegisterName("m" + name, mtf); MatrixAnimationUsingPath maup = new MatrixAnimationUsingPath();
maup.PathGeometry = particlePath.Data.GetFlattenedPathGeometry();
maup.Duration = new Duration(TimeSpan.FromSeconds(pointTime));
maup.RepeatBehavior = RepeatBehavior.Forever;
maup.AutoReverse = false;
maup.IsOffsetCumulative = false;
maup.DoesRotateWithTangent = true;
//Storyboard.SetTargetName(maup, "m" + name);
//Storyboard.SetTargetProperty(maup, new PropertyPath(MatrixTransform.MatrixProperty));
Storyboard.SetTarget(maup, runPoint);
Storyboard.SetTargetProperty(maup, new PropertyPath("(Grid.RenderTransform).Children[0].(MatrixTransform.Matrix)"));
sb.Children.Add(maup);
#endregion #region 达到城市的圆
//this.RegisterName("ell" + name, toEll);
//轨迹到达圆时 圆呈现
DoubleAnimation ellda = new DoubleAnimation();
ellda.From = 0.2;//此处值设置0-1会有不同的呈现效果
ellda.To = ;
ellda.Duration = new Duration(TimeSpan.FromSeconds(particleTime));
ellda.BeginTime = TimeSpan.FromSeconds(particleTime);//推迟动画开始时间 等轨迹连接到圆时 开始播放圆的呈现动画
ellda.FillBehavior = FillBehavior.HoldEnd;
//Storyboard.SetTargetName(ellda, "ell" + name);
//Storyboard.SetTargetProperty(ellda, new PropertyPath(Ellipse.OpacityProperty));
Storyboard.SetTarget(ellda, toEll);
Storyboard.SetTargetProperty(ellda, new PropertyPath(Ellipse.OpacityProperty));
sb.Children.Add(ellda);
//圆呈放射状
RadialGradientBrush rgBrush = new RadialGradientBrush();
GradientStop gStop0 = new GradientStop(Color.FromArgb(, , , ), );
//此为控制点 color的a值设为0 off值走0-1 透明部分向外放射 初始设为255是为了初始化效果 开始不呈放射状 等跑动的点运动到城市的圆后 color的a值才设为0开始呈现放射动画
GradientStop gStopT = new GradientStop(Color.FromArgb(, , , ), );
GradientStop gStop1 = new GradientStop(Color.FromArgb(, , , ), );
rgBrush.GradientStops.Add(gStop0);
rgBrush.GradientStops.Add(gStopT);
rgBrush.GradientStops.Add(gStop1);
toEll.OpacityMask = rgBrush;
//this.RegisterName("e" + name, gStopT);
//跑动的点达到城市的圆时 控制点由不透明变为透明 color的a值设为0 动画时间为0
ColorAnimation ca = new ColorAnimation();
ca.To = Color.FromArgb(, , , );
ca.Duration = new Duration(TimeSpan.FromSeconds());
ca.BeginTime = TimeSpan.FromSeconds(pointTime);
ca.FillBehavior = FillBehavior.HoldEnd;
//Storyboard.SetTargetName(ca, "e" + name);
//Storyboard.SetTargetProperty(ca, new PropertyPath(GradientStop.ColorProperty));
Storyboard.SetTarget(ca, toEll);
Storyboard.SetTargetProperty(ca, new PropertyPath("(Ellipse.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Color)"));
sb.Children.Add(ca);
//点达到城市的圆时 呈现放射状动画 控制点的off值走0-1 透明部分向外放射
DoubleAnimation eda = new DoubleAnimation();
eda.To = ;
eda.Duration = new Duration(TimeSpan.FromSeconds());
eda.RepeatBehavior = RepeatBehavior.Forever;
eda.BeginTime = TimeSpan.FromSeconds(particleTime);
//Storyboard.SetTargetName(eda, "e" + name);
//Storyboard.SetTargetProperty(eda, new PropertyPath(GradientStop.OffsetProperty));
Storyboard.SetTarget(eda, toEll);
Storyboard.SetTargetProperty(eda, new PropertyPath("(Ellipse.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Offset)"));
sb.Children.Add(eda);
#endregion #region 运动轨迹
//找到渐变的起点和终点
Point startPoint = GetProvincialCapitalPoint(from);
Point endPoint = GetProvincialCapitalPoint(toItem.To);
Point start = new Point(, );
Point end = new Point(, );
if (startPoint.X > endPoint.X)
{
start.X = ;
end.X = ;
}
if (startPoint.Y > endPoint.Y)
{
start.Y = ;
end.Y = ;
}
LinearGradientBrush lgBrush = new LinearGradientBrush();
lgBrush.StartPoint = start;
lgBrush.EndPoint = end;
GradientStop lgStop0 = new GradientStop(Color.FromArgb(, , , ), );
GradientStop lgStop1 = new GradientStop(Color.FromArgb(, , , ), );
lgBrush.GradientStops.Add(lgStop0);
lgBrush.GradientStops.Add(lgStop1);
particlePath.OpacityMask = lgBrush;
//this.RegisterName("p0" + name, lgStop0);
//this.RegisterName("p1" + name, lgStop1);
//运动轨迹呈现
DoubleAnimation pda0 = new DoubleAnimation();
pda0.To = ;
pda0.Duration = new Duration(TimeSpan.FromSeconds(particleTime));
pda0.FillBehavior = FillBehavior.HoldEnd;
//Storyboard.SetTargetName(pda0, "p0" + name);
//Storyboard.SetTargetProperty(pda0, new PropertyPath(GradientStop.OffsetProperty));
Storyboard.SetTarget(pda0, particlePath);
Storyboard.SetTargetProperty(pda0, new PropertyPath("(Path.OpacityMask).(GradientBrush.GradientStops)[0].(GradientStop.Offset)"));
sb.Children.Add(pda0);
DoubleAnimation pda1 = new DoubleAnimation();
//pda1.From = 0.5; //此处解开注释 值设为0-1 会有不同的轨迹呈现效果
pda1.To = ;
pda1.Duration = new Duration(TimeSpan.FromSeconds(particleTime));
pda1.FillBehavior = FillBehavior.HoldEnd;
//Storyboard.SetTargetName(pda1, "p1" + name);
//Storyboard.SetTargetProperty(pda1, new PropertyPath(GradientStop.OffsetProperty));
Storyboard.SetTarget(pda1, particlePath);
Storyboard.SetTargetProperty(pda1, new PropertyPath("(Path.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Offset)"));
sb.Children.Add(pda1);
#endregion
}
2016-12-19更新:
发布到GitHub,地址:https://github.com/ptddqr/wpf-echarts-map/tree/master
源码下载:仿百度Echarts人口迁移图.zip
WPF仿百度Echarts人口迁移图的更多相关文章
- 百度 echarts K线图使用
看个效果图先 首先在需要插入图例的HTML中嵌入 <div id="main" style="height:400px"></div> ...
- 使用百度Echarts制作力导向图
最近项目需求制作一个力导向图来展示企业的画像等关系信息,故想到了百度Echarts的关系图,在这使用Echarts3.0版本来实现.先上效果图,再看代吗 哎,本来想整个工程扔出来,发现好像没地方上传附 ...
- C#+JQuery+.Ashx+百度Echarts实现全国省市地图和饼状图动态数据图形报表的统计
在目前的一个项目中,需要用到报表表现数据,这些数据有多个维度,需要同时表现出来,同时可能会有大量数据呈现的需求,经过几轮挑选,最终选择了百度的echarts作为报表基础类库.echarts功能强大,界 ...
- 仿百度壁纸客户端(二)——主页自定义ViewPager广告定时轮播图
仿百度壁纸客户端(二)--主页自定义ViewPager广告定时轮播图 百度壁纸系列 仿百度壁纸客户端(一)--主框架搭建,自定义Tab + ViewPager + Fragment 仿百度壁纸客户端( ...
- echarts迁移图动态加载
迁移图 获取迁移城市的经纬度 可以调用高德的接口,实现根据地名找寻经纬度的方法 #!/usr/bin/env python3 #-*- coding:utf-8 -*- ''' 利用高德地图api实现 ...
- 流量分析系统----讲解-echarts模拟迁移(结合china.js)
百度 Echarts 地图->模拟迁徙,实现自动切换地图 小航哥注释: 1.本文主要是把模拟迁移的流程讲了一遍,讲的很好.具体实现参考航哥这篇随笔“流量分析系统----实现-echarts模拟迁 ...
- iOS-Andriod百度地图仿百度外卖-饿了么-选择我的地址-POI检索/
http://zanderzhang.gitcafe.io/2015/09/19/iOS-Andriod百度地图仿百度外卖-饿了么-选择我的地址-POI检索/ 百度外卖选择送货地址: 饿了么选择送货地 ...
- 百度echarts使用--y轴label数字太长难以全部显示
问题: 今天遇到个小问题,我们系统前端呈现使用了百度echarts.在绘制折线图的时候,因为数字过大,导致显示出现了问题. 解决方案: 左边y轴的值默认是根据我们填充进去的值来默认分割的,因为原始值就 ...
- 仿百度壁纸客户端(六)——完结篇之Gallery画廊实现壁纸预览已经项目细节优化
仿百度壁纸客户端(六)--完结篇之Gallery画廊实现壁纸预览已经项目细节优化 百度壁纸系列 仿百度壁纸客户端(一)--主框架搭建,自定义Tab + ViewPager + Fragment 仿百度 ...
随机推荐
- ASP.NET Core 中文文档 第三章 原理(6)全球化与本地化
原文:Globalization and localization 作者:Rick Anderson.Damien Bowden.Bart Calixto.Nadeem Afana 翻译:谢炀(Kil ...
- Effective前端3:用CSS画一个三角形
p { text-indent: 2em } .triangle-container p { text-indent: 0 } img { margin: 15px 0 } 三角形的场景很常见,打开一 ...
- js实现蛇形矩阵
参加腾讯前端实习生笔试,真的是被虐了千百遍,除了一条js程序题,其他半点前端都没有,都是考算法,计算机原理,数据结构.下面贴上腾讯笔试最后三大条中的一条,实现一个蛇形矩阵的输出.蛇形矩阵的什么样这里我 ...
- StackExchange.Redis客户端读写主从配置,以及哨兵配置。
今天简单分享一下StackExchange.Redis客户端中配置主从分离以及哨兵的配置. 关于哨兵如果有不了解的朋友,可以看我之前的一篇分享,当然主从复制文章也可以找到.http://www.cnb ...
- SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端
目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模拟OpenID认证 通过authorization co ...
- 【微信开发】公众号后台设置错误导致的微信redirect_uri参数错误【图】
在微信开发中,如微信网页授权登录,分享到朋友圈自定义内容,微信h5支付时 可能会遇到微信redirect_uri参数错误的情况. 此时除了检查自己代码正确性外,还要检查一下是否正确地设置了公众号后台的 ...
- entityframework学习笔记--006-表拆分与实体拆分
1.1 拆分实体到多张表 假设你有如下表,如图6-1.Product表用于存储商品的字符类信息,ProductWebInfo用于存储商品的图片,两张表通过SKU关联.现在你想把两张表的信息整合到一个实 ...
- 解决ngnix服务器上的Discuz!x2.5 Upload Error:413错误
1.修改php.ini sudo nano /etc/php5/fpm/php.ini #打开php.ini找到并修改以下的参数,目的是修改上传限制 max_execution_time = 900 ...
- iOS 隐藏状态栏
1.整个项目隐藏状态栏 在Targets->General->勾选中Hide status bar . 整个项目隐藏状态栏 2.单个界面隐藏状态栏,例如登录注册页面 1.首先在info.p ...
- 腾讯开放平台 手机QQ登录 错误码:110406 解决办法
作者:Panda Fang 出处:http://www.cnblogs.com/lonkiss/p/4204284.html 原创文章,转载请注明作者和出处,未经允许不可用于商业营利活动 腾讯开发平台 ...