Skip to content

Instantly share code, notes, and snippets.

@cirediew
Last active January 30, 2021 18:41
Show Gist options
  • Save cirediew/c22b2736028ef06b10b934d6c863664d to your computer and use it in GitHub Desktop.
Save cirediew/c22b2736028ef06b10b934d6c863664d to your computer and use it in GitHub Desktop.
import 'dart:math' show Rectangle;
import 'package:my_app/database/database.dart';
import 'package:my_app/util/chart/tooltip_data.dart';
import 'package:charts_flutter/flutter.dart'
show
CircleSymbolRenderer,
Color,
SelectionModel,
ChartCanvas,
FillPatternType;
import 'package:charts_flutter/src/text_element.dart'; // ignore: implementation_imports
import 'package:charts_flutter/src/text_style.dart'; // ignore: implementation_imports
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
class SingleBundleSymbolRenderer extends CircleSymbolRenderer
with EquatableMixin {
final Color textColor;
final Color backgroundColor;
final Color strokeColor;
final EnergyAssetCategory category;
final TooltipData tooltipData;
final SelectionModel<DateTime> model;
final int horizontalMargin;
final int verticalMargin;
final int bottomPadding;
final int textSize;
final double textScaleFactor;
final TextStyle textStyle;
SingleBundleSymbolRenderer({
@required this.model,
@required this.tooltipData,
@required this.textColor,
@required this.backgroundColor,
@required this.strokeColor,
this.category,
this.horizontalMargin = 4,
this.verticalMargin = 7,
this.bottomPadding = 4,
this.textSize = 14,
this.textScaleFactor = 1.0,
bool isSolid = true,
}) : textStyle = TextStyle()
..color = textColor
..fontSize = textSize,
super(isSolid: isSolid);
@override
bool shouldRepaint(SingleBundleSymbolRenderer oldRenderer) =>
this != oldRenderer;
@override
List<Object> get props => [
model,
tooltipData,
textColor,
backgroundColor,
strokeColor,
category,
horizontalMargin,
verticalMargin,
bottomPadding,
textSize,
textScaleFactor,
isSolid,
textStyle,
];
@override
void paint(
ChartCanvas canvas,
Rectangle<num> bounds, {
List<int> dashPattern,
Color fillColor,
FillPatternType fillPattern,
Color strokeColor,
double strokeWidthPx,
}) {
super.paint(
canvas,
bounds,
dashPattern: dashPattern,
fillColor: fillColor,
strokeColor: strokeColor,
strokeWidthPx: strokeWidthPx,
);
if (tooltipData.dateIsInFuture) {
return;
}
TextElement textElement = category != null
? TextElement(
'${tooltipData.formattedIndex}\n'
'${category.title}: ${tooltipData.formattedValue}',
style: textStyle,
textScaleFactor: tooltipData.textScaleFactor,
)
: TextElement(
'${tooltipData.formattedIndex}\n'
'${tooltipData.formattedValue}',
style: textStyle,
textScaleFactor: tooltipData.textScaleFactor,
);
final offsetX = bounds.left -
(textElement.measurement.horizontalSliceWidth *
(tooltipData.index /
((tooltipData.size - 1 == 0 ? 1 : tooltipData.size - 1))));
final offsetY = bounds.top -
textElement.measurement.verticalSliceWidth -
verticalMargin;
canvas.drawRect(
Rectangle(
offsetX - horizontalMargin,
offsetY - verticalMargin - bottomPadding,
textElement.measurement.horizontalSliceWidth + horizontalMargin * 2,
textElement.measurement.verticalSliceWidth + verticalMargin * 2,
),
fill: backgroundColor,
stroke: strokeColor,
strokeWidthPx: 1,
);
canvas.drawText(
textElement,
offsetX.toInt(),
offsetY.toInt() - bottomPadding,
);
}
}
import 'package:my_app/database/database.dart';
import 'package:my_app/util/extensions/iterable.ext.dart'; //used for firstOrNull
import 'package:my_app/util/number_formatter.dart';
import 'package:charts_flutter/flutter.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
class TooltipData with EquatableMixin {
num value = 0;
String formattedValue = '';
String formattedIndex = '';
int index = 0;
int size = 0;
int fractionDigits = 2;
bool dateIsInFuture = false;
int textSize = 14;
double textScaleFactor = 1.0;
NumberFormat format = NumberFormat.decimalPattern('en');
TooltipData({
this.value,
this.formattedValue,
this.formattedIndex,
this.index,
this.size,
this.fractionDigits,
});
@override
List get props => [
value,
formattedValue,
formattedIndex,
index,
size,
fractionDigits,
dateIsInFuture,
textSize,
textScaleFactor,
];
void updateFromModel(
BuildContext context, {
SelectionModel<DateTime> selectionModel,
EnergyAssetCategory category,
DateFormat dateFormat,
double textScaleFactor,
}) {
value = selectionModel.selectedSeries.firstOrNull?.measureFn(
selectionModel.selectedDatum.firstOrNull?.index,
);
format = decimalFormat(
decimalDigits: fractionDigits ?? category.fractionDigits,
);
formattedValue = '${format.format(value)} ${category.suffix}';
final date = selectionModel.selectedSeries.firstOrNull?.domainFn(
selectionModel.selectedDatum.firstOrNull?.index,
);
dateIsInFuture = date?.isAfter(DateTime.now()) ?? false;
formattedIndex = dateFormat.format(date);
index = selectionModel.selectedDatum.firstOrNull?.index;
size = selectionModel.selectedSeries.firstOrNull?.data?.length;
this.textScaleFactor = textScaleFactor;
}
@override
String toString() => 'TooltipData{'
'hash: $hashCode, '
'value: $value, '
'formattedValue: $formattedValue, '
'formattedIndex: $formattedIndex, '
'index: $index, '
'size: $size, '
'dateIsInFuture: $dateIsInFuture}';
}
import 'package:my_app/database/data_classes/bundle_chart_data.dart';
import 'package:my_app/database/database.dart';
import 'package:my_app/util/chart/chart_util.dart';
import 'package:my_app/util/chart/custom_date_time_factory.dart';
import 'package:my_app/util/chart/single_bundle_symbol_renderer.dart';
import 'package:my_app/util/chart/tooltip_data.dart';
import 'package:charts_common/common.dart' show SymbolRenderer;
import 'package:charts_flutter/flutter.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class YearChart extends StatefulWidget {
final EnergyAssetCategory category;
final List<Series<ChartDataPoint, DateTime>> seriesList;
const YearChart(this.seriesList, this.category);
@override
_YearChartState createState() => _YearChartStateState();
}
class _YearChartState extends State<YearChart> {
SelectionModel<DateTime> _selectionModel;
TooltipData _tooltipData;
MarginSpec _marginSpec;
@override
void initState() {
super.initState();
_tooltipData = TooltipData(fractionDigits: 0);
_marginSpec = MarginSpec.fromPercent(
minPercent: 5,
maxPercent: 50,
);
}
@override
Widget build(BuildContext context) {
final Color textColor =
ColorUtil.fromDartColor(Theme.of(context).colorScheme.onSurface);
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
if (_tooltipData.textScaleFactor != textScaleFactor) {
_tooltipData.textScaleFactor = textScaleFactor;
}
return TimeSeriesChart(
widget.seriesList,
layoutConfig: LayoutConfig(
leftMarginSpec: _marginSpec,
topMarginSpec: MarginSpec.fromPercent(
minPercent: 10,
maxPercent: 50,
),
rightMarginSpec: MarginSpec.defaultSpec,
bottomMarginSpec: _marginSpec,
),
defaultRenderer:
BarRendererConfig<DateTime>(groupingType: BarGroupingType.stacked),
dateTimeFactory:
CustomDateTimeFactory(Localizations.localeOf(context).languageCode),
primaryMeasureAxis: NumericAxisSpec(
showAxisLine: false,
renderSpec: SmallTickRendererSpec<num>(
labelStyle: TextStyleSpec(
color: textColor,
fontSize: (ChartUtil.defaultLabelSize * textScaleFactor).round()),
),
),
domainAxis: DateTimeAxisSpec(
renderSpec: SmallTickRendererSpec<DateTime>(
labelStyle: TextStyleSpec(
color: textColor,
fontSize: (ChartUtil.defaultLabelSize * textScaleFactor).round()),
),
tickFormatterSpec: const AutoDateTimeTickFormatterSpec(
minute: ChartUtil.emptyFormatterSpec,
hour: ChartUtil.emptyFormatterSpec,
day: ChartUtil.emptyFormatterSpec,
month: ChartUtil.monthFormatterSpec,
year: ChartUtil.yearFormatterSpec,
),
),
behaviors: [
SelectNearest(),
DomainHighlighter(),
CustomLinePointHighlighter(
symbolRenderer: SingleBundleSymbolRenderer(
model: _selectionModel,
tooltipData: _tooltipData,
textColor: textColor,
textScaleFactor: textScaleFactor,
backgroundColor: ColorUtil.fromDartColor(
Theme.of(context).canvasColor,
),
strokeColor: ColorUtil.fromDartColor(
widget.category.getColor(),
),
),
drawFollowLinesAcrossChart: true,
showVerticalFollowLine: LinePointHighlighterFollowLineType.none,
showHorizontalFollowLine: LinePointHighlighterFollowLineType.all,
defaultRadiusPx: 0,
),
],
selectionModels: [
SelectionModelConfig(
changedListener: (SelectionModel<DateTime> model) {
if (model != null && model.hasAnySelection) {
_tooltipData.updateFromModel(context,
selectionModel: model,
category: widget.category,
dateFormat: DateFormat.MMMM(),
textScaleFactor: textScaleFactor);
}
},
),
],
);
}
}
///Overridden class because [symbolRenderer] is missing from equals and hashcode in [LinePointHighlighter]
class CustomLinePointHighlighter extends LinePointHighlighter
with EquatableMixin {
CustomLinePointHighlighter({
SelectionModelType selectionModelType,
double defaultRadiusPx,
double radiusPaddingPx,
LinePointHighlighterFollowLineType showHorizontalFollowLine,
LinePointHighlighterFollowLineType showVerticalFollowLine,
List<int> dashPattern,
bool drawFollowLinesAcrossChart,
SymbolRenderer symbolRenderer,
}) : super(
selectionModelType: selectionModelType,
defaultRadiusPx: defaultRadiusPx,
radiusPaddingPx: radiusPaddingPx,
showHorizontalFollowLine: showHorizontalFollowLine,
showVerticalFollowLine: showVerticalFollowLine,
dashPattern: dashPattern,
drawFollowLinesAcrossChart: drawFollowLinesAcrossChart,
symbolRenderer: symbolRenderer,
);
@override
List<Object> get props => [
selectionModelType,
defaultRadiusPx,
radiusPaddingPx,
showHorizontalFollowLine,
showVerticalFollowLine,
dashPattern,
drawFollowLinesAcrossChart,
symbolRenderer,
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment