rajath_pai

LineChart

Aug 4th, 2021
895
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /// @name LineChart
  2. /// @version 0.0.5
  3. /// @description Simple line chart widget
  4. /// @author Patryk "PsychoX" Ludwikowski <patryk.ludwikowski.7+dart@gmail.com>
  5. /// @license MIT License (see https://mit-license.org/)
  6. import 'dart:math' as math show min, max;
  7. import 'dart:ui' as ui;
  8.  
  9. import 'package:flutter/material.dart';
  10.  
  11. import './PaintStyle.dart';
  12.  
  13. class LabelEntry {
  14.   final double value;
  15.   final String label;
  16.  
  17.   LabelEntry(this.value, this.label);
  18. }
  19.  
  20. /// Widget that allows to show data on line chart.
  21. ///
  22. /// All arguments, values and labels data should be sorted!
  23. /// Since both the arguments and the values must be `double` type,
  24. /// be aware of the precision.
  25. class LineChart extends StatelessWidget {
  26.   /// Constraints for the line chart.
  27.   final BoxConstraints constraints;
  28.  
  29.   // @TODO ? Both `_LineChartPainter` and `LineChart` have most the same fields.
  30.   //  `LineChart` is just mainly passing them to the painter. Shouldn't there be
  31.   //  only one class containing these data? Some `LineChartData` forged inside here
  32.   //  and then passed and used by the painter? :thinking:
  33.  
  34.   /// Padding around main drawng area. Necessary for displaying labels (around the chart).
  35.   final EdgeInsets padding;
  36.  
  37.   /* Arguments */
  38.   /// Collection of doubles as arguments.
  39.   final Iterable<double> arguments;
  40.  
  41.   /// Mappings of strings for doubles arguments, which allow to specify custom
  42.   /// strings as labels for certain arguments.
  43.   final Iterable<LabelEntry> argumentsLabels;
  44.  
  45.   /* Values */
  46.   /// Collection of data series as collections of next values on corresponding arguments.
  47.   final Iterable<Iterable<double>> values;
  48.  
  49.   /// Mappings of string for doubles values, which allow to specify custom
  50.   /// string as labels for certain values.
  51.   final Iterable<LabelEntry>? valuesLabels;
  52.  
  53.   /* Labels & lines styles */
  54.   /// Style of horizontal lines labels
  55.   final TextStyle? horizontalLabelsTextStyle;
  56.  
  57.   /// Style of vertical lines labels
  58.   final TextStyle? verticalLabelsTextStyle;
  59.  
  60.   /// Defines style of horizontal lines. Might be null in order to prevent lines from drawing.
  61.   final Paint? horizontalLinesPaint;
  62.  
  63.   /// Defines style of vertical lines. Might be null in order to prevent lines from drawing.
  64.   final Paint? verticalLinesPaint;
  65.  
  66.   // @TODO . expose it
  67.   final bool snapToLeftLabel = false;
  68.   final bool snapToTopLabel = true;
  69.   final bool snapToRightLabel = false;
  70.   final bool snapToBottomLabel = true;
  71.  
  72.   /* Series points & lines styles */
  73.   /// List of paint styles for series values points.
  74.   ///
  75.   /// On whole list null would use predefined set of styles.
  76.   /// On list entry null there will be no points for certain series.
  77.   final List<Paint?> seriesPointsPaints;
  78.  
  79.   /// List of paint styles for lines between next series points.
  80.   ///
  81.   /// On null there will be no lines.
  82.   final List<Paint>? seriesLinesPaints;
  83.  
  84.   final double additionalMinimalHorizontalLabelsInterval;
  85.   final double additionalMinimalVerticalLablesInterval;
  86.  
  87.   LineChart({
  88.     required this.constraints,
  89.     this.padding = const EdgeInsets.fromLTRB(32, 12, 20, 28),
  90.     required this.arguments,
  91.     required this.argumentsLabels,
  92.     required this.values,
  93.     this.valuesLabels,
  94.     this.horizontalLabelsTextStyle,
  95.     this.verticalLabelsTextStyle,
  96.     PaintStyle horizontalLinesStyle = const PaintStyle(color: Colors.grey),
  97.     PaintStyle? verticalLinesStyle, // null for default
  98.  
  99.     this.additionalMinimalHorizontalLabelsInterval = 8,
  100.     this.additionalMinimalVerticalLablesInterval = 8,
  101.     Iterable<PaintStyle?>?
  102.         seriesPointsStyles, // null would use predefined set of styles
  103.     Iterable<PaintStyle>? seriesLinesStyles, // null for default
  104.   })  : horizontalLinesPaint = horizontalLinesStyle.toPaint(),
  105.         verticalLinesPaint = verticalLinesStyle?.toPaint(),
  106.         seriesPointsPaints = _prepareSeriesPointsPaints(seriesPointsStyles),
  107.         seriesLinesPaints = _prepareSeriesLinesPaints(seriesLinesStyles) {
  108.     if ((seriesPointsStyles?.length ?? values.length) < values.length &&
  109.         12 /* default paints */ < values.length) {
  110.       throw "Too few `seriesPointsPaintStyle`s! Try define more or limit number of displayed series";
  111.     }
  112.     if ((seriesLinesStyles?.length ?? values.length) < values.length) {
  113.       throw "Too few `seriesLinesStyles`s! Try define more or limit number of displayed series";
  114.     }
  115.   }
  116.  
  117.   static List<Paint?> _prepareSeriesPointsPaints(
  118.       Iterable<PaintStyle?>? seriesPointsStyles) {
  119.     if (seriesPointsStyles == null) {
  120.       // Default paint for points
  121.       return List<Paint?>.unmodifiable(<Paint>[
  122.         PaintStyle(strokeWidth: 1.7, color: Colors.blue).toPaint(),
  123.         PaintStyle(strokeWidth: 1.7, color: Colors.red).toPaint(),
  124.         PaintStyle(strokeWidth: 1.7, color: Colors.yellow).toPaint(),
  125.         PaintStyle(strokeWidth: 1.7, color: Colors.green).toPaint(),
  126.  
  127.         PaintStyle(strokeWidth: 1.7, color: Colors.purple).toPaint(),
  128.         PaintStyle(strokeWidth: 1.7, color: Colors.deepOrange).toPaint(),
  129.         PaintStyle(strokeWidth: 1.7, color: Colors.brown).toPaint(),
  130.         PaintStyle(strokeWidth: 1.7, color: Colors.lime).toPaint(),
  131.  
  132.         PaintStyle(strokeWidth: 1.7, color: Colors.indigo).toPaint(),
  133.         PaintStyle(strokeWidth: 1.7, color: Colors.pink).toPaint(),
  134.         PaintStyle(strokeWidth: 1.7, color: Colors.amber).toPaint(),
  135.         PaintStyle(strokeWidth: 1.7, color: Colors.teal).toPaint(),
  136.  
  137.         // For more, user should specify them :F
  138.       ]);
  139.     } else {
  140.       return seriesPointsStyles.map((style) => style?.toPaint()).toList();
  141.     }
  142.   }
  143.  
  144.   static List<Paint>? _prepareSeriesLinesPaints(
  145.       Iterable<PaintStyle>? seriesLinesStyles) {
  146.     if (seriesLinesStyles == null) {
  147.       return null;
  148.     } else {
  149.       return seriesLinesStyles.map((style) => style.toPaint()).toList();
  150.     }
  151.   }
  152.  
  153.   @override
  154.   Widget build(BuildContext context) {
  155.     return ConstrainedBox(
  156.         constraints: this.constraints,
  157.         child: CustomPaint(
  158.             painter: _LineChartPainter(
  159.           padding: padding,
  160.           arguments: arguments,
  161.           argumentsLabels: argumentsLabels,
  162.           values: values,
  163.           valuesLabels: valuesLabels,
  164.           horizontalLabelsTextStyle:
  165.               horizontalLabelsTextStyle ?? Theme.of(context).textTheme.caption,
  166.           verticalLabelsTextStyle:
  167.               verticalLabelsTextStyle ?? Theme.of(context).textTheme.caption,
  168.           horizontalLinesPaint: horizontalLinesPaint,
  169.           verticalLinesPaint: verticalLinesPaint,
  170.           additionalMinimalHorizontalLabelsInterval:
  171.               additionalMinimalHorizontalLabelsInterval,
  172.           additionalMinimalVerticalLablesInterval:
  173.               additionalMinimalVerticalLablesInterval,
  174.           seriesPointsPaints: seriesPointsPaints,
  175.           seriesLinesPaints: seriesLinesPaints,
  176.         )));
  177.   }
  178. }
  179.  
  180. class _LineChartPainter extends CustomPainter {
  181.   /// Padding around main drawng area. Necessary for displaying labels (around the chart).
  182.   final EdgeInsets padding;
  183.  
  184.   /* Arguments */
  185.   /// Collection of doubles as arguments.
  186.   final Iterable<double> arguments;
  187.  
  188.   /// Mappings of strings for doubles arguments, which allow to specify custom
  189.   /// strings as labels for certain arguments.
  190.   final Iterable<LabelEntry>? argumentsLabels;
  191.  
  192.   /* Values */
  193.   /// Collection of data series as collections of next values on corresponding arguments.
  194.   final Iterable<Iterable<double>> values;
  195.  
  196.   /// Mappings of string for doubles values, which allow to specify custom
  197.   /// string as labels for certain values.
  198.   final Iterable<LabelEntry>? valuesLabels;
  199.  
  200.   /* Labels & lines styles */
  201.   /// Style of horizontal lines labels
  202.   final TextStyle? horizontalLabelsTextStyle;
  203.  
  204.   /// Style of vertical lines labels
  205.   final TextStyle? verticalLabelsTextStyle;
  206.  
  207.   /// Defines style of horizontal lines. Might be null in order to prevent lines from drawing.
  208.   final Paint? horizontalLinesPaint;
  209.  
  210.   /// Defines style of vertical lines. Might be null in order to prevent lines from drawing.
  211.   final Paint? verticalLinesPaint;
  212.  
  213.   // @TODO . expose it
  214.   final bool snapToLeftLabel = false;
  215.   final bool snapToTopLabel = true;
  216.   final bool snapToRightLabel = false;
  217.   final bool snapToBottomLabel = true;
  218.  
  219.   /* Series points & lines styles */
  220.   /// Collection of paint styles for series values points.
  221.   ///
  222.   /// On whole argument null would use predefined set of styles.
  223.   /// On collection entry null there will be no points for certain series.
  224.   final Iterable<Paint?> seriesPointsPaints;
  225.  
  226.   /// Collection of paint styles for lines between next series points.
  227.   ///
  228.   /// On null there will be no lines.
  229.   final Iterable<Paint>? seriesLinesPaints;
  230.  
  231.   /* Runtime */
  232.   /// Minimal allowed interval between horizontal lines. Calculated from font size.
  233.   final double minimalHorizontalLabelsInterval;
  234.  
  235.   /// Maximal value of all data series values
  236.   double maxValue = -double.maxFinite;
  237.  
  238.   /// Minimal value of all data series values
  239.   double minValue = double.maxFinite;
  240.  
  241.   double _minimalHorizontalRatio = 0;
  242.   double _minimalVerticalRatio = 0;
  243.  
  244.   /// Creates `_LineChartPainter` (`CustomPainter`) with given data and styling.
  245.   _LineChartPainter({
  246.     this.padding = const EdgeInsets.fromLTRB(40, 8, 8, 32),
  247.     required this.arguments,
  248.     required this.argumentsLabels,
  249.     required this.values,
  250.     required this.valuesLabels,
  251.     required this.horizontalLabelsTextStyle,
  252.     required this.verticalLabelsTextStyle,
  253.     required this.horizontalLinesPaint,
  254.     required this.verticalLinesPaint,
  255.     double additionalMinimalHorizontalLabelsInterval = 8,
  256.     double additionalMinimalVerticalLablesInterval = 8,
  257.     required this.seriesPointsPaints,
  258.     required this.seriesLinesPaints,
  259.   }) : this.minimalHorizontalLabelsInterval =
  260.             (horizontalLabelsTextStyle?.fontSize ?? 12) +
  261.                 additionalMinimalHorizontalLabelsInterval {
  262.     // Find max & min values of data to be show
  263.     for (Iterable<double> series in values) {
  264.       for (double value in series) {
  265.         if (value > maxValue) {
  266.           maxValue = value;
  267.         } else if (value < minValue) {
  268.           minValue = value;
  269.         }
  270.       }
  271.     }
  272.  
  273.     if (valuesLabels != null) {
  274.       // Find minimal vertical ratio to fit all provided values labels
  275.       Iterator<LabelEntry> entry = valuesLabels!.iterator;
  276.       entry.moveNext();
  277.       double lastValue = entry.current.value;
  278.  
  279.       while (entry.moveNext()) {
  280.         final double goodRatio =
  281.             minimalHorizontalLabelsInterval / (entry.current.value - lastValue);
  282.         if (goodRatio > _minimalVerticalRatio) {
  283.           _minimalVerticalRatio = goodRatio;
  284.         }
  285.  
  286.         lastValue = entry.current.value;
  287.       }
  288.     }
  289.  
  290.     if (argumentsLabels != null) {
  291.       // Find minimal horizontal ratio to fit all provided arguments labels
  292.       Iterator<LabelEntry> entry = argumentsLabels!.iterator;
  293.       entry.moveNext();
  294.       double lastValue = entry.current.value;
  295.       double lastWidth =
  296.           _getLabelTextPainter(entry.current.label, verticalLabelsTextStyle)
  297.               .width;
  298.  
  299.       while (entry.moveNext()) {
  300.         final double nextValue = entry.current.value;
  301.         final double nextWidth =
  302.             _getLabelTextPainter(entry.current.label, verticalLabelsTextStyle)
  303.                 .width;
  304.  
  305.         final double goodRatio = ((lastWidth + nextWidth) / 2 +
  306.                 additionalMinimalVerticalLablesInterval) /
  307.             (nextValue - lastValue);
  308.         if (goodRatio > _minimalHorizontalRatio) {
  309.           _minimalHorizontalRatio = goodRatio;
  310.         }
  311.  
  312.         lastValue = nextValue;
  313.         lastWidth = nextWidth;
  314.       }
  315.     }
  316.   }
  317.  
  318.   @override
  319.   void paint(Canvas canvas, Size size) {
  320.     final double width = size.width - padding.left - padding.right;
  321.     final double height = size.height - padding.top - padding.bottom;
  322.  
  323.     /* Horizontal lines with labels */
  324.     double valuesOffset = 0; // @TODO ? could be used in future for scrolling
  325.     double verticalRatio;
  326.  
  327.     {
  328.       Iterable<LabelEntry> labels;
  329.  
  330.       // If no labels provided - generate them!
  331.       if (valuesLabels == null) {
  332.         final double optimalStepValue =
  333.             _calculateOptimalStepValue(maxValue - minValue, height);
  334.         int stepsNumber = 1;
  335.  
  336.         // Find bottom line value
  337.         double bottomValue = 0;
  338.         if (minValue > 0) {
  339.           while (bottomValue < minValue) {
  340.             bottomValue += optimalStepValue;
  341.           }
  342.           bottomValue -= optimalStepValue;
  343.         } else {
  344.           while (bottomValue > minValue) {
  345.             bottomValue -= optimalStepValue;
  346.           }
  347.         }
  348.         valuesOffset = bottomValue;
  349.  
  350.         // Find top line value
  351.         double topValue = bottomValue;
  352.         while (topValue < maxValue) {
  353.           topValue += optimalStepValue;
  354.           stepsNumber += 1;
  355.         }
  356.  
  357.         // Set labels iterable from prepared generator
  358.         Iterable<LabelEntry> generator(double optimalStepValue, int stepsNumber,
  359.             [double value = 0.0]) sync* {
  360.           //double value = _bottomValue;
  361.           for (int i = 0; i < stepsNumber; i++) {
  362.             yield LabelEntry(
  363.                 value,
  364.                 value
  365.                     .toString()); // @TODO , choose better precision based on optimal step value while parsing to string
  366.             value += optimalStepValue;
  367.           }
  368.         }
  369.  
  370.         labels = generator(optimalStepValue, stepsNumber, bottomValue);
  371.  
  372.         if (!snapToTopLabel) {
  373.           topValue = maxValue;
  374.         }
  375.         if (!snapToBottomLabel) {
  376.           bottomValue = valuesOffset = minValue;
  377.         }
  378.  
  379.         // Calculate vertical ratio of pixels per value
  380.         // Note: There is no empty space already
  381.         verticalRatio = height / (topValue - bottomValue);
  382.       }
  383.       // If labels provided - use them
  384.       else {
  385.         // Set labels iterable as the provided list
  386.         labels = valuesLabels!;
  387.  
  388.         // Use minimal visible value as offset.
  389.         // Note: `minValue` is calculated in constructor and includes miniaml labels values.
  390.         valuesOffset = minValue;
  391.  
  392.         // Calculate vertical ratio of pixels per value
  393.         // Note: `_minimalVerticalRatio` is calculated in constructor
  394.         final double topValue =
  395.             snapToTopLabel ? math.max(maxValue, labels.last.value) : maxValue;
  396.         final double bottomValue = snapToBottomLabel
  397.             ? math.min(minValue, labels.first.value)
  398.             : minValue;
  399.         final double noEmptySpaceRatio = height / (topValue - bottomValue);
  400.         verticalRatio = math.max(_minimalVerticalRatio, noEmptySpaceRatio);
  401.       }
  402.  
  403.       // Draw the horizontal lines and labels
  404.       for (LabelEntry tuple in labels) {
  405.         if (tuple.value < valuesOffset) continue;
  406.         final double yOffset = (size.height -
  407.             padding.bottom -
  408.             (tuple.value - valuesOffset) * verticalRatio);
  409.         if (yOffset < padding.top) break;
  410.  
  411.         // Draw line
  412.         if (horizontalLinesPaint != null) {
  413.           canvas.drawLine(
  414.               Offset(padding.left, yOffset),
  415.               Offset(size.width - padding.right, yOffset),
  416.               horizontalLinesPaint!);
  417.         }
  418.  
  419.         // Draw label
  420.         TextPainter(
  421.             text: TextSpan(text: tuple.label, style: horizontalLabelsTextStyle),
  422.             textAlign: TextAlign.right,
  423.             textDirection: TextDirection.ltr)
  424.           ..layout(minWidth: padding.left - 4)
  425.           ..paint(
  426.               canvas,
  427.               Offset(
  428.                   0,
  429.                   yOffset -
  430.                       (horizontalLabelsTextStyle?.fontSize ?? 12) / 2 -
  431.                       1));
  432.       }
  433.     }
  434.  
  435.     /* Vertical lines with labels */
  436.     double argumentsOffset = 0;
  437.     final double xOffsetLimit = size.width - padding.right;
  438.     double horizontalRatio;
  439.  
  440.     {
  441.       Iterable<LabelEntry> labels;
  442.  
  443.       // If no labels provided - generate them!
  444.       if (argumentsLabels == null) {
  445.         throw "not implemented";
  446.         // @TODO . after few hot days of thinking about the problem for 1-2 hour a day, I just gave up.
  447.         // The hardest in the problem is that there must be trade-off between space for labels and max lines,
  448.         // but keep in mind that the label values should be in some human-readable steps (0.5, 10, 0.02...).
  449.       }
  450.       // If labels provided - use them
  451.       else {
  452.         // Set labels iterable as the provided list
  453.         labels = argumentsLabels!;
  454.  
  455.         // Use first visible argument as arguments offset
  456.         argumentsOffset = labels.first.value;
  457.  
  458.         if (!snapToLeftLabel) {
  459.           argumentsOffset = arguments.first;
  460.         }
  461.  
  462.         // Calculate vertical ratio of pixels per value
  463.         // Note: `_minimalHorizontalRatio` is calculated in constructor
  464.         final double leftMost = snapToLeftLabel
  465.             ? math.min(arguments.first, labels.first.value)
  466.             : arguments.first;
  467.         final double rightMost = snapToRightLabel
  468.             ? math.max(arguments.last, labels.last.value)
  469.             : arguments.last;
  470.         final double noEmptySpaceRatio = width / (rightMost - leftMost);
  471.         horizontalRatio = math.max(_minimalHorizontalRatio, noEmptySpaceRatio);
  472.       }
  473.  
  474.       // Draw the vertical lines and labels
  475.       for (LabelEntry tuple in labels) {
  476.         if (tuple.value < argumentsOffset) continue;
  477.         final double xOffset =
  478.             padding.left + (tuple.value - argumentsOffset) * horizontalRatio;
  479.         if (xOffset > xOffsetLimit) break;
  480.  
  481.         // Draw line
  482.         if (verticalLinesPaint != null) {
  483.           canvas.drawLine(
  484.               Offset(xOffset, padding.top),
  485.               Offset(xOffset, size.height - padding.bottom),
  486.               verticalLinesPaint!);
  487.         }
  488.  
  489.         // Draw label
  490.         final TextPainter textPainter = TextPainter(
  491.             text: TextSpan(text: tuple.label, style: verticalLabelsTextStyle),
  492.             textDirection: TextDirection.ltr)
  493.           ..layout();
  494.         textPainter.paint(
  495.             canvas,
  496.             Offset(xOffset - textPainter.width / 2,
  497.                 size.height - (verticalLabelsTextStyle?.fontSize ?? 12) - 8));
  498.       }
  499.     }
  500.  
  501.     /* Points and lines between subsequent */
  502.     Iterator<Iterable<double?>> series = values.iterator;
  503.     Iterator<Paint?> linesPaints = seriesLinesPaints == null
  504.         ? <Paint>[].iterator
  505.         : (seriesLinesPaints ?? []).iterator;
  506.     Iterator<Paint?> pointsPaints = seriesPointsPaints.iterator;
  507.     while (series.moveNext()) {
  508.       List<Offset> points = [];
  509.       Iterator<double?> value = series.current.iterator;
  510.       Iterator<double> argument = arguments.iterator;
  511.       while (value.moveNext()) {
  512.         argument.moveNext();
  513.         if (value.current == null || value.current == double.nan) continue;
  514.  
  515.         if (argument.current < argumentsOffset) continue;
  516.         final double xOffset = padding.left +
  517.             (argument.current - argumentsOffset) * horizontalRatio;
  518.         if (xOffset > xOffsetLimit) break;
  519.  
  520.         if (value.current! < valuesOffset) continue;
  521.         final double yOffset = size.height -
  522.             padding.bottom -
  523.             (value.current! - valuesOffset) * verticalRatio;
  524.         if (yOffset < padding.top) continue;
  525.  
  526.         points.add(Offset(xOffset, yOffset));
  527.       }
  528.  
  529.       // Lines
  530.       if (linesPaints.moveNext() && linesPaints.current != null) {
  531.         canvas.drawPath(
  532.             Path()..addPolygon(points, false), linesPaints.current!);
  533.       }
  534.  
  535.       // Points
  536.       if (pointsPaints.moveNext() && pointsPaints.current != null) {
  537.         canvas.drawPoints(ui.PointMode.points, points, pointsPaints.current!);
  538.       }
  539.     }
  540.   }
  541.  
  542.   @override
  543.   bool shouldRepaint(_LineChartPainter old) =>
  544.       (this.arguments != old.arguments ||
  545.           this.values != old.values ||
  546.           this.argumentsLabels != old.argumentsLabels ||
  547.           this.valuesLabels != old.valuesLabels ||
  548.           this.seriesPointsPaints != old.seriesPointsPaints ||
  549.           this.seriesLinesPaints != old.seriesLinesPaints ||
  550.           this.horizontalLabelsTextStyle != old.horizontalLabelsTextStyle ||
  551.           this.verticalLabelsTextStyle != old.verticalLabelsTextStyle ||
  552.           this.padding != old.padding //
  553.       );
  554.  
  555.   // ..., 0.01, 0.02, 0.05, 0.1, [0.125], 0.2, [0.25], 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, ...
  556.   double _calculateOptimalStepValue(double valueRange, double height) {
  557.     final int maxSteps = height ~/ minimalHorizontalLabelsInterval;
  558.     if (maxSteps <= 0) {
  559.       throw "invalid max lines!";
  560.     }
  561.     double interval = valueRange / maxSteps;
  562.     if (interval > 1) {
  563.       int zeros = 0;
  564.       while (interval >= 10) {
  565.         interval = interval / 10;
  566.         zeros += 1;
  567.       }
  568.       /**/ if (interval <= 1) {
  569.         interval = 1;
  570.       } else if (interval <= 2) {
  571.         interval = 2;
  572.       } else if (interval <= 5) {
  573.         interval = 5;
  574.       }
  575.       for (; zeros-- != 0;) {
  576.         interval *= 10;
  577.       }
  578.     } else {
  579.       // @TODO ! not working at all for lower :C
  580.       int zeros = 0;
  581.       while (interval < 0) {
  582.         interval = interval * 10;
  583.         zeros += 1;
  584.       }
  585.       /**/ if (interval <= 1) {
  586.         interval = 1;
  587.       } else if (interval <= 2) {
  588.         interval = 2;
  589.       } else if (interval <= 5) {
  590.         interval = 5;
  591.       }
  592.       for (; zeros-- != 0;) {
  593.         interval /= 10;
  594.       }
  595.     }
  596.     return interval;
  597.   }
  598.  
  599.   TextPainter _getLabelTextPainter(String text, TextStyle? style) {
  600.     return TextPainter(
  601.         text: TextSpan(text: text, style: style),
  602.         textDirection: TextDirection.ltr)
  603.       ..layout();
  604.   }
  605. }
RAW Paste Data