From 076092eef6ad506642f9f25a73ceeb3fc5045cdf Mon Sep 17 00:00:00 2001 From: imaNNeoFighT Date: Sun, 26 Nov 2023 15:41:48 +0100 Subject: [PATCH] Temp commit --- example/lib/main.dart | 47 ++++--- .../base/axis_chart/axis_chart_data.dart | 20 +++ .../axis_chart_scaffold_widget.dart | 118 ++++++++++++++---- .../side_titles/side_titles_widget.dart | 32 +++-- lib/src/chart/line_chart/line_chart_data.dart | 4 + 5 files changed, 167 insertions(+), 54 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index a021e21bb..371927f9b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,8 @@ -import 'package:fl_chart_app/cubits/app/app_cubit.dart'; -import 'package:fl_chart_app/presentation/resources/app_resources.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'dart:math'; -import 'presentation/router/app_router.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:vector_math/vector_math.dart'; void main() { runApp(const MyApp()); @@ -15,23 +13,32 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider(create: (BuildContext context) => AppCubit()), - ], - child: MaterialApp.router( - title: AppTexts.appName, - theme: ThemeData( - brightness: Brightness.dark, - useMaterial3: true, - textTheme: GoogleFonts.assistantTextTheme( - Theme.of(context).textTheme.apply( - bodyColor: AppColors.mainTextColor3, + return MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + height: 300, + child: LineChart( + LineChartData( + lineBarsData: [ + LineChartBarData( + spots: List.generate( + 360 * 1, + (index) => FlSpot( + index.toDouble(), + sin(radians(index.toDouble())), + ), + ), + ), + ], + horizontalZoomConfig: const ZoomConfig( + enabled: true, + amount: 20, ), + ), + ), ), - scaffoldBackgroundColor: AppColors.pageBackground, ), - routerConfig: appRouterConfig, ), ); } diff --git a/lib/src/chart/base/axis_chart/axis_chart_data.dart b/lib/src/chart/base/axis_chart/axis_chart_data.dart index a5cddae35..0d46db7b6 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -29,6 +29,7 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { super.borderData, required super.touchData, ExtraLinesData? extraLinesData, + this.horizontalZoomConfig = const ZoomConfig(), }) : gridData = gridData ?? const FlGridData(), rangeAnnotations = rangeAnnotations ?? const RangeAnnotations(), baselineX = baselineX ?? 0, @@ -62,6 +63,8 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { /// Extra horizontal or vertical lines to draw on the chart. final ExtraLinesData extraLinesData; + final ZoomConfig horizontalZoomConfig; + /// Used for equality check, see [EquatableMixin]. @override List get props => [ @@ -79,6 +82,7 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { borderData, touchData, extraLinesData, + horizontalZoomConfig, ]; } @@ -1613,3 +1617,19 @@ class FlDotCrossPainter extends FlDotPainter { width, ]; } + +class ZoomConfig with EquatableMixin { + const ZoomConfig({ + this.enabled = false, + this.amount = 10, + }); + + final bool enabled; + final double amount; + + @override + List get props => [ + enabled, + amount, + ]; +} diff --git a/lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart b/lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart index d67421450..2bc7fbe81 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart @@ -19,70 +19,123 @@ import 'package:flutter/material.dart'; /// `left`, `top`, `right`, `bottom` are some place holders to show titles /// provided by [AxisChartData.titlesData] around the chart /// `chart` is a centered place holder to show a raw chart. -class AxisChartScaffoldWidget extends StatelessWidget { +class AxisChartScaffoldWidget extends StatefulWidget { const AxisChartScaffoldWidget({ super.key, required this.chart, required this.data, }); + final Widget chart; final AxisChartData data; + @override + State createState() => + _AxisChartScaffoldWidgetState(); +} + +class _AxisChartScaffoldWidgetState extends State { + late ScrollController scrollController; + + @override + void initState() { + scrollController = ScrollController(); + super.initState(); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + bool get showLeftTitles { - if (!data.titlesData.show) { + if (!widget.data.titlesData.show) { return false; } - final showAxisTitles = data.titlesData.leftTitles.showAxisTitles; - final showSideTitles = data.titlesData.leftTitles.showSideTitles; + final showAxisTitles = widget.data.titlesData.leftTitles.showAxisTitles; + final showSideTitles = widget.data.titlesData.leftTitles.showSideTitles; return showAxisTitles || showSideTitles; } bool get showRightTitles { - if (!data.titlesData.show) { + if (!widget.data.titlesData.show) { return false; } - final showAxisTitles = data.titlesData.rightTitles.showAxisTitles; - final showSideTitles = data.titlesData.rightTitles.showSideTitles; + final showAxisTitles = widget.data.titlesData.rightTitles.showAxisTitles; + final showSideTitles = widget.data.titlesData.rightTitles.showSideTitles; return showAxisTitles || showSideTitles; } bool get showTopTitles { - if (!data.titlesData.show) { + if (!widget.data.titlesData.show) { return false; } - final showAxisTitles = data.titlesData.topTitles.showAxisTitles; - final showSideTitles = data.titlesData.topTitles.showSideTitles; + final showAxisTitles = widget.data.titlesData.topTitles.showAxisTitles; + final showSideTitles = widget.data.titlesData.topTitles.showSideTitles; return showAxisTitles || showSideTitles; } bool get showBottomTitles { - if (!data.titlesData.show) { + if (!widget.data.titlesData.show) { return false; } - final showAxisTitles = data.titlesData.bottomTitles.showAxisTitles; - final showSideTitles = data.titlesData.bottomTitles.showSideTitles; + final showAxisTitles = widget.data.titlesData.bottomTitles.showAxisTitles; + final showSideTitles = widget.data.titlesData.bottomTitles.showSideTitles; return showAxisTitles || showSideTitles; } List stackWidgets(BoxConstraints constraints) { + final chartWidth = constraints.maxWidth - + widget.data.titlesData.allSidesPadding.horizontal; + + final xDelta = widget.data.maxX - widget.data.minX; + final largeChartWidth = xDelta * widget.data.horizontalZoomConfig.amount; + final widgets = [ Container( - margin: data.titlesData.allSidesPadding, + margin: widget.data.titlesData.allSidesPadding, decoration: BoxDecoration( - border: data.borderData.isVisible() ? data.borderData.border : null, + border: widget.data.borderData.isVisible() + ? widget.data.borderData.border + : null, ), - child: chart, + child: switch (widget.data.horizontalZoomConfig.enabled) { + true => SingleChildScrollView( + controller: scrollController, + scrollDirection: Axis.horizontal, + child: SizedBox( + width: largeChartWidth, + height: constraints.maxHeight, + child: widget.chart, + ), + ), + false => SizedBox( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: widget.chart, + ), + }, ), ]; int insertIndex(bool drawBelow) => drawBelow ? 0 : widgets.length; + double? axisMinXOverride; + double? axisMaxXOverride; + if (scrollController.hasClients) { + final xAmount = widget.data.horizontalZoomConfig.amount; + final showingXDelta = chartWidth / xAmount; + axisMinXOverride = scrollController.offset / xAmount; + axisMaxXOverride = axisMinXOverride + showingXDelta; + } + if (showLeftTitles) { widgets.insert( - insertIndex(data.titlesData.leftTitles.drawBelowEverything), + insertIndex(widget.data.titlesData.leftTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.left, - axisChartData: data, + axisChartData: widget.data, parentSize: constraints.biggest, ), ); @@ -90,21 +143,23 @@ class AxisChartScaffoldWidget extends StatelessWidget { if (showTopTitles) { widgets.insert( - insertIndex(data.titlesData.topTitles.drawBelowEverything), + insertIndex(widget.data.titlesData.topTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.top, - axisChartData: data, + axisChartData: widget.data, parentSize: constraints.biggest, + axisMinOverride: axisMinXOverride, + axisMaxOverride: axisMaxXOverride, ), ); } if (showRightTitles) { widgets.insert( - insertIndex(data.titlesData.rightTitles.drawBelowEverything), + insertIndex(widget.data.titlesData.rightTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.right, - axisChartData: data, + axisChartData: widget.data, parentSize: constraints.biggest, ), ); @@ -112,11 +167,13 @@ class AxisChartScaffoldWidget extends StatelessWidget { if (showBottomTitles) { widgets.insert( - insertIndex(data.titlesData.bottomTitles.drawBelowEverything), + insertIndex(widget.data.titlesData.bottomTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.bottom, - axisChartData: data, + axisChartData: widget.data, parentSize: constraints.biggest, + axisMinOverride: axisMinXOverride, + axisMaxOverride: axisMaxXOverride, ), ); } @@ -125,9 +182,16 @@ class AxisChartScaffoldWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - return Stack(children: stackWidgets(constraints)); + return ListenableBuilder( + listenable: scrollController, + builder: (context, child) { + return LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: stackWidgets(constraints), + ); + }, + ); }, ); } diff --git a/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart b/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart index aa3f58ea5..a1f2046ad 100644 --- a/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart +++ b/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart @@ -14,18 +14,23 @@ class SideTitlesWidget extends StatelessWidget { required this.side, required this.axisChartData, required this.parentSize, + this.axisMinOverride, + this.axisMaxOverride, }); + final AxisSide side; final AxisChartData axisChartData; final Size parentSize; + final double? axisMinOverride; + final double? axisMaxOverride; bool get isHorizontal => side == AxisSide.top || side == AxisSide.bottom; bool get isVertical => !isHorizontal; - double get minX => axisChartData.minX; + double get minX => axisMinOverride ?? axisChartData.minX; - double get maxX => axisChartData.maxX; + double get maxX => axisMaxOverride ?? axisChartData.maxX; double get baselineX => axisChartData.baselineX; @@ -110,6 +115,7 @@ class SideTitlesWidget extends StatelessWidget { double axisMin, double axisMax, AxisSide side, + ZoomConfig xAxisZoom, ) { List axisPositions; final interval = sideTitles.interval ?? @@ -151,9 +157,17 @@ class SideTitlesWidget extends StatelessWidget { } return axisPositions.map( (metaData) { - return AxisSideTitleWidgetHolder( - metaData, - sideTitles.getTitlesWidget( + final Widget widget; + final isOutOfHorizontalAxis = isHorizontal && + (metaData.axisValue < axisChartData.minX || + metaData.axisValue > axisChartData.maxX); + final isOutOfVerticalAxis = isVertical && + (metaData.axisValue < axisChartData.minY || + metaData.axisValue > axisChartData.maxY); + if (isOutOfHorizontalAxis || isOutOfVerticalAxis) { + widget = Container(); + } else { + widget = sideTitles.getTitlesWidget( metaData.axisValue, TitleMeta( min: axisMin, @@ -169,8 +183,9 @@ class SideTitlesWidget extends StatelessWidget { parentAxisSize: axisViewSize, axisPosition: metaData.axisPixelLocation, ), - ), - ); + ); + } + return AxisSideTitleWidgetHolder(metaData, widget); }, ).toList(); } @@ -195,6 +210,7 @@ class SideTitlesWidget extends StatelessWidget { ), if (sideTitles.showTitles) Container( + color: Colors.red, width: isHorizontal ? axisViewSize : sideTitles.reservedSize, height: isHorizontal ? sideTitles.reservedSize : axisViewSize, margin: thisSidePadding, @@ -210,6 +226,7 @@ class SideTitlesWidget extends StatelessWidget { axisMin, axisMax, side, + axisChartData.horizontalZoomConfig, ), ), ), @@ -231,6 +248,7 @@ class _AxisTitleWidget extends StatelessWidget { required this.side, required this.axisViewSize, }); + final AxisTitles axisTitles; final AxisSide side; final double axisViewSize; diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index 968785691..a3a7f09ca 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -59,6 +59,7 @@ class LineChartData extends AxisChartData with EquatableMixin { super.baselineY, super.clipData = const FlClipData.none(), super.backgroundColor, + super.horizontalZoomConfig, }) : super( touchData: lineTouchData, minX: @@ -137,6 +138,7 @@ class LineChartData extends AxisChartData with EquatableMixin { double? baselineY, FlClipData? clipData, Color? backgroundColor, + ZoomConfig? horizontalZoomConfig, }) { return LineChartData( lineBarsData: lineBarsData ?? this.lineBarsData, @@ -157,6 +159,7 @@ class LineChartData extends AxisChartData with EquatableMixin { baselineY: baselineY ?? this.baselineY, clipData: clipData ?? this.clipData, backgroundColor: backgroundColor ?? this.backgroundColor, + horizontalZoomConfig: horizontalZoomConfig ?? this.horizontalZoomConfig, ); } @@ -180,6 +183,7 @@ class LineChartData extends AxisChartData with EquatableMixin { baselineY, clipData, backgroundColor, + horizontalZoomConfig, ]; }