Skip to content

Latest commit

 

History

History
731 lines (591 loc) · 32.4 KB

README.org

File metadata and controls

731 lines (591 loc) · 32.4 KB

Table of contents

New Releases - Features

features in latest release (todo number)

  • Iterative auto label layout
    • Chart automatically indicates X label overlap, and follows with rule-based iterative re-layout. By default, re-layout iterations are:
      • TODO

Flutter Charts - How to include the flutter_charts library in your application

Flutter Charts is a charting library for Flutter, written in Flutter. Currently, column chart and line chart are supported.

The package is published on pub for inclusion in your application’s pubspec.yaml: The Installing tab on https://pub.dartlang.org/packages/flutter_charts contains instructions on how to include the flutter_charts package in your application.

A chart created using flutter_charts - an example

There is an example application in flutter_charts: example/lib/main.dart. It shows how a Flutter Chart can be included in a Flutter application.

You can run the example application using one of the methods (6, 7) in the paragraph below.

This application is also used as a base to show several sample charts in the paragraphs below.

A sample Vertical Bar Chart (Column Chart)

doc/readme_images/README.org_20171102_154245_27063qmN.png

A sample point and Line Chart (Line Chart)

doc/readme_images/README.org_20171102_154329_270633wT.png

The output is generated from semi-random data. You can click the blue + button to rerun the chart with a different set of rows.

Known packages, libraries and apps that use this this flutter_charts package

  1. Michael R. Fairhurst’s Language reader app - see https://github.com/MichaelRFairhurst/flutter-language-reader-app

Flutter Charts - an overview: data, options, classes

Before we show several examples of charts, a few notes.

  • The ChartData class: allows to define data - X labels, Y values, (optional) Y labels, each-dataRow (series) legends, each-dataRow (series) color. The list below provides a summary description of each item
    • X labels: ChartData.xLabels allow to define X labels. Setting xLabels is required, but client can set them to empty strings.
    • Y values: ChartData.dataRows allow to define Y values in rows. Assumption: size of each data row in ChartData.dataRows is the same, and each data row size == ChartData.xLabels.size
    • Y labels (optional): Normally, Y labels are generated from data. The option ChartOptions.useUserProvidedYLabels (default true), asks flutter_charts to data-generate Y labels. If this option is set to false, then ChartData.yLabels must be set. Any number of such user-provided Y labels is allowed.
    • Each-dataRow (each series) legends: ChartData.dataRowsLegends allow to define a legend for each data row in ChartData.dataRows. Assumption: ChartData.dataRows.size == ChartData.dataRowsLegends.size
    • Each-dataRow (each series) color: ChartData.dataRowsColors allow to define a color for each data row in ChartData.dataRows. Assumption: ChartData.dataRows.size == ChartData.dataRowsColors.size
  • The ChartOptions class: allows to define options, by using it’s defaults, or setting some options to non default values. There are also LineChartOptions and VerticalBarChartOptions classes.
  • Support for randomly generated data, colors, labels: Flutter Charts also provides randomly generated data, in the class RandomChartData. This class generates:
    • Y values data,
    • X labels,
    • Series colors,
    • Series legends
  • Currently the only purpose of RandomChartData is for use in the examples below. To be clear, RandomChartData Y values, series colors, and series legends are not completely random - they hardcode some demoable label, legends, color values, and data ranges (data random within the range).

Experimenting with Flutter Charts: Using the included example app file:example/lib/main.dart

There are multiple ways to experiment with Flutter Charts from your computer. We describe running Flutter Charts in development mode on your device (Android, iOS - follow 1, 2 or 3, 4 and 6), or alternatively on a device emulator (device emulator running from an IDE such as IntelliJ with Android Studio installed - follow 1, 2 or 3, 5, 6 or 7).

  1. Install Flutter on your computer. See https://flutter.io/ installation section.
  2. Clone flutter_charts code from Github to your computer. Needs git client.
    cd DIRECTORY_OF_CHOICE
    git clone https://github.com/mzimmerm/flutter_charts.git
    # clone will create directory  flutter_charts
    cd flutter_charts
        
  3. (Alternative to 2.): Download and unzip flutter_charts code from Github
  4. Prepare a physical device (must be set to Development Mode) to run applications from your computer. Then connect a android device in development mode to your computer. See https://www.kingoapp.com/root-tutorials/how-to-enable-usb-debugging-mode-on-android.htm
  5. (Alternative to 4.): Prepare and start an Android device emulator on your computer.
  6. Run Flutter Charts demo app from command line (this will work in both method 4. and method 5.)
    cd DIRECTORY_OF_CHOICE/flutter_charts 
    flutter run example/lib/main.dart 
        
  7. (Alternative to 6.) Run Flutter Charts demo app from IDE. This will work only with method 5.
    • Start IntelliJ IDE, create a project in the DIRECTORY_OF_CHOICE/flutter_charts start an Android emulator, then click on the Run button in Intellij (which should show the file:example/lib/main.dart in the run button).

Flutter Charts - examples: LineChart and VerticalBarChart. Code and resulting charts

Flutter Charts code allow to define the following data elements:

Data (Y values)User-Provided or Random
X LabelsUser-Provided or Random
Options including ColorsUser-Provided or Random
Data Rows LegendsUser-Provided or Random
Y LabelsUser-Provided or Data-Generated

The examples below show a few alternative code snippets (User-Provided or Random data, labels, option) and the resulting charts.

The chart images were obtained by substituting the code snippet to the file:example/lib/main.dart code.

Random Data (Y values), Random X Labels, Random Colors, Random Data Rows Legends, Data-Generated Y Labels.

This example shows that Data-Generated Y labels is the default. Flutter Charts support reasonably intelligently generated Y Labels from data, including dealing with negatives.

Code in defineOptionsAndData():

void defineOptionsAndData() {
  _lineChartOptions = new LineChartOptions();
  _verticalBarChartOptions = new VerticalBarChartOptions();
  _chartData = new RandomChartData(useUserProvidedYLabels: _lineChartOptions.useUserProvidedYLabels);
}

Result line chart:

doc/readme_images/README.org_20171102_172324_27063E7Z.png

Result vertical bar chart:

doc/readme_images/README.org_20171102_173422_27063ePm.png

User-Provided Data (Y values), User-Provided X Labels, Random Colors, User-Provided Data Rows Legends, Data-Generated Y Labels,

Code in defineOptionsAndData():

void defineOptionsAndData() {
  _lineChartOptions = new LineChartOptions();
  _verticalBarChartOptions = new VerticalBarChartOptions();
  _chartData = new ChartData();
  _chartData.dataRowsLegends = [
    "Spring",
    "Summer",
    "Fall",
    "Winter"];
  _chartData.dataRows = [
    [10.0, 20.0,  5.0,  30.0,  5.0,  20.0, ],
    [30.0, 60.0, 16.0, 100.0, 12.0, 120.0, ],
    [25.0, 40.0, 20.0,  80.0, 12.0,  90.0, ],
    [12.0, 30.0, 18.0,  40.0, 10.0,  30.0, ],
  ];
  _chartData.xLabels =  ["Wolf", "Deer", "Owl", "Mouse", "Hawk", "Vole"];
  _chartData.assignDataRowsDefaultColors();
  // Note: ChartOptions.useUserProvidedYLabels default is still used (false);
}

Result line chart:

doc/readme_images/README.org_20171102_180657_27063rZs.png

Result vertical bar chart:

doc/readme_images/README.org_20171102_180915_270634jy.png

User-Provided Data (Y values), User-Provided X Labels, Random Colors, User-Provided Data Rows Legends, User-Provided Y Labels

This example show how to use the option useUserProvidedYLabels, and scaling of data to the Y labels range.

Code in defineOptionsAndData():

void defineOptionsAndData() {
  // This example shows user defined Y Labels.
  //   When setting Y labels by user, the dataRows value scale
  //   is irrelevant. User can use for example interval <0, 1>,
  //   <0, 10>, or any other, even negative ranges. Here we use <0-10>.
  //   The only thing that matters is  the relative values in the data Rows.

  // Note that current implementation sets
  // the minimum of dataRows range (1.0 in this example)
  // on the level of the first Y Label ("Ok" in this example),
  // and the maximum  of dataRows range (10.0 in this example)
  // on the level of the last Y Label ("High" in this example).
  // This is not desirable, we need to add a userProvidedYLabelsBoundaryMin/Max.
  _lineChartOptions = new LineChartOptions();
  _verticalBarChartOptions = new VerticalBarChartOptions();
  _chartData = new ChartData();
  _chartData.dataRowsLegends = [
    "Java",
    "Dart",
    "Python",
    "Newspeak"];
  _chartData.dataRows = [
    [9.0, 4.0,  3.0,  9.0, ],
    [7.0, 6.0,  7.0,  6.0, ],
    [4.0, 9.0,  6.0,  8.0, ],
    [3.0, 9.0, 10.0,  1.0, ],
  ];
  _chartData.xLabels =  ["Fast", "Readable", "Novel", "Use"];
  _chartData.dataRowsColors = [
    Colors.blue,
    Colors.yellow,
    Colors.green,
    Colors.amber,
  ];
  _lineChartOptions.useUserProvidedYLabels = true; // use the labels below on Y axis
  _chartData.yLabels = [
    "Ok",
    "Higher",
    "High",
  ];
}

Result line chart:

doc/readme_images/README.org_20171102_191037_27063qtB.png (Disclaimer: Not actually measured)

Result vertical bar chart: Here the Y values should be numeric (if any) as manual labeling “Ok”, “Higher”, High” does not make sense for stacked type charts.

doc/readme_images/README.org_20171102_191138_2706333H.png (Disclaimer: Not actually measured)

VerticalBar Chart - one more example, showing positive/negative stacks:

User-Provided Data (Y values), User-Provided X Labels, User-Provided Colors, User-Provided Data Rows Legends, User-Provided Y Labels

This example has again user defined Y Labels, with a bar chart, using the smart auto-layout of user defined Y Labels. The chart shows negative and positive values similar to %down/%up stock charts.

Code in defineOptionsAndData():

void defineOptionsAndData() {
  // This example shows user defined Y Labels with
  // a bar chart, showing negative and positive values
  // similar to %down/%up stock charts.
  _lineChartOptions = new LineChartOptions();
  _verticalBarChartOptions = new VerticalBarChartOptions();
  _chartData = new ChartData();
  _chartData.dataRowsLegends = [
    "-2%_0%",
    "<-2%",
    "0%_+2%",
    ">+2%"];
  // each column absolute values should add to same number todo - 100 would make more sense, to represent 100% of stocks in each category
  _chartData.dataRows = [
    [-9.0, -8.0,  -8.0,  -5.0, -8.0, ],
    [-1.0, -2.0,  -4.0,  -1.0, -1.0, ],
    [7.0, 8.0,  7.0, 11.0, 9.0, ],
    [3.0, 2.0, 1.0,  3.0,  3.0, ],
  ];
  _chartData.xLabels =  ["Energy", "Health", "Finance", "Chips", "Oil"];
  _chartData.dataRowsColors = [
    Colors.grey,
    Colors.red,
    Colors.greenAccent,
    Colors.black,
  ];
  _lineChartOptions.useUserProvidedYLabels = false; // use labels below
  //_chartData.yLabels = [
  //  "Ok",
  //  "Higher",
  //  "High",
  //];
}

Result vertical bar chart:

doc/readme_images/README.org_20171102_195745_27063ECO.png

(there is a bug here,see Known Bugs)

Bugs status

  • [ ] Chart area needs clipping in the application
  • [ ] Take a look at the stock charts example. There is a bug reverting series on the negative values - both negative dataRows, and dataRowsColors must be reverted for the chart stacks to show in intended order (black, green grey red from top). But even then, dataRowsLegends are incorrect.
  • [X] mzimmerm#5 - Coloring support: Make line chart dot colors settable
  • [ ]

Enhancements in progress or planned

Enhancements:

TextPainter notes

If layout() is called, following paint() always prints full line (goes to line 2 if the text contains LF).

Labels auto-fit: scale, tilt or skip labels to fit using HorizontalLabelsContainer

  • [ ] HorizontalFixedWidthAutoScaledLabelsContainer add method applyParentOffset, pass to LabelPainters
  • [ ] ? create class XLayoutContainter extends HorizontalFixedWidthAutoScaledLabelsContainer
    • [ ] member painters XLayoutPainter s
  • [ ] Changes refactoring needed ahead
    • [ ] LabelPainter must accept AND HAVE MEMBERS all sizing outside options: textDirection, textAlign, textScaleFactor, labelTextStyle (KEEP THIS ONE ON OPTIONS DEFAULT)
  • [-] Make X, Y labels and legend labels not to run into the neighbor, if too long.
    • [ ] X
      • [ ] First rotate by 90 degrees
      • [ ] Next decrease text size
    • [-] Legend
      • [ ] Next clip text (left justify and clip)
      • [X] LegendLayouter, LegendLayouterOutput
      • [X] LegendLayouter layout - extract inside to layoutCore (code start with “var legendMax = ui.Size.zero;” - so layout calls layoutCore()
      • [ ] test
    • [ ] LegendLayouter, remove iterables and replace with fixed lists
    • [ ] test
    • [ ] LegendLayouter, new methods
      • [ ] evalLabelPaintersForTooBig
        • [ ] Look for textPainterForLabel in LegendLayouter
        • [ ] just create new LabelPainter for each series
      • [ ] void layoutUntilFitsParent() -
        • [ ] Decrease text size to min scale 0.5 original: 1, 0.75, 0.5
        • [ ] size = 1
        • [ ] call layoutCore
        • [ ] call evalLabelPaintersForTooBig
        • [ ] if evalLabelPaintersForTooBig.isNotEmpty()
          • [ ] size = 0.75
          • [ ] call layoutCore
          • [ ] callevalLabelPaintersForTooBig
          • [ ] if evalLabelPaintersForTooBig.isNotEmpty()
            • [ ] trimLayouterOutputLabels = on all outputs, go to each, and if the output.labelPainter overflows, left justify it and trim , collect and return the trimmed outputs
      • [ ] Now, from layout, call layoutUntilFitsParent instead of layoutCore
  • [ ] Move label_painter.dart from util to the chart dirs somewhere
  • [ ] TEST
  • [ ] Clip chart to not paint outside area provided by Flutter app.
  • [ ] Clip labels and legends not to run into the neighbor, if too long.

// todo -5 class YGridLinesLayoutPainter {

List<YLinePresenter> yLinePresenters = new List();

/// Apply offset in parent. This call positions the Y Label (this instance) /// to the absolute position in the chart’s available size void applyParentOffset(ui.Offset offset) { yLinePresenters.forEach((yLinePresenter) => yLinePresenter.applyParentOffset(offset)); }

void paint(ui.Canvas canvas) { yLinePresenters.forEach((yLinePresenter) => yLinePresenter.paint(canvas)); }

}

// todo -5 class YLinePresenter eytends line_presenter.LinePresenter {

/// Constructor from parent YLinePresenter({ui.Offset lineFrom, ui.Offset lineTo, ui.Paint linePaint}) {

this.linePaint = linePaint; this.lineFrom = lineFrom; this.lineTo = lineTo; }

/// The y offset of horizical grid line in the middle of column. /// /// On all chart types, this is the same as [_tickY] - allows /// to draw a line in the middle of the column (middle of label). /// /// Generally intended to be used on charts showing data as points /// (e.g. line charts), but not on bucket type charts such as bar chart. /// /// On some chart types, [_horizGridLineY] may not be drawn; /// [_leftHorizGridLineY] and [_rightHorizGridLineY] may be used instead. double _horizGridLineY;

/// The y offset of horizical grid line on the left border of the chart column. /// /// See discussion in [_horizGridLineY]. double _leftHorizGridLineY;

/// The y offset of horizical grid line on the right border of the chart column. /// /// See discussion in [_horizGridLineY]. double _rightHorizGridLineY;

/// The y offset of point that should /// show a “tick dash” for the label center on the y axis (unused). /// /// Equal to the y offset of Y label middle point. /// /// First “tick dash” is on the first label, last on the last label. double _tickY;

/// Absolute offset in chart ui.Offset _offset;

/// Apply offset in parent. This call positions the Y Label (this instance) /// to the absolute position in the chart’s available size void applyParentOffset(ui.Offset offset) { _horizGridLineY += offset.dy; _leftHorizGridLineY += offset.dy; _rightHorizGridLineY += offset.dy; _tickY += offset.dy;

// Duplicated info _offset = new ui.Offset(_tickY, offset.dy); }

void paint(ui.Canvas canvas) { // todo -5 }

}

labelTextStyle: _options.labelTextStyle, labelTextDirection: _options.labelTextDirection, labelTextAlign: _options.labelTextAlign, // center text in available space labelTextScaleFactor: _options.labelTextScaleFactor,

For ChartOptions.useUserProvidedYLabels = true. See example with User defined YLabels: Current implementation sets the minimum of dataRows range (1.0 in the example) on the level of the first Y Label (“Ok” in this example), and the maximum of dataRows range (10.0 in this example) on the level of the last Y Label (“High” in this example). This is not desirable, we need to add a userProvidedYLabelsBoundaryMin/Max.

Create a document / image showing layout and spacing - show option variables on image

Simple:

Add options to hide the grid (keep axes)

Add options to hide axes (if axes not shown, labels should not show?)

Decrease option for default spacing around the Y axis.

First, probably need to provide tooltips

Next, a few more chart types: Spline line chart (stacked line chart), Grouped VerticalBar chart,

Next, re-implement the layout more generically and clearly. Space saving changes such as tilting labels.

Next, add ability to invert X and Y axis (values on horizontal axis)

Refactorings

Introduce Mixin Layoutable by applyParentOffset()

Introduce Mixin Paintable by paint()

Terminology and Selected Classes

(Presenter)Leaf
The finest visual element presented in each “column of view” in chart - that is, all widgets representing series of data displayed above each X label. For example, for Line chart, the leaf would be one line and dot representing one Y value at one X label. For the bar chart, the leaf would be one bar representing one (stacked) Y value at one X label.
  • Classes: Presenter, LineAndHotspotPresenter, VerticalBarPresenter, PresenterCreator
Painter
Class which paints to chart to canvas. Terminology and class structure taken from Flutter’s Painter and Painting classes.
  • Classes: todo

Internal notes

Add ability to create a Table of Contents to README.org

  • [X] Install toc-org package
  • [X] Add to init.el
    (if (require 'toc-org nil t)
      (add-hook 'org-mode-hook 'toc-org-enable)
    (warn "toc-org not found"))
        
  • [X] Every time README.org is saved, first heading with a :TOC: tag will be updated with the current table of contents.
  • [X] So nothing special need be done after the above is configured.

Adding images to README.org

  • [ ] https://pub.dartlang.org does not allow storing images.
  • [ ] Add / move new images to flutter_charts/doc/readme_images
  • [ ] org file, change image links to look like ~file:doc/readme_images/README.org_20171102_154245_27063qmN.png~

Pub - Publishing workflow on https://pub.dartlang.org

Notes:

Pub requires the following file in project to show the correct tabs on pub

Tab README.md - Needs the file
Tab CHANGELOG.md - Needs the file
Tab Example - this tab appears if the project file file:flutter_charts/example/lib/main.dart exists
Tab Installing - shows automatically

IF README.md needs change

README.org: make sure image links point to flutter_charts/doc/readme_images

README.org: Conversion steps to README.md

To convert README.org to README.md, we need to do a few extra steps for README.md image links to be readable on https://pub.dartlang.org.

  1. Note: Org file which has :TOC: in heading, generates TOC on every save.
  2. README.org: Export org to md: C-c C-e m m in the org file to create the generated md file
  3. README.md: Delete generated TOC
  4. README.md: Generate md-native TOC:
    • Cursor on top
    • M-x: markdown-toc/generate-toc
  5. README.md: Fix image links in the README.md - links must look like this:
    -![img](doc/readme_images/README.org_20171102_180657_27063rZs.png)
    +![img](https://github.com/mzimmerm/flutter_charts/raw/master/doc/readme_images/README.org_20171102_180657_27063rZs.png)
        
  6. README.md: This is achieved with: replace-string doc/readme_images/ https://github.com/mzimmerm/flutter_charts/raw/master/doc/readme_images/

pubspec.yaml: - Increase version number

CHANGELOG.md: - Add comment section for new version

cd flutter_charts; flutter packages pub upgrade

cd flutter_charts; flutter packages pub get

Test included app from IntelliJ

git add .; git commit; git push

README.md: on https://github.com/mzimmerm/flutter_charts - check if image links have a full path

flutter packages pub publish --dry-run

flutter packages pub publish

Test the package that was just published

cd flutter_charts_sample_app; flutter packages pub upgrade; flutter packages pub get; flutter run

REMOVED CODE

/// Lays out a list of labels horizontally,
/// makes labels evenly sized, and evenly spaced.
///
/// The motivation for this class is to layout labels when
/// the horizontal (X) space is restricted, and we want to manipulate
/// the label positions to automatically scale to fit.
///
/// Layout is forced to fit
/// by ensuring all labels fit within the X direction space
/// by decreasing the font size, tilting the labels, or skipping some labels,
/// or (last resource??) trimming the labels.
///
/// todo -2: No attempt is made to decrease Y direction size (height), but if
/// the passed [_maxHeight] is finite, a validity check is made
/// if the actual layed out height is within the passed height.
///
/// Instances are created from a label list; each label is
/// wrapped as a [LabelContainer] instance. All member [LabelContainer] instances
/// in [labelContainers] share the text properties (style, direction, align etc.)
/// of this parent instance
///
/// The initial text style of member [labelContainers] is from [ChartOptions].
/// The motivation is that a calling auto-fit program will change the text
/// style to fit a defined width.
///
/// Provides methods to
///   - Layout member labelContainers, for the purpose of
///   finding if they overflow their even size width.
///   - Change text style for all labels (by setting members and applying
///   them on the member [labelContainers].
///   - Layout the container by laying out the contained [labelContainers]
///   - Query size needed to paint each [labelContainers] and the whole container.
class HorizontalFixedWidthAutoScaledLabelsContainer {
  List<String> _labels;

  /// Wrappers for label strings
  List<LabelContainer> _labelContainers;

  /// Width of container. This is the fixed width this container
  /// must fill
  double _width;

  double _maxHeight = double.INFINITY;

  double _calculatedHeight;

  ChartOptions _options;

  /// Padding left of the leftmost label
  double _leftPad;

  /// Padding between each label
  double _betweenPad;

  /// Padding right of the rightmost label
  double _rightPad;

  bool _layoutClean = false;

  // TODO -4 STORING LABELSTYLE AS MEMBER IS TEMPORARY WHILE WE ARE PLUGGING FixedWidthHorizontalLabelsContainer TO X LABELS AND LEGEND LAYOUT, LAYOUTING ONCE
  LabelStyle _labelStyle;

  /// Calculated width allocated to each label.
  ///
  /// This width does not depend on text style - it is calculated
  /// by evenly dividing the available container width (total width, taking
  /// into account padding) by the number of labels.
  double get allocatedLabelWidth {
    double perLabelWidth =
        (_width - (_leftPad + (_labels.length - 1) * _betweenPad + _rightPad)) /
            _labels.length;
    if (perLabelWidth <= 0.0) {
      throw new StateError("Container does not leave space for labels.");
    }
    return perLabelWidth;
  }

  /// Validate height of this container against constructor [_maxHeight].
  /// todo -3
  double get validateHeight {
    if (_maxHeight != double.INFINITY) {
      if (_maxHeight - _calculatedHeight > util.epsilon) {
        throw new StateError("Invalid size: $_maxHeight,  $_calculatedHeight");
      }
      return _calculatedHeight;
    }
    throw new StateError("Do not need to ask.");
  }

  bool isTooBig = true; // transient layout helper

  /// Constructs the container that must fit into a fixed boundary
  /// defined by the [width] parameter.
  ///
  /// Constraints
  ///   - [_width] must be set to a finite value
  ///     (not double.INFINITY). todo -2 add condition
  ///   -  [_maxHeight] is optional; it may be INFINITY (in most cases would be).
  ///      If not INFINITY, a validation is performed for height overflow todo -2 add condition
  ///
  /// Note: This class does not keep the LabelStyle,
  ///       just passes it to member [LabelContainer]s.
  ///       // todo -10
  HorizontalFixedWidthAutoScaledLabelsContainer({
    List<String> labels,
    double width,
    double maxHeight,
    ChartOptions options,
    double leftPad,
    double betweenPad,
    double rightPad,
  }) {
    _labels = labels;
    _width = width;
    _maxHeight = maxHeight; // optional
    _options = options;
    _leftPad = leftPad;
    _betweenPad = betweenPad;
    _rightPad = rightPad;

    // Instance is created from a label list; each label is
    //   wrapped as a [LabelContainer] instance.
    // The initial text style of member [labelContainers] is from [ChartOptions].
    // All member [LabelContainer] instances
    //   in [labelContainers] share the text properties (style, direction, align etc.)
    //   of this parent instance
    _options = options;

    // Initially all [LabelContainer]s share same text style object from options.
    LabelStyle labelStyle = new LabelStyle(
      textStyle: options.labelTextStyle,
      textDirection: options.labelTextDirection,
      textAlign: options.labelTextAlign, // center text
      textScaleFactor: options.labelTextScaleFactor,
    );
    _labelStyle = labelStyle;

    _labelContainers = labels.map((label) {
      return new LabelContainer(
        label: label,
        labelMaxWidth: allocatedLabelWidth,
        labelStyle: labelStyle,
      );
    }).toList();
  }

/* todo -4 put this section back
  /// Provides methods to
  ///   - Layout individual [labelContainers], for the purpose of
  ///   finding if they overflow their even size width.
  ///
  /// anyLabelOverflows() - must be called after layoutIndividualLabels()
  ///

  ///   - Change text style for all labels (by setting members and applying
  ///   them on the member [labelContainers].
  ///   - Layout the container by laying out the contained [labelContainers].
  ///   This should layout to maxWidth, and throw exception on overflow.
  ///   - Query size needed to paint each [labelContainers] and the whole container.

  /// todo -3 add all method signatures first, implement next
  /// - layout the container with each label at evenly spaced positions
  void layoutQuaranteeFitFirstTiltNextDecreaseFontNextSkipNextTrim() {
    // TODO -4 FOR NOW, JUST LAYOUT, ONCE, NOT CHECKING FOR OVERFLOW
    _applyStyleThenLayoutAndCheckOverflow(labelStyle: _labelStyle);

    // todo -3
    // call layoutAndCheckOverflow on all labelContainers
    // if at least one overflows, tilt all labels by -70 degrees
    // etc.
  }

  /// Layout member [_labelContainers] forcing the max width and
  /// check for overflow.
  ///
  /// Returns `true` if at least one element of [_labelContainers] overflows,
  /// `false` otherwise.
  ///
  /// As a sideeffect, if false is returned, all  [_labelContainers] were
  /// layoued out, and can be painted.
  bool _layoutAndCheckOverflow() {
    // same as label_painted, on all
    return _labelContainers.any((labelContainer) {
      labelContainer.layoutAndCheckOverflow();
    });
  }

  /// Apply new text style and layout, then check if
  /// any member of [_labelContainers] overflows.
  /// returns `true` if at least one overflows.
  bool _applyStyleThenLayoutAndCheckOverflow({
    LabelStyle labelStyle,
  }) {
    // Here need to process all painters, as we want to apply style to all.
    _labelContainers.forEach((labelContainer) {
      labelContainer.applyStyleThenLayoutAndCheckOverflow(
          labelStyle: labelStyle);
    });
    // todo -3: PUT THIS BACK. FOR NOW, WE JUST LAYOUT ONCE, NOT CARING ABOUT OVERFLOW: return _labelContainers.any((labelContainer) {labelContainer.isOverflowing;});
    return false;
  }
  */
}