diff --git a/example/assets/icons/ic_gauge_chart.svg b/example/assets/icons/ic_gauge_chart.svg new file mode 100644 index 000000000..0147dab82 --- /dev/null +++ b/example/assets/icons/ic_gauge_chart.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/lib/presentation/resources/app_assets.dart b/example/lib/presentation/resources/app_assets.dart index 3575724f8..c3d8aca33 100644 --- a/example/lib/presentation/resources/app_assets.dart +++ b/example/lib/presentation/resources/app_assets.dart @@ -13,6 +13,8 @@ class AppAssets { return 'assets/icons/ic_scatter_chart.svg'; case ChartType.radar: return 'assets/icons/ic_radar_chart.svg'; + case ChartType.gauge: + return 'assets/icons/ic_gauge_chart.svg'; } } diff --git a/example/lib/presentation/samples/chart_sample.dart b/example/lib/presentation/samples/chart_sample.dart index 6d753be25..83ad72c41 100644 --- a/example/lib/presentation/samples/chart_sample.dart +++ b/example/lib/presentation/samples/chart_sample.dart @@ -40,3 +40,9 @@ class RadarChartSample extends ChartSample { @override ChartType get type => ChartType.radar; } + +class GaugeChartSample extends ChartSample { + GaugeChartSample(super.number, super.builder); + @override + ChartType get type => ChartType.gauge; +} diff --git a/example/lib/presentation/samples/chart_samples.dart b/example/lib/presentation/samples/chart_samples.dart index 4114cd5f6..fb54df010 100644 --- a/example/lib/presentation/samples/chart_samples.dart +++ b/example/lib/presentation/samples/chart_samples.dart @@ -1,3 +1,4 @@ +import 'package:fl_chart_app/presentation/samples/gauge/gauge_chart_sample1.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'bar/bar_chart_sample1.dart'; @@ -62,5 +63,8 @@ class ChartSamples { ChartType.radar: [ RadarChartSample(1, (context) => RadarChartSample1()), ], + ChartType.gauge: [ + GaugeChartSample(1, (context) => const GaugeChartSample1()), + ], }; } diff --git a/example/lib/presentation/samples/gauge/gauge_chart_sample1.dart b/example/lib/presentation/samples/gauge/gauge_chart_sample1.dart new file mode 100644 index 000000000..219209233 --- /dev/null +++ b/example/lib/presentation/samples/gauge/gauge_chart_sample1.dart @@ -0,0 +1,63 @@ +import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class GaugeChartSample1 extends StatefulWidget { + const GaugeChartSample1({super.key}); + + @override + State createState() => GaugeChartSample1State(); +} + +class GaugeChartSample1State extends State { + double _value = 0.7; + bool _isSelected = false; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(28), + child: Column( + children: [ + SizedBox( + width: 250, + height: 250, + child: GaugeChart( + GaugeChartData( + value: _value, + valueColor: VariableGaugeColor( + colors: [ + AppColors.contentColorYellow, + AppColors.contentColorBlue, + AppColors.contentColorRed + ], + limits: [0.35, 0.5], + ), + backgroundColor: AppColors.contentColorPurple + .withOpacity(_isSelected ? 0.2 : 1), + strokeWidth: 30, + startAngle: 45, + endAngle: -225, + strokeCap: StrokeCap.round, + ticks: const GaugeTicks( + count: 11, + color: AppColors.contentColorCyan, + radius: 5, + position: GaugeTickPosition.inner, + margin: 5, + ), + touchData: GaugeTouchData( + enabled: true, + touchCallback: (_, value) => setState(() { + _isSelected = value?.spot != null; + }), + ), + ), + ), + ), + Slider(value: _value, onChanged: (v) => setState(() => _value = v)), + ], + ), + ); + } +} diff --git a/example/lib/util/app_helper.dart b/example/lib/util/app_helper.dart index 38c5ac882..4d6f2a7d8 100644 --- a/example/lib/util/app_helper.dart +++ b/example/lib/util/app_helper.dart @@ -1,7 +1,7 @@ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/urls.dart'; -enum ChartType { line, bar, pie, scatter, radar } +enum ChartType { line, bar, pie, scatter, radar, gauge } extension ChartTypeExtension on ChartType { String get displayName => '$simpleName Chart'; @@ -12,6 +12,7 @@ extension ChartTypeExtension on ChartType { ChartType.pie => 'Pie', ChartType.scatter => 'Scatter', ChartType.radar => 'Radar', + ChartType.gauge => 'Gauge', }; String get documentationUrl => Urls.getChartDocumentationUrl(this); diff --git a/lib/fl_chart.dart b/lib/fl_chart.dart index d3597fb81..95cea2b67 100644 --- a/lib/fl_chart.dart +++ b/lib/fl_chart.dart @@ -7,6 +7,8 @@ export 'src/chart/base/axis_chart/axis_chart_data.dart'; export 'src/chart/base/axis_chart/axis_chart_widgets.dart'; export 'src/chart/base/base_chart/base_chart_data.dart'; export 'src/chart/base/base_chart/fl_touch_event.dart'; +export 'src/chart/gauge_chart/gauge_chart.dart'; +export 'src/chart/gauge_chart/gauge_chart_data.dart'; export 'src/chart/line_chart/line_chart.dart'; export 'src/chart/line_chart/line_chart_data.dart'; export 'src/chart/pie_chart/pie_chart.dart'; diff --git a/lib/src/chart/gauge_chart/gauge_chart.dart b/lib/src/chart/gauge_chart/gauge_chart.dart new file mode 100644 index 000000000..c73816025 --- /dev/null +++ b/lib/src/chart/gauge_chart/gauge_chart.dart @@ -0,0 +1,62 @@ +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_data.dart'; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_renderer.dart'; +import 'package:flutter/widgets.dart'; + +class GaugeChart extends ImplicitlyAnimatedWidget { + /// [data] determines how the [GaugeChart] should be look like, + /// when you make any change in the [GaugeChartData], it updates + /// new values with animation, and duration is [swapAnimationDuration]. + /// also you can change the [swapAnimationCurve] + /// which default is [Curves.linear]. + const GaugeChart( + this.data, { + this.chartRendererKey, + super.key, + Duration swapAnimationDuration = const Duration(milliseconds: 150), + Curve swapAnimationCurve = Curves.linear, + }) : super( + duration: swapAnimationDuration, + curve: swapAnimationCurve, + ); + + /// Determines how the [GaugeChartData] should be look like. + final GaugeChartData data; + + /// We pass this key to our renderers which are supposed to + /// render the chart itself (without anything around the chart). + final Key? chartRendererKey; + + /// Creates a [_GaugeChartState] + @override + _GaugeChartState createState() => _GaugeChartState(); +} + +class _GaugeChartState extends AnimatedWidgetBaseState { + /// we handle under the hood animations (implicit animations) via this tween, + /// it lerps between the old [GaugeChartData] to the new one. + GaugeChartDataTween? _gaugeChartDataTween; + + @override + Widget build(BuildContext context) { + final showingData = _getData(); + + return GaugeChartLeaf( + data: _gaugeChartDataTween!.evaluate(animation), + targetData: showingData, + ); + } + + GaugeChartData _getData() { + return widget.data; + } + + @override + void forEachTween(TweenVisitor visitor) { + _gaugeChartDataTween = visitor( + _gaugeChartDataTween, + widget.data, + (dynamic value) => + GaugeChartDataTween(begin: value as GaugeChartData, end: widget.data), + ) as GaugeChartDataTween?; + } +} diff --git a/lib/src/chart/gauge_chart/gauge_chart_data.dart b/lib/src/chart/gauge_chart/gauge_chart_data.dart new file mode 100644 index 000000000..bd7a0aed9 --- /dev/null +++ b/lib/src/chart/gauge_chart/gauge_chart_data.dart @@ -0,0 +1,268 @@ +import 'dart:ui'; + +import 'package:equatable/equatable.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/utils/lerp.dart'; +import 'package:flutter/widgets.dart'; + +class ColoredTick with EquatableMixin { + const ColoredTick(this.position, this.color); + final double position; + final Color color; + + @override + List get props => [position, color]; +} + +mixin ColoredTicksGenerator { + Iterable getColoredTicks(); +} + +@immutable +abstract class GaugeColor { + Color getColor(double value); + + static GaugeColor lerp(GaugeColor a, GaugeColor b, double t) { + return _LerpGaugeColor(a, b, t); + } +} + +class _LerpGaugeColor implements GaugeColor, ColoredTicksGenerator { + _LerpGaugeColor(this.a, this.b, this.t); + final GaugeColor a; + final GaugeColor b; + final double t; + + @override + Color getColor(double value) { + return Color.lerp(a.getColor(value), b.getColor(value), t)!; + } + + @override + Iterable getColoredTicks() sync* { + if (a is ColoredTicksGenerator) { + for (final tick in (a as ColoredTicksGenerator).getColoredTicks()) { + yield ColoredTick(tick.position, tick.color.withOpacity(1 - t)); + } + } + if (b is ColoredTicksGenerator) { + for (final tick in (b as ColoredTicksGenerator).getColoredTicks()) { + yield ColoredTick(tick.position, tick.color.withOpacity(t)); + } + } + } +} + +@immutable +class SimpleGaugeColor with EquatableMixin implements GaugeColor { + const SimpleGaugeColor({required this.color}); + final Color color; + + @override + Color getColor(double value) => color; + + @override + List get props => [color]; +} + +class VariableGaugeColor + with EquatableMixin + implements GaugeColor, ColoredTicksGenerator { + VariableGaugeColor({ + required this.limits, + required this.colors, + }) : assert( + colors.length - 1 == limits.length, + 'length of limits should be equals to colors length minus one', + ), + assert( + limits.length <= 1 || limits.reduce((a, b) => a < b ? 0 : 2) == 0, + 'the limits list should be sorted in ascending order', + ), + assert( + limits.first > 0 || limits.last < 1.0, + 'limits values should be in range 0, 1 (exclusive)', + ); + + final List limits; + final List colors; + + @override + Color getColor(double value) { + for (var i = 0; i < limits.length; i++) { + if (value < limits[i]) return colors[i]; + } + return colors.last; + } + + @override + Iterable getColoredTicks() sync* { + for (var i = 0; i < limits.length; i++) { + yield ColoredTick(limits[i], colors[i + 1]); + } + } + + @override + List get props => [limits, colors]; +} + +enum GaugeTickPosition { + inner, + outer, + center, +} + +@immutable +class GaugeTicks with EquatableMixin { + const GaugeTicks({ + this.count = 3, + this.radius = 3.0, + required this.color, + this.position = GaugeTickPosition.outer, + this.margin = 3, + this.showChangingColorTicks = true, + }) : assert(count > 2, 'count should be >= 2'), + assert(radius > 0, 'radius should be > 0'); + final int count; + final double radius; + final Color color; + final GaugeTickPosition position; + final double margin; + final bool showChangingColorTicks; + + static GaugeTicks? lerp(GaugeTicks? a, GaugeTicks? b, double t) { + // TODO(FlorianArnould): if showChangingColorTicks are different + // or just a or b is null, handle this with a fade like effect by replacing + // the null value with a default one + if (a == null || b == null) return b; + return GaugeTicks( + color: Color.lerp(a.color, b.color, t)!, + count: lerpInt(a.count, b.count, t), + margin: lerpDouble(a.margin, b.margin, t)!, + position: b.position, + radius: lerpDouble(a.radius, b.radius, t)!, + showChangingColorTicks: b.showChangingColorTicks, + ); + } + + @override + List get props => + [count, radius, color, position, margin, showChangingColorTicks]; +} + +class GaugeChartData extends BaseChartData with EquatableMixin { + GaugeChartData({ + this.strokeCap = StrokeCap.butt, + this.backgroundColor, + required this.valueColor, + required this.value, + required this.strokeWidth, + required this.startAngle, + required this.endAngle, + this.ticks, + GaugeTouchData? touchData, + }) : gaugeTouchData = touchData ?? GaugeTouchData(), + super(touchData: touchData ?? GaugeTouchData()); + final StrokeCap strokeCap; + final double value; + final double strokeWidth; + final GaugeColor valueColor; + final Color? backgroundColor; + final double startAngle; + final double endAngle; + final GaugeTouchData gaugeTouchData; + final GaugeTicks? ticks; + + GaugeChartData copyWith({ + StrokeCap? strokeCap, + Color? backgroundColor, + GaugeColor? valueColor, + double? value, + double? strokeWidth, + double? startAngle, + double? endAngle, + GaugeTicks? ticks, + FlBorderData? borderData, + GaugeTouchData? gaugeTouchData, + }) => + GaugeChartData( + strokeCap: strokeCap ?? this.strokeCap, + backgroundColor: backgroundColor ?? this.backgroundColor, + valueColor: valueColor ?? this.valueColor, + value: value ?? this.value, + strokeWidth: strokeWidth ?? this.strokeWidth, + startAngle: startAngle ?? this.startAngle, + endAngle: endAngle ?? this.endAngle, + ticks: ticks ?? this.ticks, + touchData: gaugeTouchData ?? this.gaugeTouchData, + ); + + @override + GaugeChartData lerp(BaseChartData a, BaseChartData b, double t) { + if (a is GaugeChartData && b is GaugeChartData) { + return GaugeChartData( + ticks: GaugeTicks.lerp(a.ticks, b.ticks, t), + strokeCap: b.strokeCap, + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + valueColor: GaugeColor.lerp(a.valueColor, b.valueColor, t), + value: lerpDouble(a.value, b.value, t)!, + strokeWidth: lerpDouble(a.strokeWidth, b.strokeWidth, t)!, + startAngle: lerpDouble(a.startAngle, b.startAngle, t)!, + endAngle: lerpDouble(a.endAngle, b.endAngle, t)!, + touchData: b.gaugeTouchData, + ); + } else { + throw Exception('Illegal State'); + } + } + + @override + List get props => [ + ticks, + strokeCap, + backgroundColor, + valueColor, + value, + strokeWidth, + startAngle, + endAngle, + gaugeTouchData, + ticks, + borderData, + ]; +} + +class GaugeTouchData extends FlTouchData { + GaugeTouchData({ + bool? enabled, + BaseTouchCallback? touchCallback, + MouseCursorResolver? mouseCursorResolver, + Duration? longPressDuration, + }) : super( + enabled ?? true, + touchCallback, + mouseCursorResolver, + longPressDuration, + ); +} + +class GaugeTouchResponse extends BaseTouchResponse { + GaugeTouchResponse(this.spot); + GaugeTouchedSpot? spot; +} + +class GaugeTouchedSpot extends TouchedSpot with EquatableMixin { + GaugeTouchedSpot(super.spot, super.offset); +} + +/// It lerps a [GaugeChartData] to another [GaugeChartData] (handles animation for updating values) +class GaugeChartDataTween extends Tween { + GaugeChartDataTween({ + required GaugeChartData begin, + required GaugeChartData end, + }) : super(begin: begin, end: end); + + /// Lerps a [GaugeChartData] based on [t] value, check [Tween.lerp]. + @override + GaugeChartData lerp(double t) => begin!.lerp(begin!, end!, t); +} diff --git a/lib/src/chart/gauge_chart/gauge_chart_painter.dart b/lib/src/chart/gauge_chart/gauge_chart_painter.dart new file mode 100644 index 000000000..960ec5025 --- /dev/null +++ b/lib/src/chart/gauge_chart/gauge_chart_painter.dart @@ -0,0 +1,267 @@ +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:fl_chart/src/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class GaugeChartPainter extends BaseChartPainter { + GaugeChartPainter() : super() { + _backgroundPaint = Paint()..isAntiAlias = true; + _valuePaint = Paint()..isAntiAlias = true; + _tickPaint = Paint(); + } + + late Paint _backgroundPaint; + late Paint _valuePaint; + late Paint _tickPaint; + + GaugePosition? _gaugePosition; + + @override + void paint( + BuildContext context, + CanvasWrapper canvasWrapper, + PaintHolder holder, + ) { + super.paint(context, canvasWrapper, holder); + + drawValue(canvasWrapper, holder); + drawTicks(canvasWrapper, holder); + } + + @visibleForTesting + void drawTicks( + CanvasWrapper canvasWrapper, + PaintHolder holder, + ) { + final data = holder.data; + final ticks = data.ticks; + if (ticks == null) return; + + final size = canvasWrapper.size; + + final centerOffset = center(size); + final angleRange = data.endAngle - data.startAngle; + final interTickAngle = angleRange / (ticks.count - 1); + + _tickPaint.color = ticks.color; + + final radius = gaugeRadius(size); + + /// draw gauge ticks + for (var i = 0; i < ticks.count; i++) { + final angle = Utils().radians(data.startAngle + interTickAngle * i); + _drawTick( + canvasWrapper, + centerOffset, + angle, + radius, + ticks, + data.strokeWidth, + ); + } + + // draw changing color ticks + final valueColor = data.valueColor; + if (ticks.showChangingColorTicks && valueColor is ColoredTicksGenerator) { + for (final tick + in (valueColor as ColoredTicksGenerator).getColoredTicks()) { + final angle = + Utils().radians(data.startAngle + angleRange * tick.position); + _tickPaint.color = tick.color; + _drawTick( + canvasWrapper, + centerOffset, + angle, + radius, + ticks, + data.strokeWidth, + ); + } + } + } + + void _drawTick( + CanvasWrapper canvasWrapper, + Offset center, + double angle, + double radius, + GaugeTicks ticks, + double strokeWidth, + ) { + final positionRadius = switch (ticks.position) { + GaugeTickPosition.inner => + radius - strokeWidth - ticks.radius - ticks.margin, + GaugeTickPosition.outer => radius + ticks.radius + ticks.margin, + GaugeTickPosition.center => radius - strokeWidth / 2, + }; + final tickX = center.dx + cos(angle) * positionRadius; + final tickY = center.dy + sin(angle) * positionRadius; + + canvasWrapper.drawCircle(Offset(tickX, tickY), ticks.radius, _tickPaint); + } + + GaugePosition _calculateValuePosition( + Size viewSize, + PaintHolder holder, + ) { + final data = holder.data; + final size = Size.square( + viewSize.shortestSide - data.strokeWidth, + ); + final demiStroke = data.strokeWidth / 2; + final offset = Offset( + max(viewSize.width - viewSize.height, 0) / 2 + demiStroke, + max(viewSize.height - viewSize.width, 0) / 2 + demiStroke, + ); + final angleRange = data.endAngle - data.startAngle; + return GaugePosition( + offset & size, + data.strokeCap, + data.strokeWidth, + angleRange, + data.startAngle, + angleRange * data.value.clamp(0, 1), + ); + } + + @visibleForTesting + void drawValue( + CanvasWrapper canvasWrapper, + PaintHolder holder, + ) { + final data = holder.data; + + final backgroundColor = data.backgroundColor; + final position = + _gaugePosition = _calculateValuePosition(canvasWrapper.size, holder); + + /// Draw background if needed + if (backgroundColor != null) { + _backgroundPaint + ..color = backgroundColor + ..strokeWidth = data.strokeWidth + ..strokeCap = data.strokeCap + ..style = PaintingStyle.stroke; + canvasWrapper.drawArc( + position.rect, + Utils().radians(position.startAngle), + Utils().radians(position.angleRange), + false, + _backgroundPaint, + ); + } + + /// Draw value + _valuePaint + ..color = data.valueColor.getColor(data.value) + ..strokeWidth = data.strokeWidth + ..strokeCap = data.strokeCap + ..style = PaintingStyle.stroke; + canvasWrapper.drawArc( + position.rect, + Utils().radians(position.startAngle), + Utils().radians(position.angleSize), + false, + _valuePaint, + ); + } + + GaugeTouchedSpot? handleTouch( + Offset touchedPoint, + Size viewSize, + PaintHolder holder, + ) { + final position = + _gaugePosition ??= _calculateValuePosition(viewSize, holder); + if (position.contains(touchedPoint)) { + final offset = position.getInterestSpot(); + return GaugeTouchedSpot( + FlSpot(offset.dx, offset.dy), + offset, + ); + } + return null; + } + + @visibleForTesting + Offset center(Size size) => Offset(size.width / 2.0, size.height / 2.0); + + @visibleForTesting + double gaugeRadius(Size size) => size.shortestSide / 2; +} + +class GaugePosition { + GaugePosition( + this.rect, + this.strokeCap, + this.strokeWidth, + this.angleRange, + this.startAngle, + this.angleSize, + ); + + final Rect rect; + final double strokeWidth; + final double angleRange; + final double startAngle; + final double angleSize; + final StrokeCap strokeCap; + + Range _calculateValidValueRange() { + final radius = rect.shortestSide / 2; + final halfStroke = strokeWidth / 2; + return Range.fromValues(radius - halfStroke, radius + halfStroke); + } + + Offset getInterestSpot() { + final radius = rect.shortestSide / 2; + final averageAngle = startAngle + angleSize / 2; + return rect.center + + Offset.fromDirection(Utils().radians(averageAngle), radius); + } + + bool contains(Offset point) { + final vector = point - rect.center; + var start = startAngle; + var end = angleSize < 0 ? angleSize + startAngle : angleSize - startAngle; + if (strokeCap != StrokeCap.butt) { + final strokeRadius = strokeWidth / 2; + final radius = rect.shortestSide / 2; + final bonusAngle = 180 * strokeRadius / (pi * radius); + start = angleSize < 0 ? start + bonusAngle : start - bonusAngle; + end = angleSize < 0 ? end - bonusAngle : end + bonusAngle; + } + return _calculateValidValueRange().contains(vector.distance) && + DegreeAngleRange(start, end) + .contains(Utils().degrees(vector.direction)); + } +} + +class Range { + const Range._(this.min, this.max); + factory Range.fromValues(double a, double b) { + return a > b ? Range._(b, a) : Range._(a, b); + } + + final double min; + final double max; + + bool contains(double value) { + return min <= value && max >= value; + } +} + +class DegreeAngleRange { + const DegreeAngleRange(this.start, this.end); + final double start; + final double end; + + bool contains(double angle) { + final ref = (end - start) < 0 ? end - start + 360 : end - start; + final value = (angle - start) < 0 ? angle - start + 360 : angle - start; + return end < start ? value > ref : value < ref; + } +} diff --git a/lib/src/chart/gauge_chart/gauge_chart_renderer.dart b/lib/src/chart/gauge_chart/gauge_chart_renderer.dart new file mode 100644 index 000000000..9a6147b35 --- /dev/null +++ b/lib/src/chart/gauge_chart/gauge_chart_renderer.dart @@ -0,0 +1,113 @@ +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_data.dart'; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_painter.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/widgets.dart'; + +// coverage:ignore-start + +/// Low level GaugeChart Widget. +class GaugeChartLeaf extends LeafRenderObjectWidget { + const GaugeChartLeaf({ + super.key, + required this.data, + required this.targetData, + }); + + final GaugeChartData data; + final GaugeChartData targetData; + + @override + RenderGaugeChart createRenderObject(BuildContext context) => RenderGaugeChart( + context, + data, + targetData, + MediaQuery.of(context).textScaleFactor, + ); + + @override + void updateRenderObject(BuildContext context, RenderGaugeChart renderObject) { + renderObject + ..data = data + ..targetData = targetData + ..textScale = MediaQuery.of(context).textScaleFactor + ..buildContext = context; + } +} +// coverage:ignore-end + +/// Renders our GaugeChart, also handles hitTest. +class RenderGaugeChart extends RenderBaseChart { + RenderGaugeChart( + BuildContext context, + GaugeChartData data, + GaugeChartData targetData, + double textScale, + ) : _data = data, + _targetData = targetData, + _textScale = textScale, + super(targetData.gaugeTouchData, context); + + GaugeChartData get data => _data; + GaugeChartData _data; + + set data(GaugeChartData value) { + if (_data == value) return; + _data = value; + markNeedsPaint(); + } + + GaugeChartData get targetData => _targetData; + GaugeChartData _targetData; + + set targetData(GaugeChartData value) { + if (_targetData == value) return; + _targetData = value; + super.updateBaseTouchData(_targetData.gaugeTouchData); + markNeedsPaint(); + } + + double get textScale => _textScale; + double _textScale; + + set textScale(double value) { + if (_textScale == value) return; + _textScale = value; + markNeedsPaint(); + } + + // We couldn't mock [size] property of this class, that's why we have this + @visibleForTesting + Size? mockTestSize; + + @visibleForTesting + GaugeChartPainter painter = GaugeChartPainter(); + + PaintHolder get paintHolder { + return PaintHolder(data, targetData, textScale); + } + + @override + void paint(PaintingContext context, Offset offset) { + final canvas = context.canvas + ..save() + ..translate(offset.dx, offset.dy); + painter.paint( + buildContext, + CanvasWrapper(canvas, mockTestSize ?? size), + paintHolder, + ); + canvas.restore(); + } + + @override + GaugeTouchResponse getResponseAtLocation(Offset localPosition) { + final touchedSpot = painter.handleTouch( + localPosition, + mockTestSize ?? size, + paintHolder, + ); + return GaugeTouchResponse(touchedSpot); + } +} diff --git a/test/chart/data_pool.dart b/test/chart/data_pool.dart index 2bb1f1315..a643fb793 100644 --- a/test/chart/data_pool.dart +++ b/test/chart/data_pool.dart @@ -3174,3 +3174,63 @@ final DefaultTextStyle defaultTextStyle1 = DefaultTextStyle( style: const TextStyle(), child: Container(), ); + +final GaugeTouchData gaugeTouchData1Clone = gaugeTouchData1; + +final GaugeTouchedSpot gaugeTouchedSpot1 = GaugeTouchedSpot( + const FlSpot(0, 1), + const Offset(0, 1), +); + +final gaugeTouchedSpotClone1 = gaugeTouchedSpot1; + +final GaugeTouchedSpot gaugeTouchedSpot2 = GaugeTouchedSpot( + const FlSpot(1, 0), + const Offset(0, 1), +); + +final GaugeTouchedSpot gaugeTouchedSpot3 = GaugeTouchedSpot( + const FlSpot(0, 1), + const Offset(1, 0), +); + +final GaugeTouchData gaugeTouchData1 = GaugeTouchData( + enabled: true, + touchCallback: (_, __) {}, +); + +final GaugeTouchData gaugeTouchData2 = GaugeTouchData( + enabled: false, + touchCallback: (_, __) {}, + mouseCursorResolver: (_, __) => MouseCursor.defer, +); + +const GaugeColor gaugeColor1 = SimpleGaugeColor(color: Colors.black); + +final GaugeColor gaugeColor2 = VariableGaugeColor( + limits: [0.1, 0.5], + colors: [Colors.red, Colors.red, Colors.red], +); + +const GaugeTicks gaugeTicks1 = GaugeTicks( + color: Colors.blue, + count: 4, + margin: 7, + position: GaugeTickPosition.center, + radius: 4, + showChangingColorTicks: false, +); + +final GaugeChartData gaugeChartData1 = GaugeChartData( + startAngle: 25, + endAngle: 90, + strokeWidth: 45, + value: 0.2, + valueColor: gaugeColor1, + backgroundColor: Colors.amber, + strokeCap: StrokeCap.round, + ticks: gaugeTicks1, + touchData: gaugeTouchData1, +); + +final GaugeChartData gaugeChartData1Clone = gaugeChartData1.copyWith(); diff --git a/test/chart/gauge_chart/gauge_chart_data_test.dart b/test/chart/gauge_chart/gauge_chart_data_test.dart new file mode 100644 index 000000000..62ee5ec81 --- /dev/null +++ b/test/chart/gauge_chart/gauge_chart_data_test.dart @@ -0,0 +1,368 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../data_pool.dart'; + +void main() { + group('GaugeChart Data equality check', () { + test('GaugeChartData equality test', () { + /// object equality test + expect(gaugeChartData1 == gaugeChartData1Clone, true); + + expect( + gaugeChartData1 == gaugeChartData1Clone.copyWith(value: 0.5), + false, + ); + + expect( + gaugeChartData1 == + gaugeChartData1Clone.copyWith(backgroundColor: Colors.black), + false, + ); + + expect( + gaugeChartData1 == + gaugeChartData1Clone.copyWith( + borderData: FlBorderData( + show: true, + border: Border.all(color: Colors.green), + ), + ), + true, + ); + + expect( + gaugeChartData1 == + gaugeChartData1Clone.copyWith( + ticks: const GaugeTicks(color: Colors.white), + ), + false, + ); + + expect( + gaugeChartData1 == + gaugeChartData1Clone.copyWith( + strokeCap: StrokeCap.square, + ), + false, + ); + + expect( + gaugeChartData1 == + gaugeChartData1Clone.copyWith(gaugeTouchData: gaugeTouchData2), + false, + ); + + expect( + gaugeChartData1 == gaugeChartData1Clone.copyWith(startAngle: 0), + false, + ); + + expect( + gaugeChartData1 == gaugeChartData1Clone.copyWith(endAngle: 0), + false, + ); + + expect( + gaugeChartData1 == gaugeChartData1Clone.copyWith(strokeWidth: 7), + false, + ); + + expect( + gaugeChartData1 == + gaugeChartData1Clone.copyWith(valueColor: gaugeColor2), + false, + ); + }); + + test('GaugeColor equality test', () { + expect(gaugeColor1 == const SimpleGaugeColor(color: Colors.black), true); + expect(gaugeColor1 == const SimpleGaugeColor(color: Colors.red), false); + + expect( + gaugeColor2 == + VariableGaugeColor( + limits: [0.1, 0.5], + colors: [Colors.red, Colors.red, Colors.red], + ), + true, + ); + + expect( + gaugeColor2 == + VariableGaugeColor( + limits: [0.2, 0.5], + colors: [Colors.red, Colors.red, Colors.red], + ), + false, + ); + + expect( + gaugeColor2 == + VariableGaugeColor( + limits: [0.1, 0.5], + colors: [Colors.blue, Colors.red, Colors.red], + ), + false, + ); + + expect( + gaugeColor2 == + VariableGaugeColor( + limits: [0.1, 0.5, 0.6], + colors: [Colors.red, Colors.red, Colors.red, Colors.red], + ), + false, + ); + }); + + test('GaugeTicks equality test', () { + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.blue, + count: 4, + margin: 7, + position: GaugeTickPosition.center, + radius: 4, + showChangingColorTicks: false, + ), + true, + ); + + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.red, + count: 4, + margin: 7, + position: GaugeTickPosition.center, + radius: 4, + showChangingColorTicks: false, + ), + false, + ); + + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.blue, + count: 5, + margin: 7, + position: GaugeTickPosition.center, + radius: 4, + showChangingColorTicks: false, + ), + false, + ); + + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.blue, + count: 4, + margin: 8, + position: GaugeTickPosition.center, + radius: 4, + showChangingColorTicks: false, + ), + false, + ); + + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.blue, + count: 4, + margin: 7, + position: GaugeTickPosition.inner, + radius: 4, + showChangingColorTicks: false, + ), + false, + ); + + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.blue, + count: 4, + margin: 7, + position: GaugeTickPosition.center, + radius: 1, + showChangingColorTicks: false, + ), + false, + ); + + expect( + gaugeTicks1 == + const GaugeTicks( + color: Colors.blue, + count: 4, + margin: 7, + position: GaugeTickPosition.center, + radius: 4, + ), + false, + ); + }); + + test('GaugeTouchData equality test', () { + expect(gaugeTouchData1 == gaugeTouchData1Clone, true); + + expect(gaugeTouchData1 == gaugeTouchData2, false); + + expect( + gaugeTouchData1 == + GaugeTouchData( + enabled: true, + touchCallback: (_, __) {}, + ), + false, + ); + + expect( + gaugeTouchData1 == + GaugeTouchData( + enabled: true, + mouseCursorResolver: (_, __) => MouseCursor.uncontrolled, + ), + false, + ); + + expect( + gaugeTouchData1 == + GaugeTouchData( + enabled: true, + longPressDuration: Duration.zero, + ), + false, + ); + + expect( + gaugeTouchData1 == + GaugeTouchData( + enabled: true, + touchCallback: (_, __) {}, + mouseCursorResolver: (_, __) => MouseCursor.uncontrolled, + longPressDuration: Duration.zero, + ), + false, + ); + }); + + test('GaugeTouchedSpot equality test', () { + expect(gaugeTouchedSpot1 == gaugeTouchedSpotClone1, true); + expect(gaugeTouchedSpot1 == gaugeTouchedSpot2, false); + expect(gaugeTouchedSpot1 == gaugeTouchedSpot3, false); + }); + + test('GaugeChartDataTween lerp', () { + final a = GaugeChartData( + value: 0.7, + strokeWidth: 5, + startAngle: 0, + endAngle: 270, + valueColor: const SimpleGaugeColor(color: MockData.color0), + backgroundColor: MockData.color0, + strokeCap: StrokeCap.round, + ticks: const GaugeTicks( + color: MockData.color0, + count: 5, + margin: 7, + position: GaugeTickPosition.center, + radius: 7, + showChangingColorTicks: false, + ), + touchData: GaugeTouchData( + enabled: true, + touchCallback: (_, __) {}, + longPressDuration: const Duration(seconds: 7), + mouseCursorResolver: (_, __) => MouseCursor.defer, + ), + ); + + final b = GaugeChartData( + value: 0.3, + strokeWidth: 3, + startAngle: 20, + endAngle: 250, + valueColor: VariableGaugeColor( + limits: [0.4], + colors: [MockData.color0, MockData.color2], + ), + backgroundColor: MockData.color2, + strokeCap: StrokeCap.square, + ticks: const GaugeTicks( + color: MockData.color2, + count: 7, + margin: 9, + position: GaugeTickPosition.inner, + radius: 5, + ), + touchData: GaugeTouchData( + enabled: true, + touchCallback: (_, __) {}, + longPressDuration: const Duration(seconds: 7), + mouseCursorResolver: (_, __) => MouseCursor.defer, + ), + ); + + final data = GaugeChartDataTween(begin: a, end: b).lerp(0.5); + + expect(data.value, 0.5); + expect(data.strokeWidth, 4); + expect(data.startAngle, 10); + expect(data.endAngle, 260); + expect(data.valueColor.getColor(0.5), MockData.color1); + expect(data.valueColor.getColor(0.5), MockData.color1); + final colorTicks = + (data.valueColor as ColoredTicksGenerator).getColoredTicks().toList(); + expect(colorTicks, [ColoredTick(0.4, MockData.color2.withOpacity(0.5))]); + expect(data.strokeCap, StrokeCap.square); + expect(data.ticks?.color, MockData.color1); + expect(data.ticks?.count, 6); + expect(data.ticks?.margin, 8); + expect(data.ticks?.position, GaugeTickPosition.inner); + expect(data.ticks?.radius, 6); + expect(data.ticks?.showChangingColorTicks, true); + expect(data.touchData, b.touchData); + }); + + test('GaugeColor lerp', () { + final a = VariableGaugeColor( + limits: [0.2, 0.5, 0.7], + colors: [ + MockData.color0, + MockData.color1, + MockData.color2, + MockData.color3, + ], + ); + final b = VariableGaugeColor( + limits: [0.3, 0.6, 0.8], + colors: [ + MockData.color6, + MockData.color5, + MockData.color4, + MockData.color3, + ], + ); + final color = GaugeColor.lerp(a, b, 0.2); + + expect(color is ColoredTicksGenerator, true); + final generator = color as ColoredTicksGenerator; + final ticks = generator.getColoredTicks().toList(); + expect(ticks, [ + ColoredTick(0.2, MockData.color1.withOpacity(0.8)), + ColoredTick(0.5, MockData.color2.withOpacity(0.8)), + ColoredTick(0.7, MockData.color3.withOpacity(0.8)), + ColoredTick(0.3, MockData.color5.withOpacity(0.2)), + ColoredTick(0.6, MockData.color4.withOpacity(0.2)), + ColoredTick(0.8, MockData.color3.withOpacity(0.2)), + ]); + }); + }); +} diff --git a/test/chart/gauge_chart/gauge_chart_painter_test.dart b/test/chart/gauge_chart/gauge_chart_painter_test.dart new file mode 100644 index 000000000..892009c64 --- /dev/null +++ b/test/chart/gauge_chart/gauge_chart_painter_test.dart @@ -0,0 +1,449 @@ +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_painter.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:fl_chart/src/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../data_pool.dart'; +import 'gauge_chart_painter_test.mocks.dart'; + +@GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils]) +void main() { + final utilsMainInstance = Utils(); + group('paint()', () { + test('test 1', () { + const viewSize = Size(400, 400); + final data = GaugeChartData( + startAngle: 0, + endAngle: 90, + valueColor: const SimpleGaugeColor(color: Colors.red), + strokeWidth: 2, + value: 0.5, + ); + final gaugePainter = GaugeChartPainter(); + final holder = PaintHolder(data, data, 1); + final mockUtils = MockUtils(); + Utils.changeInstance(mockUtils); + when(mockUtils.radians(any)).thenAnswer((realInvocation) => 0); + when(mockUtils.degrees(any)).thenAnswer((realInvocation) => 0); + + final mockBuildContext = MockBuildContext(); + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + gaugePainter.paint( + mockBuildContext, + mockCanvasWrapper, + holder, + ); + + verify(mockCanvasWrapper.drawArc(any, any, any, any, any)).called(1); + Utils.changeInstance(utilsMainInstance); + }); + }); + + group('drawValue()', () { + test('only value', () { + const viewSize = Size(400, 400); + final data = GaugeChartData( + startAngle: 0, + endAngle: 90, + valueColor: const SimpleGaugeColor(color: MockData.color0), + strokeWidth: 2, + value: 0.5, + ); + final gaugePainter = GaugeChartPainter(); + final holder = PaintHolder(data, data, 1); + final mockUtils = MockUtils(); + Utils.changeInstance(mockUtils); + when(mockUtils.radians(any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[0] as double, + ); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + final drawArcResults = >[]; + when( + mockCanvasWrapper.drawArc( + captureAny, + captureAny, + captureAny, + captureAny, + captureAny, + ), + ).thenAnswer((inv) { + drawArcResults.add({ + 'rect': inv.positionalArguments[0] as Rect, + 'start_angle': inv.positionalArguments[1] as double, + 'sweep_angle': inv.positionalArguments[2] as double, + 'use_center': inv.positionalArguments[3] as bool, + 'paint_color': (inv.positionalArguments[4] as Paint).color, + 'paint_stroke_width': + (inv.positionalArguments[4] as Paint).strokeWidth, + 'paint_stroke_cap': (inv.positionalArguments[4] as Paint).strokeCap, + }); + }); + + gaugePainter.drawValue(mockCanvasWrapper, holder); + + expect(drawArcResults.length, 1); + + expect( + drawArcResults[0]['rect'], + Rect.fromCircle( + center: const Offset(200, 200), + radius: 200 - data.strokeWidth / 2, + ), + ); + expect(drawArcResults[0]['start_angle'], 0); + expect( + drawArcResults[0]['sweep_angle'], + 45, + ); // (endAngle - startAngle) * value + expect(drawArcResults[0]['use_center'], false); + expect(drawArcResults[0]['paint_color'], MockData.color0); + expect(drawArcResults[0]['paint_stroke_width'], data.strokeWidth); + expect(drawArcResults[0]['paint_stroke_cap'], data.strokeCap); + + Utils.changeInstance(utilsMainInstance); + }); + + test('with background', () { + const viewSize = Size(400, 400); + final data = GaugeChartData( + startAngle: 0, + endAngle: 90, + valueColor: const SimpleGaugeColor(color: MockData.color0), + strokeWidth: 2, + value: 0.5, + backgroundColor: MockData.color1, + ); + final gaugePainter = GaugeChartPainter(); + final holder = PaintHolder(data, data, 1); + final mockUtils = MockUtils(); + Utils.changeInstance(mockUtils); + when(mockUtils.radians(any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[0] as double, + ); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + final drawArcResults = >[]; + when( + mockCanvasWrapper.drawArc( + captureAny, + captureAny, + captureAny, + captureAny, + captureAny, + ), + ).thenAnswer((inv) { + drawArcResults.add({ + 'rect': inv.positionalArguments[0] as Rect, + 'start_angle': inv.positionalArguments[1] as double, + 'sweep_angle': inv.positionalArguments[2] as double, + 'use_center': inv.positionalArguments[3] as bool, + 'paint_color': (inv.positionalArguments[4] as Paint).color, + 'paint_stroke_width': + (inv.positionalArguments[4] as Paint).strokeWidth, + 'paint_stroke_cap': (inv.positionalArguments[4] as Paint).strokeCap, + }); + }); + + gaugePainter.drawValue(mockCanvasWrapper, holder); + + expect(drawArcResults.length, 2); + + // background + expect( + drawArcResults[1]['rect'], + Rect.fromCircle( + center: const Offset(200, 200), + radius: 200 - data.strokeWidth / 2, + ), + ); + expect(drawArcResults[0]['start_angle'], 0); + expect( + drawArcResults[0]['sweep_angle'], + 90, + ); + expect(drawArcResults[0]['use_center'], false); + expect(drawArcResults[0]['paint_color'], MockData.color1); + expect(drawArcResults[0]['paint_stroke_width'], data.strokeWidth); + expect(drawArcResults[0]['paint_stroke_cap'], data.strokeCap); + + // value + expect( + drawArcResults[1]['rect'], + Rect.fromCircle( + center: const Offset(200, 200), + radius: 200 - data.strokeWidth / 2, + ), + ); + expect(drawArcResults[1]['start_angle'], 0); + expect( + drawArcResults[1]['sweep_angle'], + 45, + ); // (endAngle - startAngle) * value + expect(drawArcResults[1]['use_center'], false); + expect(drawArcResults[1]['paint_color'], MockData.color0); + expect(drawArcResults[1]['paint_stroke_width'], data.strokeWidth); + expect(drawArcResults[1]['paint_stroke_cap'], data.strokeCap); + + Utils.changeInstance(utilsMainInstance); + }); + }); + + group('drawTicks()', () { + test('ticks without changing color ticks', () { + const viewSize = Size(400, 400); + const gaugeTicks = GaugeTicks( + count: 5, + color: MockData.color0, + radius: 4, + showChangingColorTicks: false, + position: GaugeTickPosition.center, + margin: 5, + ); + final data = GaugeChartData( + startAngle: 0, + endAngle: 90, + valueColor: const SimpleGaugeColor(color: MockData.color0), + strokeWidth: 2, + value: 0.5, + ticks: gaugeTicks, + ); + final gaugePainter = GaugeChartPainter(); + final holder = PaintHolder(data, data, 1); + final mockUtils = MockUtils(); + Utils.changeInstance(mockUtils); + when(mockUtils.radians(any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[0] as double, + ); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + final drawCircleResults = >[]; + when( + mockCanvasWrapper.drawCircle(captureAny, captureAny, captureAny), + ).thenAnswer((inv) { + drawCircleResults.add({ + 'offset': inv.positionalArguments[0] as Offset, + 'radius': inv.positionalArguments[1] as double, + 'paint_color': (inv.positionalArguments[2] as Paint).color, + }); + }); + + gaugePainter.drawTicks(mockCanvasWrapper, holder); + + expect(drawCircleResults.length, 5); + + final radius = 200 - data.strokeWidth / 2; + final angle = Utils().radians(90 / 4); + for (var i = 0; i < drawCircleResults.length; i++) { + final tickX = 200 + cos(angle * i) * radius; + final tickY = 200 + sin(angle * i) * radius; + + expect(drawCircleResults[i]['offset'], Offset(tickX, tickY)); + expect(drawCircleResults[i]['radius'], gaugeTicks.radius); + expect(drawCircleResults[i]['paint_color'], gaugeTicks.color); + } + + Utils.changeInstance(utilsMainInstance); + }); + + test('ticks with changing color ticks', () { + const viewSize = Size(400, 400); + const gaugeTicks = GaugeTicks( + count: 5, + color: MockData.color0, + radius: 4, + position: GaugeTickPosition.center, + margin: 5, + ); + final gaugeColor = VariableGaugeColor( + limits: [0.3, 0.5, 0.8], + colors: [ + MockData.color0, + MockData.color1, + MockData.color2, + MockData.color3, + ], + ); + final data = GaugeChartData( + startAngle: 0, + endAngle: 270, + valueColor: gaugeColor, + strokeWidth: 2, + value: 0.5, + ticks: gaugeTicks, + ); + final gaugePainter = GaugeChartPainter(); + final holder = PaintHolder(data, data, 1); + final mockUtils = MockUtils(); + Utils.changeInstance(mockUtils); + when(mockUtils.radians(any)).thenAnswer( + (realInvocation) => realInvocation.positionalArguments[0] as double, + ); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + final drawCircleResults = >[]; + when( + mockCanvasWrapper.drawCircle(captureAny, captureAny, captureAny), + ).thenAnswer((inv) { + drawCircleResults.add({ + 'offset': inv.positionalArguments[0] as Offset, + 'radius': inv.positionalArguments[1] as double, + 'paint_color': (inv.positionalArguments[2] as Paint).color, + }); + }); + + gaugePainter.drawTicks(mockCanvasWrapper, holder); + + expect(drawCircleResults.length, 5 + 3); + + final radius = 200 - data.strokeWidth / 2; + final angle = Utils().radians(270 / 4); + for (var i = 0; i < 5; i++) { + final tickX = 200 + cos(angle * i) * radius; + final tickY = 200 + sin(angle * i) * radius; + + expect( + drawCircleResults[i]['offset'], + Offset(tickX, tickY), + reason: 'index $i', + ); + expect(drawCircleResults[i]['radius'], gaugeTicks.radius); + expect(drawCircleResults[i]['paint_color'], gaugeTicks.color); + } + + for (var i = 0; i < 3; i++) { + final angle = 270 * gaugeColor.limits[i]; + final tickX = 200 + cos(angle) * radius; + final tickY = 200 + sin(angle) * radius; + + expect( + drawCircleResults[5 + i]['offset'], + Offset(tickX, tickY), + reason: 'index $i', + ); + expect(drawCircleResults[5 + i]['radius'], gaugeTicks.radius); + expect( + drawCircleResults[5 + i]['paint_color'], + gaugeColor.colors[i + 1], + ); + } + + Utils.changeInstance(utilsMainInstance); + }); + }); + + group('Range', () { + test('test 1', () { + expect(Range.fromValues(1, 10).contains(0.5), false); + expect(Range.fromValues(0, 0.5).contains(0.5), true); + expect(Range.fromValues(1, 1.5).contains(1), true); + expect(Range.fromValues(5, 10).contains(7), true); + expect(Range.fromValues(1, 10).contains(11), false); + expect(Range.fromValues(1, 10).contains(0.5), false); + }); + }); + + group('DegreeAngleRange', () { + test('test 1', () { + expect(const DegreeAngleRange(24, 50).contains(30), true); + expect(const DegreeAngleRange(50, 24).contains(30), true); + expect(const DegreeAngleRange(45, -225).contains(100), false); + expect(const DegreeAngleRange(45, -225).contains(171), true); + expect(const DegreeAngleRange(0, 315).contains(-46), true); + }); + }); + + group('handleTouch()', () { + test('test 1', () { + const viewSize = Size(250, 250); + final data = GaugeChartData( + value: 0.7, + valueColor: const SimpleGaugeColor(color: MockData.color0), + strokeWidth: 30, + startAngle: 45, + endAngle: -225, + strokeCap: StrokeCap.round, + touchData: GaugeTouchData( + enabled: true, + touchCallback: (_, __) {}, + ), + ); + final gaugePainter = GaugeChartPainter(); + final holder = PaintHolder(data, data, 1); + final mockUtils = MockUtils(); + Utils.changeInstance(mockUtils); + when(mockUtils.radians(captureAny)).thenAnswer( + (realInvocation) => utilsMainInstance + .radians(realInvocation.positionalArguments[0] as double), + ); + when(mockUtils.degrees(captureAny)).thenAnswer( + (realInvocation) => utilsMainInstance + .degrees(realInvocation.positionalArguments[0] as double), + ); + + final mockCanvasWrapper = MockCanvasWrapper(); + when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + final drawCircleResults = >[]; + when( + mockCanvasWrapper.drawCircle(captureAny, captureAny, captureAny), + ).thenAnswer((inv) { + drawCircleResults.add({ + 'offset': inv.positionalArguments[0] as Offset, + 'radius': inv.positionalArguments[1] as double, + 'paint_color': (inv.positionalArguments[2] as Paint).color, + }); + }); + + expect( + gaugePainter.handleTouch(const Offset(189, 217), viewSize, holder), + null, + ); + expect( + gaugePainter.handleTouch(const Offset(52, 71), viewSize, holder), + null, + ); + expect( + gaugePainter.handleTouch(const Offset(40, 184), viewSize, holder), + null, + ); + expect( + gaugePainter.handleTouch(const Offset(156, 133), viewSize, holder), + null, + ); + final expected = GaugeTouchedSpot( + const FlSpot(196.4392853163202, 41.3553437839966), + const Offset(196.4, 41.4), + ); + + const offsets = [Offset(195, 209), Offset(33, 61), Offset(170, 26)]; + for (final offset in offsets) { + final touch = gaugePainter.handleTouch(offset, viewSize, holder); + expect(touch != null, true); + expect(touch!.offset.dx, closeTo(expected.offset.dx, 0.1)); + expect(touch.offset.dy, closeTo(expected.offset.dy, 0.1)); + expect(touch.spot.x, closeTo(expected.spot.x, 0.0001)); + expect(touch.spot.y, closeTo(expected.spot.y, 0.0001)); + } + + Utils.changeInstance(utilsMainInstance); + }); + }); +} diff --git a/test/chart/gauge_chart/gauge_chart_painter_test.mocks.dart b/test/chart/gauge_chart/gauge_chart_painter_test.mocks.dart new file mode 100644 index 000000000..e0f813eba --- /dev/null +++ b/test/chart/gauge_chart/gauge_chart_painter_test.mocks.dart @@ -0,0 +1,1339 @@ +// Mocks generated by Mockito 5.4.2 from annotations +// in fl_chart/test/chart/gauge_chart/gauge_chart_painter_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:typed_data' as _i5; +import 'dart:ui' as _i2; + +import 'package:fl_chart/fl_chart.dart' as _i7; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; +import 'package:fl_chart/src/utils/utils.dart' as _i8; +import 'package:flutter/cupertino.dart' as _i3; +import 'package:flutter/foundation.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { + _FakeRect_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { + _FakeCanvas_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { + _FakeSize_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { + _FakeWidget_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_4 extends _i1.SmartFake + implements _i3.InheritedWidget { + _FakeInheritedWidget_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_5 extends _i1.SmartFake + implements _i3.DiagnosticsNode { + _FakeDiagnosticsNode_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({ + _i4.TextTreeConfiguration? parentConfiguration, + _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, + }) => + super.toString(); +} + +class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { + _FakeOffset_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { + _FakeBorderSide_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { + _FakeTextStyle_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod( + Invocation.method( + #save, + [], + ), + returnValueForMissingStub: null, + ); + @override + void saveLayer( + _i2.Rect? bounds, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #saveLayer, + [ + bounds, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void restore() => super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValueForMissingStub: null, + ); + @override + void restoreToCount(int? count) => super.noSuchMethod( + Invocation.method( + #restoreToCount, + [count], + ), + returnValueForMissingStub: null, + ); + @override + int getSaveCount() => (super.noSuchMethod( + Invocation.method( + #getSaveCount, + [], + ), + returnValue: 0, + ) as int); + @override + void translate( + double? dx, + double? dy, + ) => + super.noSuchMethod( + Invocation.method( + #translate, + [ + dx, + dy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void scale( + double? sx, [ + double? sy, + ]) => + super.noSuchMethod( + Invocation.method( + #scale, + [ + sx, + sy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void rotate(double? radians) => super.noSuchMethod( + Invocation.method( + #rotate, + [radians], + ), + returnValueForMissingStub: null, + ); + @override + void skew( + double? sx, + double? sy, + ) => + super.noSuchMethod( + Invocation.method( + #skew, + [ + sx, + sy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void transform(_i5.Float64List? matrix4) => super.noSuchMethod( + Invocation.method( + #transform, + [matrix4], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Float64List getTransform() => (super.noSuchMethod( + Invocation.method( + #getTransform, + [], + ), + returnValue: _i5.Float64List(0), + ) as _i5.Float64List); + @override + void clipRect( + _i2.Rect? rect, { + _i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipRect, + [rect], + { + #clipOp: clipOp, + #doAntiAlias: doAntiAlias, + }, + ), + returnValueForMissingStub: null, + ); + @override + void clipRRect( + _i2.RRect? rrect, { + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipRRect, + [rrect], + {#doAntiAlias: doAntiAlias}, + ), + returnValueForMissingStub: null, + ); + @override + void clipPath( + _i2.Path? path, { + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipPath, + [path], + {#doAntiAlias: doAntiAlias}, + ), + returnValueForMissingStub: null, + ); + @override + _i2.Rect getLocalClipBounds() => (super.noSuchMethod( + Invocation.method( + #getLocalClipBounds, + [], + ), + returnValue: _FakeRect_0( + this, + Invocation.method( + #getLocalClipBounds, + [], + ), + ), + ) as _i2.Rect); + @override + _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( + Invocation.method( + #getDestinationClipBounds, + [], + ), + returnValue: _FakeRect_0( + this, + Invocation.method( + #getDestinationClipBounds, + [], + ), + ), + ) as _i2.Rect); + @override + void drawColor( + _i2.Color? color, + _i2.BlendMode? blendMode, + ) => + super.noSuchMethod( + Invocation.method( + #drawColor, + [ + color, + blendMode, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawLine( + _i2.Offset? p1, + _i2.Offset? p2, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawLine, + [ + p1, + p2, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPaint(_i2.Paint? paint) => super.noSuchMethod( + Invocation.method( + #drawPaint, + [paint], + ), + returnValueForMissingStub: null, + ); + @override + void drawRect( + _i2.Rect? rect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRect, + [ + rect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRRect( + _i2.RRect? rrect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRRect, + [ + rrect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawDRRect( + _i2.RRect? outer, + _i2.RRect? inner, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawDRRect, + [ + outer, + inner, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawOval( + _i2.Rect? rect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawOval, + [ + rect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawCircle( + _i2.Offset? c, + double? radius, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawCircle, + [ + c, + radius, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawArc( + _i2.Rect? rect, + double? startAngle, + double? sweepAngle, + bool? useCenter, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawArc, + [ + rect, + startAngle, + sweepAngle, + useCenter, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPath( + _i2.Path? path, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawPath, + [ + path, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawImage( + _i2.Image? image, + _i2.Offset? offset, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImage, + [ + image, + offset, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawImageRect( + _i2.Image? image, + _i2.Rect? src, + _i2.Rect? dst, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImageRect, + [ + image, + src, + dst, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawImageNine( + _i2.Image? image, + _i2.Rect? center, + _i2.Rect? dst, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImageNine, + [ + image, + center, + dst, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPicture(_i2.Picture? picture) => super.noSuchMethod( + Invocation.method( + #drawPicture, + [picture], + ), + returnValueForMissingStub: null, + ); + @override + void drawParagraph( + _i2.Paragraph? paragraph, + _i2.Offset? offset, + ) => + super.noSuchMethod( + Invocation.method( + #drawParagraph, + [ + paragraph, + offset, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPoints( + _i2.PointMode? pointMode, + List<_i2.Offset>? points, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawPoints, + [ + pointMode, + points, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRawPoints( + _i2.PointMode? pointMode, + _i5.Float32List? points, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRawPoints, + [ + pointMode, + points, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawVertices( + _i2.Vertices? vertices, + _i2.BlendMode? blendMode, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawVertices, + [ + vertices, + blendMode, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawAtlas, + [ + atlas, + transforms, + rects, + colors, + blendMode, + cullRect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i5.Float32List? rstTransforms, + _i5.Float32List? rects, + _i5.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRawAtlas, + [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawShadow( + _i2.Path? path, + _i2.Color? color, + double? elevation, + bool? transparentOccluder, + ) => + super.noSuchMethod( + Invocation.method( + #drawShadow, + [ + path, + color, + elevation, + transparentOccluder, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [CanvasWrapper]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { + MockCanvasWrapper() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Canvas get canvas => (super.noSuchMethod( + Invocation.getter(#canvas), + returnValue: _FakeCanvas_1( + this, + Invocation.getter(#canvas), + ), + ) as _i2.Canvas); + @override + _i2.Size get size => (super.noSuchMethod( + Invocation.getter(#size), + returnValue: _FakeSize_2( + this, + Invocation.getter(#size), + ), + ) as _i2.Size); + @override + void drawRRect( + _i2.RRect? rrect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRRect, + [ + rrect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void save() => super.noSuchMethod( + Invocation.method( + #save, + [], + ), + returnValueForMissingStub: null, + ); + @override + void restore() => super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValueForMissingStub: null, + ); + @override + void clipRect( + _i2.Rect? rect, { + _i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipRect, + [rect], + { + #clipOp: clipOp, + #doAntiAlias: doAntiAlias, + }, + ), + returnValueForMissingStub: null, + ); + @override + void translate( + double? dx, + double? dy, + ) => + super.noSuchMethod( + Invocation.method( + #translate, + [ + dx, + dy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void rotate(double? radius) => super.noSuchMethod( + Invocation.method( + #rotate, + [radius], + ), + returnValueForMissingStub: null, + ); + @override + void drawPath( + _i2.Path? path, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawPath, + [ + path, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void saveLayer( + _i2.Rect? bounds, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #saveLayer, + [ + bounds, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPicture(_i2.Picture? picture) => super.noSuchMethod( + Invocation.method( + #drawPicture, + [picture], + ), + returnValueForMissingStub: null, + ); + @override + void drawImage( + _i2.Image? image, + _i2.Offset? offset, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImage, + [ + image, + offset, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void clipPath( + _i2.Path? path, { + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipPath, + [path], + {#doAntiAlias: doAntiAlias}, + ), + returnValueForMissingStub: null, + ); + @override + void drawRect( + _i2.Rect? rect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRect, + [ + rect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawLine( + _i2.Offset? p1, + _i2.Offset? p2, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawLine, + [ + p1, + p2, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawCircle( + _i2.Offset? center, + double? radius, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawCircle, + [ + center, + radius, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawArc( + _i2.Rect? rect, + double? startAngle, + double? sweepAngle, + bool? useCenter, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawArc, + [ + rect, + startAngle, + sweepAngle, + useCenter, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawText( + _i3.TextPainter? tp, + _i2.Offset? offset, [ + double? rotateAngle, + ]) => + super.noSuchMethod( + Invocation.method( + #drawText, + [ + tp, + offset, + rotateAngle, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawDot( + _i7.FlDotPainter? painter, + _i7.FlSpot? spot, + _i2.Offset? offset, + ) => + super.noSuchMethod( + Invocation.method( + #drawDot, + [ + painter, + spot, + offset, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRotated({ + required _i2.Size? size, + _i2.Offset? rotationOffset = _i2.Offset.zero, + _i2.Offset? drawOffset = _i2.Offset.zero, + required double? angle, + required _i6.DrawCallback? drawCallback, + }) => + super.noSuchMethod( + Invocation.method( + #drawRotated, + [], + { + #size: size, + #rotationOffset: rotationOffset, + #drawOffset: drawOffset, + #angle: angle, + #drawCallback: drawCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + void drawDashedLine( + _i2.Offset? from, + _i2.Offset? to, + _i2.Paint? painter, + List? dashArray, + ) => + super.noSuchMethod( + Invocation.method( + #drawDashedLine, + [ + from, + to, + painter, + dashArray, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i3.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Widget get widget => (super.noSuchMethod( + Invocation.getter(#widget), + returnValue: _FakeWidget_3( + this, + Invocation.getter(#widget), + ), + ) as _i3.Widget); + @override + bool get mounted => (super.noSuchMethod( + Invocation.getter(#mounted), + returnValue: false, + ) as bool); + @override + bool get debugDoingBuild => (super.noSuchMethod( + Invocation.getter(#debugDoingBuild), + returnValue: false, + ) as bool); + @override + _i3.InheritedWidget dependOnInheritedElement( + _i3.InheritedElement? ancestor, { + Object? aspect, + }) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + returnValue: _FakeInheritedWidget_4( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + ) as _i3.InheritedWidget); + @override + void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => + super.noSuchMethod( + Invocation.method( + #visitAncestorElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + @override + void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( + Invocation.method( + #visitChildElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + @override + void dispatchNotification(_i3.Notification? notification) => + super.noSuchMethod( + Invocation.method( + #dispatchNotification, + [notification], + ), + returnValueForMissingStub: null, + ); + @override + _i3.DiagnosticsNode describeElement( + String? name, { + _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_5( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + ) as _i3.DiagnosticsNode); + @override + _i3.DiagnosticsNode describeWidget( + String? name, { + _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_5( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + ) as _i3.DiagnosticsNode); + @override + List<_i3.DiagnosticsNode> describeMissingAncestor( + {required Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method( + #describeMissingAncestor, + [], + {#expectedAncestorType: expectedAncestorType}, + ), + returnValue: <_i3.DiagnosticsNode>[], + ) as List<_i3.DiagnosticsNode>); + @override + _i3.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod( + Invocation.method( + #describeOwnershipChain, + [name], + ), + returnValue: _FakeDiagnosticsNode_5( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), + ), + ) as _i3.DiagnosticsNode); +} + +/// A class which mocks [Utils]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUtils extends _i1.Mock implements _i8.Utils { + MockUtils() { + _i1.throwOnMissingStub(this); + } + + @override + double radians(double? degrees) => (super.noSuchMethod( + Invocation.method( + #radians, + [degrees], + ), + returnValue: 0.0, + ) as double); + @override + double degrees(double? radians) => (super.noSuchMethod( + Invocation.method( + #degrees, + [radians], + ), + returnValue: 0.0, + ) as double); + @override + _i2.Size getDefaultSize(_i2.Size? screenSize) => (super.noSuchMethod( + Invocation.method( + #getDefaultSize, + [screenSize], + ), + returnValue: _FakeSize_2( + this, + Invocation.method( + #getDefaultSize, + [screenSize], + ), + ), + ) as _i2.Size); + @override + double translateRotatedPosition( + double? size, + double? degree, + ) => + (super.noSuchMethod( + Invocation.method( + #translateRotatedPosition, + [ + size, + degree, + ], + ), + returnValue: 0.0, + ) as double); + @override + _i2.Offset calculateRotationOffset( + _i2.Size? size, + double? degree, + ) => + (super.noSuchMethod( + Invocation.method( + #calculateRotationOffset, + [ + size, + degree, + ], + ), + returnValue: _FakeOffset_6( + this, + Invocation.method( + #calculateRotationOffset, + [ + size, + degree, + ], + ), + ), + ) as _i2.Offset); + @override + _i3.BorderRadius? normalizeBorderRadius( + _i3.BorderRadius? borderRadius, + double? width, + ) => + (super.noSuchMethod(Invocation.method( + #normalizeBorderRadius, + [ + borderRadius, + width, + ], + )) as _i3.BorderRadius?); + @override + _i3.BorderSide normalizeBorderSide( + _i3.BorderSide? borderSide, + double? width, + ) => + (super.noSuchMethod( + Invocation.method( + #normalizeBorderSide, + [ + borderSide, + width, + ], + ), + returnValue: _FakeBorderSide_7( + this, + Invocation.method( + #normalizeBorderSide, + [ + borderSide, + width, + ], + ), + ), + ) as _i3.BorderSide); + @override + double getEfficientInterval( + double? axisViewSize, + double? diffInAxis, { + double? pixelPerInterval = 40.0, + }) => + (super.noSuchMethod( + Invocation.method( + #getEfficientInterval, + [ + axisViewSize, + diffInAxis, + ], + {#pixelPerInterval: pixelPerInterval}, + ), + returnValue: 0.0, + ) as double); + @override + double roundInterval(double? input) => (super.noSuchMethod( + Invocation.method( + #roundInterval, + [input], + ), + returnValue: 0.0, + ) as double); + @override + String formatNumber(double? number) => (super.noSuchMethod( + Invocation.method( + #formatNumber, + [number], + ), + returnValue: '', + ) as String); + @override + _i3.TextStyle getThemeAwareTextStyle( + _i3.BuildContext? context, + _i3.TextStyle? providedStyle, + ) => + (super.noSuchMethod( + Invocation.method( + #getThemeAwareTextStyle, + [ + context, + providedStyle, + ], + ), + returnValue: _FakeTextStyle_8( + this, + Invocation.method( + #getThemeAwareTextStyle, + [ + context, + providedStyle, + ], + ), + ), + ) as _i3.TextStyle); + @override + double getBestInitialIntervalValue( + double? min, + double? max, + double? interval, { + double? baseline = 0.0, + }) => + (super.noSuchMethod( + Invocation.method( + #getBestInitialIntervalValue, + [ + min, + max, + interval, + ], + {#baseline: baseline}, + ), + returnValue: 0.0, + ) as double); + @override + double convertRadiusToSigma(double? radius) => (super.noSuchMethod( + Invocation.method( + #convertRadiusToSigma, + [radius], + ), + returnValue: 0.0, + ) as double); +} diff --git a/test/chart/gauge_chart/gauge_chart_renderer_test.dart b/test/chart/gauge_chart/gauge_chart_renderer_test.dart new file mode 100644 index 000000000..eaa9f739c --- /dev/null +++ b/test/chart/gauge_chart/gauge_chart_renderer_test.dart @@ -0,0 +1,133 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_painter.dart'; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_renderer.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../data_pool.dart'; +import 'gauge_chart_renderer_test.mocks.dart'; + +@GenerateMocks([Canvas, PaintingContext, BuildContext, GaugeChartPainter]) +void main() { + group('GaugeChartRenderer', () { + final data = GaugeChartData( + startAngle: 0, + endAngle: 270, + strokeWidth: 5, + value: 0.7, + valueColor: const SimpleGaugeColor(color: MockData.color0), + backgroundColor: MockData.color6, + strokeCap: StrokeCap.round, + ticks: const GaugeTicks( + color: MockData.color0, + margin: 5, + showChangingColorTicks: false, + radius: 4, + count: 5, + position: GaugeTickPosition.inner, + ), + ); + + final targetData = GaugeChartData( + startAngle: 0, + endAngle: 180, + strokeWidth: 5, + value: 0.5, + valueColor: const SimpleGaugeColor(color: MockData.color1), + backgroundColor: MockData.color5, + strokeCap: StrokeCap.round, + ticks: const GaugeTicks( + color: MockData.color1, + margin: 10, + radius: 20, + count: 30, + position: GaugeTickPosition.center, + ), + ); + + const textScale = 4.0; + + final mockBuildContext = MockBuildContext(); + final renderGaugeChart = RenderGaugeChart( + mockBuildContext, + data, + targetData, + textScale, + ); + + final mockPainter = MockGaugeChartPainter(); + final mockPaintingContext = MockPaintingContext(); + final mockCanvas = MockCanvas(); + const mockSize = Size(44, 44); + when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); + renderGaugeChart + ..mockTestSize = mockSize + ..painter = mockPainter; + + test('test 1 correct data', () { + expect(renderGaugeChart.data == data, true); + expect(renderGaugeChart.data == targetData, false); + expect(renderGaugeChart.targetData == targetData, true); + expect(renderGaugeChart.textScale == textScale, true); + expect(renderGaugeChart.paintHolder.data == data, true); + expect(renderGaugeChart.paintHolder.targetData == targetData, true); + expect(renderGaugeChart.paintHolder.textScale == textScale, true); + }); + + test('test 2 check paint function', () { + renderGaugeChart.paint(mockPaintingContext, const Offset(10, 10)); + verify(mockCanvas.save()).called(1); + verify(mockCanvas.translate(10, 10)).called(1); + final result = verify(mockPainter.paint(any, captureAny, captureAny)); + expect(result.callCount, 1); + + final canvasWrapper = result.captured[0] as CanvasWrapper; + expect(canvasWrapper.size, const Size(44, 44)); + expect(canvasWrapper.canvas, mockCanvas); + + final paintHolder = result.captured[1] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + + verify(mockCanvas.restore()).called(1); + }); + + test('test 3 check getResponseAtLocation function', () { + final results = >[]; + when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'local_position': inv.positionalArguments[0] as Offset, + 'size': inv.positionalArguments[1] as Size, + 'paint_holder': inv.positionalArguments[2] as PaintHolder, + }); + return gaugeTouchedSpot1; + }); + final touchResponse = + renderGaugeChart.getResponseAtLocation(MockData.offset1); + expect(touchResponse.spot, gaugeTouchedSpot1); + expect(results[0]['local_position'] as Offset, MockData.offset1); + expect(results[0]['size'] as Size, mockSize); + final paintHolder = results[0]['paint_holder'] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + }); + + test('test 4 check setters', () { + renderGaugeChart + ..data = targetData + ..targetData = data + ..textScale = 22; + + expect(renderGaugeChart.data, targetData); + expect(renderGaugeChart.targetData, data); + expect(renderGaugeChart.textScale, 22); + }); + }); +} diff --git a/test/chart/gauge_chart/gauge_chart_renderer_test.mocks.dart b/test/chart/gauge_chart/gauge_chart_renderer_test.mocks.dart new file mode 100644 index 000000000..74a1127a6 --- /dev/null +++ b/test/chart/gauge_chart/gauge_chart_renderer_test.mocks.dart @@ -0,0 +1,1270 @@ +// Mocks generated by Mockito 5.4.2 from annotations +// in fl_chart/test/chart/gauge_chart/gauge_chart_renderer_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:typed_data' as _i7; +import 'dart:ui' as _i2; + +import 'package:fl_chart/fl_chart.dart' as _i13; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' + as _i12; +import 'package:fl_chart/src/chart/gauge_chart/gauge_chart_painter.dart' + as _i10; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/gestures.dart' as _i8; +import 'package:flutter/material.dart' as _i6; +import 'package:flutter/rendering.dart' as _i3; +import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:flutter/src/widgets/notification_listener.dart' as _i9; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { + _FakeRect_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { + _FakeCanvas_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaintingContext_2 extends _i1.SmartFake + implements _i3.PaintingContext { + _FakePaintingContext_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeColorFilterLayer_3 extends _i1.SmartFake + implements _i4.ColorFilterLayer { + _FakeColorFilterLayer_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { + _FakeOpacityLayer_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { + _FakeWidget_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_6 extends _i1.SmartFake + implements _i6.InheritedWidget { + _FakeInheritedWidget_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_7 extends _i1.SmartFake + implements _i5.DiagnosticsNode { + _FakeDiagnosticsNode_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({ + _i5.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, + }) => + super.toString(); +} + +class _FakeOffset_8 extends _i1.SmartFake implements _i2.Offset { + _FakeOffset_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod( + Invocation.method( + #save, + [], + ), + returnValueForMissingStub: null, + ); + @override + void saveLayer( + _i2.Rect? bounds, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #saveLayer, + [ + bounds, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void restore() => super.noSuchMethod( + Invocation.method( + #restore, + [], + ), + returnValueForMissingStub: null, + ); + @override + void restoreToCount(int? count) => super.noSuchMethod( + Invocation.method( + #restoreToCount, + [count], + ), + returnValueForMissingStub: null, + ); + @override + int getSaveCount() => (super.noSuchMethod( + Invocation.method( + #getSaveCount, + [], + ), + returnValue: 0, + ) as int); + @override + void translate( + double? dx, + double? dy, + ) => + super.noSuchMethod( + Invocation.method( + #translate, + [ + dx, + dy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void scale( + double? sx, [ + double? sy, + ]) => + super.noSuchMethod( + Invocation.method( + #scale, + [ + sx, + sy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void rotate(double? radians) => super.noSuchMethod( + Invocation.method( + #rotate, + [radians], + ), + returnValueForMissingStub: null, + ); + @override + void skew( + double? sx, + double? sy, + ) => + super.noSuchMethod( + Invocation.method( + #skew, + [ + sx, + sy, + ], + ), + returnValueForMissingStub: null, + ); + @override + void transform(_i7.Float64List? matrix4) => super.noSuchMethod( + Invocation.method( + #transform, + [matrix4], + ), + returnValueForMissingStub: null, + ); + @override + _i7.Float64List getTransform() => (super.noSuchMethod( + Invocation.method( + #getTransform, + [], + ), + returnValue: _i7.Float64List(0), + ) as _i7.Float64List); + @override + void clipRect( + _i2.Rect? rect, { + _i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipRect, + [rect], + { + #clipOp: clipOp, + #doAntiAlias: doAntiAlias, + }, + ), + returnValueForMissingStub: null, + ); + @override + void clipRRect( + _i2.RRect? rrect, { + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipRRect, + [rrect], + {#doAntiAlias: doAntiAlias}, + ), + returnValueForMissingStub: null, + ); + @override + void clipPath( + _i2.Path? path, { + bool? doAntiAlias = true, + }) => + super.noSuchMethod( + Invocation.method( + #clipPath, + [path], + {#doAntiAlias: doAntiAlias}, + ), + returnValueForMissingStub: null, + ); + @override + _i2.Rect getLocalClipBounds() => (super.noSuchMethod( + Invocation.method( + #getLocalClipBounds, + [], + ), + returnValue: _FakeRect_0( + this, + Invocation.method( + #getLocalClipBounds, + [], + ), + ), + ) as _i2.Rect); + @override + _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( + Invocation.method( + #getDestinationClipBounds, + [], + ), + returnValue: _FakeRect_0( + this, + Invocation.method( + #getDestinationClipBounds, + [], + ), + ), + ) as _i2.Rect); + @override + void drawColor( + _i2.Color? color, + _i2.BlendMode? blendMode, + ) => + super.noSuchMethod( + Invocation.method( + #drawColor, + [ + color, + blendMode, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawLine( + _i2.Offset? p1, + _i2.Offset? p2, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawLine, + [ + p1, + p2, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPaint(_i2.Paint? paint) => super.noSuchMethod( + Invocation.method( + #drawPaint, + [paint], + ), + returnValueForMissingStub: null, + ); + @override + void drawRect( + _i2.Rect? rect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRect, + [ + rect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRRect( + _i2.RRect? rrect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRRect, + [ + rrect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawDRRect( + _i2.RRect? outer, + _i2.RRect? inner, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawDRRect, + [ + outer, + inner, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawOval( + _i2.Rect? rect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawOval, + [ + rect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawCircle( + _i2.Offset? c, + double? radius, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawCircle, + [ + c, + radius, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawArc( + _i2.Rect? rect, + double? startAngle, + double? sweepAngle, + bool? useCenter, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawArc, + [ + rect, + startAngle, + sweepAngle, + useCenter, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPath( + _i2.Path? path, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawPath, + [ + path, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawImage( + _i2.Image? image, + _i2.Offset? offset, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImage, + [ + image, + offset, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawImageRect( + _i2.Image? image, + _i2.Rect? src, + _i2.Rect? dst, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImageRect, + [ + image, + src, + dst, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawImageNine( + _i2.Image? image, + _i2.Rect? center, + _i2.Rect? dst, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawImageNine, + [ + image, + center, + dst, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPicture(_i2.Picture? picture) => super.noSuchMethod( + Invocation.method( + #drawPicture, + [picture], + ), + returnValueForMissingStub: null, + ); + @override + void drawParagraph( + _i2.Paragraph? paragraph, + _i2.Offset? offset, + ) => + super.noSuchMethod( + Invocation.method( + #drawParagraph, + [ + paragraph, + offset, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawPoints( + _i2.PointMode? pointMode, + List<_i2.Offset>? points, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawPoints, + [ + pointMode, + points, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRawPoints( + _i2.PointMode? pointMode, + _i7.Float32List? points, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRawPoints, + [ + pointMode, + points, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawVertices( + _i2.Vertices? vertices, + _i2.BlendMode? blendMode, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawVertices, + [ + vertices, + blendMode, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawAtlas, + [ + atlas, + transforms, + rects, + colors, + blendMode, + cullRect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i7.Float32List? rstTransforms, + _i7.Float32List? rects, + _i7.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint, + ) => + super.noSuchMethod( + Invocation.method( + #drawRawAtlas, + [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawShadow( + _i2.Path? path, + _i2.Color? color, + double? elevation, + bool? transparentOccluder, + ) => + super.noSuchMethod( + Invocation.method( + #drawShadow, + [ + path, + color, + elevation, + transparentOccluder, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [PaintingContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { + MockPaintingContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Rect get estimatedBounds => (super.noSuchMethod( + Invocation.getter(#estimatedBounds), + returnValue: _FakeRect_0( + this, + Invocation.getter(#estimatedBounds), + ), + ) as _i2.Rect); + @override + _i2.Canvas get canvas => (super.noSuchMethod( + Invocation.getter(#canvas), + returnValue: _FakeCanvas_1( + this, + Invocation.getter(#canvas), + ), + ) as _i2.Canvas); + @override + void paintChild( + _i3.RenderObject? child, + _i2.Offset? offset, + ) => + super.noSuchMethod( + Invocation.method( + #paintChild, + [ + child, + offset, + ], + ), + returnValueForMissingStub: null, + ); + @override + void appendLayer(_i4.Layer? layer) => super.noSuchMethod( + Invocation.method( + #appendLayer, + [layer], + ), + returnValueForMissingStub: null, + ); + @override + _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => + (super.noSuchMethod( + Invocation.method( + #addCompositionCallback, + [callback], + ), + returnValue: () {}, + ) as _i2.VoidCallback); + @override + void stopRecordingIfNeeded() => super.noSuchMethod( + Invocation.method( + #stopRecordingIfNeeded, + [], + ), + returnValueForMissingStub: null, + ); + @override + void setIsComplexHint() => super.noSuchMethod( + Invocation.method( + #setIsComplexHint, + [], + ), + returnValueForMissingStub: null, + ); + @override + void setWillChangeHint() => super.noSuchMethod( + Invocation.method( + #setWillChangeHint, + [], + ), + returnValueForMissingStub: null, + ); + @override + void addLayer(_i4.Layer? layer) => super.noSuchMethod( + Invocation.method( + #addLayer, + [layer], + ), + returnValueForMissingStub: null, + ); + @override + void pushLayer( + _i4.ContainerLayer? childLayer, + _i3.PaintingContextCallback? painter, + _i2.Offset? offset, { + _i2.Rect? childPaintBounds, + }) => + super.noSuchMethod( + Invocation.method( + #pushLayer, + [ + childLayer, + painter, + offset, + ], + {#childPaintBounds: childPaintBounds}, + ), + returnValueForMissingStub: null, + ); + @override + _i3.PaintingContext createChildContext( + _i4.ContainerLayer? childLayer, + _i2.Rect? bounds, + ) => + (super.noSuchMethod( + Invocation.method( + #createChildContext, + [ + childLayer, + bounds, + ], + ), + returnValue: _FakePaintingContext_2( + this, + Invocation.method( + #createChildContext, + [ + childLayer, + bounds, + ], + ), + ), + ) as _i3.PaintingContext); + @override + _i4.ClipRectLayer? pushClipRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? clipRect, + _i3.PaintingContextCallback? painter, { + _i2.Clip? clipBehavior = _i2.Clip.hardEdge, + _i4.ClipRectLayer? oldLayer, + }) => + (super.noSuchMethod(Invocation.method( + #pushClipRect, + [ + needsCompositing, + offset, + clipRect, + painter, + ], + { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer, + }, + )) as _i4.ClipRectLayer?); + @override + _i4.ClipRRectLayer? pushClipRRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.RRect? clipRRect, + _i3.PaintingContextCallback? painter, { + _i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipRRectLayer? oldLayer, + }) => + (super.noSuchMethod(Invocation.method( + #pushClipRRect, + [ + needsCompositing, + offset, + bounds, + clipRRect, + painter, + ], + { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer, + }, + )) as _i4.ClipRRectLayer?); + @override + _i4.ClipPathLayer? pushClipPath( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.Path? clipPath, + _i3.PaintingContextCallback? painter, { + _i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipPathLayer? oldLayer, + }) => + (super.noSuchMethod(Invocation.method( + #pushClipPath, + [ + needsCompositing, + offset, + bounds, + clipPath, + painter, + ], + { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer, + }, + )) as _i4.ClipPathLayer?); + @override + _i4.ColorFilterLayer pushColorFilter( + _i2.Offset? offset, + _i2.ColorFilter? colorFilter, + _i3.PaintingContextCallback? painter, { + _i4.ColorFilterLayer? oldLayer, + }) => + (super.noSuchMethod( + Invocation.method( + #pushColorFilter, + [ + offset, + colorFilter, + painter, + ], + {#oldLayer: oldLayer}, + ), + returnValue: _FakeColorFilterLayer_3( + this, + Invocation.method( + #pushColorFilter, + [ + offset, + colorFilter, + painter, + ], + {#oldLayer: oldLayer}, + ), + ), + ) as _i4.ColorFilterLayer); + @override + _i4.TransformLayer? pushTransform( + bool? needsCompositing, + _i2.Offset? offset, + _i8.Matrix4? transform, + _i3.PaintingContextCallback? painter, { + _i4.TransformLayer? oldLayer, + }) => + (super.noSuchMethod(Invocation.method( + #pushTransform, + [ + needsCompositing, + offset, + transform, + painter, + ], + {#oldLayer: oldLayer}, + )) as _i4.TransformLayer?); + @override + _i4.OpacityLayer pushOpacity( + _i2.Offset? offset, + int? alpha, + _i3.PaintingContextCallback? painter, { + _i4.OpacityLayer? oldLayer, + }) => + (super.noSuchMethod( + Invocation.method( + #pushOpacity, + [ + offset, + alpha, + painter, + ], + {#oldLayer: oldLayer}, + ), + returnValue: _FakeOpacityLayer_4( + this, + Invocation.method( + #pushOpacity, + [ + offset, + alpha, + painter, + ], + {#oldLayer: oldLayer}, + ), + ), + ) as _i4.OpacityLayer); + @override + void clipPathAndPaint( + _i2.Path? path, + _i2.Clip? clipBehavior, + _i2.Rect? bounds, + _i2.VoidCallback? painter, + ) => + super.noSuchMethod( + Invocation.method( + #clipPathAndPaint, + [ + path, + clipBehavior, + bounds, + painter, + ], + ), + returnValueForMissingStub: null, + ); + @override + void clipRRectAndPaint( + _i2.RRect? rrect, + _i2.Clip? clipBehavior, + _i2.Rect? bounds, + _i2.VoidCallback? painter, + ) => + super.noSuchMethod( + Invocation.method( + #clipRRectAndPaint, + [ + rrect, + clipBehavior, + bounds, + painter, + ], + ), + returnValueForMissingStub: null, + ); + @override + void clipRectAndPaint( + _i2.Rect? rect, + _i2.Clip? clipBehavior, + _i2.Rect? bounds, + _i2.VoidCallback? painter, + ) => + super.noSuchMethod( + Invocation.method( + #clipRectAndPaint, + [ + rect, + clipBehavior, + bounds, + painter, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i6.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Widget get widget => (super.noSuchMethod( + Invocation.getter(#widget), + returnValue: _FakeWidget_5( + this, + Invocation.getter(#widget), + ), + ) as _i6.Widget); + @override + bool get mounted => (super.noSuchMethod( + Invocation.getter(#mounted), + returnValue: false, + ) as bool); + @override + bool get debugDoingBuild => (super.noSuchMethod( + Invocation.getter(#debugDoingBuild), + returnValue: false, + ) as bool); + @override + _i6.InheritedWidget dependOnInheritedElement( + _i6.InheritedElement? ancestor, { + Object? aspect, + }) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + returnValue: _FakeInheritedWidget_6( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + ) as _i6.InheritedWidget); + @override + void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => + super.noSuchMethod( + Invocation.method( + #visitAncestorElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + @override + void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( + Invocation.method( + #visitChildElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + @override + void dispatchNotification(_i9.Notification? notification) => + super.noSuchMethod( + Invocation.method( + #dispatchNotification, + [notification], + ), + returnValueForMissingStub: null, + ); + @override + _i5.DiagnosticsNode describeElement( + String? name, { + _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_7( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + ) as _i5.DiagnosticsNode); + @override + _i5.DiagnosticsNode describeWidget( + String? name, { + _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_7( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + ) as _i5.DiagnosticsNode); + @override + List<_i5.DiagnosticsNode> describeMissingAncestor( + {required Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method( + #describeMissingAncestor, + [], + {#expectedAncestorType: expectedAncestorType}, + ), + returnValue: <_i5.DiagnosticsNode>[], + ) as List<_i5.DiagnosticsNode>); + @override + _i5.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod( + Invocation.method( + #describeOwnershipChain, + [name], + ), + returnValue: _FakeDiagnosticsNode_7( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), + ), + ) as _i5.DiagnosticsNode); +} + +/// A class which mocks [GaugeChartPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGaugeChartPainter extends _i1.Mock implements _i10.GaugeChartPainter { + MockGaugeChartPainter() { + _i1.throwOnMissingStub(this); + } + + @override + void paint( + _i6.BuildContext? context, + _i11.CanvasWrapper? canvasWrapper, + _i12.PaintHolder<_i13.GaugeChartData>? holder, + ) => + super.noSuchMethod( + Invocation.method( + #paint, + [ + context, + canvasWrapper, + holder, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawTicks( + _i11.CanvasWrapper? canvasWrapper, + _i12.PaintHolder<_i13.GaugeChartData>? holder, + ) => + super.noSuchMethod( + Invocation.method( + #drawTicks, + [ + canvasWrapper, + holder, + ], + ), + returnValueForMissingStub: null, + ); + @override + void drawValue( + _i11.CanvasWrapper? canvasWrapper, + _i12.PaintHolder<_i13.GaugeChartData>? holder, + ) => + super.noSuchMethod( + Invocation.method( + #drawValue, + [ + canvasWrapper, + holder, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i13.GaugeTouchedSpot? handleTouch( + _i2.Offset? touchedPoint, + _i2.Size? viewSize, + _i12.PaintHolder<_i13.GaugeChartData>? holder, + ) => + (super.noSuchMethod(Invocation.method( + #handleTouch, + [ + touchedPoint, + viewSize, + holder, + ], + )) as _i13.GaugeTouchedSpot?); + @override + _i2.Offset center(_i2.Size? size) => (super.noSuchMethod( + Invocation.method( + #center, + [size], + ), + returnValue: _FakeOffset_8( + this, + Invocation.method( + #center, + [size], + ), + ), + ) as _i2.Offset); + @override + double gaugeRadius(_i2.Size? size) => (super.noSuchMethod( + Invocation.method( + #gaugeRadius, + [size], + ), + returnValue: 0.0, + ) as double); +} diff --git a/test/utils/utils_test.mocks.dart b/test/utils/utils_test.mocks.dart index fe4c97927..3aa8f0cad 100644 --- a/test/utils/utils_test.mocks.dart +++ b/test/utils/utils_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.0 from annotations +// Mocks generated by Mockito 5.4.2 from annotations // in fl_chart/test/utils/utils_test.dart. // Do not manually edit this file.