I actually use two dependancy: syncfusion flutter chart & dotted line
My chart look like this (see picture 1)
You can tap on the bullet point on the chart (or the basketball emoji) to display a dotted line and a tooltip widget. My problem is that if there is not enough height on my chart, the displayed widget go on the bottom (which is not our goal, see picture 2). How can i achieve to display my widget always on top of the chart ?
I searched with overflow boxes and stacks, but after some long hours, i could make it by myself. The code for the chart is available here:
import 'package:dotted_line/dotted_line.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:lifetizr/misc/utils.dart';
import 'package:lifetizr/ui/screens/homepage/homepage_viewmodel.dart';
import 'package:lifetizr/ui/widgets/activity_block_preview.dart';
import 'package:stacked/stacked.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:theme_provider/theme_provider.dart';
class ChartBlock extends StatefulWidget {
@override
_ChartBlockState createState() => _ChartBlockState();
}
class _ChartBlockState extends State<ChartBlock> {
List<_DataPoint> graphData = [];
@override
void initState() {
super.initState();
loadData();
}
void loadData() async {
await HomepageViewModel().getGraphData().then(
(value) {
List<_DataPoint> tmpData = [];
tmpData.add(_DataPoint(DateTime.fromMillisecondsSinceEpoch(0).toString(), 290));
for (var item in value['allBloodglucoses']['edges']) {
tmpData.add(_DataPoint(
item['node']['timestamp'], item['node']['bloodGlucose']));
}
setState(
() {
graphData = tmpData;
},
);
},
);
}
@override
Widget build(BuildContext context) {
final controller = ThemeProvider.controllerOf(context);
return ViewModelBuilder<HomepageViewModel>.reactive(
viewModelBuilder: () => HomepageViewModel(),
builder: (context, model, child) {
return SfCartesianChart(
trackballBehavior: TrackballBehavior(
lineWidth: 5,
lineColor: reverseColorGrabber(controller),
lineType: TrackballLineType.vertical,
enable: true,
shouldAlwaysShow: true,
tooltipAlignment: ChartAlignment.far,
tooltipSettings: InteractiveTooltip(
arrowWidth: 0,
arrowLength: 0,
borderColor: reverseColorGrabber(controller),
borderRadius: 15,
borderWidth: 15,
connectorLineWidth: 50,
format: 'point.y mg/dL',
enable: true,
color: reverseColorGrabber(controller),
)),
primaryXAxis: CategoryAxis(
visibleMinimum: 0,
visibleMaximum: graphData.length.toDouble() / 6),
zoomPanBehavior: ZoomPanBehavior(
enablePanning: true,
),
indicators: [],
// Enable tooltip
tooltipBehavior: TooltipBehavior(
color: Colors.transparent,
canShowMarker: false,
enable: true,
builder: (dynamic d, dynamic f, dynamic g, int i, int j) {
if (i == 1 || i == 4 || i == 3)
return Container(
height: 100,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: Colors.green,
),
child: InkWell(
onTap: () {
print("Pressed");
},
child: Container(
width: 80,
child: ActivityBlockPreview(
score: 18,
pictureUrl: "https://i.imgur.com/nlRr5vn.jpeg",
)),
),
),
Expanded(
child: DottedLine(
direction: Axis.vertical,
lineLength: double.infinity,
lineThickness: 1.0,
dashLength: 4.0,
dashColor: reverseColorGrabber(controller),
dashRadius: 0.0,
dashGapLength: 4.0,
dashGapColor: Colors.transparent,
dashGapRadius: 0.0,
),
),
],
),
);
else
return null;
},
),
series: <ChartSeries<_DataPoint, String>>[
SplineSeries<_DataPoint, String>(
color: reverseColorGrabber(controller),
width: 5,
pointColorMapper: (_DataPoint data, int i) {
if (i == 0) return Colors.transparent;
},
dataSource: <_DataPoint>[...graphData],
xValueMapper: (_DataPoint point, _) =>
DateFormat('H:mm').format(DateTime.parse(point.timestamp)),
yValueMapper: (_DataPoint point, _) => point.glucose,
// Enable data label
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
builder: (dynamic d, dynamic f, dynamic g, int i, int _) {
if (i == 1 || i == 4)
return Container(
height: 20,
width: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
border: Border.all(width: 3, color: Colors.red),
color: Colors.white,
),
);
if (i == 3) {
return Text(
"",
style: TextStyle(fontSize: 20),
);
} else
return null;
},
),
)
],
);
},
);
}
}
class _DataPoint {
_DataPoint(this.timestamp, this.glucose);
final String timestamp;
final double glucose;
}
class ActivityBlockPreview extends StatefulWidget {
final int score;
final String pictureUrl;
final bool isSportActivity;
final bool isSleepActivity;
final String sportEmoji;
const ActivityBlockPreview(
{this.score,
this.pictureUrl,
this.isSportActivity = false,
this.isSleepActivity = false,
this.sportEmoji});
@override
_ActivityBlockPreviewState createState() => _ActivityBlockPreviewState();
}
class _ActivityBlockPreviewState extends State<ActivityBlockPreview> {
@override
Widget build(BuildContext context) {
var controller = ThemeProvider.controllerOf(context);
return Container(
decoration: BoxDecoration(
color: colorGrabber(controller),
borderRadius: BorderRadius.circular(60)),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircleAvatar(
backgroundColor: colorGrabber(controller),
backgroundImage: widget.isSportActivity == false &&
widget.isSleepActivity == false
? NetworkImage(
widget.pictureUrl,
)
: null,
child: widget.isSportActivity == true || widget.isSleepActivity
? Text(widget.sportEmoji)
: Container(),
),
if (!widget.isSportActivity)
Padding(
padding: const EdgeInsets.only(left: 5, right: 10),
child: Text(
widget.score.toString(),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
)
],
),
);
}
}
Currently, when there is no space above the data point to render its custom tooltip widget, it gets shift downwards and gets rendered. This is the default behaviour. However, if you want to always render the custom tooltip widget above the respective data point, then you can set rangePadding property of the chart axis to ChartRangePadding.additional so that there additional range padding will be added at the start and end of the axis. Also, if providing rangePadding value as additional didn’t solve the issue then you need to set the specific range for the chart according to you data points available by using the maximum property of the chart axis so that there is enough space for the tooltip widget builder to get render above the respective data point. Please refer the code snippets below for further reference.
Code snippet:
Setting rangePadding property value as additional:
Setting specific range for the chart’s y-axis:
For further reference on the rangePadding property and customizing the range of the chart, please check the user guides below.