layout method
Implements Container.layout
for the chart as a whole.
Uses this container's chartArea as available size
Note: The chartArea was set in the ChartPainter.paint(Canvas, Size)
just before calling this method:
void paint(ui.Canvas canvas, ui.Size size) {
...
container.chartArea = size;
container.layout();
...
Layout proceeds scaling the Y values to fit the available size, then lays out the legend, Y axis and labels, X axis and labels, and the data area, giving each the size it needs.
The actual layout algorithm should be made pluggable.
Implementation
@override
void layout(LayoutExpansion parentLayoutExpansion) {
// ### 1. Prepare early, from dataRows, the stackable points managed
// in [pointsColumns], as [YContainer] needs to scale y values and
// create labels from the stacked points (if chart is stacked).
setupPointsColumns();
// ### 2. Layout the legends on top
var legendLayoutExpansion = LayoutExpansion(
width: chartArea.width,
height: chartArea.height,
);
legendContainer = LegendContainer(
chartTopContainer: this,
);
legendContainer.layout(legendLayoutExpansion);
ui.Size legendContainerSize = legendContainer.layoutSize;
ui.Offset legendContainerOffset = ui.Offset.zero;
legendContainer.applyParentOffset(legendContainerOffset);
// ### 3. Ask [YContainer] to provide Y label container width.
// This provides the remaining width left for the [XContainer]
// (grid and X axis) to use. The yLabelsMaxHeightFromFirstLayout
// is not relevant in this first call.
double yContainerHeight = chartArea.height - legendContainerSize.height;
var yContainerLayoutExpansion = LayoutExpansion(
width: chartArea.width,
height: yContainerHeight,
);
var yContainerFirst = YContainer(
chartTopContainer: this,
yLabelsMaxHeightFromFirstLayout: 0.0,
);
yContainerFirst.layout(yContainerLayoutExpansion);
double yLabelsMaxHeightFromFirstLayout = yContainerFirst.yLabelsMaxHeight;
yContainer = yContainerFirst;
ui.Size yContainerSize = yContainer.layoutSize;
// ### 4. Knowing the width required by Y axis, layout X
// (from first [YContainer.layout] call).
var xContainerLayoutExpansion = LayoutExpansion(
width: chartArea.width - yContainerSize.width,
height: chartArea.height - legendContainerSize.height,
);
xContainer = XContainer(
chartTopContainer: this,
xContainerLabelLayoutStrategy: _cachedXContainerLabelLayoutStrategy,
);
xContainer.layout(xContainerLayoutExpansion);
ui.Size xContainerSize = xContainer.layoutSize;
ui.Offset xContainerOffset = ui.Offset(yContainerSize.width, chartArea.height - xContainerSize.height);
xContainer.applyParentOffset(xContainerOffset);
// ### 5. Second call to YContainer is needed, as available height for Y
// is only known after XContainer provided required height of xUserLabels
// on the bottom .
// The [yLabelsMaxHeightFromFirstLayout] are used to scale
// data values to the y axis, and put labels on ticks.
// On the second layout, make sure YContainer expand down only to
// the top of the XContainer area.
yContainerLayoutExpansion = LayoutExpansion(
width: chartArea.width,
height: yContainerHeight - xContainerSize.height,
);
yContainer = YContainer(
chartTopContainer: this,
yLabelsMaxHeightFromFirstLayout: yLabelsMaxHeightFromFirstLayout,
);
yContainer.layout(yContainerLayoutExpansion);
yContainerSize = yContainer.layoutSize;
ui.Offset yContainerOffset = ui.Offset(0.0, legendContainerSize.height);
yContainer.applyParentOffset(yContainerOffset);
ui.Offset dataContainerOffset = ui.Offset(yContainerSize.width, legendContainerSize.height);
// ### 6. Layout the data area, which included the grid
// by calculating the X and Y positions of grid.
// This must be done after X and Y are layed out - see xTickXs, yTickYs.
var dataContainerLayoutExpansion = LayoutExpansion(
width: chartArea.width - yContainerSize.width,
height: chartArea.height - (legendContainerSize.height + xContainerSize.height),
);
dataContainer = createDataContainer(
chartTopContainer: this,
);
// todo-01-morph-layout : this is where most non-Container elements are layed out.
// problem is, part of the layout happens in applyParentOffset!
dataContainer.layout(dataContainerLayoutExpansion);
dataContainer.applyParentOffset(dataContainerOffset);
}