diff --git a/lib/src/bezier_chart_widget.dart b/lib/src/bezier_chart_widget.dart index ba347fd..6d2f0d6 100644 --- a/lib/src/bezier_chart_widget.dart +++ b/lib/src/bezier_chart_widget.dart @@ -11,8 +11,8 @@ import 'package:intl/intl.dart' as intl; import 'my_single_child_scroll_view.dart'; typedef FooterValueBuilder = String Function(double value); -typedef FooterDateTimeBuilder = String Function( - DateTime value, BezierChartScale scaleType); +typedef ContentValueBuilder = String Function(double value); +typedef FooterDateTimeBuilder = String Function(DateTime value, BezierChartScale scaleType); class BezierChart extends StatefulWidget { ///Chart configuration @@ -78,6 +78,9 @@ class BezierChart extends StatefulWidget { ///Notify if the `BezierChartScale` changed, it only works with date scales. final ValueChanged onScaleChanged; + ///[Optional] This is used to display a custom value based on the current 'y' value inside the bubble + final ContentValueBuilder bubbleContentBuilder; + BezierChart({ Key key, this.config, @@ -93,51 +96,37 @@ class BezierChart extends StatefulWidget { this.onDateTimeSelected, this.onValueSelected, this.selectedValue, + this.bubbleContentBuilder, this.bezierChartAggregation = BezierChartAggregation.SUM, @required this.bezierChartScale, @required this.series, this.onScaleChanged, }) : assert( - (bezierChartScale == BezierChartScale.CUSTOM && - xAxisCustomValues != null && - series != null) || - bezierChartScale != BezierChartScale.CUSTOM, + (bezierChartScale == BezierChartScale.CUSTOM && xAxisCustomValues != null && series != null) || bezierChartScale != BezierChartScale.CUSTOM, "The xAxisCustomValues and series must not be null", ), assert( - bezierChartScale == BezierChartScale.CUSTOM && - _isSorted(xAxisCustomValues) || - bezierChartScale != BezierChartScale.CUSTOM, + bezierChartScale == BezierChartScale.CUSTOM && _isSorted(xAxisCustomValues) || bezierChartScale != BezierChartScale.CUSTOM, "The xAxisCustomValues must be sorted in increasing way", ), assert( - bezierChartScale == BezierChartScale.CUSTOM && - _compareLengths(xAxisCustomValues.length, series) || + bezierChartScale == BezierChartScale.CUSTOM && _compareLengths(xAxisCustomValues.length, series) || bezierChartScale != BezierChartScale.CUSTOM, "xAxisCustomValues lenght must be equals to series length", ), assert( - (bezierChartScale == BezierChartScale.CUSTOM && - _areAllPositive(xAxisCustomValues) && - _checkCustomValues(series)) || + (bezierChartScale == BezierChartScale.CUSTOM && _areAllPositive(xAxisCustomValues) && _checkCustomValues(series)) || bezierChartScale != BezierChartScale.CUSTOM, "xAxisCustomValues and series must be positives", ), assert( - (((bezierChartScale != BezierChartScale.CUSTOM) && - fromDate != null && - toDate != null) || - (bezierChartScale == BezierChartScale.CUSTOM && - fromDate == null && - toDate == null)), + (((bezierChartScale != BezierChartScale.CUSTOM) && fromDate != null && toDate != null) || + (bezierChartScale == BezierChartScale.CUSTOM && fromDate == null && toDate == null)), "fromDate and toDate must not be null", ), assert( - (((bezierChartScale != BezierChartScale.CUSTOM) && - toDate.isAfter(fromDate)) || - (bezierChartScale == BezierChartScale.CUSTOM && - fromDate == null && - toDate == null)), + (((bezierChartScale != BezierChartScale.CUSTOM) && toDate.isAfter(fromDate)) || + (bezierChartScale == BezierChartScale.CUSTOM && fromDate == null && toDate == null)), "toDate must be after of fromDate", ), super(key: key); @@ -147,8 +136,7 @@ class BezierChart extends StatefulWidget { } @visibleForTesting -class BezierChartState extends State - with SingleTickerProviderStateMixin { +class BezierChartState extends State with SingleTickerProviderStateMixin { AnimationController _animationController; ScrollController _scrollController; GlobalKey _keyScroll = GlobalKey(); @@ -200,8 +188,7 @@ class BezierChartState extends State ///Refresh the position of the vertical/bubble void _refreshPosition(details) { - if (_animationController.status == AnimationStatus.completed && - _displayIndicator) { + if (_animationController.status == AnimationStatus.completed && _displayIndicator) { return _updatePosition(details.globalPosition); } } @@ -213,9 +200,7 @@ class BezierChartState extends State if (position == null) return; return setState( () { - final fixedPosition = Offset( - position.dx + _scrollController.offset - horizontalPadding, - position.dy); + final fixedPosition = Offset(position.dx + _scrollController.offset - horizontalPadding, position.dy); _verticalIndicatorPosition = fixedPosition; }, ); @@ -275,8 +260,7 @@ class BezierChartState extends State final newValue = line.onMissingValue(newDate); if (!_tempYValues.contains(newValue)) _tempYValues.add(newValue); //if there is no missingvalue specified we should use 0 as minimum value to avoid overlap - } else if (widget.config.startYAxisFromNonZeroValue && - line.onMissingValue == null) { + } else if (widget.config.startYAxisFromNonZeroValue && line.onMissingValue == null) { if (!_tempYValues.contains(0)) _tempYValues.add(0); } } @@ -288,9 +272,7 @@ class BezierChartState extends State _tempYValues = []; final scale = _currentBezierChartScale; if (scale == BezierChartScale.CUSTOM) { - _xAxisDataPoints = widget.xAxisCustomValues - .map((val) => DataPoint(value: val, xAxis: val)) - .toList(); + _xAxisDataPoints = widget.xAxisCustomValues.map((val) => DataPoint(value: val, xAxis: val)).toList(); } else if (scale == BezierChartScale.HOURLY) { final hours = widget.toDate.difference(widget.fromDate).inHours; for (int i = 0; i < hours; i++) { @@ -299,17 +281,14 @@ class BezierChartState extends State hours: (i + 1), ), ); - final newDate = DateTime( - tempDate.year, tempDate.month, tempDate.day, tempDate.hour, 0); + final newDate = DateTime(tempDate.year, tempDate.month, tempDate.day, tempDate.hour, 0); _xAxisDataPoints.add( DataPoint(value: (i * 5).toDouble(), xAxis: newDate), ); _checkMissingValues(newDate); } } else if (scale == BezierChartScale.WEEKLY) { - final days = _convertToDateOnly(widget.toDate) - .difference(_convertToDateOnly(widget.fromDate)) - .inDays; + final days = _convertToDateOnly(widget.toDate).difference(_convertToDateOnly(widget.fromDate)).inDays; for (int i = 0; i <= days; i++) { final newDate = widget.fromDate.add( Duration( @@ -330,9 +309,7 @@ class BezierChartState extends State widget.toDate.year, widget.toDate.month, ); - for (int i = 0; - (startDate.isBefore(endDate) || areEqualDates(startDate, endDate)); - i++) { + for (int i = 0; (startDate.isBefore(endDate) || areEqualDates(startDate, endDate)); i++) { _xAxisDataPoints.add( DataPoint(value: (i * 5).toDouble(), xAxis: startDate), ); @@ -346,9 +323,7 @@ class BezierChartState extends State DateTime endDate = DateTime( widget.toDate.year, ); - for (int i = 0; - (startDate.isBefore(endDate) || areEqualDates(startDate, endDate)); - i++) { + for (int i = 0; (startDate.isBefore(endDate) || areEqualDates(startDate, endDate)); i++) { _xAxisDataPoints.add( DataPoint(value: (i * 5).toDouble(), xAxis: startDate), ); @@ -371,21 +346,17 @@ class BezierChartState extends State double _buildContentWidth(BoxConstraints constraints) { final scale = _currentBezierChartScale; if (scale == BezierChartScale.CUSTOM) { - return widget.config.contentWidth ?? - constraints.maxWidth - 2 * horizontalPadding; + return widget.config.contentWidth ?? constraints.maxWidth - 2 * horizontalPadding; } else { if (scale == BezierChartScale.HOURLY) { horizontalSpacing = constraints.maxWidth / 7; - return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - - horizontalPadding / 2; + return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - horizontalPadding / 2; } else if (scale == BezierChartScale.WEEKLY) { horizontalSpacing = constraints.maxWidth / 7; - return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - - horizontalPadding / 2; + return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - horizontalPadding / 2; } else if (scale == BezierChartScale.MONTHLY) { horizontalSpacing = constraints.maxWidth / 12; - return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - - horizontalPadding / 2; + return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - horizontalPadding / 2; } else if (scale == BezierChartScale.YEARLY) { if (_xAxisDataPoints.length > 12) { horizontalSpacing = constraints.maxWidth / 12; @@ -394,8 +365,7 @@ class BezierChartState extends State } else { horizontalSpacing = constraints.maxWidth / _xAxisDataPoints.length; } - return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - - horizontalPadding; + return _xAxisDataPoints.length * (horizontalSpacing * _currentScale) - horizontalPadding; } return 0.0; } @@ -405,14 +375,12 @@ class BezierChartState extends State _onLayoutDone(_) { _yAxisWidth = _keyLastYAxisItem.currentContext?.size?.width; //Move to selected position - if ((widget.selectedDate != null && - _currentBezierChartScale != BezierChartScale.CUSTOM) || - (widget.selectedValue != null && - _currentBezierChartScale == BezierChartScale.CUSTOM)) { + if ((widget.selectedDate != null && _currentBezierChartScale != BezierChartScale.CUSTOM) || + (widget.selectedValue != null && _currentBezierChartScale == BezierChartScale.CUSTOM)) { int index = -1; + if (_currentBezierChartScale == BezierChartScale.WEEKLY) { - index = _xAxisDataPoints.indexWhere( - (dp) => areEqualDates((dp.xAxis as DateTime), widget.selectedDate)); + index = _xAxisDataPoints.indexWhere((dp) => areEqualDates((dp.xAxis as DateTime), widget.selectedDate)); } else if (_currentBezierChartScale == BezierChartScale.HOURLY) { index = _xAxisDataPoints.indexWhere((dp) => (dp.xAxis as DateTime).year == widget.selectedDate.year && @@ -420,24 +388,60 @@ class BezierChartState extends State (dp.xAxis as DateTime).day == widget.selectedDate.day && (dp.xAxis as DateTime).hour == widget.selectedDate.hour); } else if (_currentBezierChartScale == BezierChartScale.MONTHLY) { - index = _xAxisDataPoints.indexWhere((dp) => - (dp.xAxis as DateTime).year == widget.selectedDate.year && - (dp.xAxis as DateTime).month == widget.selectedDate.month); + index = _xAxisDataPoints + .indexWhere((dp) => (dp.xAxis as DateTime).year == widget.selectedDate.year && (dp.xAxis as DateTime).month == widget.selectedDate.month); } else if (_currentBezierChartScale == BezierChartScale.YEARLY) { - index = _xAxisDataPoints.indexWhere( - (dp) => (dp.xAxis as DateTime).year == widget.selectedDate.year); + index = _xAxisDataPoints.indexWhere((dp) => (dp.xAxis as DateTime).year == widget.selectedDate.year); } else if (_currentBezierChartScale == BezierChartScale.CUSTOM) { - index = _xAxisDataPoints - .indexWhere((dp) => (dp.xAxis as double) == widget.selectedValue); + index = _xAxisDataPoints.indexWhere((dp) => (dp.xAxis as double) == widget.selectedValue); + } + + //If it's a valid index then scroll to the date selected based on the current position + if (index >= 0) { + Offset fixedPosition; + if (_currentBezierChartScale == BezierChartScale.CUSTOM) { + final space = (_contentWidth / _xAxisDataPoints.length); + fixedPosition = Offset(isOnlyOneAxis ? 0.0 : (index * space) + space / 2, 0.0); + _scrollController.jumpTo((index * space)); + setState( + () { + _verticalIndicatorPosition = fixedPosition; + _onDisplayIndicator( + LongPressMoveUpdateDetails( + globalPosition: fixedPosition, + offsetFromOrigin: fixedPosition, + ), + updatePosition: false, + ); + }, + ); + } else { + final jumpToX = (index * horizontalSpacing) - horizontalPadding / 2 - _keyScroll.currentContext.size.width / 2; + _scrollController.jumpTo(jumpToX); + + fixedPosition = Offset(isOnlyOneAxis ? 0.0 : (index * horizontalSpacing + 2 * horizontalPadding) - _scrollController.offset, 0.0); + _verticalIndicatorPosition = fixedPosition; + _onDisplayIndicator( + LongPressMoveUpdateDetails( + globalPosition: fixedPosition, + offsetFromOrigin: fixedPosition, + ), + updatePosition: true, + ); + } } + } else { + //Mi muovo all'ultimo item + int index = -1; + + index = _xAxisDataPoints.length - 1; //If it's a valid index then scroll to the date selected based on the current position if (index >= 0) { Offset fixedPosition; if (_currentBezierChartScale == BezierChartScale.CUSTOM) { final space = (_contentWidth / _xAxisDataPoints.length); - fixedPosition = - Offset(isOnlyOneAxis ? 0.0 : (index * space) + space / 2, 0.0); + fixedPosition = Offset(isOnlyOneAxis ? 0.0 : (index * space) + space / 2, 0.0); _scrollController.jumpTo((index * space)); setState( () { @@ -452,17 +456,10 @@ class BezierChartState extends State }, ); } else { - final jumpToX = (index * horizontalSpacing) - - horizontalPadding / 2 - - _keyScroll.currentContext.size.width / 2; + final jumpToX = (index * horizontalSpacing) - horizontalPadding / 2 - _keyScroll.currentContext.size.width / 2; _scrollController.jumpTo(jumpToX); - fixedPosition = Offset( - isOnlyOneAxis - ? 0.0 - : (index * horizontalSpacing + 2 * horizontalPadding) - - _scrollController.offset, - 0.0); + fixedPosition = Offset(isOnlyOneAxis ? 0.0 : (index * horizontalSpacing + 2 * horizontalPadding) - _scrollController.offset, 0.0); _verticalIndicatorPosition = fixedPosition; _onDisplayIndicator( LongPressMoveUpdateDetails( @@ -473,6 +470,7 @@ class BezierChartState extends State } } } + _checkIfNeedScroll(); if (_isScrollable) { setState(() {}); @@ -480,8 +478,7 @@ class BezierChartState extends State } _checkIfNeedScroll() { - if (_contentWidth > - _keyScroll.currentContext.size.width - horizontalPadding * 2) { + if (_contentWidth > _keyScroll.currentContext.size.width - horizontalPadding * 2) { _isScrollable = true; } } @@ -501,13 +498,11 @@ class BezierChartState extends State for (DataPoint dataPoint in line.data) { String key; if (_currentBezierChartScale == BezierChartScale.MONTHLY) { - key = - "${dataPoint.xAxis.year},${dataPoint.xAxis.month.toString().padLeft(2, '0')}"; + key = "${dataPoint.xAxis.year},${dataPoint.xAxis.month.toString().padLeft(2, '0')}"; } else if (_currentBezierChartScale == BezierChartScale.YEARLY) { key = "${dataPoint.xAxis.year}"; } else if (_currentBezierChartScale == BezierChartScale.WEEKLY) { - key = - "${dataPoint.xAxis.year},${dataPoint.xAxis.month.toString().padLeft(2, '0')},${dataPoint.xAxis.day.toString().padLeft(2, '0')}"; + key = "${dataPoint.xAxis.year},${dataPoint.xAxis.month.toString().padLeft(2, '0')},${dataPoint.xAxis.day.toString().padLeft(2, '0')}"; } else { key = "${dataPoint.xAxis.year},${dataPoint.xAxis.month.toString().padLeft(2, '0')},${dataPoint.xAxis.day.toString().padLeft(2, '0')},${dataPoint.xAxis.hour.toString().padLeft(2, '0')}"; @@ -522,29 +517,17 @@ class BezierChartState extends State Map valueMap = Map(); if (widget.bezierChartAggregation == BezierChartAggregation.SUM) { - valueMap = tmpMap.map((k, v) => MapEntry( - k, - v.reduce( - (c1, c2) => double.parse((c1 + c2).toStringAsFixed(2))))); - } else if (widget.bezierChartAggregation == - BezierChartAggregation.FIRST) { - valueMap = - tmpMap.map((k, v) => MapEntry(k, v.reduce((c1, c2) => c1))); - } else if (widget.bezierChartAggregation == - BezierChartAggregation.AVERAGE) { - valueMap = tmpMap.map( - (k, v) => MapEntry(k, v.reduce((c1, c2) => c1 + c2) / v.length)); - } else if (widget.bezierChartAggregation == - BezierChartAggregation.COUNT) { + valueMap = tmpMap.map((k, v) => MapEntry(k, v.reduce((c1, c2) => double.parse((c1 + c2).toStringAsFixed(2))))); + } else if (widget.bezierChartAggregation == BezierChartAggregation.FIRST) { + valueMap = tmpMap.map((k, v) => MapEntry(k, v.reduce((c1, c2) => c1))); + } else if (widget.bezierChartAggregation == BezierChartAggregation.AVERAGE) { + valueMap = tmpMap.map((k, v) => MapEntry(k, v.reduce((c1, c2) => c1 + c2) / v.length)); + } else if (widget.bezierChartAggregation == BezierChartAggregation.COUNT) { valueMap = tmpMap.map((k, v) => MapEntry(k, v.length.toDouble())); - } else if (widget.bezierChartAggregation == - BezierChartAggregation.MAX) { - valueMap = tmpMap.map( - (k, v) => MapEntry(k, v.reduce((c1, c2) => c1 > c2 ? c1 : c2))); - } else if (widget.bezierChartAggregation == - BezierChartAggregation.MIN) { - valueMap = tmpMap.map( - (k, v) => MapEntry(k, v.reduce((c1, c2) => c1 < c2 ? c1 : c2))); + } else if (widget.bezierChartAggregation == BezierChartAggregation.MAX) { + valueMap = tmpMap.map((k, v) => MapEntry(k, v.reduce((c1, c2) => c1 > c2 ? c1 : c2))); + } else if (widget.bezierChartAggregation == BezierChartAggregation.MIN) { + valueMap = tmpMap.map((k, v) => MapEntry(k, v.reduce((c1, c2) => c1 < c2 ? c1 : c2))); } List> newDataPoints = []; @@ -707,8 +690,7 @@ class BezierChartState extends State } void _notifyScaleChanged(BezierChartScale lastScale) { - if (widget.onScaleChanged != null && - lastScale != _currentBezierChartScale) { + if (widget.onScaleChanged != null && lastScale != _currentBezierChartScale) { widget.onScaleChanged(_currentBezierChartScale); } } @@ -791,9 +773,7 @@ class BezierChartState extends State //https://github.com/flutter/flutter/issues/13102 return Container( decoration: BoxDecoration( - color: widget.config.backgroundGradient != null - ? null - : widget.config.backgroundColor, + color: widget.config.backgroundGradient != null ? null : widget.config.backgroundColor, gradient: widget.config.backgroundGradient, ), alignment: Alignment.center, @@ -811,9 +791,7 @@ class BezierChartState extends State } }, child: GestureDetector( - onLongPressStart: widget.config.updatePositionOnTap - ? null - : (isPinchZoomActive ? null : _onDisplayIndicator), + onLongPressStart: widget.config.updatePositionOnTap ? null : (isPinchZoomActive ? null : _onDisplayIndicator), onLongPressMoveUpdate: isPinchZoomActive ? null : _refreshPosition, onScaleStart: (_) { _previousScale = _currentScale; @@ -824,12 +802,8 @@ class BezierChartState extends State !_displayIndicator ? (details) => _onPinchZoom(_previousScale * details.scale) : null, - onTap: widget.config.updatePositionOnTap - ? null - : (isPinchZoomActive ? null : _onHideIndicator), - onTapDown: widget.config.updatePositionOnTap - ? (isPinchZoomActive ? null : _refreshPosition) - : null, + onTap: widget.config.updatePositionOnTap ? null : (isPinchZoomActive ? null : _onHideIndicator), + onTapDown: widget.config.updatePositionOnTap ? (isPinchZoomActive ? null : _refreshPosition) : null, child: LayoutBuilder( builder: (context, constraints) { _contentWidth = _buildContentWidth(constraints); @@ -838,9 +812,7 @@ class BezierChartState extends State items.add( MySingleChildScrollView( controller: _scrollController, - physics: isPinchZoomActive || !_isScrollable - ? NeverScrollableScrollPhysics() - : widget.config.physics, + physics: isPinchZoomActive || !_isScrollable ? NeverScrollableScrollPhysics() : widget.config.physics, key: _keyScroll, scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: horizontalPadding), @@ -871,14 +843,12 @@ class BezierChartState extends State xAxisDataPoints: _xAxisDataPoints, onDataPointSnap: _onDataPointSnap, maxWidth: MediaQuery.of(context).size.width, - scrollOffset: _scrollController.hasClients - ? _scrollController.offset - : 0.0, + scrollOffset: _scrollController.hasClients ? _scrollController.offset : 0.0, footerValueBuilder: widget.footerValueBuilder, bubbleLabelValueBuilder: widget.bubbleLabelValueBuilder, footerDateTimeBuilder: widget.footerDateTimeBuilder, - bubbleLabelDateTimeBuilder: - widget.bubbleLabelDateTimeBuilder, + bubbleLabelDateTimeBuilder: widget.bubbleLabelDateTimeBuilder, + bubbleContentBuilder: widget.bubbleContentBuilder, onValueSelected: (val) { if (widget.onValueSelected != null) { if (_valueSelected == null) { @@ -919,44 +889,27 @@ class BezierChartState extends State bottom: 0, child: Container( width: _yAxisWidth + 10, - decoration: widget.config.backgroundGradient != null - ? BoxDecoration( - gradient: widget.config.backgroundGradient) - : null, - color: widget.config.backgroundGradient != null - ? null - : widget.config.backgroundColor, + decoration: widget.config.backgroundGradient != null ? BoxDecoration(gradient: widget.config.backgroundGradient) : null, + color: widget.config.backgroundGradient != null ? null : widget.config.backgroundColor, ), )); } final fontSize = widget.config.yAxisTextStyle?.fontSize ?? 8.0; - final maxValue = _yValues.last - - (widget.config.startYAxisFromNonZeroValue - ? _yValues.first - : 0.0); - final steps = widget.config.stepsYAxis != null && - widget.config.stepsYAxis > 0 - ? widget.config.stepsYAxis - : null; + final maxValue = _yValues.last - (widget.config.startYAxisFromNonZeroValue ? _yValues.first : 0.0); + final steps = widget.config.stepsYAxis != null && widget.config.stepsYAxis > 0 ? widget.config.stepsYAxis : null; _addYItem(double value, {Key key}) { items.add( Positioned( - bottom: _getRealValue( - value - - (widget.config.startYAxisFromNonZeroValue - ? _yValues.first - : 0.0), - maxHeight - widget.config.footerHeight, - maxValue) + + bottom: _getRealValue(value - (widget.config.startYAxisFromNonZeroValue ? _yValues.first : 0.0), + maxHeight - widget.config.footerHeight, maxValue) + widget.config.footerHeight + fontSize / 2, left: 10.0, child: Text( formatAsIntOrDouble(value), key: key, - style: widget.config.yAxisTextStyle ?? - TextStyle(color: Colors.white, fontSize: fontSize), + style: widget.config.yAxisTextStyle ?? TextStyle(color: Colors.white, fontSize: fontSize), ), ), ); @@ -964,21 +917,16 @@ class BezierChartState extends State if (steps != null) { final max = _yValues.last; - final min = widget.config.startYAxisFromNonZeroValue - ? _yValues.first.ceil() - : 0; + final min = widget.config.startYAxisFromNonZeroValue ? _yValues.first.ceil() : 0; for (int i = min; i < max + steps; i++) { if (i % steps == 0) { - bool isLast = - (i + steps) > max && (i + steps) >= (max + steps); - _addYItem(i.toDouble(), - key: isLast ? _keyLastYAxisItem : null); + bool isLast = (i + steps) > max && (i + steps) >= (max + steps); + _addYItem(i.toDouble(), key: isLast ? _keyLastYAxisItem : null); } } } else { for (double val in _yValues) { - _addYItem(val, - key: val == _yValues.last ? _keyLastYAxisItem : null); + _addYItem(val, key: val == _yValues.last ? _keyLastYAxisItem : null); } } } @@ -994,8 +942,7 @@ class BezierChartState extends State } ///return the real value of canvas -_getRealValue(double value, double maxConstraint, double maxValue) => - maxConstraint * value / (maxValue == 0 ? 1 : maxValue); +_getRealValue(double value, double maxConstraint, double maxValue) => maxConstraint * value / (maxValue == 0 ? 1 : maxValue); //BezierChart class _BezierChartPainter extends CustomPainter { @@ -1026,6 +973,8 @@ class _BezierChartPainter extends CustomPainter { final ValueChanged onDateTimeSelected; final bool shouldRepaintChart; + final ContentValueBuilder bubbleContentBuilder; + _BezierChartPainter({ this.shouldRepaintChart, this.config, @@ -1046,6 +995,7 @@ class _BezierChartPainter extends CustomPainter { this.minYValue, this.onDateTimeSelected, this.onValueSelected, + this.bubbleContentBuilder, }) : super(repaint: animation) { _maxValueY = _getMaxValueY(); _maxValueX = _getMaxValueX(); @@ -1077,6 +1027,11 @@ class _BezierChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { final height = size.height - config.footerHeight; Paint paintVerticalIndicator = Paint(); + + double infoWidth = 0; //base value, modified based on the label text + double infoHeight = 40; + + try { paintVerticalIndicator ..color = config.verticalIndicatorColor @@ -1176,8 +1131,7 @@ class _BezierChartPainter extends CustomPainter { if (line.onMissingValue != null) { isMissingValue = true; value = line.onMissingValue(xAxisDataPoints[i].xAxis as DateTime); - } else if (config.displayPreviousDataPointWhenNoValue && - previousValue != null) { + } else if (config.displayPreviousDataPointWhenNoValue && previousValue != null) { isMissingValue = true; value = previousValue; } @@ -1189,13 +1143,12 @@ class _BezierChartPainter extends CustomPainter { final double valueY = height - _getRealValue( axisY - (config.startYAxisFromNonZeroValue ? minYValue : 0.0), - height, + height - infoHeight, _maxValueY, ); if (config.displayLinesXAxis && series.length == 1) { - canvas.drawLine( - Offset(valueX, height), Offset(valueX, valueY), paintXLines); + canvas.drawLine(Offset(valueX, height), Offset(valueX, valueY), paintXLines); } if (lastPoint == null) { @@ -1204,8 +1157,7 @@ class _BezierChartPainter extends CustomPainter { } final double controlPointX = lastPoint.x + (valueX - lastPoint.x) / 2; - path.cubicTo( - controlPointX, lastPoint.y, controlPointX, valueY, valueX, valueY); + path.cubicTo(controlPointX, lastPoint.y, controlPointX, valueY, valueX, valueY); if (isMissingValue) { if (config.displayDataPointWhenNoValue) { dataPoints.add(Offset(valueX, valueY)); @@ -1214,9 +1166,7 @@ class _BezierChartPainter extends CustomPainter { dataPoints.add(Offset(valueX, valueY)); } - if (verticalIndicatorPosition != null && - verticalX >= lastPoint.x && - verticalX <= valueX) { + if (verticalIndicatorPosition != null && verticalX >= lastPoint.x && verticalX <= valueX) { //points to draw the info p0 = Offset(lastPoint.x, height - lastPoint.y); p1 = Offset(controlPointX, height - lastPoint.y); @@ -1244,8 +1194,7 @@ class _BezierChartPainter extends CustomPainter { } //if vertical indicator is in range then display the bubble info - if (verticalX >= valueX - (valueX - lastX) / 2 && - verticalX <= valueX + (nextX - valueX) / 2) { + if (verticalX >= valueX - (valueX - lastX) / 2 && verticalX <= valueX + (nextX - valueX) / 2) { _currentXDataPoint = xAxisDataPoints[i]; if (_currentCustomValues.length < series.length) { bool isDouble = (xAxisDataPoints[i].xAxis is double); @@ -1262,9 +1211,8 @@ class _BezierChartPainter extends CustomPainter { onDataPointSnap(xAxisDataPoints[i].value); _currentCustomValues.add( _CustomValue( - value: "${formatAsIntOrDouble(axisY)}", + value: bubbleContentBuilder != null ? bubbleContentBuilder(axisY) : "${formatAsIntOrDouble(axisY)}", label: line.label, - color: line.lineColor, ), ); } @@ -1281,8 +1229,7 @@ class _BezierChartPainter extends CustomPainter { textPainterXAxis.layout(); textPainterXAxis.paint( canvas, - Offset(valueX - textPainterXAxis.width / 2, - height + textPainterXAxis.height / 1.5), + Offset(valueX - textPainterXAxis.width / 2, height + textPainterXAxis.height / 1.5), ); } @@ -1340,17 +1287,14 @@ class _BezierChartPainter extends CustomPainter { (verticalX - p0.dx) / (p3.dx - p0.dx), ); - double infoWidth = 0; //base value, modified based on the label text - double infoHeight = 40; + //bubble indicator padding final horizontalPadding = 28.0; double offsetInfo = 42 + ((_currentCustomValues.length - 1.0) * 10.0); final centerForCircle = Offset(verticalX, height - yValue); - final center = config.verticalIndicatorFixedPosition - ? Offset(verticalX, offsetInfo) - : centerForCircle; + final center = config.verticalIndicatorFixedPosition ? Offset(verticalX, offsetInfo) : centerForCircle; if (config.showVerticalIndicator) { canvas.drawLine( @@ -1373,13 +1317,10 @@ class _BezierChartPainter extends CustomPainter { List textValues = []; List centerCircles = []; - double space = - 10 - ((infoHeight / (8.75)) * _currentCustomValues.length); - infoHeight = - infoHeight + (_currentCustomValues.length - 1) * (infoHeight / 3); + double space = 10 - ((infoHeight / (8.75)) * _currentCustomValues.length); + infoHeight = infoHeight + (_currentCustomValues.length - 1) * (infoHeight / 3); - for (_CustomValue customValue - in _currentCustomValues.reversed.toList()) { + for (_CustomValue customValue in _currentCustomValues.reversed.toList()) { textValues.add( TextSpan( text: config.bubbleIndicatorValueFormat != null @@ -1396,13 +1337,7 @@ class _BezierChartPainter extends CustomPainter { ); centerCircles.add( // Offset(center.dx - infoWidth / 2 + radiusDotIndicatorItems * 1.5, - Offset( - center.dx, - center.dy - - offsetInfo - - radiusDotIndicatorItems + - space + - (_currentCustomValues.length == 1 ? 1 : 0)), + Offset(center.dx, center.dy - offsetInfo - radiusDotIndicatorItems + space + (_currentCustomValues.length == 1 ? 1 : 0)), ); space += 12.5; } @@ -1419,20 +1354,16 @@ class _BezierChartPainter extends CustomPainter { ); textPainter.layout(); - infoWidth = - textPainter.width + radiusDotIndicatorItems * 2 + horizontalPadding; + infoWidth = textPainter.width + radiusDotIndicatorItems * 2 + horizontalPadding; ///Draw Bubble Indicator Info /// Draw shadow bubble info if (animation.isCompleted) { Path path = Path(); - path.moveTo(center.dx - infoWidth / 2 + 4, - center.dy - offsetInfo + infoHeight / 1.8); - path.lineTo(center.dx + infoWidth / 2 + 4, - center.dy - offsetInfo + infoHeight / 1.8); - path.lineTo(center.dx + infoWidth / 2 + 4, - center.dy - offsetInfo - infoHeight / 3); + path.moveTo(center.dx - infoWidth / 2 + 4, center.dy - offsetInfo + infoHeight / 1.8); + path.lineTo(center.dx + infoWidth / 2 + 4, center.dy - offsetInfo + infoHeight / 1.8); + path.lineTo(center.dx + infoWidth / 2 + 4, center.dy - offsetInfo - infoHeight / 3); //path.close(); // canvas.drawShadow(path, Colors.black, 20.0, false); canvas.drawPath(path, paintControlPoints..color = Colors.black12); @@ -1463,16 +1394,9 @@ class _BezierChartPainter extends CustomPainter { Path pathArrow = Path(); - pathArrow.moveTo(center.dx - triangleSize, - center.dy - offsetInfo * animation.value + infoHeight / 2.1); - pathArrow.lineTo( - center.dx, - center.dy - - offsetInfo * animation.value + - infoHeight / 2.1 + - triangleSize * 1.5); - pathArrow.lineTo(center.dx + triangleSize, - center.dy - offsetInfo * animation.value + infoHeight / 2.1); + pathArrow.moveTo(center.dx - triangleSize, center.dy - offsetInfo * animation.value + infoHeight / 2.1); + pathArrow.lineTo(center.dx, center.dy - offsetInfo * animation.value + infoHeight / 2.1 + triangleSize * 1.5); + pathArrow.lineTo(center.dx + triangleSize, center.dy - offsetInfo * animation.value + infoHeight / 2.1); pathArrow.close(); canvas.drawPath( pathArrow, @@ -1491,7 +1415,7 @@ class _BezierChartPainter extends CustomPainter { ); //draw circle indicators and text - for (int z = 0; z < _currentCustomValues.length; z++) { + /*for (int z = 0; z < _currentCustomValues.length; z++) { _CustomValue customValue = _currentCustomValues[z]; Offset centerIndicator = centerCircles.reversed.toList()[z]; Offset fixedCenter = Offset( @@ -1500,7 +1424,7 @@ class _BezierChartPainter extends CustomPainter { radiusDotIndicatorItems + 4, centerIndicator.dy); - canvas.drawCircle( + canvas.drawCircle( fixedCenter, radiusDotIndicatorItems, Paint() @@ -1513,7 +1437,7 @@ class _BezierChartPainter extends CustomPainter { ..color = Colors.black ..strokeWidth = 0.5 ..style = PaintingStyle.stroke); - } + } */ } } } @@ -1524,10 +1448,8 @@ class _BezierChartPainter extends CustomPainter { if (bubbleLabelValueBuilder != null && scale == BezierChartScale.CUSTOM) { return bubbleLabelValueBuilder(_currentXDataPoint.value); } - if (bubbleLabelDateTimeBuilder != null && - scale != BezierChartScale.CUSTOM) { - return bubbleLabelDateTimeBuilder( - _currentXDataPoint.xAxis as DateTime, scale); + if (bubbleLabelDateTimeBuilder != null && scale != BezierChartScale.CUSTOM) { + return bubbleLabelDateTimeBuilder(_currentXDataPoint.xAxis as DateTime, scale); } if (scale == BezierChartScale.CUSTOM) { return "${formatAsIntOrDouble(_currentXDataPoint.value)}\n"; @@ -1590,8 +1512,7 @@ class _BezierChartPainter extends CustomPainter { } else if (scale == BezierChartScale.MONTHLY) { final dateFormat = intl.DateFormat('MMM'); final dateFormatYear = intl.DateFormat('y'); - final year = - dateFormatYear.format(dataPoint.xAxis as DateTime).substring(2); + final year = dateFormatYear.format(dataPoint.xAxis as DateTime).substring(2); return "${dateFormat.format(dataPoint.xAxis as DateTime)}\n'$year"; } else if (scale == BezierChartScale.YEARLY) { final dateFormat = intl.DateFormat('y'); @@ -1618,15 +1539,11 @@ class _BezierChartPainter extends CustomPainter { //print("p0: $p0, p1: $p1, p2: $p2, p3: $p3 , t: $t"); - final y = pow(1 - t, 3) * y0 + - 3 * pow(1 - t, 2) * t * y1 + - 3 * (1 - t) * pow(t, 2) * y2 + - pow(t, 3) * y3; + final y = pow(1 - t, 3) * y0 + 3 * pow(1 - t, 2) * t * y1 + 3 * (1 - t) * pow(t, 2) * y2 + pow(t, 3) * y3; return y; } - Rect _fromCenter({Offset center, double width, double height}) => - Rect.fromLTRB( + Rect _fromCenter({Offset center, double width, double height}) => Rect.fromLTRB( center.dx - width / 2, center.dy - height / 2, center.dx + width / 2, @@ -1712,12 +1629,7 @@ class _CustomValue { } bool areEqualDates(DateTime dateTime1, DateTime dateTime2) => - dateTime1.year == dateTime2.year && - dateTime1.month == dateTime2.month && - dateTime1.day == dateTime2.day; + dateTime1.year == dateTime2.year && dateTime1.month == dateTime2.month && dateTime1.day == dateTime2.day; bool areEqualDatesIncludingHour(DateTime dateTime1, DateTime dateTime2) => - dateTime1.year == dateTime2.year && - dateTime1.month == dateTime2.month && - dateTime1.day == dateTime2.day && - dateTime1.hour == dateTime2.hour; + dateTime1.year == dateTime2.year && dateTime1.month == dateTime2.month && dateTime1.day == dateTime2.day && dateTime1.hour == dateTime2.hour;