WPF仿百度Echarts人口遷移圖

GitHub地址:https://github.com/ptddqr/wpf-echarts-map/tree/masterhtml

關於大名鼎鼎的百度Echarts我就很少說了 不瞭解的朋友直接看官方的例子吧 http://echarts.baidu.com/examples.htmlgit

效果圖:github

 

關於可行性:之前常聽人說wpf動畫開多了會很卡,而我也沒有寫過含有大量動畫的項目,不知道實際怎樣,這個地圖顯然全是動畫,因此我寫了個測試動畫性能的小程序,生成100個點和線跑動畫,發現徹底沒有什麼問題.小程序

因此wpf作這個東西確定是徹底沒有問題的.附上這個小程序 動畫性能測試 有興趣的朋友能夠開點動畫 看看windows任務管理器裏的cpu和內存的消耗狀況windows

 

先說下大致的思路吧:api

  1. 若是你沒有搞設計的幫你作地圖的話,基本得去網上找矢量地圖,轉後轉換成path
  2. 找到省會城市的座標,這就是運動軌跡的起點和終點
  3. 根據起點終點生成運動軌跡的path和跑動的點,在點上作路徑動畫,生成一個圓,中心放到到達城市的座標處
  4. 初始化過程的動畫

佈局

最初的最初,咱們得先考慮佈局,爲了防止一旦作成用戶控件的話,設置尺寸時地圖走形.echarts

  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.ide

我是在百度文庫裏找到一個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更新:

將名稱註冊動畫改成對象註冊動畫

  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             #endregion
127         }
View Code

2016-12-19更新:

發佈到GitHub,地址:https://github.com/ptddqr/wpf-echarts-map/tree/master

 

源碼下載:仿百度Echarts人口遷移圖.zip

相關文章
相關標籤/搜索