Skip to content

Labels disappearing randomly #2309

Closed
Closed
@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 componentsolvedSolved the query using existing solutions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions