我实际上使用了两个依赖:syncfusion颤动图和虚线
我的图表看起来像这样(见图1)
您可以点击图表上的项目符号点(或篮球表情符号)以显示虚线和工具提示小部件。我的问题是,如果我的图表上没有足够的高度,显示的小部件会在底部(这不是我们的目标,见图 2)。我怎样才能实现始终在图表顶部显示我的小部件?
我用溢出框和堆栈搜索,但经过几个小时后,我可以自己完成。该图表的代码可在此处获得:
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,
),
),
)
],
),
);
}
}