Skip to content

Labels disappearing randomly #2309

Open
@KrishnaPras4dGandrath

Description

@KrishnaPras4dGandrath

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

No one assigned

    Labels

    chartsCharts componentopenOpen

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions