Вертикальная пунктирная линия на графике
На самом деле я использую две зависимости: диаграмма флаттера 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,
),
),
)
],
),
);
}
}
1 ответ
Привет от Syncfusion. Мы проанализировали ваш сценарий с предоставленной информацией на нашей стороне, и в настоящее время, когда над точкой данных нет места для отображения его настраиваемого виджета всплывающей подсказки, он смещается вниз и отображается. Это поведение по умолчанию. Однако, если вы хотите всегда отображать настраиваемый виджет всплывающей подсказки над соответствующей точкой данных, вы можете установить для свойства rangePadding оси диаграммы значение ChartRangePadding.additional, чтобы в начале и в конце оси добавлялись дополнительные отступы диапазона. Кроме того, если значение rangePadding указано в качестве дополнительного не решила проблему, тогда вам нужно установить конкретный диапазон для диаграммы в соответствии с вашими точками данных, доступными с помощью максимального свойства оси диаграммы, чтобы было достаточно места для конструктора виджетов всплывающей подсказки для визуализации над соответствующими данными точка. Дополнительные сведения см. В приведенных ниже фрагментах кода.
Фрагмент кода:
Установка значения свойства rangePadding как дополнительного:
SfCartesianChart(
primaryYAxis: NumericAxis(
rangePadding: ChartRangePadding.additional
),
// Your configurations
)
Установка определенного диапазона для оси Y диаграммы:
SfCartesianChart(
primaryYAxis: NumericAxis(
minimum: 0,
maximum: 300
),
// Your configurations
)
Пожалуйста, ознакомьтесь с приведенными выше решениями и верните нам, если у вас все еще есть вопросы.
Дополнительные сведения о свойстве rangePadding и настройке диапазона диаграммы см. В руководствах пользователя ниже.
- https://help.syncfusion.com/flutter/cartesian-charts/axis-types#applying-padding-to-the-range
- https://help.syncfusion.com/flutter/cartesian-charts/axis-types#customizing-range
С уважением, Шрирам Киран