From 5da7c867b284025079638c5828e6061ab6633436 Mon Sep 17 00:00:00 2001 From: Koichi5 Date: Fri, 2 May 2025 00:01:55 +0900 Subject: [PATCH 1/5] feature: Add vertice label feature --- .../chart/radar_chart/radar_chart_data.dart | 59 +++++++++++++++ .../radar_chart/radar_chart_painter.dart | 74 ++++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/lib/src/chart/radar_chart/radar_chart_data.dart b/lib/src/chart/radar_chart/radar_chart_data.dart index c42532cbe..0c94599a2 100644 --- a/lib/src/chart/radar_chart/radar_chart_data.dart +++ b/lib/src/chart/radar_chart/radar_chart_data.dart @@ -12,6 +12,10 @@ typedef GetTitleByIndexFunction = RadarChartTitle Function( double angle, ); +typedef GetVerticeLabelByIndexFunction = RadarChartVerticeLabel Function( + int index, +); + enum RadarShape { circle, polygon, @@ -41,6 +45,22 @@ class RadarChartTitle { final double? positionPercentageOffset; } +/// Defines a label for [RadarChart] vertices +class RadarChartVerticeLabel { + const RadarChartVerticeLabel({ + required this.text, + this.positionPercentageOffset, + }); + + /// [text] is used to draw labels on the vertices of [RadarChart] + final String text; + + /// [positionPercentageOffset] is the place of showing label on the [RadarChart] vertices + /// The higher the value of this field, the more labels move away from the chart vertices. + /// This value should be between 0 and 1 + final double? positionPercentageOffset; +} + /// [RadarChart] needs this class to render itself. /// /// It holds data needed to draw a radar chart, @@ -67,8 +87,11 @@ class RadarChartData extends BaseChartData with EquatableMixin { BorderSide? radarBorderData, RadarShape? radarShape, this.getTitle, + this.getVerticeLabel, this.titleTextStyle, + this.verticeLabelTextStyle, double? titlePositionPercentageOffset, + double? verticeLabelPositionPercentageOffset, int? tickCount, this.ticksTextStyle, BorderSide? tickBorderData, @@ -87,12 +110,20 @@ class RadarChartData extends BaseChartData with EquatableMixin { titlePositionPercentageOffset <= 1, 'titlePositionPercentageOffset must be something between 0 and 1 ', ), + assert( + verticeLabelPositionPercentageOffset == null || + verticeLabelPositionPercentageOffset >= 0 && + verticeLabelPositionPercentageOffset <= 1, + 'verticeLabelPositionPercentageOffset must be something between 0 and 1 ', + ), dataSets = dataSets ?? const [], radarBackgroundColor = radarBackgroundColor ?? Colors.transparent, radarBorderData = radarBorderData ?? const BorderSide(width: 2), radarShape = radarShape ?? RadarShape.circle, radarTouchData = radarTouchData ?? RadarTouchData(), titlePositionPercentageOffset = titlePositionPercentageOffset ?? 0.2, + verticeLabelPositionPercentageOffset = + verticeLabelPositionPercentageOffset ?? 0.2, tickCount = tickCount ?? 1, tickBorderData = tickBorderData ?? const BorderSide(width: 2), gridBorderData = gridBorderData ?? const BorderSide(width: 2), @@ -130,9 +161,15 @@ class RadarChartData extends BaseChartData with EquatableMixin { /// ``` final GetTitleByIndexFunction? getTitle; + /// [getVerticeLabel] is used to draw labels on the vertices of the [RadarChart] + final GetVerticeLabelByIndexFunction? getVerticeLabel; + /// Defines style of showing [RadarChart] titles. final TextStyle? titleTextStyle; + /// Defines style of showing [RadarChart] vertice labels. + final TextStyle? verticeLabelTextStyle; + /// the [titlePositionPercentageOffset] is the place of showing title on the [RadarChart] /// The higher the value of this field, the more titles move away from the chart. /// this field should be between 0 and 1, @@ -141,6 +178,13 @@ class RadarChartData extends BaseChartData with EquatableMixin { /// the default value is 0.2. final double titlePositionPercentageOffset; + /// the [verticeLabelPositionPercentageOffset] is the place of showing labels on the [RadarChart] vertices + /// The higher the value of this field, the more labels move away from the chart vertices. + /// this field should be between 0 and 1, + /// if it is 0 the label will be drawn near the chart vertices, + /// if it is 1 the label will be drawn near the outside of chart vertices, + final double verticeLabelPositionPercentageOffset; + /// Defines the number of ticks that should be paint in [RadarChart] /// the default & minimum value of this field is 1. final int tickCount; @@ -198,8 +242,10 @@ class RadarChartData extends BaseChartData with EquatableMixin { BorderSide? radarBorderData, RadarShape? radarShape, GetTitleByIndexFunction? getTitle, + GetVerticeLabelByIndexFunction? getVerticeLabel, TextStyle? titleTextStyle, double? titlePositionPercentageOffset, + double? verticeLabelPositionPercentageOffset, int? tickCount, TextStyle? ticksTextStyle, BorderSide? tickBorderData, @@ -214,9 +260,13 @@ class RadarChartData extends BaseChartData with EquatableMixin { radarBorderData: radarBorderData ?? this.radarBorderData, radarShape: radarShape ?? this.radarShape, getTitle: getTitle ?? this.getTitle, + getVerticeLabel: getVerticeLabel ?? this.getVerticeLabel, titleTextStyle: titleTextStyle ?? this.titleTextStyle, titlePositionPercentageOffset: titlePositionPercentageOffset ?? this.titlePositionPercentageOffset, + verticeLabelPositionPercentageOffset: + verticeLabelPositionPercentageOffset ?? + this.verticeLabelPositionPercentageOffset, tickCount: tickCount ?? this.tickCount, ticksTextStyle: ticksTextStyle ?? this.ticksTextStyle, tickBorderData: tickBorderData ?? this.tickBorderData, @@ -235,12 +285,18 @@ class RadarChartData extends BaseChartData with EquatableMixin { radarBackgroundColor: Color.lerp(a.radarBackgroundColor, b.radarBackgroundColor, t), getTitle: b.getTitle, + getVerticeLabel: b.getVerticeLabel, titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), titlePositionPercentageOffset: lerpDouble( a.titlePositionPercentageOffset, b.titlePositionPercentageOffset, t, ), + verticeLabelPositionPercentageOffset: lerpDouble( + a.verticeLabelPositionPercentageOffset, + b.verticeLabelPositionPercentageOffset, + t, + ), tickCount: lerpInt(a.tickCount, b.tickCount, t), ticksTextStyle: TextStyle.lerp(a.ticksTextStyle, b.ticksTextStyle, t), gridBorderData: BorderSide.lerp(a.gridBorderData, b.gridBorderData, t), @@ -266,8 +322,11 @@ class RadarChartData extends BaseChartData with EquatableMixin { radarBorderData, radarShape, getTitle, + getVerticeLabel, titleTextStyle, + verticeLabelTextStyle, titlePositionPercentageOffset, + verticeLabelPositionPercentageOffset, tickCount, ticksTextStyle, tickBorderData, diff --git a/lib/src/chart/radar_chart/radar_chart_painter.dart b/lib/src/chart/radar_chart/radar_chart_painter.dart index f774281bf..03bea6f43 100644 --- a/lib/src/chart/radar_chart/radar_chart_painter.dart +++ b/lib/src/chart/radar_chart/radar_chart_painter.dart @@ -1,4 +1,4 @@ -import 'dart:math' show cos, min, pi, sin; +import 'dart:math' show cos, min, pi, sin, sqrt; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; @@ -66,6 +66,7 @@ class RadarChartPainter extends BaseChartPainter { drawTicks(context, canvasWrapper, holder); drawTitles(context, canvasWrapper, holder); drawDataSets(canvasWrapper, holder); + drawVerticeLabels(context, canvasWrapper, holder); } @visibleForTesting @@ -338,6 +339,77 @@ class RadarChartPainter extends BaseChartPainter { } } + @visibleForTesting + /// Draws labels at each vertex of the [RadarChart]. + void drawVerticeLabels( + BuildContext context, + CanvasWrapper canvasWrapper, + PaintHolder holder, + ) { + final data = holder.data; + if (data.getVerticeLabel == null) return; + + dataSetsPosition ??= calculateDataSetsPosition(canvasWrapper.size, holder); + if (dataSetsPosition!.isEmpty) return; + + final size = canvasWrapper.size; + final centerX = radarCenterX(size); + final centerY = radarCenterY(size); + final vertexPositions = dataSetsPosition![0].entriesOffset; + + final style = Utils().getThemeAwareTextStyle( + context, + data.verticeLabelTextStyle, + ); + + _titleTextPaint + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr + ..textScaler = holder.textScaler; + + for (var index = 0; index < vertexPositions.length; index++) { + final verticeLabel = data.getVerticeLabel!(index); + final threshold = 1.0 + + (verticeLabel.positionPercentageOffset ?? + data.verticeLabelPositionPercentageOffset); + final span = TextSpan( + text: verticeLabel.text, + style: style, + ); + _titleTextPaint + ..text = span + ..layout(); + + // Get the vertex position from dataSetsPosition + final vertexOffset = vertexPositions[index]; + + final vectorX = vertexOffset.dx - centerX; + final vectorY = vertexOffset.dy - centerY; + + final vectorLength = sqrt(vectorX * vectorX + vectorY * vectorY); + + final normalizedX = vectorX / vectorLength * threshold; + final normalizedY = vectorY / vectorLength * threshold; + + const labelOffset = 20.0; + final labelX = vertexOffset.dx + normalizedX * labelOffset; + final labelY = vertexOffset.dy + normalizedY * labelOffset; + + final rect = Rect.fromLTWH( + labelX - _titleTextPaint.width / 2, + labelY - _titleTextPaint.height / 2, + _titleTextPaint.width, + _titleTextPaint.height, + ); + + canvasWrapper.drawText( + _titleTextPaint, + rect.topLeft, + 0, + ); + } + } + @visibleForTesting void drawDataSets( CanvasWrapper canvasWrapper, From 94bc018e542bf6c9849f9b823b8bbc2a3df0e231 Mon Sep 17 00:00:00 2001 From: Koichi5 Date: Fri, 2 May 2025 00:02:26 +0900 Subject: [PATCH 2/5] feature: Add vertice label tests --- .../radar_chart/radar_chart_painter_test.dart | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/test/chart/radar_chart/radar_chart_painter_test.dart b/test/chart/radar_chart/radar_chart_painter_test.dart index aced9bfad..063c59e89 100644 --- a/test/chart/radar_chart/radar_chart_painter_test.dart +++ b/test/chart/radar_chart/radar_chart_painter_test.dart @@ -898,6 +898,193 @@ void main() { }); }); + group('drawVerticeLabels()', () { + test('test without vertice labels', () { + const viewSize = Size(400, 300); + + final data = RadarChartData( + dataSets: [ + RadarDataSet( + dataEntries: [ + const RadarEntry(value: 1), + const RadarEntry(value: 2), + const RadarEntry(value: 3), + ], + ), + ], + titleTextStyle: MockData.textStyle4, + radarBorderData: const BorderSide(color: MockData.color6, width: 33), + tickBorderData: const BorderSide(color: MockData.color5, width: 55), + gridBorderData: const BorderSide(color: MockData.color3, width: 3), + radarBackgroundColor: MockData.color2, + ); + + final radarChartPainter = RadarChartPainter(); + final holder = + PaintHolder(data, data, TextScaler.noScaling); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + final mockUtils = MockUtils(); + when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, + ); + Utils.changeInstance(mockUtils); + + final mockContext = MockBuildContext(); + + radarChartPainter.drawVerticeLabels( + mockContext, + mockCanvasWrapper, + holder, + ); + + verifyNever(mockCanvasWrapper.drawText(any, any)); + }); + + test('test with vertice labels', () { + const viewSize = Size(400, 300); + + final data = RadarChartData( + dataSets: [ + RadarDataSet( + dataEntries: [ + const RadarEntry(value: 1), + const RadarEntry(value: 2), + const RadarEntry(value: 3), + ], + ), + ], + getVerticeLabel: (index) { + return RadarChartVerticeLabel(text: 'Label $index'); + }, + verticeLabelTextStyle: MockData.textStyle4, + radarBorderData: const BorderSide(color: MockData.color6, width: 33), + tickBorderData: const BorderSide(color: MockData.color5, width: 55), + gridBorderData: const BorderSide(color: MockData.color3, width: 3), + radarBackgroundColor: MockData.color2, + ); + + final radarChartPainter = RadarChartPainter(); + final holder = + PaintHolder(data, data, TextScaler.noScaling); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + final mockUtils = MockUtils(); + when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, + ); + Utils.changeInstance(mockUtils); + + final mockContext = MockBuildContext(); + + final results = >[]; + when(mockCanvasWrapper.drawText(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'text': + ((inv.positionalArguments[0] as TextPainter).text as TextSpan?)! + .text, + 'style': + ((inv.positionalArguments[0] as TextPainter).text as TextSpan?)! + .style, + }); + }); + + radarChartPainter.drawVerticeLabels( + mockContext, + mockCanvasWrapper, + holder, + ); + expect(results.length, 3); + + expect(results[0]['text'] as String, 'Label 0'); + expect(results[0]['style'] as TextStyle, MockData.textStyle4); + + expect(results[1]['text'] as String, 'Label 1'); + expect(results[1]['style'] as TextStyle, MockData.textStyle4); + + expect(results[2]['text'] as String, 'Label 2'); + expect(results[2]['style'] as TextStyle, MockData.textStyle4); + }); + + test('test with custom position percentage offset', () { + const viewSize = Size(400, 300); + + final data = RadarChartData( + dataSets: [ + RadarDataSet( + dataEntries: [ + const RadarEntry(value: 1), + const RadarEntry(value: 2), + const RadarEntry(value: 3), + ], + ), + ], + getVerticeLabel: (index) { + return RadarChartVerticeLabel( + text: 'Label $index', + positionPercentageOffset: 0.5, + ); + }, + verticeLabelTextStyle: MockData.textStyle4, + verticeLabelPositionPercentageOffset: 0.2, + radarBorderData: const BorderSide(color: MockData.color6, width: 33), + tickBorderData: const BorderSide(color: MockData.color5, width: 55), + gridBorderData: const BorderSide(color: MockData.color3, width: 3), + radarBackgroundColor: MockData.color2, + ); + + final radarChartPainter = RadarChartPainter(); + final holder = + PaintHolder(data, data, TextScaler.noScaling); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + final mockUtils = MockUtils(); + when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, + ); + Utils.changeInstance(mockUtils); + + final mockContext = MockBuildContext(); + + final results = >[]; + when(mockCanvasWrapper.drawText(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'text': + ((inv.positionalArguments[0] as TextPainter).text as TextSpan?)! + .text, + 'style': + ((inv.positionalArguments[0] as TextPainter).text as TextSpan?)! + .style, + 'offset': inv.positionalArguments[1] as Offset, + }); + }); + + radarChartPainter.drawVerticeLabels( + mockContext, + mockCanvasWrapper, + holder, + ); + expect(results.length, 3); + + for (var i = 0; i < results.length; i++) { + expect(results[i]['text'] as String, 'Label $i'); + expect(results[i]['style'] as TextStyle, MockData.textStyle4); + expect(results[i]['offset'], isNotNull); + } + }); + }); + group('handleTouch()', () { test('test 1', () { const viewSize = Size(400, 300); From 3e5373bdc3eb491692d4ccad6fe163d32dd0067a Mon Sep 17 00:00:00 2001 From: Koichi5 Date: Sat, 10 May 2025 12:03:33 +0900 Subject: [PATCH 3/5] delete: Delete verticeLabelPositionPercentageOffset in RadarChartData --- .../chart/radar_chart/radar_chart_data.dart | 26 ------------------- .../radar_chart/radar_chart_painter.dart | 4 +-- .../radar_chart/radar_chart_painter_test.dart | 1 - 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/lib/src/chart/radar_chart/radar_chart_data.dart b/lib/src/chart/radar_chart/radar_chart_data.dart index 0c94599a2..ec6e83a0e 100644 --- a/lib/src/chart/radar_chart/radar_chart_data.dart +++ b/lib/src/chart/radar_chart/radar_chart_data.dart @@ -91,7 +91,6 @@ class RadarChartData extends BaseChartData with EquatableMixin { this.titleTextStyle, this.verticeLabelTextStyle, double? titlePositionPercentageOffset, - double? verticeLabelPositionPercentageOffset, int? tickCount, this.ticksTextStyle, BorderSide? tickBorderData, @@ -110,20 +109,12 @@ class RadarChartData extends BaseChartData with EquatableMixin { titlePositionPercentageOffset <= 1, 'titlePositionPercentageOffset must be something between 0 and 1 ', ), - assert( - verticeLabelPositionPercentageOffset == null || - verticeLabelPositionPercentageOffset >= 0 && - verticeLabelPositionPercentageOffset <= 1, - 'verticeLabelPositionPercentageOffset must be something between 0 and 1 ', - ), dataSets = dataSets ?? const [], radarBackgroundColor = radarBackgroundColor ?? Colors.transparent, radarBorderData = radarBorderData ?? const BorderSide(width: 2), radarShape = radarShape ?? RadarShape.circle, radarTouchData = radarTouchData ?? RadarTouchData(), titlePositionPercentageOffset = titlePositionPercentageOffset ?? 0.2, - verticeLabelPositionPercentageOffset = - verticeLabelPositionPercentageOffset ?? 0.2, tickCount = tickCount ?? 1, tickBorderData = tickBorderData ?? const BorderSide(width: 2), gridBorderData = gridBorderData ?? const BorderSide(width: 2), @@ -178,13 +169,6 @@ class RadarChartData extends BaseChartData with EquatableMixin { /// the default value is 0.2. final double titlePositionPercentageOffset; - /// the [verticeLabelPositionPercentageOffset] is the place of showing labels on the [RadarChart] vertices - /// The higher the value of this field, the more labels move away from the chart vertices. - /// this field should be between 0 and 1, - /// if it is 0 the label will be drawn near the chart vertices, - /// if it is 1 the label will be drawn near the outside of chart vertices, - final double verticeLabelPositionPercentageOffset; - /// Defines the number of ticks that should be paint in [RadarChart] /// the default & minimum value of this field is 1. final int tickCount; @@ -245,7 +229,6 @@ class RadarChartData extends BaseChartData with EquatableMixin { GetVerticeLabelByIndexFunction? getVerticeLabel, TextStyle? titleTextStyle, double? titlePositionPercentageOffset, - double? verticeLabelPositionPercentageOffset, int? tickCount, TextStyle? ticksTextStyle, BorderSide? tickBorderData, @@ -264,9 +247,6 @@ class RadarChartData extends BaseChartData with EquatableMixin { titleTextStyle: titleTextStyle ?? this.titleTextStyle, titlePositionPercentageOffset: titlePositionPercentageOffset ?? this.titlePositionPercentageOffset, - verticeLabelPositionPercentageOffset: - verticeLabelPositionPercentageOffset ?? - this.verticeLabelPositionPercentageOffset, tickCount: tickCount ?? this.tickCount, ticksTextStyle: ticksTextStyle ?? this.ticksTextStyle, tickBorderData: tickBorderData ?? this.tickBorderData, @@ -292,11 +272,6 @@ class RadarChartData extends BaseChartData with EquatableMixin { b.titlePositionPercentageOffset, t, ), - verticeLabelPositionPercentageOffset: lerpDouble( - a.verticeLabelPositionPercentageOffset, - b.verticeLabelPositionPercentageOffset, - t, - ), tickCount: lerpInt(a.tickCount, b.tickCount, t), ticksTextStyle: TextStyle.lerp(a.ticksTextStyle, b.ticksTextStyle, t), gridBorderData: BorderSide.lerp(a.gridBorderData, b.gridBorderData, t), @@ -326,7 +301,6 @@ class RadarChartData extends BaseChartData with EquatableMixin { titleTextStyle, verticeLabelTextStyle, titlePositionPercentageOffset, - verticeLabelPositionPercentageOffset, tickCount, ticksTextStyle, tickBorderData, diff --git a/lib/src/chart/radar_chart/radar_chart_painter.dart b/lib/src/chart/radar_chart/radar_chart_painter.dart index 03bea6f43..5740854e0 100644 --- a/lib/src/chart/radar_chart/radar_chart_painter.dart +++ b/lib/src/chart/radar_chart/radar_chart_painter.dart @@ -369,9 +369,7 @@ class RadarChartPainter extends BaseChartPainter { for (var index = 0; index < vertexPositions.length; index++) { final verticeLabel = data.getVerticeLabel!(index); - final threshold = 1.0 + - (verticeLabel.positionPercentageOffset ?? - data.verticeLabelPositionPercentageOffset); + final threshold = 1.0 + (verticeLabel.positionPercentageOffset ?? 0.2); final span = TextSpan( text: verticeLabel.text, style: style, diff --git a/test/chart/radar_chart/radar_chart_painter_test.dart b/test/chart/radar_chart/radar_chart_painter_test.dart index 063c59e89..7b4c3845d 100644 --- a/test/chart/radar_chart/radar_chart_painter_test.dart +++ b/test/chart/radar_chart/radar_chart_painter_test.dart @@ -1033,7 +1033,6 @@ void main() { ); }, verticeLabelTextStyle: MockData.textStyle4, - verticeLabelPositionPercentageOffset: 0.2, radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), gridBorderData: const BorderSide(color: MockData.color3, width: 3), From 5d2317c4d8ebff19acaea8af10a5ddceb41eeb47 Mon Sep 17 00:00:00 2001 From: Koichi5 Date: Sat, 10 May 2025 12:06:39 +0900 Subject: [PATCH 4/5] feature: Add RadarChart sample2 in samples list --- .../presentation/samples/chart_samples.dart | 2 + .../samples/radar/radar_chart_sample2.dart | 113 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 example/lib/presentation/samples/radar/radar_chart_sample2.dart diff --git a/example/lib/presentation/samples/chart_samples.dart b/example/lib/presentation/samples/chart_samples.dart index 74d2ec7a0..a68338ec8 100644 --- a/example/lib/presentation/samples/chart_samples.dart +++ b/example/lib/presentation/samples/chart_samples.dart @@ -27,6 +27,7 @@ import 'pie/pie_chart_sample1.dart'; import 'pie/pie_chart_sample2.dart'; import 'pie/pie_chart_sample3.dart'; import 'radar/radar_chart_sample1.dart'; +import 'radar/radar_chart_sample2.dart'; import 'scatter/scatter_chart_sample1.dart'; import 'scatter/scatter_chart_sample2.dart'; @@ -68,6 +69,7 @@ class ChartSamples { ], ChartType.radar: [ RadarChartSample(1, (context) => RadarChartSample1()), + RadarChartSample(2, (context) => const RadarChartSample2()), ], ChartType.candlestick: [ CandlestickChartSample(1, (context) => const CandlestickChartSample1()), diff --git a/example/lib/presentation/samples/radar/radar_chart_sample2.dart b/example/lib/presentation/samples/radar/radar_chart_sample2.dart new file mode 100644 index 000000000..16ca65c30 --- /dev/null +++ b/example/lib/presentation/samples/radar/radar_chart_sample2.dart @@ -0,0 +1,113 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:flutter/material.dart'; + +class RadarChartSample2 extends StatefulWidget { + const RadarChartSample2({super.key}); + + final List scores = const [ + SubjectScore(subject: 'Social Studies', value: 80), + SubjectScore(subject: 'Math', value: 75), + SubjectScore(subject: 'English', value: 70), + SubjectScore(subject: 'Science', value: 65), + SubjectScore(subject: 'Art', value: 60), + ]; + + final double _maxPoint = 100; + + @override + State createState() => _RadarChartSample2State(); +} + +class _RadarChartSample2State extends State { + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 1, + child: RadarChart( + RadarChartDataExtended( + dataSets: [ + RadarDataSet( + dataEntries: widget.scores + .map((score) => RadarEntry(value: score.value)) + .toList(), + ), + RadarDataSet( + dataEntries: List.generate( + widget.scores.length, + (index) => RadarEntry(value: widget._maxPoint), + ), + fillColor: Colors.transparent, + borderColor: Colors.transparent, + ), + ], + radarBackgroundColor: Colors.transparent, + borderData: FlBorderData( + show: true, + border: Border.all(color: AppColors.borderColor), + ), + radarBorderData: const BorderSide( + color: AppColors.borderColor, + ), + getTitle: (index, angle) { + return RadarChartTitle( + text: widget.scores[index].subject, + positionPercentageOffset: 0.1, + ); + }, + getVerticeLabel: (index) { + return RadarChartVerticeLabel( + text: widget.scores[index].value.toInt().toString(), + positionPercentageOffset: 0, + ); + }, + titlePositionPercentageOffset: 0.5, + verticeLabelTextStyle: const TextStyle( + color: AppColors.primary, + ), + tickBorderData: const BorderSide( + color: AppColors.mainGridLineColor, + ), + gridBorderData: const BorderSide( + color: AppColors.mainGridLineColor, + ), + tickCount: 4, + ticksTextStyle: const TextStyle( + color: Colors.transparent, + ), + ), + ), + ); + } +} + +class RadarChartDataExtended extends RadarChartData { + RadarChartDataExtended({ + required List super.dataSets, + super.radarBackgroundColor, + super.borderData, + super.radarBorderData, + super.getTitle, + super.getVerticeLabel, + super.titleTextStyle, + super.titlePositionPercentageOffset, + super.verticeLabelTextStyle, + super.tickBorderData, + super.gridBorderData, + super.tickCount, + super.ticksTextStyle, + }); + + @override + RadarEntry get minEntry => super.maxEntry; +} + +class SubjectScore { + const SubjectScore({ + required this.subject, + required this.value, + }); + + final String subject; + final double value; +} From f52d9b0d5eff40f75e5b42fa4d0c288612d08bf2 Mon Sep 17 00:00:00 2001 From: Koichi5 Date: Thu, 14 Aug 2025 22:56:01 +0900 Subject: [PATCH 5/5] fix: Fix typo and change property name --- .../samples/radar/radar_chart_sample2.dart | 4 ++-- lib/src/chart/radar_chart/radar_chart_data.dart | 17 +++++++++++------ .../chart/radar_chart/radar_chart_painter.dart | 2 +- .../radar_chart/radar_chart_painter_test.dart | 6 +++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/example/lib/presentation/samples/radar/radar_chart_sample2.dart b/example/lib/presentation/samples/radar/radar_chart_sample2.dart index 16ca65c30..efde7ab9c 100644 --- a/example/lib/presentation/samples/radar/radar_chart_sample2.dart +++ b/example/lib/presentation/samples/radar/radar_chart_sample2.dart @@ -56,9 +56,9 @@ class _RadarChartSample2State extends State { ); }, getVerticeLabel: (index) { - return RadarChartVerticeLabel( + return RadarChartVertexLabel( text: widget.scores[index].value.toInt().toString(), - positionPercentageOffset: 0, + offset: 0, ); }, titlePositionPercentageOffset: 0.5, diff --git a/lib/src/chart/radar_chart/radar_chart_data.dart b/lib/src/chart/radar_chart/radar_chart_data.dart index ec6e83a0e..f0f5b6c29 100644 --- a/lib/src/chart/radar_chart/radar_chart_data.dart +++ b/lib/src/chart/radar_chart/radar_chart_data.dart @@ -12,7 +12,7 @@ typedef GetTitleByIndexFunction = RadarChartTitle Function( double angle, ); -typedef GetVerticeLabelByIndexFunction = RadarChartVerticeLabel Function( +typedef GetVerticeLabelByIndexFunction = RadarChartVertexLabel Function( int index, ); @@ -46,19 +46,19 @@ class RadarChartTitle { } /// Defines a label for [RadarChart] vertices -class RadarChartVerticeLabel { - const RadarChartVerticeLabel({ +class RadarChartVertexLabel { + const RadarChartVertexLabel({ required this.text, - this.positionPercentageOffset, + this.offset, }); /// [text] is used to draw labels on the vertices of [RadarChart] final String text; - /// [positionPercentageOffset] is the place of showing label on the [RadarChart] vertices + /// [offset] is the place of showing label on the [RadarChart] vertices /// The higher the value of this field, the more labels move away from the chart vertices. /// This value should be between 0 and 1 - final double? positionPercentageOffset; + final double? offset; } /// [RadarChart] needs this class to render itself. @@ -228,6 +228,7 @@ class RadarChartData extends BaseChartData with EquatableMixin { GetTitleByIndexFunction? getTitle, GetVerticeLabelByIndexFunction? getVerticeLabel, TextStyle? titleTextStyle, + TextStyle? verticeLabelTextStyle, double? titlePositionPercentageOffset, int? tickCount, TextStyle? ticksTextStyle, @@ -245,6 +246,8 @@ class RadarChartData extends BaseChartData with EquatableMixin { getTitle: getTitle ?? this.getTitle, getVerticeLabel: getVerticeLabel ?? this.getVerticeLabel, titleTextStyle: titleTextStyle ?? this.titleTextStyle, + verticeLabelTextStyle: + verticeLabelTextStyle ?? this.verticeLabelTextStyle, titlePositionPercentageOffset: titlePositionPercentageOffset ?? this.titlePositionPercentageOffset, tickCount: tickCount ?? this.tickCount, @@ -267,6 +270,8 @@ class RadarChartData extends BaseChartData with EquatableMixin { getTitle: b.getTitle, getVerticeLabel: b.getVerticeLabel, titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), + verticeLabelTextStyle: + TextStyle.lerp(a.verticeLabelTextStyle, b.verticeLabelTextStyle, t), titlePositionPercentageOffset: lerpDouble( a.titlePositionPercentageOffset, b.titlePositionPercentageOffset, diff --git a/lib/src/chart/radar_chart/radar_chart_painter.dart b/lib/src/chart/radar_chart/radar_chart_painter.dart index 5740854e0..f730902ab 100644 --- a/lib/src/chart/radar_chart/radar_chart_painter.dart +++ b/lib/src/chart/radar_chart/radar_chart_painter.dart @@ -369,7 +369,7 @@ class RadarChartPainter extends BaseChartPainter { for (var index = 0; index < vertexPositions.length; index++) { final verticeLabel = data.getVerticeLabel!(index); - final threshold = 1.0 + (verticeLabel.positionPercentageOffset ?? 0.2); + final threshold = 1.0 + (verticeLabel.offset ?? 0.2); final span = TextSpan( text: verticeLabel.text, style: style, diff --git a/test/chart/radar_chart/radar_chart_painter_test.dart b/test/chart/radar_chart/radar_chart_painter_test.dart index 7b4c3845d..e30428269 100644 --- a/test/chart/radar_chart/radar_chart_painter_test.dart +++ b/test/chart/radar_chart/radar_chart_painter_test.dart @@ -958,7 +958,7 @@ void main() { ), ], getVerticeLabel: (index) { - return RadarChartVerticeLabel(text: 'Label $index'); + return RadarChartVertexLabel(text: 'Label $index'); }, verticeLabelTextStyle: MockData.textStyle4, radarBorderData: const BorderSide(color: MockData.color6, width: 33), @@ -1027,9 +1027,9 @@ void main() { ), ], getVerticeLabel: (index) { - return RadarChartVerticeLabel( + return RadarChartVertexLabel( text: 'Label $index', - positionPercentageOffset: 0.5, + offset: 0.5, ); }, verticeLabelTextStyle: MockData.textStyle4,