开发者

WPF实现绘制扇形统计图的示例代码

开发者 https://www.devze.com 2022-12-01 15:09 出处:网络 作者: ArcherSong
扇形统计图绘制一个扇形原理也是基于Canvas进行绘制;ArcSegment[1]绘制弧形;绘制指示线;绘制文本;鼠标移入动画;显示详情Popup;源码Github[2]Gitee[3]示例代码1)S...

扇形统计图

  • 绘制一个扇形原理也是基于Canvas进行绘制;
  • ArcSegmewww.devze.comnt[1]绘制弧形;
  • 绘制指示线;
  • 绘制文本;
  • 鼠标移入动画;
  • 显示详情Popup
  • 源码github[2] Gitee[3]

WPF实现绘制扇形统计图的示例代码

示例代码

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=1编程0*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扇形统计图的资料请关注我们其它相关文章!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号