Skip to content

Commit

Permalink
Add custom distance function and order spots in LineTouchResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
joeldomke committed Feb 10, 2022
1 parent 10867bf commit 0b528c9
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857.
* **BUGFIX** Allow to show title when axis diff is zero.
* **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0).
* **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance.

## 0.41.0
* **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645.
Expand Down
34 changes: 32 additions & 2 deletions lib/src/chart/line_chart/line_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,9 @@ class LineTouchData extends FlTouchData<LineTouchResponse> with EquatableMixin {
/// Distance threshold to handle the touch event.
final double touchSpotThreshold;

/// Distance function used when finding closest points to touch point
final CalculateTouchDistance distanceCalculator;

/// Determines to handle default built-in touch responses,
/// [LineTouchResponse] shows a tooltip popup above the touched spot.
final bool handleBuiltInTouches;
Expand Down Expand Up @@ -1433,13 +1436,15 @@ class LineTouchData extends FlTouchData<LineTouchResponse> with EquatableMixin {
LineTouchTooltipData? touchTooltipData,
GetTouchedSpotIndicator? getTouchedSpotIndicator,
double? touchSpotThreshold,
CalculateTouchDistance? distanceCalculator,
bool? handleBuiltInTouches,
GetTouchLineY? getTouchLineStart,
GetTouchLineY? getTouchLineEnd,
}) : touchTooltipData = touchTooltipData ?? LineTouchTooltipData(),
getTouchedSpotIndicator =
getTouchedSpotIndicator ?? defaultTouchedIndicators,
touchSpotThreshold = touchSpotThreshold ?? 10,
distanceCalculator = distanceCalculator ?? xDistance,
handleBuiltInTouches = handleBuiltInTouches ?? true,
getTouchLineStart = getTouchLineStart ?? defaultGetTouchLineStart,
getTouchLineEnd = getTouchLineEnd ?? defaultGetTouchLineEnd,
Expand All @@ -1454,6 +1459,7 @@ class LineTouchData extends FlTouchData<LineTouchResponse> with EquatableMixin {
LineTouchTooltipData? touchTooltipData,
GetTouchedSpotIndicator? getTouchedSpotIndicator,
double? touchSpotThreshold,
CalculateTouchDistance? distanceCalculator,
GetTouchLineY? getTouchLineStart,
GetTouchLineY? getTouchLineEnd,
bool? handleBuiltInTouches,
Expand Down Expand Up @@ -1481,6 +1487,7 @@ class LineTouchData extends FlTouchData<LineTouchResponse> with EquatableMixin {
touchTooltipData,
getTouchedSpotIndicator,
touchSpotThreshold,
distanceCalculator,
handleBuiltInTouches,
getTouchLineStart,
getTouchLineEnd,
Expand All @@ -1500,6 +1507,15 @@ typedef GetTouchedSpotIndicator = List<TouchedSpotIndicatorData?> Function(
typedef GetTouchLineY = double Function(
LineChartBarData barData, int spotIndex);

/// Used to calculate the distance between coordinates of a touch event and a spot
typedef CalculateTouchDistance = double Function(
Offset touchPoint, Offset spotPixelCoordinates);

/// Default distanceCalculator only considers distance on x axis
double xDistance(Offset touchPoint, Offset spotPixelCoordinates) {
return ((touchPoint.dx - spotPixelCoordinates.dx)).abs();
}

/// Default presentation of touched indicators.
List<TouchedSpotIndicatorData> defaultTouchedIndicators(
LineChartBarData barData, List<int> indicators) {
Expand Down Expand Up @@ -1675,6 +1691,19 @@ class LineBarSpot extends FlSpot with EquatableMixin {
];
}

/// A [LineBarSpot] that holds information about the event that selected it
class TouchLineBarSpot extends LineBarSpot {
/// Distance in pixels from where the user taped
final double distance;

TouchLineBarSpot(
LineChartBarData bar,
int barIndex,
FlSpot spot,
this.distance,
) : super(bar, barIndex, spot);
}

/// Holds data of showing each row item in the tooltip popup.
class LineTooltipItem with EquatableMixin {
/// Showing text.
Expand Down Expand Up @@ -1760,16 +1789,17 @@ class ShowingTooltipIndicators with EquatableMixin {
class LineTouchResponse extends BaseTouchResponse {
/// touch happened on these spots
/// (if a single line provided on the chart, [lineBarSpots]'s length will be 1 always)
final List<LineBarSpot>? lineBarSpots;
final List<TouchLineBarSpot>? lineBarSpots;

/// If touch happens, [LineChart] processes it internally and
/// passes out a list of [lineBarSpots] it gives you information about the touched spot.
/// They are sorted based on their distance to the touch event
LineTouchResponse(this.lineBarSpots) : super();

/// Copies current [LineTouchResponse] to a new [LineTouchResponse],
/// and replaces provided values.
LineTouchResponse copyWith({
List<LineBarSpot>? lineBarSpots,
List<TouchLineBarSpot>? lineBarSpots,
}) {
return LineTouchResponse(
lineBarSpots ?? this.lineBarSpots,
Expand Down
32 changes: 20 additions & 12 deletions lib/src/chart/line_chart/line_chart_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1498,7 +1498,7 @@ class LineChartPainter extends AxisChartPainter<LineChartData> {
/// Processes [localPosition] and checks
/// the elements of the chart that are near the offset,
/// then makes a [LineTouchResponse] from the elements that has been touched.
List<LineBarSpot>? handleTouch(
List<TouchLineBarSpot>? handleTouch(
Offset localPosition,
Size size,
PaintHolder<LineChartData> holder,
Expand All @@ -1507,7 +1507,7 @@ class LineChartPainter extends AxisChartPainter<LineChartData> {

/// it holds list of nearest touched spots of each line
/// and we use it to draw touch stuff on them
final touchedSpots = <LineBarSpot>[];
final touchedSpots = <TouchLineBarSpot>[];

/// draw each line independently on the chart
for (var i = 0; i < data.lineBarsData.length; i++) {
Expand All @@ -1521,31 +1521,38 @@ class LineChartPainter extends AxisChartPainter<LineChartData> {
}
}

touchedSpots.sort((a, b) => a.distance.compareTo(b.distance));

return touchedSpots.isEmpty ? null : touchedSpots;
}

/// find the nearest spot base on the touched offset
@visibleForTesting
LineBarSpot? getNearestTouchedSpot(
Size viewSize,
Offset touchedPoint,
LineChartBarData barData,
int barDataPosition,
PaintHolder<LineChartData> holder) {
TouchLineBarSpot? getNearestTouchedSpot(
Size viewSize,
Offset touchedPoint,
LineChartBarData barData,
int barDataPosition,
PaintHolder<LineChartData> holder,
) {
final data = holder.data;
if (!barData.show) {
return null;
}

final chartViewSize = getChartUsableDrawSize(viewSize, holder);

/// Find the nearest spot (on X axis)
/// Find the nearest spot (based on distanceCalculator)
final sortedSpots = <FlSpot>[];
double? smallestDistance;
for (var spot in barData.spots) {
if (spot.isNull()) continue;
final distance =
(touchedPoint.dx - getPixelX(spot.x, chartViewSize, holder)).abs();
final distance = data.lineTouchData.distanceCalculator(
touchedPoint,
Offset(
getPixelX(spot.x, chartViewSize, holder),
getPixelY(spot.y, chartViewSize, holder),
));

if (distance <= data.lineTouchData.touchSpotThreshold) {
smallestDistance ??= distance;
Expand All @@ -1560,7 +1567,8 @@ class LineChartPainter extends AxisChartPainter<LineChartData> {
}

if (sortedSpots.isNotEmpty) {
return LineBarSpot(barData, barDataPosition, sortedSpots.first);
return TouchLineBarSpot(
barData, barDataPosition, sortedSpots.first, smallestDistance!);
} else {
return null;
}
Expand Down
22 changes: 12 additions & 10 deletions test/chart/data_pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -274,15 +274,17 @@ class MockData {
33,
);

static final lineBarSpot1 = LineBarSpot(
static final lineBarSpot1 = TouchLineBarSpot(
lineChartBarData1,
0,
lineChartBarData1.spots.first,
0,
);
static final lineBarSpot2 = LineBarSpot(
static final lineBarSpot2 = TouchLineBarSpot(
MockData.lineChartBarData1,
1,
MockData.lineChartBarData1.spots.last,
2,
);

static final lineTouchResponse1 =
Expand Down Expand Up @@ -1082,27 +1084,27 @@ final LineChartBarData lineChartBarData9 = LineChartBarData(
showingIndicators: [0, 1],
);

final LineBarSpot lineBarSpot1 = LineBarSpot(
final TouchLineBarSpot lineBarSpot1 = TouchLineBarSpot(
lineChartBarData1,
0,
flSpot1,
0,
);
final LineBarSpot lineBarSpot1Clone = LineBarSpot(
final TouchLineBarSpot lineBarSpot1Clone = TouchLineBarSpot(
lineChartBarData1Clone,
0,
flSpot1Clone,
0,
);

final LineBarSpot lineBarSpot2 = LineBarSpot(
lineChartBarData1,
2,
flSpot1,
);
final TouchLineBarSpot lineBarSpot2 =
TouchLineBarSpot(lineChartBarData1, 2, flSpot1, 2);

final LineBarSpot lineBarSpot3 = LineBarSpot(
final TouchLineBarSpot lineBarSpot3 = TouchLineBarSpot(
lineChartBarData1,
100,
flSpot1,
2,
);

final LineTouchResponse lineTouchResponse1 = LineTouchResponse(
Expand Down
Loading

0 comments on commit 0b528c9

Please sign in to comment.