图表上的垂直虚线
Vertical dotted line on chart
我其实用了两个依赖:syncfusion flutter chart & dotted line
我的图表是这样的(见图 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,
),
),
)
],
),
);
}
}
目前,当数据点上方没有 space 来呈现其自定义工具提示小部件时,它会向下移动并呈现。这是默认行为。但是,如果您希望始终在相应数据点上方呈现自定义工具提示小部件,则可以将图表轴的 rangePadding 属性 设置为 ChartRangePadding.additional 以便在轴的起点和终点添加额外的范围填充。此外,如果提供 rangePadding 值作为 additional 没有解决问题,那么您需要设置根据您使用图表轴的最大值 属性 可用的数据点,图表的特定范围,以便工具提示小部件构建器有足够的 space 来在相应数据点上方呈现。请参考下面的代码片段以供进一步参考。
代码片段:
将 rangePadding 属性 值设置为附加值:
SfCartesianChart(
primaryYAxis: NumericAxis(
rangePadding: ChartRangePadding.additional
),
// Your configurations
)
为图表的 y 轴设置特定范围:
SfCartesianChart(
primaryYAxis: NumericAxis(
minimum: 0,
maximum: 300
),
// Your configurations
)
有关 rangePadding 属性 和自定义图表范围的更多参考,请查看下面的用户指南。
我其实用了两个依赖:syncfusion flutter chart & dotted line
我的图表是这样的(见图 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,
),
),
)
],
),
);
}
}
目前,当数据点上方没有 space 来呈现其自定义工具提示小部件时,它会向下移动并呈现。这是默认行为。但是,如果您希望始终在相应数据点上方呈现自定义工具提示小部件,则可以将图表轴的 rangePadding 属性 设置为 ChartRangePadding.additional 以便在轴的起点和终点添加额外的范围填充。此外,如果提供 rangePadding 值作为 additional 没有解决问题,那么您需要设置根据您使用图表轴的最大值 属性 可用的数据点,图表的特定范围,以便工具提示小部件构建器有足够的 space 来在相应数据点上方呈现。请参考下面的代码片段以供进一步参考。
代码片段:
将 rangePadding 属性 值设置为附加值:
SfCartesianChart(
primaryYAxis: NumericAxis(
rangePadding: ChartRangePadding.additional
),
// Your configurations
)
为图表的 y 轴设置特定范围:
SfCartesianChart(
primaryYAxis: NumericAxis(
minimum: 0,
maximum: 300
),
// Your configurations
)
有关 rangePadding 属性 和自定义图表范围的更多参考,请查看下面的用户指南。