首先來看一看實現的效果:
PS:原始的程序中更新曲線數據時添加了過渡的效果,具體可查看官網的示例:
http://www.visifire.com/silverlight_spline_charts_gallery.php
點擊其中的一個例子,然后點擊Live Updates,就可看到數據更新時的過渡效果。但是蛋疼的博客園,不知道為什么,我插入了我原始的xap文件,過渡效果卻沒有了,每次更新數據時,曲線直接就出來了(中間沒有過渡行為),開始的時候以為是上傳后xap文件有可能發生了變化,或者進行壓縮什么的,或者瀏覽器解析什么的,於是我把上傳到博客園的xap文件又下載下來,然后嵌入到一個htm頁面中,發現還是有過度效果的,但是為什么在博客園的日志中就沒有呢?整了好久還是沒有整明白,求解釋!
下面來看看具體實現的過程吧:
首先需要添加SLVisifire.Charts,FJ.Core的引用。然后開始我們正式的工作:
1.定義一個Chart,該Chart即為顯示的圖表,以及定義一個Timer,用於定時刷新數據。
Chart chart; Random rand = new Random(DateTime.Now.Millisecond); System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
2.定義一個方法:CreateChart實例化Chart,並完成Chart的相關設置(如:高,寬,曲線樣式等等)
/// <summary> /// Function to create a Visifire Chart /// </summary> public void CreateChart() { try { // Create a new instance of a Chart chart = new Chart(); //添加X,Y坐標的描述 Axis axisX = new Axis() { Title = "月份", FontSize = 18, }; chart.AxesX.Add(axisX); Axis axisY = new Axis() { Title = "實時監測值", FontSize = 16, }; chart.AxesY.Add(axisY); // 設置圖表的高寬 chart.Width = 640; chart.Height = 300; //Line數據更新時的過渡效果 chart.AnimatedUpdate = true; // 定義DatatSeries實例,即一條曲線 DataSeries dataSeries1 = new DataSeries(); DataSeries dataSeries2 = new DataSeries(); //只有當Series的個數大於等於2個時Lenged才會生效 dataSeries1.LegendText = "CO2"; dataSeries2.LegendText = "SO2"; // 設置DataSeries樣式 dataSeries1.RenderAs = RenderAs.Spline; dataSeries2.RenderAs = RenderAs.Spline; // 定義數據點 DataPoint dataPoint1; DataPoint dataPoint2; for (int i = 1; i <= 12; i++) { // 實例化數據點 dataPoint1= new DataPoint(); dataPoint2 = new DataPoint(); //設置數據值 dataPoint1.YValue = rand.Next(0, 99); dataPoint2.YValue = rand.Next(0, 99); //設置X軸顯示名稱 dataPoint1.AxisXLabel = string.Format("{0} 月", i); dataPoint2.AxisXLabel = string.Format("{0} 月", i); // 添加數據點 dataSeries1.DataPoints.Add(dataPoint1); dataSeries2.DataPoints.Add(dataPoint2); } // 將DataSeries(曲線)添加到Chart中 chart.Series.Add(dataSeries1); chart.Series.Add(dataSeries2); // 注冊Chart Loaded事件,在該事件中設置Timer的間斷值及注冊Timer Tick事件 chart.Loaded += new RoutedEventHandler(chart_Loaded); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }
3.我們看到在創建Chart中注冊了Loaded事件,在該事件的完成函數我們設置Timer的間斷值及注冊Timer Tick事件
void chart_Loaded(object sender, RoutedEventArgs e) { timer.Tick += new EventHandler(timer_Tick); timer.Interval = new TimeSpan(0, 0, 0, 0, 2000); }
/// <summary> /// Event handler for Tick event of Dispatcher Timer /// </summary> /// <param name="sender">System.Windows.Threading.DispatcherTimer</param> /// <param name="e">EventArgs</param> void timer_Tick(object sender, EventArgs e) { for (Int32 i = 0; i < 12; i++) { // 更新曲線數據 chart.Series[0].DataPoints[i].YValue = rand.Next(0, 99); chart.Series[1].DataPoints[i].YValue = rand.Next(20, 80); } }
4.這里我們再寫一個方法:AddChartToMapLayer,該方法用來將Chart添加到地圖中
/// <summary> /// Add Chart to Map /// </summary> /// <param name="chart">Chart</param> /// <param name="position">Position</param> public void AddChartToMapLayer(Chart chart, Graphic position) { ElementLayer chartlayer = new ElementLayer(); chartlayer.ID = "ChartLayer"; chartlayer.Opacity = 1; if (Map.Layers["ChartLayer"] != null) { Map.Layers.Remove(Map.Layers["ChartLayer"]); } chart.Titles.Add(new Title() { Text=string.Format("City: {0}",position.Attributes["CITY_NAME"].ToString ()) }); //獲得Graphic的中心坐標 ESRI.ArcGIS.Client.Geometry.Geometry geometry=position.Geometry; MapPoint mapPoint=new MapPoint ((geometry.Extent.XMax+geometry.Extent.XMin)/2,(geometry.Extent.YMax+geometry.Extent.YMin)/2); //獲得圖層X,Y方向對應的比例 double cell_X = (Map.Extent.XMax - Map.Extent.XMin) / Map.ActualWidth; double cell_Y = (Map.Extent.YMax - Map.Extent.YMin) / Map.ActualHeight; //Chart的寬度為500所以,Chart的中心則位於寬度等於250的位置,將Chart向右移動,以防Chart覆蓋Graphic Envelope extent = new Envelope(mapPoint.X + (320 + 20) * cell_X, mapPoint.Y, mapPoint.X + (320 + 20) * cell_X, mapPoint.Y); //設置ElementLayer的外包范圍 ElementLayer.SetEnvelope(chart, extent); chartlayer.Children.Add(chart); Map.Layers.Add(chartlayer); }
現在准備工作已經完成,下面就調用這些方法來實現上述的功能,我們大體的功能過程是:
當鼠標移入Graphic是就顯示其對應的Chart,移除的移除Chart,這里我們注冊一下FeatureLayer的MouseEnter和MouseLeave事件就行,我們再后台的XAML中注冊這兩個事件。
<esri:FeatureLayer ID="MyFeatureLayer" Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/0" Where="POP1990 > 500000" MouseEnter="FeatureLayer_MouseEnter" MouseLeave="FeatureLayer_MouseLeave" Renderer="{StaticResource MySimplePointRenderer}"/>
最后在后台添加如下代碼:
private void FeatureLayer_MouseEnter(object sender, GraphicMouseEventArgs e) { //繪制Chart CreateChart(); //將Chart添加到Map AddChartToMapLayer(chart, e.Graphic); //啟動Timer,定時刷新數據 timer.Start(); } private void FeatureLayer_MouseLeave(object sender, GraphicMouseEventArgs e) { //停止Timer timer.Stop(); //移除ElementLayer if (Map.Layers["ChartLayer"] != null) { Map.Layers.Remove(Map.Layers["ChartLayer"]); } }
后台的XAML代碼:

<UserControl x:Class="SilverlightChartDemo.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400" xmlns:esri="http://schemas.esri.com/arcgis/client/2009"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.Resources> <esri:SimpleRenderer x:Key="MySimplePointRenderer"> <esri:SimpleRenderer.Symbol> <esri:SimpleMarkerSymbol Size="15" Style="Circle"> <esri:SimpleMarkerSymbol.ControlTemplate> <ControlTemplate> <Grid x:Name="RootElement" RenderTransformOrigin="0.5,0.5" Width="15" Height="15"> <Grid.RenderTransform> <ScaleTransform x:Name="customEnlargeRotatingMarkerSymbolScale" ScaleX="1" ScaleY="1" /> </Grid.RenderTransform> <Grid.Resources> <DropShadowEffect x:Key="customEnlargeRotatingMarkerSymbolEffect" /> </Grid.Resources> <vsm:VisualStateManager.VisualStateGroups> <vsm:VisualStateGroup x:Name="CommonStates"> <vsm:VisualState x:Name="Normal"> <Storyboard> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="customEnlargeRotatingMarkerSymbolScale" Storyboard.TargetProperty="ScaleX" To="1" Duration="0:0:0.2" /> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="customEnlargeRotatingMarkerSymbolScale" Storyboard.TargetProperty="ScaleY" To="1" Duration="0:0:0.2" /> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="customEnlargeRotatingMarkerSymbolRotate" Storyboard.TargetProperty="Angle" To="360" Duration="0:0:0.2" /> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.ShadowDepth)" To="2" Duration="0:0:0.2" /> </Storyboard> </vsm:VisualState> <vsm:VisualState x:Name="MouseOver"> <Storyboard> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="customEnlargeRotatingMarkerSymbolScale" Storyboard.TargetProperty="ScaleX" To="2" Duration="0:0:0.2" /> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="customEnlargeRotatingMarkerSymbolScale" Storyboard.TargetProperty="ScaleY" To="2" Duration="0:0:0.2" /> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="customEnlargeRotatingMarkerSymbolRotate" Storyboard.TargetProperty="Angle" To="0" Duration="0:0:0.2" /> <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.ShadowDepth)" To="5" Duration="0:0:0.2" /> </Storyboard> </vsm:VisualState> </vsm:VisualStateGroup> </vsm:VisualStateManager.VisualStateGroups> <Ellipse x:Name="ellipse" Width="15" Height="15" Fill="Green" Stroke="White" StrokeThickness="2" HorizontalAlignment="Center" VerticalAlignment="Center" Effect="{StaticResource customEnlargeRotatingMarkerSymbolEffect}"></Ellipse> <Canvas x:Name="RotateCanvas" HorizontalAlignment="Left" VerticalAlignment="Top" Width="15" Height="15" RenderTransformOrigin="0.5,0.5"> <Canvas.Clip> <EllipseGeometry RadiusX="7.5" RadiusY="7.5" Center="7.5,7.5" /> </Canvas.Clip> <Canvas.RenderTransform> <RotateTransform x:Name="customEnlargeRotatingMarkerSymbolRotate" Angle="360" /> </Canvas.RenderTransform> <Line Stroke="White" StrokeThickness="2" X1="0" Y1="0" X2="15" Y2="15" /> <Line Stroke="White" StrokeThickness="2" X1="0" Y1="15" X2="15" Y2="0" /> </Canvas> </Grid> </ControlTemplate> </esri:SimpleMarkerSymbol.ControlTemplate> </esri:SimpleMarkerSymbol> </esri:SimpleRenderer.Symbol> </esri:SimpleRenderer> </Grid.Resources> <esri:Map Background="White" HorizontalAlignment="Stretch" Margin="0" Grid.ColumnSpan="3" Name="Map" VerticalAlignment="Stretch" WrapAround="True" Extent="-15000000,2000000,-7000000,8000000"> <esri:Map.Layers> <esri:LayerCollection> <esri:ArcGISTiledMapServiceLayer Url="http://www.arcgisonline.cn/ArcGIS/rest/services/ChinaOnlineCommunity/MapServer" /> <esri:FeatureLayer ID="MyFeatureLayer" Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/0" Where="POP1990 > 500000" MouseEnter="FeatureLayer_MouseEnter" MouseLeave="FeatureLayer_MouseLeave" Renderer="{StaticResource MySimplePointRenderer}"/> </esri:LayerCollection> </esri:Map.Layers> </esri:Map> </Grid> </UserControl>
這樣我們便實現了上述的效果。
需要注意的幾點:
1.Chart控件第一次加載數據的動畫效果(一個點一個點添加),只能使用一次,也就是實例化Chart,加載數據時會有這樣的效果,之后更新Chart控件的數據不會有類似第一次加載Chart時顯示曲線動畫效果,而是上面說的過渡效果。
2.注意設置Chart的AnimatedUpdate屬性為True,否則數據更新時繪制曲線沒有過渡的效果
3.將Chart作為ElementLayer添加到地圖中,注意設置ElementLayer的顯示位置。
總結:
以上的過程數據都是用Timer組件來定時生成的,當然這里也可以擴展,比如你的數據也可以是通過SQL查詢得到。此外,本例沒有用到數據綁定,這是因為本文的Chart是動態生成的,每次選擇Graphic時都會重新生成Chart.所以沒有用數據綁定。當然如果你的Chart是寫死的(實例化一次),或者定義在后台的xaml中,那么建議您選擇數據綁定的方式,數據綁定的方式也很簡單,例如在XAML定義如下的Chart(詳見Visifire documentation):
<vc:Chart Name="MyChart" Width="500" Height="300" Theme="Theme1"> <vc:Chart.Series> <vc:DataSeries RenderAs="Column" DataSource="{Binding}"> <vc:DataSeries.DataMappings> <vc:DataMapping MemberName="XValue" Path="Key"></vc:DataMapping> <vc:DataMapping MemberName="YValue" Path="Value"></vc:DataMapping> </vc:DataSeries.DataMappings> </vc:DataSeries> </vc:Chart.Series> </vc:Chart>
在后台添加如下代碼:
public partial class Page : UserControl { public Page() { InitializeComponent(); for (int i = 0; i < 10; i++) { values.Add(new KeyValuePair<double,double>(i, i + 1)); } MyChart.DataContext = values; } Random rand = new Random(); ObservableCollection<KeyValuePair<Double, Double>> values = new ObservableCollection<KeyValuePair<double, double>>(); }
這樣便可以實現Chart曲線的數據綁定。
PS:有那么一段時間沒有用Visifire了,很多又忘記了,以上算是自己的一個總結,同時很多人也問個類似的效果怎么做,在此和大家分享一下,鑒於時間和知識的關系,疏漏和錯誤在所難免,還望各位指正。
//關於解決地圖縮放時Chart偏移的問題。
1.注冊Map的ExtentChanging事件,然后在事件完成函數中重新設定Elmentlayer的Envelop,代碼如下:
private void Map_ExtentChanging(object sender, ExtentEventArgs e) { double cell_X = (Map.Extent.XMax - Map.Extent.XMin) / Map.ActualWidth; ESRI.ArcGIS.Client.Geometry.Geometry geometry = selectedGraphic.Geometry; MapPoint mapPoint = new MapPoint((geometry.Extent.XMax + geometry.Extent.XMin) / 2, (geometry.Extent.YMax + geometry.Extent.YMin) / 2); Envelope extent = new Envelope(mapPoint.X + (320 + 20) * cell_X, mapPoint.Y, mapPoint.X + (320 + 20) * cell_X, mapPoint.Y); Chart Mychart = chartlayer.Children[0] as Chart; ElementLayer.SetEnvelope(Mychart, extent); }
感謝8樓 vergiljzy的建議和提示,有興趣的不妨試試采用Inforwindow的方式。