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:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
121
web/charts/common/lib/src/chart/layout/layout_config.dart
Normal file
121
web/charts/common/lib/src/chart/layout/layout_config.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
web/charts/common/lib/src/chart/layout/layout_manager.dart
Normal file
70
web/charts/common/lib/src/chart/layout/layout_manager.dart
Normal 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;
|
||||
}
|
||||
368
web/charts/common/lib/src/chart/layout/layout_manager_impl.dart
Normal file
368
web/charts/common/lib/src/chart/layout/layout_manager_impl.dart
Normal 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});
|
||||
}
|
||||
@@ -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++;
|
||||
});
|
||||
}
|
||||
}
|
||||
208
web/charts/common/lib/src/chart/layout/layout_view.dart
Normal file
208
web/charts/common/lib/src/chart/layout/layout_view.dart
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user