Open
Description
Bug description
I'm using dynamic graph where user will be able to select y axis and x axes values , and graph changes based on selected x-axis, y-axis and secondary y-axis.When axes are changed labels are showing while the graph is being animated but when animation is complete, the labels are disappearing.
Steps to reproduce
code
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
void main() {
runApp(const ProviderScope(
child: MaterialApp(
home: GraphSampleView(),
),
));
}
// Constants
const kClearOption = 'CLEAR';
const kAllowedXAxis = [
'Category A',
'Category B',
'Category C',
];
const kAllowedYAxes = [
'Value 1',
'Value 2',
'Value 3',
'Value 4',
];
const kAxisTitles = {
'Category A': 'Category A (Long Title)',
'Category B': 'Category B (Medium)',
'Category C': 'Category C',
'Value 1': 'Value 1 (Numbers)',
'Value 2': 'Value 2 (Percentages)',
'Value 3': 'Value 3 (Counts)',
'Value 4': 'Value 4 (Ratios)',
'CLEAR': 'Clear Selection',
};
const kPrimaryAxisColor = Colors.blue;
final kSecondaryAxisColor = Colors.red.shade400;
// Controller
final graphSampleProvider =
ChangeNotifierProvider((ref) => GraphSampleController());
class GraphSampleController extends ChangeNotifier {
String selectedXAxis = kAllowedXAxis[0];
String selectedY1Axis = kAllowedYAxes[0];
String? selectedY2Axis = kAllowedYAxes[1];
bool graphRefreshIndicator = false;
// Static data for testing
final List<Map<String, dynamic>> _sampleData = [
{
'label': 'Very Long Label That Should Be Truncated',
'value1': 100,
'value2': 45.5,
'value3': 200,
'value4': 0.75,
},
{
'label': 'Medium Label',
'value1': 75,
'value2': 60.2,
'value3': 150,
'value4': 0.85,
},
{
'label': 'Short',
'value1': 50,
'value2': 30.8,
'value3': 100,
'value4': 0.45,
},
{
'label': 'Test Label 1234',
'value1': 125,
'value2': 80.0,
'value3': 300,
'value4': 0.95,
},
{
'label': 'Another Long Label For Testing',
'value1': 90,
'value2': 55.5,
'value3': 180,
'value4': 0.65,
},
];
List<Map<String, dynamic>> get y1DataSource {
String valueKey = _getValueKey(selectedY1Axis);
return _sampleData
.map((item) => {
'label': item['label'],
'value': item[valueKey],
})
.toList();
}
List<Map<String, dynamic>> get y2DataSource {
if (selectedY2Axis == null) return [];
String valueKey = _getValueKey(selectedY2Axis!);
return _sampleData
.map((item) => {
'label': item['label'],
'value': item[valueKey],
})
.toList();
}
String _getValueKey(String axis) {
switch (axis) {
case 'Value 1':
return 'value1';
case 'Value 2':
return 'value2';
case 'Value 3':
return 'value3';
case 'Value 4':
return 'value4';
default:
return 'value1';
}
}
void onXAxisChanged(String value) {
selectedXAxis = value;
_refreshGraph();
}
void onY1AxisChanged(String value) {
if (value == selectedY2Axis) {
selectedY2Axis = selectedY1Axis;
}
selectedY1Axis = value;
_refreshGraph();
}
void onY2AxisChanged(String value) {
selectedY2Axis = value;
_refreshGraph();
}
void resetY2Axis() {
selectedY2Axis = null;
_refreshGraph();
}
void _refreshGraph() {
graphRefreshIndicator = !graphRefreshIndicator;
notifyListeners();
}
}
// View
class GraphSampleView extends ConsumerStatefulWidget {
const GraphSampleView({super.key});
@override
ConsumerState<GraphSampleView> createState() => _GraphSampleViewState();
}
class _GraphSampleViewState extends ConsumerState<GraphSampleView> {
late final controller = ref.read(graphSampleProvider);
final _tooltipBehavior = TooltipBehavior(enable: true);
@override
Widget build(BuildContext context) {
ref.watch(graphSampleProvider.select((v) => v.graphRefreshIndicator));
ref.watch(graphSampleProvider
.select((v) => {v.selectedXAxis, v.selectedY1Axis, v.selectedY2Axis}));
return Scaffold(
appBar: AppBar(title: const Text('Graph Sample')),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(
height: 40.0,
child: Row(
children: [
const SizedBox(width: 16),
axisDropdown(
label: 'X Axis',
options: kAllowedXAxis,
selected: controller.selectedXAxis,
onChanged: controller.onXAxisChanged,
),
const SizedBox(width: 16),
axisDropdown(
label: 'Y Axis I',
options: [...kAllowedYAxes]
..remove(controller.selectedY2Axis),
selected: controller.selectedY1Axis,
onChanged: controller.onY1AxisChanged,
),
const SizedBox(width: 16),
axisDropdown(
label: 'Y Axis II',
options: [...kAllowedYAxes]
..remove(controller.selectedY1Axis)
..add(kClearOption),
selected: controller.selectedY2Axis,
onChanged: controller.onY2AxisChanged,
onReset: controller.resetY2Axis,
),
],
),
),
Expanded(child: _buildGraph()),
],
),
);
}
Widget _buildGraph() {
return GestureDetector(
onTap: () => _tooltipBehavior.hide(),
child: Container(
padding: const EdgeInsets.all(20.0),
child: SfCartesianChart(
tooltipBehavior: TooltipBehavior(
enable: true,
activationMode: ActivationMode.singleTap,
animationDuration: 200,
decimalPlaces: 3,
),
primaryXAxis: CategoryAxis(
name: 'primaryXAxis',
isVisible: true,
labelRotation: controller.selectedXAxis == 'Category A' ? 270 : 0,
majorTickLines: const MajorTickLines(size: 0),
majorGridLines: const MajorGridLines(width: 0),
labelStyle: const TextStyle(fontSize: 10.0),
interval: 1,
maximumLabelWidth: 500.0,
labelsExtent: 80,
axisLabelFormatter: (axisLabelRenderArgs) {
String text = axisLabelRenderArgs.text;
return ChartAxisLabel(
text,
const TextStyle(fontSize: 10.0, overflow: TextOverflow.visible),
);
},
interactiveTooltip: const InteractiveTooltip(enable: true),
title: AxisTitle(text: kAxisTitles[controller.selectedXAxis]),
),
primaryYAxis: NumericAxis(
name: 'primaryYAxis',
maximumLabelWidth: 300.0,
labelIntersectAction: AxisLabelIntersectAction.trim,
isVisible: true,
title: AxisTitle(text: kAxisTitles[controller.selectedY1Axis]),
majorTickLines: const MajorTickLines(size: 0),
majorGridLines: MajorGridLines(
width: 0.5,
color: Colors.grey.withOpacity(0.4),
dashArray: const [3, 3],
),
),
axes: [
if (controller.selectedY2Axis != null)
NumericAxis(
name: 'secondaryYAxis',
isVisible: true,
maximumLabelWidth: 300.0,
anchorRangeToVisiblePoints: true,
labelIntersectAction: AxisLabelIntersectAction.trim,
decimalPlaces: 3,
opposedPosition: true,
majorTickLines: const MajorTickLines(size: 0),
majorGridLines: MajorGridLines(
width: 0.5,
color: Colors.red.withOpacity(0.4),
dashArray: const [0, 3],
),
labelStyle: TextStyle(color: kSecondaryAxisColor),
title: AxisTitle(
text: kAxisTitles[controller.selectedY2Axis],
textStyle: TextStyle(color: kSecondaryAxisColor),
),
),
],
isTransposed: true,
series: [
BarSeries<Map<String, dynamic>, String>(
yAxisName: 'primaryYAxis',
animationDuration: 400,
name: kAxisTitles[controller.selectedY1Axis],
dataSource: controller.y1DataSource,
xValueMapper: (data, _) => data['label'] as String,
yValueMapper: (data, _) => data['value'] as num,
enableTooltip: true,
color: kPrimaryAxisColor,
width: 0.4,
),
if (controller.selectedY2Axis != null)
LineSeries<Map<String, dynamic>, String>(
yAxisName: 'secondaryYAxis',
animationDuration: 400,
name: kAxisTitles[controller.selectedY2Axis],
dataSource: controller.y2DataSource,
xValueMapper: (data, _) => data['label'] as String,
yValueMapper: (data, _) => data['value'] as num? ?? 0,
enableTooltip: true,
markerSettings: MarkerSettings(
isVisible: true,
borderWidth: 0,
color: kSecondaryAxisColor,
height: 6.0,
width: 6.0,
),
color: kSecondaryAxisColor,
),
],
),
),
);
}
Widget axisDropdown({
required String label,
required List<String> options,
required String? selected,
required void Function(String) onChanged,
VoidCallback? onReset,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(label, style: const TextStyle(fontSize: 11.0)),
const SizedBox(width: 8),
DecoratedBox(
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 0,
offset: Offset(0, 0),
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
hint: const Text('-- Select --',
style: TextStyle(fontSize: 11.0)),
value: selected,
menuMaxHeight: 200.0,
items: options
.map((e) => DropdownMenuItem<String>(
value: e,
child: Text(
kAxisTitles[e] ?? '-',
style: const TextStyle(fontSize: 11.0),
),
))
.toList(),
onChanged: (value) => value != null
? value == kClearOption
? onReset?.call()
: onChanged(value)
: null,
),
),
),
),
],
);
}
}
Screenshots or Video
Screenshots / Video demonstration
Screen.Recording.2025-03-17.at.9.49.06.PM.mov
Stack Traces
Stack Traces
[Add the Stack Traces here]
On which target platforms have you observed this bug?
Web
Flutter Doctor output
Doctor output
[Add your output here]