1
0
mirror of https://github.com/flutter/samples.git synced 2026-04-05 03:01:19 +00:00

Add flutter_web samples (#75)

This commit is contained in:
Kevin Moore
2019-05-07 13:32:08 -07:00
committed by Andrew Brogdon
parent 42f2dce01b
commit 3fe927cb29
697 changed files with 241026 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Collection of configurations that apply to the [LayoutManager].
class LayoutConfig {
final MarginSpec leftSpec;
final MarginSpec rightSpec;
final MarginSpec topSpec;
final MarginSpec bottomSpec;
/// Create a new [LayoutConfig] used by [DynamicLayoutManager].
LayoutConfig({
MarginSpec leftSpec,
MarginSpec rightSpec,
MarginSpec topSpec,
MarginSpec bottomSpec,
}) : leftSpec = leftSpec ?? MarginSpec.defaultSpec,
rightSpec = rightSpec ?? MarginSpec.defaultSpec,
topSpec = topSpec ?? MarginSpec.defaultSpec,
bottomSpec = bottomSpec ?? MarginSpec.defaultSpec;
}
/// Specs that applies to one margin.
class MarginSpec {
/// [MarginSpec] that has max of 50 percent.
static const defaultSpec = const MarginSpec._internal(null, null, null, 50);
final int _minPixel;
final int _maxPixel;
final int _minPercent;
final int _maxPercent;
const MarginSpec._internal(
int minPixel, int maxPixel, int minPercent, int maxPercent)
: _minPixel = minPixel,
_maxPixel = maxPixel,
_minPercent = minPercent,
_maxPercent = maxPercent;
/// Create [MarginSpec] that specifies min/max pixels.
///
/// [minPixel] if set must be greater than or equal to 0 and less than max if
/// it is also set.
/// [maxPixel] if set must be greater than or equal to 0.
factory MarginSpec.fromPixel({int minPixel, int maxPixel}) {
// Require zero or higher settings if set
assert(minPixel == null || minPixel >= 0);
assert(maxPixel == null || maxPixel >= 0);
// Min must be less than or equal to max.
// Can be equal to enforce strict pixel size.
if (minPixel != null && maxPixel != null) {
assert(minPixel <= maxPixel);
}
return new MarginSpec._internal(minPixel, maxPixel, null, null);
}
/// Create [MarginSpec] with a fixed pixel size [pixels].
///
/// [pixels] if set must be greater than or equal to 0.
factory MarginSpec.fixedPixel(int pixels) {
// Require require or higher setting if set
assert(pixels == null || pixels >= 0);
return new MarginSpec._internal(pixels, pixels, null, null);
}
/// Create [MarginSpec] that specifies min/max percentage.
///
/// [minPercent] if set must be between 0 and 100 inclusive. If [maxPercent]
/// is also set, then must be less than [maxPercent].
/// [maxPercent] if set must be between 0 and 100 inclusive.
factory MarginSpec.fromPercent({int minPercent, int maxPercent}) {
// Percent must be within 0 to 100
assert(minPercent == null || (minPercent >= 0 && minPercent <= 100));
assert(maxPercent == null || (maxPercent >= 0 && maxPercent <= 100));
// Min must be less than or equal to max.
// Can be equal to enforce strict percentage.
if (minPercent != null && maxPercent != null) {
assert(minPercent <= maxPercent);
}
return new MarginSpec._internal(null, null, minPercent, maxPercent);
}
/// Get the min pixels, given the [totalPixels].
int getMinPixels(int totalPixels) {
if (_minPixel != null) {
assert(_minPixel < totalPixels);
return _minPixel;
} else if (_minPercent != null) {
return (totalPixels * (_minPercent / 100)).round();
} else {
return 0;
}
}
/// Get the max pixels, given the [totalPixels].
int getMaxPixels(int totalPixels) {
if (_maxPixel != null) {
assert(_maxPixel < totalPixels);
return _maxPixel;
} else if (_maxPercent != null) {
return (totalPixels * (_maxPercent / 100)).round();
} else {
return totalPixels;
}
}
}

View File

@@ -0,0 +1,70 @@
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:math' show Point, Rectangle;
import 'layout_view.dart' show LayoutView;
abstract class LayoutManager {
/// Adds a view to be managed by the LayoutManager.
void addView(LayoutView view);
/// Removes a view previously added to the LayoutManager.
/// No-op if it wasn't there to begin with.
void removeView(LayoutView view);
/// Returns true if view is already attached.
bool isAttached(LayoutView view);
/// Walk through the child views and determine their desired sizes storing
/// off the information for layout.
void measure(int width, int height);
/// Walk through the child views and set their bounds from the perspective
/// of the canvas origin.
void layout(int width, int height);
/// Returns the bounds of the drawArea. Must be called after layout().
Rectangle<int> get drawAreaBounds;
/// Returns the combined bounds of the drawArea, and all components that
/// function as series draw areas. Must be called after layout().
Rectangle<int> get drawableLayoutAreaBounds;
/// Gets the measured size of the bottom margin, available after layout.
int get marginBottom;
/// Gets the measured size of the left margin, available after layout.
int get marginLeft;
/// Gets the measured size of the right margin, available after layout.
int get marginRight;
/// Gets the measured size of the top margin, available after layout.
int get marginTop;
/// Returns whether or not [point] is within the draw area bounds.
bool withinDrawArea(Point<num> point);
/// Walk through the child views and apply the function passed in.
void applyToViews(void apply(LayoutView view));
/// Return the child views in the order that they should be drawn.
List<LayoutView> get paintOrderedViews;
/// Return the child views in the order that they should be positioned within
/// chart margins.
List<LayoutView> get positionOrderedViews;
}

View File

@@ -0,0 +1,368 @@
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:math' show Point, Rectangle, max;
import 'package:meta/meta.dart' show required;
import 'layout_config.dart' show LayoutConfig;
import 'layout_manager.dart';
import 'layout_margin_strategy.dart';
import 'layout_view.dart' show LayoutView, LayoutPosition;
/// Default Layout manager for [LayoutView]s.
class LayoutManagerImpl implements LayoutManager {
static const _minDrawWidth = 20;
static const _minDrawHeight = 20;
// Allow [Layoutconfig] to be mutable so it can be modified without requiring
// a new copy of [DefaultLayoutManager] to be created.
LayoutConfig config;
/// Unordered list of views in the layout.
final _views = <LayoutView>[];
/// List of views in the order they should be drawn on the canvas.
///
/// First element is painted first.
List<LayoutView> _paintOrderedViews;
/// List of vies in the order they should be positioned in a chart margin.
///
/// First element is closest to the draw area.
List<LayoutView> _positionOrderedViews;
_MeasuredSizes _measurements;
Rectangle<int> _drawAreaBounds;
bool _drawAreaBoundsOutdated = true;
bool _viewsNeedPaintSort = true;
bool _viewsNeedPositionSort = true;
/// Create a new [LayoutManager].
LayoutManagerImpl({LayoutConfig config})
: this.config = config ?? new LayoutConfig();
/// Add one [LayoutView].
void addView(LayoutView view) {
_views.add(view);
_drawAreaBoundsOutdated = true;
_viewsNeedPositionSort = true;
_viewsNeedPaintSort = true;
}
/// Remove one [LayoutView].
void removeView(LayoutView view) {
if (_views.remove(view)) {
_drawAreaBoundsOutdated = true;
_viewsNeedPositionSort = true;
_viewsNeedPaintSort = true;
}
}
/// Returns true if [view] is already attached.
bool isAttached(LayoutView view) => _views.contains(view);
/// Get all layout components in the order to be drawn.
@override
List<LayoutView> get paintOrderedViews {
if (_viewsNeedPaintSort) {
_paintOrderedViews = new List<LayoutView>.from(_views);
_paintOrderedViews.sort((LayoutView v1, LayoutView v2) =>
v1.layoutConfig.paintOrder.compareTo(v2.layoutConfig.paintOrder));
_viewsNeedPaintSort = false;
}
return _paintOrderedViews;
}
/// Get all layout components in the order to be visited.
@override
List<LayoutView> get positionOrderedViews {
if (_viewsNeedPositionSort) {
_positionOrderedViews = new List<LayoutView>.from(_views);
_positionOrderedViews.sort((LayoutView v1, LayoutView v2) => v1
.layoutConfig.positionOrder
.compareTo(v2.layoutConfig.positionOrder));
_viewsNeedPositionSort = false;
}
return _positionOrderedViews;
}
@override
Rectangle<int> get drawAreaBounds {
assert(_drawAreaBoundsOutdated == false);
return _drawAreaBounds;
}
@override
Rectangle<int> get drawableLayoutAreaBounds {
assert(_drawAreaBoundsOutdated == false);
final drawableViews =
_views.where((LayoutView view) => view.isSeriesRenderer);
var componentBounds = drawableViews?.first?.componentBounds;
if (componentBounds != null) {
for (LayoutView view in drawableViews.skip(1)) {
if (view.componentBounds != null) {
componentBounds = componentBounds.boundingBox(view.componentBounds);
}
}
} else {
componentBounds = new Rectangle(0, 0, 0, 0);
}
return componentBounds;
}
@override
int get marginBottom {
assert(_drawAreaBoundsOutdated == false);
return _measurements.bottomHeight;
}
@override
int get marginLeft {
assert(_drawAreaBoundsOutdated == false);
return _measurements.leftWidth;
}
@override
int get marginRight {
assert(_drawAreaBoundsOutdated == false);
return _measurements.rightWidth;
}
@override
int get marginTop {
assert(_drawAreaBoundsOutdated == false);
return _measurements.topHeight;
}
@override
withinDrawArea(Point<num> point) {
return _drawAreaBounds.containsPoint(point);
}
/// Measure and layout with given [width] and [height].
@override
void measure(int width, int height) {
var topViews =
_viewsForPositions(LayoutPosition.Top, LayoutPosition.FullTop);
var rightViews =
_viewsForPositions(LayoutPosition.Right, LayoutPosition.FullRight);
var bottomViews =
_viewsForPositions(LayoutPosition.Bottom, LayoutPosition.FullBottom);
var leftViews =
_viewsForPositions(LayoutPosition.Left, LayoutPosition.FullLeft);
// Assume the full width and height of the chart is available when measuring
// for the first time but adjust the maximum if margin spec is set.
var measurements = _measure(width, height,
topViews: topViews,
rightViews: rightViews,
bottomViews: bottomViews,
leftViews: leftViews,
useMax: true);
// Measure a second time but pass in the preferred width and height from
// the first measure cycle.
// Allow views to report a different size than the previously measured max.
final secondMeasurements = _measure(width, height,
topViews: topViews,
rightViews: rightViews,
bottomViews: bottomViews,
leftViews: leftViews,
previousMeasurements: measurements,
useMax: true);
// If views need more space with the 2nd pass, perform a third pass.
if (measurements.leftWidth != secondMeasurements.leftWidth ||
measurements.rightWidth != secondMeasurements.rightWidth ||
measurements.topHeight != secondMeasurements.topHeight ||
measurements.bottomHeight != secondMeasurements.bottomHeight) {
final thirdMeasurements = _measure(width, height,
topViews: topViews,
rightViews: rightViews,
bottomViews: bottomViews,
leftViews: leftViews,
previousMeasurements: secondMeasurements,
useMax: false);
measurements = thirdMeasurements;
} else {
measurements = secondMeasurements;
}
_measurements = measurements;
// Draw area size.
// Set to a minimum size if there is not enough space for the draw area.
// Prevents the app from crashing by rendering overlapping content instead.
final drawAreaWidth = max(
_minDrawWidth,
(width - measurements.leftWidth - measurements.rightWidth),
);
final drawAreaHeight = max(
_minDrawHeight,
(height - measurements.bottomHeight - measurements.topHeight),
);
// Bounds for the draw area.
_drawAreaBounds = new Rectangle(measurements.leftWidth,
measurements.topHeight, drawAreaWidth, drawAreaHeight);
_drawAreaBoundsOutdated = false;
}
@override
void layout(int width, int height) {
var topViews =
_viewsForPositions(LayoutPosition.Top, LayoutPosition.FullTop);
var rightViews =
_viewsForPositions(LayoutPosition.Right, LayoutPosition.FullRight);
var bottomViews =
_viewsForPositions(LayoutPosition.Bottom, LayoutPosition.FullBottom);
var leftViews =
_viewsForPositions(LayoutPosition.Left, LayoutPosition.FullLeft);
var drawAreaViews = _viewsForPositions(LayoutPosition.DrawArea);
final fullBounds = new Rectangle(0, 0, width, height);
// Layout the margins.
new LeftMarginLayoutStrategy()
.layout(leftViews, _measurements.leftSizes, fullBounds, drawAreaBounds);
new RightMarginLayoutStrategy().layout(
rightViews, _measurements.rightSizes, fullBounds, drawAreaBounds);
new BottomMarginLayoutStrategy().layout(
bottomViews, _measurements.bottomSizes, fullBounds, drawAreaBounds);
new TopMarginLayoutStrategy()
.layout(topViews, _measurements.topSizes, fullBounds, drawAreaBounds);
// Layout the drawArea.
drawAreaViews.forEach(
(LayoutView view) => view.layout(_drawAreaBounds, _drawAreaBounds));
}
Iterable<LayoutView> _viewsForPositions(LayoutPosition p1,
[LayoutPosition p2]) {
return positionOrderedViews.where((LayoutView view) =>
(view.layoutConfig.position == p1 ||
(p2 != null && view.layoutConfig.position == p2)));
}
/// Measure and return size measurements.
/// [width] full width of chart
/// [height] full height of chart
_MeasuredSizes _measure(
int width,
int height, {
Iterable<LayoutView> topViews,
Iterable<LayoutView> rightViews,
Iterable<LayoutView> bottomViews,
Iterable<LayoutView> leftViews,
_MeasuredSizes previousMeasurements,
@required bool useMax,
}) {
final maxLeftWidth = config.leftSpec.getMaxPixels(width);
final maxRightWidth = config.rightSpec.getMaxPixels(width);
final maxBottomHeight = config.bottomSpec.getMaxPixels(height);
final maxTopHeight = config.topSpec.getMaxPixels(height);
// Assume the full width and height of the chart is available when measuring
// for the first time but adjust the maximum if margin spec is set.
var leftWidth = previousMeasurements?.leftWidth ?? maxLeftWidth;
var rightWidth = previousMeasurements?.rightWidth ?? maxRightWidth;
var bottomHeight = previousMeasurements?.bottomHeight ?? maxBottomHeight;
var topHeight = previousMeasurements?.topHeight ?? maxTopHeight;
// Only adjust the height if we have previous measurements.
final adjustedHeight = (previousMeasurements != null)
? height - bottomHeight - topHeight
: height;
var leftSizes = new LeftMarginLayoutStrategy().measure(leftViews,
maxWidth: useMax ? maxLeftWidth : leftWidth,
height: adjustedHeight,
fullHeight: height);
leftWidth = max(leftSizes.total, config.leftSpec.getMinPixels(width));
var rightSizes = new RightMarginLayoutStrategy().measure(rightViews,
maxWidth: useMax ? maxRightWidth : rightWidth,
height: adjustedHeight,
fullHeight: height);
rightWidth = max(rightSizes.total, config.rightSpec.getMinPixels(width));
final adjustedWidth = width - leftWidth - rightWidth;
var bottomSizes = new BottomMarginLayoutStrategy().measure(bottomViews,
maxHeight: useMax ? maxBottomHeight : bottomHeight,
width: adjustedWidth,
fullWidth: width);
bottomHeight =
max(bottomSizes.total, config.bottomSpec.getMinPixels(height));
var topSizes = new TopMarginLayoutStrategy().measure(topViews,
maxHeight: useMax ? maxTopHeight : topHeight,
width: adjustedWidth,
fullWidth: width);
topHeight = max(topSizes.total, config.topSpec.getMinPixels(height));
return new _MeasuredSizes(
leftWidth: leftWidth,
leftSizes: leftSizes,
rightWidth: rightWidth,
rightSizes: rightSizes,
topHeight: topHeight,
topSizes: topSizes,
bottomHeight: bottomHeight,
bottomSizes: bottomSizes);
}
@override
void applyToViews(void apply(LayoutView view)) {
_views.forEach((view) => apply(view));
}
}
/// Helper class that stores measured width and height during measure cycles.
class _MeasuredSizes {
final int leftWidth;
final SizeList leftSizes;
final int rightWidth;
final SizeList rightSizes;
final int topHeight;
final SizeList topSizes;
final int bottomHeight;
final SizeList bottomSizes;
_MeasuredSizes(
{this.leftWidth,
this.leftSizes,
this.rightWidth,
this.rightSizes,
this.topHeight,
this.topSizes,
this.bottomHeight,
this.bottomSizes});
}

View File

@@ -0,0 +1,273 @@
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:math' show Rectangle;
import 'package:meta/meta.dart';
import 'layout_view.dart';
class SizeList {
final _sizes = <int>[];
int _total = 0;
operator [](i) => _sizes[i];
int get total => _total;
int get length => _sizes.length;
void add(size) {
_sizes.add(size);
_total += size;
}
void adjust(int index, int amount) {
_sizes[index] += amount;
_total += amount;
}
}
class _DesiredViewSizes {
final preferredSizes = new SizeList();
final minimumSizes = new SizeList();
void add(int preferred, int minimum) {
preferredSizes.add(preferred);
minimumSizes.add(minimum);
}
void adjustedTo(maxSize) {
if (maxSize < preferredSizes.total) {
int delta = preferredSizes.total - maxSize;
for (int i = preferredSizes.length - 1; i >= 0; i--) {
int viewAvailablePx = preferredSizes[i] - minimumSizes[i];
if (viewAvailablePx < delta) {
// We need even more than this one view can give up, so assign the
// minimum to the view and adjust totals.
preferredSizes.adjust(i, -viewAvailablePx);
delta -= viewAvailablePx;
} else {
// We can adjust this view to account for the delta.
preferredSizes.adjust(i, -delta);
return;
}
}
}
}
}
/// A strategy for calculating size of vertical margins (RIGHT & LEFT).
abstract class VerticalMarginStrategy {
SizeList measure(Iterable<LayoutView> views,
{@required int maxWidth,
@required int height,
@required int fullHeight}) {
final measuredWidths = new _DesiredViewSizes();
int remainingWidth = maxWidth;
views.forEach((LayoutView view) {
final params = view.layoutConfig;
final viewMargin = params.viewMargin;
final availableHeight =
(params.isFullPosition ? fullHeight : height) - viewMargin.height;
// Measure with all available space, minus the buffer.
remainingWidth = remainingWidth - viewMargin.width;
maxWidth -= viewMargin.width;
var size = ViewMeasuredSizes.zero;
// Don't ask component to measure if both measurements are 0.
//
// Measure still needs to be called even when one dimension has a size of
// zero because if the component is an axis, the axis needs to still
// recalculate ticks even if it is not to be shown.
if (remainingWidth > 0 || availableHeight > 0) {
size = view.measure(remainingWidth, availableHeight);
remainingWidth -= size.preferredWidth;
}
measuredWidths.add(size.preferredWidth, size.minWidth);
});
measuredWidths.adjustedTo(maxWidth);
return measuredWidths.preferredSizes;
}
void layout(List<LayoutView> views, SizeList measuredSizes,
Rectangle<int> fullBounds, Rectangle<int> drawAreaBounds);
}
/// A strategy for calculating size and bounds of left margins.
class LeftMarginLayoutStrategy extends VerticalMarginStrategy {
@override
void layout(Iterable<LayoutView> views, SizeList measuredSizes,
Rectangle<int> fullBounds, Rectangle<int> drawAreaBounds) {
var prevBoundsRight = drawAreaBounds.left;
int i = 0;
views.forEach((LayoutView view) {
final params = view.layoutConfig;
final width = measuredSizes[i];
final left = prevBoundsRight - params.viewMargin.rightPx - width;
final height =
(params.isFullPosition ? fullBounds.height : drawAreaBounds.height) -
params.viewMargin.height;
final top = params.viewMargin.topPx +
(params.isFullPosition ? fullBounds.top : drawAreaBounds.top);
// Update the remaining bounds.
prevBoundsRight = left - params.viewMargin.leftPx;
// Layout this component.
view.layout(new Rectangle(left, top, width, height), drawAreaBounds);
i++;
});
}
}
/// A strategy for calculating size and bounds of right margins.
class RightMarginLayoutStrategy extends VerticalMarginStrategy {
@override
void layout(Iterable<LayoutView> views, SizeList measuredSizes,
Rectangle<int> fullBounds, Rectangle<int> drawAreaBounds) {
var prevBoundsLeft = drawAreaBounds.right;
int i = 0;
views.forEach((LayoutView view) {
final params = view.layoutConfig;
final width = measuredSizes[i];
final left = prevBoundsLeft + params.viewMargin.leftPx;
final height =
(params.isFullPosition ? fullBounds.height : drawAreaBounds.height) -
params.viewMargin.height;
final top = params.viewMargin.topPx +
(params.isFullPosition ? fullBounds.top : drawAreaBounds.top);
// Update the remaining bounds.
prevBoundsLeft = left + width + params.viewMargin.rightPx;
// Layout this component.
view.layout(new Rectangle(left, top, width, height), drawAreaBounds);
i++;
});
}
}
/// A strategy for calculating size of horizontal margins (TOP & BOTTOM).
abstract class HorizontalMarginStrategy {
SizeList measure(Iterable<LayoutView> views,
{@required int maxHeight, @required int width, @required int fullWidth}) {
final measuredHeights = new _DesiredViewSizes();
int remainingHeight = maxHeight;
views.forEach((LayoutView view) {
final params = view.layoutConfig;
final viewMargin = params.viewMargin;
final availableWidth =
(params.isFullPosition ? fullWidth : width) - viewMargin.width;
// Measure with all available space, minus the buffer.
remainingHeight = remainingHeight - viewMargin.height;
maxHeight -= viewMargin.height;
var size = ViewMeasuredSizes.zero;
// Don't ask component to measure if both measurements are 0.
//
// Measure still needs to be called even when one dimension has a size of
// zero because if the component is an axis, the axis needs to still
// recalculate ticks even if it is not to be shown.
if (remainingHeight > 0 || availableWidth > 0) {
size = view.measure(availableWidth, remainingHeight);
remainingHeight -= size.preferredHeight;
}
measuredHeights.add(size.preferredHeight, size.minHeight);
});
measuredHeights.adjustedTo(maxHeight);
return measuredHeights.preferredSizes;
}
void layout(Iterable<LayoutView> views, SizeList measuredSizes,
Rectangle<int> fullBounds, Rectangle<int> drawAreaBounds);
}
/// A strategy for calculating size and bounds of top margins.
class TopMarginLayoutStrategy extends HorizontalMarginStrategy {
@override
void layout(Iterable<LayoutView> views, SizeList measuredSizes,
Rectangle<int> fullBounds, Rectangle<int> drawAreaBounds) {
var prevBoundsBottom = drawAreaBounds.top;
int i = 0;
views.forEach((LayoutView view) {
final params = view.layoutConfig;
final height = measuredSizes[i];
final top = prevBoundsBottom - height - params.viewMargin.bottomPx;
final width =
(params.isFullPosition ? fullBounds.width : drawAreaBounds.width) -
params.viewMargin.width;
final left = params.viewMargin.leftPx +
(params.isFullPosition ? fullBounds.left : drawAreaBounds.left);
// Update the remaining bounds.
prevBoundsBottom = top - params.viewMargin.topPx;
// Layout this component.
view.layout(new Rectangle(left, top, width, height), drawAreaBounds);
i++;
});
}
}
/// A strategy for calculating size and bounds of bottom margins.
class BottomMarginLayoutStrategy extends HorizontalMarginStrategy {
@override
void layout(Iterable<LayoutView> views, SizeList measuredSizes,
Rectangle<int> fullBounds, Rectangle<int> drawAreaBounds) {
var prevBoundsTop = drawAreaBounds.bottom;
int i = 0;
views.forEach((LayoutView view) {
final params = view.layoutConfig;
final height = measuredSizes[i];
final top = prevBoundsTop + params.viewMargin.topPx;
final width =
(params.isFullPosition ? fullBounds.width : drawAreaBounds.width) -
params.viewMargin.width;
final left = params.viewMargin.leftPx +
(params.isFullPosition ? fullBounds.left : drawAreaBounds.left);
// Update the remaining bounds.
prevBoundsTop = top + height + params.viewMargin.bottomPx;
// Layout this component.
view.layout(new Rectangle(left, top, width, height), drawAreaBounds);
i++;
});
}
}

View File

@@ -0,0 +1,208 @@
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:math' show Rectangle;
import 'package:meta/meta.dart';
import '../../common/graphics_factory.dart' show GraphicsFactory;
import '../common/chart_canvas.dart' show ChartCanvas;
/// Position of a [LayoutView].
enum LayoutPosition {
Bottom,
FullBottom,
Top,
FullTop,
Left,
FullLeft,
Right,
FullRight,
DrawArea,
}
/// Standard layout paint orders for all internal components.
///
/// Custom component layers should define their paintOrder by taking the nearest
/// layer from this list, and adding or subtracting 1. This will help reduce the
/// chance of custom behaviors, renderers, etc. from breaking if we need to
/// re-order these components internally.
class LayoutViewPaintOrder {
// Draw range annotations beneath axis grid lines.
static const rangeAnnotation = -10;
// Axis elements form the "base layer" of all components on the chart. Domain
// axes are drawn on top of measure axes to ensure that the domain axis line
// appears on top of any measure axis grid lines.
static const measureAxis = 0;
static const domainAxis = 5;
// Draw series data on top of axis elements.
static const arc = 10;
static const bar = 10;
static const barTargetLine = 15;
static const line = 20;
static const point = 25;
// Draw most behaviors on top of series data.
static const legend = 100;
static const linePointHighlighter = 110;
static const slider = 150;
static const chartTitle = 160;
}
/// Standard layout position orders for all internal components.
///
/// Custom component layers should define their positionOrder by taking the
/// nearest component from this list, and adding or subtracting 1. This will
/// help reduce the chance of custom behaviors, renderers, etc. from breaking if
/// we need to re-order these components internally.
class LayoutViewPositionOrder {
static const drawArea = 0;
static const symbolAnnotation = 10;
static const axis = 20;
static const legend = 30;
static const chartTitle = 40;
}
/// A configuration for margin (empty space) around a layout child view.
class ViewMargin {
/// A [ViewMargin] with all zero px.
static const empty =
const ViewMargin(topPx: 0, bottomPx: 0, rightPx: 0, leftPx: 0);
final int topPx;
final int bottomPx;
final int rightPx;
final int leftPx;
const ViewMargin({int topPx, int bottomPx, int rightPx, int leftPx})
: topPx = topPx ?? 0,
bottomPx = bottomPx ?? 0,
rightPx = rightPx ?? 0,
leftPx = leftPx ?? 0;
/// Total width.
int get width => leftPx + rightPx;
/// Total height.
int get height => topPx + bottomPx;
}
/// Configuration of a [LayoutView].
class LayoutViewConfig {
/// Unique identifier for the [LayoutView].
String id;
/// The order to paint a [LayoutView] on the canvas.
///
/// The smaller number is drawn first.
int paintOrder;
/// The position of a [LayoutView] defining where to place the view.
LayoutPosition position;
/// The order to place the [LayoutView] within a chart margin.
///
/// The smaller number is closer to the draw area. Elements positioned closer
/// to the draw area will be given extra layout space first, before those
/// further away.
///
/// Note that all views positioned in the draw area are given the entire draw
/// area bounds as their component bounds.
int positionOrder;
/// Defines the space around a layout component.
ViewMargin viewMargin;
/// Creates new [LayoutParams].
///
/// [paintOrder] the order that this component will be drawn.
/// [position] the [ComponentPosition] of this component.
/// [positionOrder] the order of this component in a chart margin.
LayoutViewConfig(
{@required this.paintOrder,
@required this.position,
@required this.positionOrder,
ViewMargin viewMargin})
: viewMargin = viewMargin ?? ViewMargin.empty;
/// Returns true if it is a full position.
bool get isFullPosition =>
position == LayoutPosition.FullBottom ||
position == LayoutPosition.FullTop ||
position == LayoutPosition.FullRight ||
position == LayoutPosition.FullLeft;
}
/// Size measurements of one component.
///
/// The measurement is tight to the component, without adding [ComponentBuffer].
class ViewMeasuredSizes {
/// All zeroes component size.
static const zero = const ViewMeasuredSizes(
preferredWidth: 0, preferredHeight: 0, minWidth: 0, minHeight: 0);
final int preferredWidth;
final int preferredHeight;
final int minWidth;
final int minHeight;
/// Create a new [ViewSizes].
///
/// [preferredWidth] the component's preferred width.
/// [preferredHeight] the component's preferred width.
/// [minWidth] the component's minimum width. If not set, default to 0.
/// [minHeight] the component's minimum height. If not set, default to 0.
const ViewMeasuredSizes(
{@required this.preferredWidth,
@required this.preferredHeight,
int minWidth,
int minHeight})
: minWidth = minWidth ?? 0,
minHeight = minHeight ?? 0;
}
/// A component that measures its size and accepts bounds to complete layout.
abstract class LayoutView {
GraphicsFactory get graphicsFactory;
set graphicsFactory(GraphicsFactory value);
/// Layout params for this component.
LayoutViewConfig get layoutConfig;
/// Measure and return the size of this component.
///
/// This measurement is without the [ComponentBuffer], which is added by the
/// layout manager.
ViewMeasuredSizes measure(int maxWidth, int maxHeight);
/// Layout this component.
void layout(Rectangle<int> componentBounds, Rectangle<int> drawAreaBounds);
/// Draw this component on the canvas.
void paint(ChartCanvas canvas, double animationPercent);
/// Bounding box for drawing this component.
Rectangle<int> get componentBounds;
/// Whether or not this component is a series renderer that draws series
/// data.
///
/// This component may either render into the chart's draw area, or into a
/// separate area bounded by the component bounds.
bool get isSeriesRenderer;
}