I created a Calendar widget.
I have 7 users and I would like to create 7 personal calendars.
For this purpose, I am using a list view builder.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:test_honours/model/doctors.dart';
import '../core/booking_calendar.dart';
import '../model/booking_service.dart';
import '../model/enums.dart';
import 'appointment_screen.dart';
class BookingCalendarDemoApp extends StatelessWidget {
BookingCalendarDemoApp({Key? key}) : super(key: key);
final now = DateTime.now();
late BookingModel bookingCalendarModel;
List<BookingModel> bookingList = [];
void createBooking() {
for (var i = 0; i < doctorsList.length; i++) {
Doctor doctor = doctorsList[i];
bookingList.add(bookingCalendarModel = new BookingModel(
apptName: 'Mock Service',
apptDuration: doctor.duration,
bookingEnd: doctor.endHour,
bookingStart: doctor.startHour));
}
}
Stream<dynamic>? getBookingStreamMock(
{required DateTime end, required DateTime start}) {
return Stream.value([]);
}
Future<dynamic> uploadBookingMock({required BookingModel newBooking}) async {
await Future.delayed(const Duration(seconds: 1));
converted.add(DateTimeRange(
start: newBooking.bookingStart, end: newBooking.bookingEnd));
print('${newBooking.toJson()} has been uploaded');
}
List<DateTimeRange> converted = [];
List<DateTimeRange> convertStreamResultMock({required dynamic streamResult}) {
///here you can parse the streamresult and convert to [List<DateTimeRange>]
///take care this is only mock, so if you add today as disabledDays it will still be visible on the first load
///disabledDays will properly work with real data
DateTime first = now;
DateTime tomorrow = now.add(Duration(days: 1));
DateTime second = now.add(const Duration(minutes: 55));
DateTime third = now.subtract(const Duration(minutes: 240));
DateTime fourth = now.subtract(const Duration(minutes: 500));
converted.add(
DateTimeRange(start: first, end: now.add(const Duration(minutes: 30))));
converted.add(DateTimeRange(
start: second, end: second.add(const Duration(minutes: 23))));
converted.add(DateTimeRange(
start: third, end: third.add(const Duration(minutes: 15))));
converted.add(DateTimeRange(
start: fourth, end: fourth.add(const Duration(minutes: 50))));
//book whole day example
converted.add(DateTimeRange(
start: DateTime(tomorrow.year, tomorrow.month, tomorrow.day, 5, 0),
end: DateTime(tomorrow.year, tomorrow.month, tomorrow.day, 23, 0)));
return converted;
}
List<DateTimeRange> generatePauseSlots() {
return [
DateTimeRange(
start: DateTime(now.year, now.month, now.day, 12, 0),
end: DateTime(now.year, now.month, now.day, 14, 0))
];
}
#override
Widget build(BuildContext context) {
double h = MediaQuery.of(context).size.height;
return Container(
height: h,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: bookingList.length,
itemBuilder: (context, index) {
return Column(
children: [
BookingCalendar(
bookingService: bookingCalendarModel,
convertStreamResultToDateTimeRanges:
convertStreamResultMock,
getBookingStream: getBookingStreamMock,
uploadBooking: uploadBookingMock,
pauseSlots: generatePauseSlots(),
hideBreakTime: false,
loadingWidget: const Text('Fetching data...'),
uploadingWidget: const CircularProgressIndicator(),
locale: 'en_US',
startingDayOfWeek: CalendarDays.monday,
wholeDayIsBookedWidget:
const Text('Fully booked! Choose another day'),
disabledDates: [DateTime(2023, 1, 20)],
disabledDays: [6, 7])
],
);
}),
);
}
}
When i run the app the whole screen is black. And I am getting this error:
The following assertion was thrown during paint():
RenderBox was not laid out: RenderRepaintBoundary#557e1 NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 2001 pos 12: 'hasSize'
Could you please give me any suggestions what is wrong? In addition, this class should be opened after the user click on a button.
Use Expanded()
Expanded(
child: ListView.builder()
),
Try to set width too, not only height, to Container parent of ListView.builder(
Related
Here is my the GridView that I use in my app when I run it locally :
And here when I make build (flutter build web) and run it on a server :
I get this error in the browser's console:
TypeError: Instance of 'minified:jO': type 'minified:jO' is not a
subtype of type 'minified:fC' main.dart.js:11277 at Object.c
(http://localhost/web/main.dart.js:10095:3) main.dart.js:11277 at
Object.az8 (http://localhost/web/main.dart.js:10740:18)
main.dart.js:11277 at iR.aMx [as a]
(http://localhost/web/main.dart.js:10735:3) main.dart.js:11277 at
eR.tq (http://localhost/web/main.dart.js:63249:6) main.dart.js:11277
at tE.xS (http://localhost/web/main.dart.js:66379:58)
main.dart.js:11277 at tE.ep
(http://localhost/web/main.dart.js:66299:3) main.dart.js:11277 at
tE.ep (http://localhost/web/main.dart.js:66408:3) main.dart.js:11277
at eD.ul (http://localhost/web/main.dart.js:66063:3)
main.dart.js:11277 at eD.eu
(http://localhost/web/main.dart.js:66023:16) main.dart.js:11277 at
eD.iZ (http://localhost/web/main.dart.js:66186:32)
edit : I added the code of CardTest
Here is the code of my GridView :
Column(
children: [
buildSubheadingText('Mes projets'),
buildVerticalSpace(5.0),
GridView.count(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
childAspectRatio: kIsWeb ? 4/1.3 : 1 /1.4,
//physics: NeverScrollableScrollPhysics(),
crossAxisCount: 2,
children: [
CardTest(
loadingPercent: 0.25,
title: 'Making History Notes',
subtitle: '20 hours progress',
dueDate: DateTime(2022, 4, 12)
),
CardTest(
loadingPercent: 0.25,
title: 'Making History Notes',
subtitle: '20 hours progress',
dueDate: DateTime(2022, 4, 12)
),
CardTest(
loadingPercent: 0.25,
title: 'Making History Notes',
subtitle: '20 hours progress',
dueDate: DateTime(2022, 4, 12)
)
)
],
);
Here is the code of CardTest widget :
import 'package:flutter/material.dart';
class CardTest extends StatelessWidget {
final double loadingPercent;
final String title;
final String subtitle;
final DateTime dueDate;
CardTest({
required this.loadingPercent,
required this.title,
required this.subtitle,
required this.dueDate
});
#override
Widget build(BuildContext context) {
return Flexible(
child: Container(
color: Colors.amber,
child : Column(
children: [
Text(loadingPercent.toString()),
Text(title),
Text(subtitle),
Text(dueDate.toString()),
],
),
),
);
}
}
From #MarianoZorrilla in the comments :
I simply had to remove the Flexible from my CardTest.
i want to make gridview of elearning page and call data from API(Postman) but unfortunately, I get this error:
Another exception was thrown: RenderFlex children have non-zero flex
but incoming height constraints are unbounded.
Another exception was thrown: RenderBox was not laid out:
RenderFlex#92214 relayoutBoundary=up1 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
Another exception was thrown: RenderBox was not laid out:
RenderFlex#79539 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
Another exception was thrown: RenderBox was not laid out:
RenderConstrainedBox#1c3d5 relayoutBoundary=up4 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
Another exception was thrown: RenderBox was not laid out:
RenderRepaintBoundary#1943e relayoutBoundary=up3 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE
Another exception was thrown:
'package:flutter/src/rendering/sliver_multi_box_adaptor.dart': Failed
assertion: line 544 pos 12: 'child.hasSize': is not true.
Another exception was thrown: Null check operator used on a null value
Another exception was thrown: Null check operator used on a null value
I/flutter (14832): CacheManager: Failed to download file from
a8fawUT6lZk with error: I/flutter (14832): Invalid argument(s): No
host specified in URI a8fawUT6lZk
I want to make like this :
This is my elearningscreen.dart file :
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:e_digital_nepal/api/http_services.dart';
import 'package:e_digital_nepal/model/e-learning/elearning.dart';
import 'package:e_digital_nepal/provider/e-learning_provider.dart';
import 'package:e_digital_nepal/util/form_data.dart';
import 'package:e_digital_nepal/util/global_config.dart';
import 'package:e_digital_nepal/util/services/toastr_service.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../../../widgets/widget.dart';
import 'package:e_digital_nepal/provider/mainProvider.dart';
import 'package:e_digital_nepal/screen/login/e-learning/student/detail_online_resource.dart';
import 'package:e_digital_nepal/util/utility.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'grid_view_info.dart';
class ELearningStudentScreen extends StatefulWidget {
const ELearningStudentScreen({Key? key}) : super(key: key);
#override
State<ELearningStudentScreen> createState() => _ELearningStudentScreenState();
}
class _ELearningStudentScreenState extends State<ELearningStudentScreen> {
HttpRepo _httpRepo = HttpServices();
bool _isLoading = true;
bool infiniteLoading = false;
int pageNumber = 1;
List<StudentOnlineResource> onlineresources = [];
ScrollController _scrollController = ScrollController();
late AnimationController controller;
RefreshController _refreshController =
RefreshController(initialRefresh: false);
ToastrService _toastr = ToastrService();
fetchInit(int page) async {
try {
var userInfo = context.read<MainProvider>().studentUser;
Map<String, dynamic> body = <String, dynamic>{
"studentId": userInfo.id,
"teamId": userInfo.teamId,
"pageNumber": 1,
// "currentVideoId": "",
// "subjectId": ""
};
DataUtility.getFormData(body);
var data = DataUtility.getFormData(body);
// var url = '/student/online-resource/student-wise';
var response =
await _httpRepo.post('/student/online-resource/student-wise', data);
print('Response:: $response');
print(response.body.toString());
if (response.body.length == 0) {
_toastr.showDefaultToaster('No more data found');
}
var studentOnlineResources =
studentOnlineResourceFromJson(json.encode(response.body));
setState(() {
_isLoading = false;
onlineresources = [...onlineresources, ...studentOnlineResources];
});
} catch (e) {}
}
void _onRefresh() async {
setState(() {
pageNumber = 1;
_isLoading = true;
onlineresources = [];
});
await fetchInit(1);
setState(() {
_isLoading = false;
});
_refreshController.refreshCompleted();
}
#override
void initState() {
super.initState();
fetchInit(1);
_scrollController.addListener(() async {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent) {
setState(() {
infiniteLoading = true;
});
await fetchInit(pageNumber + 1);
setState(() {
pageNumber = pageNumber + 1;
infiniteLoading = false;
});
}
});
}
void dispose() {
super.dispose();
_scrollController.dispose();
}
Widget build(BuildContext context) {
final _mainProvider = Provider.of<MainProvider>(context, listen: false);
double width = MediaQuery.of(context).size.width;
return Scaffold(
appBar: CoustomAppBar(
title: 'Online Resources',
isCenterTitle: true,
),
body: _isLoading
? ListLoader()
: SmartRefresher(
controller: _refreshController,
onRefresh: _onRefresh,
enablePullDown: true,
child: ListView.builder(
controller: _scrollController,
itemCount: onlineresources.length,
itemBuilder: (context, index) {
return SizedBox(
height: 200,
child: Column(
children: [
GridViewInfo(
onlineStudentResource: onlineresources[index],
),
if (infiniteLoading &&
onlineresources[index].id ==
onlineresources[onlineresources.length - 1]
.id)
// ChangeNotifierProvider<ELearningProvider>(
// create: (context) => ELearningProvider(
// context, _mainProvider.loginInfo),
// child: Consumer<ELearningProvider>(
// builder: (_, _provider, child) => Padding(
// padding: const EdgeInsets.all(16),
// child: OrientationBuilder(
// builder: (context, orientation) {
// return GridView.builder(
// gridDelegate:
// SliverGridDelegateWithMaxCrossAxisExtent(
// maxCrossAxisExtent: 200,
// childAspectRatio: 0.5,
// crossAxisSpacing: 16,
// mainAxisSpacing: 16),
// itemCount: 3,
// itemBuilder: (BuildContext context, index) {
// return Container(
// child: GridViewInfo(
// onlineStudentResource:
// onlineresources[index],
// ),
// decoration: BoxDecoration(
// color: Colors.white,
// border: Border.all(
// color: Colors.grey,
// width: 1,
// ),
// borderRadius:
// BorderRadius.circular(15)),
// );
// });
// }),
// ),
// ),
// ),
Column(
children: [
SizedBox(
height: 20,
),
SizedBox(
height: 5,
width: width - 50,
child: LinearProgressIndicator(
backgroundColor:
Theme.of(context).canvasColor,
color: Theme.of(context).primaryColor,
semanticsLabel: 'Linear progress indicator',
),
),
SizedBox(
height: 10,
),
],
)
],
),
);
},
),
));
}
}
// GridViewInfo(
// onlineStudentResource: onlineresources[index],
// ),
// if (infiniteLoading &&
// onlineresources[index].id ==
// onlineresources[onlineresources.length - 1].id)
wrap GridViewInfo in Expanded because you are using scrollable view in the column you should give the size of the scrollable area
Expanded(
child: GridViewInfo()
)
You need to use Expanded. Wrap GridViewInfo inside of the Expanded widget. If you are a beginner and do not know how Expanded widget works you can check the documentation. In future you will face this kind of issues a lot so you should better learn the usage of Expanded and Flexible widgets.
Expanded(
child: YourCustomWidget(),// That is your GridViewInfo() widget
),
Documentation link: https://api.flutter.dev/flutter/widgets/Expanded-class.html
Video link: https://youtu.be/_rnZaagadyo
I am trying to change my Y-axis label from number like 60,000 to 60k. Currently, I am using Charts_Flutter dependency.
Here is how my graph looks like:
And here is how my code looks like:
child: charts.TimeSeriesChart(
series,
animate: true,
domainAxis: new charts.DateTimeAxisSpec(
tickFormatterSpec: new charts.AutoDateTimeTickFormatterSpec(
day: new charts.TimeFormatterSpec(
format: 'dd',
transitionFormat: 'dd MMM',
),
),
),
primaryMeasureAxis: new charts.NumericAxisSpec(
renderSpec: new charts.GridlineRendererSpec(
// Tick and Label styling here.
labelStyle: new charts.TextStyleSpec(
fontSize: 10, // size in Pts.
color: charts.MaterialPalette.black
),
)
),
defaultRenderer: charts.LineRendererConfig(
includeArea: true,
includeLine: true,
includePoints: true,
strokeWidthPx: 0.5,
radiusPx: 1.5
),
dateTimeFactory: const charts.LocalDateTimeFactory(),
behaviors: [
charts.PanAndZoomBehavior(),
charts.SelectNearest(
eventTrigger: charts.SelectionTrigger.tap
),
charts.LinePointHighlighter(
symbolRenderer: CustomCircleSymbolRenderer(size: size),
),
],
selectionModels: [
charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
changedListener: (charts.SelectionModel model) {
if(model.hasDatumSelection) {
final tankVolumeValue = model.selectedSeries[0].measureFn(model.selectedDatum[0].index).round();
final dateValue = model.selectedSeries[0].domainFn(model.selectedDatum[0].index);
CustomCircleSymbolRenderer.value = '$dateValue \n $tankVolumeValue L';
}
})
]),
),
);
Any idea on how to change the label is welcome.
With charts_flutter you can specify a NumberFormat using their BasicNumericTickFormatterSpec class (which... doesn't seem basic at all).
Then supply this formatter class to your chart
Below I've used NumberFormat.compact() which would turn 60,000 into 60K.
/// Formatter for Y-axis numbers using [NumberFormat] compact
///
/// Used in the [NumericAxisSpec] below.
final myNumericFormatter =
BasicNumericTickFormatterSpec.fromNumberFormat(
NumberFormat.compact() // ← your format goes here
);
return TimeSeriesChart(seriesList,
animate: animate,
// Use myNumericFormatter on the primaryMeasureAxis
primaryMeasureAxis: NumericAxisSpec(
tickFormatterSpec: myNumericFormatter // ← pass your formatter here
),
Here's a full running example using the above:
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ChartFormatPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chart Format'),
),
body: Center(
child: ChartCustomYAxis.withSampleData(),
)
);
}
}
class ChartCustomYAxis extends StatelessWidget {
final List<Series> seriesList;
final bool animate;
ChartCustomYAxis(this.seriesList, {this.animate});
/// Creates a [TimeSeriesChart] with sample data and no transition.
factory ChartCustomYAxis.withSampleData() {
return ChartCustomYAxis(
_createSampleData(),
// Disable animations for image tests.
animate: false,
);
}
#override
Widget build(BuildContext context) {
/// Formatter for Y-axis numbers using [NumberFormat] compact
///
/// Used in the [NumericAxisSpec] below.
final myNumericFormatter =
BasicNumericTickFormatterSpec.fromNumberFormat(
NumberFormat.compact() // ← your format goes here
);
return TimeSeriesChart(seriesList,
animate: animate,
// Use myNumericFormatter on the primaryMeasureAxis
primaryMeasureAxis: NumericAxisSpec(
tickFormatterSpec: myNumericFormatter // ← pass your formatter here
),
/// 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: DateTimeAxisSpec(
tickFormatterSpec: AutoDateTimeTickFormatterSpec(
day: TimeFormatterSpec(
format: 'd', transitionFormat: 'MM/dd/yyyy'))));
}
/// Create one series with sample hard coded data.
static List<Series<MyRow, DateTime>> _createSampleData() {
final data = [
MyRow(DateTime(2017, 9, 25), 6000),
MyRow(DateTime(2017, 9, 26), 8000),
MyRow(DateTime(2017, 9, 27), 6000),
MyRow(DateTime(2017, 9, 28), 9000),
MyRow(DateTime(2017, 9, 29), 11000),
MyRow(DateTime(2017, 9, 30), 15000),
MyRow(DateTime(2017, 10, 01), 25000),
MyRow(DateTime(2017, 10, 02), 33000),
MyRow(DateTime(2017, 10, 03), 27000),
MyRow(DateTime(2017, 10, 04), 31000),
MyRow(DateTime(2017, 10, 05), 23000),
];
return [
Series<MyRow, DateTime>(
id: 'Cost',
domainFn: (MyRow row, _) => row.timeStamp,
measureFn: (MyRow row, _) => row.cost,
data: data,
)
];
}
}
/// Sample time series data type.
class MyRow {
final DateTime timeStamp;
final int cost;
MyRow(this.timeStamp, this.cost);
}
The above example was stolen from here and modified to show NumberFormat.compact.
I am trying to create a time series chart with tooltips in flutter, but I can't manage to overcome this error message. "Offset argument contained a NaN value. 'dart:ui/painting.dart': Failed assertion: line 43: ''". https://github.com/google/charts/issues/58 is where I got this code from, but nobody else seems to have this issue. The same error message always appears when trying other types of charts and different blocks of code found on that github page. Here is the relevant code:
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:charts_flutter/flutter.dart';
import 'package:charts_flutter/src/text_element.dart' as text;
import 'package:charts_flutter/src/text_style.dart' as style;
class CustomMeasureTickCount extends StatelessWidget {
final List<charts.Series> seriesList;
final bool animate;
static String pointerValue;
CustomMeasureTickCount(this.seriesList, {this.animate});
factory CustomMeasureTickCount.withSampleData() {
return new CustomMeasureTickCount(
_createSampleData(),
animate: false,
);
}
#override
Widget build(BuildContext context) {
return new charts.TimeSeriesChart(seriesList,
animate: animate,
behaviors: [
LinePointHighlighter(symbolRenderer: CustomCircleSymbolRenderer())
],
selectionModels: [
SelectionModelConfig(changedListener: (SelectionModel model) {
if (model.hasDatumSelection)
pointerValue = model.selectedSeries[0]
.measureFn(model.selectedDatum[0].index)
.toString();
})
],
/// Customize the measure axis to have 10 ticks
primaryMeasureAxis: new charts.NumericAxisSpec(
tickProviderSpec:
new charts.BasicNumericTickProviderSpec(desiredTickCount: 10)));
}
/// Create one series with sample hard coded data.
static List<charts.Series<MyRow, DateTime>> _createSampleData() {
final data = [
new MyRow(new DateTime(2017, 9, 25), 6),
new MyRow(new DateTime(2017, 9, 26), 8),
new MyRow(new DateTime(2017, 9, 27), 6),
new MyRow(new DateTime(2017, 9, 28), 9),
new MyRow(new DateTime(2017, 9, 29), 11),
new MyRow(new DateTime(2017, 9, 30), 15),
new MyRow(new DateTime(2017, 10, 01), 25),
new MyRow(new DateTime(2017, 10, 02), 33),
new MyRow(new DateTime(2017, 10, 03), 27),
new MyRow(new DateTime(2017, 10, 04), 31),
new MyRow(new DateTime(2017, 10, 05), 23),
];
return [
new charts.Series<MyRow, DateTime>(
id: 'Cost',
domainFn: (MyRow row, _) => row.timeStamp,
measureFn: (MyRow row, _) => row.cost,
data: data,
colorFn: (_, __) => charts.MaterialPalette.indigo.shadeDefault,
)
];
}
}
/// Sample time series data type.
class MyRow {
final DateTime timeStamp;
final int cost;
MyRow(this.timeStamp, this.cost);
}
class CustomCircleSymbolRenderer extends CircleSymbolRenderer {
#override
void paint(ChartCanvas canvas, Rectangle bounds,
{List dashPattern,
Color fillColor,
FillPatternType fillPattern,
Color strokeColor,
double strokeWidthPx}) {
super.paint(canvas, bounds,
dashPattern: dashPattern,
fillColor: fillColor,
fillPattern: fillPattern,
strokeColor: strokeColor,
strokeWidthPx: strokeWidthPx);
canvas.drawRect(
Rectangle(bounds.left - 5, bounds.top - 30, bounds.width + 10,
bounds.height + 10),
fill: Color.white);
var textStyle = style.TextStyle();
textStyle.color = Color.black;
textStyle.fontSize = 15;
canvas.drawText(
text.TextElement(CustomMeasureTickCount.pointerValue, style: textStyle),
(bounds.left).round(),
(bounds.top - 28).round());
}
}
The error occurs because of an new event occuring like navigating to a new page while the textfield is in foccus. To fox the issue just simply remove focuss from the textfield.
For example
FocusScope.of(context).requestFocus(new FocusNode());
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: