I am using the syncfusion_flutter_charts package to create a chart. I need to make a value check and display the column with the largest value in red. Tell me how to put a condition / loop correctly in order to check the values of the y-axis and recolor the larger value in red? I will be grateful for help.
chart
class ChartWidget extends StatefulWidget {
const ChartWidget({Key? key}) : super(key: key);
#override
State<ChartWidget> createState() => _ChartWidget();
}
class _ChartWidget extends State<ChartWidget> {
late List<_ChartData> data;
TooltipBehavior? _tooltipBehavior;
#override
void initState() {
data = [
_ChartData('6:00', 18),
_ChartData('7:00', 11),
_ChartData('8:00', 14),
_ChartData('9:00', 5),
_ChartData('10:00', 16),
_ChartData('11:00', 13),
_ChartData('12:00', 15),
_ChartData('13:00', 1),
_ChartData('14:00', 2),
_ChartData('15:00', 15),
_ChartData('16:00', 18),
_ChartData('17:00', 11),
_ChartData('18:00', 14),
_ChartData('19:00', 5),
_ChartData('20:00', 16),
_ChartData('21:00', 13),
_ChartData('22:00', 20),
_ChartData('23:00', 1),
_ChartData('24:00', 2),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return _buildColumnChart();
}
SfCartesianChart _buildColumnChart() {
return SfCartesianChart(
plotAreaBorderWidth: 0,
zoomPanBehavior: ZoomPanBehavior(enablePanning: true),
primaryXAxis: CategoryAxis(
interval: 3,
visibleMaximum: 16,
axisLine: const AxisLine(width: 0),
labelStyle: constants.Styles.xxTinyLtStdTextStyleWhite,
majorTickLines: const MajorTickLines(width: 0),
majorGridLines: const MajorGridLines(width: 0),
),
primaryYAxis:
NumericAxis(isVisible: false, minimum: 0, maximum: 20, interval: 1),
tooltipBehavior: _tooltipBehavior,
series: <ChartSeries<_ChartData, String>>[
ColumnSeries<_ChartData, String>(
dataSource: data,
color: constants.Colors.greyMiddle,
borderColor: constants.Colors.greyChart,
borderWidth: 1,
borderRadius: BorderRadius.circular(4),
xValueMapper: (_ChartData data, _) => data.x,
yValueMapper: (_ChartData data, _) => data.y,
name: 'Test'),
],
);
}
}
class _ChartData {
_ChartData(this.x, this.y);
final String x;
final double y;
}
This is the desired result
You can use pointColorMapper
find the max value
double maxValue = 0;
//loop list of _ChartData to compare its y value and find the max
data.forEach((data){
if(data.y>maxValue) {
maxValue = data.y;
}
});
Then instead of using color use pointColorMapper
//Instead of
color: constants.Colors.greyMiddle,
//use
pointColorMapper: (_ChartData data,_){
if(data.y == maxValue){
return constants.Colors.YourRedColor;
}
else{
return constants.Colors.greyMiddle;
}
},
This was the code I worked with, the final output being 'largest value is 20 and index: 16'
void main() {
var data = [
_ChartData('6:00', 18),
_ChartData('7:00', 11),
_ChartData('8:00', 14),
_ChartData('9:00', 5),
_ChartData('10:00', 16),
_ChartData('11:00', 13),
_ChartData('12:00', 15),
_ChartData('13:00', 1),
_ChartData('14:00', 2),
_ChartData('15:00', 15),
_ChartData('16:00', 18),
_ChartData('17:00', 11),
_ChartData('18:00', 14),
_ChartData('19:00', 5),
_ChartData('20:00', 16),
_ChartData('21:00', 13),
_ChartData('22:00', 20),
_ChartData('23:00', 1),
_ChartData('24:00', 2),
];
double largest_val = 0.0;
int largest_val_index = 0;
for(int k = 0; k < data.length; k++){
if(data[k].y >largest_val){
largest_val = data[k].y;
largest_val_index = k;
}
}
print('largest value is ${largest_val} and index: ${largest_val_index}');
}
class _ChartData {
_ChartData(this.x, this.y);
final String x;
final double y;
}
variable largest_val_index can be used to get the index in list data while largest_val gives the largest value.
void main() should be removed while execution.
To use a specific color to a point, you can make use of pointColorMapper property. Find the maximum y-value from your data source and based on that you can apply color to each point using the pointColorMapper. We have attached the code below
late double yMaximum = 0;
#override
void initState() {
data = [
//Your data
];
getMax(data);
super.initState();
}
void getMax(List<_ChartData> data) {
for (int i = 0; i < data.length; i++)
if (data[i].y > yMaximum) yMaximum = data[i].y;
}
SfCartesianChart(
//Other properties
series: <ChartSeries<_ChartData, String>>[
ColumnSeries<_ChartData, String>(
pointColorMapper: (_ChartData data, _) =>
data.y == yMaximum ? constants.Colors.YourRedColor : constants.Colors.greyMiddle,
),
],
)
Already we have a demo sample to apply the color based on the y-value using the pointColorMapper, which can be found below.
Demo
UG
Related
I have a list named activityList mapped to ActivityDataModel class and I have been using Provider flutter package to receive all values from firestore. I have also have been using a DatePicker package to select dates.
So, when I press on a date and the date is changed, I want to show the total activity data for the day.
In onDateChange of the DatePicker, I use forEach loop to cycle through the list items and extract each value and then compute the total value. ie. each list item has "coins" variable and then use "totalCoins" variable to store total number of coins, after cycling through each "coins" variable in the list item.
But the totalCoins or any total...variable does not seem to be updating for some reason. I used setState to update the values in the UI but that does not seem to work.
ActivityDataModel class
class ActivityDataModel {
int score, coins, time, calories;
String dateTime;
ActivityDataModel(
{required this.score,
required this.coins,
required this.calories,
required this.time,
required this.dateTime});
}
MainActivity.dart
class ActivityHome extends StatefulWidget {
const ActivityHome({Key? key}) : super(key: key);
static const routeName = "/activityhome";
#override
State<ActivityHome> createState() => _ActivityHomeState();
}
class _ActivityHomeState extends State<ActivityHome> {
DateTime selectedDate = DateTime.now();
int coins = 0, score = 0, calories = 0;
double time = 0.0;
int totalCoins = 0, totalScore = 0, totalCalories = 0;
double totalTime = 0.0;
#override
Widget build(BuildContext context) {
final user = Provider.of<UserModel?>(context, listen: true);
List<ActivityDataModel?>? activityList =
Provider.of<List<ActivityDataModel?>?>(context, listen: true);
// print(activityList?.elementAt(2)?.coins.toString());
return Scaffold(
appBar: AppBar(
iconTheme: const IconThemeData(
color: Colors.black, //change your color here
),
backgroundColor: Colors.white,
elevation: 0.0,
centerTitle: true,
title: Text(
"Activity",
style: TextStyle(
fontSize: 25.sp,
color: Colors.black,
fontWeight: FontWeight.bold),
),
),
body: Padding(
padding: EdgeInsets.all(20.0.r),
child: Column(
children: [
DatePicker(
DateTime(2022, 9, 9, 0, 0,
0), //DateTime.now(), // replace with Joining date
height: 100,
width: 80,
initialSelectedDate:
DateTime(2022, 9, 9, 0, 0, 0), //DateTime.now(),
selectionColor: Colors.blueAccent,
daysCount: 10,
onDateChange: (date) {
selectedDate = date;
DateFormat dateFormat = DateFormat("MM-dd-yyyy HH:mm:ss");
activityList?.forEach((activityData) {
DateTime activityDate =
dateFormat.parse(activityData?.dateTime ?? "0");
if (activityDate.day == selectedDate.day) {
print("true");
print(activityDate.day);
print(selectedDate.day);
coins = activityData!.coins;
calories = activityData.calories;
score = activityData.score;
time = (activityData.time / 60);
setState(() {
totalCoins += coins;
totalCalories += calories;
totalScore += score;
totalTime += time;
});
} else {
setState(() {
totalCoins = 0;
totalScore = 0;
totalCalories = 0;
totalTime = 0;
});
}
});
setState(() {});
print(totalCoins);
print(totalCalories);
print(totalScore);
print(totalTime);
},
),
VerticalSpace(10.h),
const Divider(
height: 1,
thickness: 0.3,
color: Colors.black,
),
VerticalSpace(10.h),
ActivityCardWidget(
coins: totalCoins.toString(),
time: totalTime.toStringAsFixed(1),
calories: totalCalories.toString(),
score: totalScore.toString(),
),
],
),
),
);
}
}
The correct o/p only shows for one date and the rest shows zero.
I used nominatim OpenStreetMap API to recieve coordinate of london city and try to draw polygon of london city, but cause the number of points is too alot when I use this code
try {
await state.getLocationPolygonByAddress(
state.foundedAddress[index].address);
var foundedSvg = state.foundedSvg;
List<LatLng> list = [];
setState(() {
testPolygon.points.clear();
});
for (int i = 0; i < foundedSvg.length; i++) {
for (int j = 0; j <
foundedSvg[i].geojson.coordinates
.length; j++) {
for (int k =0;k<foundedSvg[i].geojson.coordinates[j].length;k++)
{
for (int z = 0; z <
foundedSvg[i].geojson.coordinates[j][k]
.length; z++) {
/* if(foundedSvg[i].geojson
.coordinates[j][k].first is double &&
foundedSvg[i].geojson
.coordinates[j][k].last is double) {*/
list.add(LatLng(foundedSvg[i].geojson
.coordinates[j][k].first,
foundedSvg[i].geojson
.coordinates[j][k].last));
/* }
else{
print(k);
}*/
}
}
}
}
print(list.length);
state.clearfoundedAddress();
setState(() {
testPolygon.points.addAll(list);
});
print('polyeditor length: ${polyEditor.points.length}');
}catch(e){
print('error :'+e.toString());
}
and when try to add list of points to polygon in this line
testPolygon.points.addAll(list);
app crashed .
but it work good If the number of points is small.
this is my FlutterMap layer
late PolyEditor polyEditor;
List<Polygon> polygons = [];
// LatLngBounds boundingBox = LatLngBounds();
var testPolygon = Polygon(
color: Colors.black.withOpacity(0.3),
points: [],
borderColor: Colors.black,
isFilled: true,
borderStrokeWidth: 1.0);
#override
void initState() {
super.initState();
// processData();
polyEditor = PolyEditor(
addClosePathMarker: true,
points: testPolygon.points,
pointIcon: const Icon(
Icons.lens,
size: 10,
color: Colors.black,
),
intermediateIcon: const Icon(Icons.lens, size: 10, color:
Colors.black),
callbackRefresh: () => {setState(() {})},
);
polygons.add(testPolygon);
}
SearchController searchController = Get.put(SearchController());
MapController controller = MapController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: [
Center(
child: FlutterMap(
mapController: controller,
options: MapOptions(
// bounds: LatLngBounds(),
allowPanningOnScrollingParent: false,
onTap: (_, ll) {
print(ll);
polyEditor.add(testPolygon.points, ll);
},
plugins: [
DragMarkerPlugin(),
],
center: LatLng(32.5231, 51.6765),
zoom: 4.0,
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c']),
PolygonLayerOptions(polygons: polygons,polygonCulling:
true),
DragMarkerPluginOptions(markers: polyEditor.edit(),),
],
),
),
the result should be like this
result picture
I want a result like this link
Yes this problem solved by deleting import ' package:flutter_map_line_editor/polyeditor.dart'; and delete this lines
DragMarkerPluginOptions(markers: polyEditor.edit(),),
polyEditor = PolyEditor(
addClosePathMarker: true,
points: testPolygon.points,
pointIcon: const Icon(
Icons.lens,
size: 10,
color: Colors.black,
),
intermediateIcon: const Icon(Icons.lens, size: 10, color: Colors.black),
callbackRefresh: () => {setState(() {})},
);`
in code
#lan say correct answer
This is SfCartesianChart and I want to make it dynamic as per the rest API data given below but when I put the dynamic data to it, the graph shows only null in the legend text and no data shown but I've posted the required data. Kindly help me it's right or not.
SfCartesianChart _getSpacingColumnChart() {
return SfCartesianChart(
// borderColor: Colors.red,
// borderWidth: 2,
// Sets 15 logical pixels as margin for all the 4 sides.
margin: EdgeInsets.all(0),
plotAreaBorderWidth: 0,
title: ChartTitle(
// text: 'Inventory - Finished Products',
// textStyle: TextStyle(
// fontSize: 18.0,
// color: Colors.blueAccent,
// ),
text: widget.title,
// backgroundColor: Colors.lightGreen,
// borderColor: Colors.blue,
borderWidth: 2,
// Aligns the chart title to left
alignment: ChartAlignment.center,
// ignore: deprecated_member_use
textStyle: ChartTextStyle(
color: Colors.blueAccent,
// fontFamily: 'Roboto',
// fontStyle: FontStyle.italic,
fontSize: 11,
)),
primaryXAxis: CategoryAxis(
majorGridLines: MajorGridLines(width: 0),
),
primaryYAxis: NumericAxis(
// maximum: 150,
// minimum: 0,
interval: 25,
axisLine: AxisLine(width: 0),
majorTickLines: MajorTickLines(size: 0)),
palette: <Color>[
Color.fromRGBO(15, 207, 105, 1.0),
Color.fromRGBO(242, 209, 106, 1.0),
Color.fromRGBO(0, 72, 205, 1.0)
],
series: _getDefaultColumn(),
legend: Legend(
isVisible: true,
// Legend will be placed at the bottom
position: LegendPosition.bottom,
// Overflowing legend content will be wraped
overflowMode: LegendItemOverflowMode.wrap),
// tooltipBehavior: TooltipBehavior(
// enable: true,
// canShowMarker: true,
// header: '',
// format: 'point.y marks in point.x'),
tooltipBehavior: TooltipBehavior(enable: true),
);
}
List<ColumnSeries<ColumnChartDataModel, String>> _getDefaultColumn() {
List<ColumnChartDataModel> chartData = <ColumnChartDataModel>[];
for (Map i in widget.data)
chartData
.add(ColumnChartDataModel.fromJson(i) // Deserialization step #3
);
print('chartDataNewchartDataNew=>${widget.data}');
return <ColumnSeries<ColumnChartDataModel, String>>[
ColumnSeries<ColumnChartDataModel, String>(
/// To apply the column width here.
width: isCardView ? 0.8 : _columnWidth,
/// To apply the spacing betweeen to two columns here.
spacing: isCardView ? 0.2 : _columnSpacing,
animationDuration: 3000,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2), topRight: Radius.circular(2)),
dataSource: chartData,
// color: const Color.fromRGBO(252, 216, 20, 1),
xValueMapper: (ColumnChartDataModel sales, _) => sales.x,
yValueMapper: (ColumnChartDataModel sales, _) => sales.y,
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
textStyle: TextStyle(fontSize: 10, color: Colors.white)),
name: 'In'),
ColumnSeries<ColumnChartDataModel, String>(
dataSource: chartData,
width: isCardView ? 0.8 : _columnWidth,
spacing: isCardView ? 0.2 : _columnSpacing,
animationDuration: 3000,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2), topRight: Radius.circular(2)),
// color: const Color.fromRGBO(169, 169, 169, 1),
xValueMapper: (ColumnChartDataModel sales, _) => sales.x,
yValueMapper: (ColumnChartDataModel sales, _) =>
sales.secondSeriesYValue,
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
textStyle: TextStyle(fontSize: 10, color: Colors.white)),
name: 'Out'),
ColumnSeries<ColumnChartDataModel, String>(
dataSource: chartData,
width: isCardView ? 0.8 : _columnWidth,
spacing: isCardView ? 0.2 : _columnSpacing,
animationDuration: 3000,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2), topRight: Radius.circular(2)),
// color: const Color.fromRGBO(205, 127, 50, 1),
xValueMapper: (ColumnChartDataModel sales, _) => sales.x,
yValueMapper: (ColumnChartDataModel sales, _) =>
sales.thirdSeriesYValue,
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
textStyle: TextStyle(fontSize: 10, color: Colors.white)),
name: 'Stock')
];
}
this page is the model data
Model Data
/// Package import
import 'package:flutter/material.dart';
/// Base class of the sample's stateful widget class
abstract class ColumnChartModel extends StatefulWidget {
/// base class constructor of sample's stateful widget class
const ColumnChartModel({Key key}) : super(key: key);
}
/// Base class of the sample's state class
abstract class ColumnChartModelState extends State<ColumnChartModel> {
/// Holds the information of current page is card view or not
bool isCardView;
#override
void initState() {
isCardView = true;
super.initState();
}
/// Get the settings panel content.
Widget buildSettings(BuildContext context) {
return null;
}
}
///Chart sample data
class ColumnChartDataModel {
/// Holds the datapoint values like x, y, etc.,
ColumnChartDataModel(
this.x,
this.y,
this.xValue,
this.yValue,
this.secondSeriesYValue,
this.thirdSeriesYValue,
this.pointColor,
this.size,
this.text,
this.open,
this.close,
this.low,
this.high,
this.volume);
/// Holds x value of the datapoint
final dynamic x;
/// Holds y value of the datapoint
final num y;
/// Holds x value of the datapoint
final dynamic xValue;
/// Holds y value of the datapoint
final num yValue;
/// Holds y value of the datapoint(for 2nd series)
final num secondSeriesYValue;
/// Holds y value of the datapoint(for 3nd series)
final num thirdSeriesYValue;
/// Holds point color of the datapoint
final Color pointColor;
/// Holds size of the datapoint
final num size;
/// Holds datalabel/text value mapper of the datapoint
final String text;
/// Holds open value of the datapoint
final num open;
/// Holds close value of the datapoint
final num close;
/// Holds low value of the datapoint
final num low;
/// Holds high value of the datapoint
final num high;
/// Holds open value of the datapoint
final num volume;
// factory ColumnChartDataModel.fromJson(Map<String, dynamic> json) => ColumnChartDataModel(
// x: json["x"],
// y: json["y"],
// secondSeriesYValue: json["secondSeriesYValue"],
// thirdSeriesYValue: json["thirdSeriesYValue"],
// );
factory ColumnChartDataModel.fromJson(Map<String, dynamic> parsedJson) {
return ColumnChartDataModel(
parsedJson['x'].toString(),
parsedJson['y'] as num,
parsedJson['secondSeriesYValue'] as num,
parsedJson['thirdSeriesYValue'] as num,
parsedJson['xValue'] as dynamic,
parsedJson['yValue'] as num,
parsedJson['pointColor'] as Color,
parsedJson['size'] as num,
parsedJson['text'] as String,
parsedJson['open'] as num,
parsedJson['close'] as num,
parsedJson['low'] as num,
parsedJson['high'] as num,
parsedJson['volume'] as num);
}
}
widget.data == [{productName: abcd-4, totalIn: 10, totalOut: 40, currentStock: 270}, {productName: afegwt-10 Pairs, totalIn: 110, totalOut: 80, currentStock: 530}]
widget.title == "some text"
Please help me..
Thankx in advance.
In the json data the key names should be matched as per the data model created. or you have to put separate instead of .fromJson().
ive currently got a screen that has a linechart widget this is a stateless widget that uses the Flchart plugin
the datasource for the linechart is uisng a provider that is listening for changes, i also have a Date filter that calls an api and gets the data for that date range and refreshes the data
what i am finding is when i refresh the data for a diffrent date range, i can see the data is being sent to the widget but the line chart is not rendering/rebuilt, if i switch back to the default date range the first loaded chart is displayed correctly.
line chart widget
class LineChartWidget extends StatelessWidget {
final List<FlSpot> list;
final bool isDollar;
LineChartWidget({this.list, this.isDollar});
final DateTime now = DateTime.now();
double minY = 0;
double maxY = 0;
#override
Widget build(BuildContext context) {
list.forEach((f) => print(f.y.toString()));
return list.isEmpty
? Container(
child: Center(
child: Text("No Chart Data..."),
),
)
: ConstrainedBox(
constraints: BoxConstraints.expand(height: 140),
child: LineChart(mainData()),
);
}
static int dse2mse(double daysSinceEpoch) {
return (daysSinceEpoch * 86400000).floor();
}
static double mse2dse(int millisecondsSinceEpoch) {
return millisecondsSinceEpoch / 86400000;
}
String getTitleFunction(double value) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(dse2mse(value));
return DateFormat('MMM d').format(dateTime);
}
LineChartData mainData() {
maxY = list[0].y;
minY = list[0].y;
list.forEach((i) {
if (i.y < minY) {
minY = i.y;
}
if (i.y > maxY) {
maxY = i.y;
}
});
return LineChartData(
minX: mse2dse(DateTime(now.year, now.month, 1).millisecondsSinceEpoch),
maxX: mse2dse(now.millisecondsSinceEpoch),
minY: minY - 1.9 * minY,
maxY: maxY + 0.2 * maxY,
clipToBorder: true,
gridData: FlGridData(
show: false,
),
titlesData: FlTitlesData(
show: false,
),
borderData: FlBorderData(
show: false,
),
lineTouchData: LineTouchData(
fullHeightTouchLine: false,
handleBuiltInTouches: true,
getTouchedSpotIndicator:
(LineChartBarData barData, List<int> spotIndexes) {
return spotIndexes.map((spotIndex) {
final FlSpot spot = barData.spots[spotIndex];
if (spot.x == 0 || spot.x == 30 || spot.x == 29) {
return null;
}
return TouchedSpotIndicatorData(
const FlLine(color: Colors.transparent, strokeWidth: 0),
const FlDotData(
dotSize: 5, dotColor: Color.fromRGBO(253, 54, 94, 1)),
);
}).toList();
},
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: Colors.white,
fitInsideHorizontally: true,
fitInsideVertically: true,
getTooltipItems: (List<LineBarSpot> touchedBarSpots) {
return touchedBarSpots.map((barSpot) {
final flSpot = barSpot;
if (flSpot.x == 0 || flSpot.x == 30 || flSpot.x == 29) {
return null;
}
return LineTooltipItem(
isDollar
? '${getTitleFunction(flSpot.x)} | \$${flSpot.y}'
: '${getTitleFunction(flSpot.x)} | ${flSpot.y.toStringAsFixed(0)}',
const TextStyle(
color: Colors.black87,
fontFamily: 'NeueMontreal',
letterSpacing: 0.9,
fontWeight: FontWeight.w600,
fontSize: 12),
);
}).toList();
})),
lineBarsData: [
LineChartBarData(
spots: list,
isCurved: true,
colors: [Color.fromRGBO(253, 54, 94, 1)],
curveSmoothness: 0.17,
barWidth: 1,
isStrokeCapRound: true,
dotData: const FlDotData(
show: false,
),
),
],
);
}
}
im not sure whether changing the linechart widget to Stateful would help as i cant call setstate because the data is being refreshed by NotifyListers automatically
thanks
Resolved the issue, with the line chart not upating, MinX and MaxX values need to be updated when the date range changes
I have been working with the online gallery of Flutter charts (https://google.github.io/charts/flutter/gallery.html) but I'm struggling to add a title for x & y axis values.
Can somebody help me or tell me how to add the labels to the graph?
Its possible using behaviors property, check the code
var chart = charts.LineChart(seriesList,
behaviors: [
new charts.ChartTitle('Dimension',
behaviorPosition: charts.BehaviorPosition.bottom,
titleStyleSpec: chartsCommon.TextStyleSpec(fontSize: 11),
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('Dose, mg',
behaviorPosition: charts.BehaviorPosition.start,
titleStyleSpec: chartsCommon.TextStyleSpec(fontSize: 11),
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea)
],
defaultRenderer: new charts.LineRendererConfig(includePoints: true));
Source https://google.github.io/charts/flutter/example/behaviors/chart_title
use the 'behavior' list for set title of chart
Widget build(BuildContext context) {
return new charts.LineChart(
seriesList,
animate: animate,
behaviors: [
new charts.ChartTitle('Top title text',
subTitle: 'Top sub-title text',
behaviorPosition: charts.BehaviorPosition.top,
titleOutsideJustification: charts.OutsideJustification.start,
innerPadding: 18),
new charts.ChartTitle('Bottom title text',
behaviorPosition: charts.BehaviorPosition.bottom,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('Start title',
behaviorPosition: charts.BehaviorPosition.start,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('End title',
behaviorPosition: charts.BehaviorPosition.end,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
],
);
}
You can do it by using behaviors using line annotations iterating your list data and make a new LineAnnotationSegment array but you should be aware that some titles may overlap when the next time point is very close.
final data = [
LinearPrices(DateTime(2020, 9, 19), 5),
LinearPrices(DateTime(2020, 9, 26), 15),
LinearPrices(DateTime(2020, 10, 3), 20),
LinearPrices(DateTime(2020, 10, 10), 17),
];
#override
Widget build(BuildContext context) {
return charts.TimeSeriesChart(seriesList, animate: false, behaviors: [
charts.RangeAnnotation( data.map((e) => charts.LineAnnotationSegment(
e.timestamp, charts.RangeAnnotationAxisType.domain,
middleLabel: '\$${e.price}')).toList()),
]);
}
Nevertheless you can use a callback to paint when the user clicks the line by painting either a custom text at the bottom or as a custom label using behaviors like this:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:intl/intl.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final data = [
LinearPrices(DateTime(2020, 9, 19), 5),
LinearPrices(DateTime(2020, 9, 26), 15),
LinearPrices(DateTime(2020, 10, 3), 20),
LinearPrices(DateTime(2020, 10, 10), 17),
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Chart'),
),
body: ChartPricesItem(data),
));
}
}
class ChartPricesItem extends StatefulWidget {
final List<LinearPrices> data;
ChartPricesItem(this.data);
static List<charts.Series<LinearPrices, DateTime>> _createSeries(
List<LinearPrices> data) {
return [
charts.Series<LinearPrices, DateTime>(
id: 'Prices',
colorFn: (_, __) => charts.MaterialPalette.deepOrange.shadeDefault,
domainFn: (LinearPrices sales, _) => sales.timestamp,
measureFn: (LinearPrices sales, _) => sales.price,
data: data,
)
];
}
#override
_ChartPricesItemState createState() => _ChartPricesItemState();
}
class _ChartPricesItemState extends State<ChartPricesItem> {
DateTime _time;
double _price;
// Listens to the underlying selection changes, and updates the information relevant
void _onSelectionChanged(charts.SelectionModel model) {
final selectedDatum = model.selectedDatum;
DateTime time;
double price;
// We get the model that updated with a list of [SeriesDatum] which is
// simply a pair of series & datum.
if (selectedDatum.isNotEmpty) {
time = selectedDatum.first.datum.timestamp;
price = selectedDatum.first.datum.price;
}
// Request a build.
setState(() {
_time = time;
_price = price;
});
}
#override
Widget build(BuildContext context) {
final simpleCurrencyFormatter =
charts.BasicNumericTickFormatterSpec.fromNumberFormat(
NumberFormat.compactSimpleCurrency());
var behaviors;
// Check if the user click over the line.
if (_time != null && _price != null) {
behaviors = [
charts.RangeAnnotation([
charts.LineAnnotationSegment(
_time,
charts.RangeAnnotationAxisType.domain,
labelDirection: charts.AnnotationLabelDirection.horizontal,
labelPosition: charts.AnnotationLabelPosition.margin,
labelStyleSpec:
charts.TextStyleSpec(fontWeight: FontWeight.bold.toString()),
middleLabel: '\$$_price',
),
]),
];
}
var chart = charts.TimeSeriesChart(
ChartPricesItem._createSeries(widget.data),
animate: false,
// Include timeline points in line
defaultRenderer: charts.LineRendererConfig(includePoints: true),
selectionModels: [
charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
changedListener: _onSelectionChanged,
)
],
// This is the part where you paint label when you click over the line.
behaviors: behaviors,
// Sets up a currency formatter for the measure axis.
primaryMeasureAxis: charts.NumericAxisSpec(
tickFormatterSpec: simpleCurrencyFormatter,
tickProviderSpec:
charts.BasicNumericTickProviderSpec(zeroBound: false)),
/// Customizes the date tick formatter. It will print the day of month
/// as the default format, but include the month and year if it
/// transitions to a new month.
///
/// minute, hour, day, month, and year are all provided by default and
/// you can override them following this pattern.
domainAxis: charts.DateTimeAxisSpec(
tickFormatterSpec: charts.AutoDateTimeTickFormatterSpec(
day: charts.TimeFormatterSpec(
format: 'd', transitionFormat: 'dd/MM/yyyy'),
minute: charts.TimeFormatterSpec(
format: 'mm', transitionFormat: 'dd/MM/yyyy HH:mm'))),
);
var chartWidget = Padding(
padding: EdgeInsets.all(16),
child: SizedBox(
height: 200.0,
child: chart,
),
);
final children = <Widget>[chartWidget];
// If there is a selection, then include the details.
if (_time != null) {
children.add(Padding(
padding: EdgeInsets.only(top: 4.0),
child: Text(DateFormat('dd/MM/yyyy hh:mm').format(_time),
style: Theme.of(context).textTheme.bodyText1)));
}
return SingleChildScrollView(
child: Column(
children: <Widget>[
const SizedBox(height: 8),
Text("Product Prices", style: Theme.of(context).textTheme.headline5),
Column(children: children),
],
),
);
}
}
/// Sample linear data type.
class LinearPrices {
final DateTime timestamp;
final double price;
LinearPrices(this.timestamp, this.price);
}
This is the result: