WPF实现绘制扇形统计图的示例代码
扇形统计图
- 绘制一个扇形原理也是基于
Canvas
进行绘制; - ArcSegmewww.devze.comnt[1]绘制弧形;
- 绘制指示线;
- 绘制文本;
- 鼠标移入动画;
- 显示详情
Popup
; - 源码github[2] Gitee[3]
示例代码
1)SectorChart.cs代码如下;
usingSystem; usingSystem.Collections.Generic; usingSystem.Collections.ObjectModel; usingSystem.Linq; usingSystem.Windows; usingSystem.Windows.Controls; usingSystem.Windows.Controls.Primitives; usingSystem.Windows.Input; usingSystem.Windows.Media; usingSystem.Windows.Media.Animation; usingSystem.Windows.Media.Effects; usingSystem.Windows.Shapes; usingwpFDevelopers.Charts.Models; namespaceWPFDevelopers.Charts.Controls { [TemplatePart(Name=CanvasTemplateName,Type=typeof(Canvas))] [TemplatePart(Name=PopupTemplateName,Type=typeof(Popup))] publicclassSectorChart:Control { conststringCanvasTemplateName="PART_Canvas"; conststringPopupTemplateName="PART_Popup"; privateCanvas_canvas; privatePopup_popup; privatedoublecentenrX,centenrY,radius,offsetX,offsetY; privatePointminPoint; privatedoublefontsize=12; privateboolflg=false; publicBrushFill { get{return(Brush)GetValue(FillProperty);} set{SetValue(FillProperty,value);} } publicstaticreadonlyDependencyPropertyFillProperty= DependencyProperty.Register("Fill",typeof(Brush),typeof(SectorChart),newPropertyMetadata(null)); publicstringText { get{return(string)GetValue(TextProperty);} set{SetValue(TextProperty,value);} } publicstaticreadonlyDependencyPropertyTextProperty= DependencyProperty.Register("Text",typeof(string),typeof(SectorChart),newPropertyMetadata(null)); publicObservableCollection<PieSerise>ItemsSource { get{return(ObservableCollection<PieSerise>)GetValue(ItemsSourceProperty);} set{SetValue(ItemsSourceProperty,value);} } publicstaticreadonlyDependencyPropertyItemsSourceProperty= DependencyProperty.Register("ItemsSource",typeof(ObservableCollection<PieSerise>),typeof(SectorChart),newPropertyMetadata(null,newPropertyChangedCallback(ItemsSourceChanged))); privatestaticvoidItemsSourceChanged(DependencyObjectd,DependencyPropertyChangedEventArgse) { varview=dasSectorChart; if(e.NewValue!=null) view.DrawArc(); } staticSectorChart() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SectorChart),newFrameworkPropertyMetadata(typeof(SectorChart))); } publicoverridevoidOnApplyTemplate() { base.OnApplyTemplate(); _canvas=GetTemplateChild(CanvasTemplateName)asCanvas; _popup=GetTemplateChild(PopupTemplateName)ASPopup; } voidDr编程客栈awArc() { if(ItemsSourceisnull||!ItemsSource.Any()||_canvasisnull) return; _canvas.Children.Clear(); varpieWidth=_canvas.ActualWidth>_canvas.ActualHeight?_canvas.ActualHeight:_canvas.ActualWidth; varpieHeight=_canvas.ActualWidth>_canvas.ActualHeight?_canvas.ActualHeight:_canvas.ActualWidth; centenrX=pieWidth/2; centenrY=pieHeight/2; radius=this.ActualWidth>this.ActualHeight?this.ActualHeight/2:this.ActualWidth/2; doubleangle=0; doubleprevAngle=0; varsum=ItemsSource.Select(ser=>ser.Percentage).Sum(); foreach(variteminItemsSource) { varline1X=radius*Math.Cos(angle*Math.PI/180)+centenrX; varline1Y=radius*Math.Sin(angle*Math.PI/180)+centenrY; angle=item.Percentage/sum*360+prevAngle; doublearcX=0; doublearcY=0; if(ItemsSource.Count()==1&&angle==360) { arcX=centenrX+Math.Cos(359.99999*Math.PI/180)*radius; arcY=(radius*Math.Sin(359.99999*Math.PI/180))+centenrY; } else { arcX=centenrX+Math.Cos(angle*Math.PI/180)*radius; arcY=(radius*Math.Sin(angle*Math.PI/180))+centenrY; } varline1Segment=newLineSegment(newPoint(line1X,line1Y),false); boolisLargeArc=item.Percentage/sum>0.5; vararcWidth=radius; vararcHeight=radius; vararcSegment=newArcSegment(); arcSegment.Size=newSize(arcWidth,arcHeight); arcSegment.Point=newPoint(arcX,arcY); arcSegment.SweepDirection=SweepDirection.Clockwise; arcSegment.IsLargeArc=isLargeArc; varline2Segment=newLineSegment(newPoint(centenrX,centenrY),false); PieBasepiebase=newPieBase(); piebase.Title=item.Title; piebase.Percentage=item.Percentage; piebase.PieColor=item.PieColor; piebase.LineSegmentStar=line1Segment; piebase.ArcSegment=arcSegment; piebase.LineSegmentEnd=line2Segment; piebase.Angle=item.Percentage/sum*360; piebase.StarPoint=newPoint(line1X,line1Y); piebase.EndPoint=newPoint(arcX,arcY); varpathFigure=newPathFigure(newPoint(centenrX,centenrY),newList<PathSegment>() { line1Segment, arcSegment, line2Segment, },true); varpathFigures=newList<PathFigure>() { pathFigure, }; varpathGeometry=newPathGeometry(pathFigures); varpath=newPath(){Fill=item.PieColor,Data=pathGeometry,DataContext=piebase}; _canvas.Children.Add(path); prevAngle=angle; varline3=DrawLine(path); if(line3!=null) piebase.Line=line3; vartextPathGeo=DrawText(path); vartextpath=newPath(){Fill=item.PieColor,Data=textPathGeo}; piebase.TextPath=textpath; _canvas.Children.Add(textpath); path.MouseMove+=Path_MouseMove1; path.MouseLeave+=Path_MouseLeave; if(ItemsSource.Count()==1&&angle==360) { _canvas.Children.Add(line3); } else { varoutline1=newLine() { X1=centenrX, Y1=centenrY, X2=line1Segment.Point.X, Y2=line1Segment.Point.Y, Stroke=Brushes.White, StrokeThickness=0.8, }; varoutline2=newLine() { X1=centenrX, Y1=centenrY, X2=arcSegment.Point.X, Y2=arcSegment.Point.Y, Stroke=Brushes.White, StrokeThickness=0.8, }; _canvas.Children.Add(outline1); _canvas.Children.Add(outline2); _canvas.Children.Add(line3); } } } privatevoidPath_MouseLeave(objectsender,MouseEventArgse) { _popup.IsOpen=false; varpath=senderasPath; vardt=path.DataContextasPieBase; TranslateTransformttf=newTranslateTransform(); ttf.X=0; ttf.Y=0; path.RenderTransform=ttf; dt.Line.RenderTransform=newTranslateTransform() { X=0, Y=0, }; dt.TextPath.RenderTransform=newTranslateTransform() { X=0, Y=0, }; path.Effect=newDropShadowEffect() { Color=(Color)ColorConverter.ConvertFromString("#FF949494"), BlurRadius=20, Opacity=0, ShadowDepth=0 }; flg=false; } privatevoidPath_MouseMove1(objectsender,MouseEventArgse) { Pathpath=senderasPath; //动画 if(!flg) { BegionOffsetAnimation(path); } ShowMousePopup(path,e); } voidShowMousePopup(Pathpath,MouseEventArgse) { vardata=path.DataContextasPieBase; if(!_popup.IsOpen) _popup.IsOpen=true; varmousePosition=e.GetPosition((UIElement)_canvas.Parent); _popup.HorizontalOffset=mousePosition.X+20; _popup.VerticalOffset=mousePosition.Y+20; Text=(data.Title+":"+data.Percentage);//显示鼠标当前坐标点 Fill=data.PieColor; } voidBegionOffsetAnimation(Pathpath) { NameScope.SetNameScope(this,newNameScope()); varpathDataContext=path.DataContextasPieBase; varangle=pathDataContext.Angle; minPoint=newPoint(Math.Round(pathDataContext.StarPoint.X+pathDataContext.EndPoint.X)/2,Math.Round(pathDataContext.StarPoint.Y+pathDataContext.EndPoint.Y)/2); varv1=minPoint-newPoint(centenrX,centenrY); varv2=newPoint(2000,0)-newPoint(0,0); doublevAngle=0; if(180<angle&&angle<=360&&pathDataContext.Percentage/ItemsSource.Select(p=>p.Percentage).Sum()>=0.5) { vAngle=Math.Round(Vector.AngleBetween(v2,-v1)); } else { vAngle=Math.Round(Vector.AngleBetween(v2,v1)); } offsetX=10*Math.Cos(vAngle*Math.PI/180); offsetY=10*Math.Sin(vAngle*Math.PI/180); varline3=pathDataContext.Line; vartextPath=pathDataContext.TextPath; TranslateTransformLineAnimatedTranslateTransform= newTranslateTransform(); this.RegisterName("LineAnimatedTranslateTransform",LineAnimatedTranslateTransform); line3.RenderTransform=LineAnimatedTranslateTransform; TranslateTransformanimatedTranslateTransform= newTranslateTransform(); this.RegisterName("AnimatedTranslateTransform",animatedTranslateTransform); path.RenderTransform=animatedTranslateTransform; TranslateTransformTextAnimatedTranslateTransform= newTranslateTransform(); this.RegisterName("TextAnimatedTranslateTransform",animatedTranslateTransform); textPath.RenderTransform=animatedTranslateTransform; DoubleAnimationdaX=newDoubleAnimation(); Storyboard.SetTargetProperty(daX,newPropertyPath(TranslateTransform.XProperty)); daX.Duration=newDuration(TimeSpan.FromSeconds(0.2)); daX.From=0; daX.To=offsetX; DoubleAnimationdaY=newDoubleAnimation(); Storyboard.SetTargetName(daY,nameof(animatedTranslateTransform)); Storyboard.SetTargmhbrinetProperty(daY,newPropertyPath(TranslateTransform.YProperty)); daY.Duration=newDuration(TimeSpan.FromSeconds(0.2)); daY.From=0; daY.To=offsetY; path.Effect=newDropShadowEffect() { Color=(Color)ColorConverter.ConvertFromString("#2E2E2E"), BlurRadius=33, Opacity=0.6, ShadowDepth=0 }; animatedTranslateTransform.BeginAnimation(TranslateTransform.XProperty,daX); animatedTranslateTransform.BeginAnimation(TranslateTransform.YProperty,daY); LineAnimatedTranslateTransform.BeginAnimation(TranslateTransform.XProperty,daX); LineAnimatedTranslateTransform.BeginAnimation(TranslateTransform.YProperty,daY); TextAnimatedTranslateTransform.BeginAnimation(TranslateTransform.XProperty,daX); TextAnimatedTranslateTransform.BeginAnimation(TranslateTransform.YProperty,daY); flg=true; } ///<summary> ///画指示线 ///</summary> ///<paramname="path"></param> ///<returns></returns> PolylineDrawLine(Pathpath) { NameScope.SetNameScope(this,newNameScope()); varpathDataContext=path.DataContextasPieBase; varangle=pathDataContext.Angle; pathDataContext.Line=null; minPoint=newPoint(Math.Round(pathDataContext.StarPoint.X+pathDataContext.EndPoint.X)/2,Math.Round(pathDataContext.StarPoint.Y+pathDataContext.EndPoint.Y)/2); Vectorv1; if(angle>180&&angle<360) { v1=newPoint(centenrX,centenrY)-minPoint; } elseif(angle==180||angle==360) { if(Math.Round(pathDataContext.StarPoint.X)==Math.Round(pathDataContext.EndPoint.X)) { v1=newPoint(radius*2,radius)-newPoint(centenrX,centenrY); } else { if(Math.Round(pathDataContext.StarPoint.X)-Math.Round(pathDataContext.EndPoint.X)==2*radius) { v1=newPoint(radius,2*radius)-newPoint(centenrX,centenrY); } else { v1=newPoint(radius,0)-newPoint(centenrX,centenrY); } } } else { v1=minPoint-newPoint(centenrX,centenrY); } v1.Normalize(); varVmin=v1*radius; varRadiusToNodal=Vmin+newPoint(centenrX,centenrY); varv2=newPoint(2000,0)-newPoint(0,0); doublevAngle=0; vAngle=Math.Round(Vector.AngleBetween(v2,v1)); offsetX=10*Math.Cos(vAngle*Math.PI/180); offsetY=10*Math.Sin(vAngle*Math.PI/180); varprolongPoint=newPoint(RadiusToNodal.X+offsetX*1,RadiusToNodal.Y+offsetY*1); if(RadiusToNodal.X==double.NaN||RadiusToNodal.Y==double.NaN||prolongPoint.X==double.NaN||prolongPoint.Y==double.NaN) returnnull; varpoint1=RadiusToNodal; varpoint2=prolongPoint; Pointpoint3; if(prolongPoint.X>=radius) point3=newPoint(prolongPoint.X+10,prolongPoint.Y); else point3=newPoint(prolongPoint.X-10,prolongPoint.Y); PointCollectionpolygonPoints=newPointCollection(); polygonPoints.Add(point1); polygonPoints.Add(point2); polygonPoints.Add(point3); varline3=newPolyline(); line3.Points=polygonPoints; line3.Stroke=pathDataContext.PieColor; pathDataContext.PolylineEndPoint=point3; returnline3; } PathGeometryDrawText(Pathpath) { NameScope.SetNameScope(this,newNameScope()); varpathDataContext=path.DataContextasPieBase; Typefacetypeface=newTypeface (newFontFamily("MicrosoftYaHei"), FontStyles.Normal, FontWeights.Normal,FontStretches.Normal); FormattedTexttext=newFormattedText( pathDataContext.Title, newSystem.Globalization.CultureInfo("zh-cn"), FlowDirection.LeftToRight,typeface,fontsize,Brushes.RosyBrown ); vartextWidth=text.Width; Geometrygeo=null; if(pathDataContext.PolylineEndPoint.X>radius) geo=text.BuildGeometry(newPoint(pathDataContext.PolylineEndPoint.X+4,pathDataContext.PolylineEndPoint.Y-fontsize/1.8)); else geo=text.BuildGeometry(newPoint(pathDataContext.PolylineEndPoint.X-textWidth-4,pathDataContext.PolylineEndPoint.Y-fontsize/1.8)); PathGeometrypathGeometry=geo.GetFlattenedPathGeometry(); returnpathGeometry; } } }
2)SectorChart.xaml 代码如下;
<ResourceDictionaryXMLns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:WPFDevelopers.Charts.Controls"> <StyleTargetType="{x:Typecontrols:SectorChart}"> <SetterProperty="Width"Value="300"/> <SetterProperty="Height"Value="300"/> <SetterProperty="Template"> <Setter.Value> <ControlTemplateTargetType="{x:Typecontrols:SectorChart}"> <Grid> <Popupx:Name="PART_Popup" IsOpen="False" Placement="Relative" AllowsTransparency="True"> <BorderBackground="White" CornerRadius="5" Padding="14" BorderThickness="0" BorderBrush="Transparent"> <StackPanel> <EllipseWidth=js"20"Height="20" Fill="{TemplateBindingFill}"/> <TextblockBackground="White" Padding="9,4,9,4"TextWrapping="Wrap" Text="{TemplateBindingText}"/> </StackPanel> </Border> </Popup> <Canvasx:Name="PART_Canvas"HorizontalAlignment="{TemplateBindingHorizontalContentAlignment}" Width="{TemplateBindingActualWidth}" Height="{TemplateBindingActualHeight}"> </Canvas> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
3) MainWindow.xaml使用如下;
xmlns:wsCharts="https://github.com/WPFDevelopersOrg.WPFDevelopers.Charts" <wsCharts:SectorChartItemsSource="{BindingItemsSource,RelativeSource={RelativeSourceAncestorType=local:MainWindow}}" Margin="30"/>
4) MainWindow.xaml.cs代码如下;
usingSystem.Collections.ObjectModel; usingSystem.Windows; usingSystem.Windows.Media; usingWPFDevelopers.Charts.Models; namespaceWPFDevelopers.Charts.Samples { ///<summary> ///MainWindow.xaml的交互逻辑 ///</summary> publicpartialclassMainWindow { publicObservableCollection<PieSerise>ItemsSource { get{return(ObservableCollection<PieSerise>)GetValue(ItemsSourceProperty);} set{SetValue(ItemsSourceProperty,value);} } publicstaticreadonlyDependencyPropertyItemsSourceProperty= DependencyProperty.Register("ItemsSource",typeof(ObservableCollection<PieSerise>),typeof(MainWindow),newPropertyMetadata(null)); publicMainWindow() { InitializeComponent(); Loaded+=MainWindow_Loaded; } privatevoidMainWindow_Loaded(objectsender,RoutedEventArgse) { ItemsSource=newObservableCollection<PieSerise>(); varcollection1=newObservableCollection<PieSerise>(); collection1.Add(newPieSerise { Title="2012", Percentage=30, PieColor=newSolidColorBrush((Color)ColorConverter.ConvertFromString("#5B9BD5")), }); collection1.Add( newPieSerise { Title="2013", Percentage=140, PieColor=newSolidColorBrush((Color)ColorConverte开发者_Python开发r.ConvertFromString("#4472C4")), }); collection1.Add(newPieSerise { Title="2014", Percentage=49, PieColor=newSolidColorBrush((Color)ColorConverter.ConvertFromString("#007fff")), }); collection1.Add(newPieSerise { Title="2015", Percentage=50, PieColor=newSolidColorBrush((Color)ColorConverter.ConvertFromString("#ED7D31")), }); collection1.Add(newPieSerise { Title="2016", Percentage=30, PieColor=newSolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC000")), }); collection1.Add(newPieSerise { Title="2017", Percentage=30, PieColor=newSolidColorBrush((Color)ColorConverter.ConvertFromString("#ff033e")), }); ItemsSource=collection1; } } }
以上就是WPF实现绘制扇形统计图的示例代码的详细内容,更多关于WPF扇形统计图的资料请关注我们其它相关文章!
精彩评论