Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Horizontal Scroll #1507

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -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());
Expand All @@ -15,23 +13,32 @@ class MyApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AppCubit>(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,
),
);
}
Expand Down
20 changes: 20 additions & 0 deletions lib/src/chart/base/axis_chart/axis_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Object?> get props => [
Expand All @@ -79,6 +82,7 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin {
borderData,
touchData,
extraLinesData,
horizontalZoomConfig,
];
}

Expand Down Expand Up @@ -1639,3 +1643,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<Object?> get props => [
enabled,
amount,
];
}
118 changes: 91 additions & 27 deletions lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,104 +19,161 @@ 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<AxisChartScaffoldWidget> createState() =>
_AxisChartScaffoldWidgetState();
}

class _AxisChartScaffoldWidgetState extends State<AxisChartScaffoldWidget> {
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<Widget> 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 = <Widget>[
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,
),
);
}

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,
),
);
}

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,
),
);
}
Expand All @@ -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),
);
},
);
},
);
}
Expand Down
Loading
Loading