Skip to content

Commit

Permalink
Merge pull request #136 from krispypen/betweenbarsdata
Browse files Browse the repository at this point in the history
Added possibility to draw area between 2 lines in the line chart
  • Loading branch information
imaNNeo authored Dec 27, 2019
2 parents 8279231 + c53d0e5 commit 4e25bbd
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 6 deletions.
4 changes: 3 additions & 1 deletion example/lib/line_chart/line_chart_page2.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:example/line_chart/samples/line_chart_sample7.dart';
import 'package:flutter/material.dart';

import 'samples/line_chart_sample3.dart';
Expand All @@ -15,10 +16,11 @@ class LineChartPage2 extends StatelessWidget {
children: <Widget>[
Text(
'LineChart',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.black),
),
LineChartSample3(),
LineChartSample4(),
LineChartSample7(),
LineChartSample5(),
],
),
Expand Down
146 changes: 146 additions & 0 deletions example/lib/line_chart/samples/line_chart_sample7.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';

class LineChartSample7 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
width: 300,
height: 140,
child: LineChart(
LineChartData(
lineTouchData: const LineTouchData(enabled: false),
lineBarsData: [
LineChartBarData(
spots: const [
FlSpot(0, 4),
FlSpot(1, 3.5),
FlSpot(2, 4.5),
FlSpot(3, 1),
FlSpot(4, 4),
FlSpot(5, 6),
FlSpot(6, 6.5),
FlSpot(7, 6),
FlSpot(8, 4),
FlSpot(9, 6),
FlSpot(10, 6),
FlSpot(11, 7),
],
isCurved: true,
barWidth: 2,
colors: [
Colors.green,
],
dotData: const FlDotData(
show: false,
),
),
const LineChartBarData(
spots: [
FlSpot(0, 0),
FlSpot(1, 3),
FlSpot(2, 4),
FlSpot(3, 5),
FlSpot(4, 8),
FlSpot(5, 3),
FlSpot(6, 5),
FlSpot(7, 8),
FlSpot(8, 4),
FlSpot(9, 7),
FlSpot(10, 7),
FlSpot(11, 8),
],
isCurved: true,
barWidth: 2,
colors: [
Colors.black,
],
dotData: FlDotData(
show: false,
),
),
LineChartBarData(
spots: const [
FlSpot(0, 7),
FlSpot(1, 3),
FlSpot(2, 4),
FlSpot(3, 0),
FlSpot(4, 3),
FlSpot(5, 4),
FlSpot(6, 5),
FlSpot(7, 3),
FlSpot(8, 2),
FlSpot(9, 4),
FlSpot(10, 1),
FlSpot(11, 3),
],
isCurved: false,
barWidth: 2,
colors: [
Colors.red,
],
dotData: const FlDotData(
show: false,
),
),
],
betweenBarsData: [
BetweenBarsData(
fromIndex: 0,
toIndex: 2,
colors: [Colors.red.withOpacity(0.3)],
)
],
minY: 0,
titlesData: FlTitlesData(
bottomTitles: SideTitles(
showTitles: true,
textStyle:
TextStyle(fontSize: 10, color: Colors.purple, fontWeight: FontWeight.bold),
getTitles: (value) {
switch (value.toInt()) {
case 0:
return 'Jan';
case 1:
return 'Feb';
case 2:
return 'Mar';
case 3:
return 'Apr';
case 4:
return 'May';
case 5:
return 'Jun';
case 6:
return 'Jul';
case 7:
return 'Aug';
case 8:
return 'Sep';
case 9:
return 'Oct';
case 10:
return 'Nov';
case 11:
return 'Dec';
default:
return '';
}
}),
leftTitles: SideTitles(
showTitles: true,
getTitles: (value) {
return '\$ ${value + 0.5}';
},
),
),
gridData: FlGridData(
show: true,
checkToShowHorizontalLine: (double value) {
return value == 1 || value == 6 || value == 4 || value == 5;
}),
),
),
);
}
}
55 changes: 55 additions & 0 deletions lib/src/chart/line_chart/line_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import 'package:flutter/material.dart';
/// [showingTooltipIndicators] show the tooltip based on provided position(x), and list of [LineBarSpot]
class LineChartData extends AxisChartData {
final List<LineChartBarData> lineBarsData;
final List<BetweenBarsData> betweenBarsData;
final FlTitlesData titlesData;
final ExtraLinesData extraLinesData;
final LineTouchData lineTouchData;
final List<MapEntry<int, List<LineBarSpot>>> showingTooltipIndicators;

LineChartData({
this.lineBarsData = const [],
this.betweenBarsData = const [],
this.titlesData = const FlTitlesData(),
this.extraLinesData = const ExtraLinesData(),
this.lineTouchData = const LineTouchData(),
Expand Down Expand Up @@ -126,6 +128,7 @@ class LineChartData extends AxisChartData {
gridData: FlGridData.lerp(a.gridData, b.gridData, t),
titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t),
lineBarsData: lerpLineChartBarDataList(a.lineBarsData, b.lineBarsData, t),
betweenBarsData: lerpBetweenBarsDataList(a.betweenBarsData, b.betweenBarsData, t),
lineTouchData: b.lineTouchData,
showingTooltipIndicators: b.showingTooltipIndicators,
);
Expand All @@ -136,6 +139,7 @@ class LineChartData extends AxisChartData {

LineChartData copyWith({
List<LineChartBarData> lineBarsData,
List<BetweenBarsData> betweenBarsData,
FlTitlesData titlesData,
ExtraLinesData extraLinesData,
LineTouchData lineTouchData,
Expand All @@ -151,6 +155,7 @@ class LineChartData extends AxisChartData {
}) {
return LineChartData(
lineBarsData: lineBarsData ?? this.lineBarsData,
betweenBarsData: betweenBarsData ?? this.betweenBarsData,
titlesData: titlesData ?? this.titlesData,
extraLinesData: extraLinesData ?? this.extraLinesData,
lineTouchData: lineTouchData ?? this.lineTouchData,
Expand Down Expand Up @@ -363,6 +368,56 @@ class BarAreaData {
}
}

/***** BarAreaData *****/
/// This class holds data about draw on below or above space of the bar line,
class BetweenBarsData {

/// The index of the lineBarsData from where the area has to be rendered
final int fromIndex;

/// The index of the lineBarsData until where the area has to be rendered
final int toIndex;

/// if you pass just one color, the solid color will be used,
/// or if you pass more than one color, we use gradient mode to draw.
/// then the [gradientFrom], [gradientTo] and [gradientColorStops] is important,
final List<Color> colors;

/// if the gradient mode is enabled (if you have more than one color)
/// [gradientFrom] and [gradientTo] is important otherwise they will be skipped.
/// you can determine where the gradient should start and end,
/// values are available between 0 to 1,
/// Offset(0, 0) represent the top / left
/// Offset(1, 1) represent the bottom / right
final Offset gradientFrom;
final Offset gradientTo;

/// if more than one color provided gradientColorStops will hold
/// stop points of the gradient.
final List<double> gradientColorStops;


const BetweenBarsData({
@required this.fromIndex,
@required this.toIndex,
this.colors = const [Colors.blueGrey],
this.gradientFrom = const Offset(0, 0),
this.gradientTo = const Offset(1, 0),
this.gradientColorStops,
});

static BetweenBarsData lerp(BetweenBarsData a, BetweenBarsData b, double t) {
return BetweenBarsData(
fromIndex: b.fromIndex,
toIndex: b.toIndex,
gradientFrom: Offset.lerp(a.gradientFrom, b.gradientFrom, t),
gradientTo: Offset.lerp(a.gradientTo, b.gradientTo, t),
colors: lerpColorList(a.colors, b.colors, t),
gradientColorStops: lerpDoubleList(a.gradientColorStops, b.gradientColorStops, t),
);
}
}

typedef CheckToShowSpotLine = bool Function(FlSpot spot);

bool showAllSpotsBelowLine(FlSpot spot) {
Expand Down
75 changes: 71 additions & 4 deletions lib/src/chart/line_chart/line_chart_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ class LineChartPainter extends AxisChartPainter<LineChartData> with TouchHandler
canvas.saveLayer(Rect.fromLTWH(0, -40, size.width + 40, size.height + 40), Paint());
}

for(BetweenBarsData betweenBarsData in data.betweenBarsData) {
drawBetweenBarsArea(canvas, size, data, betweenBarsData);
}

/// draw each line independently on the chart
for (int i = 0; i < data.lineBarsData.length; i++) {
final barData = data.lineBarsData[i];
Expand Down Expand Up @@ -139,6 +143,18 @@ class LineChartPainter extends AxisChartPainter<LineChartData> with TouchHandler
_drawBar(canvas, viewSize, barPath, barData);
}

void drawBetweenBarsArea(Canvas canvas, Size viewSize, LineChartData data, BetweenBarsData betweenBarsData) {
final LineChartBarData fromBarData = data.lineBarsData[betweenBarsData.fromIndex];
final LineChartBarData toBarData = data.lineBarsData[betweenBarsData.toIndex];

final List<FlSpot> spots = [];
spots.addAll(toBarData.spots.reversed.toList());
final fromBarPath = _generateBarPath(viewSize, fromBarData);
final barPath = _generateBarPath(viewSize, toBarData.copyWith(spots: spots), appendToPath: fromBarPath);

_drawBetweenBar(canvas, viewSize, barPath, betweenBarsData);
}

void drawDots(Canvas canvas, Size viewSize, LineChartBarData barData) {
if (!barData.dotData.show) {
return;
Expand Down Expand Up @@ -200,17 +216,22 @@ class LineChartPainter extends AxisChartPainter<LineChartData> with TouchHandler
/// first one is the sharp corners line on spot connections
/// second one is curved corners line on spot connections,
/// and we use isCurved to find out how we should generate it,
Path _generateBarPath(Size viewSize, LineChartBarData barData) {
/// If you want to concatenate paths together for creating an area between
/// multiple bars for example, you can pass the appendToPath
Path _generateBarPath(Size viewSize, LineChartBarData barData, {Path appendToPath}) {
viewSize = getChartUsableDrawSize(viewSize);
final Path path = Path();
final Path path = appendToPath ?? Path();
final int size = barData.spots.length;
path.reset();

var temp = const Offset(0.0, 0.0);

final double x = getPixelX(barData.spots[0].x, viewSize);
final double y = getPixelY(barData.spots[0].y, viewSize);
path.moveTo(x, y);
if(appendToPath==null) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
for (int i = 1; i < size; i++) {
/// CurrentSpot
final current = Offset(
Expand Down Expand Up @@ -494,6 +515,52 @@ class LineChartPainter extends AxisChartPainter<LineChartData> with TouchHandler
}
}

void _drawBetweenBar(Canvas canvas, Size viewSize, Path aboveBarPath,
BetweenBarsData betweenBarsData) {
final chartViewSize = getChartUsableDrawSize(viewSize);

/// here we update the [betweenBarsData] to draw the solid color
/// or the gradient based on the [BetweenBarsData] class.
if (betweenBarsData.colors.length == 1) {
barAreaPaint.color = betweenBarsData.colors[0];
barAreaPaint.shader = null;
} else {
List<double> stops = [];
if (betweenBarsData.gradientColorStops == null ||
betweenBarsData.gradientColorStops.length != betweenBarsData.colors.length) {
/// provided gradientColorStops is invalid and we calculate it here
betweenBarsData.colors.asMap().forEach((index, color) {
final percent = 1.0 / betweenBarsData.colors.length;
stops.add(percent * (index + 1));
});
} else {
stops = betweenBarsData.gradientColorStops;
}

final from = betweenBarsData.gradientFrom;
final to = betweenBarsData.gradientTo;
barAreaPaint.shader = ui.Gradient.linear(
Offset(
getLeftOffsetDrawSize() + (chartViewSize.width * from.dx),
getTopOffsetDrawSize() + (chartViewSize.height * from.dy),
),
Offset(
getLeftOffsetDrawSize() + (chartViewSize.width * to.dx),
getTopOffsetDrawSize() + (chartViewSize.height * to.dy),
),
betweenBarsData.colors,
stops,
);
}

canvas.saveLayer(Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), Paint());
canvas.drawPath(aboveBarPath, barAreaPaint);

// clear the above area that get out of the bar line
canvas.restore();

}

/// draw the main bar line by the [barPath]
void _drawBar(Canvas canvas, Size viewSize, Path barPath, LineChartBarData barData) {
if (!barData.show) {
Expand Down
10 changes: 10 additions & 0 deletions lib/src/utils/lerp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ List<LineChartBarData> lerpLineChartBarDataList(List<LineChartBarData> a, List<L
}
}

List<BetweenBarsData> lerpBetweenBarsDataList(List<BetweenBarsData> a, List<BetweenBarsData> b, double t) {
if (a != null && b != null && a.length == b.length) {
return List.generate(a.length, (i) {
return BetweenBarsData.lerp(a[i], b[i], t);
});
} else {
return b;
}
}

List<BarChartGroupData> lerpBarChartGroupDataList(List<BarChartGroupData> a, List<BarChartGroupData> b, double t) {
if (a != null && b != null && a.length == b.length) {
return List.generate(a.length, (i) {
Expand Down
Loading

0 comments on commit 4e25bbd

Please sign in to comment.