-
Couldn't load subscription status.
- Fork 900
Closed
Labels
chartsCharts componentCharts componentsolvedSolved the query using existing solutionsSolved the query using existing solutions
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]Metadata
Metadata
Assignees
Labels
chartsCharts componentCharts componentsolvedSolved the query using existing solutionsSolved the query using existing solutions