@@ -17,6 +17,9 @@ public class Pie : Shape
1717 private const FrameworkPropertyMetadataOptions __DependendPropertyMetadataOptions =
1818 FrameworkPropertyMetadataOptions . AffectsRender ;
1919
20+ private const double FullCircleDegrees = 360d ;
21+ private const double MinArcDegrees = 1e-6 ; // минимальная длина дуги в градусах
22+
2023 static Pie ( )
2124 {
2225 //StretchProperty.OverrideMetadata(typeof(Pie), new FrameworkPropertyMetadata(Stretch.None));
@@ -82,6 +85,10 @@ static Pie()
8285 coerceValueCallback : null ) ) ;
8386
8487 /// <summary>Получает или устанавливает начальный угол сектора в градусах</summary>
88+ /// <remarks>
89+ /// Отсчёт ведётся по часовой стрелке, 0 градусов направлен вправо, 90 градусов вниз
90+ /// Сектор всегда рисуется по часовой стрелке от нормализованного <see cref="StartAngle"/> к нормализованному <see cref="StopAngle"/>
91+ /// </remarks>
8592 public double StartAngle { get => ( double ) GetValue ( StartAngleProperty ) ; set => SetValue ( StartAngleProperty , value ) ; }
8693
8794 /// <summary>Определяет зависимое свойство для конечного угла сектора</summary>
@@ -98,6 +105,13 @@ private static void OnStopAngleChanged(DependencyObject o, DependencyPropertyCha
98105 o . SetValue ( AngleProperty , ( double ) e . NewValue - ( ( Pie ) o ) . StartAngle ) ;
99106
100107 /// <summary>Получает или устанавливает конечный угол сектора в градусах</summary>
108+ /// <remarks>
109+ /// После нормализации обоих углов к диапазону [0;360) сектор рисуется по часовой стрелке
110+ /// Примеры:
111+ /// - StartAngle=270, StopAngle=60 → сектор 150° по часовой (270→360→60)
112+ /// - StartAngle=60, StopAngle=270 → сектор 210° по часовой (60→270)
113+ /// - StartAngle=0, StopAngle=360 → полный круг (разность исходных углов = 360°)
114+ /// </remarks>
101115 public double StopAngle { get => ( double ) GetValue ( StopAngleProperty ) ; set => SetValue ( StopAngleProperty , value ) ; }
102116
103117 /// <summary>Определяет зависимое свойство для угла раствора сектора</summary>
@@ -177,7 +191,7 @@ protected override Size ArrangeOverride(Size FinalSize)
177191 return size ;
178192 }
179193
180- /// <summary>Вычисляетсектора на основе заданных параметров</summary>
194+ /// <summary>Вычисляет геометрию сектора на основе заданных параметров</summary>
181195 /// <param name="rect">Прямоугольник ограничивающей области</param>
182196 /// <param name="start">Начальный угол в градусах</param>
183197 /// <param name="stop">Конечный угол в градусах</param>
@@ -205,7 +219,7 @@ private Geometry GetGeometry(Rect rect, double start, double stop, double R, dou
205219 return _Pie ;
206220 }
207221
208- /// <summary>Обновляетэллипсов внешнего и внутреннего радиусов</summary>
222+ /// <summary>Обновляет геометрию эллипсов внешнего и внутреннего радиусов</summary>
209223 /// <param name="rect">Прямоугольник ограничивающей области</param>
210224 /// <param name="R">Внешний радиус</param>
211225 /// <param name="r">Внутренний радиус</param>
@@ -227,15 +241,7 @@ private void ChangeGeometry(Rect rect, double R, double r, bool aligned)
227241 _InnerEllipse . RadiusY = h * r ;
228242 }
229243
230- //private static Point GetPoint(Point p0, Rect rect, double a, double r, double w, double h)
231- //{
232- // const double to_rad = Math.PI / 180.0;
233- // a -= 90;
234- // a *= to_rad;
235- // return new Point(p0.X + r * Math.Cos(a) * w / 2, p0.Y + r * Math.Sin(a) * h / 2);
236- //}
237-
238- /// <summary>Вычисляетна эллипсе по углу и радиусу</summary>
244+ /// <summary>Вычисляет координата точки на эллипсе по углу и радиусу</summary>
239245 /// <param name="rect">Прямоугольник ограничивающей области</param>
240246 /// <param name="a">Угол в градусах</param>
241247 /// <param name="r">Радиус (от 0 до 1)</param>
@@ -251,7 +257,14 @@ private static Point GetPoint(Rect rect, double a, double r)
251257 return new ( x , y ) ;
252258 }
253259
254- /// <summary>Рисует гев контекст потока</summary>
260+ /// <summary>Нормализует угол к диапазону [0;360)</summary>
261+ private static double NormalizeAngle ( double angle )
262+ {
263+ angle %= FullCircleDegrees ;
264+ return angle < 0 ? angle + FullCircleDegrees : angle ;
265+ }
266+
267+ /// <summary>Рисует геометрию сектора в контекст потока</summary>
255268 /// <param name="g">Контекст потока геометрии</param>
256269 /// <param name="rect">Прямоугольник ограничивающей области</param>
257270 /// <param name="R">Внешний радиус</param>
@@ -269,11 +282,16 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d
269282 // Вычисляем центральную точку прямоугольника
270283 var p0 = new Point ( 0.5 * rect . Width + rect . Left , 0.5 * rect . Height + rect . Top ) ;
271284
272- // Нормализуем углы: a - меньший угол, b - больший угол
273- var a = Math . Min ( start , stop ) ;
274- var b = Math . Max ( start , stop ) ;
275- var d = b - a ; // Разница углов (угол раствора сектора)
276- if ( d is 0d ) return ;
285+ // Нормализуем углы к диапазону [0;360)
286+ var start_angle = NormalizeAngle ( start ) ;
287+ var stop_angle = NormalizeAngle ( stop ) ;
288+
289+ // Вычисляем угловое расстояние по часовой стрелке от start_angle до stop_angle
290+ var delta_clockwise = stop_angle - start_angle ;
291+ if ( delta_clockwise < 0 ) delta_clockwise += FullCircleDegrees ; // Приводим к диапазону [0;360)
292+
293+ // Слишком маленькая дуга считается нулевой
294+ if ( delta_clockwise < MinArcDegrees ) return ;
277295
278296 // Если включено выравнивание, приводим к квадрату по меньшей стороне
279297 if ( aligned )
@@ -283,13 +301,13 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d
283301 }
284302
285303 // Вычисляем ключевые точки для построения сектора:
286- var in_arc_stop = GetPoint ( rect , a , r ) ; // конечная точка внутренней дуги (начальный угол)
287- var out_arc_start = GetPoint ( rect , a , R ) ; // начальная точка внешней дуги (начальный угол)
288- var out_arc_stop = GetPoint ( rect , b , R ) ; // конечная точка внешней дуги (конечный угол)
289- var in_arc_start = GetPoint ( rect , b , r ) ; // начальная точка внутренней дуги (конечный угол)
304+ var out_arc_start = GetPoint ( rect , start_angle , R ) ; // начальная точка внешней дуги
305+ var out_arc_stop = GetPoint ( rect , stop_angle , R ) ; // конечная точка внешней дуги
306+ var in_arc_start = GetPoint ( rect , start_angle , r ) ; // начальная точка внутренней дуги
307+ var in_arc_stop = GetPoint ( rect , stop_angle , r ) ; // конечная точка внутренней дуги
290308
291309 // Определяем тип дуги (большая дуга если угол > 180°)
292- var arc_isout = d > 180.0 ;
310+ var arc_isout = delta_clockwise > 180.0 ;
293311
294312 // Вычисляем размеры эллипсов для внутренней и внешней дуг
295313 var in_arc_size = new Size ( r * w / 2 , r * h / 2 ) ;
@@ -304,7 +322,7 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d
304322 else
305323 {
306324 // Начинаем с центра (если r = 0) или с точки на внутренней дуге
307- g . BeginFigure ( r is 0d ? p0 : in_arc_stop , true , true ) ;
325+ g . BeginFigure ( r is 0d ? p0 : in_arc_start , true , true ) ;
308326 g . LineTo ( out_arc_start , true , true ) ; // Линия к началу внешней дуги
309327 }
310328
@@ -314,9 +332,9 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d
314332 if ( r is 0d || line_only ) return ; // Если внутренний радиус 0 или это линия, завершаем
315333
316334 // Рисуем линию к началу внутренней дуги
317- g . LineTo ( in_arc_start , true , true ) ;
335+ g . LineTo ( in_arc_stop , true , true ) ;
318336
319337 // Рисуем внутреннюю дугу от конечного до начального угла против часовой стрелки
320- g . ArcTo ( in_arc_stop , in_arc_size , 0 , arc_isout , SweepDirection . Counterclockwise , true , true ) ;
338+ g . ArcTo ( in_arc_start , in_arc_size , 0 , arc_isout , SweepDirection . Counterclockwise , true , true ) ;
321339 }
322340}
0 commit comments