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

Bugfix/chart memory leak #1692

Merged
merged 4 commits into from
Jun 21, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## newVersion
* **BUGFIX** Fix a memory leak issue in the axis-based charts, there was a logic to calculate and cache the minX, maxX, minY and maxY properties to reduce the computation cost. But it caused some memory issues, as we don't have a quick solution for this, we disabled the caching logic for now, later we can move the calculation logic to the render objects to keep and update them only when the data is changed, #1106, #1693

## 0.68.0
* **Improvement** (by @imaNNeo) Update LineChartSample6 to implement a way to show a tooltip on a single spot, #1620
* **Feature** (by @herna) Add `titleSunbeamLayout` inside the [BarChartData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartdata) to allow the user to customize the layout of the title sunbeam
Expand Down
7 changes: 4 additions & 3 deletions lib/src/chart/bar_chart/bar_chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ class _BarChartState extends AnimatedWidgetBaseState<BarChart> {
BarChartData _getData() {
var newData = widget.data;
if (newData.minY.isNaN || newData.maxY.isNaN) {
final values = _barChartHelper.calculateMaxAxisValues(newData.barGroups);
final (minY, maxY) =
_barChartHelper.calculateMaxAxisValues(newData.barGroups);
newData = newData.copyWith(
minY: newData.minY.isNaN ? values.minY : newData.minY,
maxY: newData.maxY.isNaN ? values.maxY : newData.maxY,
minY: newData.minY.isNaN ? minY : newData.minY,
maxY: newData.maxY.isNaN ? maxY : newData.maxY,
);
}

Expand Down
49 changes: 4 additions & 45 deletions lib/src/chart/bar_chart/bar_chart_helper.dart
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
import 'dart:math';

import 'package:equatable/equatable.dart';
import 'package:fl_chart/src/chart/bar_chart/bar_chart_data.dart';
import 'package:fl_chart/src/utils/list_wrapper.dart';

/// Contains anything that helps BarChart works
class BarChartHelper {
/// Contains List of cached results, base on [List<BarChartGroupData>]
///
/// We use it to prevent redundant calculations
final Map<ListWrapper<BarChartGroupData>, BarChartMinMaxAxisValues>
_cachedResults = {};

/// Calculates minY, and maxY based on [barGroups],
/// returns cached values, to prevent redundant calculations.
BarChartMinMaxAxisValues calculateMaxAxisValues(
(double minY, double maxY) calculateMaxAxisValues(
List<BarChartGroupData> barGroups,
) {
if (barGroups.isEmpty) {
return BarChartMinMaxAxisValues(0, 0);
}

final listWrapper = barGroups.toWrapperClass();

if (_cachedResults.containsKey(listWrapper)) {
return _cachedResults[listWrapper]!.copyWith(readFromCache: true);
return (0, 0);
}

final BarChartGroupData barGroup;
try {
barGroup = barGroups.firstWhere((element) => element.barRods.isNotEmpty);
} catch (e) {
// There is no barChartGroupData with at least one barRod
return BarChartMinMaxAxisValues(0, 0);
return (0, 0);
}

var maxY = max(barGroup.barRods[0].fromY, barGroup.barRods[0].toY);
Expand All @@ -57,33 +43,6 @@ class BarChartHelper {
}
}
}

final result = BarChartMinMaxAxisValues(minY, maxY);
_cachedResults[listWrapper] = result;
return result;
}
}

/// Holds minY, and maxY for use in [BarChartData]
class BarChartMinMaxAxisValues with EquatableMixin {
BarChartMinMaxAxisValues(this.minY, this.maxY, {this.readFromCache = false});

final double minY;
final double maxY;
final bool readFromCache;

@override
List<Object?> get props => [minY, maxY, readFromCache];

BarChartMinMaxAxisValues copyWith({
double? minY,
double? maxY,
bool? readFromCache,
}) {
return BarChartMinMaxAxisValues(
minY ?? this.minY,
maxY ?? this.maxY,
readFromCache: readFromCache ?? this.readFromCache,
);
return (minY, maxY);
}
}
10 changes: 5 additions & 5 deletions lib/src/chart/line_chart/line_chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ class _LineChartState extends AnimatedWidgetBaseState<LineChart> {
newData.maxX.isNaN ||
newData.minY.isNaN ||
newData.maxY.isNaN) {
final values = _lineChartHelper.calculateMaxAxisValues(
final (minX, maxX, minY, maxY) = _lineChartHelper.calculateMaxAxisValues(
newData.lineBarsData,
);
newData = newData.copyWith(
minX: newData.minX.isNaN ? values.minX : newData.minX,
maxX: newData.maxX.isNaN ? values.maxX : newData.maxX,
minY: newData.minY.isNaN ? values.minY : newData.minY,
maxY: newData.maxY.isNaN ? values.maxY : newData.maxY,
minX: newData.minX.isNaN ? minX : newData.minX,
maxX: newData.maxX.isNaN ? maxX : newData.maxX,
minY: newData.minY.isNaN ? minY : newData.minY,
maxY: newData.maxY.isNaN ? maxY : newData.maxY,
);
}

Expand Down
63 changes: 7 additions & 56 deletions lib/src/chart/line_chart/line_chart_helper.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import 'package:equatable/equatable.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:fl_chart/src/utils/list_wrapper.dart';

/// Contains anything that helps LineChart works
class LineChartHelper {
/// Contains List of cached results, base on [List<LineChartBarData>]
///
/// We use it to prevent redundant calculations
final Map<ListWrapper<LineChartBarData>, LineChartMinMaxAxisValues>
_cachedResults = {};

LineChartMinMaxAxisValues calculateMaxAxisValues(
/// Calculates the [minX], [maxX], [minY], and [maxY] values of
/// the provided [lineBarsData].
(double minX, double maxX, double minY, double maxY) calculateMaxAxisValues(
List<LineChartBarData> lineBarsData,
) {
if (lineBarsData.isEmpty) {
return const LineChartMinMaxAxisValues(0, 0, 0, 0);
}

final listWrapper = lineBarsData.toWrapperClass();

if (_cachedResults.containsKey(listWrapper)) {
return _cachedResults[listWrapper]!.copyWith(readFromCache: true);
return (0, 0, 0, 0);
}

final LineChartBarData lineBarData;
Expand All @@ -29,7 +17,7 @@ class LineChartHelper {
lineBarsData.firstWhere((element) => element.spots.isNotEmpty);
} catch (e) {
// There is no lineBarData with at least one spot
return const LineChartMinMaxAxisValues(0, 0, 0, 0);
return (0, 0, 0, 0);
}

final FlSpot firstValidSpot;
Expand All @@ -38,7 +26,7 @@ class LineChartHelper {
lineBarData.spots.firstWhere((element) => element != FlSpot.nullSpot);
} catch (e) {
// There is no valid spot
return const LineChartMinMaxAxisValues(0, 0, 0, 0);
return (0, 0, 0, 0);
}

var minX = firstValidSpot.x;
Expand Down Expand Up @@ -68,43 +56,6 @@ class LineChartHelper {
}
}

final result = LineChartMinMaxAxisValues(minX, maxX, minY, maxY);
_cachedResults[listWrapper] = result;
return result;
}
}

/// Holds minX, maxX, minY, and maxY for use in [LineChartData]
class LineChartMinMaxAxisValues with EquatableMixin {
const LineChartMinMaxAxisValues(
this.minX,
this.maxX,
this.minY,
this.maxY, {
this.readFromCache = false,
});
final double minX;
final double maxX;
final double minY;
final double maxY;
final bool readFromCache;

@override
List<Object?> get props => [minX, maxX, minY, maxY, readFromCache];

LineChartMinMaxAxisValues copyWith({
double? minX,
double? maxX,
double? minY,
double? maxY,
bool? readFromCache,
}) {
return LineChartMinMaxAxisValues(
minX ?? this.minX,
maxX ?? this.maxX,
minY ?? this.minY,
maxY ?? this.maxY,
readFromCache: readFromCache ?? this.readFromCache,
);
return (minX, maxX, minY, maxY);
}
}
8 changes: 4 additions & 4 deletions lib/src/chart/scatter_chart/scatter_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ class ScatterChartData extends AxisChartData with EquatableMixin {
minX: minX ??
ScatterChartHelper.calculateMaxAxisValues(
scatterSpots ?? const [],
).minX,
).$1,
maxX: maxX ??
ScatterChartHelper.calculateMaxAxisValues(
scatterSpots ?? const [],
).maxX,
).$2,
minY: minY ??
ScatterChartHelper.calculateMaxAxisValues(
scatterSpots ?? const [],
).minY,
).$3,
maxY: maxY ??
ScatterChartHelper.calculateMaxAxisValues(
scatterSpots ?? const [],
).maxY,
).$4,
);
final List<ScatterSpot> scatterSpots;
final ScatterTouchData scatterTouchData;
Expand Down
63 changes: 8 additions & 55 deletions lib/src/chart/scatter_chart/scatter_chart_helper.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import 'package:equatable/equatable.dart';
import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart';
import 'package:fl_chart/src/utils/list_wrapper.dart';

/// Contains anything that helps ScatterChart works
class ScatterChartHelper {
/// Contains List of cached results, base on [List<ScatterSpot>]
///
/// We use it to prevent redundant calculations
static final Map<ListWrapper<ScatterSpot>, ScatterChartMinMaxAxisValues>
_cachedResults = {};

/// Calculates minX, maxX, minY, and maxY based on [scatterSpots],
/// returns cached values, to prevent redundant calculations.
static ScatterChartMinMaxAxisValues calculateMaxAxisValues(
static (
double minX,
double maxX,
double minY,
double maxY,
) calculateMaxAxisValues(
List<ScatterSpot> scatterSpots,
) {
if (scatterSpots.isEmpty) {
return ScatterChartMinMaxAxisValues(0, 0, 0, 0);
}

final listWrapper = scatterSpots.toWrapperClass();

if (_cachedResults.containsKey(listWrapper)) {
return _cachedResults[listWrapper]!.copyWith(readFromCache: true);
return (0, 0, 0, 0);
}

var minX = scatterSpots[0].x;
Expand All @@ -47,44 +38,6 @@ class ScatterChartHelper {
minY = spot.y;
}
}

final result = ScatterChartMinMaxAxisValues(minX, maxX, minY, maxY);
_cachedResults[listWrapper] = result;
return result;
}
}

/// Holds minX, maxX, minY, and maxY for use in [ScatterChartData]
class ScatterChartMinMaxAxisValues with EquatableMixin {
ScatterChartMinMaxAxisValues(
this.minX,
this.maxX,
this.minY,
this.maxY, {
this.readFromCache = false,
});
final double minX;
final double maxX;
final double minY;
final double maxY;
final bool readFromCache;

@override
List<Object?> get props => [minX, maxX, minY, maxY, readFromCache];

ScatterChartMinMaxAxisValues copyWith({
double? minX,
double? maxX,
double? minY,
double? maxY,
bool? readFromCache,
}) {
return ScatterChartMinMaxAxisValues(
minX ?? this.minX,
maxX ?? this.maxX,
minY ?? this.minY,
maxY ?? this.maxY,
readFromCache: readFromCache ?? this.readFromCache,
);
return (minX, maxX, minY, maxY);
}
}
21 changes: 0 additions & 21 deletions lib/src/utils/list_wrapper.dart

This file was deleted.

4 changes: 2 additions & 2 deletions repo_files/documentations/bar_chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ When you change the chart's state, it animates to the new state internally (usin
|barTouchData| [BarTouchData](#bartouchdata-read-about-touch-handling) holds the touch interactivity details|BarTouchData()|
|gridData| check the [FlGridData](base_chart.md#FlGridData)|FlGridData()|
|borderData| check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()|
|maxY| gets maximum y of y axis, if null, value will be read from the input barGroups | null|
|minY| gets minimum y of y axis, if null, value will be read from the input barGroups | null|
|maxY| gets maximum y of y axis, if null, value will be read from the input barGroups (But it is more performant if you provide them) | null|
|minY| gets minimum y of y axis, if null, value will be read from the input barGroups (But it is more performant if you provide them) | null|
|baselineY| defines the baseline of y-axis | 0|
|extraLinesData| allows extra horizontal lines to be drawn on the chart. Vertical lines are ignored when used with BarChartData, please see [#1149](https://github.com/imaNNeo/fl_chart/issues/1149), check [ExtraLinesData](base_chart.md#ExtraLinesData)|ExtraLinesData()|

Expand Down
8 changes: 4 additions & 4 deletions repo_files/documentations/line_chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ When you change the chart's state, it animates to the new state internally (usin
|showingTooltipIndicators| show the tooltip based on provided list of [LineBarSpot](#LineBarSpot), The point is that you need to disable touches to show these tooltips manually| [] |
|gridData| check the [FlGridData](base_chart.md#FlGridData)|FlGridData()|
|borderData| check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()|
|minX| gets minimum x of x axis, if null, value will read from the input lineBars |null|
|maxX| gets maximum x of x axis, if null, value will read from the input lineBars | null|
|minX| gets minimum x of x axis, if null, value will read from the input lineBars (But it is more performant if you provide them)|null|
|maxX| gets maximum x of x axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null|
|baselineX| defines the baseline of x-axis | 0|
|minY| gets minimum y of y axis, if null, value will read from the input lineBars | null|
|maxY| gets maximum y of y axis, if null, value will read from the input lineBars | null|
|minY| gets minimum y of y axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null|
|maxY| gets maximum y of y axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null|
|baselineY| defines the baseline of y-axis | 0|
|clipData| clip the chart to the border (prevent drawing outside the border) | FlClipData.none()|
|backgroundColor| a background color which is drawn behind th chart| null |
Expand Down
Loading