Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chart are displayed incorrectly when axisMin and axisMax are set #94

Open
iamiota opened this issue Sep 10, 2023 · 3 comments · May be fixed by #95
Open

Chart are displayed incorrectly when axisMin and axisMax are set #94

iamiota opened this issue Sep 10, 2023 · 3 comments · May be fixed by #95
Assignees
Labels
bug Something isn't working

Comments

@iamiota
Copy link

iamiota commented Sep 10, 2023

First of all, thank you for this amazing library, I really like the idea of using widgets to create charts.

I'm creating a line chart with target line(y: 3.6) and target area(y: 0 ~ 2).
I encountered some issues after setting axisMin and axisMax:

WX20230910-125832@2x

1. I have a TargetAreaDecoration from 0 to 2, when axisMin is set to 2, it is incorrectly displayed on the x-axis.

The solution I came up with is to dynamically modify the targetMin and targetMax of the TargetAreaDecoration based on axisMin and axisMax. For example, when axisMin is 1, I would change the TargetAreaDecoration's targetMin from 0 to 1, so it won't be displayed on the x-axis anymore.

2. From the blue area in the graph, it can be observed that the rendering behavior of widgetItemBuilder is inconsistent with that of SparkLineDecoration. The size of widgetItemBuilder is much larger than SparkLineDecoration.

I created a Position Widget within widgetItemBuilder and positioned the point widget using its top property. If the verticalMultiplier parameter can be added to widgetItemBuilder, I can calculate the correct position of the point:

Position(
    bottom: (_mappedValues[data.listIndex][data.itemIndex].max! - axisMin) * verticalMultiplier + 8,
)

3. The target line is also being displayed in the wrong place.

The solution I came up with is similar to the first issue. I dynamically modify verticalMultiplier * (3.6 - axisMin) based on axisMin and axisMax.

Do you have any suggestions? Thank you.

Codes

import 'package:charts_painter/chart.dart';
import 'package:flutter/material.dart';

const double axisWidth = 80.0;

class LineChart extends StatelessWidget {
  final bool useAxis;

  LineChart({Key? key, this.useAxis = false}) : super(key: key);

  final List<List<ChartItem<double>>> _mappedValues = [
    [ChartItem(2.0), ChartItem(5.0), ChartItem(8.0), ChartItem(3.0), ChartItem(6.0)]
  ];

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: MediaQuery.of(context).size.height / 2,
      child: Row(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 32),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 350),
              width: axisWidth,
              child: DecorationsRenderer(
                [
                  HorizontalAxisDecoration(
                    asFixedDecoration: true,
                    lineWidth: 0,
                    axisStep: 2,
                    showValues: true,
                    endWithChart: false,
                    axisValue: (value) => '$value',
                    legendFontStyle: Theme.of(context).textTheme.bodyMedium,
                    valuesAlign: TextAlign.center,
                    valuesPadding: const EdgeInsets.only(left: -axisWidth, bottom: -10),
                    showLines: false,
                    showTopValue: true,
                  )
                ],
                ChartState<double>(
                  data: ChartData(
                    _mappedValues,
                    axisMin: useAxis ? 2 : null,
                    axisMax: useAxis ? 8 : null,
                    dataStrategy: const DefaultDataStrategy(stackMultipleValues: true),
                  ),
                  itemOptions: WidgetItemOptions(widgetItemBuilder: (data) {
                    return const SizedBox();
                  }),
                  backgroundDecorations: [
                    GridDecoration(
                      showVerticalValues: true,
                      verticalLegendPosition: VerticalLegendPosition.bottom,
                      verticalValuesPadding: const EdgeInsets.only(top: 8.0),
                      verticalAxisStep: 2,
                      gridWidth: 1,
                      textStyle: Theme.of(context).textTheme.labelSmall,
                    ),
                  ],
                ),
              ),
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 32),
              child: AnimatedChart<double>(
                width: MediaQuery.of(context).size.width - axisWidth - 8,
                duration: const Duration(milliseconds: 450),
                state: ChartState<double>(
                  data: ChartData(
                    _mappedValues,
                    axisMin: useAxis ? 2 : null,
                    axisMax: useAxis ? 8 : null,
                    dataStrategy: const DefaultDataStrategy(stackMultipleValues: true),
                  ),
                  itemOptions: WidgetItemOptions(widgetItemBuilder: (data) {
                    return Stack(
                      clipBehavior: Clip.none,
                      children: [
                        Container(color: Colors.blue.withOpacity(0.2)),
                        Positioned(
                          top: -24,
                          left: 0,
                          right: 0,
                          child: Column(
                            children: [
                              Center(
                                  child: Text(
                                      _mappedValues[data.listIndex][data.itemIndex].max.toString()))
                            ],
                          ),
                        ),
                        Positioned(
                          top: -5,
                          left: 0,
                          right: 0,
                          child: Column(
                            children: [
                              Center(
                                child: Container(
                                  width: 10,
                                  height: 10,
                                  decoration: BoxDecoration(
                                      color: Theme.of(context).colorScheme.primary,
                                      borderRadius: const BorderRadius.all(Radius.circular(8)),
                                      border: Border.all(
                                          width: 1.4,
                                          color: Theme.of(context).colorScheme.surface)),
                                ),
                              )
                            ],
                          ),
                        ),
                      ],
                    );
                  }),
                  foregroundDecorations: [],
                  backgroundDecorations: [
                    GridDecoration(
                      horizontalAxisStep: 2,
                      showVerticalGrid: false,
                      showVerticalValues: true,
                      verticalLegendPosition: VerticalLegendPosition.bottom,
                      verticalValuesPadding: const EdgeInsets.only(top: 8.0),
                      verticalAxisStep: 1,
                      gridColor: Theme.of(context).colorScheme.outline.withOpacity(0.3),
                      dashArray: [8, 8],
                      gridWidth: 1,
                      textStyle: Theme.of(context).textTheme.labelSmall,
                    ),
                    WidgetDecoration(
                      widgetDecorationBuilder:
                          (context, chartState, itemWidth, verticalMultiplier) {
                        return Padding(
                          padding: chartState.defaultMargin,
                          child: Stack(
                            children: [
                              Positioned(
                                right: 0,
                                left: 0,
                                bottom: verticalMultiplier * 3.6,
                                child: CustomPaint(painter: DashedLinePainter()),
                              ),
                            ],
                          ),
                        );
                      },
                    ),
                    TargetAreaDecoration(
                      targetAreaFillColor: Theme.of(context).colorScheme.error.withOpacity(0.6),
                      targetLineColor: Colors.transparent,
                      lineWidth: 0,
                      targetMax: 2,
                      targetMin: 0,
                    ),
                    SparkLineDecoration(
                      lineWidth: 2,
                      lineColor: Theme.of(context).colorScheme.primary,
                      smoothPoints: true,
                      listIndex: 0,
                    ),
                  ],
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class DashedLinePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    double dashWidth = 8, dashSpace = 8, startX = 0;
    final paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 1;
    while (startX < size.width) {
      canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
      startX += dashWidth + dashSpace;
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}
@lukaknezic lukaknezic self-assigned this Sep 11, 2023
@lukaknezic lukaknezic added the bug Something isn't working label Sep 11, 2023
@lukaknezic
Copy link
Contributor

Hi @iamiota

Thanks for detailed report. I was able to reproduce it with your code easily and fixing it right now. There is a issue when calculating size for widgets when axisMin is set. I will add some tests to cover this case as well so we can be sure that values are displayed correctly 😄

@lukaknezic lukaknezic linked a pull request Sep 12, 2023 that will close this issue
@iamiota
Copy link
Author

iamiota commented Sep 20, 2023

@lukaknezic Hi Luka, thank you for your fix.❤️
I have tried the latest code, and the size of the widget is now correct.

However, I have encountered another issue when dynamically modifying the chart data.
If _mappedValues, axisMin and axisMax are the props of LineChart, and when the props change according to the following, the chart throws repeat errors: 'BoxConstraints has a negative minimum height.'
Additionally, in the video, it can be observed that the animation of the line extends beyond the horizontal axis area.
I used ClipRect to clip the AnimatedChart to prevent the line animation from being drawn across the full screen.

final List<List<ChartItem<double>>> _mappedValues = [
   [ChartItem(2.0), ChartItem(5.0), ChartItem(8.0), ChartItem(3.0), ChartItem(6.0)]
];
axisMin: 2
axisMax: 8

/// AnimatedChart's duration: const Duration(milliseconds: 500),
/// If milliseconds = 30, everything is good.
/// data change to

final List<List<ChartItem<double>>> _mappedValues = [
   [ChartItem(32.0), ChartItem(35.0), ChartItem(38.0), ChartItem(33.0), ChartItem(36.0)]
];
axisMin: 32
axisMax: 38
Sep-20-2023.18-47-26.mp4
image

@lukaknezic
Copy link
Contributor

negative minimum height should also be fixed, in same branch 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants