diff --git a/web/.gitignore b/web/.gitignore index 51955fa2a..dea4d4c6d 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1 +1,2 @@ !**/pubspec.lock +*/build diff --git a/web/_tool/peanut_post_build.dart b/web/_tool/peanut_post_build.dart index 6a9486087..331260e36 100644 --- a/web/_tool/peanut_post_build.dart +++ b/web/_tool/peanut_post_build.dart @@ -110,7 +110,7 @@ class _Demo { String get html => '''
- $name + $name $name
diff --git a/web/charts/example/README.md b/web/charts/README.md similarity index 100% rename from web/charts/example/README.md rename to web/charts/README.md diff --git a/web/charts/example/web/preview.png b/web/charts/assets/preview.png similarity index 100% rename from web/charts/example/web/preview.png rename to web/charts/assets/preview.png diff --git a/web/charts/common/.gitignore b/web/charts/common/.gitignore deleted file mode 100644 index d16386367..000000000 --- a/web/charts/common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ \ No newline at end of file diff --git a/web/charts/common/CHANGELOG.md b/web/charts/common/CHANGELOG.md deleted file mode 100644 index f5597bf94..000000000 --- a/web/charts/common/CHANGELOG.md +++ /dev/null @@ -1,48 +0,0 @@ -# 0.6.0 -* Bars can now be rendered on line charts. -* Negative measure values will now be rendered on bar charts as a separate stack from the positive -values. -* Added a Datum Legend, which displays one entry per value in the first series on the chart. This is - useful for pie and scatter plot charts. -* The AxisPosition enum in RTLSpec was refactored to AxisDirection to better reflect its effect on -swapping the positions of all start and end components, and not just positioning the measure axes. -* Added custom colors for line renderer area skirts and confidence intervals. A new "areaColorFn" -has been added to Series, and corresponding data to the datum. We could not use the fillColorFn for -these elements, because that color is already applied to the internal section of points on line -charts (including highlighter behaviors). - -# 0.5.0 -* SelectionModelConfig's listener parameter has been renamed to "changeListener". This is a breaking -change. Please rename any existing uses of the "listener" parameter to "changeListener". This was -named in order to add an additional listener "updateListener" that listens to any update requests, -regardless if the selection model has changed. -* CartesianChart's method getMeasureAxis(String axisId) has been changed to -getMeasureAxis({String axisId) so that getting the primary measure axis will not need passing any id -that does not match the secondary measure axis id. This affects users implementing custom behaviors -using the existing method. - -# 0.4.0 -* Declare compatibility with Dart 2. -* BasicNumericTickFormatterSpec now takes in a callback instead of NumberFormat as the default constructor. Use named constructor withNumberFormat instead. This is a breaking change. -* BarRendererConfig is no longer default of type String, please change current usage to BarRendererConfig. This is a breaking change. -* BarTargetLineRendererConfig is no longer default of type String, please change current usage to BarTargetLineRendererConfig. This is a breaking change. - - -# 0.3.0 -* Simplified API by removing the requirement for specifying the datum type when creating a chart. -For example, previously to construct a bar chart the syntax was 'new BarChart()'. -The syntax is now cleaned up to be 'new BarChart()'. Please refer to the -[online gallery](https://google.github.io/charts/flutter/gallery.html) for the correct syntax. -* Added scatter plot charts -* Added tap to hide for legends -* Added support for rendering area skirts to line charts -* Added support for configurable fill colors to bar charts - -# 0.2.0 - -* Update color palette. Please use MaterialPalette instead of QuantumPalette. -* Dart2 fixes - -# 0.1.0 - -Initial release. diff --git a/web/charts/common/LICENSE b/web/charts/common/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/web/charts/common/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/web/charts/common/README.md b/web/charts/common/README.md deleted file mode 100644 index f7e5eee6d..000000000 --- a/web/charts/common/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Common Charting library - -[![pub package](https://img.shields.io/pub/v/charts_common.svg)](https://pub.dartlang.org/packages/charts_common) - -Common componnets for charting libraries. diff --git a/web/charts/common/analysis_options.yaml b/web/charts/common/analysis_options.yaml deleted file mode 100644 index 08b772347..000000000 --- a/web/charts/common/analysis_options.yaml +++ /dev/null @@ -1,32 +0,0 @@ -include: package:pedantic/analysis_options.yaml - -analyzer: - # strong-mode: - # implicit-casts: false - # implicit-dynamic: false - -linter: - rules: - - avoid_types_on_closure_parameters - - avoid_void_async - - await_only_futures - - camel_case_types - - cancel_subscriptions - - close_sinks - # TODO(domesticmouse): rename constants - # - constant_identifier_names - - control_flow_in_finally - - empty_statements - # TODO(domesticmouse): implement hashCode methods - # - hash_and_equals - - implementation_imports - - non_constant_identifier_names - - package_api_docs - - package_names - - package_prefixed_library_names - - test_types_in_equals - - throw_in_finally - - unnecessary_brace_in_string_interps - - unnecessary_getters_setters - - unnecessary_new - - unnecessary_statements diff --git a/web/charts/common/lib/common.dart b/web/charts/common/lib/common.dart deleted file mode 100644 index 12de87b2b..000000000 --- a/web/charts/common/lib/common.dart +++ /dev/null @@ -1,240 +0,0 @@ -// 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. - -export 'src/chart/bar/bar_chart.dart' show BarChart; -export 'src/chart/bar/bar_label_decorator.dart' - show BarLabelAnchor, BarLabelDecorator, BarLabelPosition; -export 'src/chart/bar/bar_lane_renderer_config.dart' show BarLaneRendererConfig; -export 'src/chart/bar/bar_renderer.dart' - show BarRenderer, ImmutableBarRendererElement; -export 'src/chart/bar/bar_renderer_config.dart' - show - BarRendererConfig, - CornerStrategy, - ConstCornerStrategy, - NoCornerStrategy; -export 'src/chart/bar/bar_renderer_decorator.dart' show BarRendererDecorator; -export 'src/chart/bar/bar_target_line_renderer.dart' show BarTargetLineRenderer; -export 'src/chart/bar/bar_target_line_renderer_config.dart' - show BarTargetLineRendererConfig; -export 'src/chart/bar/base_bar_renderer_config.dart' - show BarGroupingType, BaseBarRendererConfig; -export 'src/chart/cartesian/axis/axis.dart' - show - domainAxisKey, - measureAxisIdKey, - measureAxisKey, - Axis, - NumericAxis, - OrdinalAxis, - OrdinalViewport; -export 'src/chart/cartesian/axis/numeric_extents.dart' show NumericExtents; -export 'src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart' - show GridlineRendererSpec; -export 'src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart' - show NoneRenderSpec; -export 'src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart' - show SmallTickRendererSpec; -export 'src/chart/cartesian/axis/tick_formatter.dart' - show SimpleTickFormatterBase, TickFormatter; -export 'src/chart/cartesian/axis/spec/axis_spec.dart' - show - AxisSpec, - LineStyleSpec, - RenderSpec, - TextStyleSpec, - TickLabelAnchor, - TickLabelJustification, - TickFormatterSpec; -export 'src/chart/cartesian/axis/spec/bucketing_axis_spec.dart' - show BucketingAxisSpec, BucketingNumericTickProviderSpec; -export 'src/chart/cartesian/axis/spec/date_time_axis_spec.dart' - show - DateTimeAxisSpec, - DayTickProviderSpec, - AutoDateTimeTickFormatterSpec, - AutoDateTimeTickProviderSpec, - DateTimeEndPointsTickProviderSpec, - DateTimeTickFormatterSpec, - DateTimeTickProviderSpec, - TimeFormatterSpec, - StaticDateTimeTickProviderSpec; -export 'src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart' - show EndPointsTimeAxisSpec; -export 'src/chart/cartesian/axis/spec/numeric_axis_spec.dart' - show - NumericAxisSpec, - NumericEndPointsTickProviderSpec, - NumericTickProviderSpec, - NumericTickFormatterSpec, - BasicNumericTickFormatterSpec, - BasicNumericTickProviderSpec, - StaticNumericTickProviderSpec; -export 'src/chart/cartesian/axis/spec/ordinal_axis_spec.dart' - show - BasicOrdinalTickProviderSpec, - BasicOrdinalTickFormatterSpec, - OrdinalAxisSpec, - OrdinalTickFormatterSpec, - OrdinalTickProviderSpec, - StaticOrdinalTickProviderSpec; -export 'src/chart/cartesian/axis/spec/percent_axis_spec.dart' - show PercentAxisSpec; -export 'src/chart/cartesian/axis/time/date_time_extents.dart' - show DateTimeExtents; -export 'src/chart/cartesian/axis/time/date_time_tick_formatter.dart' - show DateTimeTickFormatter; -export 'src/chart/cartesian/axis/spec/tick_spec.dart' show TickSpec; -export 'src/chart/cartesian/cartesian_chart.dart' - show CartesianChart, NumericCartesianChart, OrdinalCartesianChart; -export 'src/chart/cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; -export 'src/chart/common/base_chart.dart' show BaseChart, LifecycleListener; -export 'src/chart/common/behavior/a11y/a11y_explore_behavior.dart' - show ExploreModeTrigger; -export 'src/chart/common/behavior/a11y/a11y_node.dart' show A11yNode; -export 'src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart' - show DomainA11yExploreBehavior, VocalizationCallback; -export 'src/chart/common/behavior/chart_behavior.dart' - show - BehaviorPosition, - ChartBehavior, - InsideJustification, - OutsideJustification; -export 'src/chart/common/behavior/calculation/percent_injector.dart' - show PercentInjector, PercentInjectorTotalType; -export 'src/chart/common/behavior/domain_highlighter.dart' - show DomainHighlighter; -export 'src/chart/common/behavior/initial_selection.dart' show InitialSelection; -export 'src/chart/common/behavior/legend/legend.dart' - show Legend, LegendCellPadding, LegendState, LegendTapHandling; -export 'src/chart/common/behavior/legend/legend_entry.dart' show LegendEntry; -export 'src/chart/common/behavior/legend/legend_entry_generator.dart' - show LegendEntryGenerator, LegendDefaultMeasure; -export 'src/chart/common/behavior/legend/datum_legend.dart' show DatumLegend; -export 'src/chart/common/behavior/legend/series_legend.dart' show SeriesLegend; -export 'src/chart/common/behavior/line_point_highlighter.dart' - show LinePointHighlighter, LinePointHighlighterFollowLineType; -export 'src/chart/common/behavior/range_annotation.dart' - show - AnnotationLabelAnchor, - AnnotationLabelDirection, - AnnotationLabelPosition, - AnnotationSegment, - LineAnnotationSegment, - RangeAnnotation, - RangeAnnotationAxisType, - RangeAnnotationSegment; -export 'src/chart/common/behavior/sliding_viewport.dart' show SlidingViewport; -export 'src/chart/common/behavior/chart_title/chart_title.dart' - show ChartTitle, ChartTitleDirection; -export 'src/chart/common/behavior/selection/lock_selection.dart' - show LockSelection; -export 'src/chart/common/behavior/selection/select_nearest.dart' - show SelectNearest; -export 'src/chart/common/behavior/selection/selection_trigger.dart' - show SelectionTrigger; -export 'src/chart/common/behavior/slider/slider.dart' - show - Slider, - SliderHandlePosition, - SliderListenerCallback, - SliderListenerDragState, - SliderStyle; -export 'src/chart/common/behavior/zoom/initial_hint_behavior.dart' - show InitialHintBehavior; -export 'src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart' - show PanAndZoomBehavior; -export 'src/chart/common/behavior/zoom/pan_behavior.dart' - show PanBehavior, PanningCompletedCallback; -export 'src/chart/common/behavior/zoom/panning_tick_provider.dart' - show PanningTickProviderMode; -export 'src/chart/common/canvas_shapes.dart' - show CanvasBarStack, CanvasPie, CanvasPieSlice, CanvasRect; -export 'src/chart/common/chart_canvas.dart' show ChartCanvas, FillPatternType; -export 'src/chart/common/chart_context.dart' show ChartContext; -export 'src/chart/common/datum_details.dart' - show DatumDetails, DomainFormatter, MeasureFormatter; -export 'src/chart/common/processed_series.dart' - show ImmutableSeries, MutableSeries; -export 'src/chart/common/series_datum.dart' show SeriesDatum, SeriesDatumConfig; -export 'src/chart/common/selection_model/selection_model.dart' - show SelectionModel, SelectionModelType, SelectionModelListener; -export 'src/chart/common/series_renderer.dart' - show rendererIdKey, rendererKey, SeriesRenderer; -export 'src/chart/common/series_renderer_config.dart' - show RendererAttributeKey, SeriesRendererConfig; -export 'src/chart/layout/layout_config.dart' show LayoutConfig, MarginSpec; -export 'src/chart/layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - ViewMargin, - ViewMeasuredSizes; -export 'src/chart/line/line_chart.dart' show LineChart; -export 'src/chart/line/line_renderer.dart' show LineRenderer; -export 'src/chart/line/line_renderer_config.dart' show LineRendererConfig; -export 'src/chart/pie/arc_label_decorator.dart' - show ArcLabelDecorator, ArcLabelLeaderLineStyleSpec, ArcLabelPosition; -export 'src/chart/pie/arc_renderer.dart' show ArcRenderer; -export 'src/chart/pie/arc_renderer_config.dart' show ArcRendererConfig; -export 'src/chart/pie/pie_chart.dart' show PieChart; -export 'src/chart/scatter_plot/comparison_points_decorator.dart' - show ComparisonPointsDecorator; -export 'src/chart/scatter_plot/point_renderer.dart' - show - boundsLineRadiusPxKey, - boundsLineRadiusPxFnKey, - pointSymbolRendererFnKey, - pointSymbolRendererIdKey, - PointRenderer; -export 'src/chart/scatter_plot/point_renderer_config.dart' - show PointRendererConfig; -export 'src/chart/scatter_plot/point_renderer_decorator.dart' - show PointRendererDecorator; -export 'src/chart/scatter_plot/scatter_plot_chart.dart' show ScatterPlotChart; -export 'src/chart/scatter_plot/symbol_annotation_renderer.dart' - show SymbolAnnotationRenderer; -export 'src/chart/scatter_plot/symbol_annotation_renderer_config.dart' - show SymbolAnnotationRendererConfig; -export 'src/chart/time_series/time_series_chart.dart' show TimeSeriesChart; -export 'src/common/color.dart' show Color; -export 'src/common/date_time_factory.dart' - show DateTimeFactory, LocalDateTimeFactory, UTCDateTimeFactory; -export 'src/common/gesture_listener.dart' show GestureListener; -export 'src/common/graphics_factory.dart' show GraphicsFactory; -export 'src/common/line_style.dart' show LineStyle; -export 'src/common/material_palette.dart' show MaterialPalette; -export 'src/common/performance.dart' show Performance; -export 'src/common/proxy_gesture_listener.dart' show ProxyGestureListener; -export 'src/common/rtl_spec.dart' show AxisDirection, RTLSpec; -export 'src/common/style/material_style.dart' show MaterialStyle; -export 'src/common/style/style_factory.dart' show StyleFactory; -export 'src/common/symbol_renderer.dart' - show - CircleSymbolRenderer, - CylinderSymbolRenderer, - LineSymbolRenderer, - PointSymbolRenderer, - RectSymbolRenderer, - RoundedRectSymbolRenderer, - SymbolRenderer; -export 'src/common/text_element.dart' - show TextElement, TextDirection, MaxWidthStrategy; -export 'src/common/text_measurement.dart' show TextMeasurement; -export 'src/common/text_style.dart' show TextStyle; -export 'src/data/series.dart' show Series, TypedAccessorFn; diff --git a/web/charts/common/lib/src/chart/bar/bar_chart.dart b/web/charts/common/lib/src/chart/bar/bar_chart.dart deleted file mode 100644 index a9810c6c1..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_chart.dart +++ /dev/null @@ -1,42 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import '../bar/bar_renderer.dart' show BarRenderer; -import '../cartesian/axis/axis.dart' show NumericAxis; -import '../cartesian/cartesian_chart.dart' show OrdinalCartesianChart; -import '../common/series_renderer.dart' show SeriesRenderer; -import '../layout/layout_config.dart' show LayoutConfig; - -class BarChart extends OrdinalCartesianChart { - BarChart( - {bool vertical, - LayoutConfig layoutConfig, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes}) - : super( - vertical: vertical, - layoutConfig: layoutConfig, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes); - - @override - SeriesRenderer makeDefaultRenderer() { - return BarRenderer()..rendererId = SeriesRenderer.defaultRendererId; - } -} diff --git a/web/charts/common/lib/src/chart/bar/bar_label_decorator.dart b/web/charts/common/lib/src/chart/bar/bar_label_decorator.dart deleted file mode 100644 index 5202e02d0..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_label_decorator.dart +++ /dev/null @@ -1,234 +0,0 @@ -// 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' show required; - -import '../../common/color.dart' show Color; -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/text_element.dart' show TextDirection; -import '../../common/text_style.dart' show TextStyle; -import '../../data/series.dart' show AccessorFn; -import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../common/chart_canvas.dart' show ChartCanvas; -import 'bar_renderer.dart' show ImmutableBarRendererElement; -import 'bar_renderer_decorator.dart' show BarRendererDecorator; - -class BarLabelDecorator extends BarRendererDecorator { - // Default configuration - static const _defaultLabelPosition = BarLabelPosition.auto; - static const _defaultLabelPadding = 5; - static const _defaultLabelAnchor = BarLabelAnchor.start; - static final _defaultInsideLabelStyle = - TextStyleSpec(fontSize: 12, color: Color.white); - static final _defaultOutsideLabelStyle = - TextStyleSpec(fontSize: 12, color: Color.black); - - /// Configures [TextStyleSpec] for labels placed inside the bars. - final TextStyleSpec insideLabelStyleSpec; - - /// Configures [TextStyleSpec] for labels placed outside the bars. - final TextStyleSpec outsideLabelStyleSpec; - - /// Configures where to place the label relative to the bars. - final BarLabelPosition labelPosition; - - /// For labels drawn inside the bar, configures label anchor position. - final BarLabelAnchor labelAnchor; - - /// Space before and after the label text. - final int labelPadding; - - BarLabelDecorator( - {TextStyleSpec insideLabelStyleSpec, - TextStyleSpec outsideLabelStyleSpec, - this.labelPosition = _defaultLabelPosition, - this.labelPadding = _defaultLabelPadding, - this.labelAnchor = _defaultLabelAnchor}) - : insideLabelStyleSpec = insideLabelStyleSpec ?? _defaultInsideLabelStyle, - outsideLabelStyleSpec = - outsideLabelStyleSpec ?? _defaultOutsideLabelStyle; - - @override - void decorate(Iterable> barElements, - ChartCanvas canvas, GraphicsFactory graphicsFactory, - {@required Rectangle drawBounds, - @required double animationPercent, - @required bool renderingVertically, - bool rtl = false}) { - // TODO: Decorator not yet available for vertical charts. - assert(renderingVertically == false); - - // Only decorate the bars when animation is at 100%. - if (animationPercent != 1.0) { - return; - } - - // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. - // The [GraphicsFactory] is needed so it can't be created earlier. - final insideLabelStyle = - _getTextStyle(graphicsFactory, insideLabelStyleSpec); - final outsideLabelStyle = - _getTextStyle(graphicsFactory, outsideLabelStyleSpec); - - for (var element in barElements) { - final labelFn = element.series.labelAccessorFn; - final datumIndex = element.index; - final label = (labelFn != null) ? labelFn(datumIndex) : null; - - // If there are custom styles, use that instead of the default or the - // style defined for the entire decorator. - final datumInsideLabelStyle = _getDatumStyle( - element.series.insideLabelStyleAccessorFn, - datumIndex, - graphicsFactory, - defaultStyle: insideLabelStyle); - final datumOutsideLabelStyle = _getDatumStyle( - element.series.outsideLabelStyleAccessorFn, - datumIndex, - graphicsFactory, - defaultStyle: outsideLabelStyle); - - // Skip calculation and drawing for this element if no label. - if (label == null || label.isEmpty) { - continue; - } - - final bounds = element.bounds; - - // Get space available inside and outside the bar. - final totalPadding = labelPadding * 2; - final insideBarWidth = bounds.width - totalPadding; - final outsideBarWidth = drawBounds.width - bounds.width - totalPadding; - - final labelElement = graphicsFactory.createTextElement(label); - var calculatedLabelPosition = labelPosition; - if (calculatedLabelPosition == BarLabelPosition.auto) { - // For auto, first try to fit the text inside the bar. - labelElement.textStyle = datumInsideLabelStyle; - - // A label fits if the space inside the bar is >= outside bar or if the - // length of the text fits and the space. This is because if the bar has - // more space than the outside, it makes more sense to place the label - // inside the bar, even if the entire label does not fit. - calculatedLabelPosition = (insideBarWidth >= outsideBarWidth || - labelElement.measurement.horizontalSliceWidth < insideBarWidth) - ? BarLabelPosition.inside - : BarLabelPosition.outside; - } - - // Set the max width and text style. - if (calculatedLabelPosition == BarLabelPosition.inside) { - labelElement.textStyle = datumInsideLabelStyle; - labelElement.maxWidth = insideBarWidth; - } else { - // calculatedLabelPosition == LabelPosition.outside - labelElement.textStyle = datumOutsideLabelStyle; - labelElement.maxWidth = outsideBarWidth; - } - - // Only calculate and draw label if there's actually space for the label. - if (labelElement.maxWidth > 0) { - // Calculate the start position of label based on [labelAnchor]. - int labelX; - if (calculatedLabelPosition == BarLabelPosition.inside) { - switch (labelAnchor) { - case BarLabelAnchor.middle: - labelX = (bounds.left + - bounds.width / 2 - - labelElement.measurement.horizontalSliceWidth / 2) - .round(); - labelElement.textDirection = - rtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case BarLabelAnchor.end: - case BarLabelAnchor.start: - final alignLeft = rtl - ? (labelAnchor == BarLabelAnchor.end) - : (labelAnchor == BarLabelAnchor.start); - - if (alignLeft) { - labelX = bounds.left + labelPadding; - labelElement.textDirection = TextDirection.ltr; - } else { - labelX = bounds.right - labelPadding; - labelElement.textDirection = TextDirection.rtl; - } - break; - } - } else { - // calculatedLabelPosition == LabelPosition.outside - labelX = bounds.right + labelPadding; - labelElement.textDirection = TextDirection.ltr; - } - - // Center the label inside the bar. - final labelY = (bounds.top + - (bounds.bottom - bounds.top) / 2 - - labelElement.measurement.verticalSliceWidth / 2) - .round(); - - canvas.drawText(labelElement, labelX, labelY); - } - } - } - - // Helper function that converts [TextStyleSpec] to [TextStyle]. - TextStyle _getTextStyle( - GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { - return graphicsFactory.createTextPaint() - ..color = labelSpec?.color ?? Color.black - ..fontFamily = labelSpec?.fontFamily - ..fontSize = labelSpec?.fontSize ?? 12; - } - - /// Helper function to get datum specific style - TextStyle _getDatumStyle(AccessorFn labelFn, int datumIndex, - GraphicsFactory graphicsFactory, - {TextStyle defaultStyle}) { - final styleSpec = (labelFn != null) ? labelFn(datumIndex) : null; - return (styleSpec != null) - ? _getTextStyle(graphicsFactory, styleSpec) - : defaultStyle; - } -} - -/// Configures where to place the label relative to the bars. -enum BarLabelPosition { - /// Automatically try to place the label inside the bar first and place it on - /// the outside of the space available outside the bar is greater than space - /// available inside the bar. - auto, - - /// Always place label on the outside. - outside, - - /// Always place label on the inside. - inside, -} - -/// Configures where to anchor the label for labels drawn inside the bars. -enum BarLabelAnchor { - /// Anchor to the measure start. - start, - - /// Anchor to the middle of the measure range. - middle, - - /// Anchor to the measure end. - end, -} diff --git a/web/charts/common/lib/src/chart/bar/bar_lane_renderer.dart b/web/charts/common/lib/src/chart/bar/bar_lane_renderer.dart deleted file mode 100644 index c4b9fe9cb..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_lane_renderer.dart +++ /dev/null @@ -1,365 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import '../../data/series.dart' show AttributeKey; -import '../cartesian/axis/axis.dart' - show ImmutableAxis, domainAxisKey, measureAxisKey; -import '../cartesian/cartesian_chart.dart' show CartesianChart; -import '../common/chart_canvas.dart' show ChartCanvas; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import 'bar_lane_renderer_config.dart' show BarLaneRendererConfig; -import 'bar_renderer.dart' show AnimatedBar, BarRenderer, BarRendererElement; -import 'bar_renderer_decorator.dart' show BarRendererDecorator; -import 'base_bar_renderer.dart' - show - barGroupCountKey, - barGroupIndexKey, - barGroupWeightKey, - previousBarGroupWeightKey, - stackKeyKey; -import 'base_bar_renderer_element.dart' show BaseBarRendererElement; - -/// Key for storing a list of all domain values that exist in the series data. -/// -/// In grouped stacked mode, this list will contain a combination of domain -/// value and series category. -const domainValuesKey = AttributeKey('BarLaneRenderer.domainValues'); - -/// Renders series data as a series of bars with lanes. -/// -/// Every stack of bars will have a swim lane rendered underneath the series -/// data, in a gray color by default. The swim lane occupies the same width as -/// the bar elements, and will be completely covered up if the bar stack happens -/// to take up the entire measure domain range. -/// -/// If every bar that shares a domain value has a null measure value, then the -/// swim lanes may optionally be merged together into one wide lane that covers -/// the full domain range band width. -class BarLaneRenderer extends BarRenderer { - final BarRendererDecorator barRendererDecorator; - - /// Store a map of domain+barGroupIndex+category index to bar lanes in a - /// stack. - /// - /// This map is used to render all the bars in a stack together, to account - /// for rendering effects that need to take the full stack into account (e.g. - /// corner rounding). - /// - /// [LinkedHashMap] is used to render the bars on the canvas in the same order - /// as the data was given to the chart. For the case where both grouping and - /// stacking are disabled, this means that bars for data later in the series - /// will be drawn "on top of" bars earlier in the series. - final _barLaneStackMap = LinkedHashMap>>(); - - /// Store a map of flags to track whether all measure values for a given - /// domain value are null, for every series on the chart. - final _allMeasuresForDomainNullMap = LinkedHashMap(); - - factory BarLaneRenderer({BarLaneRendererConfig config, String rendererId}) { - rendererId ??= 'bar'; - config ??= BarLaneRendererConfig(); - return BarLaneRenderer._internal(config: config, rendererId: rendererId); - } - - BarLaneRenderer._internal({BarLaneRendererConfig config, String rendererId}) - : barRendererDecorator = config.barRendererDecorator, - super.internal(config: config, rendererId: rendererId); - - @override - void preprocessSeries(List> seriesList) { - super.preprocessSeries(seriesList); - - _allMeasuresForDomainNullMap.clear(); - - seriesList.forEach((series) { - final domainFn = series.domainFn; - final measureFn = series.rawMeasureFn; - - final domainValues = Set(); - - for (var barIndex = 0; barIndex < series.data.length; barIndex++) { - final domain = domainFn(barIndex); - final measure = measureFn(barIndex); - - domainValues.add(domain); - - // Update the "all measure null" tracking for bars that have the - // current domain value. - if ((config as BarLaneRendererConfig).mergeEmptyLanes) { - final allNull = _allMeasuresForDomainNullMap[domain]; - final isNull = measure == null; - - _allMeasuresForDomainNullMap[domain] = - allNull != null ? allNull && isNull : isNull; - } - } - - series.setAttr(domainValuesKey, domainValues); - }); - } - - @override - void update(List> seriesList, bool isAnimatingThisDraw) { - super.update(seriesList, isAnimatingThisDraw); - - // Add gray bars to render under every bar stack. - seriesList.forEach((series) { - Set domainValues = series.getAttr(domainValuesKey) as Set; - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - final seriesStackKey = series.getAttr(stackKeyKey); - final barGroupCount = series.getAttr(barGroupCountKey); - final barGroupIndex = series.getAttr(barGroupIndexKey); - final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); - final barGroupWeight = series.getAttr(barGroupWeightKey); - final measureAxisPosition = measureAxis.getLocation(0.0); - final maxMeasureValue = _getMaxMeasureValue(measureAxis); - - // Create a fake series for [BarLabelDecorator] to use when looking up the - // index of each datum. - final laneSeries = MutableSeries.clone(seriesList[0]); - laneSeries.data = []; - - // Don't render any labels on the swim lanes. - laneSeries.labelAccessorFn = (index) => ''; - - var laneSeriesIndex = 0; - domainValues.forEach((domainValue) { - // Skip adding any background bars if they will be covered up by the - // domain-spanning null bar. - if (_allMeasuresForDomainNullMap[domainValue] == true) { - return; - } - - // Add a fake datum to the series for [BarLabelDecorator]. - final datum = {'index': laneSeriesIndex}; - laneSeries.data.add(datum); - - // Each bar should be stored in barStackMap in a structure that mirrors - // the visual rendering of the bars. Thus, they should be grouped by - // domain value, series category (by way of the stack keys that were - // generated for each series in the preprocess step), and bar group - // index to account for all combinations of grouping and stacking. - final barStackMapKey = domainValue.toString() + - '__' + - seriesStackKey + - '__' + - barGroupIndex.toString(); - - final barKey = barStackMapKey + '0'; - - final barStackList = _barLaneStackMap.putIfAbsent( - barStackMapKey, () => >[]); - - // If we already have an AnimatingBar for that index, use it. - var animatingBar = barStackList.firstWhere((bar) => bar.key == barKey, - orElse: () => null); - - // If we don't have any existing bar element, create a new bar and have - // it animate in from the domain axis. - if (animatingBar == null) { - animatingBar = makeAnimatedBar( - key: barKey, - series: laneSeries, - datum: datum, - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - color: (config as BarLaneRendererConfig).backgroundBarColor, - details: BarRendererElement(), - domainValue: domainValue, - domainAxis: domainAxis, - domainWidth: domainAxis.rangeBand.round(), - fillColor: (config as BarLaneRendererConfig).backgroundBarColor, - measureValue: maxMeasureValue, - measureOffsetValue: 0.0, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - numBarGroups: barGroupCount, - strokeWidthPx: config.strokeWidthPx, - measureIsNull: false, - measureIsNegative: false); - - barStackList.add(animatingBar); - } else { - animatingBar - ..datum = datum - ..series = laneSeries - ..domainValue = domainValue; - } - - // Get the barElement we are going to setup. - // Optimization to prevent allocation in non-animating case. - BaseBarRendererElement barElement = makeBarRendererElement( - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - color: (config as BarLaneRendererConfig).backgroundBarColor, - details: BarRendererElement(), - domainValue: domainValue, - domainAxis: domainAxis, - domainWidth: domainAxis.rangeBand.round(), - fillColor: (config as BarLaneRendererConfig).backgroundBarColor, - measureValue: maxMeasureValue, - measureOffsetValue: 0.0, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - numBarGroups: barGroupCount, - strokeWidthPx: config.strokeWidthPx, - measureIsNull: false, - measureIsNegative: false); - - animatingBar.setNewTarget(barElement); - - laneSeriesIndex++; - }); - }); - - // Add domain-spanning bars to render when every measure value for every - // datum of a given domain is null. - if ((config as BarLaneRendererConfig).mergeEmptyLanes) { - // Use the axes from the first series. - final domainAxis = - seriesList[0].getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = - seriesList[0].getAttr(measureAxisKey) as ImmutableAxis; - - final measureAxisPosition = measureAxis.getLocation(0.0); - final maxMeasureValue = _getMaxMeasureValue(measureAxis); - - final barGroupIndex = 0; - final previousBarGroupWeight = 0.0; - final barGroupWeight = 1.0; - final barGroupCount = 1; - - // Create a fake series for [BarLabelDecorator] to use when looking up the - // index of each datum. We don't care about any other series values for - // the merged lanes, so just clone the first series. - final mergedSeries = MutableSeries.clone(seriesList[0]); - mergedSeries.data = []; - - // Add a label accessor that returns the empty lane label. - mergedSeries.labelAccessorFn = - (index) => (config as BarLaneRendererConfig).emptyLaneLabel; - - var mergedSeriesIndex = 0; - _allMeasuresForDomainNullMap.forEach((domainValue, allNull) { - if (allNull) { - // Add a fake datum to the series for [BarLabelDecorator]. - final datum = {'index': mergedSeriesIndex}; - mergedSeries.data.add(datum); - - final barStackMapKey = domainValue.toString() + '__allNull__'; - - final barKey = barStackMapKey + '0'; - - final barStackList = _barLaneStackMap.putIfAbsent( - barStackMapKey, () => >[]); - - // If we already have an AnimatingBar for that index, use it. - var animatingBar = barStackList.firstWhere((bar) => bar.key == barKey, - orElse: () => null); - - // If we don't have any existing bar element, create a new bar and have - // it animate in from the domain axis. - if (animatingBar == null) { - animatingBar = makeAnimatedBar( - key: barKey, - series: mergedSeries, - datum: datum, - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - color: (config as BarLaneRendererConfig).backgroundBarColor, - details: BarRendererElement(), - domainValue: domainValue, - domainAxis: domainAxis, - domainWidth: domainAxis.rangeBand.round(), - fillColor: (config as BarLaneRendererConfig).backgroundBarColor, - measureValue: maxMeasureValue, - measureOffsetValue: 0.0, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - numBarGroups: barGroupCount, - strokeWidthPx: config.strokeWidthPx, - measureIsNull: false, - measureIsNegative: false); - - barStackList.add(animatingBar); - } else { - animatingBar - ..datum = datum - ..series = mergedSeries - ..domainValue = domainValue; - } - - // Get the barElement we are going to setup. - // Optimization to prevent allocation in non-animating case. - BaseBarRendererElement barElement = makeBarRendererElement( - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - color: (config as BarLaneRendererConfig).backgroundBarColor, - details: BarRendererElement(), - domainValue: domainValue, - domainAxis: domainAxis, - domainWidth: domainAxis.rangeBand.round(), - fillColor: (config as BarLaneRendererConfig).backgroundBarColor, - measureValue: maxMeasureValue, - measureOffsetValue: 0.0, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - numBarGroups: barGroupCount, - strokeWidthPx: config.strokeWidthPx, - measureIsNull: false, - measureIsNegative: false); - - animatingBar.setNewTarget(barElement); - - mergedSeriesIndex++; - } - }); - } - } - - /// Gets the maximum measure value that will fit in the draw area. - num _getMaxMeasureValue(ImmutableAxis measureAxis) { - final pos = (chart as CartesianChart).vertical - ? chart.drawAreaBounds.top - : isRtl ? chart.drawAreaBounds.left : chart.drawAreaBounds.right; - - return measureAxis.getDomain(pos.toDouble()); - } - - /// Paints the current bar data on the canvas. - @override - void paint(ChartCanvas canvas, double animationPercent) { - _barLaneStackMap.forEach((stackKey, barStack) { - // Turn this into a list so that the getCurrentBar isn't called more than - // once for each animationPercent if the barElements are iterated more - // than once. - List> barElements = barStack - .map((animatingBar) => animatingBar.getCurrentBar(animationPercent)) - .toList(); - - paintBar(canvas, animationPercent, barElements); - }); - - super.paint(canvas, animationPercent); - } -} diff --git a/web/charts/common/lib/src/chart/bar/bar_lane_renderer_config.dart b/web/charts/common/lib/src/chart/bar/bar_lane_renderer_config.dart deleted file mode 100644 index 436eb46e9..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_lane_renderer_config.dart +++ /dev/null @@ -1,103 +0,0 @@ -// 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 '../../common/color.dart' show Color; -import '../../common/style/style_factory.dart' show StyleFactory; -import '../../common/symbol_renderer.dart'; -import '../common/chart_canvas.dart' show FillPatternType; -import '../layout/layout_view.dart' show LayoutViewPaintOrder; -import 'bar_label_decorator.dart' show BarLabelDecorator; -import 'bar_lane_renderer.dart' show BarLaneRenderer; -import 'bar_renderer_config.dart' show BarRendererConfig, CornerStrategy; -import 'bar_renderer_decorator.dart' show BarRendererDecorator; -import 'base_bar_renderer_config.dart' show BarGroupingType; - -/// Configuration for a bar lane renderer. -class BarLaneRendererConfig extends BarRendererConfig { - /// The color of background bars. - final Color backgroundBarColor; - - /// Label text to draw on a merged empty lane. - /// - /// This will only be drawn if all of the measures for a domain are null, and - /// [mergeEmptyLanes] is enabled. - /// - /// The renderer must be configured with a [BarLabelDecorator] for this label - /// to be drawn. - final String emptyLaneLabel; - - /// Whether or not all lanes for a given domain value should be merged into - /// one wide lane if all measure values for said domain are null. - final bool mergeEmptyLanes; - - BarLaneRendererConfig({ - String customRendererId, - CornerStrategy cornerStrategy, - this.emptyLaneLabel = 'No data', - FillPatternType fillPattern, - BarGroupingType groupingType, - int layoutPaintOrder = LayoutViewPaintOrder.bar, - this.mergeEmptyLanes = false, - int minBarLengthPx = 0, - double stackHorizontalSeparator, - double strokeWidthPx = 0.0, - BarRendererDecorator barRendererDecorator, - SymbolRenderer symbolRenderer, - Color backgroundBarColor, - List weightPattern, - }) : backgroundBarColor = - backgroundBarColor ?? StyleFactory.style.noDataColor, - super( - barRendererDecorator: barRendererDecorator, - cornerStrategy: cornerStrategy, - customRendererId: customRendererId, - groupingType: groupingType ?? BarGroupingType.grouped, - layoutPaintOrder: layoutPaintOrder, - minBarLengthPx: minBarLengthPx, - fillPattern: fillPattern, - stackHorizontalSeparator: stackHorizontalSeparator, - strokeWidthPx: strokeWidthPx, - symbolRenderer: symbolRenderer, - weightPattern: weightPattern, - ); - - @override - BarLaneRenderer build() { - return BarLaneRenderer(config: this, rendererId: customRendererId); - } - - @override - bool operator ==(other) { - if (identical(this, other)) { - return true; - } - if (!(other is BarLaneRendererConfig)) { - return false; - } - return other.backgroundBarColor == backgroundBarColor && - other.emptyLaneLabel == emptyLaneLabel && - other.mergeEmptyLanes == mergeEmptyLanes && - super == (other); - } - - @override - int get hashCode { - var hash = super.hashCode; - hash = hash * 31 + (backgroundBarColor?.hashCode ?? 0); - hash = hash * 31 + (emptyLaneLabel?.hashCode ?? 0); - hash = hash * 31 + (mergeEmptyLanes?.hashCode ?? 0); - return hash; - } -} diff --git a/web/charts/common/lib/src/chart/bar/bar_renderer.dart b/web/charts/common/lib/src/chart/bar/bar_renderer.dart deleted file mode 100644 index 61b922c16..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_renderer.dart +++ /dev/null @@ -1,556 +0,0 @@ -// 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 max, min, Point, Rectangle; - -import 'package:meta/meta.dart' show protected, required; - -import '../../common/color.dart' show Color; -import '../cartesian/axis/axis.dart' - show ImmutableAxis, domainAxisKey, measureAxisKey; -import '../common/canvas_shapes.dart' show CanvasBarStack, CanvasRect; -import '../common/chart_canvas.dart' show ChartCanvas, FillPatternType; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import '../common/series_datum.dart' show SeriesDatum; -import 'bar_renderer_config.dart' show BarRendererConfig, CornerStrategy; -import 'bar_renderer_decorator.dart' show BarRendererDecorator; -import 'base_bar_renderer.dart' - show - BaseBarRenderer, - barGroupCountKey, - barGroupIndexKey, - previousBarGroupWeightKey, - barGroupWeightKey; -import 'base_bar_renderer_element.dart' - show BaseAnimatedBar, BaseBarRendererElement; - -/// Renders series data as a series of bars. -class BarRenderer - extends BaseBarRenderer, AnimatedBar> { - /// If we are grouped, use this spacing between the bars in a group. - final _barGroupInnerPadding = 2; - - /// The padding between bar stacks. - /// - /// The padding comes out of the bottom of the bar. - final _stackedBarPadding = 1; - - final BarRendererDecorator barRendererDecorator; - - factory BarRenderer({BarRendererConfig config, String rendererId}) { - rendererId ??= 'bar'; - config ??= BarRendererConfig(); - return BarRenderer.internal(config: config, rendererId: rendererId); - } - - /// This constructor is protected because it is used by child classes, which - /// cannot call the factory in their own constructors. - @protected - BarRenderer.internal({BarRendererConfig config, String rendererId}) - : barRendererDecorator = config.barRendererDecorator, - super( - config: config, - rendererId: rendererId, - layoutPaintOrder: config.layoutPaintOrder); - - @override - void configureSeries(List> seriesList) { - assignMissingColors(getOrderedSeriesList(seriesList), - emptyCategoryUsesSinglePalette: true); - } - - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - final series = details.series; - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final barGroupIndex = series.getAttr(barGroupIndexKey); - final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); - final barGroupWeight = series.getAttr(barGroupWeightKey); - final numBarGroups = series.getAttr(barGroupCountKey); - - final bounds = _getBarBounds( - details.domain, - domainAxis, - domainAxis.rangeBand.round(), - details.measure, - details.measureOffset, - measureAxis, - barGroupIndex, - previousBarGroupWeight, - barGroupWeight, - numBarGroups); - - Point chartPosition; - - if (renderingVertically) { - chartPosition = Point( - (bounds.left + (bounds.width / 2)).toDouble(), bounds.top.toDouble()); - } else { - chartPosition = Point( - isRtl ? bounds.left.toDouble() : bounds.right.toDouble(), - (bounds.top + (bounds.height / 2)).toDouble()); - } - - return DatumDetails.from(details, chartPosition: chartPosition); - } - - @override - BarRendererElement getBaseDetails(dynamic datum, int index) { - return BarRendererElement(); - } - - CornerStrategy get cornerStrategy { - return (config as BarRendererConfig).cornerStrategy; - } - - /// Generates an [AnimatedBar] to represent the previous and current state - /// of one bar on the chart. - @override - AnimatedBar makeAnimatedBar( - {String key, - ImmutableSeries series, - List dashPattern, - dynamic datum, - Color color, - BarRendererElement details, - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - double measureAxisPosition, - Color fillColor, - FillPatternType fillPattern, - double strokeWidthPx, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - int numBarGroups, - bool measureIsNull, - bool measureIsNegative}) { - return AnimatedBar( - key: key, datum: datum, series: series, domainValue: domainValue) - ..setNewTarget(makeBarRendererElement( - color: color, - dashPattern: dashPattern, - details: details, - domainValue: domainValue, - domainAxis: domainAxis, - domainWidth: domainWidth, - measureValue: measureValue, - measureOffsetValue: measureOffsetValue, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - fillColor: fillColor, - fillPattern: fillPattern, - strokeWidthPx: strokeWidthPx, - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - numBarGroups: numBarGroups, - measureIsNull: measureIsNull, - measureIsNegative: measureIsNegative)); - } - - /// Generates a [BarRendererElement] to represent the rendering data for one - /// bar on the chart. - @override - BarRendererElement makeBarRendererElement( - {Color color, - List dashPattern, - BarRendererElement details, - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - double measureAxisPosition, - Color fillColor, - FillPatternType fillPattern, - double strokeWidthPx, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - int numBarGroups, - bool measureIsNull, - bool measureIsNegative}) { - return BarRendererElement() - ..color = color - ..dashPattern = dashPattern - ..fillColor = fillColor - ..fillPattern = fillPattern - ..measureAxisPosition = measureAxisPosition - ..roundPx = details.roundPx - ..strokeWidthPx = strokeWidthPx - ..measureIsNull = measureIsNull - ..measureIsNegative = measureIsNegative - ..bounds = _getBarBounds( - domainValue, - domainAxis, - domainWidth, - measureValue, - measureOffsetValue, - measureAxis, - barGroupIndex, - previousBarGroupWeight, - barGroupWeight, - numBarGroups); - } - - @override - void paintBar(ChartCanvas canvas, double animationPercent, - Iterable> barElements) { - final bars = []; - - // When adjusting bars for stacked bar padding, do not modify the first bar - // if rendering vertically and do not modify the last bar if rendering - // horizontally. - final unmodifiedBar = - renderingVertically ? barElements.first : barElements.last; - - // Find the max bar width from each segment to calculate corner radius. - int maxBarWidth = 0; - - var measureIsNegative = false; - - for (var bar in barElements) { - var bounds = bar.bounds; - - measureIsNegative = measureIsNegative || bar.measureIsNegative; - - if (bar != unmodifiedBar) { - bounds = renderingVertically - ? Rectangle( - bar.bounds.left, - max( - 0, - bar.bounds.top + - (measureIsNegative ? _stackedBarPadding : 0)), - bar.bounds.width, - max(0, bar.bounds.height - _stackedBarPadding), - ) - : Rectangle( - max( - 0, - bar.bounds.left + - (measureIsNegative ? _stackedBarPadding : 0)), - bar.bounds.top, - max(0, bar.bounds.width - _stackedBarPadding), - bar.bounds.height, - ); - } - - bars.add(CanvasRect(bounds, - dashPattern: bar.dashPattern, - fill: bar.fillColor, - pattern: bar.fillPattern, - stroke: bar.color, - strokeWidthPx: bar.strokeWidthPx)); - - maxBarWidth = max( - maxBarWidth, (renderingVertically ? bounds.width : bounds.height)); - } - - bool roundTopLeft; - bool roundTopRight; - bool roundBottomLeft; - bool roundBottomRight; - - if (measureIsNegative) { - // Negative bars should be rounded towards the negative axis direction. - // In vertical mode, this is the bottom. In horizontal mode, this is the - // left side of the chart for LTR, or the right side for RTL. - roundTopLeft = !renderingVertically && !isRtl ? true : false; - roundTopRight = !renderingVertically && isRtl ? true : false; - roundBottomLeft = renderingVertically || !isRtl ? true : false; - roundBottomRight = renderingVertically || isRtl ? true : false; - } else { - // Positive bars should be rounded towards the positive axis direction. - // In vertical mode, this is the top. In horizontal mode, this is the - // right side of the chart for LTR, or the left side for RTL. - roundTopLeft = renderingVertically || isRtl ? true : false; - roundTopRight = isRtl ? false : true; - roundBottomLeft = isRtl ? true : false; - roundBottomRight = renderingVertically || isRtl ? false : true; - } - - final barStack = CanvasBarStack( - bars, - radius: cornerStrategy.getRadius(maxBarWidth), - stackedBarPadding: _stackedBarPadding, - roundTopLeft: roundTopLeft, - roundTopRight: roundTopRight, - roundBottomLeft: roundBottomLeft, - roundBottomRight: roundBottomRight, - ); - - // If bar stack's range width is: - // * Within the component bounds, then draw the bar stack. - // * Partially out of component bounds, then clip the stack where it is out - // of bounds. - // * Fully out of component bounds, do not draw. - - final barOutsideBounds = renderingVertically - ? barStack.fullStackRect.left < componentBounds.left || - barStack.fullStackRect.right > componentBounds.right - : barStack.fullStackRect.top < componentBounds.top || - barStack.fullStackRect.bottom > componentBounds.bottom; - - // TODO: When we have initial viewport, add image test for - // clipping. - if (barOutsideBounds) { - final clipBounds = _getBarStackBounds(barStack.fullStackRect); - - // Do not draw the bar stack if it is completely outside of the component - // bounds. - if (clipBounds.width <= 0 || clipBounds.height <= 0) { - return; - } - - canvas.setClipBounds(clipBounds); - } - - canvas.drawBarStack(barStack, drawAreaBounds: componentBounds); - - if (barOutsideBounds) { - canvas.resetClipBounds(); - } - - // Decorate the bar segments if there is a decorator. - barRendererDecorator?.decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: animationPercent, - renderingVertically: renderingVertically, - rtl: isRtl); - } - - /// Calculate the clipping region for a rectangle that represents the full bar - /// stack. - Rectangle _getBarStackBounds(Rectangle barStackRect) { - int left; - int right; - int top; - int bottom; - - if (renderingVertically) { - // Only clip at the start and end so that the bar's width stays within - // the viewport, but any bar decorations above the bar can still show. - left = max(componentBounds.left, barStackRect.left); - right = min(componentBounds.right, barStackRect.right); - top = barStackRect.top; - bottom = barStackRect.bottom; - } else { - // Only clip at the top and bottom so that the bar's height stays within - // the viewport, but any bar decorations to the right of the bar can still - // show. - left = barStackRect.left; - right = barStackRect.right; - top = max(componentBounds.top, barStackRect.top); - bottom = min(componentBounds.bottom, barStackRect.bottom); - } - - final width = right - left; - final height = bottom - top; - - return Rectangle(left, top, width, height); - } - - /// Generates a set of bounds that describe a bar. - Rectangle _getBarBounds( - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - int numBarGroups) { - // If no weights were passed in, default to equal weight per bar. - if (barGroupWeight == null) { - barGroupWeight = 1 / numBarGroups; - previousBarGroupWeight = barGroupIndex * barGroupWeight; - } - - // Calculate how wide each bar should be within the group of bars. If we - // only have one series, or are stacked, then barWidth should equal - // domainWidth. - int spacingLoss = (_barGroupInnerPadding * (numBarGroups - 1)); - int barWidth = ((domainWidth - spacingLoss) * barGroupWeight).round(); - - // Make sure that bars are at least one pixel wide, so that they will always - // be visible on the chart. Ideally we should do something clever with the - // size of the chart, and the density and periodicity of the data, but this - // at least ensures that dense charts still have visible data. - barWidth = max(1, barWidth); - - // Flip bar group index for calculating location on the domain axis if RTL. - final adjustedBarGroupIndex = - isRtl ? numBarGroups - barGroupIndex - 1 : barGroupIndex; - - // Calculate the start and end of the bar, taking into account accumulated - // padding for grouped bars. - int previousAverageWidth = adjustedBarGroupIndex > 0 - ? ((domainWidth - spacingLoss) * - (previousBarGroupWeight / adjustedBarGroupIndex)) - .round() - : 0; - - int domainStart = (domainAxis.getLocation(domainValue) - - (domainWidth / 2) + - (previousAverageWidth + _barGroupInnerPadding) * - adjustedBarGroupIndex) - .round(); - - int domainEnd = domainStart + barWidth; - - measureValue = measureValue != null ? measureValue : 0; - - // Calculate measure locations. Stacked bars should have their - // offset calculated previously. - int measureStart; - int measureEnd; - if (measureValue < 0) { - measureEnd = measureAxis.getLocation(measureOffsetValue).round(); - measureStart = - measureAxis.getLocation(measureValue + measureOffsetValue).round(); - } else { - measureStart = measureAxis.getLocation(measureOffsetValue).round(); - measureEnd = - measureAxis.getLocation(measureValue + measureOffsetValue).round(); - } - - Rectangle bounds; - if (this.renderingVertically) { - // Rectangle clamps to zero width/height - bounds = Rectangle(domainStart, measureEnd, domainEnd - domainStart, - measureStart - measureEnd); - } else { - // Rectangle clamps to zero width/height - bounds = Rectangle(min(measureStart, measureEnd), domainStart, - (measureEnd - measureStart).abs(), domainEnd - domainStart); - } - return bounds; - } - - @override - Rectangle getBoundsForBar(BarRendererElement bar) => bar.bounds; -} - -abstract class ImmutableBarRendererElement { - ImmutableSeries get series; - - dynamic get datum; - - int get index; - - Rectangle get bounds; -} - -class BarRendererElement extends BaseBarRendererElement - implements ImmutableBarRendererElement { - ImmutableSeries series; - Rectangle bounds; - int roundPx; - int index; - dynamic _datum; - - dynamic get datum => _datum; - - set datum(dynamic datum) { - _datum = datum; - index = series?.data?.indexOf(datum); - } - - BarRendererElement(); - - BarRendererElement.clone(BarRendererElement other) : super.clone(other) { - series = other.series; - bounds = other.bounds; - roundPx = other.roundPx; - index = other.index; - _datum = other._datum; - } - - @override - void updateAnimationPercent(BaseBarRendererElement previous, - BaseBarRendererElement target, double animationPercent) { - final BarRendererElement localPrevious = previous; - final BarRendererElement localTarget = target; - - final previousBounds = localPrevious.bounds; - final targetBounds = localTarget.bounds; - - var top = ((targetBounds.top - previousBounds.top) * animationPercent) + - previousBounds.top; - var right = - ((targetBounds.right - previousBounds.right) * animationPercent) + - previousBounds.right; - var bottom = - ((targetBounds.bottom - previousBounds.bottom) * animationPercent) + - previousBounds.bottom; - var left = ((targetBounds.left - previousBounds.left) * animationPercent) + - previousBounds.left; - - bounds = Rectangle(left.round(), top.round(), (right - left).round(), - (bottom - top).round()); - - roundPx = localTarget.roundPx; - - super.updateAnimationPercent(previous, target, animationPercent); - } -} - -class AnimatedBar extends BaseAnimatedBar> { - AnimatedBar( - {@required String key, - @required dynamic datum, - @required ImmutableSeries series, - @required D domainValue}) - : super(key: key, datum: datum, series: series, domainValue: domainValue); - - @override - animateElementToMeasureAxisPosition(BaseBarRendererElement target) { - final BarRendererElement localTarget = target; - - // TODO: Animate out bars in the middle of a stack. - localTarget.bounds = Rectangle( - localTarget.bounds.left + (localTarget.bounds.width / 2).round(), - localTarget.measureAxisPosition.round(), - 0, - 0); - } - - BarRendererElement getCurrentBar(double animationPercent) { - final BarRendererElement bar = super.getCurrentBar(animationPercent); - - // Update with series and datum information to pass to bar decorator. - bar.series = series; - bar.datum = datum; - - return bar; - } - - @override - BarRendererElement clone(BarRendererElement bar) => - BarRendererElement.clone(bar); -} diff --git a/web/charts/common/lib/src/chart/bar/bar_renderer_config.dart b/web/charts/common/lib/src/chart/bar/bar_renderer_config.dart deleted file mode 100644 index 123a83ae7..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_renderer_config.dart +++ /dev/null @@ -1,99 +0,0 @@ -// 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 '../../common/symbol_renderer.dart'; -import '../common/chart_canvas.dart' show FillPatternType; -import '../layout/layout_view.dart' show LayoutViewPaintOrder; -import 'bar_renderer.dart' show BarRenderer; -import 'bar_renderer_decorator.dart' show BarRendererDecorator; -import 'base_bar_renderer_config.dart' - show BarGroupingType, BaseBarRendererConfig; - -/// Configuration for a bar renderer. -class BarRendererConfig extends BaseBarRendererConfig { - /// Strategy for determining the corner radius of a bar. - final CornerStrategy cornerStrategy; - - /// Decorator for optionally decorating painted bars. - final BarRendererDecorator barRendererDecorator; - - BarRendererConfig({ - String customRendererId, - CornerStrategy cornerStrategy, - FillPatternType fillPattern, - BarGroupingType groupingType, - int layoutPaintOrder = LayoutViewPaintOrder.bar, - int minBarLengthPx = 0, - double stackHorizontalSeparator, - double strokeWidthPx = 0.0, - this.barRendererDecorator, - SymbolRenderer symbolRenderer, - List weightPattern, - }) : cornerStrategy = cornerStrategy ?? const ConstCornerStrategy(2), - super( - customRendererId: customRendererId, - groupingType: groupingType ?? BarGroupingType.grouped, - layoutPaintOrder: layoutPaintOrder, - minBarLengthPx: minBarLengthPx, - fillPattern: fillPattern, - stackHorizontalSeparator: stackHorizontalSeparator, - strokeWidthPx: strokeWidthPx, - symbolRenderer: symbolRenderer, - weightPattern: weightPattern, - ); - - @override - BarRenderer build() { - return BarRenderer(config: this, rendererId: customRendererId); - } - - @override - bool operator ==(other) { - if (identical(this, other)) { - return true; - } - if (!(other is BarRendererConfig)) { - return false; - } - return other.cornerStrategy == cornerStrategy && super == (other); - } - - @override - int get hashCode { - var hash = super.hashCode; - hash = hash * 31 + (cornerStrategy?.hashCode ?? 0); - return hash; - } -} - -abstract class CornerStrategy { - /// Returns the radius of the rounded corners in pixels. - int getRadius(int barWidth); -} - -/// Strategy for constant corner radius. -class ConstCornerStrategy implements CornerStrategy { - final int radius; - - const ConstCornerStrategy(this.radius); - - @override - int getRadius(_) => radius; -} - -/// Strategy for no corner radius. -class NoCornerStrategy extends ConstCornerStrategy { - const NoCornerStrategy() : super(0); -} diff --git a/web/charts/common/lib/src/chart/bar/bar_renderer_decorator.dart b/web/charts/common/lib/src/chart/bar/bar_renderer_decorator.dart deleted file mode 100644 index ecf551528..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_renderer_decorator.dart +++ /dev/null @@ -1,34 +0,0 @@ -// 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' show required; - -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../common/chart_canvas.dart' show ChartCanvas; -import 'bar_renderer.dart' show ImmutableBarRendererElement; - -/// Decorates bars after the bars have already been painted. -abstract class BarRendererDecorator { - const BarRendererDecorator(); - - void decorate(Iterable> barElements, - ChartCanvas canvas, GraphicsFactory graphicsFactory, - {@required Rectangle drawBounds, - @required double animationPercent, - @required bool renderingVertically, - bool rtl = false}); -} diff --git a/web/charts/common/lib/src/chart/bar/bar_target_line_renderer.dart b/web/charts/common/lib/src/chart/bar/bar_target_line_renderer.dart deleted file mode 100644 index 77103e87a..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_target_line_renderer.dart +++ /dev/null @@ -1,422 +0,0 @@ -// 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, min; - -import 'package:meta/meta.dart' show required; - -import '../../common/color.dart' show Color; -import '../cartesian/axis/axis.dart' - show ImmutableAxis, domainAxisKey, measureAxisKey; -import '../common/chart_canvas.dart' show ChartCanvas, FillPatternType; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import '../common/series_datum.dart' show SeriesDatum; -import 'bar_target_line_renderer_config.dart' show BarTargetLineRendererConfig; -import 'base_bar_renderer.dart' - show - BaseBarRenderer, - barGroupCountKey, - barGroupIndexKey, - previousBarGroupWeightKey, - barGroupWeightKey; -import 'base_bar_renderer_element.dart' - show BaseAnimatedBar, BaseBarRendererElement; - -/// Renders series data as a series of bar target lines. -/// -/// Usually paired with a BarRenderer to display target metrics alongside actual -/// metrics. -class BarTargetLineRenderer extends BaseBarRenderer> { - /// If we are grouped, use this spacing between the bars in a group. - final _barGroupInnerPadding = 2; - - /// Standard color for all bar target lines. - final _color = Color(r: 0, g: 0, b: 0, a: 153); - - factory BarTargetLineRenderer( - {BarTargetLineRendererConfig config, - String rendererId = 'barTargetLine'}) { - config ??= BarTargetLineRendererConfig(); - return BarTargetLineRenderer._internal( - config: config, rendererId: rendererId); - } - - BarTargetLineRenderer._internal( - {BarTargetLineRendererConfig config, String rendererId}) - : super( - config: config, - rendererId: rendererId, - layoutPaintOrder: config.layoutPaintOrder); - - @override - void configureSeries(List> seriesList) { - seriesList.forEach((series) { - series.colorFn ??= (_) => _color; - series.fillColorFn ??= (_) => _color; - }); - } - - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - final series = details.series; - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final barGroupIndex = series.getAttr(barGroupIndexKey); - final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); - final barGroupWeight = series.getAttr(barGroupWeightKey); - final numBarGroups = series.getAttr(barGroupCountKey); - - final points = _getTargetLinePoints( - details.domain, - domainAxis, - domainAxis.rangeBand.round(), - details.measure, - details.measureOffset, - measureAxis, - barGroupIndex, - previousBarGroupWeight, - barGroupWeight, - numBarGroups); - - Point chartPosition; - - if (renderingVertically) { - chartPosition = Point( - (points[0].x + (points[1].x - points[0].x) / 2).toDouble(), - points[0].y.toDouble()); - } else { - chartPosition = Point(points[0].x.toDouble(), - (points[0].y + (points[1].y - points[0].y) / 2).toDouble()); - } - - return DatumDetails.from(details, chartPosition: chartPosition); - } - - @override - _BarTargetLineRendererElement getBaseDetails(dynamic datum, int index) { - final BarTargetLineRendererConfig localConfig = config; - return _BarTargetLineRendererElement() - ..roundEndCaps = localConfig.roundEndCaps; - } - - /// Generates an [_AnimatedBarTargetLine] to represent the previous and - /// current state of one bar target line on the chart. - @override - _AnimatedBarTargetLine makeAnimatedBar( - {String key, - ImmutableSeries series, - dynamic datum, - Color color, - List dashPattern, - _BarTargetLineRendererElement details, - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - double measureAxisPosition, - Color fillColor, - FillPatternType fillPattern, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - int numBarGroups, - double strokeWidthPx, - bool measureIsNull, - bool measureIsNegative}) { - return _AnimatedBarTargetLine( - key: key, datum: datum, series: series, domainValue: domainValue) - ..setNewTarget(makeBarRendererElement( - color: color, - details: details, - dashPattern: dashPattern, - domainValue: domainValue, - domainAxis: domainAxis, - domainWidth: domainWidth, - measureValue: measureValue, - measureOffsetValue: measureOffsetValue, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - fillColor: fillColor, - fillPattern: fillPattern, - strokeWidthPx: strokeWidthPx, - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - numBarGroups: numBarGroups, - measureIsNull: measureIsNull, - measureIsNegative: measureIsNegative)); - } - - /// Generates a [_BarTargetLineRendererElement] to represent the rendering - /// data for one bar target line on the chart. - @override - _BarTargetLineRendererElement makeBarRendererElement( - {Color color, - List dashPattern, - _BarTargetLineRendererElement details, - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - double measureAxisPosition, - Color fillColor, - FillPatternType fillPattern, - double strokeWidthPx, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - int numBarGroups, - bool measureIsNull, - bool measureIsNegative}) { - return _BarTargetLineRendererElement() - ..color = color - ..dashPattern = dashPattern - ..fillColor = fillColor - ..fillPattern = fillPattern - ..measureAxisPosition = measureAxisPosition - ..roundEndCaps = details.roundEndCaps - ..strokeWidthPx = strokeWidthPx - ..measureIsNull = measureIsNull - ..measureIsNegative = measureIsNegative - ..points = _getTargetLinePoints( - domainValue, - domainAxis, - domainWidth, - measureValue, - measureOffsetValue, - measureAxis, - barGroupIndex, - previousBarGroupWeight, - barGroupWeight, - numBarGroups); - } - - @override - void paintBar( - ChartCanvas canvas, - double animationPercent, - Iterable<_BarTargetLineRendererElement> barElements, - ) { - barElements.forEach((bar) { - // TODO: Combine common line attributes into - // GraphicsFactory.lineStyle or similar. - canvas.drawLine( - clipBounds: drawBounds, - points: bar.points, - stroke: bar.color, - roundEndCaps: bar.roundEndCaps, - strokeWidthPx: bar.strokeWidthPx); - }); - } - - /// Generates a set of points that describe a bar target line. - List> _getTargetLinePoints( - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - int numBarGroups) { - // If no weights were passed in, default to equal weight per bar. - if (barGroupWeight == null) { - barGroupWeight = 1 / numBarGroups; - previousBarGroupWeight = barGroupIndex * barGroupWeight; - } - - final BarTargetLineRendererConfig localConfig = config; - - // Calculate how wide each bar target line should be within the group of - // bar target lines. If we only have one series, or are stacked, then - // barWidth should equal domainWidth. - int spacingLoss = (_barGroupInnerPadding * (numBarGroups - 1)); - int barWidth = ((domainWidth - spacingLoss) * barGroupWeight).round(); - - // Get the overdraw boundaries. - var overDrawOuterPx = localConfig.overDrawOuterPx; - var overDrawPx = localConfig.overDrawPx; - - int overDrawStartPx = (barGroupIndex == 0) && overDrawOuterPx != null - ? overDrawOuterPx - : overDrawPx; - - int overDrawEndPx = - (barGroupIndex == numBarGroups - 1) && overDrawOuterPx != null - ? overDrawOuterPx - : overDrawPx; - - // Flip bar group index for calculating location on the domain axis if RTL. - final adjustedBarGroupIndex = - isRtl ? numBarGroups - barGroupIndex - 1 : barGroupIndex; - - // Calculate the start and end of the bar target line, taking into account - // accumulated padding for grouped bars. - num previousAverageWidth = adjustedBarGroupIndex > 0 - ? ((domainWidth - spacingLoss) * - (previousBarGroupWeight / adjustedBarGroupIndex)) - .round() - : 0; - - int domainStart = (domainAxis.getLocation(domainValue) - - (domainWidth / 2) + - (previousAverageWidth + _barGroupInnerPadding) * - adjustedBarGroupIndex - - overDrawStartPx) - .round(); - - int domainEnd = domainStart + barWidth + overDrawStartPx + overDrawEndPx; - - measureValue = measureValue != null ? measureValue : 0; - - // Calculate measure locations. Stacked bars should have their - // offset calculated previously. - int measureStart = - measureAxis.getLocation(measureValue + measureOffsetValue).round(); - - List> points; - if (renderingVertically) { - points = [ - Point(domainStart, measureStart), - Point(domainEnd, measureStart) - ]; - } else { - points = [ - Point(measureStart, domainStart), - Point(measureStart, domainEnd) - ]; - } - return points; - } - - @override - Rectangle getBoundsForBar(_BarTargetLineRendererElement bar) { - final points = bar.points; - int top; - int bottom; - int left; - int right; - points.forEach((p) { - top = top != null ? min(top, p.y) : p.y; - left = left != null ? min(left, p.x) : p.x; - bottom = bottom != null ? max(bottom, p.y) : p.y; - right = right != null ? max(right, p.x) : p.x; - }); - return Rectangle(left, top, right - left, bottom - top); - } -} - -class _BarTargetLineRendererElement extends BaseBarRendererElement { - List> points; - bool roundEndCaps; - - _BarTargetLineRendererElement(); - - _BarTargetLineRendererElement.clone(_BarTargetLineRendererElement other) - : super.clone(other) { - points = List>.from(other.points); - roundEndCaps = other.roundEndCaps; - } - - @override - void updateAnimationPercent(BaseBarRendererElement previous, - BaseBarRendererElement target, double animationPercent) { - final _BarTargetLineRendererElement localPrevious = previous; - final _BarTargetLineRendererElement localTarget = target; - - final previousPoints = localPrevious.points; - final targetPoints = localTarget.points; - - Point lastPoint; - - int pointIndex; - for (pointIndex = 0; pointIndex < targetPoints.length; pointIndex++) { - var targetPoint = targetPoints[pointIndex]; - - // If we have more points than the previous line, animate in the new point - // by starting its measure position at the last known official point. - Point previousPoint; - if (previousPoints.length - 1 >= pointIndex) { - previousPoint = previousPoints[pointIndex]; - lastPoint = previousPoint; - } else { - previousPoint = Point(targetPoint.x, lastPoint.y); - } - - var x = ((targetPoint.x - previousPoint.x) * animationPercent) + - previousPoint.x; - - var y = ((targetPoint.y - previousPoint.y) * animationPercent) + - previousPoint.y; - - if (points.length - 1 >= pointIndex) { - points[pointIndex] = Point(x.round(), y.round()); - } else { - points.add(Point(x.round(), y.round())); - } - } - - // Removing extra points that don't exist anymore. - if (pointIndex < points.length) { - points.removeRange(pointIndex, points.length); - } - - strokeWidthPx = ((localTarget.strokeWidthPx - localPrevious.strokeWidthPx) * - animationPercent) + - localPrevious.strokeWidthPx; - - roundEndCaps = localTarget.roundEndCaps; - - super.updateAnimationPercent(previous, target, animationPercent); - } -} - -class _AnimatedBarTargetLine - extends BaseAnimatedBar { - _AnimatedBarTargetLine( - {@required String key, - @required dynamic datum, - @required ImmutableSeries series, - @required D domainValue}) - : super(key: key, datum: datum, series: series, domainValue: domainValue); - - @override - animateElementToMeasureAxisPosition(BaseBarRendererElement target) { - final _BarTargetLineRendererElement localTarget = target; - - final newPoints = >[]; - for (var index = 0; index < localTarget.points.length; index++) { - final targetPoint = localTarget.points[index]; - - newPoints.add( - Point(targetPoint.x, localTarget.measureAxisPosition.round())); - } - localTarget.points = newPoints; - } - - @override - _BarTargetLineRendererElement clone(_BarTargetLineRendererElement bar) => - _BarTargetLineRendererElement.clone(bar); -} diff --git a/web/charts/common/lib/src/chart/bar/bar_target_line_renderer_config.dart b/web/charts/common/lib/src/chart/bar/bar_target_line_renderer_config.dart deleted file mode 100644 index 293d07cb3..000000000 --- a/web/charts/common/lib/src/chart/bar/bar_target_line_renderer_config.dart +++ /dev/null @@ -1,91 +0,0 @@ -// 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 '../../common/symbol_renderer.dart' - show SymbolRenderer, LineSymbolRenderer; -import '../layout/layout_view.dart' show LayoutViewPaintOrder; -import 'bar_target_line_renderer.dart' show BarTargetLineRenderer; -import 'base_bar_renderer_config.dart' - show BarGroupingType, BaseBarRendererConfig; - -/// Configuration for a bar target line renderer. -class BarTargetLineRendererConfig extends BaseBarRendererConfig { - /// The number of pixels that the line will extend beyond the bandwidth at the - /// edges of the bar group. - /// - /// If set, this overrides overDrawPx for the beginning side of the first bar - /// target line in the group, and the ending side of the last bar target line. - /// overDrawPx will be used for overdrawing the target lines for interior - /// sides of the bars. - final int overDrawOuterPx; - - /// The number of pixels that the line will extend beyond the bandwidth for - /// every bar in a group. - final int overDrawPx; - - /// Whether target lines should have round end caps, or square if false. - final bool roundEndCaps; - - BarTargetLineRendererConfig( - {String customRendererId, - List dashPattern, - groupingType = BarGroupingType.grouped, - int layoutPaintOrder = LayoutViewPaintOrder.barTargetLine, - int minBarLengthPx = 0, - this.overDrawOuterPx, - this.overDrawPx = 0, - this.roundEndCaps = true, - double strokeWidthPx = 3.0, - SymbolRenderer symbolRenderer, - List weightPattern}) - : super( - customRendererId: customRendererId, - dashPattern: dashPattern, - groupingType: groupingType, - layoutPaintOrder: layoutPaintOrder, - minBarLengthPx: minBarLengthPx, - strokeWidthPx: strokeWidthPx, - symbolRenderer: symbolRenderer ?? LineSymbolRenderer(), - weightPattern: weightPattern, - ); - - @override - BarTargetLineRenderer build() { - return BarTargetLineRenderer(config: this, rendererId: customRendererId); - } - - @override - bool operator ==(other) { - if (identical(this, other)) { - return true; - } - if (!(other is BarTargetLineRendererConfig)) { - return false; - } - return other.overDrawOuterPx == overDrawOuterPx && - other.overDrawPx == overDrawPx && - other.roundEndCaps == roundEndCaps && - super == (other); - } - - @override - int get hashCode { - var hash = 1; - hash = hash * 31 + (overDrawOuterPx?.hashCode ?? 0); - hash = hash * 31 + (overDrawPx?.hashCode ?? 0); - hash = hash * 31 + (roundEndCaps?.hashCode ?? 0); - return hash; - } -} diff --git a/web/charts/common/lib/src/chart/bar/base_bar_renderer.dart b/web/charts/common/lib/src/chart/bar/base_bar_renderer.dart deleted file mode 100644 index ff44af57a..000000000 --- a/web/charts/common/lib/src/chart/bar/base_bar_renderer.dart +++ /dev/null @@ -1,797 +0,0 @@ -// 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:collection' show LinkedHashMap, HashSet; -import 'dart:math' show Point, Rectangle, max; - -import 'package:meta/meta.dart' show protected, required; - -import '../../common/color.dart' show Color; -import '../../common/math.dart' show clamp; -import '../../common/symbol_renderer.dart' show RoundedRectSymbolRenderer; -import '../../data/series.dart' show AttributeKey; -import '../cartesian/axis/axis.dart' - show ImmutableAxis, OrdinalAxis, domainAxisKey, measureAxisKey; -import '../cartesian/axis/scale.dart' show RangeBandConfig; -import '../cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; -import '../common/base_chart.dart' show BaseChart; -import '../common/chart_canvas.dart' show ChartCanvas, FillPatternType; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import 'base_bar_renderer_config.dart' show BaseBarRendererConfig; -import 'base_bar_renderer_element.dart' - show BaseAnimatedBar, BaseBarRendererElement; - -const barGroupIndexKey = AttributeKey('BarRenderer.barGroupIndex'); - -const barGroupCountKey = AttributeKey('BarRenderer.barGroupCount'); - -const barGroupWeightKey = AttributeKey('BarRenderer.barGroupWeight'); - -const previousBarGroupWeightKey = - AttributeKey('BarRenderer.previousBarGroupWeight'); - -const stackKeyKey = AttributeKey('BarRenderer.stackKey'); - -const barElementsKey = - AttributeKey>('BarRenderer.elements'); - -/// Base class for bar renderers that implements common stacking and grouping -/// logic. -/// -/// Bar renderers support 4 different modes of rendering multiple series on the -/// chart, configured by the grouped and stacked flags. -/// * grouped - Render bars for each series that shares a domain value -/// side-by-side. -/// * stacked - Render bars for each series that shares a domain value in a -/// stack, ordered in the same order as the series list. -/// * grouped-stacked: Render bars for each series that shares a domain value in -/// a group of bar stacks. Each stack will contain all the series that share a -/// series category. -/// * floating style - When grouped and stacked are both false, all bars that -/// share a domain value will be rendered in the same domain space. Each datum -/// should be configured with a measure offset to position its bar along the -/// measure axis. Bars will freely overlap if their measure values and measure -/// offsets overlap. Note that bars for each series will be rendered in order, -/// such that bars from the last series will be "on top" of bars from previous -/// series. -abstract class BaseBarRenderer> extends BaseCartesianRenderer { - final BaseBarRendererConfig config; - - @protected - BaseChart chart; - - /// Store a map of domain+barGroupIndex+category index to bars in a stack. - /// - /// This map is used to render all the bars in a stack together, to account - /// for rendering effects that need to take the full stack into account (e.g. - /// corner rounding). - /// - /// [LinkedHashMap] is used to render the bars on the canvas in the same order - /// as the data was given to the chart. For the case where both grouping and - /// stacking are disabled, this means that bars for data later in the series - /// will be drawn "on top of" bars earlier in the series. - final _barStackMap = LinkedHashMap>(); - - // Store a list of bar stacks that exist in the series data. - // - // This list will be used to remove any AnimatingBars that were rendered in - // previous draw cycles, but no longer have a corresponding datum in the new - // data. - final _currentKeys = []; - - /// Stores a list of stack keys for each group key. - final _currentGroupsStackKeys = LinkedHashMap>(); - - /// Optimization for getNearest to avoid scanning all data if possible. - ImmutableAxis _prevDomainAxis; - - BaseBarRenderer( - {@required this.config, String rendererId, int layoutPaintOrder}) - : super( - rendererId: rendererId, - layoutPaintOrder: layoutPaintOrder, - symbolRenderer: config?.symbolRenderer ?? RoundedRectSymbolRenderer(), - ); - - @override - void preprocessSeries(List> seriesList) { - var barGroupIndex = 0; - - // Maps used to store the final measure offset of the previous series, for - // each domain value. - final posDomainToStackKeyToDetailsMap = {}; - final negDomainToStackKeyToDetailsMap = {}; - final categoryToIndexMap = {}; - - // Keep track of the largest bar stack size. This should be 1 for grouped - // bars, and it should be the size of the tallest stack for stacked or - // grouped stacked bars. - var maxBarStackSize = 0; - - final orderedSeriesList = getOrderedSeriesList(seriesList); - - orderedSeriesList.forEach((series) { - var elements = []; - - var domainFn = series.domainFn; - var measureFn = series.measureFn; - var measureOffsetFn = series.measureOffsetFn; - var fillPatternFn = series.fillPatternFn; - var strokeWidthPxFn = series.strokeWidthPxFn; - - series.dashPatternFn ??= (_) => config.dashPattern; - - // Identifies which stack the series will go in, by default a single - // stack. - var stackKey = '__defaultKey__'; - - // Override the stackKey with seriesCategory if we are GROUPED_STACKED - // so we have a way to choose which series go into which stacks. - if (config.grouped && config.stacked) { - if (series.seriesCategory != null) { - stackKey = series.seriesCategory; - } - - barGroupIndex = categoryToIndexMap[stackKey]; - if (barGroupIndex == null) { - barGroupIndex = categoryToIndexMap.length; - categoryToIndexMap[stackKey] = barGroupIndex; - } - } - - var needsMeasureOffset = false; - - for (var barIndex = 0; barIndex < series.data.length; barIndex++) { - dynamic datum = series.data[barIndex]; - final details = getBaseDetails(datum, barIndex); - - details.barStackIndex = 0; - details.measureOffset = 0; - - if (fillPatternFn != null) { - details.fillPattern = fillPatternFn(barIndex); - } else { - details.fillPattern = config.fillPattern; - } - - if (strokeWidthPxFn != null) { - details.strokeWidthPx = strokeWidthPxFn(barIndex).toDouble(); - } else { - details.strokeWidthPx = config.strokeWidthPx; - } - - // When stacking is enabled, adjust the measure offset for each domain - // value in each series by adding up the measures and offsets of lower - // series. - if (config.stacked) { - needsMeasureOffset = true; - var domain = domainFn(barIndex); - var measure = measureFn(barIndex); - - // We will render positive bars in one stack, and negative bars in a - // separate stack. Keep track of the measure offsets for these stacks - // independently. - var domainToCategoryToDetailsMap = measure == null || measure >= 0 - ? posDomainToStackKeyToDetailsMap - : negDomainToStackKeyToDetailsMap; - - var categoryToDetailsMap = - domainToCategoryToDetailsMap.putIfAbsent(domain, () => {}); - - var prevDetail = categoryToDetailsMap[stackKey]; - - if (prevDetail != null) { - details.barStackIndex = prevDetail.barStackIndex + 1; - } - - details.cumulativeTotal = measure != null ? measure : 0; - - // Get the previous series' measure offset. - var measureOffset = measureOffsetFn(barIndex); - if (prevDetail != null) { - measureOffset += prevDetail.measureOffsetPlusMeasure; - - details.cumulativeTotal += prevDetail.cumulativeTotal; - } - - // And overwrite the details measure offset. - details.measureOffset = measureOffset; - var measureValue = (measure != null ? measure : 0); - details.measureOffsetPlusMeasure = measureOffset + measureValue; - - categoryToDetailsMap[stackKey] = details; - } - - maxBarStackSize = max(maxBarStackSize, details.barStackIndex + 1); - - elements.add(details); - } - - if (needsMeasureOffset) { - // Override the measure offset function to return the measure offset we - // calculated for each datum. This already includes any measure offset - // that was configured in the series data. - series.measureOffsetFn = (index) => elements[index].measureOffset; - } - - series.setAttr(barGroupIndexKey, barGroupIndex); - series.setAttr(stackKeyKey, stackKey); - series.setAttr(barElementsKey, elements); - - if (config.grouped) { - barGroupIndex++; - } - }); - - // Compute number of bar groups. This must be done after we have processed - // all of the series once, so that we know how many categories we have. - var numBarGroups = 0; - if (config.grouped && config.stacked) { - // For grouped stacked bars, categoryToIndexMap effectively one list per - // group of stacked bars. - numBarGroups = categoryToIndexMap.length; - } else if (config.stacked) { - numBarGroups = 1; - } else { - numBarGroups = seriesList.length; - } - - // Compute bar group weights. - final barWeights = _calculateBarWeights(numBarGroups); - - seriesList.forEach((series) { - series.setAttr(barGroupCountKey, numBarGroups); - - if (barWeights.isNotEmpty) { - final barGroupIndex = series.getAttr(barGroupIndexKey); - final barWeight = barWeights[barGroupIndex]; - - // In RTL mode, we need to grab the weights for the bars that follow - // this datum in the series (instead of precede it). The first datum is - // physically positioned on the canvas to the right of all the rest of - // the bar group data that follows it. - final previousBarWeights = isRtl - ? barWeights.getRange(barGroupIndex + 1, numBarGroups) - : barWeights.getRange(0, barGroupIndex); - - final previousBarWeight = previousBarWeights.isNotEmpty - ? previousBarWeights.reduce((a, b) => a + b) - : 0.0; - - series.setAttr(barGroupWeightKey, barWeight); - series.setAttr(previousBarGroupWeightKey, previousBarWeight); - } - }); - } - - /// Calculates bar weights for a list of series from [config.weightPattern]. - /// - /// If [config.weightPattern] is not set, then this will assign a weight - /// proportional to the number of bar groups for every series. - List _calculateBarWeights(int numBarGroups) { - // Set up bar weights for each series as a ratio of the total weight. - final weights = []; - - if (config.weightPattern != null) { - if (numBarGroups > config.weightPattern.length) { - throw ArgumentError('Number of series exceeds length of weight ' - 'pattern ${config.weightPattern}'); - } - - var totalBarWeight = 0; - - for (var i = 0; i < numBarGroups; i++) { - totalBarWeight += config.weightPattern[i]; - } - - for (var i = 0; i < numBarGroups; i++) { - weights.add(config.weightPattern[i] / totalBarWeight); - } - } else { - for (var i = 0; i < numBarGroups; i++) { - weights.add(1 / numBarGroups); - } - } - - return weights; - } - - /// Construct a base details element for a given datum. - /// - /// This is intended to be overridden by child classes that need to add - /// customized rendering properties. - R getBaseDetails(dynamic datum, int index); - - @override - void configureDomainAxes(List> seriesList) { - super.configureDomainAxes(seriesList); - - // Configure the domain axis to use a range band configuration. - if (seriesList.isNotEmpty) { - // Given that charts can only have one domain axis, just grab it from the - // first series. - final domainAxis = seriesList.first.getAttr(domainAxisKey); - domainAxis.setRangeBandConfig(RangeBandConfig.styleAssignedPercent()); - } - } - - void update(List> seriesList, bool isAnimatingThisDraw) { - _currentKeys.clear(); - _currentGroupsStackKeys.clear(); - - final orderedSeriesList = getOrderedSeriesList(seriesList); - - orderedSeriesList.forEach((final series) { - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final domainFn = series.domainFn; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - final measureFn = series.measureFn; - final colorFn = series.colorFn; - final dashPatternFn = series.dashPatternFn; - final fillColorFn = series.fillColorFn; - final seriesStackKey = series.getAttr(stackKeyKey); - final barGroupCount = series.getAttr(barGroupCountKey); - final barGroupIndex = series.getAttr(barGroupIndexKey); - final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); - final barGroupWeight = series.getAttr(barGroupWeightKey); - final measureAxisPosition = measureAxis.getLocation(0.0); - - var elementsList = series.getAttr(barElementsKey); - - // Save off domainAxis for getNearest. - _prevDomainAxis = domainAxis; - - for (var barIndex = 0; barIndex < series.data.length; barIndex++) { - final datum = series.data[barIndex]; - BaseBarRendererElement details = elementsList[barIndex]; - D domainValue = domainFn(barIndex); - - final measureValue = measureFn(barIndex); - final measureIsNull = measureValue == null; - final measureIsNegative = !measureIsNull && measureValue < 0; - - // Each bar should be stored in barStackMap in a structure that mirrors - // the visual rendering of the bars. Thus, they should be grouped by - // domain value, series category (by way of the stack keys that were - // generated for each series in the preprocess step), and bar group - // index to account for all combinations of grouping and stacking. - var barStackMapKey = domainValue.toString() + - '__' + - seriesStackKey + - '__' + - (measureIsNegative ? 'pos' : 'neg') + - '__' + - barGroupIndex.toString(); - - var barKey = barStackMapKey + details.barStackIndex.toString(); - - var barStackList = _barStackMap.putIfAbsent(barStackMapKey, () => []); - - // If we already have an AnimatingBarfor that index, use it. - var animatingBar = barStackList.firstWhere((bar) => bar.key == barKey, - orElse: () => null); - - // If we don't have any existing bar element, create a new bar and have - // it animate in from the domain axis. - // TODO: Animate bars in the middle of a stack from their - // nearest neighbors, instead of the measure axis. - if (animatingBar == null) { - // If the measure is null and there was no existing animating bar, it - // means we don't need to draw this bar at all. - if (!measureIsNull) { - animatingBar = makeAnimatedBar( - key: barKey, - series: series, - datum: datum, - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - color: colorFn(barIndex), - dashPattern: dashPatternFn(barIndex), - details: details, - domainValue: domainFn(barIndex), - domainAxis: domainAxis, - domainWidth: domainAxis.rangeBand.round(), - fillColor: fillColorFn(barIndex), - fillPattern: details.fillPattern, - measureValue: 0.0, - measureOffsetValue: 0.0, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - numBarGroups: barGroupCount, - strokeWidthPx: details.strokeWidthPx, - measureIsNull: measureIsNull, - measureIsNegative: measureIsNegative); - - barStackList.add(animatingBar); - } - } else { - animatingBar - ..datum = datum - ..series = series - ..domainValue = domainValue; - } - - if (animatingBar == null) { - continue; - } - - // Update the set of bars that still exist in the series data. - _currentKeys.add(barKey); - - // Store off stack keys for each bar group to help getNearest identify - // groups of stacks. - _currentGroupsStackKeys - .putIfAbsent(domainValue, () => {}) - .add(barStackMapKey); - - // Get the barElement we are going to setup. - // Optimization to prevent allocation in non-animating case. - BaseBarRendererElement barElement = makeBarRendererElement( - barGroupIndex: barGroupIndex, - previousBarGroupWeight: previousBarGroupWeight, - barGroupWeight: barGroupWeight, - color: colorFn(barIndex), - dashPattern: dashPatternFn(barIndex), - details: details, - domainValue: domainFn(barIndex), - domainAxis: domainAxis, - domainWidth: domainAxis.rangeBand.round(), - fillColor: fillColorFn(barIndex), - fillPattern: details.fillPattern, - measureValue: measureValue, - measureOffsetValue: details.measureOffset, - measureAxisPosition: measureAxisPosition, - measureAxis: measureAxis, - numBarGroups: barGroupCount, - strokeWidthPx: details.strokeWidthPx, - measureIsNull: measureIsNull, - measureIsNegative: measureIsNegative); - - animatingBar.setNewTarget(barElement); - } - }); - - // Animate out bars that don't exist anymore. - _barStackMap.forEach((key, barStackList) { - for (var barIndex = 0; barIndex < barStackList.length; barIndex++) { - final bar = barStackList[barIndex]; - if (_currentKeys.contains(bar.key) != true) { - bar.animateOut(); - } - } - }); - } - - /// Generates a [BaseAnimatedBar] to represent the previous and current state - /// of one bar on the chart. - B makeAnimatedBar( - {String key, - ImmutableSeries series, - dynamic datum, - int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - Color color, - List dashPattern, - R details, - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - double measureAxisPosition, - int numBarGroups, - Color fillColor, - FillPatternType fillPattern, - double strokeWidthPx, - bool measureIsNull, - bool measureIsNegative}); - - /// Generates a [BaseBarRendererElement] to represent the rendering data for - /// one bar on the chart. - R makeBarRendererElement( - {int barGroupIndex, - double previousBarGroupWeight, - double barGroupWeight, - Color color, - List dashPattern, - R details, - D domainValue, - ImmutableAxis domainAxis, - int domainWidth, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - double measureAxisPosition, - int numBarGroups, - Color fillColor, - FillPatternType fillPattern, - double strokeWidthPx, - bool measureIsNull, - bool measureIsNegative}); - - @override - void onAttach(BaseChart chart) { - super.onAttach(chart); - // We only need the chart.context.isRtl setting, but context is not yet - // available when the default renderer is attached to the chart on chart - // creation time, since chart onInit is called after the chart is created. - this.chart = chart; - } - - /// Paints the current bar data on the canvas. - void paint(ChartCanvas canvas, double animationPercent) { - // Clean up the bars that no longer exist. - if (animationPercent == 1.0) { - final keysToRemove = HashSet(); - - _barStackMap.forEach((key, barStackList) { - barStackList.retainWhere( - (bar) => !bar.animatingOut && !bar.targetBar.measureIsNull); - - if (barStackList.isEmpty) { - keysToRemove.add(key); - } - }); - - // When cleaning up the animation, also clean up the keys used to lookup - // if a bar is selected. - for (String key in keysToRemove) { - _barStackMap.remove(key); - _currentKeys.remove(key); - } - _currentGroupsStackKeys.forEach((domain, keys) { - keys.removeWhere(keysToRemove.contains); - }); - } - - _barStackMap.forEach((stackKey, barStack) { - // Turn this into a list so that the getCurrentBar isn't called more than - // once for each animationPercent if the barElements are iterated more - // than once. - final barElements = barStack - .map((animatingBar) => animatingBar.getCurrentBar(animationPercent)) - .toList(); - - if (barElements.isNotEmpty) { - paintBar(canvas, animationPercent, barElements); - } - }); - } - - /// Paints a stack of bar elements on the canvas. - void paintBar( - ChartCanvas canvas, double animationPercent, Iterable barElements); - - @override - List> getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride) { - var nearest = >[]; - - // Was it even in the component bounds? - if (!isPointWithinBounds(chartPoint, boundsOverride)) { - return nearest; - } - - if (_prevDomainAxis is OrdinalAxis) { - final domainValue = _prevDomainAxis - .getDomain(renderingVertically ? chartPoint.x : chartPoint.y); - - // If we have a domainValue for the event point, then find all segments - // that match it. - if (domainValue != null) { - if (renderingVertically) { - nearest = _getVerticalDetailsForDomainValue(domainValue, chartPoint); - } else { - nearest = - _getHorizontalDetailsForDomainValue(domainValue, chartPoint); - } - } - } else { - if (renderingVertically) { - nearest = _getVerticalDetailsForDomainValue(null, chartPoint); - } else { - nearest = _getHorizontalDetailsForDomainValue(null, chartPoint); - } - - // Find the closest domain and only keep values that match the domain. - var minRelativeDistance = double.maxFinite; - var minDomainDistance = double.maxFinite; - var minMeasureDistance = double.maxFinite; - D nearestDomain; - - // TODO: Optimize this with a binary search based on chartX. - for (DatumDetails detail in nearest) { - if (byDomain) { - if (detail.domainDistance < minDomainDistance || - (detail.domainDistance == minDomainDistance && - detail.measureDistance < minMeasureDistance)) { - minDomainDistance = detail.domainDistance; - minMeasureDistance = detail.measureDistance; - nearestDomain = detail.domain; - } - } else { - if (detail.relativeDistance < minRelativeDistance) { - minRelativeDistance = detail.relativeDistance; - nearestDomain = detail.domain; - } - } - } - - nearest.retainWhere((d) => d.domain == nearestDomain); - } - - // If we didn't find anything, then keep an empty list. - nearest ??= >[]; - - // Note: the details are already sorted by domain & measure distance in - // base chart. - return nearest; - } - - Rectangle getBoundsForBar(R bar); - - @protected - List> _getSegmentsForDomainValue(D domainValue, - {bool where(BaseAnimatedBar bar)}) { - final matchingSegments = >[]; - - // [domainValue] is null only when the bar renderer is being used with in - // a non ordinal axis (ex. date time axis). - // - // In the case of null [domainValue] return all values to be compared, since - // we can't use the optimized comparison for [OrdinalAxis]. - final stackKeys = (domainValue != null) - ? _currentGroupsStackKeys[domainValue] - : _currentGroupsStackKeys.values - .reduce((allKeys, keys) => allKeys..addAll(keys)); - stackKeys?.forEach((stackKey) { - if (where != null) { - matchingSegments.addAll(_barStackMap[stackKey].where(where)); - } else { - matchingSegments.addAll(_barStackMap[stackKey]); - } - }); - - return matchingSegments; - } - - // In the case of null [domainValue] return all values to be compared, since - // we can't use the optimized comparison for [OrdinalAxis]. - List> _getVerticalDetailsForDomainValue( - D domainValue, Point chartPoint) { - return List>.from(_getSegmentsForDomainValue(domainValue, - where: (bar) => !bar.series.overlaySeries).map>((bar) { - final barBounds = getBoundsForBar(bar.currentBar); - final segmentDomainDistance = - _getDistance(chartPoint.x.round(), barBounds.left, barBounds.right); - final segmentMeasureDistance = - _getDistance(chartPoint.y.round(), barBounds.top, barBounds.bottom); - - final nearestPoint = Point( - clamp(chartPoint.x, barBounds.left, barBounds.right).toDouble(), - clamp(chartPoint.y, barBounds.top, barBounds.bottom).toDouble()); - - final relativeDistance = chartPoint.distanceTo(nearestPoint); - - return DatumDetails( - series: bar.series, - datum: bar.datum, - domain: bar.domainValue, - domainDistance: segmentDomainDistance, - measureDistance: segmentMeasureDistance, - relativeDistance: relativeDistance, - ); - })); - } - - List> _getHorizontalDetailsForDomainValue( - D domainValue, Point chartPoint) { - return List>.from(_getSegmentsForDomainValue(domainValue, - where: (bar) => !bar.series.overlaySeries).map((bar) { - final barBounds = getBoundsForBar(bar.currentBar); - final segmentDomainDistance = - _getDistance(chartPoint.y.round(), barBounds.top, barBounds.bottom); - final segmentMeasureDistance = - _getDistance(chartPoint.x.round(), barBounds.left, barBounds.right); - - return DatumDetails( - series: bar.series, - datum: bar.datum, - domain: bar.domainValue, - domainDistance: segmentDomainDistance, - measureDistance: segmentMeasureDistance, - ); - })); - } - - double _getDistance(int point, int min, int max) { - if (max >= point && min <= point) { - return 0.0; - } - return (point > max ? (point - max) : (min - point)).toDouble(); - } - - /// Gets the iterator for the series based grouped/stacked and orientation. - /// - /// For vertical stacked bars: - /// * If grouped, return the iterator that keeps the category order but - /// reverse the order of the series so the first series is on the top of the - /// stack. - /// * Otherwise, return iterator of the reversed list - /// - /// All other types, use the in order iterator. - @protected - Iterable getOrderedSeriesList( - List seriesList) { - return (renderingVertically && config.stacked) - ? config.grouped - ? _ReversedSeriesIterable(seriesList) - : seriesList.reversed - : seriesList; - } - - bool get isRtl => chart.context.isRtl; -} - -/// Iterable wrapping the seriesList that returns the ReversedSeriesItertor. -class _ReversedSeriesIterable extends Iterable { - final List seriesList; - - _ReversedSeriesIterable(this.seriesList); - - @override - Iterator get iterator => _ReversedSeriesIterator(seriesList); -} - -/// Iterator that keeps reverse series order but keeps category order. -/// -/// This is needed because for grouped stacked bars, the category stays in the -/// order it was passed in for the grouping, but the series is flipped so that -/// the first series of that category is on the top of the stack. -class _ReversedSeriesIterator extends Iterator { - final List _list; - final _visitIndex = []; - int _current; - - _ReversedSeriesIterator(List list) : _list = list { - // In the order of the list, save the category and the indices of the series - // with the same category. - final categoryAndSeriesIndexMap = >{}; - for (var i = 0; i < list.length; i++) { - categoryAndSeriesIndexMap - .putIfAbsent(list[i].seriesCategory, () => []) - .add(i); - } - - // Creates a visit that is categories in order, but the series is reversed. - categoryAndSeriesIndexMap - .forEach((_, indices) => _visitIndex.addAll(indices.reversed)); - } - - @override - bool moveNext() { - _current = (_current == null) ? 0 : _current + 1; - - return _current < _list.length; - } - - @override - S get current => _list[_visitIndex[_current]]; -} diff --git a/web/charts/common/lib/src/chart/bar/base_bar_renderer_config.dart b/web/charts/common/lib/src/chart/bar/base_bar_renderer_config.dart deleted file mode 100644 index 6cbd9611e..000000000 --- a/web/charts/common/lib/src/chart/bar/base_bar_renderer_config.dart +++ /dev/null @@ -1,151 +0,0 @@ -// 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 'package:collection/collection.dart' show ListEquality; - -import '../../common/symbol_renderer.dart' - show SymbolRenderer, RoundedRectSymbolRenderer; -import '../common/chart_canvas.dart' show FillPatternType; -import '../common/series_renderer_config.dart' - show RendererAttributes, SeriesRendererConfig; -import '../layout/layout_view.dart' show LayoutViewConfig; - -/// Shared configuration for bar chart renderers. -/// -/// Bar renderers support 4 different modes of rendering multiple series on the -/// chart, configured by the grouped and stacked flags. -/// * grouped - Render bars for each series that shares a domain value -/// side-by-side. -/// * stacked - Render bars for each series that shares a domain value in a -/// stack, ordered in the same order as the series list. -/// * grouped-stacked: Render bars for each series that shares a domain value in -/// a group of bar stacks. Each stack will contain all the series that share a -/// series category. -/// * floating style - When grouped and stacked are both false, all bars that -/// share a domain value will be rendered in the same domain space. Each datum -/// should be configured with a measure offset to position its bar along the -/// measure axis. Bars will freely overlap if their measure values and measure -/// offsets overlap. Note that bars for each series will be rendered in order, -/// such that bars from the last series will be "on top" of bars from previous -/// series. -abstract class BaseBarRendererConfig extends LayoutViewConfig - implements SeriesRendererConfig { - final String customRendererId; - - final SymbolRenderer symbolRenderer; - - /// Dash pattern for the stroke line around the edges of the bar. - final List dashPattern; - - /// Defines the way multiple series of bars are rendered per domain. - final BarGroupingType groupingType; - - /// The order to paint this renderer on the canvas. - final int layoutPaintOrder; - - final int minBarLengthPx; - - final FillPatternType fillPattern; - - final double stackHorizontalSeparator; - - /// Stroke width of the target line. - final double strokeWidthPx; - - /// Sets the series weight pattern. This is a pattern of weights used to - /// calculate the width of bars within a bar group. If not specified, each bar - /// in the group will have an equal width. - /// - /// The pattern will not repeat. If more series are assigned to the renderer - /// than there are segments in the weight pattern, an error will be thrown. - /// - /// e.g. For the pattern [2, 1], the first bar in a group should be rendered - /// twice as wide as the second bar. - /// - /// If the expected bar width of the chart is 12px, then the first bar will - /// render at 16px and the second will render at 8px. The default weight - /// pattern of null means that all bars should be the same width, or 12px in - /// this case. - /// - /// Not used for stacked bars. - final List weightPattern; - - final rendererAttributes = RendererAttributes(); - - BaseBarRendererConfig( - {this.customRendererId, - this.dashPattern, - this.groupingType = BarGroupingType.grouped, - this.layoutPaintOrder, - this.minBarLengthPx = 0, - this.fillPattern, - this.stackHorizontalSeparator, - this.strokeWidthPx = 0.0, - SymbolRenderer symbolRenderer, - this.weightPattern}) - : this.symbolRenderer = symbolRenderer ?? RoundedRectSymbolRenderer(); - - /// Whether or not the bars should be organized into groups. - bool get grouped => - groupingType == BarGroupingType.grouped || - groupingType == BarGroupingType.groupedStacked; - - /// Whether or not the bars should be organized into stacks. - bool get stacked => - groupingType == BarGroupingType.stacked || - groupingType == BarGroupingType.groupedStacked; - - @override - bool operator ==(other) { - if (identical(this, other)) { - return true; - } - return other is BaseBarRendererConfig && - other.customRendererId == customRendererId && - other.dashPattern == dashPattern && - other.fillPattern == fillPattern && - other.groupingType == groupingType && - other.minBarLengthPx == minBarLengthPx && - other.stackHorizontalSeparator == stackHorizontalSeparator && - other.strokeWidthPx == strokeWidthPx && - other.symbolRenderer == symbolRenderer && - ListEquality().equals(other.weightPattern, weightPattern); - } - - int get hashcode { - var hash = 1; - hash = hash * 31 + (customRendererId?.hashCode ?? 0); - hash = hash * 31 + (dashPattern?.hashCode ?? 0); - hash = hash * 31 + (fillPattern?.hashCode ?? 0); - hash = hash * 31 + (groupingType?.hashCode ?? 0); - hash = hash * 31 + (minBarLengthPx?.hashCode ?? 0); - hash = hash * 31 + (stackHorizontalSeparator?.hashCode ?? 0); - hash = hash * 31 + (strokeWidthPx?.hashCode ?? 0); - hash = hash * 31 + (symbolRenderer?.hashCode ?? 0); - hash = hash * 31 + (weightPattern?.hashCode ?? 0); - return hash; - } -} - -/// Defines the way multiple series of bars are renderered per domain. -/// -/// * [grouped] - Render bars for each series that shares a domain value -/// side-by-side. -/// * [stacked] - Render bars for each series that shares a domain value in a -/// stack, ordered in the same order as the series list. -/// * [groupedStacked]: Render bars for each series that shares a domain value -/// in a group of bar stacks. Each stack will contain all the series that -/// share a series category. -enum BarGroupingType { grouped, groupedStacked, stacked } diff --git a/web/charts/common/lib/src/chart/bar/base_bar_renderer_element.dart b/web/charts/common/lib/src/chart/bar/base_bar_renderer_element.dart deleted file mode 100644 index bfa5fb16a..000000000 --- a/web/charts/common/lib/src/chart/bar/base_bar_renderer_element.dart +++ /dev/null @@ -1,128 +0,0 @@ -// 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 '../../common/color.dart' show Color; -import '../common/chart_canvas.dart' show getAnimatedColor, FillPatternType; -import '../common/processed_series.dart' show ImmutableSeries; - -abstract class BaseBarRendererElement { - int barStackIndex; - Color color; - num cumulativeTotal; - List dashPattern; - Color fillColor; - FillPatternType fillPattern; - double measureAxisPosition; - num measureOffset; - num measureOffsetPlusMeasure; - double strokeWidthPx; - bool measureIsNull; - bool measureIsNegative; - - BaseBarRendererElement(); - - BaseBarRendererElement.clone(BaseBarRendererElement other) { - barStackIndex = other.barStackIndex; - color = other.color != null ? Color.fromOther(color: other.color) : null; - cumulativeTotal = other.cumulativeTotal; - dashPattern = other.dashPattern; - fillColor = other.fillColor != null - ? Color.fromOther(color: other.fillColor) - : null; - fillPattern = other.fillPattern; - measureAxisPosition = other.measureAxisPosition; - measureOffset = other.measureOffset; - measureOffsetPlusMeasure = other.measureOffsetPlusMeasure; - strokeWidthPx = other.strokeWidthPx; - measureIsNull = other.measureIsNull; - measureIsNegative = other.measureIsNegative; - } - - void updateAnimationPercent(BaseBarRendererElement previous, - BaseBarRendererElement target, double animationPercent) { - color = getAnimatedColor(previous.color, target.color, animationPercent); - fillColor = getAnimatedColor( - previous.fillColor, target.fillColor, animationPercent); - measureIsNull = target.measureIsNull; - measureIsNegative = target.measureIsNegative; - } -} - -abstract class BaseAnimatedBar { - final String key; - dynamic datum; - ImmutableSeries series; - D domainValue; - - R _previousBar; - R _targetBar; - R _currentBar; - - // Flag indicating whether this bar is being animated out of the chart. - bool animatingOut = false; - - BaseAnimatedBar({this.key, this.datum, this.series, this.domainValue}); - - /// Animates a bar that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for bars that represent - /// data that has been removed from the series. - /// - /// Animates the height of the bar down to the measure axis position (position - /// of 0). Animates the width of the bar down to 0, centered in the middle of - /// the original bar width. - void animateOut() { - var newTarget = clone(_currentBar); - - animateElementToMeasureAxisPosition(newTarget); - - setNewTarget(newTarget); - animatingOut = true; - } - - /// Sets the bounds for the target to the measure axis position. - void animateElementToMeasureAxisPosition(R target); - - /// Sets a new element to render. - void setNewTarget(R newTarget) { - animatingOut = false; - _currentBar ??= clone(newTarget); - _previousBar = clone(_currentBar); - _targetBar = newTarget; - } - - R get currentBar => _currentBar; - - R get previousBar => _previousBar; - - R get targetBar => _targetBar; - - /// Gets the new state of the bar element for painting, updated for a - /// transition between the previous state and the new animationPercent. - R getCurrentBar(double animationPercent) { - if (animationPercent == 1.0 || _previousBar == null) { - _currentBar = _targetBar; - _previousBar = _targetBar; - return _currentBar; - } - - _currentBar.updateAnimationPercent( - _previousBar, _targetBar, animationPercent); - - return _currentBar; - } - - R clone(R bar); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/axis.dart b/web/charts/common/lib/src/chart/cartesian/axis/axis.dart deleted file mode 100644 index 5203224ee..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/axis.dart +++ /dev/null @@ -1,572 +0,0 @@ -// 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, min, max; - -import 'package:meta/meta.dart' show protected, visibleForTesting; - -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/text_element.dart' show TextElement; -import '../../../data/series.dart' show AttributeKey; -import '../../common/chart_canvas.dart' show ChartCanvas; -import '../../common/chart_context.dart' show ChartContext; -import '../../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - ViewMeasuredSizes; -import 'axis_tick.dart' show AxisTicks; -import 'draw_strategy/small_tick_draw_strategy.dart' show SmallTickDrawStrategy; -import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import 'linear/linear_scale.dart' show LinearScale; -import 'numeric_extents.dart' show NumericExtents; -import 'numeric_scale.dart' show NumericScale; -import 'numeric_tick_provider.dart' show NumericTickProvider; -import 'ordinal_tick_provider.dart' show OrdinalTickProvider; -import 'scale.dart' - show MutableScale, RangeBandConfig, ScaleOutputExtent, Scale; -import 'simple_ordinal_scale.dart' show SimpleOrdinalScale; -import 'tick.dart' show Tick; -import 'tick_formatter.dart' - show TickFormatter, OrdinalTickFormatter, NumericTickFormatter; -import 'tick_provider.dart' show TickProvider; - -const measureAxisIdKey = AttributeKey('Axis.measureAxisId'); -const measureAxisKey = AttributeKey('Axis.measureAxis'); -const domainAxisKey = AttributeKey('Axis.domainAxis'); - -/// Orientation of an Axis. -enum AxisOrientation { top, right, bottom, left } - -abstract class ImmutableAxis { - /// Compare domain to the viewport. - /// - /// 0 if the domain is in the viewport. - /// 1 if the domain is to the right of the viewport. - /// -1 if the domain is to the left of the viewport. - int compareDomainValueToViewport(D domain); - - /// Get location for the domain. - double getLocation(D domain); - - D getDomain(double location); - - /// Rangeband for this axis. - double get rangeBand; - - /// Step size for this axis. - double get stepSize; - - /// Output range for this axis. - ScaleOutputExtent get range; -} - -abstract class Axis extends ImmutableAxis implements LayoutView { - static const primaryMeasureAxisId = 'primaryMeasureAxisId'; - static const secondaryMeasureAxisId = 'secondaryMeasureAxisId'; - - final MutableScale _scale; - - /// [Scale] of this axis. - @protected - MutableScale get scale => _scale; - - /// Previous [Scale] of this axis, used to calculate tick animation. - MutableScale _previousScale; - - /// [TickProvider] for this axis. - TickProvider tickProvider; - - /// [TickFormatter] for this axis. - TickFormatter _tickFormatter; - - set tickFormatter(TickFormatter formatter) { - if (_tickFormatter != formatter) { - _tickFormatter = formatter; - _formatterValueCache.clear(); - } - } - - TickFormatter get tickFormatter => _tickFormatter; - final _formatterValueCache = {}; - - /// [TickDrawStrategy] for this axis. - TickDrawStrategy tickDrawStrategy; - - /// [AxisOrientation] for this axis. - AxisOrientation axisOrientation; - - ChartContext context; - - /// If the output range should be reversed. - bool reverseOutputRange = false; - - /// Configures whether the viewport should be reset back to default values - /// when the domain is reset. - /// - /// This should generally be disabled when the viewport will be managed - /// externally, e.g. from pan and zoom behaviors. - bool autoViewport = true; - - /// If the axis line should always be drawn. - bool forceDrawAxisLine; - - /// If true, do not allow axis to be modified. - /// - /// Ticks (including their location) are not updated. - /// Viewport changes not allowed. - bool lockAxis = false; - - /// Ticks provided by the tick provider. - List _providedTicks; - - /// Ticks used by the axis for drawing. - final _axisTicks = >[]; - - Rectangle _componentBounds; - Rectangle _drawAreaBounds; - GraphicsFactory graphicsFactory; - - /// Order for chart layout painting. - /// - /// In general, domain axes should be drawn on top of measure axes to ensure - /// that the domain axis line appears on top of any measure axis grid lines. - int layoutPaintOrder = LayoutViewPaintOrder.measureAxis; - - Axis( - {TickProvider tickProvider, - TickFormatter tickFormatter, - MutableScale scale}) - : this._scale = scale, - this.tickProvider = tickProvider, - this._tickFormatter = tickFormatter; - - @protected - MutableScale get mutableScale => _scale; - - /// Rangeband for this axis. - @override - double get rangeBand => _scale.rangeBand; - - @override - double get stepSize => _scale.stepSize; - - @override - ScaleOutputExtent get range => _scale.range; - - void setRangeBandConfig(RangeBandConfig rangeBandConfig) { - mutableScale.rangeBandConfig = rangeBandConfig; - } - - void addDomainValue(D domain) { - if (lockAxis) { - return; - } - - _scale.addDomain(domain); - } - - void resetDomains() { - if (lockAxis) { - return; - } - - // If the series list changes, clear the cache. - // - // There are cases where tick formatter has not "changed", but if measure - // formatter provided to the tick formatter uses a closure value, the - // formatter cache needs to be cleared. - // - // This type of use case for the measure formatter surfaced where the series - // list also changes. So this is a round about way to also clear the - // tick formatter cache. - // - // TODO: Measure formatter should be changed from a typedef to - // a concrete class to force users to create a new tick formatter when - // formatting is different, so we can recognize when the tick formatter is - // changed and then clear cache accordingly. - // - // Remove this when bug above is fixed, and verify it did not cause - // regression for b/110371453. - _formatterValueCache.clear(); - - _scale.resetDomain(); - reverseOutputRange = false; - - if (autoViewport) { - _scale.resetViewportSettings(); - } - - // TODO: Reset rangeband and step size when we port over config - //scale.rangeBandConfig = get range band config - //scale.stepSizeConfig = get step size config - } - - @override - double getLocation(D domain) => domain != null ? _scale[domain] : null; - - @override - D getDomain(double location) => _scale.reverse(location); - - @override - int compareDomainValueToViewport(D domain) { - return _scale.compareDomainValueToViewport(domain); - } - - void setOutputRange(int start, int end) { - _scale.range = ScaleOutputExtent(start, end); - } - - /// Request update ticks from tick provider and update the painted ticks. - void updateTicks() { - _updateProvidedTicks(); - _updateAxisTicks(); - } - - /// Request ticks from tick provider. - void _updateProvidedTicks() { - if (lockAxis) { - return; - } - - // TODO: Ensure that tick providers take manually configured - // viewport settings into account, so that we still get the right number. - _providedTicks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: _scale, - formatter: tickFormatter, - formatterValueCache: _formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - orientation: axisOrientation, - viewportExtensionEnabled: autoViewport); - } - - /// Updates the ticks that are actually used for drawing. - void _updateAxisTicks() { - if (lockAxis) { - return; - } - - final providedTicks = List.from(_providedTicks ?? []); - - for (AxisTicks animatedTick in _axisTicks) { - final tick = providedTicks?.firstWhere( - (t) => t.value == animatedTick.value, - orElse: () => null); - - if (tick != null) { - // Swap out the text element only if the settings are different. - // This prevents a costly new TextPainter in Flutter. - if (!TextElement.elementSettingsSame( - animatedTick.textElement, tick.textElement)) { - animatedTick.textElement = tick.textElement; - } - // Update target for all existing ticks - animatedTick.setNewTarget(_scale[tick.value]); - providedTicks.remove(tick); - } else { - // Animate out ticks that do not exist any more. - animatedTick.animateOut(_scale[animatedTick.value].toDouble()); - } - } - - // Add new ticks - providedTicks?.forEach((tick) { - final animatedTick = AxisTicks(tick); - if (_previousScale != null) { - animatedTick.animateInFrom(_previousScale[tick.value].toDouble()); - } - _axisTicks.add(animatedTick); - }); - - _axisTicks.sort(); - - // Save a copy of the current scale to be used as the previous scale when - // ticks are updated. - _previousScale = _scale.copy(); - } - - /// Configures the zoom and translate. - /// - /// [viewportScale] is the zoom factor to use, likely >= 1.0 where 1.0 maps - /// the complete data extents to the output range, and 2.0 only maps half the - /// data to the output range. - /// - /// [viewportTranslatePx] is the translate/pan to use in pixel units, - /// likely <= 0 which shifts the start of the data before the edge of the - /// chart giving us a pan. - /// - /// [drawAreaWidth] is the width of the draw area for the series data in pixel - /// units, at minimum viewport scale level (1.0). When provided, - /// [viewportTranslatePx] will be clamped such that the axis cannot be panned - /// beyond the bounds of the data. - void setViewportSettings(double viewportScale, double viewportTranslatePx, - {int drawAreaWidth}) { - // Don't let the viewport be panned beyond the bounds of the data. - viewportTranslatePx = _clampTranslatePx(viewportScale, viewportTranslatePx, - drawAreaWidth: drawAreaWidth); - - _scale.setViewportSettings(viewportScale, viewportTranslatePx); - } - - /// Returns the current viewport scale. - /// - /// A scale of 1.0 would map the data directly to the output range, while a - /// value of 2.0 would map the data to an output of double the range so you - /// only see half the data in the viewport. This is the equivalent to - /// zooming. Its value is likely >= 1.0. - double get viewportScalingFactor => _scale.viewportScalingFactor; - - /// Returns the current pixel viewport offset - /// - /// The translate is used by the scale function when it applies the scale. - /// This is the equivalent to panning. Its value is likely <= 0 to pan the - /// data to the left. - double get viewportTranslatePx => _scale?.viewportTranslatePx; - - /// Clamps a possible change in domain translation to fit within the range of - /// the data. - double _clampTranslatePx( - double viewportScalingFactor, double viewportTranslatePx, - {int drawAreaWidth}) { - if (drawAreaWidth == null) { - return viewportTranslatePx; - } - - // Bound the viewport translate to the range of the data. - final maxNegativeTranslate = - -1.0 * ((drawAreaWidth * viewportScalingFactor) - drawAreaWidth); - - viewportTranslatePx = - min(max(viewportTranslatePx, maxNegativeTranslate), 0.0); - - return viewportTranslatePx; - } - - // - // LayoutView methods. - // - - @override - LayoutViewConfig get layoutConfig => LayoutViewConfig( - paintOrder: layoutPaintOrder, - position: _layoutPosition, - positionOrder: LayoutViewPositionOrder.axis); - - /// Get layout position from axis orientation. - LayoutPosition get _layoutPosition { - LayoutPosition position; - switch (axisOrientation) { - case AxisOrientation.top: - position = LayoutPosition.Top; - break; - case AxisOrientation.right: - position = LayoutPosition.Right; - break; - case AxisOrientation.bottom: - position = LayoutPosition.Bottom; - break; - case AxisOrientation.left: - position = LayoutPosition.Left; - break; - } - - return position; - } - - /// The axis is rendered vertically. - bool get isVertical => - axisOrientation == AxisOrientation.left || - axisOrientation == AxisOrientation.right; - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - return isVertical - ? _measureVerticalAxis(maxWidth, maxHeight) - : _measureHorizontalAxis(maxWidth, maxHeight); - } - - ViewMeasuredSizes _measureVerticalAxis(int maxWidth, int maxHeight) { - setOutputRange(maxHeight, 0); - _updateProvidedTicks(); - - return tickDrawStrategy.measureVerticallyDrawnTicks( - _providedTicks, maxWidth, maxHeight); - } - - ViewMeasuredSizes _measureHorizontalAxis(int maxWidth, int maxHeight) { - setOutputRange(0, maxWidth); - _updateProvidedTicks(); - - return tickDrawStrategy.measureHorizontallyDrawnTicks( - _providedTicks, maxWidth, maxHeight); - } - - /// Layout this component. - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - _componentBounds = componentBounds; - _drawAreaBounds = drawAreaBounds; - - // Update the output range if it is different than the current one. - // This is necessary because during the measure cycle, the output range is - // set between zero and the max range available. On layout, the output range - // needs to be updated to account of the offset of the axis view. - - final outputStart = - isVertical ? _componentBounds.bottom : _componentBounds.left; - final outputEnd = - isVertical ? _componentBounds.top : _componentBounds.right; - - final outputRange = reverseOutputRange - ? ScaleOutputExtent(outputEnd, outputStart) - : ScaleOutputExtent(outputStart, outputEnd); - - if (_scale.range != outputRange) { - _scale.range = outputRange; - } - - _updateProvidedTicks(); - // Update animated ticks in layout, because updateTicks are called during - // measure and we don't want to update the animation at that time. - _updateAxisTicks(); - } - - @override - bool get isSeriesRenderer => false; - - @override - Rectangle get componentBounds => this._componentBounds; - - bool get drawAxisLine { - if (forceDrawAxisLine != null) { - return forceDrawAxisLine; - } - - return tickDrawStrategy is SmallTickDrawStrategy; - } - - @override - void paint(ChartCanvas canvas, double animationPercent) { - if (animationPercent == 1.0) { - _axisTicks.removeWhere((t) => t.markedForRemoval); - } - - for (var i = 0; i < _axisTicks.length; i++) { - final animatedTick = _axisTicks[i]; - tickDrawStrategy.draw( - canvas, animatedTick..setCurrentTick(animationPercent), - orientation: axisOrientation, - axisBounds: _componentBounds, - drawAreaBounds: _drawAreaBounds, - isFirst: i == 0, - isLast: i == _axisTicks.length - 1); - } - - if (drawAxisLine) { - tickDrawStrategy.drawAxisLine(canvas, axisOrientation, _componentBounds); - } - } -} - -class NumericAxis extends Axis { - NumericAxis({TickProvider tickProvider}) - : super( - tickProvider: tickProvider ?? NumericTickProvider(), - tickFormatter: NumericTickFormatter(), - scale: LinearScale(), - ); - - void setScaleViewport(NumericExtents viewport) { - autoViewport = false; - (_scale as NumericScale).viewportDomain = viewport; - } -} - -class OrdinalAxis extends Axis { - OrdinalAxis({ - TickDrawStrategy tickDrawStrategy, - TickProvider tickProvider, - TickFormatter tickFormatter, - }) : super( - tickProvider: tickProvider ?? const OrdinalTickProvider(), - tickFormatter: tickFormatter ?? const OrdinalTickFormatter(), - scale: SimpleOrdinalScale(), - ); - - void setScaleViewport(OrdinalViewport viewport) { - autoViewport = false; - (_scale as SimpleOrdinalScale) - .setViewport(viewport.dataSize, viewport.startingDomain); - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - super.layout(componentBounds, drawAreaBounds); - - // We are purposely clearing the viewport starting domain and data size - // post layout. - // - // Originally we set a flag in [setScaleViewport] to recalculate viewport - // settings on next scale update and then reset the flag. This doesn't work - // because chart's measure cycle provides different ranges to the scale, - // causing the scale to update multiple times before it is finalized after - // layout. - // - // By resetting the viewport after layout, we guarantee the correct range - // was used to apply the viewport and behaviors that update the viewport - // based on translate and scale changes will not be affected (pan/zoom). - (_scale as SimpleOrdinalScale).setViewport(null, null); - } -} - -/// Viewport to cover [dataSize] data points starting at [startingDomain] value. -class OrdinalViewport { - final String startingDomain; - final int dataSize; - - OrdinalViewport(this.startingDomain, this.dataSize); - - @override - bool operator ==(Object other) { - return other is OrdinalViewport && - startingDomain == other.startingDomain && - dataSize == other.dataSize; - } - - @override - int get hashCode { - int hashcode = startingDomain.hashCode; - hashcode = (hashcode * 37) + dataSize; - return hashcode; - } -} - -@visibleForTesting -class AxisTester { - final Axis _axis; - - AxisTester(this._axis); - - List> get axisTicks => _axis._axisTicks; - - MutableScale get scale => _axis._scale; - - List get axisValues => axisTicks.map((t) => t.value).toList(); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/axis_tick.dart b/web/charts/common/lib/src/chart/cartesian/axis/axis_tick.dart deleted file mode 100644 index 58f60a533..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/axis_tick.dart +++ /dev/null @@ -1,112 +0,0 @@ -// 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 'tick.dart' show Tick; - -class AxisTicks extends Tick implements Comparable> { - /// This tick is being animated out. - bool _markedForRemoval; - - /// This tick's current location. - double _currentLocation; - - /// This tick's previous target location. - double _previousLocation; - - /// This tick's current target location. - double _targetLocation; - - /// This tick's current opacity. - double _currentOpacity; - - /// This tick's previous opacity. - double _previousOpacity; - - /// This tick's target opacity. - double _targetOpacity; - - AxisTicks(Tick tick) - : super( - value: tick.value, - textElement: tick.textElement, - locationPx: tick.locationPx, - labelOffsetPx: tick.labelOffsetPx) { - /// Set the initial target for a new animated tick. - _markedForRemoval = false; - _targetLocation = tick.locationPx; - } - - bool get markedForRemoval => _markedForRemoval; - - /// Animate the tick in from [previousLocation]. - void animateInFrom(double previousLocation) { - _markedForRemoval = false; - _previousLocation = previousLocation; - _previousOpacity = 0.0; - _targetOpacity = 1.0; - } - - /// Animate out this tick to [newLocation]. - void animateOut(double newLocation) { - _markedForRemoval = true; - _previousLocation = _currentLocation; - _targetLocation = newLocation; - _previousOpacity = _currentOpacity; - _targetOpacity = 0.0; - } - - /// Set new target for this tick to be [newLocation]. - void setNewTarget(double newLocation) { - _markedForRemoval = false; - _previousLocation = _currentLocation; - _targetLocation = newLocation; - _previousOpacity = _currentOpacity; - _targetOpacity = 1.0; - } - - /// Update tick's location and opacity based on animation percent. - void setCurrentTick(double animationPercent) { - if (animationPercent == 1.0) { - _currentLocation = _targetLocation; - _previousLocation = _targetLocation; - _currentOpacity = markedForRemoval ? 0.0 : 1.0; - } else if (_previousLocation == null) { - _currentLocation = _targetLocation; - _currentOpacity = 1.0; - } else { - _currentLocation = - _lerpDouble(_previousLocation, _targetLocation, animationPercent); - _currentOpacity = - _lerpDouble(_previousOpacity, _targetOpacity, animationPercent); - } - - locationPx = _currentLocation; - textElement.opacity = _currentOpacity; - } - - /// Linearly interpolate between two numbers. - /// - /// From lerpDouble in dart:ui which is Flutter only. - double _lerpDouble(double a, double b, double t) { - if (a == null && b == null) return null; - a ??= 0.0; - b ??= 0.0; - return a + (b - a) * t; - } - - int compareTo(AxisTicks other) { - return _targetLocation.compareTo(other._targetLocation); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/collision_report.dart b/web/charts/common/lib/src/chart/cartesian/axis/collision_report.dart deleted file mode 100644 index 82141c414..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/collision_report.dart +++ /dev/null @@ -1,38 +0,0 @@ -// 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 'package:meta/meta.dart' show required; -import 'tick.dart' show Tick; - -/// A report that contains a list of ticks and if they collide. -class CollisionReport { - /// If [ticks] collide. - final bool ticksCollide; - - final List ticks; - - final bool alternateTicksUsed; - - CollisionReport( - {@required this.ticksCollide, - @required this.ticks, - bool alternateTicksUsed}) - : alternateTicksUsed = alternateTicksUsed ?? false; - - CollisionReport.empty() - : ticksCollide = false, - ticks = [], - alternateTicksUsed = false; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart b/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart deleted file mode 100644 index 66e01c0a9..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart +++ /dev/null @@ -1,436 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart' show immutable, protected, required; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../../common/line_style.dart' show LineStyle; -import '../../../../common/style/style_factory.dart' show StyleFactory; -import '../../../../common/text_element.dart' show TextDirection; -import '../../../../common/text_style.dart' show TextStyle; -import '../../../common/chart_canvas.dart' show ChartCanvas; -import '../../../common/chart_context.dart' show ChartContext; -import '../../../layout/layout_view.dart' show ViewMeasuredSizes; -import '../axis.dart' show AxisOrientation; -import '../collision_report.dart' show CollisionReport; -import '../spec/axis_spec.dart' - show - TextStyleSpec, - TickLabelAnchor, - TickLabelJustification, - LineStyleSpec, - RenderSpec; -import '../tick.dart' show Tick; -import 'tick_draw_strategy.dart' show TickDrawStrategy; - -@immutable -abstract class BaseRenderSpec implements RenderSpec { - final TextStyleSpec labelStyle; - final TickLabelAnchor labelAnchor; - final TickLabelJustification labelJustification; - - final int labelOffsetFromAxisPx; - - /// Absolute distance from the tick to the text if using start/end - final int labelOffsetFromTickPx; - - final int minimumPaddingBetweenLabelsPx; - - final LineStyleSpec axisLineStyle; - - const BaseRenderSpec({ - this.labelStyle, - this.labelAnchor, - this.labelJustification, - this.labelOffsetFromAxisPx, - this.labelOffsetFromTickPx, - this.minimumPaddingBetweenLabelsPx, - this.axisLineStyle, - }); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is BaseRenderSpec && - labelStyle == other.labelStyle && - labelAnchor == other.labelAnchor && - labelJustification == other.labelJustification && - labelOffsetFromTickPx == other.labelOffsetFromTickPx && - labelOffsetFromAxisPx == other.labelOffsetFromAxisPx && - minimumPaddingBetweenLabelsPx == - other.minimumPaddingBetweenLabelsPx && - axisLineStyle == other.axisLineStyle); - } - - @override - int get hashCode { - int hashcode = labelStyle?.hashCode ?? 0; - hashcode = (hashcode * 37) + labelAnchor?.hashCode ?? 0; - hashcode = (hashcode * 37) + labelJustification?.hashCode ?? 0; - hashcode = (hashcode * 37) + labelOffsetFromTickPx?.hashCode ?? 0; - hashcode = (hashcode * 37) + labelOffsetFromAxisPx?.hashCode ?? 0; - hashcode = (hashcode * 37) + minimumPaddingBetweenLabelsPx?.hashCode ?? 0; - hashcode = (hashcode * 37) + axisLineStyle?.hashCode ?? 0; - return hashcode; - } -} - -/// Base strategy that draws tick labels and checks for label collisions. -abstract class BaseTickDrawStrategy implements TickDrawStrategy { - final ChartContext chartContext; - - LineStyle axisLineStyle; - TextStyle labelStyle; - TickLabelAnchor tickLabelAnchor; - TickLabelJustification tickLabelJustification; - int labelOffsetFromAxisPx; - int labelOffsetFromTickPx; - - int minimumPaddingBetweenLabelsPx; - - BaseTickDrawStrategy(this.chartContext, GraphicsFactory graphicsFactory, - {TextStyleSpec labelStyleSpec, - LineStyleSpec axisLineStyleSpec, - TickLabelAnchor labelAnchor, - TickLabelJustification labelJustification, - int labelOffsetFromAxisPx, - int labelOffsetFromTickPx, - int minimumPaddingBetweenLabelsPx}) { - labelStyle = (graphicsFactory.createTextPaint() - ..color = labelStyleSpec?.color ?? StyleFactory.style.tickColor - ..fontFamily = labelStyleSpec?.fontFamily - ..fontSize = labelStyleSpec?.fontSize ?? 12); - - axisLineStyle = graphicsFactory.createLinePaint() - ..color = axisLineStyleSpec?.color ?? labelStyle.color - ..dashPattern = axisLineStyleSpec?.dashPattern - ..strokeWidth = axisLineStyleSpec?.thickness ?? 1; - - tickLabelAnchor = labelAnchor ?? TickLabelAnchor.centered; - tickLabelJustification = - labelJustification ?? TickLabelJustification.inside; - this.labelOffsetFromAxisPx = labelOffsetFromAxisPx ?? 5; - this.labelOffsetFromTickPx = labelOffsetFromTickPx ?? 5; - this.minimumPaddingBetweenLabelsPx = minimumPaddingBetweenLabelsPx ?? 50; - } - - @override - void decorateTicks(List> ticks) { - for (Tick tick in ticks) { - // If no style at all, set the default style. - if (tick.textElement.textStyle == null) { - tick.textElement.textStyle = labelStyle; - } else { - //Fill in whatever is missing - tick.textElement.textStyle.color ??= labelStyle.color; - tick.textElement.textStyle.fontFamily ??= labelStyle.fontFamily; - tick.textElement.textStyle.fontSize ??= labelStyle.fontSize; - } - } - } - - @override - CollisionReport collides(List> ticks, AxisOrientation orientation) { - // If there are no ticks, they do not collide. - if (ticks == null) { - return CollisionReport( - ticksCollide: false, ticks: ticks, alternateTicksUsed: false); - } - - final vertical = orientation == AxisOrientation.left || - orientation == AxisOrientation.right; - - // First sort ticks by smallest locationPx first (NOT sorted by value). - // This allows us to only check if a tick collides with the previous tick. - ticks.sort((a, b) { - if (a.locationPx < b.locationPx) { - return -1; - } else if (a.locationPx > b.locationPx) { - return 1; - } else { - return 0; - } - }); - - double previousEnd = double.negativeInfinity; - bool collides = false; - - for (final tick in ticks) { - final tickSize = tick.textElement.measurement; - - if (vertical) { - final adjustedHeight = - tickSize.verticalSliceWidth + minimumPaddingBetweenLabelsPx; - - if (tickLabelAnchor == TickLabelAnchor.inside) { - if (identical(tick, ticks.first)) { - // Top most tick draws down from the location - collides = false; - previousEnd = tick.locationPx + adjustedHeight; - } else if (identical(tick, ticks.last)) { - // Bottom most tick draws up from the location - collides = previousEnd > tick.locationPx - adjustedHeight; - previousEnd = tick.locationPx; - } else { - // All other ticks is centered. - final halfHeight = adjustedHeight / 2; - collides = previousEnd > tick.locationPx - halfHeight; - previousEnd = tick.locationPx + halfHeight; - } - } else { - collides = previousEnd > tick.locationPx; - previousEnd = tick.locationPx + adjustedHeight; - } - } else { - // Use the text direction the ticks specified, unless the label anchor - // is set to [TickLabelAnchor.inside]. When 'inside' is set, the text - // direction is normalized such that the left most tick is drawn ltr, - // the last tick is drawn rtl, and all other ticks are in the center. - // This is not set until it is painted, so collision check needs to get - // the value also. - final textDirection = _normalizeHorizontalAnchor( - tickLabelAnchor, - chartContext.isRtl, - identical(tick, ticks.first), - identical(tick, ticks.last)); - final adjustedWidth = - tickSize.horizontalSliceWidth + minimumPaddingBetweenLabelsPx; - switch (textDirection) { - case TextDirection.ltr: - collides = previousEnd > tick.locationPx; - previousEnd = tick.locationPx + adjustedWidth; - break; - case TextDirection.rtl: - collides = previousEnd > (tick.locationPx - adjustedWidth); - previousEnd = tick.locationPx; - break; - case TextDirection.center: - final halfWidth = adjustedWidth / 2; - collides = previousEnd > tick.locationPx - halfWidth; - previousEnd = tick.locationPx + halfWidth; - - break; - } - } - - if (collides) { - return CollisionReport( - ticksCollide: true, ticks: ticks, alternateTicksUsed: false); - } - } - - return CollisionReport( - ticksCollide: false, ticks: ticks, alternateTicksUsed: false); - } - - @override - ViewMeasuredSizes measureVerticallyDrawnTicks( - List> ticks, int maxWidth, int maxHeight) { - // TODO: Add spacing to account for the distance between the - // text and the axis baseline (even if it isn't drawn). - final maxHorizontalSliceWidth = ticks - .fold( - 0.0, - (prevMax, tick) => max( - prevMax, - tick.textElement.measurement.horizontalSliceWidth + - labelOffsetFromAxisPx)) - .round(); - - return ViewMeasuredSizes( - preferredWidth: maxHorizontalSliceWidth, preferredHeight: maxHeight); - } - - @override - ViewMeasuredSizes measureHorizontallyDrawnTicks( - List> ticks, int maxWidth, int maxHeight) { - final maxVerticalSliceWidth = ticks - .fold( - 0.0, - (prevMax, tick) => max( - prevMax, tick.textElement.measurement.verticalSliceWidth)) - .round(); - - return ViewMeasuredSizes( - preferredWidth: maxWidth, - preferredHeight: maxVerticalSliceWidth + labelOffsetFromAxisPx); - } - - @override - void drawAxisLine(ChartCanvas canvas, AxisOrientation orientation, - Rectangle axisBounds) { - Point start; - Point end; - - switch (orientation) { - case AxisOrientation.top: - start = axisBounds.bottomLeft; - end = axisBounds.bottomRight; - break; - case AxisOrientation.bottom: - start = axisBounds.topLeft; - end = axisBounds.topRight; - break; - case AxisOrientation.right: - start = axisBounds.topLeft; - end = axisBounds.bottomLeft; - break; - case AxisOrientation.left: - start = axisBounds.topRight; - end = axisBounds.bottomRight; - break; - } - - canvas.drawLine( - points: [start, end], - fill: axisLineStyle.color, - stroke: axisLineStyle.color, - strokeWidthPx: axisLineStyle.strokeWidth.toDouble(), - dashPattern: axisLineStyle.dashPattern, - ); - } - - @protected - void drawLabel(ChartCanvas canvas, Tick tick, - {@required AxisOrientation orientation, - @required Rectangle axisBounds, - @required Rectangle drawAreaBounds, - @required bool isFirst, - @required bool isLast}) { - final locationPx = tick.locationPx; - final measurement = tick.textElement.measurement; - final isRtl = chartContext.isRtl; - - int x = 0; - int y = 0; - - final labelOffsetPx = tick.labelOffsetPx ?? 0; - - if (orientation == AxisOrientation.bottom || - orientation == AxisOrientation.top) { - y = orientation == AxisOrientation.bottom - ? axisBounds.top + labelOffsetFromAxisPx - : axisBounds.bottom - - measurement.verticalSliceWidth.toInt() - - labelOffsetFromAxisPx; - - final direction = - _normalizeHorizontalAnchor(tickLabelAnchor, isRtl, isFirst, isLast); - tick.textElement.textDirection = direction; - - switch (direction) { - case TextDirection.rtl: - x = (locationPx + labelOffsetFromTickPx + labelOffsetPx).toInt(); - break; - case TextDirection.ltr: - x = (locationPx - labelOffsetFromTickPx - labelOffsetPx).toInt(); - break; - case TextDirection.center: - default: - x = (locationPx - labelOffsetPx).toInt(); - break; - } - } else { - if (orientation == AxisOrientation.left) { - if (tickLabelJustification == TickLabelJustification.inside) { - x = axisBounds.right - labelOffsetFromAxisPx; - tick.textElement.textDirection = TextDirection.rtl; - } else { - x = axisBounds.left + labelOffsetFromAxisPx; - tick.textElement.textDirection = TextDirection.ltr; - } - } else { - // orientation == right - if (tickLabelJustification == TickLabelJustification.inside) { - x = axisBounds.left + labelOffsetFromAxisPx; - tick.textElement.textDirection = TextDirection.ltr; - } else { - x = axisBounds.right - labelOffsetFromAxisPx; - tick.textElement.textDirection = TextDirection.rtl; - } - } - - switch (_normalizeVerticalAnchor(tickLabelAnchor, isFirst, isLast)) { - case _PixelVerticalDirection.over: - y = (locationPx - - measurement.verticalSliceWidth - - labelOffsetFromTickPx - - labelOffsetPx) - .toInt(); - break; - case _PixelVerticalDirection.under: - y = (locationPx + labelOffsetFromTickPx + labelOffsetPx).toInt(); - break; - case _PixelVerticalDirection.center: - default: - y = (locationPx - measurement.verticalSliceWidth / 2 + labelOffsetPx) - .toInt(); - break; - } - } - - canvas.drawText(tick.textElement, x, y); - } - - TextDirection _normalizeHorizontalAnchor( - TickLabelAnchor anchor, bool isRtl, bool isFirst, bool isLast) { - switch (anchor) { - case TickLabelAnchor.before: - return isRtl ? TextDirection.ltr : TextDirection.rtl; - case TickLabelAnchor.after: - return isRtl ? TextDirection.rtl : TextDirection.ltr; - case TickLabelAnchor.inside: - if (isFirst) { - return TextDirection.ltr; - } - if (isLast) { - return TextDirection.rtl; - } - return TextDirection.center; - case TickLabelAnchor.centered: - default: - return TextDirection.center; - } - } - - _PixelVerticalDirection _normalizeVerticalAnchor( - TickLabelAnchor anchor, bool isFirst, bool isLast) { - switch (anchor) { - case TickLabelAnchor.before: - return _PixelVerticalDirection.under; - case TickLabelAnchor.after: - return _PixelVerticalDirection.over; - case TickLabelAnchor.inside: - if (isFirst) { - return _PixelVerticalDirection.over; - } - if (isLast) { - return _PixelVerticalDirection.under; - } - return _PixelVerticalDirection.center; - case TickLabelAnchor.centered: - default: - return _PixelVerticalDirection.center; - } - } -} - -enum _PixelVerticalDirection { - over, - center, - under, -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart b/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart deleted file mode 100644 index 4e6f06f9f..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart +++ /dev/null @@ -1,174 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart' show immutable, required; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../../common/line_style.dart' show LineStyle; -import '../../../../common/style/style_factory.dart' show StyleFactory; -import '../../../common/chart_canvas.dart' show ChartCanvas; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show AxisOrientation; -import '../spec/axis_spec.dart' - show TextStyleSpec, LineStyleSpec, TickLabelAnchor, TickLabelJustification; -import '../tick.dart' show Tick; -import 'base_tick_draw_strategy.dart' show BaseTickDrawStrategy; -import 'small_tick_draw_strategy.dart' show SmallTickRendererSpec; -import 'tick_draw_strategy.dart' show TickDrawStrategy; - -@immutable -class GridlineRendererSpec extends SmallTickRendererSpec { - const GridlineRendererSpec({ - TextStyleSpec labelStyle, - LineStyleSpec lineStyle, - LineStyleSpec axisLineStyle, - TickLabelAnchor labelAnchor, - TickLabelJustification labelJustification, - int tickLengthPx, - int labelOffsetFromAxisPx, - int labelOffsetFromTickPx, - int minimumPaddingBetweenLabelsPx, - }) : super( - labelStyle: labelStyle, - lineStyle: lineStyle, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, - tickLengthPx: tickLengthPx, - axisLineStyle: axisLineStyle); - - @override - TickDrawStrategy createDrawStrategy( - ChartContext context, GraphicsFactory graphicsFactory) => - GridlineTickDrawStrategy(context, graphicsFactory, - tickLengthPx: tickLengthPx, - lineStyleSpec: lineStyle, - labelStyleSpec: labelStyle, - axisLineStyleSpec: axisLineStyle, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is GridlineRendererSpec && super == (other)); - } - - @override - int get hashCode { - int hashcode = super.hashCode; - return hashcode; - } -} - -/// Draws line across chart draw area for each tick. -/// -/// Extends [BaseTickDrawStrategy]. -class GridlineTickDrawStrategy extends BaseTickDrawStrategy { - int tickLength; - LineStyle lineStyle; - - GridlineTickDrawStrategy( - ChartContext chartContext, - GraphicsFactory graphicsFactory, { - int tickLengthPx, - LineStyleSpec lineStyleSpec, - TextStyleSpec labelStyleSpec, - LineStyleSpec axisLineStyleSpec, - TickLabelAnchor labelAnchor, - TickLabelJustification labelJustification, - int labelOffsetFromAxisPx, - int labelOffsetFromTickPx, - int minimumPaddingBetweenLabelsPx, - }) : super(chartContext, graphicsFactory, - labelStyleSpec: labelStyleSpec, - axisLineStyleSpec: axisLineStyleSpec ?? lineStyleSpec, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx) { - lineStyle = - StyleFactory.style.createGridlineStyle(graphicsFactory, lineStyleSpec); - - this.tickLength = tickLengthPx ?? 0; - } - - @override - void draw(ChartCanvas canvas, Tick tick, - {@required AxisOrientation orientation, - @required Rectangle axisBounds, - @required Rectangle drawAreaBounds, - @required bool isFirst, - @required bool isLast}) { - Point lineStart; - Point lineEnd; - switch (orientation) { - case AxisOrientation.top: - final x = tick.locationPx; - lineStart = Point(x, axisBounds.bottom - tickLength); - lineEnd = Point(x, drawAreaBounds.bottom); - break; - case AxisOrientation.bottom: - final x = tick.locationPx; - lineStart = Point(x, drawAreaBounds.top + tickLength); - lineEnd = Point(x, axisBounds.top); - break; - case AxisOrientation.right: - final y = tick.locationPx; - if (tickLabelAnchor == TickLabelAnchor.after || - tickLabelAnchor == TickLabelAnchor.before) { - lineStart = Point(axisBounds.right, y); - } else { - lineStart = Point(axisBounds.left + tickLength, y); - } - lineEnd = Point(drawAreaBounds.left, y); - break; - case AxisOrientation.left: - final y = tick.locationPx; - - if (tickLabelAnchor == TickLabelAnchor.after || - tickLabelAnchor == TickLabelAnchor.before) { - lineStart = Point(axisBounds.left, y); - } else { - lineStart = Point(axisBounds.right - tickLength, y); - } - lineEnd = Point(drawAreaBounds.right, y); - break; - } - - canvas.drawLine( - points: [lineStart, lineEnd], - dashPattern: lineStyle.dashPattern, - fill: lineStyle.color, - stroke: lineStyle.color, - strokeWidthPx: lineStyle.strokeWidth.toDouble(), - ); - - drawLabel(canvas, tick, - orientation: orientation, - axisBounds: axisBounds, - drawAreaBounds: drawAreaBounds, - isFirst: isFirst, - isLast: isLast); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart b/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart deleted file mode 100644 index 82aa96aae..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart +++ /dev/null @@ -1,136 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart' show immutable, required; - -import '../../../../common/color.dart' show Color; -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../../common/line_style.dart' show LineStyle; -import '../../../../common/style/style_factory.dart' show StyleFactory; -import '../../../../common/text_style.dart' show TextStyle; -import '../../../common/chart_canvas.dart' show ChartCanvas; -import '../../../common/chart_context.dart' show ChartContext; -import '../../../layout/layout_view.dart' show ViewMeasuredSizes; -import '../axis.dart' show AxisOrientation; -import '../collision_report.dart' show CollisionReport; -import '../spec/axis_spec.dart' show RenderSpec, LineStyleSpec; -import '../tick.dart' show Tick; -import 'tick_draw_strategy.dart'; - -/// Renders no ticks no labels, and claims no space in layout. -/// However, it does render the axis line if asked to by the axis. -@immutable -class NoneRenderSpec extends RenderSpec { - final LineStyleSpec axisLineStyle; - - const NoneRenderSpec({this.axisLineStyle}); - - @override - TickDrawStrategy createDrawStrategy( - ChartContext context, GraphicsFactory graphicFactory) => - NoneDrawStrategy(context, graphicFactory, - axisLineStyleSpec: axisLineStyle); - - @override - bool operator ==(Object other) => - identical(this, other) || other is NoneRenderSpec; - - @override - int get hashCode => 0; -} - -class NoneDrawStrategy implements TickDrawStrategy { - LineStyle axisLineStyle; - TextStyle noneTextStyle; - - NoneDrawStrategy(ChartContext chartContext, GraphicsFactory graphicsFactory, - {LineStyleSpec axisLineStyleSpec}) { - axisLineStyle = StyleFactory.style - .createAxisLineStyle(graphicsFactory, axisLineStyleSpec); - noneTextStyle = graphicsFactory.createTextPaint() - ..color = Color.transparent - ..fontSize = 0; - } - - @override - CollisionReport collides(List ticks, AxisOrientation orientation) => - CollisionReport(ticksCollide: false, ticks: ticks); - - @override - void decorateTicks(List ticks) { - // Even though no text is rendered, the text style for each element should - // still be set to handle the case of the draw strategy being switched to - // a different draw strategy. The new draw strategy will try to animate - // the old ticks out and the text style property is used. - ticks.forEach((tick) => tick.textElement.textStyle = noneTextStyle); - } - - @override - void drawAxisLine(ChartCanvas canvas, AxisOrientation orientation, - Rectangle axisBounds) { - Point start; - Point end; - - switch (orientation) { - case AxisOrientation.top: - start = axisBounds.bottomLeft; - end = axisBounds.bottomRight; - - break; - case AxisOrientation.bottom: - start = axisBounds.topLeft; - end = axisBounds.topRight; - break; - case AxisOrientation.right: - start = axisBounds.topLeft; - end = axisBounds.bottomLeft; - break; - case AxisOrientation.left: - start = axisBounds.topRight; - end = axisBounds.bottomRight; - break; - } - - canvas.drawLine( - points: [start, end], - dashPattern: axisLineStyle.dashPattern, - fill: axisLineStyle.color, - stroke: axisLineStyle.color, - strokeWidthPx: axisLineStyle.strokeWidth.toDouble(), - ); - } - - @override - void draw(ChartCanvas canvas, Tick tick, - {@required AxisOrientation orientation, - @required Rectangle axisBounds, - @required Rectangle drawAreaBounds, - @required bool isFirst, - @required bool isLast}) {} - - @override - ViewMeasuredSizes measureHorizontallyDrawnTicks( - List ticks, int maxWidth, int maxHeight) { - return ViewMeasuredSizes(preferredWidth: 0, preferredHeight: 0); - } - - @override - ViewMeasuredSizes measureVerticallyDrawnTicks( - List ticks, int maxWidth, int maxHeight) { - return ViewMeasuredSizes(preferredWidth: 0, preferredHeight: 0); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart b/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart deleted file mode 100644 index 575779e35..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart +++ /dev/null @@ -1,168 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart' show immutable, required; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../../common/line_style.dart' show LineStyle; -import '../../../../common/style/style_factory.dart' show StyleFactory; -import '../../../common/chart_canvas.dart' show ChartCanvas; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show AxisOrientation; -import '../spec/axis_spec.dart' - show TextStyleSpec, LineStyleSpec, TickLabelAnchor, TickLabelJustification; -import '../tick.dart' show Tick; -import 'base_tick_draw_strategy.dart' show BaseRenderSpec, BaseTickDrawStrategy; -import 'tick_draw_strategy.dart' show TickDrawStrategy; - -/// -@immutable -class SmallTickRendererSpec extends BaseRenderSpec { - final LineStyleSpec lineStyle; - final int tickLengthPx; - - const SmallTickRendererSpec({ - TextStyleSpec labelStyle, - this.lineStyle, - LineStyleSpec axisLineStyle, - TickLabelAnchor labelAnchor, - TickLabelJustification labelJustification, - int labelOffsetFromAxisPx, - int labelOffsetFromTickPx, - this.tickLengthPx, - int minimumPaddingBetweenLabelsPx, - }) : super( - labelStyle: labelStyle, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, - axisLineStyle: axisLineStyle); - - @override - TickDrawStrategy createDrawStrategy( - ChartContext context, GraphicsFactory graphicsFactory) => - SmallTickDrawStrategy(context, graphicsFactory, - tickLengthPx: tickLengthPx, - lineStyleSpec: lineStyle, - labelStyleSpec: labelStyle, - axisLineStyleSpec: axisLineStyle, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is SmallTickRendererSpec && - lineStyle == other.lineStyle && - tickLengthPx == other.tickLengthPx && - super == (other)); - } - - @override - int get hashCode { - int hashcode = lineStyle?.hashCode ?? 0; - hashcode = (hashcode * 37) + tickLengthPx?.hashCode ?? 0; - hashcode = (hashcode * 37) + super.hashCode; - return hashcode; - } -} - -/// Draws small tick lines for each tick. Extends [BaseTickDrawStrategy]. -class SmallTickDrawStrategy extends BaseTickDrawStrategy { - int tickLength; - LineStyle lineStyle; - - SmallTickDrawStrategy( - ChartContext chartContext, - GraphicsFactory graphicsFactory, { - int tickLengthPx, - LineStyleSpec lineStyleSpec, - TextStyleSpec labelStyleSpec, - LineStyleSpec axisLineStyleSpec, - TickLabelAnchor labelAnchor, - TickLabelJustification labelJustification, - int labelOffsetFromAxisPx, - int labelOffsetFromTickPx, - int minimumPaddingBetweenLabelsPx, - }) : super(chartContext, graphicsFactory, - labelStyleSpec: labelStyleSpec, - axisLineStyleSpec: axisLineStyleSpec ?? lineStyleSpec, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx) { - this.tickLength = tickLengthPx ?? StyleFactory.style.tickLength; - lineStyle = - StyleFactory.style.createTickLineStyle(graphicsFactory, lineStyleSpec); - } - - @override - void draw(ChartCanvas canvas, Tick tick, - {@required AxisOrientation orientation, - @required Rectangle axisBounds, - @required Rectangle drawAreaBounds, - @required bool isFirst, - @required bool isLast}) { - Point tickStart; - Point tickEnd; - switch (orientation) { - case AxisOrientation.top: - double x = tick.locationPx; - tickStart = Point(x, axisBounds.bottom - tickLength); - tickEnd = Point(x, axisBounds.bottom); - break; - case AxisOrientation.bottom: - double x = tick.locationPx; - tickStart = Point(x, axisBounds.top); - tickEnd = Point(x, axisBounds.top + tickLength); - break; - case AxisOrientation.right: - double y = tick.locationPx; - - tickStart = Point(axisBounds.left, y); - tickEnd = Point(axisBounds.left + tickLength, y); - break; - case AxisOrientation.left: - double y = tick.locationPx; - - tickStart = Point(axisBounds.right - tickLength, y); - tickEnd = Point(axisBounds.right, y); - break; - } - - canvas.drawLine( - points: [tickStart, tickEnd], - dashPattern: lineStyle.dashPattern, - fill: lineStyle.color, - stroke: lineStyle.color, - strokeWidthPx: lineStyle.strokeWidth.toDouble(), - ); - - drawLabel(canvas, tick, - orientation: orientation, - axisBounds: axisBounds, - drawAreaBounds: drawAreaBounds, - isFirst: isFirst, - isLast: isLast); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart b/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart deleted file mode 100644 index 7824fb388..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart +++ /dev/null @@ -1,59 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart' show required; - -import '../../../common/chart_canvas.dart' show ChartCanvas; -import '../../../layout/layout_view.dart' show ViewMeasuredSizes; -import '../axis.dart' show AxisOrientation; -import '../collision_report.dart' show CollisionReport; -import '../tick.dart' show Tick; - -/// Strategy for drawing ticks and checking for collisions. -abstract class TickDrawStrategy { - /// Decorate the existing list of ticks. - /// - /// This can be used to further modify ticks after they have been generated - /// with location data and formatted labels. - void decorateTicks(List> ticks); - - /// Returns a [CollisionReport] indicating if there are any collisions. - CollisionReport collides(List> ticks, AxisOrientation orientation); - - /// Returns measurement of ticks drawn vertically. - ViewMeasuredSizes measureVerticallyDrawnTicks( - List> ticks, int maxWidth, int maxHeight); - - /// Returns measurement of ticks drawn horizontally. - ViewMeasuredSizes measureHorizontallyDrawnTicks( - List> ticks, int maxWidth, int maxHeight); - - /// Draws tick onto [ChartCanvas]. - /// - /// [orientation] the orientation of the axis that this [tick] belongs to. - /// [axisBounds] the bounds of the axis. - /// [drawAreaBounds] the bounds of the chart draw area adjacent to the axis. - void draw(ChartCanvas canvas, Tick tick, - {@required AxisOrientation orientation, - @required Rectangle axisBounds, - @required Rectangle drawAreaBounds, - @required bool isFirst, - @required bool isLast}); - - void drawAxisLine(ChartCanvas canvas, AxisOrientation orientation, - Rectangle axisBounds); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/end_points_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/end_points_tick_provider.dart deleted file mode 100644 index 8ba188f89..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/end_points_tick_provider.dart +++ /dev/null @@ -1,111 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/chart_context.dart' show ChartContext; -import 'axis.dart' show AxisOrientation; -import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import 'numeric_scale.dart' show NumericScale; -import 'ordinal_scale.dart' show OrdinalScale; -import 'scale.dart' show MutableScale; -import 'tick.dart' show Tick; -import 'tick_formatter.dart' show TickFormatter; -import 'tick_provider.dart' show BaseTickProvider, TickHint; -import 'time/date_time_scale.dart' show DateTimeScale; - -/// Tick provider that provides ticks at the two end points of the axis range. -class EndPointsTickProvider extends BaseTickProvider { - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required MutableScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - final ticks = >[]; - - // Check to see if the axis has been configured with some domain values. - // - // An un-configured axis has no domain step size, and its scale defaults to - // infinity. - if (scale.domainStepSize.abs() != double.infinity) { - final start = _getStartValue(tickHint, scale); - final end = _getEndValue(tickHint, scale); - - final labels = formatter.format([start, end], formatterValueCache, - stepSize: scale.domainStepSize); - - ticks.add(Tick( - value: start, - textElement: graphicsFactory.createTextElement(labels[0]), - locationPx: scale[start])); - - ticks.add(Tick( - value: end, - textElement: graphicsFactory.createTextElement(labels[1]), - locationPx: scale[end])); - - // Allow draw strategy to decorate the ticks. - tickDrawStrategy.decorateTicks(ticks); - } - - return ticks; - } - - /// Get the start value from the scale. - D _getStartValue(TickHint tickHint, MutableScale scale) { - Object start; - - if (tickHint != null) { - start = tickHint.start; - } else { - if (scale is NumericScale) { - start = (scale as NumericScale).viewportDomain.min; - } else if (scale is DateTimeScale) { - start = (scale as DateTimeScale).viewportDomain.start; - } else if (scale is OrdinalScale) { - start = (scale as OrdinalScale).domain.first; - } - } - - return start; - } - - /// Get the end value from the scale. - D _getEndValue(TickHint tickHint, MutableScale scale) { - Object end; - - if (tickHint != null) { - end = tickHint.end; - } else { - if (scale is NumericScale) { - end = (scale as NumericScale).viewportDomain.max; - } else if (scale is DateTimeScale) { - end = (scale as DateTimeScale).viewportDomain.end; - } else if (scale is OrdinalScale) { - end = (scale as OrdinalScale).domain.last; - } - } - - return end; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_axis.dart b/web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_axis.dart deleted file mode 100644 index 30f5d22c6..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_axis.dart +++ /dev/null @@ -1,75 +0,0 @@ -// 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 '../axis.dart' show NumericAxis; -import 'bucketing_numeric_tick_provider.dart' show BucketingNumericTickProvider; - -/// A numeric [Axis] that positions all values beneath a certain [threshold] -/// into a reserved space on the axis range. The label for the bucket line will -/// be drawn in the middle of the bucket range, rather than aligned with the -/// gridline for that value's position on the scale. -/// -/// An example illustration of a bucketing measure axis on a point chart -/// follows. In this case, values such as "6%" and "3%" are drawn in the bucket -/// of the axis, since they are less than the [threshold] value of 10%. -/// -/// 100% ┠───────────────────────── -/// ┃ * -/// ┃ * -/// 50% ┠──────*────────────────── -/// ┃ -/// ┠───────────────────────── -/// < 10% ┃ * * -/// ┗┯━━━━━━━━━━┯━━━━━━━━━━━┯━ -/// 0 50 100 -/// -/// This axis will format numbers as percents by default. -class BucketingNumericAxis extends NumericAxis { - /// All values smaller than the threshold will be bucketed into the same - /// position in the reserved space on the axis. - num _threshold; - - /// Whether or not measure values bucketed below the [threshold] should be - /// visible on the chart, or collapsed. - /// - /// If this is false, then any data with measure values smaller than - /// [threshold] will be rendered at the baseline of the chart. The - bool _showBucket; - - BucketingNumericAxis() : super(tickProvider: BucketingNumericTickProvider()); - - set threshold(num threshold) { - _threshold = threshold; - (tickProvider as BucketingNumericTickProvider).threshold = threshold; - } - - set showBucket(bool showBucket) { - _showBucket = showBucket; - (tickProvider as BucketingNumericTickProvider).showBucket = showBucket; - } - - /// Gets the location of [domain] on the axis, repositioning any value less - /// than [threshold] to the middle of the reserved bucket. - @override - double getLocation(num domain) { - if (domain == null) { - return null; - } else if (_threshold != null && domain < _threshold) { - return _showBucket ? scale[_threshold / 2] : scale[0.0]; - } else { - return scale[domain]; - } - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart deleted file mode 100644 index ed50a105f..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart +++ /dev/null @@ -1,151 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show AxisOrientation; -import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import '../numeric_scale.dart' show NumericScale; -import '../numeric_tick_provider.dart' show NumericTickProvider; -import '../tick.dart' show Tick; -import '../tick_formatter.dart' show SimpleTickFormatterBase, TickFormatter; -import '../tick_provider.dart' show TickHint; - -/// Tick provider that generates ticks for a [BucketingNumericAxis]. -/// -/// An example illustration of a bucketing measure axis on a point chart -/// follows. In this case, values such as "6%" and "3%" are drawn in the bucket -/// of the axis, since they are less than the [threshold] value of 10%. -/// -/// 100% ┠───────────────────────── -/// ┃ * -/// ┃ * -/// 50% ┠──────*────────────────── -/// ┃ -/// ┠───────────────────────── -/// < 10% ┃ * * -/// ┗┯━━━━━━━━━━┯━━━━━━━━━━━┯━ -/// 0 50 100 -/// -/// This tick provider will generate ticks using the same strategy as -/// [NumericTickProvider], except that any ticks that are smaller than -/// [threshold] will be hidden with an empty label. A special tick will be added -/// at the [threshold] position, with a label offset that moves its label down -/// to the middle of the bucket. -class BucketingNumericTickProvider extends NumericTickProvider { - /// All values smaller than the threshold will be bucketed into the same - /// position in the reserved space on the axis. - num _threshold; - - set threshold(num threshold) { - _threshold = threshold; - } - - /// Whether or not measure values bucketed below the [threshold] should be - /// visible on the chart, or collapsed. - bool _showBucket; - - set showBucket(bool showBucket) { - _showBucket = showBucket; - } - - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required NumericScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - if (_threshold == null) { - throw ('Bucketing threshold must be set before getting ticks.'); - } - - if (_showBucket == null) { - throw ('The showBucket flag must be set before getting ticks.'); - } - - final localFormatter = _BucketingFormatter() - ..threshold = _threshold - ..originalFormatter = formatter; - - final ticks = super.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: localFormatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - orientation: orientation, - viewportExtensionEnabled: viewportExtensionEnabled); - - assert(scale != null); - - // Create a tick for the threshold. - final thresholdTick = Tick( - value: _threshold, - textElement: graphicsFactory - .createTextElement(localFormatter.formatValue(_threshold)), - locationPx: _showBucket ? scale[_threshold] : scale[0], - labelOffsetPx: - _showBucket ? -0.5 * (scale[_threshold] - scale[0]) : 0.0); - tickDrawStrategy.decorateTicks(>[thresholdTick]); - - // Filter out ticks that sit below the threshold. - ticks.removeWhere( - (tick) => tick.value <= thresholdTick.value && tick.value != 0.0); - - // Finally, add our threshold tick to the list. - ticks.add(thresholdTick); - - // Make sure they are sorted by increasing value. - ticks.sort((a, b) { - if (a.value < b.value) { - return -1; - } else if (a.value > b.value) { - return 1; - } else { - return 0; - } - }); - - return ticks; - } -} - -class _BucketingFormatter extends SimpleTickFormatterBase { - /// All values smaller than the threshold will be formatted into an empty - /// string. - num threshold; - - SimpleTickFormatterBase originalFormatter; - - /// Formats a single tick value. - String formatValue(num value) { - if (value < threshold) { - return ''; - } else if (value == threshold) { - return '< ' + originalFormatter.formatValue(value); - } else { - return originalFormatter.formatValue(value); - } - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale.dart b/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale.dart deleted file mode 100644 index 71d1476cf..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale.dart +++ /dev/null @@ -1,246 +0,0 @@ -// 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 '../numeric_extents.dart' show NumericExtents; -import '../numeric_scale.dart' show NumericScale; -import '../scale.dart' show RangeBandConfig, ScaleOutputExtent, StepSizeConfig; -import 'linear_scale_domain_info.dart' show LinearScaleDomainInfo; -import 'linear_scale_function.dart' show LinearScaleFunction; -import 'linear_scale_viewport.dart' show LinearScaleViewportSettings; - -/// [NumericScale] that lays out the domain linearly across the range. -/// -/// A [Scale] which converts numeric domain units to a given numeric range units -/// linearly (as opposed to other methods like log scales). This is used to map -/// the domain's values to the available pixel range of the chart using the -/// apply method. -/// -///

The domain extent of the scale are determined by adding all domain -/// values to the scale. It can, however, be overwritten by calling -/// [domainOverride] to define the extent of the data. -/// -///

The scale can be zoomed & panned by calling either [setViewportSettings] -/// with a zoom and translate, or by setting [viewportExtent] with the domain -/// extent to show in the output range. -/// -///

[rangeBandConfig]: By default, this scale will map the domain extent -/// exactly to the output range in a simple ratio mapping. If a -/// [RangeBandConfig] other than NONE is used to define the width of bar groups, -/// then the scale calculation may be altered to that there is a half a stepSize -/// at the start and end of the range to ensure that a bar group can be shown -/// and centered on the scale's result. -/// -///

[stepSizeConfig]: By default, this scale will calculate the stepSize as -/// being auto detected using the minimal distance between two consecutive -/// datum. If you don't assign a [RangeBandConfig], then changing the -/// [stepSizeConfig] is a no-op. -class LinearScale implements NumericScale { - final LinearScaleDomainInfo _domainInfo; - final LinearScaleViewportSettings _viewportSettings; - final LinearScaleFunction _scaleFunction = LinearScaleFunction(); - - RangeBandConfig rangeBandConfig = const RangeBandConfig.none(); - StepSizeConfig stepSizeConfig = const StepSizeConfig.auto(); - - bool _scaleReady = false; - - LinearScale() - : _domainInfo = LinearScaleDomainInfo(), - _viewportSettings = LinearScaleViewportSettings(); - - LinearScale._copy(LinearScale other) - : _domainInfo = LinearScaleDomainInfo.copy(other._domainInfo), - _viewportSettings = - LinearScaleViewportSettings.copy(other._viewportSettings), - rangeBandConfig = other.rangeBandConfig, - stepSizeConfig = other.stepSizeConfig; - - @override - LinearScale copy() => LinearScale._copy(this); - - // - // Domain methods - // - - @override - addDomain(num domainValue) { - _domainInfo.addDomainValue(domainValue); - } - - @override - resetDomain() { - _scaleReady = false; - _domainInfo.reset(); - } - - @override - resetViewportSettings() { - _viewportSettings.reset(); - } - - @override - NumericExtents get dataExtent => - NumericExtents(_domainInfo.dataDomainStart, _domainInfo.dataDomainEnd); - - @override - num get minimumDomainStep => _domainInfo.minimumDetectedDomainStep; - - @override - bool canTranslate(_) => true; - - @override - set domainOverride(NumericExtents domainMaxExtent) { - _domainInfo.domainOverride = domainMaxExtent; - } - - get domainOverride => _domainInfo.domainOverride; - - @override - int compareDomainValueToViewport(num domainValue) { - NumericExtents dataExtent = _viewportSettings.domainExtent != null - ? _viewportSettings.domainExtent - : _domainInfo.extent; - return dataExtent.compareValue(domainValue); - } - - // - // Viewport methods - // - - @override - setViewportSettings(double viewportScale, double viewportTranslatePx) { - _viewportSettings - ..scalingFactor = viewportScale - ..translatePx = viewportTranslatePx - ..domainExtent = null; - _scaleReady = false; - } - - @override - double get viewportScalingFactor => _viewportSettings.scalingFactor; - - @override - double get viewportTranslatePx => _viewportSettings.translatePx; - - @override - set viewportDomain(NumericExtents extent) { - _scaleReady = false; - _viewportSettings.domainExtent = extent; - } - - @override - NumericExtents get viewportDomain { - _configureScale(); - return _viewportSettings.domainExtent; - } - - @override - set keepViewportWithinData(bool autoAdjustViewportToNiceValues) { - _scaleReady = false; - _viewportSettings.keepViewportWithinData = true; - } - - @override - bool get keepViewportWithinData => _viewportSettings.keepViewportWithinData; - - @override - double computeViewportScaleFactor(double domainWindow) => - _domainInfo.domainDiff / domainWindow; - - @override - set range(ScaleOutputExtent extent) { - _viewportSettings.range = extent; - _scaleReady = false; - } - - @override - ScaleOutputExtent get range => _viewportSettings.range; - - // - // Scale application methods - // - - @override - num operator [](num domainValue) { - _configureScale(); - return _scaleFunction[domainValue]; - } - - @override - num reverse(double viewPixels) { - _configureScale(); - final num domain = _scaleFunction.reverse(viewPixels); - return domain; - } - - @override - double get rangeBand { - _configureScale(); - return _scaleFunction.rangeBandPixels; - } - - @override - double get stepSize { - _configureScale(); - return _scaleFunction.stepSizePixels; - } - - @override - double get domainStepSize => _domainInfo.minimumDetectedDomainStep.toDouble(); - - @override - int get rangeWidth => (range.end - range.start).abs().toInt(); - - @override - bool isRangeValueWithinViewport(double rangeValue) => - range.containsValue(rangeValue); - - // - // Private update - // - - _configureScale() { - if (_scaleReady) return; - - assert(_viewportSettings.range != null); - - // If the viewport's domainExtent are set, then we can calculate the - // viewport's scaleFactor now that the domainInfo has been loaded. - // The viewport also has a chance to correct the scaleFactor. - _viewportSettings.updateViewportScaleFactor(_domainInfo); - // Now that the viewport's scalingFactor is setup, set it on the scale - // function. - _scaleFunction.updateScaleFactor( - _viewportSettings, _domainInfo, rangeBandConfig, stepSizeConfig); - - // If the viewport's domainExtent are set, then we can calculate the - // viewport's translate now that the scaleFactor has been loaded. - // The viewport also has a chance to correct the translate. - _viewportSettings.updateViewportTranslatePx( - _domainInfo, _scaleFunction.scalingFactor); - // Now that the viewport has a chance to update the translate, set it on the - // scale function. - _scaleFunction.updateTranslateAndRangeBand( - _viewportSettings, _domainInfo, rangeBandConfig); - - // Now that the viewport's scaleFactor and translate have been updated - // set the effective domainExtent of the viewport. - _viewportSettings.updateViewportDomainExtent( - _domainInfo, _scaleFunction.scalingFactor); - - // Cached computed values are updated. - _scaleReady = true; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_domain_info.dart b/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_domain_info.dart deleted file mode 100644 index 6e37515b0..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_domain_info.dart +++ /dev/null @@ -1,118 +0,0 @@ -// 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 '../numeric_extents.dart' show NumericExtents; - -/// Encapsulation of all the domain processing logic for the [LinearScale]. -class LinearScaleDomainInfo { - /// User (or axis) overridden extent in domain units. - NumericExtents domainOverride; - - /// The minimum added domain value. - num _dataDomainStart = double.infinity; - num get dataDomainStart => _dataDomainStart; - - /// The maximum added domain value. - num _dataDomainEnd = double.negativeInfinity; - num get dataDomainEnd => _dataDomainEnd; - - /// Previous domain added so we can calculate minimumDetectedDomainStep. - num _previouslyAddedDomain; - - /// The step size between data points in domain units. - /// - /// Measured as the minimum distance between consecutive added points. - num _minimumDetectedDomainStep = double.infinity; - num get minimumDetectedDomainStep => _minimumDetectedDomainStep; - - ///The diff of the nicedDomain extent. - num get domainDiff => extent.width; - - LinearScaleDomainInfo(); - - LinearScaleDomainInfo.copy(LinearScaleDomainInfo other) { - if (other.domainOverride != null) { - domainOverride = other.domainOverride; - } - _dataDomainStart = other._dataDomainStart; - _dataDomainEnd = other._dataDomainEnd; - _previouslyAddedDomain = other._previouslyAddedDomain; - _minimumDetectedDomainStep = other._minimumDetectedDomainStep; - } - - /// Resets everything back to initial state. - void reset() { - _previouslyAddedDomain = null; - _dataDomainStart = double.infinity; - _dataDomainEnd = double.negativeInfinity; - _minimumDetectedDomainStep = double.infinity; - } - - /// Updates the domain extent and detected step size given the [domainValue]. - void addDomainValue(num domainValue) { - if (domainValue == null || !domainValue.isFinite) { - return; - } - - extendDomain(domainValue); - - if (_previouslyAddedDomain != null) { - final domainStep = (domainValue - _previouslyAddedDomain).abs(); - if (domainStep != 0.0 && domainStep < minimumDetectedDomainStep) { - _minimumDetectedDomainStep = domainStep; - } - } - _previouslyAddedDomain = domainValue; - } - - /// Extends the data domain extent without modifying step size detection. - /// - /// Returns whether the the domain interval was extended. If the domain value - /// was already contained in the domain interval, the domain interval does not - /// change. - bool extendDomain(num domainValue) { - if (domainValue == null || !domainValue.isFinite) { - return false; - } - - bool domainExtended = false; - if (domainValue < _dataDomainStart) { - _dataDomainStart = domainValue; - domainExtended = true; - } - if (domainValue > _dataDomainEnd) { - _dataDomainEnd = domainValue; - domainExtended = true; - } - return domainExtended; - } - - /// Returns the extent based on the current domain range and overrides. - NumericExtents get extent { - num tmpDomainStart; - num tmpDomainEnd; - if (domainOverride != null) { - // override was set. - tmpDomainStart = domainOverride.min; - tmpDomainEnd = domainOverride.max; - } else { - // domainEnd is less than domainStart if no domain values have been set. - tmpDomainStart = _dataDomainStart.isFinite ? _dataDomainStart : 0.0; - tmpDomainEnd = _dataDomainEnd.isFinite ? _dataDomainEnd : 1.0; - } - - return NumericExtents(tmpDomainStart, tmpDomainEnd); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_function.dart b/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_function.dart deleted file mode 100644 index 57a659487..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_function.dart +++ /dev/null @@ -1,201 +0,0 @@ -// 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 '../scale.dart' - show RangeBandConfig, RangeBandType, StepSizeConfig, StepSizeType; -import 'linear_scale_domain_info.dart' show LinearScaleDomainInfo; -import 'linear_scale_viewport.dart' show LinearScaleViewportSettings; - -/// Component of the LinearScale which actually handles the apply and reverse -/// function of the scale. -class LinearScaleFunction { - /// Cached rangeBand width in pixels given the RangeBandConfig and the current - /// domain & range. - double rangeBandPixels = 0.0; - - /// Cached amount in domain units to shift the input value as a part of - /// translation. - num domainTranslate = 0.0; - - /// Cached translation ratio for scale translation. - double scalingFactor = 1.0; - - /// Cached amount in pixel units to shift the output value as a part of - /// translation. - double rangeTranslate = 0.0; - - /// The calculated step size given the step size config. - double stepSizePixels = 0.0; - - /// Translates the given domainValue to the range output. - double operator [](num domainValue) { - return (((domainValue + domainTranslate) * scalingFactor) + rangeTranslate) - .toDouble(); - } - - /// Translates the given range output back to a domainValue. - double reverse(double viewPixels) { - return ((viewPixels - rangeTranslate) / scalingFactor) - domainTranslate; - } - - /// Update the scale function's scaleFactor given the current state of the - /// viewport. - void updateScaleFactor( - LinearScaleViewportSettings viewportSettings, - LinearScaleDomainInfo domainInfo, - RangeBandConfig rangeBandConfig, - StepSizeConfig stepSizeConfig) { - double rangeDiff = viewportSettings.range.diff.toDouble(); - // Note: if you provided a nicing function that extends the domain, we won't - // muck with the extended side. - bool hasHalfStepAtStart = - domainInfo.extent.min == domainInfo.dataDomainStart; - bool hasHalfStepAtEnd = domainInfo.extent.max == domainInfo.dataDomainEnd; - - // Determine the stepSize and reserved range values. - // The percentage of the step reserved from the scale's range due to the - // possible half step at the start and end. - double reservedRangePercentOfStep = - getStepReservationPercent(hasHalfStepAtStart, hasHalfStepAtEnd); - _updateStepSizeAndScaleFactor(viewportSettings, domainInfo, rangeDiff, - reservedRangePercentOfStep, rangeBandConfig, stepSizeConfig); - } - - /// Returns the percentage of the step reserved from the output range due to - /// maybe having to hold half stepSizes on the start and end of the output. - double getStepReservationPercent( - bool hasHalfStepAtStart, bool hasHalfStepAtEnd) { - if (!hasHalfStepAtStart && !hasHalfStepAtEnd) { - return 0.0; - } - if (hasHalfStepAtStart && hasHalfStepAtEnd) { - return 1.0; - } - return 0.5; - } - - /// Updates the scale function's translate and rangeBand given the current - /// state of the viewport. - void updateTranslateAndRangeBand(LinearScaleViewportSettings viewportSettings, - LinearScaleDomainInfo domainInfo, RangeBandConfig rangeBandConfig) { - // Assign the rangeTranslate using the current viewportSettings.translatePx - // and diffs. - if (domainInfo.domainDiff == 0) { - // Translate it to the center of the range. - rangeTranslate = - viewportSettings.range.start + (viewportSettings.range.diff / 2); - } else { - bool hasHalfStepAtStart = - domainInfo.extent.min == domainInfo.dataDomainStart; - // The pixel shift of the scale function due to the half a step at the - // beginning. - double reservedRangePixelShift = - hasHalfStepAtStart ? (stepSizePixels / 2.0) : 0.0; - - rangeTranslate = (viewportSettings.range.start + - viewportSettings.translatePx + - reservedRangePixelShift); - } - - // We need to subtract the start from any incoming domain to apply the - // scale, so flip its sign. - domainTranslate = -1 * domainInfo.extent.min; - - // Update the rangeBand size. - rangeBandPixels = _calculateRangeBandSize(rangeBandConfig); - } - - /// Calculates and stores the current rangeBand given the config and current - /// step size. - double _calculateRangeBandSize(RangeBandConfig rangeBandConfig) { - switch (rangeBandConfig.type) { - case RangeBandType.fixedDomain: - return rangeBandConfig.size * scalingFactor; - case RangeBandType.fixedPixel: - return rangeBandConfig.size; - case RangeBandType.fixedPixelSpaceFromStep: - return stepSizePixels - rangeBandConfig.size; - case RangeBandType.styleAssignedPercentOfStep: - case RangeBandType.fixedPercentOfStep: - return stepSizePixels * rangeBandConfig.size; - case RangeBandType.none: - return 0.0; - } - return 0.0; - } - - /// Calculates and Stores the current step size and scale factor together, - /// given the viewport, domain, and config. - /// - ///

Scale factor and step size are related closely and should be calculated - /// together so that we do not lose accuracy due to double arithmetic. - void _updateStepSizeAndScaleFactor( - LinearScaleViewportSettings viewportSettings, - LinearScaleDomainInfo domainInfo, - double rangeDiff, - double reservedRangePercentOfStep, - RangeBandConfig rangeBandConfig, - StepSizeConfig stepSizeConfig) { - final domainDiff = domainInfo.domainDiff; - - // If we are going to have any rangeBands, then ensure that we account for - // needed space on the beginning and end of the range. - if (rangeBandConfig.type != RangeBandType.none) { - switch (stepSizeConfig.type) { - case StepSizeType.autoDetect: - double minimumDetectedDomainStep = - domainInfo.minimumDetectedDomainStep.toDouble(); - if (minimumDetectedDomainStep != null && - minimumDetectedDomainStep.isFinite) { - scalingFactor = viewportSettings.scalingFactor * - (rangeDiff / - (domainDiff + - (minimumDetectedDomainStep * - reservedRangePercentOfStep))); - stepSizePixels = (minimumDetectedDomainStep * scalingFactor); - } else { - stepSizePixels = rangeDiff.abs(); - scalingFactor = 1.0; - } - return; - case StepSizeType.fixedPixels: - stepSizePixels = stepSizeConfig.size; - double reservedRangeForStepPixels = - stepSizePixels * reservedRangePercentOfStep; - scalingFactor = domainDiff == 0 - ? 1.0 - : viewportSettings.scalingFactor * - (rangeDiff - reservedRangeForStepPixels) / - domainDiff; - return; - case StepSizeType.fixedDomain: - double domainStepWidth = stepSizeConfig.size; - double totalDomainDiff = - (domainDiff + (domainStepWidth * reservedRangePercentOfStep)); - scalingFactor = totalDomainDiff == 0 - ? 1.0 - : viewportSettings.scalingFactor * (rangeDiff / totalDomainDiff); - stepSizePixels = domainStepWidth * scalingFactor; - return; - } - } - - // If no cases matched, use zero step size. - stepSizePixels = 0.0; - scalingFactor = domainDiff == 0 - ? 1.0 - : viewportSettings.scalingFactor * rangeDiff / domainDiff; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_viewport.dart b/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_viewport.dart deleted file mode 100644 index d01d6034e..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_viewport.dart +++ /dev/null @@ -1,141 +0,0 @@ -// 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' as math show max, min; - -import '../numeric_extents.dart' show NumericExtents; -import '../scale.dart' show ScaleOutputExtent; -import 'linear_scale_domain_info.dart' show LinearScaleDomainInfo; - -/// Component of the LinearScale responsible for the configuration and -/// calculations of the viewport. -class LinearScaleViewportSettings { - /// Output extent for the scale, typically set by the axis as the pixel - /// output. - ScaleOutputExtent range; - - /// Determines whether the scale should be extended to the nice values - /// provided by the tick provider. If true, we wont touch the viewport config - /// since the axis will configure it, if false, we will still ensure sane zoom - /// and translates. - bool keepViewportWithinData = true; - - /// User configured viewport scale as a zoom multiplier where 1.0 is - /// 100% (default) and 2.0 is 200% zooming in making the data take up twice - /// the space (showing half as much data in the viewport). - double scalingFactor = 1.0; - - /// User configured viewport translate in pixel units. - double translatePx = 0.0; - - /// The current extent of the viewport in domain units. - NumericExtents _domainExtent; - set domainExtent(NumericExtents extent) { - _domainExtent = extent; - _manualDomainExtent = extent != null; - } - - NumericExtents get domainExtent => _domainExtent; - - /// Indicates that the viewportExtends are to be read from to determine the - /// internal scaleFactor and rangeTranslate. - - bool _manualDomainExtent = false; - - LinearScaleViewportSettings(); - - LinearScaleViewportSettings.copy(LinearScaleViewportSettings other) { - range = other.range; - keepViewportWithinData = other.keepViewportWithinData; - scalingFactor = other.scalingFactor; - translatePx = other.translatePx; - _manualDomainExtent = other._manualDomainExtent; - _domainExtent = other._domainExtent; - } - - /// Resets the viewport calculated fields back to their initial settings. - void reset() { - // Likely an auto assigned viewport (niced), so reset it between draws. - scalingFactor = 1.0; - translatePx = 0.0; - domainExtent = null; - } - - int get rangeWidth => range.diff.abs().toInt(); - - bool isRangeValueWithinViewport(double rangeValue) => - range.containsValue(rangeValue); - - /// Updates the viewport's internal scalingFactor given the current - /// domainInfo. - void updateViewportScaleFactor(LinearScaleDomainInfo domainInfo) { - // If we are loading from the viewport, then update the scalingFactor given - // the viewport size compared to the data size. - if (_manualDomainExtent) { - double viewportDomainDiff = _domainExtent?.width?.toDouble(); - if (domainInfo.domainDiff != 0.0) { - scalingFactor = domainInfo.domainDiff / viewportDomainDiff; - } else { - scalingFactor = 1.0; - // The domain claims to have no date, extend it to the viewport's - domainInfo.extendDomain(_domainExtent?.min); - domainInfo.extendDomain(_domainExtent?.max); - } - } - - // Make sure that the viewportSettings.scalingFactor is sane if desired. - if (!keepViewportWithinData) { - // Make sure we don't zoom out beyond the max domain extent. - scalingFactor = math.max(1.0, scalingFactor); - } - } - - /// Updates the viewport's internal translate given the current domainInfo and - /// main scalingFactor from LinearScaleFunction (not internal scalingFactor). - void updateViewportTranslatePx( - LinearScaleDomainInfo domainInfo, double scaleScalingFactor) { - // If we are loading from the viewport, then update the translate now that - // the scaleFactor has been setup. - if (_manualDomainExtent) { - translatePx = (-1.0 * - scaleScalingFactor * - (_domainExtent.min - domainInfo.extent.min)); - } - - // Make sure that the viewportSettings.translatePx is sane if desired. - if (!keepViewportWithinData) { - int rangeDiff = range.diff.toInt(); - - // Make sure we don't translate beyond the max domain extent. - translatePx = math.min(0.0, translatePx); - translatePx = math.max(rangeDiff * (1.0 - scalingFactor), translatePx); - } - } - - /// Calculates and stores the viewport's domainExtent if we did not load from - /// them in the first place. - void updateViewportDomainExtent( - LinearScaleDomainInfo domainInfo, double scaleScalingFactor) { - // If we didn't load from the viewport extent, then update them given the - // current scale configuration. - if (!_manualDomainExtent) { - double viewportDomainDiff = domainInfo.domainDiff / scalingFactor; - double viewportStart = - (-1.0 * translatePx / scaleScalingFactor) + domainInfo.extent.min; - _domainExtent = - NumericExtents(viewportStart, viewportStart + viewportDomainDiff); - } - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/numeric_extents.dart b/web/charts/common/lib/src/chart/cartesian/axis/numeric_extents.dart deleted file mode 100644 index fc923e331..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/numeric_extents.dart +++ /dev/null @@ -1,105 +0,0 @@ -// 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 'scale.dart' show Extents; - -/// Represents the starting and ending extent of a dataset. -class NumericExtents implements Extents { - final num min; - final num max; - - /// Precondition: [min] <= [max]. - // TODO: When initializer list asserts are supported everywhere, - // add the precondition as an initializer list assert. This is supported in - // Flutter only. - const NumericExtents(this.min, this.max); - - /// Returns [Extents] based on the min and max of the given values. - /// Returns [NumericExtents.empty] if [values] are empty - factory NumericExtents.fromValues(Iterable values) { - if (values.isEmpty) { - return NumericExtents.empty; - } - var min = values.first; - var max = values.first; - for (final value in values) { - if (value < min) { - min = value; - } else if (max < value) { - max = value; - } - } - return NumericExtents(min, max); - } - - /// Returns the union of this and other. - NumericExtents plus(NumericExtents other) { - if (min <= other.min) { - if (max >= other.max) { - return this; - } else { - return NumericExtents(min, other.max); - } - } else { - if (other.max >= max) { - return other; - } else { - return NumericExtents(other.min, max); - } - } - } - - /// Compares the given [value] against the extents. - /// - /// Returns -1 if the value is less than the extents. - /// Returns 0 if the value is within the extents inclusive. - /// Returns 1 if the value is greater than the extents. - int compareValue(num value) { - if (value < min) { - return -1; - } - if (value > max) { - return 1; - } - return 0; - } - - bool _containsValue(double value) => compareValue(value) == 0; - - // Returns true if these [NumericExtents] collides with [other]. - bool overlaps(NumericExtents other) { - return _containsValue(other.min) || - _containsValue(other.max) || - other._containsValue(min) || - other._containsValue(max); - } - - @override - bool operator ==(other) { - return other is NumericExtents && min == other.min && max == other.max; - } - - @override - int get hashCode => (min.hashCode + (max.hashCode * 31)); - - num get width => max - min; - - @override - String toString() => 'Extent($min, $max)'; - - static const NumericExtents unbounded = - NumericExtents(double.negativeInfinity, double.infinity); - static const NumericExtents empty = NumericExtents(0.0, 0.0); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/numeric_scale.dart b/web/charts/common/lib/src/chart/cartesian/axis/numeric_scale.dart deleted file mode 100644 index 1007a8314..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/numeric_scale.dart +++ /dev/null @@ -1,57 +0,0 @@ -// 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 'numeric_extents.dart' show NumericExtents; -import 'scale.dart' show MutableScale; - -/// Scale used to convert numeric domain input units to output range units. -/// -/// The input represents a continuous numeric domain which maps to a given range -/// output. This is used to map the domain's values to the available pixel -/// range of the chart. -abstract class NumericScale extends MutableScale { - /// Keeps the scale and translate sane if true (default). - /// - /// Setting this to false disables some pan/zoom protections that prevent you - /// from going beyond the data extent. - bool get keepViewportWithinData; - set keepViewportWithinData(bool keep); - - /// Returns the extent of the actual data (not the viewport max). - NumericExtents get dataExtent; - - /// Returns the minimum step size of the actual data. - num get minimumDomainStep; - - /// Overrides the domain extent if set, null otherwise. - /// - /// Overrides the extent of the actual data to lie about the range of the - /// data so that panning has a start and end point to go between beyond the - /// received data. This allows lazy loading of data into the gaps in the - /// expanded lied about areas. - NumericExtents get domainOverride; - set domainOverride(NumericExtents extent); - - /// Returns the domain extent visible in the viewport of the drawArea. - NumericExtents get viewportDomain; - - /// Sets the domain extent visible in the viewport of the drawArea. - /// - /// Invalidates the viewportScale & viewportTranslatePx. - set viewportDomain(NumericExtents extent); - - /// Returns the viewportScaleFactor needed to present the given domainWindow. - double computeViewportScaleFactor(double domainWindow); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/numeric_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/numeric_tick_provider.dart deleted file mode 100644 index 56c177e24..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/numeric_tick_provider.dart +++ /dev/null @@ -1,584 +0,0 @@ -// 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 log, log10e, max, min, pow; - -import 'package:meta/meta.dart' show required; - -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/chart_context.dart' show ChartContext; -import '../../common/unitconverter/identity_converter.dart' - show IdentityConverter; -import '../../common/unitconverter/unit_converter.dart' show UnitConverter; -import 'axis.dart' show AxisOrientation; -import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import 'numeric_extents.dart' show NumericExtents; -import 'numeric_scale.dart' show NumericScale; -import 'tick.dart' show Tick; -import 'tick_formatter.dart' show TickFormatter; -import 'tick_provider.dart' show BaseTickProvider, TickHint; - -/// Tick provider that allows you to specify how many ticks to present while -/// also choosing tick values that appear "nice" or "rounded" to the user. By -/// default it will try to guess an appropriate number of ticks given the size -/// of the range available, but the min and max tick counts can be set by -/// calling setTickCounts(). -/// -/// You can control whether the axis is bound to zero (default) or follows the -/// data by calling setZeroBound(). -/// -/// This provider will choose "nice" ticks with the following priority order. -/// * Ticks do not collide with each other. -/// * Alternate rendering is not used to avoid collisions. -/// * Provide the least amount of domain range covering all data points (while -/// still selecting "nice" ticks values. -class NumericTickProvider extends BaseTickProvider { - /// Used to determine the automatic tick count calculation. - static const MIN_DIPS_BETWEEN_TICKS = 25; - - /// Potential steps available to the baseTen value of the data. - static const DEFAULT_STEPS = [ - 0.01, - 0.02, - 0.025, - 0.03, - 0.04, - 0.05, - 0.06, - 0.07, - 0.08, - 0.09, - 0.1, - 0.2, - 0.25, - 0.3, - 0.4, - 0.5, - 0.6, - 0.7, - 0.8, - 0.9, - 1.0, - 2.0, - 2.50, - 3.0, - 4.0, - 5.0, - 6.0, - 7.0, - 8.0, - 9.0 - ]; - - // Settings - - /// Sets whether the the tick provider should always include a zero tick. - /// - /// If set the data range may be extended to include zero. - /// - /// Note that the zero value in axis units is chosen, which may be different - /// than zero value in data units if a data to axis unit converter is set. - bool zeroBound = true; - - /// If your data can only be in whole numbers, then set this to true. - /// - /// It should prevent the scale from choosing fractional ticks. For example, - /// if you had a office head count, don't generate a tick for 1.5, instead - /// jump to 2. - /// - /// Note that the provider will choose whole number ticks in the axis units, - /// not data units if a data to axis unit converter is set. - bool dataIsInWholeNumbers = true; - - // Desired min and max tick counts are set by [setFixedTickCount] and - // [setTickCount]. These are not guaranteed tick counts. - int _desiredMaxTickCount; - int _desiredMinTickCount; - - /// Allowed steps the tick provider can choose from. - var _allowedSteps = DEFAULT_STEPS; - - /// Convert input data units to the desired units on the axis. - /// If not set no conversion will take place. - /// - /// Combining this with an appropriate [TickFormatter] would result in axis - /// ticks that are in different unit than the actual data units. - UnitConverter dataToAxisUnitConverter = - const IdentityConverter(); - - // Tick calculation state - num _low; - num _high; - int _rangeWidth; - int _minTickCount; - int _maxTickCount; - - // The parameters used in previous tick calculation - num _prevLow; - num _prevHigh; - int _prevRangeWidth; - int _prevMinTickCount; - int _prevMaxTickCount; - bool _prevDataIsInWholeNumbers; - - /// Sets the desired tick count. - /// - /// While the provider will try to satisfy the requirement, it is not - /// guaranteed, such as cases where ticks may overlap or are insufficient. - /// - /// [tickCount] the fixed number of major (labeled) ticks to draw for the axis - /// Passing null will result in falling back on the automatic tick count - /// assignment. - void setFixedTickCount(int tickCount) { - // Don't allow a single tick, it doesn't make sense. so tickCount > 1 - _desiredMinTickCount = - tickCount != null && tickCount > 1 ? tickCount : null; - _desiredMaxTickCount = _desiredMinTickCount; - } - - /// Sets the desired min and max tick count when providing ticks. - /// - /// The values are suggested requirements but are not guaranteed to be the - /// actual tick count in cases where it is not possible. - /// - /// [maxTickCount] The max tick count must be greater than 1. - /// [minTickCount] The min tick count must be greater than 1. - void setTickCount(int maxTickCount, int minTickCount) { - // Don't allow a single tick, it doesn't make sense. so tickCount > 1 - if (maxTickCount != null && maxTickCount > 1) { - _desiredMaxTickCount = maxTickCount; - if (minTickCount != null && - minTickCount > 1 && - minTickCount <= _desiredMaxTickCount) { - _desiredMinTickCount = minTickCount; - } else { - _desiredMinTickCount = 2; - } - } else { - _desiredMaxTickCount = null; - _desiredMinTickCount = null; - } - } - - /// Sets the allowed step sizes this tick provider can choose from. - /// - /// All ticks will be a power of 10 multiple of the given step sizes. - /// - /// Note that if only very few step sizes are allowed the tick range maybe - /// much bigger than the data range. - /// - /// The step sizes setup here apply in axis units, which is different than - /// input units if a data to axis unit converter is set. - /// - /// [steps] allowed step sizes in the [1, 10) range. - set allowedSteps(List steps) { - assert(steps != null && steps.isNotEmpty); - steps.sort(); - - final stepSet = Set.from(steps); - _allowedSteps = List(stepSet.length * 3); - int stepIndex = 0; - for (double step in stepSet) { - assert(1.0 <= step && step < 10.0); - _allowedSteps[stepIndex] = _removeRoundingErrors(step / 100); - _allowedSteps[stepSet.length + stepIndex] = - _removeRoundingErrors(step / 10).toDouble(); - _allowedSteps[2 * stepSet.length + stepIndex] = - _removeRoundingErrors(step); - stepIndex++; - } - } - - List> _getTicksFromHint({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required NumericScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required TickHint tickHint, - }) { - final stepSize = (tickHint.end - tickHint.start) / (tickHint.tickCount - 1); - // Find the first tick that is greater than or equal to the min - // viewportDomain. - final tickZeroShift = tickHint.start - - (stepSize * - (tickHint.start >= 0 - ? (tickHint.start / stepSize).floor() - : (tickHint.start / stepSize).ceil())); - final tickStart = - (scale.viewportDomain.min / stepSize).ceil() * stepSize + tickZeroShift; - final stepInfo = _TickStepInfo(stepSize.abs(), tickStart); - final tickValues = _getTickValues(stepInfo, tickHint.tickCount); - - // Create ticks from domain values. - return createTicks(tickValues, - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - stepSize: stepInfo.stepSize); - } - - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required NumericScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - List> ticks; - - _rangeWidth = scale.rangeWidth; - _updateDomainExtents(scale.viewportDomain); - - // Bypass searching for a tick range since we are getting ticks using - // information in [tickHint]. - if (tickHint != null) { - return _getTicksFromHint( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - tickHint: tickHint, - ); - } - - if (_hasTickParametersChanged() || ticks == null) { - var selectedTicksRange = double.maxFinite; - var foundPreferredTicks = false; - var viewportDomain = scale.viewportDomain; - final axisUnitsHigh = dataToAxisUnitConverter.convert(_high); - final axisUnitsLow = dataToAxisUnitConverter.convert(_low); - - _updateTickCounts(axisUnitsHigh, axisUnitsLow); - - // Only create a copy of the scale if [viewportExtensionEnabled]. - NumericScale mutableScale = - viewportExtensionEnabled ? scale.copy() : null; - - // Walk to available tick count from max to min looking for the first one - // that gives you the least amount of range used. If a non colliding tick - // count is not found use the min tick count to generate ticks. - for (int tickCount = _maxTickCount; - tickCount >= _minTickCount; - tickCount--) { - final stepInfo = - _getStepsForTickCount(tickCount, axisUnitsHigh, axisUnitsLow); - if (stepInfo == null) { - continue; - } - final firstTick = dataToAxisUnitConverter.invert(stepInfo.tickStart); - final lastTick = dataToAxisUnitConverter - .invert(stepInfo.tickStart + stepInfo.stepSize * (tickCount - 1)); - final range = lastTick - firstTick; - // Calculate ticks if it is a better range or if preferred ticks have - // not been found yet. - if (range < selectedTicksRange || !foundPreferredTicks) { - final tickValues = _getTickValues(stepInfo, tickCount); - - if (viewportExtensionEnabled) { - mutableScale.viewportDomain = NumericExtents(firstTick, lastTick); - } - - // Create ticks from domain values. - final preferredTicks = createTicks(tickValues, - context: context, - graphicsFactory: graphicsFactory, - scale: viewportExtensionEnabled ? mutableScale : scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - stepSize: stepInfo.stepSize); - - // Request collision check from draw strategy. - final collisionReport = - tickDrawStrategy.collides(preferredTicks, orientation); - - // Don't choose colliding ticks unless it was our last resort - if (collisionReport.ticksCollide && tickCount > _minTickCount) { - continue; - } - // Only choose alternate ticks if preferred ticks is not found. - if (foundPreferredTicks && collisionReport.alternateTicksUsed) { - continue; - } - - ticks = collisionReport.alternateTicksUsed - ? collisionReport.ticks - : preferredTicks; - foundPreferredTicks = !collisionReport.alternateTicksUsed; - selectedTicksRange = range; - // If viewport extended, save the viewport used. - viewportDomain = mutableScale?.viewportDomain ?? scale.viewportDomain; - } - } - _setPreviousTickCalculationParameters(); - // If [viewportExtensionEnabled] and has changed, then set the scale's - // viewport to what was used to generate ticks. By only setting viewport - // when it has changed, we do not trigger the flag to recalculate scale. - if (viewportExtensionEnabled && scale.viewportDomain != viewportDomain) { - scale.viewportDomain = viewportDomain; - } - } - - return ticks; - } - - /// Checks whether the parameters that are used in determining the right set - /// of ticks changed from the last time we calculated ticks. If not we should - /// be able to use the cached ticks. - bool _hasTickParametersChanged() { - return _low != _prevLow || - _high != _prevHigh || - _rangeWidth != _prevRangeWidth || - _minTickCount != _prevMinTickCount || - _maxTickCount != _prevMaxTickCount || - dataIsInWholeNumbers != _prevDataIsInWholeNumbers; - } - - /// Save the last set of parameters used while determining ticks. - void _setPreviousTickCalculationParameters() { - _prevLow = _low; - _prevHigh = _high; - _prevRangeWidth = _rangeWidth; - _prevMinTickCount = _minTickCount; - _prevMaxTickCount = _maxTickCount; - _prevDataIsInWholeNumbers = dataIsInWholeNumbers; - } - - /// Calculates the domain extents that this provider will cover based on the - /// axis extents passed in and the settings in the numeric tick provider. - /// Stores the domain extents in [_low] and [_high]. - void _updateDomainExtents(NumericExtents axisExtents) { - _low = axisExtents.min; - _high = axisExtents.max; - - // Correct the extents for zero bound - if (zeroBound) { - _low = _low > 0.0 ? 0.0 : _low; - _high = _high < 0.0 ? 0.0 : _high; - } - - // Correct cases where high and low equal to give the tick provider an - // actual range to go off of when picking ticks. - if (_high == _low) { - if (_high == 0.0) { - // Corner case: the only values we've seen are zero, so lets just say - // the high is 1 and leave the low at zero. - _high = 1.0; - } else { - // The values are all the same, so assume a range of -5% to +5% from the - // single value. - if (_high > 0.0) { - _high = _high * 1.05; - _low = _low * 0.95; - } else { - // (high == low) < 0 - _high = _high * 0.95; - _low = _low * 1.05; - } - } - } - } - - /// Given [tickCount] and the domain range, finds the smallest tick increment, - /// chosen from power of 10 multiples of allowed steps, that covers the whole - /// data range. - _TickStepInfo _getStepsForTickCount(int tickCount, num high, num low) { - // A region is the space between ticks. - final regionCount = tickCount - 1; - - // If the range contains zero, ensure that zero is a tick. - if (high >= 0 && low <= 0) { - // determine the ratio of regions that are above the zero axis. - final posRegionRatio = (high > 0 ? min(1.0, high / (high - low)) : 0.0); - var positiveRegionCount = (regionCount * posRegionRatio).ceil(); - var negativeRegionCount = regionCount - positiveRegionCount; - // Ensure that negative regions are not excluded, unless there are no - // regions to spare. - if (negativeRegionCount == 0 && low < 0 && regionCount > 1) { - positiveRegionCount--; - negativeRegionCount++; - } - - // If we have positive and negative values, ensure that we have ticks in - // both regions. - // - // This should not happen unless the axis is manually configured with a - // tick count. [_updateTickCounts] should ensure that we have do not try - // to generate fewer than three. - assert( - !(low < 0 && - high > 0 && - (negativeRegionCount == 0 || positiveRegionCount == 0)), - 'Numeric tick provider cannot generate $tickCount ' - 'ticks when the axis range contains both positive and negative ' - 'values. A minimum of three ticks are required to include zero.'); - - // Determine the "favored" axis direction (the one which will control the - // ticks based on having a greater value / regions). - // - // Example: 13 / 3 (4.33 per tick) vs -5 / 1 (5 per tick) - // making -5 the favored number. A step size that includes this number - // ensures the other is also includes in the opposite direction. - final favorPositive = (high > 0 ? high / positiveRegionCount : 0).abs() > - (low < 0 ? low / negativeRegionCount : 0).abs(); - final favoredNum = (favorPositive ? high : low).abs(); - final favoredRegionCount = - favorPositive ? positiveRegionCount : negativeRegionCount; - final favoredTensBase = (_getEnclosingPowerOfTen(favoredNum)).abs(); - - // Check each step size and see if it would contain the "favored" value - for (double step in _allowedSteps) { - final tmpStepSize = _removeRoundingErrors(step * favoredTensBase); - - // If prefer whole number, then don't allow a step that isn't one. - if (dataIsInWholeNumbers && (tmpStepSize).round() != tmpStepSize) { - continue; - } - - // TODO: Skip steps that format to the same string. - // But wait until the last step to prevent the cost of the formatter. - // Potentially store the formatted strings in TickStepInfo? - if (tmpStepSize * favoredRegionCount >= favoredNum) { - double stepStart = negativeRegionCount > 0 - ? (-1 * tmpStepSize * negativeRegionCount) - : 0.0; - return _TickStepInfo(tmpStepSize, stepStart); - } - } - } else { - // Find the range base to calculate step sizes. - final diffTensBase = _getEnclosingPowerOfTen(high - low); - // Walk the step sizes calculating a starting point and seeing if the high - // end is included in the range given that step size. - for (double step in _allowedSteps) { - final tmpStepSize = _removeRoundingErrors(step * diffTensBase); - - // If prefer whole number, then don't allow a step that isn't one. - if (dataIsInWholeNumbers && (tmpStepSize).round() != tmpStepSize) { - continue; - } - - // TODO: Skip steps that format to the same string. - // But wait until the last step to prevent the cost of the formatter. - double tmpStepStart = _getStepLessThan(low, tmpStepSize); - if (tmpStepStart + (tmpStepSize * regionCount) >= high) { - return _TickStepInfo(tmpStepSize, tmpStepStart); - } - } - } - - return _TickStepInfo(1.0, low.floorToDouble()); - } - - List _getTickValues(_TickStepInfo steps, int tickCount) { - final tickValues = List(tickCount); - // We have our size and start, assign all the tick values to the given array. - for (int i = 0; i < tickCount; i++) { - tickValues[i] = dataToAxisUnitConverter.invert( - _removeRoundingErrors(steps.tickStart + (i * steps.stepSize))); - } - return tickValues; - } - - /// Given the axisDimensions update the tick counts given they are not fixed. - void _updateTickCounts(num high, num low) { - int tmpMaxNumMajorTicks; - int tmpMinNumMajorTicks; - - // If the domain range contains both positive and negative values, then we - // need a minimum of three ticks to include zero as a tick. Otherwise, we - // only need an upper and lower tick. - final absoluteMinTicks = (low < 0 && 0 < high) ? 3 : 2; - - // If there is a desired tick range use it, if not calculate one. - if (_desiredMaxTickCount != null) { - tmpMinNumMajorTicks = max(_desiredMinTickCount, absoluteMinTicks); - tmpMaxNumMajorTicks = max(_desiredMaxTickCount, tmpMinNumMajorTicks); - } else { - double minPixelsPerTick = MIN_DIPS_BETWEEN_TICKS.toDouble(); - tmpMinNumMajorTicks = absoluteMinTicks; - tmpMaxNumMajorTicks = - max(absoluteMinTicks, (_rangeWidth / minPixelsPerTick).floor()); - } - - // Don't blow away the previous array if it hasn't changed. - if (tmpMaxNumMajorTicks != _maxTickCount || - tmpMinNumMajorTicks != _minTickCount) { - _maxTickCount = tmpMaxNumMajorTicks; - _minTickCount = tmpMinNumMajorTicks; - } - } - - /// Returns the power of 10 which contains the [number]. - /// - /// If [number] is 0 returns 1. - /// Examples: - /// [number] of 63 returns 100 - /// [number] of -63 returns -100 - /// [number] of 0.63 returns 1 - static double _getEnclosingPowerOfTen(num number) { - if (number == 0) { - return 1.0; - } - - return pow(10, (log10e * log(number.abs())).ceil()) * - (number < 0.0 ? -1.0 : 1.0); - } - - /// Returns the step numerically less than the number by step increments. - static double _getStepLessThan(double number, double stepSize) { - if (number == 0.0 || stepSize == 0.0) { - return 0.0; - } - return (stepSize > 0.0 - ? (number / stepSize).floor() - : (number / stepSize).ceil()) * - stepSize; - } - - /// Attempts to slice off very small floating point rounding effects for the - /// given number. - /// - /// @param number the number to round. - /// @return the rounded number. - static double _removeRoundingErrors(double number) { - // sufficiently large multiplier to handle generating ticks on the order - // of 10^-9. - const multiplier = 1.0e9; - - return number > 100.0 - ? number.roundToDouble() - : (number * multiplier).roundToDouble() / multiplier; - } -} - -class _TickStepInfo { - double stepSize; - double tickStart; - - _TickStepInfo(this.stepSize, this.tickStart); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_extents.dart b/web/charts/common/lib/src/chart/cartesian/axis/ordinal_extents.dart deleted file mode 100644 index c5012e6f5..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_extents.dart +++ /dev/null @@ -1,44 +0,0 @@ -// 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:collection' show HashSet; -import 'scale.dart' show Extents; - -/// A range of ordinals. -class OrdinalExtents extends Extents { - final List _range; - - /// The extents representing the ordinal values in [range]. - /// - /// The elements of [range] must all be unique. - /// - /// [D] is the domain class type for the elements in the extents. - OrdinalExtents(List range) : _range = range { - // This asserts that all elements in [range] are unique. - final uniqueValueCount = HashSet.from(_range).length; - assert(uniqueValueCount == range.length); - } - - factory OrdinalExtents.all(List range) => OrdinalExtents(range); - - bool get isEmpty => _range.isEmpty; - - /// The number of values inside this extent. - int get length => _range.length; - - String operator [](int index) => _range[index]; - - int indexOf(String value) => _range.indexOf(value); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale.dart b/web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale.dart deleted file mode 100644 index b5a548d5c..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale.dart +++ /dev/null @@ -1,40 +0,0 @@ -// 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 'ordinal_scale_domain_info.dart' show OrdinalScaleDomainInfo; -import 'scale.dart' show MutableScale; - -abstract class OrdinalScale extends MutableScale { - /// The current domain collection with all added unique values. - OrdinalScaleDomainInfo get domain; - - /// Sets the viewport of the scale based on the number of data points to show - /// and the starting domain value. - /// - /// [viewportDataSize] How many ordinal domain values to show in the viewport. - /// [startingDomain] The starting domain value of the viewport. Note that if - /// the starting domain is in terms of position less than [domainValuesToShow] - /// from the last domain value the viewport will be fixed to the last value - /// and not guaranteed that this domain value is the first in the viewport. - void setViewport(int viewportDataSize, String startingDomain); - - /// The number of full ordinal steps that fit in the viewport. - int get viewportDataSize; - - /// The first fully visible ordinal step within the viewport. - /// - /// Null if no domains exist. - String get viewportStartingDomain; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale_domain_info.dart b/web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale_domain_info.dart deleted file mode 100644 index e1729cbe0..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale_domain_info.dart +++ /dev/null @@ -1,77 +0,0 @@ -// 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:collection' show HashMap; -import 'ordinal_extents.dart' show OrdinalExtents; - -/// A domain processor for [OrdinalScale]. -/// -/// [D] domain class type of the values being tracked. -/// -/// Unique domain values are kept, so duplicates will not increase the extent. -class OrdinalScaleDomainInfo { - int _index = 0; - - /// A map of domain value and the order it was added. - final _domainsToOrder = HashMap(); - - /// A list of domain values kept to support [getDomainAtIndex]. - final _domainList = []; - - OrdinalScaleDomainInfo(); - - OrdinalScaleDomainInfo copy() { - return OrdinalScaleDomainInfo() - .._domainsToOrder.addAll(_domainsToOrder) - .._index = _index - .._domainList.addAll(_domainList); - } - - void add(String domain) { - if (!_domainsToOrder.containsKey(domain)) { - _domainsToOrder[domain] = _index; - _index += 1; - _domainList.add(domain); - } - } - - int indexOf(String domain) => _domainsToOrder[domain]; - - String getDomainAtIndex(int index) { - assert(index >= 0); - assert(index < _index); - return _domainList[index]; - } - - List get domains => _domainList; - - String get first => _domainList.isEmpty ? null : _domainList.first; - - String get last => _domainList.isEmpty ? null : _domainList.last; - - bool get isEmpty => (_index == 0); - bool get isNotEmpty => !isEmpty; - - OrdinalExtents get extent => OrdinalExtents.all(_domainList); - - int get size => _index; - - /// Clears all domain values. - void clear() { - _domainsToOrder.clear(); - _domainList.clear(); - _index = 0; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/ordinal_tick_provider.dart deleted file mode 100644 index 5b5b57750..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/ordinal_tick_provider.dart +++ /dev/null @@ -1,58 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/chart_context.dart' show ChartContext; -import 'axis.dart' show AxisOrientation; -import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import 'ordinal_scale.dart' show OrdinalScale; -import 'tick.dart' show Tick; -import 'tick_formatter.dart' show TickFormatter; -import 'tick_provider.dart' show BaseTickProvider, TickHint; - -/// A strategy for selecting ticks to draw given ordinal domain values. -class OrdinalTickProvider extends BaseTickProvider { - const OrdinalTickProvider(); - - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required List domainValues, - @required OrdinalScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - return createTicks(scale.domain.domains, - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy); - } - - @override - bool operator ==(other) => other is OrdinalTickProvider; - - @override - int get hashCode => 31; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/scale.dart b/web/charts/common/lib/src/chart/cartesian/axis/scale.dart deleted file mode 100644 index 176241808..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/scale.dart +++ /dev/null @@ -1,313 +0,0 @@ -// 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' as math show max, min; - -/// Scale used to convert data input domain units to output range units. -/// -/// This is the immutable portion of the Scale definition. Used for converting -/// data from the dataset in domain units to an output in range units (likely -/// pixel range of the area to draw on). -/// -///

The Scale/MutableScale split is to show the intention of what you can or -/// should be doing with the scale during different stages of chart draw -/// process. -/// -/// [D] is the domain class type for the values passed in. -abstract class Scale { - /// Applies the scale function to the [domainValue]. - /// - /// Returns the pixel location for the given [domainValue] or null if the - /// domainValue could not be found/translated by this scale. - /// Non-numeric scales should be the only ones that can return null. - num operator [](D domainValue); - - /// Reverse application of the scale. - D reverse(double pixelLocation); - - /// Tests a [domainValue] to see if the scale can translate it. - /// - /// Returns true if the scale can translate the given domainValue. - /// (Ex: linear scales can translate any number, but ordinal scales can only - /// translate values previously passed in.) - bool canTranslate(D domainValue); - - /// Returns the previously set output range for the scale function. - ScaleOutputExtent get range; - - /// Returns the absolute width between the max and min range values. - int get rangeWidth; - - /// Returns the configuration used to determine the rangeBand. - /// - /// This is most often used to define the bar group width. - RangeBandConfig get rangeBandConfig; - - /// Returns the rangeBand width in pixels. - /// - /// The rangeBand is determined using the RangeBandConfig potentially with the - /// measured step size. This value is used as the bar group width. If - /// StepSizeConfig is set to auto detect, then you must wait until after - /// the chart's onPostLayout phase before you'll get a valid number. - double get rangeBand; - - /// Returns the stepSize width in pixels. - /// - /// The step size is determined using the [StepSizeConfig]. - double get stepSize; - - /// Returns the stepSize domain value. - double get domainStepSize; - - /// Tests whether the given [domainValue] is within the axis' range. - /// - /// Returns < 0 if the [domainValue] would plot before the viewport, 0 if it - /// would plot within the viewport and > 0 if it would plot beyond the - /// viewport of the axis. - int compareDomainValueToViewport(D domainValue); - - /// Returns true if the given [rangeValue] point is within the output range. - /// - /// Not to be confused with the start and end of the domain. - bool isRangeValueWithinViewport(double rangeValue); - - /// Returns the current viewport scale. - /// - /// A scale of 1.0 would map the data directly to the output range, while a - /// value of 2.0 would map the data to an output of double the range so you - /// only see half the data in the viewport. This is the equivalent to - /// zooming. Its value is likely >= 1.0. - double get viewportScalingFactor; - - /// Returns the current pixel viewport offset - /// - /// The translate is used by the scale function when it applies the scale. - /// This is the equivalent to panning. Its value is likely <= 0 to pan the - /// data to the left. - double get viewportTranslatePx; - - /// Returns a mutable copy of the scale. - /// - /// Mutating the returned scale will not effect the original one. - MutableScale copy(); -} - -/// Mutable extension of the [Scale] definition. -/// -/// Used for converting data from the dataset to some range (likely pixel range) -/// of the area to draw on. -/// -/// [D] the domain class type for the values passed in. -abstract class MutableScale extends Scale { - /// Reset the domain for this [Scale]. - void resetDomain(); - - /// Reset the viewport settings for this [Scale]. - void resetViewportSettings(); - - /// Add [domainValue] to this [Scale]'s domain. - /// - /// Domains should be added in order to allow proper stepSize detection. - /// [domainValue] is the data value to add to the scale used to update the - /// domain extent. - void addDomain(D domainValue); - - /// Sets the output range to use for the scale's conversion. - /// - /// The range start is mapped to the domain's min and the range end is - /// mapped to the domain's max for the conversion using the domain nicing - /// function. - /// - /// [extent] is the extent of the range which will likely be the pixel - /// range of the drawing area to convert to. - set range(ScaleOutputExtent extent); - - /// Configures the zoom and translate. - /// - /// [viewportScale] is the zoom factor to use, likely >= 1.0 where 1.0 maps - /// the complete data extents to the output range, and 2.0 only maps half the - /// data to the output range. - /// - /// [viewportTranslatePx] is the translate/pan to use in pixel units, - /// likely <= 0 which shifts the start of the data before the edge of the - /// chart giving us a pan. - void setViewportSettings(double viewportScale, double viewportTranslatePx); - - /// Sets the configuration used to determine the rangeBand (bar group width). - set rangeBandConfig(RangeBandConfig barGroupWidthConfig); - - /// Sets the method for determining the step size. - /// - /// This is the domain space between data points. - StepSizeConfig get stepSizeConfig; - set stepSizeConfig(StepSizeConfig config); -} - -/// Tuple of the output for a scale in pixels from [start] to [end] inclusive. -/// -/// It is different from [Extent] because it focuses on start and end and not -/// min and max, meaning that start could be greater or less than end. -class ScaleOutputExtent { - final int start; - final int end; - - const ScaleOutputExtent(this.start, this.end); - - int get min => math.min(start, end); - int get max => math.max(start, end); - - bool containsValue(double value) => value >= min && value <= max; - - /// Returns the difference between the extents. - /// - /// If the [end] is less than the [start] (think vertical measure axis), then - /// this will correctly return a negative value. - int get diff => end - start; - - /// Returns the width of the extent. - int get width => diff.abs(); - - @override - bool operator ==(other) => - other is ScaleOutputExtent && start == other.start && end == other.end; - - @override - int get hashCode => start.hashCode + (end.hashCode * 31); - - @override - String toString() => "ScaleOutputRange($start, $end)"; -} - -/// Type of RangeBand used to determine the rangeBand size units. -enum RangeBandType { - /// No rangeBand (not suitable for bars or step line charts). - none, - - /// Size is specified in pixel units. - fixedPixel, - - /// Size is specified domain scale units. - fixedDomain, - - /// Size is a percentage of the minimum step size between points. - fixedPercentOfStep, - - /// Size is a style pack assigned percentage of the minimum step size between - /// points. - styleAssignedPercentOfStep, - - /// Size is subtracted from the minimum step size between points in pixel - /// units. - fixedPixelSpaceFromStep, -} - -/// Defines the method for calculating the rangeBand of the Scale. -/// -/// The rangeBand is used to determine the width of a group of bars. The term -/// rangeBand comes from the d3 JavaScript library which the JS library uses -/// internally. -/// -///

RangeBandConfig is immutable, See factory methods for creating one. -class RangeBandConfig { - final RangeBandType type; - - /// The width of the band in units specified by the bandType. - final double size; - - /// Creates a rangeBand definition of zero, no rangeBand. - const RangeBandConfig.none() - : type = RangeBandType.none, - size = 0.0; - - /// Creates a fixed rangeBand definition in pixel width. - /// - /// Used to determine a bar width or a step width in the line renderer. - const RangeBandConfig.fixedPixel(double pixels) - : type = RangeBandType.fixedPixel, - size = pixels; - - /// Creates a fixed rangeBand definition in domain unit width. - /// - /// Used to determine a bar width or a step width in the line renderer. - const RangeBandConfig.fixedDomain(double domainSize) - : type = RangeBandType.fixedDomain, - size = domainSize; - - /// Creates a config that defines the rangeBand as equal to the stepSize. - const RangeBandConfig.stepChartBand() - : type = RangeBandType.fixedPercentOfStep, - size = 1.0; - - /// Creates a config that defines the rangeBand as percentage of the stepSize. - /// - /// [percentOfStepWidth] is the percentage of the step from 0.0 - 1.0. - RangeBandConfig.percentOfStep(double percentOfStepWidth) - : type = RangeBandType.fixedPercentOfStep, - size = percentOfStepWidth { - assert(percentOfStepWidth >= 0 && percentOfStepWidth <= 1.0); - } - - /// Creates a config that assigns the rangeBand according to the stylepack. - /// - ///

Note: renderers can detect this setting and update the percent based on - /// the number of series in their preprocess. - RangeBandConfig.styleAssignedPercent([int seriesCount = 1]) - : type = RangeBandType.styleAssignedPercentOfStep, - // TODO: retrieve value from the stylepack once available. - size = 0.65; - - /// Creates a config that defines the rangeBand as the stepSize - pixels. - /// - /// Where fixedPixels() gave you a constant rangBand in pixels, this will give - /// you a constant space between rangeBands in pixels. - const RangeBandConfig.fixedPixelSpaceBetweenStep(double pixels) - : type = RangeBandType.fixedPixelSpaceFromStep, - size = pixels; -} - -/// Type of step size calculation to use. -enum StepSizeType { autoDetect, fixedDomain, fixedPixels } - -/// Defines the method for calculating the stepSize between points. -/// -/// Typically auto will work fine in most cases, but if your data is -/// irregular or you only have one data point, then you may want to override the -/// stepSize detection specifying the exact expected stepSize. -class StepSizeConfig { - final StepSizeType type; - final double size; - - /// Creates a StepSizeConfig that calculates step size based on incoming data. - /// - /// The stepSize is determined is calculated by detecting the smallest - /// distance between two adjacent data points. This may not be suitable if - /// you have irregular data or just a single data point. - const StepSizeConfig.auto() - : type = StepSizeType.autoDetect, - size = 0.0; - - /// Creates a StepSizeConfig specifying the exact step size in pixel units. - const StepSizeConfig.fixedPixels(double pixels) - : type = StepSizeType.fixedPixels, - size = pixels; - - /// Creates a StepSizeConfig specifying the exact step size in domain units. - const StepSizeConfig.fixedDomain(double domainSize) - : type = StepSizeType.fixedDomain, - size = domainSize; -} - -// TODO: make other extent subclasses plural. -abstract class Extents {} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/simple_ordinal_scale.dart b/web/charts/common/lib/src/chart/cartesian/axis/simple_ordinal_scale.dart deleted file mode 100644 index 4c882d2f8..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/simple_ordinal_scale.dart +++ /dev/null @@ -1,344 +0,0 @@ -// 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 min, max; - -import 'ordinal_scale.dart' show OrdinalScale; -import 'ordinal_scale_domain_info.dart' show OrdinalScaleDomainInfo; -import 'scale.dart' - show - RangeBandConfig, - RangeBandType, - StepSizeConfig, - StepSizeType, - ScaleOutputExtent; - -/// Scale that converts ordinal values of type [D] to a given range output. -/// -/// A `SimpleOrdinalScale` is used to map values from its domain to the -/// available pixel range of the chart. Typically used for bar charts where the -/// width of the bar is [rangeBand] and the position of the bar is retrieved -/// by [[]]. -class SimpleOrdinalScale implements OrdinalScale { - final _stepSizeConfig = StepSizeConfig.auto(); - OrdinalScaleDomainInfo _domain; - ScaleOutputExtent _range = ScaleOutputExtent(0, 1); - double _viewportScale = 1.0; - double _viewportTranslatePx = 0.0; - RangeBandConfig _rangeBandConfig = RangeBandConfig.styleAssignedPercent(); - - bool _scaleChanged = true; - double _cachedStepSizePixels; - double _cachedRangeBandShift; - double _cachedRangeBandSize; - - int _viewportDataSize; - String _viewportStartingDomain; - - SimpleOrdinalScale() : _domain = OrdinalScaleDomainInfo(); - - SimpleOrdinalScale._copy(SimpleOrdinalScale other) - : _domain = other._domain.copy(), - _range = ScaleOutputExtent(other._range.start, other._range.end), - _viewportScale = other._viewportScale, - _viewportTranslatePx = other._viewportTranslatePx, - _rangeBandConfig = other._rangeBandConfig; - - @override - double get rangeBand { - if (_scaleChanged) { - _updateScale(); - } - - return _cachedRangeBandSize; - } - - @override - double get stepSize { - if (_scaleChanged) { - _updateScale(); - } - - return _cachedStepSizePixels; - } - - @override - double get domainStepSize => 1.0; - - @override - set rangeBandConfig(RangeBandConfig barGroupWidthConfig) { - if (barGroupWidthConfig == null) { - throw ArgumentError.notNull('RangeBandConfig must not be null.'); - } - - if (barGroupWidthConfig.type == RangeBandType.fixedDomain || - barGroupWidthConfig.type == RangeBandType.none) { - throw ArgumentError( - 'barGroupWidthConfig must not be NONE or FIXED_DOMAIN'); - } - - _rangeBandConfig = barGroupWidthConfig; - _scaleChanged = true; - } - - @override - RangeBandConfig get rangeBandConfig => _rangeBandConfig; - - @override - set stepSizeConfig(StepSizeConfig config) { - if (config != null && config.type != StepSizeType.autoDetect) { - throw ArgumentError( - 'Ordinal scales only support StepSizeConfig of type Auto'); - } - // Nothing is set because only auto is supported. - } - - @override - StepSizeConfig get stepSizeConfig => _stepSizeConfig; - - /// Converts [domainValue] to the position to place the band/bar. - /// - /// Returns 0 if not found. - @override - num operator [](String domainValue) { - if (_scaleChanged) { - _updateScale(); - } - - final i = _domain.indexOf(domainValue); - if (i != null) { - return viewportTranslatePx + - _range.start + - _cachedRangeBandShift + - (_cachedStepSizePixels * i); - } - // If it wasn't found - return 0.0; - } - - @override - String reverse(double pixelLocation) { - final index = ((pixelLocation - - viewportTranslatePx - - _range.start - - _cachedRangeBandShift) / - _cachedStepSizePixels); - - // The last pixel belongs in the last step even if it tries to round up. - // - // Index may be less than 0 when [pixelLocation] is less than the width of - // the range band shift. This may happen on the far left side of the chart, - // where we want the first datum anyways. Wrapping the result in "max(0, x)" - // cuts off these negative values. - return _domain - .getDomainAtIndex(max(0, min(index.round(), domain.size - 1))); - } - - @override - bool canTranslate(String domainValue) => - (_domain.indexOf(domainValue) != null); - - @override - OrdinalScaleDomainInfo get domain => _domain; - - /// Update the scale to include [domainValue]. - @override - void addDomain(String domainValue) { - _domain.add(domainValue); - _scaleChanged = true; - } - - @override - set range(ScaleOutputExtent extent) { - _range = extent; - _scaleChanged = true; - } - - @override - ScaleOutputExtent get range => _range; - - @override - resetDomain() { - _domain.clear(); - _scaleChanged = true; - } - - @override - resetViewportSettings() { - _viewportScale = 1.0; - _viewportTranslatePx = 0.0; - _scaleChanged = true; - } - - @override - int get rangeWidth => (range.start - range.end).abs().toInt(); - - @override - double get viewportScalingFactor => _viewportScale; - - @override - double get viewportTranslatePx => _viewportTranslatePx; - - @override - void setViewportSettings(double viewportScale, double viewportTranslatePx) { - _viewportScale = viewportScale; - _viewportTranslatePx = - min(0.0, max(rangeWidth * (1.0 - viewportScale), viewportTranslatePx)); - - _scaleChanged = true; - } - - @override - void setViewport(int viewportDataSize, String startingDomain) { - if (startingDomain != null && - viewportDataSize != null && - viewportDataSize <= 0) { - throw ArgumentError('viewportDataSize can' 't be less than 1.'); - } - - _scaleChanged = true; - _viewportDataSize = viewportDataSize; - _viewportStartingDomain = startingDomain; - } - - /// Update this scale's viewport using settings [_viewportDataSize] and - /// [_viewportStartingDomain]. - void _updateViewport() { - setViewportSettings(1.0, 0.0); - _recalculateScale(); - if (_domain.isEmpty) { - return; - } - - // Update the scale with zoom level to help find the correct translate. - setViewportSettings( - _domain.size / min(_viewportDataSize, _domain.size), 0.0); - _recalculateScale(); - final domainIndex = _domain.indexOf(_viewportStartingDomain); - if (domainIndex != null) { - // Update the translate so that the scale starts half a step before the - // chosen domain. - final viewportTranslatePx = -(_cachedStepSizePixels * domainIndex); - setViewportSettings(_viewportScale, viewportTranslatePx); - } - } - - @override - int get viewportDataSize { - if (_scaleChanged) { - _updateScale(); - } - - return _domain.isEmpty ? 0 : (rangeWidth ~/ _cachedStepSizePixels); - } - - @override - String get viewportStartingDomain { - if (_scaleChanged) { - _updateScale(); - } - if (_domain.isEmpty) { - return null; - } - return _domain.getDomainAtIndex( - (-_viewportTranslatePx / _cachedStepSizePixels).ceil().toInt()); - } - - @override - bool isRangeValueWithinViewport(double rangeValue) { - return range != null && rangeValue >= range.min && rangeValue <= range.max; - } - - @override - int compareDomainValueToViewport(String domainValue) { - // TODO: This currently works because range defaults to 0-1 - // This needs to be looked into further. - var i = _domain.indexOf(domainValue); - if (i != null && range != null) { - var domainPx = this[domainValue]; - if (domainPx < range.min) { - return -1; - } - if (domainPx > range.max) { - return 1; - } - return 0; - } - return -1; - } - - @override - SimpleOrdinalScale copy() => SimpleOrdinalScale._copy(this); - - void _updateCachedFields( - double stepSizePixels, double rangeBandPixels, double rangeBandShift) { - _cachedStepSizePixels = stepSizePixels; - _cachedRangeBandSize = rangeBandPixels; - _cachedRangeBandShift = rangeBandShift; - - // TODO: When there are horizontal bars increasing from where - // the domain and measure axis intersects but the desired behavior is - // flipped. The plan is to fix this by fixing code to flip the range in the - // code. - - // If range start is less than range end, then the domain is calculated by - // adding the band width. If range start is greater than range end, then the - // domain is calculated by subtracting from the band width (ex. horizontal - // bar charts where first series is at the bottom of the chart). - if (range.start > range.end) { - _cachedStepSizePixels *= -1; - _cachedRangeBandShift *= -1; - } - - _scaleChanged = false; - } - - void _updateScale() { - if (_viewportStartingDomain != null && _viewportDataSize != null) { - // Update viewport recalculates the scale. - _updateViewport(); - } - _recalculateScale(); - } - - void _recalculateScale() { - final stepSizePixels = _domain.isEmpty - ? 0.0 - : _viewportScale * (rangeWidth.toDouble() / _domain.size.toDouble()); - double rangeBandPixels; - - switch (rangeBandConfig.type) { - case RangeBandType.fixedPixel: - rangeBandPixels = rangeBandConfig.size.toDouble(); - break; - case RangeBandType.fixedPixelSpaceFromStep: - var spaceInPixels = rangeBandConfig.size.toDouble(); - rangeBandPixels = max(0.0, stepSizePixels - spaceInPixels); - break; - case RangeBandType.styleAssignedPercentOfStep: - case RangeBandType.fixedPercentOfStep: - var percent = rangeBandConfig.size.toDouble(); - rangeBandPixels = stepSizePixels * percent; - break; - case RangeBandType.fixedDomain: - case RangeBandType.none: - default: - throw StateError('RangeBandType must not be NONE or FIXED_DOMAIN'); - break; - } - - _updateCachedFields(stepSizePixels, rangeBandPixels, stepSizePixels / 2.0); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/axis_spec.dart deleted file mode 100644 index 0d7b3b42b..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/axis_spec.dart +++ /dev/null @@ -1,181 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; - -import '../../../../common/color.dart' show Color; -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show Axis; -import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import '../tick_formatter.dart' show TickFormatter; -import '../tick_provider.dart' show TickProvider; - -@immutable -class AxisSpec { - final bool showAxisLine; - final RenderSpec renderSpec; - final TickProviderSpec tickProviderSpec; - final TickFormatterSpec tickFormatterSpec; - - const AxisSpec({ - this.renderSpec, - this.tickProviderSpec, - this.tickFormatterSpec, - this.showAxisLine, - }); - - factory AxisSpec.from( - AxisSpec other, { - RenderSpec renderSpec, - TickProviderSpec tickProviderSpec, - TickFormatterSpec tickFormatterSpec, - bool showAxisLine, - }) { - return AxisSpec( - renderSpec: renderSpec ?? other.renderSpec, - tickProviderSpec: tickProviderSpec ?? other.tickProviderSpec, - tickFormatterSpec: tickFormatterSpec ?? other.tickFormatterSpec, - showAxisLine: showAxisLine ?? other.showAxisLine, - ); - } - - configure( - Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { - if (showAxisLine != null) { - axis.forceDrawAxisLine = showAxisLine; - } - - if (renderSpec != null) { - axis.tickDrawStrategy = - renderSpec.createDrawStrategy(context, graphicsFactory); - } - - if (tickProviderSpec != null) { - axis.tickProvider = tickProviderSpec.createTickProvider(context); - } - - if (tickFormatterSpec != null) { - axis.tickFormatter = tickFormatterSpec.createTickFormatter(context); - } - } - - /// Creates an appropriately typed [Axis]. - Axis createAxis() => null; - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is AxisSpec && - renderSpec == other.renderSpec && - tickProviderSpec == other.tickProviderSpec && - tickFormatterSpec == other.tickFormatterSpec && - showAxisLine == other.showAxisLine); - - @override - int get hashCode { - int hashcode = renderSpec?.hashCode ?? 0; - hashcode = (hashcode * 37) + tickProviderSpec.hashCode; - hashcode = (hashcode * 37) + tickFormatterSpec.hashCode; - hashcode = (hashcode * 37) + showAxisLine.hashCode; - return hashcode; - } -} - -@immutable -abstract class TickProviderSpec { - TickProvider createTickProvider(ChartContext context); -} - -@immutable -abstract class TickFormatterSpec { - TickFormatter createTickFormatter(ChartContext context); -} - -@immutable -abstract class RenderSpec { - const RenderSpec(); - - TickDrawStrategy createDrawStrategy( - ChartContext context, GraphicsFactory graphicFactory); -} - -@immutable -class TextStyleSpec { - final String fontFamily; - final int fontSize; - final Color color; - - const TextStyleSpec({this.fontFamily, this.fontSize, this.color}); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is TextStyleSpec && - fontFamily == other.fontFamily && - fontSize == other.fontSize && - color == other.color); - } - - @override - int get hashCode { - int hashcode = fontFamily?.hashCode ?? 0; - hashcode = (hashcode * 37) + fontSize?.hashCode ?? 0; - hashcode = (hashcode * 37) + color?.hashCode ?? 0; - return hashcode; - } -} - -@immutable -class LineStyleSpec { - final Color color; - final List dashPattern; - final int thickness; - - const LineStyleSpec({this.color, this.dashPattern, this.thickness}); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is LineStyleSpec && - color == other.color && - dashPattern == other.dashPattern && - thickness == other.thickness); - } - - @override - int get hashCode { - int hashcode = color?.hashCode ?? 0; - hashcode = (hashcode * 37) + dashPattern?.hashCode ?? 0; - hashcode = (hashcode * 37) + thickness?.hashCode ?? 0; - return hashcode; - } -} - -enum TickLabelAnchor { - before, - centered, - after, - - /// The top most tick draws all text under the location. - /// The bottom most tick draws all text above the location. - /// The rest of the ticks are centered. - inside, -} - -enum TickLabelJustification { - inside, - outside, -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/bucketing_axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/bucketing_axis_spec.dart deleted file mode 100644 index 0f5e5065f..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/bucketing_axis_spec.dart +++ /dev/null @@ -1,170 +0,0 @@ -// 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 'package:intl/intl.dart'; -import 'package:meta/meta.dart' show immutable; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show Axis, NumericAxis; -import '../linear/bucketing_numeric_axis.dart' show BucketingNumericAxis; -import '../linear/bucketing_numeric_tick_provider.dart' - show BucketingNumericTickProvider; -import '../numeric_extents.dart' show NumericExtents; -import 'axis_spec.dart' show AxisSpec, RenderSpec; -import 'numeric_axis_spec.dart' - show - BasicNumericTickFormatterSpec, - BasicNumericTickProviderSpec, - NumericAxisSpec, - NumericTickProviderSpec, - NumericTickFormatterSpec; - -/// A numeric [AxisSpec] that positions all values beneath a certain [threshold] -/// into a reserved space on the axis range. The label for the bucket line will -/// be drawn in the middle of the bucket range, rather than aligned with the -/// gridline for that value's position on the scale. -/// -/// An example illustration of a bucketing measure axis on a point chart -/// follows. In this case, values such as "6%" and "3%" are drawn in the bucket -/// of the axis, since they are less than the [threshold] value of 10%. -/// -/// 100% ┠───────────────────────── -/// ┃ * -/// ┃ * -/// 50% ┠──────*────────────────── -/// ┃ -/// ┠───────────────────────── -/// < 10% ┃ * * -/// ┗┯━━━━━━━━━━┯━━━━━━━━━━━┯━ -/// 0 50 100 -/// -/// This axis will format numbers as percents by default. -@immutable -class BucketingAxisSpec extends NumericAxisSpec { - /// All values smaller than the threshold will be bucketed into the same - /// position in the reserved space on the axis. - final num threshold; - - /// Whether or not measure values bucketed below the [threshold] should be - /// visible on the chart, or collapsed. - /// - /// If this is false, then any data with measure values smaller than - /// [threshold] will not be rendered on the chart. - final bool showBucket; - - /// Creates a [NumericAxisSpec] that is specialized for percentage data. - BucketingAxisSpec({ - RenderSpec renderSpec, - NumericTickProviderSpec tickProviderSpec, - NumericTickFormatterSpec tickFormatterSpec, - bool showAxisLine, - bool showBucket, - this.threshold, - NumericExtents viewport, - }) : this.showBucket = showBucket ?? true, - super( - renderSpec: renderSpec, - tickProviderSpec: - tickProviderSpec ?? const BucketingNumericTickProviderSpec(), - tickFormatterSpec: tickFormatterSpec ?? - BasicNumericTickFormatterSpec.fromNumberFormat( - NumberFormat.percentPattern()), - showAxisLine: showAxisLine, - viewport: viewport ?? const NumericExtents(0.0, 1.0)); - - @override - configure( - Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { - super.configure(axis, context, graphicsFactory); - - if (axis is NumericAxis && viewport != null) { - axis.setScaleViewport(viewport); - } - - if (axis is BucketingNumericAxis && threshold != null) { - axis.threshold = threshold; - } - - if (axis is BucketingNumericAxis && showBucket != null) { - axis.showBucket = showBucket; - } - } - - @override - BucketingNumericAxis createAxis() => BucketingNumericAxis(); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is BucketingAxisSpec && - showBucket == other.showBucket && - threshold == other.threshold && - super == (other)); - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + showBucket.hashCode; - hashcode = (hashcode * 37) + threshold.hashCode; - return hashcode; - } -} - -@immutable -class BucketingNumericTickProviderSpec extends BasicNumericTickProviderSpec { - /// Creates a [TickProviderSpec] that generates ticks for a bucketing axis. - /// - /// [zeroBound] automatically include zero in the data range. - /// [dataIsInWholeNumbers] skip over ticks that would produce - /// fractional ticks that don't make sense for the domain (ie: headcount). - /// [desiredTickCount] the fixed number of ticks to try to make. Convenience - /// that sets [desiredMinTickCount] and [desiredMaxTickCount] the same. - /// Both min and max win out if they are set along with - /// [desiredTickCount]. - /// [desiredMinTickCount] automatically choose the best tick - /// count to produce the 'nicest' ticks but make sure we have this many. - /// [desiredMaxTickCount] automatically choose the best tick - /// count to produce the 'nicest' ticks but make sure we don't have more - /// than this many. - const BucketingNumericTickProviderSpec( - {bool zeroBound, - bool dataIsInWholeNumbers, - int desiredTickCount, - int desiredMinTickCount, - int desiredMaxTickCount}) - : super( - zeroBound: zeroBound ?? true, - dataIsInWholeNumbers: dataIsInWholeNumbers ?? false, - desiredTickCount: desiredTickCount, - desiredMinTickCount: desiredMinTickCount, - desiredMaxTickCount: desiredMaxTickCount, - ); - - @override - BucketingNumericTickProvider createTickProvider(ChartContext context) { - final provider = BucketingNumericTickProvider() - ..zeroBound = zeroBound - ..dataIsInWholeNumbers = dataIsInWholeNumbers; - - if (desiredMinTickCount != null || - desiredMaxTickCount != null || - desiredTickCount != null) { - provider.setTickCount(desiredMaxTickCount ?? desiredTickCount ?? 10, - desiredMinTickCount ?? desiredTickCount ?? 2); - } - return provider; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/date_time_axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/date_time_axis_spec.dart deleted file mode 100644 index 37716147d..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/date_time_axis_spec.dart +++ /dev/null @@ -1,327 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; - -import '../../../../common/date_time_factory.dart' show DateTimeFactory; -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show Axis; -import '../end_points_tick_provider.dart' show EndPointsTickProvider; -import '../static_tick_provider.dart' show StaticTickProvider; -import '../time/auto_adjusting_date_time_tick_provider.dart' - show AutoAdjustingDateTimeTickProvider; -import '../time/date_time_axis.dart' show DateTimeAxis; -import '../time/date_time_extents.dart' show DateTimeExtents; -import '../time/date_time_tick_formatter.dart' show DateTimeTickFormatter; -import '../time/day_time_stepper.dart' show DayTimeStepper; -import '../time/hour_tick_formatter.dart' show HourTickFormatter; -import '../time/time_range_tick_provider_impl.dart' - show TimeRangeTickProviderImpl; -import '../time/time_tick_formatter.dart' show TimeTickFormatter; -import '../time/time_tick_formatter_impl.dart' - show CalendarField, TimeTickFormatterImpl; -import 'axis_spec.dart' - show AxisSpec, TickProviderSpec, TickFormatterSpec, RenderSpec; -import 'tick_spec.dart' show TickSpec; - -/// Generic [AxisSpec] specialized for Timeseries charts. -@immutable -class DateTimeAxisSpec extends AxisSpec { - /// Sets viewport for this Axis. - /// - /// If pan / zoom behaviors are set, this is the initial viewport. - final DateTimeExtents viewport; - - /// Creates a [AxisSpec] that specialized for timeseries charts. - /// - /// [renderSpec] spec used to configure how the ticks and labels - /// actually render. Possible values are [GridlineRendererSpec], - /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the - /// given to the RenderSpec is of type [DateTime] for Timeseries. - /// [tickProviderSpec] spec used to configure what ticks are generated. - /// [tickFormatterSpec] spec used to configure how the tick labels - /// are formatted. - /// [showAxisLine] override to force the axis to draw the axis - /// line. - const DateTimeAxisSpec({ - RenderSpec renderSpec, - DateTimeTickProviderSpec tickProviderSpec, - DateTimeTickFormatterSpec tickFormatterSpec, - bool showAxisLine, - this.viewport, - }) : super( - renderSpec: renderSpec, - tickProviderSpec: tickProviderSpec, - tickFormatterSpec: tickFormatterSpec, - showAxisLine: showAxisLine); - - @override - configure(Axis axis, ChartContext context, - GraphicsFactory graphicsFactory) { - super.configure(axis, context, graphicsFactory); - - if (axis is DateTimeAxis && viewport != null) { - axis.setScaleViewport(viewport); - } - } - - Axis createAxis() { - assert(false, 'Call createDateTimeAxis() to create a DateTimeAxis.'); - return null; - } - - /// Creates a [DateTimeAxis]. This should be called in place of createAxis. - DateTimeAxis createDateTimeAxis(DateTimeFactory dateTimeFactory) => - DateTimeAxis(dateTimeFactory); - - @override - bool operator ==(Object other) => - other is DateTimeAxisSpec && - viewport == other.viewport && - super == (other); - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + viewport.hashCode; - return hashcode; - } -} - -abstract class DateTimeTickProviderSpec extends TickProviderSpec {} - -abstract class DateTimeTickFormatterSpec extends TickFormatterSpec {} - -/// [TickProviderSpec] that sets up the automatically assigned time ticks based -/// on the extents of your data. -@immutable -class AutoDateTimeTickProviderSpec implements DateTimeTickProviderSpec { - final bool includeTime; - - /// Creates a [TickProviderSpec] that dynamically chooses ticks based on the - /// extents of the data. - /// - /// [includeTime] - flag that indicates whether the time should be - /// included when choosing appropriate tick intervals. - const AutoDateTimeTickProviderSpec({this.includeTime = true}); - - @override - AutoAdjustingDateTimeTickProvider createTickProvider(ChartContext context) { - if (includeTime) { - return AutoAdjustingDateTimeTickProvider.createDefault( - context.dateTimeFactory); - } else { - return AutoAdjustingDateTimeTickProvider.createWithoutTime( - context.dateTimeFactory); - } - } - - @override - bool operator ==(Object other) => - other is AutoDateTimeTickProviderSpec && includeTime == other.includeTime; - - @override - int get hashCode => includeTime?.hashCode ?? 0; -} - -/// [TickProviderSpec] that sets up time ticks with days increments only. -@immutable -class DayTickProviderSpec implements DateTimeTickProviderSpec { - final List increments; - - const DayTickProviderSpec({this.increments}); - - /// Creates a [TickProviderSpec] that dynamically chooses ticks based on the - /// extents of the data, limited to day increments. - /// - /// [increments] specify the number of day increments that can be chosen from - /// when searching for the appropriate tick intervals. - @override - AutoAdjustingDateTimeTickProvider createTickProvider(ChartContext context) { - return AutoAdjustingDateTimeTickProvider.createWith([ - TimeRangeTickProviderImpl(DayTimeStepper(context.dateTimeFactory, - allowedTickIncrements: increments)) - ]); - } - - @override - bool operator ==(Object other) => - other is DayTickProviderSpec && increments == other.increments; - - @override - int get hashCode => increments?.hashCode ?? 0; -} - -/// [TickProviderSpec] that sets up time ticks at the two end points of the axis -/// range. -@immutable -class DateTimeEndPointsTickProviderSpec implements DateTimeTickProviderSpec { - const DateTimeEndPointsTickProviderSpec(); - - /// Creates a [TickProviderSpec] that dynamically chooses time ticks at the - /// two end points of the axis range - @override - EndPointsTickProvider createTickProvider(ChartContext context) { - return EndPointsTickProvider(); - } - - @override - bool operator ==(Object other) => other is DateTimeEndPointsTickProviderSpec; -} - -/// [TickProviderSpec] that allows you to specific the ticks to be used. -@immutable -class StaticDateTimeTickProviderSpec implements DateTimeTickProviderSpec { - final List> tickSpecs; - - const StaticDateTimeTickProviderSpec(this.tickSpecs); - - @override - StaticTickProvider createTickProvider(ChartContext context) => - StaticTickProvider(tickSpecs); - - @override - bool operator ==(Object other) => - other is StaticDateTimeTickProviderSpec && tickSpecs == other.tickSpecs; - - @override - int get hashCode => tickSpecs.hashCode; -} - -/// Formatters for a single level of the [DateTimeTickFormatterSpec]. -@immutable -class TimeFormatterSpec { - final String format; - final String transitionFormat; - final String noonFormat; - - /// Creates a formatter for a particular granularity of data. - /// - /// [format] [DateFormat] format string used to format non-transition ticks. - /// The string is given to the dateTimeFactory to support i18n formatting. - /// [transitionFormat] [DateFormat] format string used to format transition - /// ticks. Examples of transition ticks: - /// Day ticks would have a transition tick at month boundaries. - /// Hour ticks would have a transition tick at day boundaries. - /// The first tick is typically a transition tick. - /// [noonFormat] [DateFormat] format string used only for formatting hours - /// in the event that you want to format noon differently than other - /// hours (ie: [10, 11, 12p, 1, 2, 3]). - const TimeFormatterSpec( - {this.format, this.transitionFormat, this.noonFormat}); - - @override - bool operator ==(Object other) => - other is TimeFormatterSpec && - format == other.format && - transitionFormat == other.transitionFormat && - noonFormat == other.noonFormat; - - @override - int get hashCode { - int hashcode = format?.hashCode ?? 0; - hashcode = (hashcode * 37) + transitionFormat?.hashCode ?? 0; - hashcode = (hashcode * 37) + noonFormat?.hashCode ?? 0; - return hashcode; - } -} - -/// [TickFormatterSpec] that automatically chooses the appropriate level of -/// formatting based on the tick stepSize. Each level of date granularity has -/// its own [TimeFormatterSpec] used to specify the formatting strings at that -/// level. -@immutable -class AutoDateTimeTickFormatterSpec implements DateTimeTickFormatterSpec { - final TimeFormatterSpec minute; - final TimeFormatterSpec hour; - final TimeFormatterSpec day; - final TimeFormatterSpec month; - final TimeFormatterSpec year; - - /// Creates a [TickFormatterSpec] that automatically chooses the formatting - /// given the individual [TimeFormatterSpec] formatters that are set. - /// - /// There is a default formatter for each level that is configurable, but - /// by specifying a level here it replaces the default for that particular - /// granularity. This is useful for swapping out one or all of the formatters. - const AutoDateTimeTickFormatterSpec( - {this.minute, this.hour, this.day, this.month, this.year}); - - @override - DateTimeTickFormatter createTickFormatter(ChartContext context) { - final Map map = {}; - - if (minute != null) { - map[DateTimeTickFormatter.MINUTE] = - _makeFormatter(minute, CalendarField.hourOfDay, context); - } - if (hour != null) { - map[DateTimeTickFormatter.HOUR] = - _makeFormatter(hour, CalendarField.date, context); - } - if (day != null) { - map[23 * DateTimeTickFormatter.HOUR] = - _makeFormatter(day, CalendarField.month, context); - } - if (month != null) { - map[28 * DateTimeTickFormatter.DAY] = - _makeFormatter(month, CalendarField.year, context); - } - if (year != null) { - map[364 * DateTimeTickFormatter.DAY] = - _makeFormatter(year, CalendarField.year, context); - } - - return DateTimeTickFormatter(context.dateTimeFactory, overrides: map); - } - - TimeTickFormatterImpl _makeFormatter(TimeFormatterSpec spec, - CalendarField transitionField, ChartContext context) { - if (spec.noonFormat != null) { - return HourTickFormatter( - dateTimeFactory: context.dateTimeFactory, - simpleFormat: spec.format, - transitionFormat: spec.transitionFormat, - noonFormat: spec.noonFormat); - } else { - return TimeTickFormatterImpl( - dateTimeFactory: context.dateTimeFactory, - simpleFormat: spec.format, - transitionFormat: spec.transitionFormat, - transitionField: transitionField); - } - } - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is AutoDateTimeTickFormatterSpec && - minute == other.minute && - hour == other.hour && - day == other.day && - month == other.month && - year == other.year); - - @override - int get hashCode { - int hashcode = minute?.hashCode ?? 0; - hashcode = (hashcode * 37) + hour?.hashCode ?? 0; - hashcode = (hashcode * 37) + day?.hashCode ?? 0; - hashcode = (hashcode * 37) + month?.hashCode ?? 0; - hashcode = (hashcode * 37) + year?.hashCode ?? 0; - return hashcode; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart deleted file mode 100644 index 706c38973..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart +++ /dev/null @@ -1,65 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; - -import '../draw_strategy/small_tick_draw_strategy.dart' - show SmallTickRendererSpec; -import '../time/date_time_extents.dart' show DateTimeExtents; -import 'axis_spec.dart' show AxisSpec, RenderSpec, TickLabelAnchor; -import 'date_time_axis_spec.dart' - show - DateTimeAxisSpec, - DateTimeEndPointsTickProviderSpec, - DateTimeTickFormatterSpec, - DateTimeTickProviderSpec; - -/// Default [AxisSpec] used for Timeseries charts. -@immutable -class EndPointsTimeAxisSpec extends DateTimeAxisSpec { - /// Creates a [AxisSpec] that specialized for timeseries charts. - /// - /// [renderSpec] spec used to configure how the ticks and labels - /// actually render. Possible values are [GridlineRendererSpec], - /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the - /// given to the RenderSpec is of type [DateTime] for Timeseries. - /// [tickProviderSpec] spec used to configure what ticks are generated. - /// [tickFormatterSpec] spec used to configure how the tick labels - /// are formatted. - /// [showAxisLine] override to force the axis to draw the axis - /// line. - const EndPointsTimeAxisSpec({ - RenderSpec renderSpec, - DateTimeTickProviderSpec tickProviderSpec, - DateTimeTickFormatterSpec tickFormatterSpec, - bool showAxisLine, - DateTimeExtents viewport, - bool usingBarRenderer = false, - }) : super( - renderSpec: renderSpec ?? - const SmallTickRendererSpec( - labelAnchor: TickLabelAnchor.inside, - labelOffsetFromTickPx: 0), - tickProviderSpec: - tickProviderSpec ?? const DateTimeEndPointsTickProviderSpec(), - tickFormatterSpec: tickFormatterSpec, - showAxisLine: showAxisLine, - viewport: viewport); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is EndPointsTimeAxisSpec && super == (other)); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/numeric_axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/numeric_axis_spec.dart deleted file mode 100644 index 95f1c7a00..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/numeric_axis_spec.dart +++ /dev/null @@ -1,253 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; -import 'package:meta/meta.dart' show immutable; -import 'package:intl/intl.dart'; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../../../common/datum_details.dart' show MeasureFormatter; -import '../axis.dart' show Axis, NumericAxis; -import '../end_points_tick_provider.dart' show EndPointsTickProvider; -import '../numeric_extents.dart' show NumericExtents; -import '../numeric_tick_provider.dart' show NumericTickProvider; -import '../static_tick_provider.dart' show StaticTickProvider; -import '../tick_formatter.dart' show NumericTickFormatter; -import 'axis_spec.dart' - show AxisSpec, TickProviderSpec, TickFormatterSpec, RenderSpec; -import 'tick_spec.dart' show TickSpec; - -/// [AxisSpec] specialized for numeric/continuous axes like the measure axis. -@immutable -class NumericAxisSpec extends AxisSpec { - /// Sets viewport for this Axis. - /// - /// If pan / zoom behaviors are set, this is the initial viewport. - final NumericExtents viewport; - - /// Creates a [AxisSpec] that specialized for numeric data. - /// - /// [renderSpec] spec used to configure how the ticks and labels - /// actually render. Possible values are [GridlineRendererSpec], - /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the - /// given to the RenderSpec is of type [num] when using this spec. - /// [tickProviderSpec] spec used to configure what ticks are generated. - /// [tickFormatterSpec] spec used to configure how the tick labels are - /// formatted. - /// [showAxisLine] override to force the axis to draw the axis line. - const NumericAxisSpec({ - RenderSpec renderSpec, - NumericTickProviderSpec tickProviderSpec, - NumericTickFormatterSpec tickFormatterSpec, - bool showAxisLine, - this.viewport, - }) : super( - renderSpec: renderSpec, - tickProviderSpec: tickProviderSpec, - tickFormatterSpec: tickFormatterSpec, - showAxisLine: showAxisLine); - - factory NumericAxisSpec.from( - NumericAxisSpec other, { - RenderSpec renderSpec, - TickProviderSpec tickProviderSpec, - TickFormatterSpec tickFormatterSpec, - bool showAxisLine, - NumericExtents viewport, - }) { - return NumericAxisSpec( - renderSpec: renderSpec ?? other.renderSpec, - tickProviderSpec: tickProviderSpec ?? other.tickProviderSpec, - tickFormatterSpec: tickFormatterSpec ?? other.tickFormatterSpec, - showAxisLine: showAxisLine ?? other.showAxisLine, - viewport: viewport ?? other.viewport, - ); - } - - @override - configure( - Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { - super.configure(axis, context, graphicsFactory); - - if (axis is NumericAxis && viewport != null) { - axis.setScaleViewport(viewport); - } - } - - @override - NumericAxis createAxis() => NumericAxis(); - - @override - bool operator ==(Object other) => - other is NumericAxisSpec && - viewport == other.viewport && - super == (other); - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + viewport.hashCode; - hashcode = (hashcode * 37) + super.hashCode; - return hashcode; - } -} - -abstract class NumericTickProviderSpec extends TickProviderSpec {} - -abstract class NumericTickFormatterSpec extends TickFormatterSpec {} - -@immutable -class BasicNumericTickProviderSpec implements NumericTickProviderSpec { - final bool zeroBound; - final bool dataIsInWholeNumbers; - final int desiredTickCount; - final int desiredMinTickCount; - final int desiredMaxTickCount; - - /// Creates a [TickProviderSpec] that dynamically chooses the number of - /// ticks based on the extents of the data. - /// - /// [zeroBound] automatically include zero in the data range. - /// [dataIsInWholeNumbers] skip over ticks that would produce - /// fractional ticks that don't make sense for the domain (ie: headcount). - /// [desiredTickCount] the fixed number of ticks to try to make. Convenience - /// that sets [desiredMinTickCount] and [desiredMaxTickCount] the same. - /// Both min and max win out if they are set along with - /// [desiredTickCount]. - /// [desiredMinTickCount] automatically choose the best tick - /// count to produce the 'nicest' ticks but make sure we have this many. - /// [desiredMaxTickCount] automatically choose the best tick - /// count to produce the 'nicest' ticks but make sure we don't have more - /// than this many. - const BasicNumericTickProviderSpec( - {this.zeroBound, - this.dataIsInWholeNumbers, - this.desiredTickCount, - this.desiredMinTickCount, - this.desiredMaxTickCount}); - - @override - NumericTickProvider createTickProvider(ChartContext context) { - final provider = NumericTickProvider(); - if (zeroBound != null) { - provider.zeroBound = zeroBound; - } - if (dataIsInWholeNumbers != null) { - provider.dataIsInWholeNumbers = dataIsInWholeNumbers; - } - - if (desiredMinTickCount != null || - desiredMaxTickCount != null || - desiredTickCount != null) { - provider.setTickCount(desiredMaxTickCount ?? desiredTickCount ?? 10, - desiredMinTickCount ?? desiredTickCount ?? 2); - } - return provider; - } - - @override - bool operator ==(Object other) => - other is BasicNumericTickProviderSpec && - zeroBound == other.zeroBound && - dataIsInWholeNumbers == other.dataIsInWholeNumbers && - desiredTickCount == other.desiredTickCount && - desiredMinTickCount == other.desiredMinTickCount && - desiredMaxTickCount == other.desiredMaxTickCount; - - @override - int get hashCode { - int hashcode = zeroBound?.hashCode ?? 0; - hashcode = (hashcode * 37) + dataIsInWholeNumbers?.hashCode ?? 0; - hashcode = (hashcode * 37) + desiredTickCount?.hashCode ?? 0; - hashcode = (hashcode * 37) + desiredMinTickCount?.hashCode ?? 0; - hashcode = (hashcode * 37) + desiredMaxTickCount?.hashCode ?? 0; - return hashcode; - } -} - -/// [TickProviderSpec] that sets up numeric ticks at the two end points of the -/// axis range. -@immutable -class NumericEndPointsTickProviderSpec implements NumericTickProviderSpec { - /// Creates a [TickProviderSpec] that dynamically chooses numeric ticks at the - /// two end points of the axis range - const NumericEndPointsTickProviderSpec(); - - @override - EndPointsTickProvider createTickProvider(ChartContext context) { - return EndPointsTickProvider(); - } - - @override - bool operator ==(Object other) => other is NumericEndPointsTickProviderSpec; -} - -/// [TickProviderSpec] that allows you to specific the ticks to be used. -@immutable -class StaticNumericTickProviderSpec implements NumericTickProviderSpec { - final List> tickSpecs; - - const StaticNumericTickProviderSpec(this.tickSpecs); - - @override - StaticTickProvider createTickProvider(ChartContext context) => - StaticTickProvider(tickSpecs); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is StaticNumericTickProviderSpec && tickSpecs == other.tickSpecs); - - @override - int get hashCode => tickSpecs.hashCode; -} - -@immutable -class BasicNumericTickFormatterSpec implements NumericTickFormatterSpec { - final MeasureFormatter formatter; - final NumberFormat numberFormat; - - /// Simple [TickFormatterSpec] that delegates formatting to the given - /// [NumberFormat]. - const BasicNumericTickFormatterSpec(this.formatter) : numberFormat = null; - - const BasicNumericTickFormatterSpec.fromNumberFormat(this.numberFormat) - : formatter = null; - - /// A formatter will be created with the number format if it is not null. - /// Otherwise, it will create one with the [MeasureFormatter] callback. - @override - NumericTickFormatter createTickFormatter(ChartContext context) { - return numberFormat != null - ? NumericTickFormatter.fromNumberFormat(numberFormat) - : NumericTickFormatter(formatter: formatter); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is BasicNumericTickFormatterSpec && - formatter == other.formatter && - numberFormat == other.numberFormat); - } - - @override - int get hashCode { - int hashcode = formatter.hashCode; - hashcode = (hashcode * 37) * numberFormat.hashCode; - return hashcode; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart deleted file mode 100644 index b41c73644..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart +++ /dev/null @@ -1,139 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show Axis, OrdinalAxis, OrdinalViewport; -import '../ordinal_tick_provider.dart' show OrdinalTickProvider; -import '../static_tick_provider.dart' show StaticTickProvider; -import '../tick_formatter.dart' show OrdinalTickFormatter; -import 'axis_spec.dart' - show AxisSpec, TickProviderSpec, TickFormatterSpec, RenderSpec; -import 'tick_spec.dart' show TickSpec; - -/// [AxisSpec] specialized for ordinal/non-continuous axes typically for bars. -@immutable -class OrdinalAxisSpec extends AxisSpec { - /// Sets viewport for this Axis. - /// - /// If pan / zoom behaviors are set, this is the initial viewport. - final OrdinalViewport viewport; - - /// Creates a [AxisSpec] that specialized for ordinal domain charts. - /// - /// [renderSpec] spec used to configure how the ticks and labels - /// actually render. Possible values are [GridlineRendererSpec], - /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the - /// given to the RenderSpec is of type [String] when using this spec. - /// [tickProviderSpec] spec used to configure what ticks are generated. - /// [tickFormatterSpec] spec used to configure how the tick labels are - /// formatted. - /// [showAxisLine] override to force the axis to draw the axis line. - const OrdinalAxisSpec({ - RenderSpec renderSpec, - OrdinalTickProviderSpec tickProviderSpec, - OrdinalTickFormatterSpec tickFormatterSpec, - bool showAxisLine, - this.viewport, - }) : super( - renderSpec: renderSpec, - tickProviderSpec: tickProviderSpec, - tickFormatterSpec: tickFormatterSpec, - showAxisLine: showAxisLine); - - @override - configure(Axis axis, ChartContext context, - GraphicsFactory graphicsFactory) { - super.configure(axis, context, graphicsFactory); - - if (axis is OrdinalAxis && viewport != null) { - axis.setScaleViewport(viewport); - } - } - - @override - OrdinalAxis createAxis() => OrdinalAxis(); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other is OrdinalAxisSpec && - viewport == other.viewport && - super == (other)); - } - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + viewport.hashCode; - return hashcode; - } -} - -abstract class OrdinalTickProviderSpec extends TickProviderSpec {} - -abstract class OrdinalTickFormatterSpec extends TickFormatterSpec {} - -@immutable -class BasicOrdinalTickProviderSpec implements OrdinalTickProviderSpec { - const BasicOrdinalTickProviderSpec(); - - @override - OrdinalTickProvider createTickProvider(ChartContext context) => - OrdinalTickProvider(); - - @override - bool operator ==(Object other) => other is BasicOrdinalTickProviderSpec; - - @override - int get hashCode => 37; -} - -/// [TickProviderSpec] that allows you to specific the ticks to be used. -@immutable -class StaticOrdinalTickProviderSpec implements OrdinalTickProviderSpec { - final List> tickSpecs; - - const StaticOrdinalTickProviderSpec(this.tickSpecs); - - @override - StaticTickProvider createTickProvider(ChartContext context) => - StaticTickProvider(tickSpecs); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is StaticOrdinalTickProviderSpec && tickSpecs == other.tickSpecs); - - @override - int get hashCode => tickSpecs.hashCode; -} - -@immutable -class BasicOrdinalTickFormatterSpec implements OrdinalTickFormatterSpec { - const BasicOrdinalTickFormatterSpec(); - - @override - OrdinalTickFormatter createTickFormatter(ChartContext context) => - OrdinalTickFormatter(); - - @override - bool operator ==(Object other) => other is BasicOrdinalTickFormatterSpec; - - @override - int get hashCode => 37; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/percent_axis_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/percent_axis_spec.dart deleted file mode 100644 index 6c9f0cac8..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/percent_axis_spec.dart +++ /dev/null @@ -1,54 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; -import 'package:intl/intl.dart'; - -import '../numeric_extents.dart' show NumericExtents; -import 'axis_spec.dart' show AxisSpec, RenderSpec; -import 'numeric_axis_spec.dart' - show - BasicNumericTickFormatterSpec, - BasicNumericTickProviderSpec, - NumericAxisSpec, - NumericTickProviderSpec, - NumericTickFormatterSpec; - -/// Convenience [AxisSpec] specialized for numeric percentage axes. -@immutable -class PercentAxisSpec extends NumericAxisSpec { - /// Creates a [NumericAxisSpec] that is specialized for percentage data. - PercentAxisSpec({ - RenderSpec renderSpec, - NumericTickProviderSpec tickProviderSpec, - NumericTickFormatterSpec tickFormatterSpec, - bool showAxisLine, - NumericExtents viewport, - }) : super( - renderSpec: renderSpec, - tickProviderSpec: tickProviderSpec ?? - const BasicNumericTickProviderSpec(dataIsInWholeNumbers: false), - tickFormatterSpec: tickFormatterSpec ?? - BasicNumericTickFormatterSpec.fromNumberFormat( - NumberFormat.percentPattern()), - showAxisLine: showAxisLine, - viewport: viewport ?? const NumericExtents(0.0, 1.0)); - - @override - bool operator ==(Object other) => - other is PercentAxisSpec && - viewport == other.viewport && - super == (other); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/spec/tick_spec.dart b/web/charts/common/lib/src/chart/cartesian/axis/spec/tick_spec.dart deleted file mode 100644 index 1f81c05ba..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/spec/tick_spec.dart +++ /dev/null @@ -1,32 +0,0 @@ -// 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 'axis_spec.dart' show TextStyleSpec; - -/// Definition for a tick. -/// -/// Used to define a tick that is used by static tick provider. -class TickSpec { - final D value; - final String label; - final TextStyleSpec style; - - /// [value] the value of this tick - /// [label] optional label for this tick. If not set, uses the tick formatter - /// of the axis. - /// [style] optional style for this tick. If not set, uses the style of the - /// axis. - const TickSpec(this.value, {this.label, this.style}); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/static_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/static_tick_provider.dart deleted file mode 100644 index 9617bfd1a..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/static_tick_provider.dart +++ /dev/null @@ -1,106 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/chart_context.dart' show ChartContext; -import 'axis.dart' show AxisOrientation; -import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import 'numeric_scale.dart' show NumericScale; -import 'scale.dart' show MutableScale; -import 'spec/tick_spec.dart' show TickSpec; -import 'tick.dart' show Tick; -import 'tick_formatter.dart' show TickFormatter; -import 'tick_provider.dart' show TickProvider, TickHint; -import 'time/date_time_scale.dart' show DateTimeScale; - -/// A strategy that uses the ticks provided and only assigns positioning. -/// -/// The [TextStyle] is not overridden during tick draw strategy decorateTicks. -/// If it is null, then the default is used. -class StaticTickProvider extends TickProvider { - final List> tickSpec; - - StaticTickProvider(this.tickSpec); - - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required MutableScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - final ticks = >[]; - - bool allTicksHaveLabels = true; - - for (TickSpec spec in tickSpec) { - // When static ticks are being used with a numeric axis, extend the axis - // with the values specified. - if (scale is NumericScale || scale is DateTimeScale) { - scale.addDomain(spec.value); - } - - // Save off whether all ticks have labels. - allTicksHaveLabels = allTicksHaveLabels && (spec.label != null); - } - - // Use the formatter's label if the tick spec does not provide one. - List formattedValues; - if (allTicksHaveLabels == false) { - formattedValues = formatter.format( - tickSpec.map((spec) => spec.value).toList(), formatterValueCache, - stepSize: scale.domainStepSize); - } - - for (var i = 0; i < tickSpec.length; i++) { - final spec = tickSpec[i]; - // We still check if the spec is within the viewport because we do not - // extend the axis for OrdinalScale. - if (scale.compareDomainValueToViewport(spec.value) == 0) { - final tick = Tick( - value: spec.value, - textElement: graphicsFactory - .createTextElement(spec.label ?? formattedValues[i]), - locationPx: scale[spec.value]); - if (spec.style != null) { - tick.textElement.textStyle = graphicsFactory.createTextPaint() - ..fontFamily = spec.style.fontFamily - ..fontSize = spec.style.fontSize - ..color = spec.style.color; - } - ticks.add(tick); - } - } - - // Allow draw strategy to decorate the ticks. - tickDrawStrategy.decorateTicks(ticks); - - return ticks; - } - - @override - bool operator ==(other) => - other is StaticTickProvider && tickSpec == other.tickSpec; - - @override - int get hashCode => tickSpec.hashCode; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/tick.dart b/web/charts/common/lib/src/chart/cartesian/axis/tick.dart deleted file mode 100644 index 907f2f6b9..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/tick.dart +++ /dev/null @@ -1,47 +0,0 @@ -// 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 'package:meta/meta.dart'; -import '../../../common/text_element.dart'; - -/// A labeled point on an axis. -/// -/// [D] is the type of the value this tick is associated with. -class Tick { - /// The value that this tick represents - final D value; - - /// [TextElement] for this tick. - TextElement textElement; - - /// Location on the axis where this tick is rendered (in canvas coordinates). - double locationPx; - - /// Offset of the label for this tick from its location. - /// - /// This is a vertical offset for ticks on a vertical axis, or horizontal - /// offset for ticks on a horizontal axis. - double labelOffsetPx; - - Tick( - {@required this.value, - @required this.textElement, - this.locationPx, - this.labelOffsetPx}); - - @override - String toString() => 'Tick(value: $value, locationPx: $locationPx, ' - 'labelOffsetPx: $labelOffsetPx)'; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/tick_formatter.dart b/web/charts/common/lib/src/chart/cartesian/axis/tick_formatter.dart deleted file mode 100644 index 9f864b5b3..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/tick_formatter.dart +++ /dev/null @@ -1,107 +0,0 @@ -// 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 'package:intl/intl.dart'; -import '../../common/datum_details.dart' show MeasureFormatter; - -// TODO: Break out into separate files. - -/// A strategy used for converting domain values of the ticks into Strings. -/// -/// [D] is the domain type. -abstract class TickFormatter { - const TickFormatter(); - - /// Formats a list of tick values. - List format(List tickValues, Map cache, {num stepSize}); -} - -abstract class SimpleTickFormatterBase implements TickFormatter { - const SimpleTickFormatterBase(); - - @override - List format(List tickValues, Map cache, - {num stepSize}) => - tickValues.map((value) { - // Try to use the cached formats first. - String formattedString = cache[value]; - if (formattedString == null) { - formattedString = formatValue(value); - cache[value] = formattedString; - } - return formattedString; - }).toList(); - - /// Formats a single tick value. - String formatValue(D value); -} - -/// A strategy that converts tick labels using toString(). -class OrdinalTickFormatter extends SimpleTickFormatterBase { - const OrdinalTickFormatter(); - - @override - String formatValue(String value) => value; - - @override - bool operator ==(other) => other is OrdinalTickFormatter; - - @override - int get hashCode => 31; -} - -/// A strategy for formatting the labels on numeric ticks using [NumberFormat]. -/// -/// The default format is [NumberFormat.decimalPattern]. -class NumericTickFormatter extends SimpleTickFormatterBase { - final MeasureFormatter formatter; - - NumericTickFormatter._internal(this.formatter); - - /// Construct a a new [NumericTickFormatter]. - /// - /// [formatter] optionally specify a formatter to be used. Defaults to using - /// [NumberFormat.decimalPattern] if none is specified. - factory NumericTickFormatter({MeasureFormatter formatter}) { - formatter ??= _getFormatter(NumberFormat.decimalPattern()); - return NumericTickFormatter._internal(formatter); - } - - /// Constructs a new [NumericTickFormatter] that formats using [numberFormat]. - factory NumericTickFormatter.fromNumberFormat(NumberFormat numberFormat) { - return NumericTickFormatter._internal(_getFormatter(numberFormat)); - } - - /// Constructs a new formatter that uses [NumberFormat.compactCurrency]. - factory NumericTickFormatter.compactSimpleCurrency() { - return NumericTickFormatter._internal( - _getFormatter(NumberFormat.compactCurrency())); - } - - /// Returns a [MeasureFormatter] that calls format on [numberFormat]. - static MeasureFormatter _getFormatter(NumberFormat numberFormat) { - return (value) => numberFormat.format(value); - } - - @override - String formatValue(num value) => formatter(value); - - @override - bool operator ==(other) => - other is NumericTickFormatter && formatter == other.formatter; - - @override - int get hashCode => formatter.hashCode; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/tick_provider.dart deleted file mode 100644 index ef36ec03d..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/tick_provider.dart +++ /dev/null @@ -1,103 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/chart_context.dart' show ChartContext; -import 'axis.dart' show AxisOrientation; -import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import 'scale.dart' show MutableScale; -import 'tick.dart' show Tick; -import 'tick_formatter.dart' show TickFormatter; - -/// A strategy for selecting values for axis ticks based on the domain values. -/// -/// [D] is the domain type. -abstract class TickProvider { - /// Returns a list of ticks in value order that should be displayed. - /// - /// This method should not return null. If no ticks are desired an empty list - /// should be returned. - /// - /// [graphicsFactory] The graphics factory used for text measurement. - /// [scale] The scale of the data. - /// [formatter] The formatter to use for generating tick labels. - /// [orientation] Orientation of this axis ticks. - /// [tickDrawStrategy] Draw strategy for ticks. - /// [viewportExtensionEnabled] allow extending the viewport for 'niced' ticks. - /// [tickHint] tick values for provider to calculate a desired tick range. - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required covariant MutableScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }); -} - -/// A base tick provider. -abstract class BaseTickProvider implements TickProvider { - const BaseTickProvider(); - - /// Create ticks from [domainValues]. - List> createTicks( - List domainValues, { - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required MutableScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - num stepSize, - }) { - final ticks = >[]; - final labels = - formatter.format(domainValues, formatterValueCache, stepSize: stepSize); - - for (var i = 0; i < domainValues.length; i++) { - final value = domainValues[i]; - final tick = Tick( - value: value, - textElement: graphicsFactory.createTextElement(labels[i]), - locationPx: scale[value]); - - ticks.add(tick); - } - - // Allow draw strategy to decorate the ticks. - tickDrawStrategy.decorateTicks(ticks); - - return ticks; - } -} - -/// A hint for the tick provider to determine step size and tick count. -class TickHint { - /// The starting hint tick value. - final D start; - - /// The ending hint tick value. - final D end; - - /// Number of ticks. - final int tickCount; - - TickHint(this.start, this.end, {this.tickCount}); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart deleted file mode 100644 index 99a1475a2..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart +++ /dev/null @@ -1,176 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../../common/date_time_factory.dart' show DateTimeFactory; -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show AxisOrientation; -import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import '../tick.dart' show Tick; -import '../tick_formatter.dart' show TickFormatter; -import '../tick_provider.dart' show TickProvider, TickHint; -import 'date_time_scale.dart' show DateTimeScale; -import 'day_time_stepper.dart' show DayTimeStepper; -import 'hour_time_stepper.dart' show HourTimeStepper; -import 'minute_time_stepper.dart' show MinuteTimeStepper; -import 'month_time_stepper.dart' show MonthTimeStepper; -import 'time_range_tick_provider.dart' show TimeRangeTickProvider; -import 'time_range_tick_provider_impl.dart' show TimeRangeTickProviderImpl; -import 'year_time_stepper.dart' show YearTimeStepper; - -/// Tick provider for date and time. -/// -/// When determining the ticks for a given domain, the provider will use choose -/// one of the internal tick providers appropriate to the size of the data's -/// domain range. It does this in an attempt to ensure there are at least 3 -/// ticks, before jumping to the next more fine grain provider. The 3 tick -/// minimum is not a hard rule as some of the ticks might be eliminated because -/// of collisions, but the data was within the targeted range. -/// -/// Once a tick provider is chosen the selection of ticks is done by the child -/// tick provider. -class AutoAdjustingDateTimeTickProvider implements TickProvider { - /// List of tick providers to be selected from. - final List _potentialTickProviders; - - AutoAdjustingDateTimeTickProvider._internal( - List tickProviders) - : _potentialTickProviders = tickProviders; - - /// Creates a default [AutoAdjustingDateTimeTickProvider] for day and time. - factory AutoAdjustingDateTimeTickProvider.createDefault( - DateTimeFactory dateTimeFactory) { - return AutoAdjustingDateTimeTickProvider._internal([ - createYearTickProvider(dateTimeFactory), - createMonthTickProvider(dateTimeFactory), - createDayTickProvider(dateTimeFactory), - createHourTickProvider(dateTimeFactory), - createMinuteTickProvider(dateTimeFactory) - ]); - } - - /// Creates a default [AutoAdjustingDateTimeTickProvider] for day only. - factory AutoAdjustingDateTimeTickProvider.createWithoutTime( - DateTimeFactory dateTimeFactory) { - return AutoAdjustingDateTimeTickProvider._internal([ - createYearTickProvider(dateTimeFactory), - createMonthTickProvider(dateTimeFactory), - createDayTickProvider(dateTimeFactory) - ]); - } - - /// Creates [AutoAdjustingDateTimeTickProvider] with custom tick providers. - /// - /// [potentialTickProviders] must have at least one [TimeRangeTickProvider] - /// and this list of tick providers are used in the order they are provided. - factory AutoAdjustingDateTimeTickProvider.createWith( - List potentialTickProviders) { - if (potentialTickProviders == null || potentialTickProviders.isEmpty) { - throw ArgumentError('At least one TimeRangeTickProvider is required'); - } - - return AutoAdjustingDateTimeTickProvider._internal(potentialTickProviders); - } - - /// Generates a list of ticks for the given data which should not collide - /// unless the range is not large enough. - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required DateTimeScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - List tickProviders; - - /// If tick hint is provided, use the closest tick provider, otherwise - /// look through the tick providers for one that provides sufficient ticks - /// for the viewport. - if (tickHint != null) { - tickProviders = [_getClosestTickProvider(tickHint)]; - } else { - tickProviders = _potentialTickProviders; - } - - final lastTickProvider = tickProviders.last; - - final viewport = scale.viewportDomain; - for (final tickProvider in tickProviders) { - final isLastProvider = (tickProvider == lastTickProvider); - if (isLastProvider || - tickProvider.providesSufficientTicksForRange(viewport)) { - return tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - orientation: orientation, - ); - } - } - - return >[]; - } - - /// Find the closest tick provider based on the tick hint. - TimeRangeTickProvider _getClosestTickProvider(TickHint tickHint) { - final stepSize = ((tickHint.end.difference(tickHint.start).inMilliseconds) / - (tickHint.tickCount - 1)) - .round(); - - int minDifference; - TimeRangeTickProvider closestTickProvider; - - for (final tickProvider in _potentialTickProviders) { - final difference = - (stepSize - tickProvider.getClosestStepSize(stepSize)).abs(); - if (minDifference == null || minDifference > difference) { - minDifference = difference; - closestTickProvider = tickProvider; - } - } - - return closestTickProvider; - } - - static TimeRangeTickProvider createYearTickProvider( - DateTimeFactory dateTimeFactory) => - TimeRangeTickProviderImpl(YearTimeStepper(dateTimeFactory)); - - static TimeRangeTickProvider createMonthTickProvider( - DateTimeFactory dateTimeFactory) => - TimeRangeTickProviderImpl(MonthTimeStepper(dateTimeFactory)); - - static TimeRangeTickProvider createDayTickProvider( - DateTimeFactory dateTimeFactory) => - TimeRangeTickProviderImpl(DayTimeStepper(dateTimeFactory)); - - static TimeRangeTickProvider createHourTickProvider( - DateTimeFactory dateTimeFactory) => - TimeRangeTickProviderImpl(HourTimeStepper(dateTimeFactory)); - - static TimeRangeTickProvider createMinuteTickProvider( - DateTimeFactory dateTimeFactory) => - TimeRangeTickProviderImpl(MinuteTimeStepper(dateTimeFactory)); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/base_time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/base_time_stepper.dart deleted file mode 100644 index dcb3def86..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/base_time_stepper.dart +++ /dev/null @@ -1,141 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart'; -import 'date_time_extents.dart' show DateTimeExtents; -import 'time_stepper.dart' - show TimeStepper, TimeStepIteratorFactory, TimeStepIterator; - -/// A base stepper for operating with DateTimeFactory and time range steps. -abstract class BaseTimeStepper implements TimeStepper { - /// The factory to generate a DateTime object. - /// - /// This is needed because Dart's DateTime does not handle time zone. - /// There is a time zone aware library that we could use that implements the - /// DateTime interface. - final DateTimeFactory dateTimeFactory; - - _TimeStepIteratorFactoryImpl _stepsIterable; - - BaseTimeStepper(this.dateTimeFactory); - - /// Get the step time before or on the given [time] from [tickIncrement]. - DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement); - - /// Get the next step time after [time] from [tickIncrement]. - DateTime getNextStepTime(DateTime time, int tickIncrement); - - @override - int getStepCountBetween(DateTimeExtents timeExtent, int tickIncrement) { - checkTickIncrement(tickIncrement); - final min = timeExtent.start; - final max = timeExtent.end; - var time = getStepTimeAfterInclusive(min, tickIncrement); - - var cnt = 0; - while (time.compareTo(max) <= 0) { - cnt++; - time = getNextStepTime(time, tickIncrement); - } - return cnt; - } - - @override - TimeStepIteratorFactory getSteps(DateTimeExtents timeExtent) { - // Keep the steps iterable unless time extent changes, so the same iterator - // can be used and reset for different increments. - if (_stepsIterable == null || _stepsIterable.timeExtent != timeExtent) { - _stepsIterable = _TimeStepIteratorFactoryImpl(timeExtent, this); - } - return _stepsIterable; - } - - @override - DateTimeExtents updateBoundingSteps(DateTimeExtents timeExtent) { - final stepBefore = getStepTimeBeforeInclusive(timeExtent.start, 1); - final stepAfter = getStepTimeAfterInclusive(timeExtent.end, 1); - - return DateTimeExtents(start: stepBefore, end: stepAfter); - } - - DateTime getStepTimeAfterInclusive(DateTime time, int tickIncrement) { - final boundedStart = getStepTimeBeforeInclusive(time, tickIncrement); - if (boundedStart == time) { - return boundedStart; - } - return getNextStepTime(boundedStart, tickIncrement); - } -} - -class _TimeStepIteratorImpl implements TimeStepIterator { - final DateTime extentStartTime; - final DateTime extentEndTime; - final BaseTimeStepper stepper; - DateTime _current; - int _tickIncrement = 1; - - _TimeStepIteratorImpl( - this.extentStartTime, this.extentEndTime, this.stepper) { - reset(_tickIncrement); - } - - @override - bool moveNext() { - if (_current == null) { - _current = - stepper.getStepTimeAfterInclusive(extentStartTime, _tickIncrement); - } else { - _current = stepper.getNextStepTime(_current, _tickIncrement); - } - - return _current.compareTo(extentEndTime) <= 0; - } - - @override - DateTime get current => _current; - - @override - TimeStepIterator reset(int tickIncrement) { - checkTickIncrement(tickIncrement); - _tickIncrement = tickIncrement; - _current = null; - return this; - } -} - -class _TimeStepIteratorFactoryImpl extends TimeStepIteratorFactory { - final DateTimeExtents timeExtent; - final _TimeStepIteratorImpl _timeStepIterator; - - _TimeStepIteratorFactoryImpl._internal( - _TimeStepIteratorImpl timeStepIterator, this.timeExtent) - : _timeStepIterator = timeStepIterator; - - factory _TimeStepIteratorFactoryImpl( - DateTimeExtents timeExtent, BaseTimeStepper stepper) { - final startTime = timeExtent.start; - final endTime = timeExtent.end; - return _TimeStepIteratorFactoryImpl._internal( - _TimeStepIteratorImpl(startTime, endTime, stepper), timeExtent); - } - - @override - TimeStepIterator get iterator => _timeStepIterator; -} - -void checkTickIncrement(int tickIncrement) { - /// tickIncrement must be greater than 0 - assert(tickIncrement > 0); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_axis.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_axis.dart deleted file mode 100644 index 99e067756..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_axis.dart +++ /dev/null @@ -1,40 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import '../axis.dart' show Axis; -import '../tick_formatter.dart' show TickFormatter; -import '../tick_provider.dart' show TickProvider; -import 'auto_adjusting_date_time_tick_provider.dart' - show AutoAdjustingDateTimeTickProvider; -import 'date_time_extents.dart' show DateTimeExtents; -import 'date_time_scale.dart' show DateTimeScale; -import 'date_time_tick_formatter.dart' show DateTimeTickFormatter; - -class DateTimeAxis extends Axis { - DateTimeAxis(DateTimeFactory dateTimeFactory, - {TickProvider tickProvider, TickFormatter tickFormatter}) - : super( - tickProvider: tickProvider ?? - AutoAdjustingDateTimeTickProvider.createDefault(dateTimeFactory), - tickFormatter: - tickFormatter ?? DateTimeTickFormatter(dateTimeFactory), - scale: DateTimeScale(dateTimeFactory), - ); - - void setScaleViewport(DateTimeExtents viewport) { - (mutableScale as DateTimeScale).viewportDomain = viewport; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_scale.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_scale.dart deleted file mode 100644 index c02bc45ab..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_scale.dart +++ /dev/null @@ -1,138 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import '../linear/linear_scale.dart' show LinearScale; -import '../numeric_extents.dart' show NumericExtents; -import '../scale.dart' - show MutableScale, StepSizeConfig, RangeBandConfig, ScaleOutputExtent; -import 'date_time_extents.dart' show DateTimeExtents; - -/// [DateTimeScale] is a wrapper for [LinearScale]. -/// [DateTime] values are converted to millisecondsSinceEpoch and passed to the -/// [LinearScale]. -class DateTimeScale extends MutableScale { - final DateTimeFactory dateTimeFactory; - final LinearScale _linearScale; - - DateTimeScale(this.dateTimeFactory) : _linearScale = LinearScale(); - - DateTimeScale._copy(DateTimeScale other) - : dateTimeFactory = other.dateTimeFactory, - _linearScale = other._linearScale.copy(); - - @override - num operator [](DateTime domainValue) => - _linearScale[domainValue.millisecondsSinceEpoch]; - - @override - DateTime reverse(double pixelLocation) => - dateTimeFactory.createDateTimeFromMilliSecondsSinceEpoch( - _linearScale.reverse(pixelLocation).round()); - - @override - void resetDomain() { - _linearScale.resetDomain(); - } - - @override - set stepSizeConfig(StepSizeConfig config) { - _linearScale.stepSizeConfig = config; - } - - @override - StepSizeConfig get stepSizeConfig => _linearScale.stepSizeConfig; - - @override - set rangeBandConfig(RangeBandConfig barGroupWidthConfig) { - _linearScale.rangeBandConfig = barGroupWidthConfig; - } - - @override - void setViewportSettings(double viewportScale, double viewportTranslatePx) { - _linearScale.setViewportSettings(viewportScale, viewportTranslatePx); - } - - @override - set range(ScaleOutputExtent extent) { - _linearScale.range = extent; - } - - @override - void addDomain(DateTime domainValue) { - _linearScale.addDomain(domainValue.millisecondsSinceEpoch); - } - - @override - void resetViewportSettings() { - _linearScale.resetViewportSettings(); - } - - DateTimeExtents get viewportDomain { - final extents = _linearScale.viewportDomain; - return DateTimeExtents( - start: dateTimeFactory - .createDateTimeFromMilliSecondsSinceEpoch(extents.min.toInt()), - end: dateTimeFactory - .createDateTimeFromMilliSecondsSinceEpoch(extents.max.toInt())); - } - - set viewportDomain(DateTimeExtents extents) { - _linearScale.viewportDomain = NumericExtents( - extents.start.millisecondsSinceEpoch, - extents.end.millisecondsSinceEpoch); - } - - @override - DateTimeScale copy() => DateTimeScale._copy(this); - - @override - double get viewportTranslatePx => _linearScale.viewportTranslatePx; - - @override - double get viewportScalingFactor => _linearScale.viewportScalingFactor; - - @override - bool isRangeValueWithinViewport(double rangeValue) => - _linearScale.isRangeValueWithinViewport(rangeValue); - - @override - int compareDomainValueToViewport(DateTime domainValue) => _linearScale - .compareDomainValueToViewport(domainValue.millisecondsSinceEpoch); - - @override - double get rangeBand => _linearScale.rangeBand; - - @override - double get stepSize => _linearScale.stepSize; - - @override - double get domainStepSize => _linearScale.domainStepSize; - - @override - RangeBandConfig get rangeBandConfig => _linearScale.rangeBandConfig; - - @override - int get rangeWidth => _linearScale.rangeWidth; - - @override - ScaleOutputExtent get range => _linearScale.range; - - @override - bool canTranslate(DateTime domainValue) => - _linearScale.canTranslate(domainValue.millisecondsSinceEpoch); - - NumericExtents get dataExtent => _linearScale.dataExtent; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_tick_formatter.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_tick_formatter.dart deleted file mode 100644 index 4bf7d12c3..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_tick_formatter.dart +++ /dev/null @@ -1,218 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../../common/date_time_factory.dart' show DateTimeFactory; -import '../tick_formatter.dart' show TickFormatter; -import 'hour_tick_formatter.dart' show HourTickFormatter; -import 'time_tick_formatter.dart' show TimeTickFormatter; -import 'time_tick_formatter_impl.dart' - show CalendarField, TimeTickFormatterImpl; - -/// A [TickFormatter] that formats date/time values based on minimum difference -/// between subsequent ticks. -/// -/// This formatter assumes that the Tick values passed in are sorted in -/// increasing order. -/// -/// This class is setup with a list of formatters that format the input ticks at -/// a given time resolution. The time resolution which will accurately display -/// the difference between 2 subsequent ticks is picked. Each time resolution -/// can be setup with a [TimeTickFormatter], which is used to format ticks as -/// regular or transition ticks based on whether the tick has crossed the time -/// boundary defined in the [TimeTickFormatter]. -class DateTimeTickFormatter implements TickFormatter { - static const int SECOND = 1000; - static const int MINUTE = 60 * SECOND; - static const int HOUR = 60 * MINUTE; - static const int DAY = 24 * HOUR; - - /// Used for the case when there is only one formatter. - static const int ANY = -1; - - final Map _timeFormatters; - - /// Creates a [DateTimeTickFormatter] that works well with time tick provider - /// classes. - /// - /// The default formatter makes assumptions on border cases that time tick - /// providers will still provide ticks that make sense. Example: Tick provider - /// does not provide ticks with 23 hour intervals. For custom tick providers - /// where these assumptions are not correct, please create a custom - /// [TickFormatter]. - factory DateTimeTickFormatter(DateTimeFactory dateTimeFactory, - {Map overrides}) { - final Map map = { - MINUTE: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'mm', - transitionFormat: 'h mm', - transitionField: CalendarField.hourOfDay), - HOUR: HourTickFormatter( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'h', - transitionFormat: 'MMM d ha', - noonFormat: 'ha'), - 23 * HOUR: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'd', - transitionFormat: 'MMM d', - transitionField: CalendarField.month), - 28 * DAY: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'MMM', - transitionFormat: 'MMM yyyy', - transitionField: CalendarField.year), - 364 * DAY: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'yyyy', - transitionFormat: 'yyyy', - transitionField: CalendarField.year), - }; - - // Allow the user to override some of the defaults. - if (overrides != null) { - map.addAll(overrides); - } - - return DateTimeTickFormatter._internal(map); - } - - /// Creates a [DateTimeTickFormatter] without the time component. - factory DateTimeTickFormatter.withoutTime(DateTimeFactory dateTimeFactory) { - return DateTimeTickFormatter._internal({ - 23 * HOUR: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'd', - transitionFormat: 'MMM d', - transitionField: CalendarField.month), - 28 * DAY: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'MMM', - transitionFormat: 'MMM yyyy', - transitionField: CalendarField.year), - 365 * DAY: TimeTickFormatterImpl( - dateTimeFactory: dateTimeFactory, - simpleFormat: 'yyyy', - transitionFormat: 'yyyy', - transitionField: CalendarField.year), - }); - } - - /// Creates a [DateTimeTickFormatter] that formats all ticks the same. - /// - /// Only use this formatter for data with fixed intervals, otherwise use the - /// default, or build from scratch. - /// - /// [formatter] The format for all ticks. - factory DateTimeTickFormatter.uniform(TimeTickFormatter formatter) { - return DateTimeTickFormatter._internal({ANY: formatter}); - } - - /// Creates a [DateTimeTickFormatter] that formats ticks with [formatters]. - /// - /// The formatters are expected to be provided with keys in increasing order. - factory DateTimeTickFormatter.withFormatters( - Map formatters) { - // Formatters must be non empty. - if (formatters == null || formatters.isEmpty) { - throw ArgumentError('At least one TimeTickFormatter is required.'); - } - - return DateTimeTickFormatter._internal(formatters); - } - - DateTimeTickFormatter._internal(this._timeFormatters) { - // If there is only one formatter, just use this one and skip this check. - if (_timeFormatters.length == 1) { - return; - } - _checkPositiveAndSorted(_timeFormatters.keys); - } - - @override - List format(List tickValues, Map cache, - {@required num stepSize}) { - final tickLabels = []; - if (tickValues.isEmpty) { - return tickLabels; - } - - // Find the formatter that is the largest interval that has enough - // resolution to describe the difference between ticks. If no such formatter - // exists pick the highest res one. - var formatter = _timeFormatters[_timeFormatters.keys.first]; - var formatterFound = false; - if (_timeFormatters.keys.first == ANY) { - formatterFound = true; - } else { - int minTimeBetweenTicks = stepSize.toInt(); - - // TODO: Skip the formatter if the formatter's step size is - // smaller than the minimum step size of the data. - - var keys = _timeFormatters.keys.iterator; - while (keys.moveNext() && !formatterFound) { - if (keys.current > minTimeBetweenTicks) { - formatterFound = true; - } else { - formatter = _timeFormatters[keys.current]; - } - } - } - - // Format the ticks. - final tickValuesIt = tickValues.iterator; - - var tickValue = (tickValuesIt..moveNext()).current; - var prevTickValue = tickValue; - tickLabels.add(formatter.formatFirstTick(tickValue)); - - while (tickValuesIt.moveNext()) { - tickValue = tickValuesIt.current; - if (formatter.isTransition(tickValue, prevTickValue)) { - tickLabels.add(formatter.formatTransitionTick(tickValue)); - } else { - tickLabels.add(formatter.formatSimpleTick(tickValue)); - } - prevTickValue = tickValue; - } - - return tickLabels; - } - - static void _checkPositiveAndSorted(Iterable values) { - final valuesIterator = values.iterator; - var prev = (valuesIterator..moveNext()).current; - var isSorted = true; - - // Only need to check the first value, because the values after are expected - // to be greater. - if (prev <= 0) { - throw ArgumentError('Formatter keys must be positive'); - } - - while (valuesIterator.moveNext() && isSorted) { - isSorted = prev < valuesIterator.current; - prev = valuesIterator.current; - } - - if (!isSorted) { - throw ArgumentError( - 'Formatters must be sorted with keys in increasing order'); - } - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/day_time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/day_time_stepper.dart deleted file mode 100644 index ba7f88909..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/day_time_stepper.dart +++ /dev/null @@ -1,81 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import 'base_time_stepper.dart' show BaseTimeStepper; - -/// Day stepper. -class DayTimeStepper extends BaseTimeStepper { - // TODO: Remove the 14 day increment if we add week stepper. - static const _defaultIncrements = [1, 2, 3, 7, 14]; - static const _hoursInDay = 24; - - final List _allowedTickIncrements; - - DayTimeStepper._internal( - DateTimeFactory dateTimeFactory, List increments) - : _allowedTickIncrements = increments, - super(dateTimeFactory); - - factory DayTimeStepper(DateTimeFactory dateTimeFactory, - {List allowedTickIncrements}) { - // Set the default increments if null. - allowedTickIncrements ??= _defaultIncrements; - - // Must have at least one increment option. - assert(allowedTickIncrements.isNotEmpty); - // All increments must be > 0. - assert(allowedTickIncrements.any((increment) => increment <= 0) == false); - - return DayTimeStepper._internal(dateTimeFactory, allowedTickIncrements); - } - - @override - int get typicalStepSizeMs => _hoursInDay * 3600 * 1000; - - @override - List get allowedTickIncrements => _allowedTickIncrements; - - /// Get the step time before or on the given [time] from [tickIncrement]. - /// - /// Increments are based off the beginning of the month. - /// Ex. 5 day increments in a month is 1,6,11,16,21,26,31 - /// Ex. Time is Aug 20, increment is 1 day. Returns Aug 20. - /// Ex. Time is Aug 20, increment is 2 days. Returns Aug 19 because 2 day - /// increments in a month is 1,3,5,7,9,11,13,15,17,19,21.... - @override - DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { - final dayRemainder = (time.day - 1) % tickIncrement; - // Subtract an extra hour in case stepping through a daylight saving change. - final dayBefore = dayRemainder > 0 - ? time.subtract(Duration(hours: (_hoursInDay * dayRemainder) - 1)) - : time; - // Explicitly leaving off hours and beyond to truncate to start of day. - final stepBefore = dateTimeFactory.createDateTime( - dayBefore.year, dayBefore.month, dayBefore.day); - - return stepBefore; - } - - @override - DateTime getNextStepTime(DateTime time, int tickIncrement) { - // Add an extra hour in case stepping through a daylight saving change. - final stepAfter = - time.add(Duration(hours: (_hoursInDay * tickIncrement) + 1)); - // Explicitly leaving off hours and beyond to truncate to start of day. - return dateTimeFactory.createDateTime( - stepAfter.year, stepAfter.month, stepAfter.day); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/hour_tick_formatter.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/hour_tick_formatter.dart deleted file mode 100644 index ef262c3dc..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/hour_tick_formatter.dart +++ /dev/null @@ -1,45 +0,0 @@ -// 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 'package:intl/intl.dart' show DateFormat; -import 'package:meta/meta.dart' show required; -import '../../../../common/date_time_factory.dart'; -import 'time_tick_formatter_impl.dart' - show CalendarField, TimeTickFormatterImpl; - -/// Hour specific tick formatter which will format noon differently. -class HourTickFormatter extends TimeTickFormatterImpl { - DateFormat _noonFormat; - - HourTickFormatter( - {@required DateTimeFactory dateTimeFactory, - @required String simpleFormat, - @required String transitionFormat, - @required String noonFormat}) - : super( - dateTimeFactory: dateTimeFactory, - simpleFormat: simpleFormat, - transitionFormat: transitionFormat, - transitionField: CalendarField.date) { - _noonFormat = dateTimeFactory.createDateFormat(noonFormat); - } - - @override - String formatSimpleTick(DateTime date) { - return (date.hour == 12) - ? _noonFormat.format(date) - : super.formatSimpleTick(date); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/hour_time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/hour_time_stepper.dart deleted file mode 100644 index c259fc830..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/hour_time_stepper.dart +++ /dev/null @@ -1,87 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import 'base_time_stepper.dart' show BaseTimeStepper; - -/// Hour stepper. -class HourTimeStepper extends BaseTimeStepper { - static const _defaultIncrements = [1, 2, 3, 4, 6, 12, 24]; - static const _hoursInDay = 24; - static const _millisecondsInHour = 3600 * 1000; - - final List _allowedTickIncrements; - - HourTimeStepper._internal( - DateTimeFactory dateTimeFactory, List increments) - : _allowedTickIncrements = increments, - super(dateTimeFactory); - - factory HourTimeStepper(DateTimeFactory dateTimeFactory, - {List allowedTickIncrements}) { - // Set the default increments if null. - allowedTickIncrements ??= _defaultIncrements; - - // Must have at least one increment option. - assert(allowedTickIncrements.isNotEmpty); - // All increments must be between 1 and 24 inclusive. - assert(allowedTickIncrements - .any((increment) => increment <= 0 || increment > 24) == - false); - - return HourTimeStepper._internal(dateTimeFactory, allowedTickIncrements); - } - - @override - int get typicalStepSizeMs => _millisecondsInHour; - - @override - List get allowedTickIncrements => _allowedTickIncrements; - - /// Get the step time before or on the given [time] from [tickIncrement]. - /// - /// Guarantee a step at the start of the next day. - /// Ex. Time is Aug 20 10 AM, increment is 1 hour. Returns 10 AM. - /// Ex. Time is Aug 20 6 AM, increment is 4 hours. Returns 4 AM. - @override - DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { - final nextDay = dateTimeFactory - .createDateTime(time.year, time.month, time.day) - .add(Duration(hours: _hoursInDay + 1)); - final nextDayStart = dateTimeFactory.createDateTime( - nextDay.year, nextDay.month, nextDay.day); - - final hoursToNextDay = - ((nextDayStart.millisecondsSinceEpoch - time.millisecondsSinceEpoch) / - _millisecondsInHour) - .ceil(); - - final hoursRemainder = hoursToNextDay % tickIncrement; - final rewindHours = - hoursRemainder == 0 ? 0 : tickIncrement - hoursRemainder; - final stepBefore = dateTimeFactory.createDateTime( - time.year, time.month, time.day, time.hour - rewindHours); - - return stepBefore; - } - - /// Get next step time. - /// - /// [time] is expected to be a [DateTime] with the hour at start of the hour. - @override - DateTime getNextStepTime(DateTime time, int tickIncrement) { - return time.add(Duration(hours: tickIncrement)); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/minute_time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/minute_time_stepper.dart deleted file mode 100644 index 0b1b79227..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/minute_time_stepper.dart +++ /dev/null @@ -1,77 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import 'base_time_stepper.dart'; - -/// Minute stepper where ticks generated aligns with the hour. -class MinuteTimeStepper extends BaseTimeStepper { - static const _defaultIncrements = [5, 10, 15, 20, 30]; - static const _millisecondsInMinute = 60 * 1000; - - final List _allowedTickIncrements; - - MinuteTimeStepper._internal( - DateTimeFactory dateTimeFactory, List increments) - : _allowedTickIncrements = increments, - super(dateTimeFactory); - - factory MinuteTimeStepper(DateTimeFactory dateTimeFactory, - {List allowedTickIncrements}) { - // Set the default increments if null. - allowedTickIncrements ??= _defaultIncrements; - - // Must have at least one increment - assert(allowedTickIncrements.isNotEmpty); - // Increment must be between 1 and 60 inclusive. - assert(allowedTickIncrements - .any((increment) => increment <= 0 || increment > 60) == - false); - - return MinuteTimeStepper._internal(dateTimeFactory, allowedTickIncrements); - } - - @override - int get typicalStepSizeMs => _millisecondsInMinute; - - List get allowedTickIncrements => _allowedTickIncrements; - - /// Picks a tick start time that guarantees the start of the hour is included. - /// - /// Ex. Time is 3:46, increments is 5 minutes, step before is 3:45, because - /// we can guarantee a step at 4:00. - @override - DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { - final nextHourStart = time.millisecondsSinceEpoch + - (60 - time.minute) * _millisecondsInMinute; - - final minutesToNextHour = - ((nextHourStart - time.millisecondsSinceEpoch) / _millisecondsInMinute) - .ceil(); - - final minRemainder = minutesToNextHour % tickIncrement; - final rewindMinutes = minRemainder == 0 ? 0 : tickIncrement - minRemainder; - - final stepBefore = dateTimeFactory.createDateTimeFromMilliSecondsSinceEpoch( - time.millisecondsSinceEpoch - rewindMinutes * _millisecondsInMinute); - - return stepBefore; - } - - @override - DateTime getNextStepTime(DateTime time, int tickIncrement) { - return time.add(Duration(minutes: tickIncrement)); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/month_time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/month_time_stepper.dart deleted file mode 100644 index 300f4689b..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/month_time_stepper.dart +++ /dev/null @@ -1,76 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import 'base_time_stepper.dart' show BaseTimeStepper; - -/// Month stepper. -class MonthTimeStepper extends BaseTimeStepper { - static const _defaultIncrements = [1, 2, 3, 4, 6, 12]; - - final List _allowedTickIncrements; - - MonthTimeStepper._internal( - DateTimeFactory dateTimeFactory, List increments) - : _allowedTickIncrements = increments, - super(dateTimeFactory); - - factory MonthTimeStepper(DateTimeFactory dateTimeFactory, - {List allowedTickIncrements}) { - // Set the default increments if null. - allowedTickIncrements ??= _defaultIncrements; - - // Must have at least one increment option. - assert(allowedTickIncrements.isNotEmpty); - // All increments must be > 0. - assert(allowedTickIncrements.any((increment) => increment <= 0) == false); - - return MonthTimeStepper._internal(dateTimeFactory, allowedTickIncrements); - } - - @override - int get typicalStepSizeMs => 30 * 24 * 3600 * 1000; - - @override - List get allowedTickIncrements => _allowedTickIncrements; - - /// Guarantee a step ending in the last month of the year. - /// - /// If date is 2017 Oct and increments is 6, the step before is 2017 June. - @override - DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { - final monthRemainder = time.month % tickIncrement; - var newMonth = (time.month - monthRemainder) % DateTime.monthsPerYear; - // Handles the last month of the year (December) edge case. - // Ex. When month is December and increment is 1 - if (time.month == DateTime.monthsPerYear && newMonth == 0) { - newMonth = DateTime.monthsPerYear; - } - final newYear = - time.year - (monthRemainder / DateTime.monthsPerYear).floor(); - - return dateTimeFactory.createDateTime(newYear, newMonth); - } - - @override - DateTime getNextStepTime(DateTime time, int tickIncrement) { - final incrementedMonth = time.month + tickIncrement; - final newMonth = incrementedMonth % DateTime.monthsPerYear; - final newYear = - time.year + (incrementedMonth / DateTime.monthsPerYear).floor(); - - return dateTimeFactory.createDateTime(newYear, newMonth); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider.dart deleted file mode 100644 index b768d4883..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider.dart +++ /dev/null @@ -1,29 +0,0 @@ -// 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 '../tick_provider.dart' show BaseTickProvider; -import '../time/date_time_extents.dart' show DateTimeExtents; - -/// Provides ticks for a particular time unit. -/// -/// Used by [AutoAdjustingDateTimeTickProvider]. -abstract class TimeRangeTickProvider extends BaseTickProvider { - /// Returns if this tick provider will produce a sufficient number of ticks - /// for [domainExtents]. - bool providesSufficientTicksForRange(DateTimeExtents domainExtents); - - /// Find the closet step size, from provided step size, in milliseconds. - int getClosestStepSize(int stepSize); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider_impl.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider_impl.dart deleted file mode 100644 index 32455af06..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider_impl.dart +++ /dev/null @@ -1,129 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/chart_context.dart' show ChartContext; -import '../axis.dart' show AxisOrientation; -import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; -import '../tick.dart' show Tick; -import '../tick_formatter.dart' show TickFormatter; -import '../tick_provider.dart' show TickHint; -import 'date_time_extents.dart' show DateTimeExtents; -import 'date_time_scale.dart' show DateTimeScale; -import 'time_range_tick_provider.dart' show TimeRangeTickProvider; -import 'time_stepper.dart' show TimeStepper; - -// Contains all the common code for the time range tick providers. -class TimeRangeTickProviderImpl extends TimeRangeTickProvider { - final int requiredMinimumTicks; - final TimeStepper timeStepper; - - TimeRangeTickProviderImpl(this.timeStepper, {this.requiredMinimumTicks = 3}); - - @override - bool providesSufficientTicksForRange(DateTimeExtents domainExtents) { - final cnt = timeStepper.getStepCountBetween(domainExtents, 1); - return cnt >= requiredMinimumTicks; - } - - /// Find the closet step size, from provided step size, in milliseconds. - @override - int getClosestStepSize(int stepSize) { - return timeStepper.typicalStepSizeMs * - _getClosestIncrementFromStepSize(stepSize); - } - - // Find the increment that is closest to the step size. - int _getClosestIncrementFromStepSize(int stepSize) { - int minDifference; - int closestIncrement; - - for (int increment in timeStepper.allowedTickIncrements) { - final difference = - (stepSize - (timeStepper.typicalStepSizeMs * increment)).abs(); - if (minDifference == null || minDifference > difference) { - minDifference = difference; - closestIncrement = increment; - } - } - - return closestIncrement; - } - - @override - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required DateTimeScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - List> currentTicks; - final tickValues = []; - final timeStepIt = timeStepper.getSteps(scale.viewportDomain).iterator; - - // Try different tickIncrements and choose the first that has no collisions. - // If none exist use the last one which should have the fewest ticks and - // hope that the renderer will resolve collisions. - // - // If a tick hint was provided, use the tick hint to search for the closest - // increment and use that. - List allowedTickIncrements; - if (tickHint != null) { - final stepSize = tickHint.end.difference(tickHint.start).inMilliseconds; - allowedTickIncrements = [_getClosestIncrementFromStepSize(stepSize)]; - } else { - allowedTickIncrements = timeStepper.allowedTickIncrements; - } - - for (int i = 0; i < allowedTickIncrements.length; i++) { - // Create tick values with a specified increment. - final tickIncrement = allowedTickIncrements[i]; - tickValues.clear(); - timeStepIt.reset(tickIncrement); - while (timeStepIt.moveNext()) { - tickValues.add(timeStepIt.current); - } - - // Create ticks - currentTicks = createTicks(tickValues, - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - stepSize: timeStepper.typicalStepSizeMs * tickIncrement); - - // Request collision check from draw strategy. - final collisionReport = - tickDrawStrategy.collides(currentTicks, orientation); - - if (!collisionReport.ticksCollide) { - // Return the first non colliding ticks. - return currentTicks; - } - } - - // If all ticks collide, return the last generated ticks. - return currentTicks; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/time_stepper.dart deleted file mode 100644 index 480f7284f..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/time_stepper.dart +++ /dev/null @@ -1,60 +0,0 @@ -// 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 'date_time_extents.dart' show DateTimeExtents; - -/// Represents the step/tick information for the given time range. -abstract class TimeStepper { - /// Get new bounding extents to the ticks that would contain the given - /// timeExtents. - DateTimeExtents updateBoundingSteps(DateTimeExtents timeExtents); - - /// Returns the number steps/ticks are between the given extents inclusive. - /// - /// Does not extend the extents to the bounding ticks. - int getStepCountBetween(DateTimeExtents timeExtents, int tickIncrement); - - /// Generates an Iterable for iterating over the time steps bounded by the - /// given timeExtents. The desired tickIncrement can be set on the returned - /// [TimeStepIteratorFactory]. - TimeStepIteratorFactory getSteps(DateTimeExtents timeExtents); - - /// Returns the typical stepSize for this stepper assuming increment by 1. - int get typicalStepSizeMs; - - /// An ordered list of step increments that makes sense given the step. - /// - /// Example: hours may increment by 1, 2, 3, 4, 6, 12. It doesn't make sense - /// to increment hours by 7. - List get allowedTickIncrements; -} - -/// Iterator with a reset function that can be used multiple times to avoid -/// object instantiation during the Android layout/draw phases. -abstract class TimeStepIterator extends Iterator { - /// Reset the iterator and set the tickIncrement to the specified value. - /// - /// This method is provided so that the same iterator instance can be used for - /// different tick increments, avoiding object allocation during Android - /// layout/draw phases. - TimeStepIterator reset(int tickIncrement); -} - -/// Factory that creates TimeStepIterator with the set tickIncrement value. -abstract class TimeStepIteratorFactory extends Iterable { - /// Get iterator and optionally set the tickIncrement. - @override - TimeStepIterator get iterator; -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter.dart deleted file mode 100644 index cb13e486d..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter.dart +++ /dev/null @@ -1,31 +0,0 @@ -// 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. - -/// Formatter of [DateTime] ticks -abstract class TimeTickFormatter { - /// Format for tick that is the first in a set of ticks. - String formatFirstTick(DateTime date); - - /// Format for a 'simple' tick. - /// - /// Ex. Not a first tick or transition tick. - String formatSimpleTick(DateTime date); - - /// Format for a transitional tick. - String formatTransitionTick(DateTime date); - - /// Returns true if tick is a transitional tick. - bool isTransition(DateTime tickValue, DateTime prevTickValue); -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter_impl.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter_impl.dart deleted file mode 100644 index b5696d94e..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter_impl.dart +++ /dev/null @@ -1,100 +0,0 @@ -// 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 'package:intl/intl.dart' show DateFormat; -import 'package:meta/meta.dart' show required; -import '../../../../common/date_time_factory.dart' show DateTimeFactory; -import 'time_tick_formatter.dart' show TimeTickFormatter; - -/// Formatter that can format simple and transition time ticks differently. -class TimeTickFormatterImpl implements TimeTickFormatter { - DateFormat _simpleFormat; - DateFormat _transitionFormat; - final CalendarField transitionField; - - /// Create time tick formatter. - /// - /// [dateTimeFactory] factory to use to generate the [DateFormat]. - /// [simpleFormat] format to use for most ticks. - /// [transitionFormat] format to use when the time unit transitions. - /// For example showing the month with the date for Jan 1. - /// [transitionField] the calendar field that indicates transition. - TimeTickFormatterImpl( - {@required DateTimeFactory dateTimeFactory, - @required String simpleFormat, - @required String transitionFormat, - this.transitionField}) { - _simpleFormat = dateTimeFactory.createDateFormat(simpleFormat); - _transitionFormat = dateTimeFactory.createDateFormat(transitionFormat); - } - - @override - String formatFirstTick(DateTime date) => _transitionFormat.format(date); - - @override - String formatSimpleTick(DateTime date) => _simpleFormat.format(date); - - @override - String formatTransitionTick(DateTime date) => _transitionFormat.format(date); - - @override - bool isTransition(DateTime tickValue, DateTime prevTickValue) { - // Transition is always false if no transition field is specified. - if (transitionField == null) { - return false; - } - final prevTransitionFieldValue = - getCalendarField(prevTickValue, transitionField); - final transitionFieldValue = getCalendarField(tickValue, transitionField); - return prevTransitionFieldValue != transitionFieldValue; - } - - /// Gets the calendar field for [dateTime]. - int getCalendarField(DateTime dateTime, CalendarField field) { - int value; - - switch (field) { - case CalendarField.year: - value = dateTime.year; - break; - case CalendarField.month: - value = dateTime.month; - break; - case CalendarField.date: - value = dateTime.day; - break; - case CalendarField.hourOfDay: - value = dateTime.hour; - break; - case CalendarField.minute: - value = dateTime.minute; - break; - case CalendarField.second: - value = dateTime.second; - break; - } - - return value; - } -} - -enum CalendarField { - year, - month, - date, - hourOfDay, - minute, - second, -} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/year_time_stepper.dart b/web/charts/common/lib/src/chart/cartesian/axis/time/year_time_stepper.dart deleted file mode 100644 index 71787ca43..000000000 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/year_time_stepper.dart +++ /dev/null @@ -1,62 +0,0 @@ -// 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 '../../../../common/date_time_factory.dart' show DateTimeFactory; -import 'base_time_stepper.dart' show BaseTimeStepper; - -/// Year stepper. -class YearTimeStepper extends BaseTimeStepper { - static const _defaultIncrements = [1, 2, 5, 10, 50, 100, 500, 1000]; - - final List _allowedTickIncrements; - - YearTimeStepper._internal( - DateTimeFactory dateTimeFactory, List increments) - : _allowedTickIncrements = increments, - super(dateTimeFactory); - - factory YearTimeStepper(DateTimeFactory dateTimeFactory, - {List allowedTickIncrements}) { - // Set the default increments if null. - allowedTickIncrements ??= _defaultIncrements; - - // Must have at least one increment option. - assert(allowedTickIncrements.isNotEmpty); - // All increments must be > 0. - assert(allowedTickIncrements.any((increment) => increment <= 0) == false); - - return YearTimeStepper._internal(dateTimeFactory, allowedTickIncrements); - } - - @override - int get typicalStepSizeMs => 365 * 24 * 3600 * 1000; - - @override - List get allowedTickIncrements => _allowedTickIncrements; - - /// Guarantees the increment is a factor of the tick value. - /// - /// Example: 2017, tick increment of 10, step before is 2010. - @override - DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { - final yearRemainder = time.year % tickIncrement; - return dateTimeFactory.createDateTime(time.year - yearRemainder); - } - - @override - DateTime getNextStepTime(DateTime time, int tickIncrement) { - return dateTimeFactory.createDateTime(time.year + tickIncrement); - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/cartesian_chart.dart b/web/charts/common/lib/src/chart/cartesian/cartesian_chart.dart deleted file mode 100644 index 44d4d4f98..000000000 --- a/web/charts/common/lib/src/chart/cartesian/cartesian_chart.dart +++ /dev/null @@ -1,466 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show Point; - -import 'package:meta/meta.dart' show protected; - -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../data/series.dart' show Series; -import '../bar/bar_renderer.dart' show BarRenderer; -import '../common/base_chart.dart' show BaseChart; -import '../common/chart_context.dart' show ChartContext; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show MutableSeries; -import '../common/selection_model/selection_model.dart' show SelectionModelType; -import '../common/series_renderer.dart' show SeriesRenderer; -import '../layout/layout_config.dart' show LayoutConfig, MarginSpec; -import '../layout/layout_view.dart' show LayoutViewPaintOrder; -import 'axis/axis.dart' - show - Axis, - AxisOrientation, - OrdinalAxis, - NumericAxis, - domainAxisKey, - measureAxisIdKey, - measureAxisKey; -import 'axis/draw_strategy/gridline_draw_strategy.dart' - show GridlineRendererSpec; -import 'axis/draw_strategy/none_draw_strategy.dart' show NoneDrawStrategy; -import 'axis/draw_strategy/small_tick_draw_strategy.dart' - show SmallTickRendererSpec; -import 'axis/spec/axis_spec.dart' show AxisSpec; - -class NumericCartesianChart extends CartesianChart { - NumericCartesianChart( - {bool vertical, - LayoutConfig layoutConfig, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes}) - : super( - vertical: vertical, - layoutConfig: layoutConfig, - domainAxis: NumericAxis(), - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes); - - @protected - void initDomainAxis() { - _domainAxis.tickDrawStrategy = SmallTickRendererSpec() - .createDrawStrategy(context, graphicsFactory); - } -} - -class OrdinalCartesianChart extends CartesianChart { - OrdinalCartesianChart( - {bool vertical, - LayoutConfig layoutConfig, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes}) - : super( - vertical: vertical, - layoutConfig: layoutConfig, - domainAxis: OrdinalAxis(), - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes); - - @protected - void initDomainAxis() { - _domainAxis - ..tickDrawStrategy = SmallTickRendererSpec() - .createDrawStrategy(context, graphicsFactory); - } -} - -abstract class CartesianChart extends BaseChart { - static final _defaultLayoutConfig = LayoutConfig( - topSpec: MarginSpec.fromPixel(minPixel: 20), - bottomSpec: MarginSpec.fromPixel(minPixel: 20), - leftSpec: MarginSpec.fromPixel(minPixel: 20), - rightSpec: MarginSpec.fromPixel(minPixel: 20), - ); - - bool vertical; - - /// The current domain axis for this chart. - Axis _domainAxis; - - /// Temporarily stores the new domain axis that is passed in the constructor - /// and the new domain axis created when [domainAxisSpec] is set to a new - /// spec. - /// - /// This step is necessary because the axis cannot be fully configured until - /// [context] is available. [configurationChanged] is called after [context] - /// is available and [_newDomainAxis] will be set to [_domainAxis] and then - /// reset back to null. - Axis _newDomainAxis; - - /// The current domain axis spec that was used to configure [_domainAxis]. - /// - /// This is kept to check if the axis spec has changed when [domainAxisSpec] - /// is set. - AxisSpec _domainAxisSpec; - - /// Temporarily stores the new domain axis spec that is passed in when - /// [domainAxisSpec] is set and is different from [_domainAxisSpec]. This spec - /// is then applied to the new domain axis when [configurationChanged] is - /// called. - AxisSpec _newDomainAxisSpec; - - final Axis _primaryMeasureAxis; - - final Axis _secondaryMeasureAxis; - - final LinkedHashMap _disjointMeasureAxes; - - /// If set to true, the vertical axis will render the opposite of the default - /// direction. - bool flipVerticalAxisOutput = false; - - bool _usePrimaryMeasureAxis = false; - bool _useSecondaryMeasureAxis = false; - - CartesianChart( - {bool vertical, - LayoutConfig layoutConfig, - Axis domainAxis, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes}) - : vertical = vertical ?? true, - // [domainAxis] will be set to the new axis in [configurationChanged]. - _newDomainAxis = domainAxis, - _primaryMeasureAxis = primaryMeasureAxis ?? NumericAxis(), - _secondaryMeasureAxis = secondaryMeasureAxis ?? NumericAxis(), - _disjointMeasureAxes = disjointMeasureAxes ?? {}, - super(layoutConfig: layoutConfig ?? _defaultLayoutConfig) { - // As a convenience for chart configuration, set the paint order on any axis - // that is missing one. - _primaryMeasureAxis.layoutPaintOrder ??= LayoutViewPaintOrder.measureAxis; - _secondaryMeasureAxis.layoutPaintOrder ??= LayoutViewPaintOrder.measureAxis; - - _disjointMeasureAxes.forEach((axisId, axis) { - axis.layoutPaintOrder ??= LayoutViewPaintOrder.measureAxis; - }); - } - - void init(ChartContext context, GraphicsFactory graphicsFactory) { - super.init(context, graphicsFactory); - - _primaryMeasureAxis.context = context; - _primaryMeasureAxis.tickDrawStrategy = GridlineRendererSpec() - .createDrawStrategy(context, graphicsFactory); - - _secondaryMeasureAxis.context = context; - _secondaryMeasureAxis.tickDrawStrategy = GridlineRendererSpec() - .createDrawStrategy(context, graphicsFactory); - - _disjointMeasureAxes.forEach((axisId, axis) { - axis.context = context; - axis.tickDrawStrategy = NoneDrawStrategy(context, graphicsFactory); - }); - } - - Axis get domainAxis => _domainAxis; - - /// Allows the chart to configure the domain axis when it is created. - @protected - void initDomainAxis(); - - /// Create a new domain axis and save the new spec to be applied during - /// [configurationChanged]. - set domainAxisSpec(AxisSpec axisSpec) { - if (_domainAxisSpec != axisSpec) { - _newDomainAxis = createDomainAxisFromSpec(axisSpec); - _newDomainAxisSpec = axisSpec; - } - } - - /// Creates the domain axis spec from provided axis spec. - @protected - Axis createDomainAxisFromSpec(AxisSpec axisSpec) { - return axisSpec.createAxis(); - } - - @override - void configurationChanged() { - if (_newDomainAxis != null) { - if (_domainAxis != null) { - removeView(_domainAxis); - } - - _domainAxis = _newDomainAxis; - _domainAxis - ..context = context - ..layoutPaintOrder = LayoutViewPaintOrder.domainAxis; - - initDomainAxis(); - - addView(_domainAxis); - - _newDomainAxis = null; - } - - if (_newDomainAxisSpec != null) { - _domainAxisSpec = _newDomainAxisSpec; - _newDomainAxisSpec.configure(_domainAxis, context, graphicsFactory); - _newDomainAxisSpec = null; - } - } - - /// Gets the measure axis matching the provided id. - /// - /// If none is provided, this returns the primary measure axis. - Axis getMeasureAxis({String axisId}) { - Axis axis; - if (axisId == Axis.secondaryMeasureAxisId) { - axis = _secondaryMeasureAxis; - } else if (axisId == Axis.primaryMeasureAxisId) { - axis = _primaryMeasureAxis; - } else if (_disjointMeasureAxes[axisId] != null) { - axis = _disjointMeasureAxes[axisId]; - } - - // If no valid axisId was provided, fall back to primary axis. - axis ??= _primaryMeasureAxis; - - return axis; - } - - // TODO: Change measure axis spec to create new measure axis. - /// Sets the primary measure axis for the chart, rendered on the start side of - /// the domain axis. - set primaryMeasureAxisSpec(AxisSpec axisSpec) { - axisSpec.configure(_primaryMeasureAxis, context, graphicsFactory); - } - - /// Sets the secondary measure axis for the chart, rendered on the end side of - /// the domain axis. - set secondaryMeasureAxisSpec(AxisSpec axisSpec) { - axisSpec.configure(_secondaryMeasureAxis, context, graphicsFactory); - } - - /// Sets a map of disjoint measure axes for the chart. - /// - /// Disjoint measure axes can be used to scale a sub-set of series on the - /// chart independently from the primary and secondary axes. The general use - /// case for this type of chart is to show differences in the trends of the - /// data, without comparing their absolute values. - /// - /// Disjoint axes will not render any tick or gridline elements. With - /// independent scales, there would be a lot of collision in labels were they - /// to do so. - /// - /// If any series is rendered with a disjoint axis, it is highly recommended - /// to render all series with disjoint axes. Otherwise, the chart may be - /// visually misleading. - /// - /// A [LinkedHashMap] is used to ensure consistent ordering when painting the - /// axes. - set disjointMeasureAxisSpecs(LinkedHashMap axisSpecs) { - axisSpecs.forEach((axisId, axisSpec) { - axisSpec.configure( - _disjointMeasureAxes[axisId], context, graphicsFactory); - }); - } - - @override - MutableSeries makeSeries(Series series) { - MutableSeries s = super.makeSeries(series); - - s.measureOffsetFn ??= (_) => 0; - - // Setup the Axes - s.setAttr(domainAxisKey, domainAxis); - s.setAttr(measureAxisKey, - getMeasureAxis(axisId: series.getAttribute(measureAxisIdKey))); - - return s; - } - - @override - SeriesRenderer makeDefaultRenderer() { - return BarRenderer()..rendererId = SeriesRenderer.defaultRendererId; - } - - @override - Map>> preprocessSeries( - List> seriesList) { - var rendererToSeriesList = super.preprocessSeries(seriesList); - - // Check if primary or secondary measure axis is being used. - for (final series in seriesList) { - final measureAxisId = series.getAttr(measureAxisIdKey); - _usePrimaryMeasureAxis = _usePrimaryMeasureAxis || - (measureAxisId == null || measureAxisId == Axis.primaryMeasureAxisId); - _useSecondaryMeasureAxis = _useSecondaryMeasureAxis || - (measureAxisId == Axis.secondaryMeasureAxisId); - } - - // Add or remove the primary axis view. - if (_usePrimaryMeasureAxis) { - addView(_primaryMeasureAxis); - } else { - removeView(_primaryMeasureAxis); - } - - // Add or remove the secondary axis view. - if (_useSecondaryMeasureAxis) { - addView(_secondaryMeasureAxis); - } else { - removeView(_secondaryMeasureAxis); - } - - // Add all disjoint axis views so that their range will be configured. - _disjointMeasureAxes.forEach((axisId, axis) { - addView(axis); - }); - - // Reset stale values from previous draw cycles. - domainAxis.resetDomains(); - _primaryMeasureAxis.resetDomains(); - _secondaryMeasureAxis.resetDomains(); - - _disjointMeasureAxes.forEach((axisId, axis) { - axis.resetDomains(); - }); - - final reverseAxisDirection = context != null && context.isRtl; - - if (vertical) { - domainAxis - ..axisOrientation = AxisOrientation.bottom - ..reverseOutputRange = reverseAxisDirection; - - _primaryMeasureAxis - ..axisOrientation = (reverseAxisDirection - ? AxisOrientation.right - : AxisOrientation.left) - ..reverseOutputRange = flipVerticalAxisOutput; - - _secondaryMeasureAxis - ..axisOrientation = (reverseAxisDirection - ? AxisOrientation.left - : AxisOrientation.right) - ..reverseOutputRange = flipVerticalAxisOutput; - - _disjointMeasureAxes.forEach((axisId, axis) { - axis - ..axisOrientation = (reverseAxisDirection - ? AxisOrientation.left - : AxisOrientation.right) - ..reverseOutputRange = flipVerticalAxisOutput; - }); - } else { - domainAxis - ..axisOrientation = (reverseAxisDirection - ? AxisOrientation.right - : AxisOrientation.left) - ..reverseOutputRange = flipVerticalAxisOutput; - - _primaryMeasureAxis - ..axisOrientation = AxisOrientation.bottom - ..reverseOutputRange = reverseAxisDirection; - - _secondaryMeasureAxis - ..axisOrientation = AxisOrientation.top - ..reverseOutputRange = reverseAxisDirection; - - _disjointMeasureAxes.forEach((axisId, axis) { - axis - ..axisOrientation = AxisOrientation.top - ..reverseOutputRange = reverseAxisDirection; - }); - } - - // Have each renderer configure the axes with their domain and measure - // values. - rendererToSeriesList.forEach((rendererId, seriesList) { - getSeriesRenderer(rendererId).configureDomainAxes(seriesList); - getSeriesRenderer(rendererId).configureMeasureAxes(seriesList); - }); - - return rendererToSeriesList; - } - - @override - void onSkipLayout() { - // Update ticks only when skipping layout. - domainAxis.updateTicks(); - - if (_usePrimaryMeasureAxis) { - _primaryMeasureAxis.updateTicks(); - } - - if (_useSecondaryMeasureAxis) { - _secondaryMeasureAxis.updateTicks(); - } - - _disjointMeasureAxes.forEach((axisId, axis) { - axis.updateTicks(); - }); - - super.onSkipLayout(); - } - - @override - void onPostLayout(Map>> rendererToSeriesList) { - fireOnAxisConfigured(); - - super.onPostLayout(rendererToSeriesList); - } - - /// Returns a list of datum details from selection model of [type]. - @override - List> getDatumDetails(SelectionModelType type) { - final entries = >[]; - - getSelectionModel(type).selectedDatum.forEach((seriesDatum) { - final series = seriesDatum.series; - final datum = seriesDatum.datum; - final datumIndex = seriesDatum.index; - - final domain = series.domainFn(datumIndex); - final measure = series.measureFn(datumIndex); - final rawMeasure = series.rawMeasureFn(datumIndex); - final color = series.colorFn(datumIndex); - - final domainPosition = series.getAttr(domainAxisKey).getLocation(domain); - final measurePosition = - series.getAttr(measureAxisKey).getLocation(measure); - - final chartPosition = Point( - vertical ? domainPosition : measurePosition, - vertical ? measurePosition : domainPosition); - - entries.add(DatumDetails( - datum: datum, - domain: domain, - measure: measure, - rawMeasure: rawMeasure, - series: series, - color: color, - chartPosition: chartPosition)); - }); - - return entries; - } -} diff --git a/web/charts/common/lib/src/chart/cartesian/cartesian_renderer.dart b/web/charts/common/lib/src/chart/cartesian/cartesian_renderer.dart deleted file mode 100644 index 1bc772fc4..000000000 --- a/web/charts/common/lib/src/chart/cartesian/cartesian_renderer.dart +++ /dev/null @@ -1,264 +0,0 @@ -// 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 'package:meta/meta.dart'; - -import '../../common/symbol_renderer.dart' show SymbolRenderer; -import '../../data/series.dart' show AccessorFn; -import '../common/base_chart.dart' show BaseChart; -import '../common/processed_series.dart' show MutableSeries; -import '../common/series_renderer.dart' show BaseSeriesRenderer, SeriesRenderer; -import 'axis/axis.dart' show Axis, domainAxisKey, measureAxisKey; -import 'cartesian_chart.dart' show CartesianChart; - -abstract class CartesianRenderer extends SeriesRenderer { - void configureDomainAxes(List> seriesList); - - void configureMeasureAxes(List> seriesList); -} - -abstract class BaseCartesianRenderer extends BaseSeriesRenderer - implements CartesianRenderer { - bool _renderingVertically = true; - - BaseCartesianRenderer( - {@required String rendererId, - @required int layoutPaintOrder, - SymbolRenderer symbolRenderer}) - : super( - rendererId: rendererId, - layoutPaintOrder: layoutPaintOrder, - symbolRenderer: symbolRenderer); - - @override - void onAttach(BaseChart chart) { - super.onAttach(chart); - _renderingVertically = (chart as CartesianChart).vertical; - } - - bool get renderingVertically => _renderingVertically; - - @override - void configureDomainAxes(List> seriesList) { - seriesList.forEach((series) { - if (series.data.isEmpty) { - return; - } - - final domainAxis = series.getAttr(domainAxisKey); - final domainFn = series.domainFn; - final domainLowerBoundFn = series.domainLowerBoundFn; - final domainUpperBoundFn = series.domainUpperBoundFn; - - if (domainAxis == null) { - return; - } - - if (renderingVertically) { - for (int i = 0; i < series.data.length; i++) { - domainAxis.addDomainValue(domainFn(i)); - - if (domainLowerBoundFn != null && domainUpperBoundFn != null) { - final domainLowerBound = domainLowerBoundFn(i); - final domainUpperBound = domainUpperBoundFn(i); - if (domainLowerBound != null && domainUpperBound != null) { - domainAxis.addDomainValue(domainLowerBound); - domainAxis.addDomainValue(domainUpperBound); - } - } - } - } else { - // When rendering horizontally, domains are displayed from top to bottom - // in order to match visual display in legend. - for (int i = series.data.length - 1; i >= 0; i--) { - domainAxis.addDomainValue(domainFn(i)); - - if (domainLowerBoundFn != null && domainUpperBoundFn != null) { - final domainLowerBound = domainLowerBoundFn(i); - final domainUpperBound = domainUpperBoundFn(i); - if (domainLowerBound != null && domainUpperBound != null) { - domainAxis.addDomainValue(domainLowerBound); - domainAxis.addDomainValue(domainUpperBound); - } - } - } - } - }); - } - - @override - void configureMeasureAxes(List> seriesList) { - seriesList.forEach((series) { - if (series.data.isEmpty) { - return; - } - - final domainAxis = series.getAttr(domainAxisKey); - final domainFn = series.domainFn; - - if (domainAxis == null) { - return; - } - - final measureAxis = series.getAttr(measureAxisKey); - if (measureAxis == null) { - return; - } - - // Only add the measure values for datum who's domain is within the - // domainAxis viewport. - int startIndex = - findNearestViewportStart(domainAxis, domainFn, series.data); - int endIndex = findNearestViewportEnd(domainAxis, domainFn, series.data); - - addMeasureValuesFor(series, measureAxis, startIndex, endIndex); - }); - } - - void addMeasureValuesFor( - MutableSeries series, Axis measureAxis, int startIndex, int endIndex) { - final measureFn = series.measureFn; - final measureOffsetFn = series.measureOffsetFn; - final measureLowerBoundFn = series.measureLowerBoundFn; - final measureUpperBoundFn = series.measureUpperBoundFn; - - for (int i = startIndex; i <= endIndex; i++) { - final measure = measureFn(i); - final measureOffset = measureOffsetFn(i); - - if (measure != null && measureOffset != null) { - measureAxis.addDomainValue(measure + measureOffset); - - if (measureLowerBoundFn != null && measureUpperBoundFn != null) { - measureAxis.addDomainValue(measureLowerBoundFn(i) + measureOffset); - measureAxis.addDomainValue(measureUpperBoundFn(i) + measureOffset); - } - } - } - } - - @visibleForTesting - int findNearestViewportStart( - Axis domainAxis, AccessorFn domainFn, List data) { - if (data.isEmpty) { - return null; - } - - // Quick optimization for full viewport (likely). - if (domainAxis.compareDomainValueToViewport(domainFn(0)) == 0) { - return 0; - } - - var start = 1; // Index zero was already checked for above. - var end = data.length - 1; - - // Binary search for the start of the viewport. - while (end >= start) { - int searchIndex = ((end - start) / 2).floor() + start; - int prevIndex = searchIndex - 1; - - var comparisonValue = - domainAxis.compareDomainValueToViewport(domainFn(searchIndex)); - var prevComparisonValue = - domainAxis.compareDomainValueToViewport(domainFn(prevIndex)); - - // Found start? - if (prevComparisonValue == -1 && comparisonValue == 0) { - return searchIndex; - } - - // Straddling viewport? - // Return previous index as the nearest start of the viewport. - if (comparisonValue == 1 && prevComparisonValue == -1) { - return (searchIndex - 1); - } - - // Before start? Update startIndex - if (comparisonValue == -1) { - start = searchIndex + 1; - } else { - // Middle or after viewport? Update endIndex - end = searchIndex - 1; - } - } - - // Binary search would reach this point for the edge cases where the domain - // specified is prior or after the domain viewport. - // If domain is prior to the domain viewport, return the first index as the - // nearest viewport start. - // If domain is after the domain viewport, return the last index as the - // nearest viewport start. - final lastComparison = - domainAxis.compareDomainValueToViewport(domainFn(data.length - 1)); - return lastComparison == 1 ? (data.length - 1) : 0; - } - - @visibleForTesting - int findNearestViewportEnd( - Axis domainAxis, AccessorFn domainFn, List data) { - if (data.isEmpty) { - return null; - } - - var start = 1; - var end = data.length - 1; - - // Quick optimization for full viewport (likely). - if (domainAxis.compareDomainValueToViewport(domainFn(end)) == 0) { - return end; - } - end = end - 1; // Last index was already checked for above. - - // Binary search for the start of the viewport. - while (end >= start) { - int searchIndex = ((end - start) / 2).floor() + start; - int prevIndex = searchIndex - 1; - - int comparisonValue = - domainAxis.compareDomainValueToViewport(domainFn(searchIndex)); - int prevComparisonValue = - domainAxis.compareDomainValueToViewport(domainFn(prevIndex)); - - // Found end? - if (prevComparisonValue == 0 && comparisonValue == 1) { - return prevIndex; - } - - // Straddling viewport? - // Return the current index as the start of the viewport. - if (comparisonValue == 1 && prevComparisonValue == -1) { - return searchIndex; - } - - // After end? Update endIndex - if (comparisonValue == 1) { - end = searchIndex - 1; - } else { - // Middle or before viewport? Update startIndex - start = searchIndex + 1; - } - } - - // Binary search would reach this point for the edge cases where the domain - // specified is prior or after the domain viewport. - // If domain is prior to the domain viewport, return the first index as the - // nearest viewport end. - // If domain is after the domain viewport, return the last index as the - // nearest viewport end. - final lastComparison = - domainAxis.compareDomainValueToViewport(domainFn(data.length - 1)); - return lastComparison == 1 ? (data.length - 1) : 0; - } -} diff --git a/web/charts/common/lib/src/chart/common/base_chart.dart b/web/charts/common/lib/src/chart/common/base_chart.dart deleted file mode 100644 index 08fdf53f4..000000000 --- a/web/charts/common/lib/src/chart/common/base_chart.dart +++ /dev/null @@ -1,707 +0,0 @@ -// 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, Point; - -import 'package:meta/meta.dart' show protected; - -import '../../common/gesture_listener.dart' show GestureListener; -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/proxy_gesture_listener.dart' show ProxyGestureListener; -import '../../data/series.dart' show Series; -import '../layout/layout_config.dart' show LayoutConfig; -import '../layout/layout_manager.dart' show LayoutManager; -import '../layout/layout_manager_impl.dart' show LayoutManagerImpl; -import '../layout/layout_view.dart' show LayoutView; -import 'behavior/chart_behavior.dart' show ChartBehavior; -import 'chart_canvas.dart' show ChartCanvas; -import 'chart_context.dart' show ChartContext; -import 'datum_details.dart' show DatumDetails; -import 'processed_series.dart' show MutableSeries; -import 'selection_model/selection_model.dart' - show MutableSelectionModel, SelectionModelType; -import 'series_datum.dart' show SeriesDatum; -import 'series_renderer.dart' show SeriesRenderer, rendererIdKey, rendererKey; - -typedef BehaviorCreator = ChartBehavior Function(); - -abstract class BaseChart { - ChartContext context; - - /// Internal use only. - GraphicsFactory graphicsFactory; - - LayoutManager _layoutManager; - - int _chartWidth; - int _chartHeight; - - Duration transition = const Duration(milliseconds: 300); - double animationPercent; - - bool _animationsTemporarilyDisabled = false; - - /// List of series that were passed into the previous draw call. - /// - /// This list will be used when redraw is called, to reset the state of all - /// behaviors to the original list. - List> _originalSeriesList; - - /// List of series that are currently drawn on the chart. - /// - /// This list should be used by interactive behaviors between chart draw - /// cycles. It may be filtered or modified by some behaviors during the - /// initial draw cycle (e.g. a [Legend] may hide some series). - List> _currentSeriesList; - - Set _usingRenderers = Set(); - Map>> _rendererToSeriesList; - - final _seriesRenderers = >{}; - - /// Map of named chart behaviors attached to this chart. - final _behaviorRoleMap = >{}; - final _behaviorStack = >[]; - - final _behaviorTappableMap = >{}; - - /// Whether or not the chart will respond to tap events. - /// - /// This will generally be true if there is a behavior attached to the chart - /// that does something with tap events, such as "click to select data." - bool get isTappable => _behaviorTappableMap.isNotEmpty; - - final _gestureProxy = ProxyGestureListener(); - - final _selectionModels = >{}; - - /// Whether data should be selected by nearest domain distance, or by relative - /// distance. - /// - /// This should generally be true for chart types that are intended to be - /// aggregated by domain, and false for charts that plot arbitrary x,y data. - /// Scatter plots, for example, may have many overlapping data with the same - /// domain value. - bool get selectNearestByDomain => true; - - final _lifecycleListeners = >[]; - - BaseChart({LayoutConfig layoutConfig}) { - _layoutManager = LayoutManagerImpl(config: layoutConfig); - } - - void init(ChartContext context, GraphicsFactory graphicsFactory) { - this.context = context; - - // When graphics factory is updated, update all the views. - if (this.graphicsFactory != graphicsFactory) { - this.graphicsFactory = graphicsFactory; - - _layoutManager - .applyToViews((view) => view.graphicsFactory = graphicsFactory); - } - - configurationChanged(); - } - - /// Finish configuring components that require context and graphics factory. - /// - /// Some components require context and graphics factory to be set again when - /// configuration has changed but the configuration is set prior to the - /// chart first calling init with the context. - void configurationChanged() {} - - int get chartWidth => _chartWidth; - - int get chartHeight => _chartHeight; - - // - // Gesture proxy methods - // - ProxyGestureListener get gestureProxy => _gestureProxy; - - /// Add a [GestureListener] to this chart. - GestureListener addGestureListener(GestureListener listener) { - _gestureProxy.add(listener); - return listener; - } - - /// Remove a [GestureListener] from this chart. - void removeGestureListener(GestureListener listener) { - _gestureProxy.remove(listener); - } - - LifecycleListener addLifecycleListener(LifecycleListener listener) { - _lifecycleListeners.add(listener); - return listener; - } - - bool removeLifecycleListener(LifecycleListener listener) => - _lifecycleListeners.remove(listener); - - /// Returns MutableSelectionModel for the given type. Lazy creates one upon first - /// request. - MutableSelectionModel getSelectionModel(SelectionModelType type) { - return _selectionModels.putIfAbsent(type, () => MutableSelectionModel()); - } - - /// Returns a list of datum details from selection model of [type]. - List> getDatumDetails(SelectionModelType type); - - // - // Renderer methods - // - - set defaultRenderer(SeriesRenderer renderer) { - renderer.rendererId = SeriesRenderer.defaultRendererId; - addSeriesRenderer(renderer); - } - - SeriesRenderer get defaultRenderer => - getSeriesRenderer(SeriesRenderer.defaultRendererId); - - void addSeriesRenderer(SeriesRenderer renderer) { - String rendererId = renderer.rendererId; - - SeriesRenderer previousRenderer = _seriesRenderers[rendererId]; - if (previousRenderer != null) { - removeView(previousRenderer); - previousRenderer.onDetach(this); - } - - addView(renderer); - renderer.onAttach(this); - _seriesRenderers[rendererId] = renderer; - } - - SeriesRenderer getSeriesRenderer(String rendererId) { - SeriesRenderer renderer = _seriesRenderers[rendererId]; - - // Special case, if we are asking for the default and we haven't made it - // yet, then make it now. - if (renderer == null) { - if (rendererId == SeriesRenderer.defaultRendererId) { - renderer = makeDefaultRenderer(); - defaultRenderer = renderer; - } - } - // TODO: throw error if couldn't find renderer by id? - - return renderer; - } - - SeriesRenderer makeDefaultRenderer(); - - bool pointWithinRenderer(Point chartPosition) { - return _usingRenderers.any((rendererId) => getSeriesRenderer(rendererId) - .componentBounds - .containsPoint(chartPosition)); - } - - /// Retrieves the datum details that are nearest to the given [drawAreaPoint]. - /// - /// [drawAreaPoint] represents a point in the chart, such as a point that was - /// clicked/tapped on by a user. - /// - /// [selectAcrossAllDrawAreaComponents] specifies whether nearest data - /// selection should be done across the combined draw area of all components - /// with series draw areas, or just the chart's primary draw area bounds. - List> getNearestDatumDetailPerSeries( - Point drawAreaPoint, bool selectAcrossAllDrawAreaComponents) { - // Optionally grab the combined draw area bounds of all components. If this - // is disabled, then we expect each series renderer to filter out the event - // if [chartPoint] is located outside of its own component bounds. - final boundsOverride = - selectAcrossAllDrawAreaComponents ? drawableLayoutAreaBounds : null; - - final details = >[]; - _usingRenderers.forEach((rendererId) { - details.addAll(getSeriesRenderer(rendererId) - .getNearestDatumDetailPerSeries( - drawAreaPoint, selectNearestByDomain, boundsOverride)); - }); - - details.sort((a, b) { - // Sort so that the nearest one is first. - // Special sort, sort by domain distance first, then by measure distance. - if (selectNearestByDomain) { - int domainDiff = a.domainDistance.compareTo(b.domainDistance); - if (domainDiff == 0) { - return a.measureDistance.compareTo(b.measureDistance); - } - return domainDiff; - } else { - return a.relativeDistance.compareTo(b.relativeDistance); - } - }); - - return details; - } - - /// Retrieves the datum details for the current chart selection. - /// - /// [selectionModelType] specifies the type of the selection model to use. - List> getSelectedDatumDetails( - SelectionModelType selectionModelType) { - final details = >[]; - - if (_currentSeriesList == null) { - return details; - } - - final selectionModel = getSelectionModel(selectionModelType); - if (selectionModel == null || !selectionModel.hasDatumSelection) { - return details; - } - - // Pass each selected datum to the appropriate series renderer to get full - // details appropriate to its series type. - for (SeriesDatum seriesDatum in selectionModel.selectedDatum) { - final rendererId = seriesDatum.series.getAttr(rendererIdKey); - details.add( - getSeriesRenderer(rendererId).getDetailsForSeriesDatum(seriesDatum)); - } - - return details; - } - - // - // Behavior methods - // - - /// Helper method to create a behavior with congruent types. - /// - /// This invokes the provides helper with type parameters that match this - /// chart. - ChartBehavior createBehavior(BehaviorCreator creator) => creator(); - - /// Attaches a behavior to the chart. - /// - /// Setting a new behavior with the same role as a behavior already attached - /// to the chart will replace the old behavior. The old behavior's removeFrom - /// method will be called before we attach the new behavior. - void addBehavior(ChartBehavior behavior) { - final role = behavior.role; - - if (role != null && _behaviorRoleMap[role] != behavior) { - // Remove any old behavior with the same role. - removeBehavior(_behaviorRoleMap[role]); - // Add the new behavior. - _behaviorRoleMap[role] = behavior; - } - - // Add the behavior if it wasn't already added. - if (!_behaviorStack.contains(behavior)) { - _behaviorStack.add(behavior); - behavior.attachTo(this); - } - } - - /// Removes a behavior from the chart. - /// - /// Returns true if a behavior was removed, otherwise returns false. - bool removeBehavior(ChartBehavior behavior) { - if (behavior == null) { - return false; - } - - final role = behavior?.role; - if (role != null && _behaviorRoleMap[role] == behavior) { - _behaviorRoleMap.remove(role); - } - - // Make sure the removed behavior is no longer registered for tap events. - unregisterTappable(behavior); - - final wasAttached = _behaviorStack.remove(behavior); - behavior.removeFrom(this); - - return wasAttached; - } - - /// Tells the chart that this behavior responds to tap events. - /// - /// This should only be called after [behavior] has been attached to the chart - /// via [addBehavior]. - void registerTappable(ChartBehavior behavior) { - final role = behavior.role; - - if (role != null && - _behaviorRoleMap[role] == behavior && - _behaviorTappableMap[role] != behavior) { - _behaviorTappableMap[role] = behavior; - } - } - - /// Tells the chart that this behavior no longer responds to tap events. - void unregisterTappable(ChartBehavior behavior) { - final role = behavior?.role; - if (role != null && _behaviorTappableMap[role] == behavior) { - _behaviorTappableMap.remove(role); - } - } - - /// Returns a list of behaviors that have been added. - List> get behaviors => List.unmodifiable(_behaviorStack); - - // - // Layout methods - // - void measure(int width, int height) { - if (_rendererToSeriesList != null) { - _layoutManager.measure(width, height); - } - } - - void layout(int width, int height) { - if (_rendererToSeriesList != null) { - layoutInternal(width, height); - - onPostLayout(_rendererToSeriesList); - } - } - - void layoutInternal(int width, int height) { - _chartWidth = width; - _chartHeight = height; - _layoutManager.layout(width, height); - } - - void addView(LayoutView view) { - if (_layoutManager.isAttached(view) == false) { - view.graphicsFactory = graphicsFactory; - _layoutManager.addView(view); - } - } - - void removeView(LayoutView view) { - _layoutManager.removeView(view); - } - - /// Returns whether or not [point] is within the draw area bounds. - bool withinDrawArea(Point point) { - return _layoutManager.withinDrawArea(point); - } - - /// Returns the bounds of the chart draw area. - Rectangle get drawAreaBounds => _layoutManager.drawAreaBounds; - - int get marginBottom => _layoutManager.marginBottom; - - int get marginLeft => _layoutManager.marginLeft; - - int get marginRight => _layoutManager.marginRight; - - int get marginTop => _layoutManager.marginTop; - - /// Returns the combined bounds of the chart draw area and all layout - /// components that draw series data. - Rectangle get drawableLayoutAreaBounds => - _layoutManager.drawableLayoutAreaBounds; - - // - // Draw methods - // - void draw(List> seriesList) { - // Clear the selection model when [seriesList] changes. - for (final selectionModel in _selectionModels.values) { - selectionModel.clearSelection(notifyListeners: false); - } - - var processedSeriesList = - List>.from(seriesList.map(makeSeries)); - - // Allow listeners to manipulate the seriesList. - fireOnDraw(processedSeriesList); - - // Set an index on the series list. - // This can be used by listeners of selection to determine the order of - // series, because the selection details are not returned in this order. - int seriesIndex = 0; - processedSeriesList.forEach((series) => series.seriesIndex = seriesIndex++); - - // Initially save a reference to processedSeriesList. After drawInternal - // finishes, we expect _currentSeriesList to contain a new, possibly - // modified list. - _currentSeriesList = processedSeriesList; - - // Store off processedSeriesList for use later during redraw calls. This - // list will not reflect any modifications that were made to - // _currentSeriesList by behaviors during the draw cycle. - _originalSeriesList = processedSeriesList; - - drawInternal(processedSeriesList, skipAnimation: false, skipLayout: false); - } - - /// Redraws and re-lays-out the chart using the previously rendered layout - /// dimensions. - void redraw({bool skipAnimation = false, bool skipLayout = false}) { - drawInternal(_originalSeriesList, - skipAnimation: skipAnimation, skipLayout: skipLayout); - - // Trigger layout and actually redraw the chart. - if (!skipLayout) { - measure(_chartWidth, _chartHeight); - layout(_chartWidth, _chartHeight); - } else { - onSkipLayout(); - } - } - - void drawInternal(List> seriesList, - {bool skipAnimation, bool skipLayout}) { - seriesList = - seriesList.map((series) => MutableSeries.clone(series)).toList(); - - // TODO: Handle exiting renderers. - _animationsTemporarilyDisabled = skipAnimation; - - configureSeries(seriesList); - - // Allow listeners to manipulate the processed seriesList. - fireOnPreprocess(seriesList); - - _rendererToSeriesList = preprocessSeries(seriesList); - - // Allow listeners to manipulate the processed seriesList. - fireOnPostprocess(seriesList); - - _currentSeriesList = seriesList; - } - - List> get currentSeriesList => _currentSeriesList; - - MutableSeries makeSeries(Series series) { - final s = MutableSeries(series); - - // Setup the Renderer - final rendererId = - series.getAttribute(rendererIdKey) ?? SeriesRenderer.defaultRendererId; - s.setAttr(rendererIdKey, rendererId); - s.setAttr(rendererKey, getSeriesRenderer(rendererId)); - - return s; - } - - /// Preprocess series to assign missing color functions. - void configureSeries(List> seriesList) { - Map>> rendererToSeriesList = {}; - - // Build map of rendererIds to SeriesLists. This map can't be re-used later - // in the preprocessSeries call because some behaviors might alter the - // seriesList. - seriesList.forEach((series) { - String rendererId = series.getAttr(rendererIdKey); - rendererToSeriesList.putIfAbsent(rendererId, () => []).add(series); - }); - - // Have each renderer add missing color functions to their seriesLists. - rendererToSeriesList.forEach((rendererId, seriesList) { - getSeriesRenderer(rendererId).configureSeries(seriesList); - }); - } - - /// Preprocess series to allow stacking and other mutations. - /// - /// Build a map of rendererId to series. - Map>> preprocessSeries( - List> seriesList) { - Map>> rendererToSeriesList = {}; - - var unusedRenderers = _usingRenderers; - _usingRenderers = Set(); - - // Build map of rendererIds to SeriesLists. - seriesList.forEach((series) { - String rendererId = series.getAttr(rendererIdKey); - rendererToSeriesList.putIfAbsent(rendererId, () => []).add(series); - - _usingRenderers.add(rendererId); - unusedRenderers.remove(rendererId); - }); - - // Allow unused renderers to render out content. - unusedRenderers - .forEach((rendererId) => rendererToSeriesList[rendererId] = []); - - // Have each renderer preprocess their seriesLists. - rendererToSeriesList.forEach((rendererId, seriesList) { - getSeriesRenderer(rendererId).preprocessSeries(seriesList); - }); - - return rendererToSeriesList; - } - - void onSkipLayout() { - onPostLayout(_rendererToSeriesList); - } - - void onPostLayout(Map>> rendererToSeriesList) { - // Update each renderer with - rendererToSeriesList.forEach((rendererId, seriesList) { - getSeriesRenderer(rendererId).update(seriesList, animatingThisDraw); - }); - - // Request animation - if (animatingThisDraw) { - animationPercent = 0.0; - context.requestAnimation(this.transition); - } else { - animationPercent = 1.0; - context.requestPaint(); - } - - _animationsTemporarilyDisabled = false; - } - - void paint(ChartCanvas canvas) { - canvas.drawingView = 'BaseView'; - _layoutManager.paintOrderedViews.forEach((view) { - canvas.drawingView = view.runtimeType.toString(); - view.paint(canvas, animatingThisDraw ? animationPercent : 1.0); - }); - - canvas.drawingView = 'PostRender'; - fireOnPostrender(canvas); - canvas.drawingView = null; - - if (animationPercent == 1.0) { - fireOnAnimationComplete(); - } - } - - bool get animatingThisDraw => (transition != null && - transition.inMilliseconds > 0 && - !_animationsTemporarilyDisabled); - - @protected - fireOnDraw(List> seriesList) { - _lifecycleListeners.forEach((listener) { - if (listener.onData != null) { - listener.onData(seriesList); - } - }); - } - - @protected - fireOnPreprocess(List> seriesList) { - _lifecycleListeners.forEach((listener) { - if (listener.onPreprocess != null) { - listener.onPreprocess(seriesList); - } - }); - } - - @protected - fireOnPostprocess(List> seriesList) { - _lifecycleListeners.forEach((listener) { - if (listener.onPostprocess != null) { - listener.onPostprocess(seriesList); - } - }); - } - - @protected - fireOnAxisConfigured() { - _lifecycleListeners.forEach((listener) { - if (listener.onAxisConfigured != null) { - listener.onAxisConfigured(); - } - }); - } - - @protected - fireOnPostrender(ChartCanvas canvas) { - _lifecycleListeners.forEach((listener) { - if (listener.onPostrender != null) { - listener.onPostrender(canvas); - } - }); - } - - @protected - fireOnAnimationComplete() { - _lifecycleListeners.forEach((listener) { - if (listener.onAnimationComplete != null) { - listener.onAnimationComplete(); - } - }); - } - - /// Called to free up any resources due to chart going away. - destroy() { - // Walk them in add order to support behaviors that remove other behaviors. - for (var i = 0; i < _behaviorStack.length; i++) { - _behaviorStack[i].removeFrom(this); - } - _behaviorStack.clear(); - _behaviorRoleMap.clear(); - _selectionModels.values - .forEach((selectionModel) => selectionModel.clearAllListeners()); - } -} - -class LifecycleListener { - /// Called when new data is drawn to the chart (not a redraw). - /// - /// This step is good for processing the data (running averages, percentage of - /// first, etc). It can also be used to add Series of data (trend line) or - /// remove a line as mentioned above, removing Series. - final LifecycleSeriesListCallback onData; - - /// Called for every redraw given the original SeriesList resulting from the - /// previous onData. - /// - /// This step is good for injecting default attributes on the Series before - /// the renderers process the data (ex: before stacking measures). - final LifecycleSeriesListCallback onPreprocess; - - /// Called after the chart and renderers get a chance to process the data but - /// before the axes process them. - /// - /// This step is good if you need to alter the Series measure values after the - /// renderers have processed them (ex: after stacking measures). - final LifecycleSeriesListCallback onPostprocess; - - /// Called after the Axes have been configured. - /// This step is good if you need to use the axes to get any cartesian - /// location information. At this point Axes should be immutable and stable. - final LifecycleEmptyCallback onAxisConfigured; - - /// Called after the chart is done rendering passing along the canvas allowing - /// a behavior or other listener to render on top of the chart. - /// - /// This is a convenience callback, however if there is any significant canvas - /// interaction or stacking needs, it is preferred that a AplosView/ChartView - /// is added to the chart instead to fully participate in the view stacking. - final LifecycleCanvasCallback onPostrender; - - /// Called after animation hits 100%. This allows a behavior or other listener - /// to chain animations to create a multiple step animation transition. - final LifecycleEmptyCallback onAnimationComplete; - - LifecycleListener( - {this.onData, - this.onPreprocess, - this.onPostprocess, - this.onAxisConfigured, - this.onPostrender, - this.onAnimationComplete}); -} - -typedef LifecycleSeriesListCallback = Function( - List> seriesList); -typedef LifecycleCanvasCallback = Function(ChartCanvas canvas); -typedef LifecycleEmptyCallback = Function(); diff --git a/web/charts/common/lib/src/chart/common/behavior/a11y/a11y_explore_behavior.dart b/web/charts/common/lib/src/chart/common/behavior/a11y/a11y_explore_behavior.dart deleted file mode 100644 index 521bbb2a3..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/a11y/a11y_explore_behavior.dart +++ /dev/null @@ -1,97 +0,0 @@ -// 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 '../../../../common/gesture_listener.dart' show GestureListener; -import '../../base_chart.dart' show BaseChart; -import '../chart_behavior.dart' show ChartBehavior; -import 'a11y_node.dart' show A11yNode; - -/// The gesture to use for triggering explore mode. -enum ExploreModeTrigger { - pressHold, - tap, -} - -/// Chart behavior for adding A11y information. -abstract class A11yExploreBehavior implements ChartBehavior { - /// The gesture that activates explore mode. Defaults to long press. - /// - /// Turning on explore mode asks this [A11yExploreBehavior] to generate nodes within - /// this chart. - final ExploreModeTrigger exploreModeTrigger; - - /// Minimum width of the bounding box for the a11y focus. - /// - /// Must be 1 or higher because invisible semantic nodes should not be added. - final double minimumWidth; - - /// Optionally notify the OS when explore mode is enabled. - final String exploreModeEnabledAnnouncement; - - /// Optionally notify the OS when explore mode is disabled. - final String exploreModeDisabledAnnouncement; - - BaseChart _chart; - GestureListener _listener; - bool _exploreModeOn = false; - - A11yExploreBehavior({ - this.exploreModeTrigger = ExploreModeTrigger.pressHold, - double minimumWidth, - this.exploreModeEnabledAnnouncement, - this.exploreModeDisabledAnnouncement, - }) : minimumWidth = minimumWidth ?? 1.0 { - assert(this.minimumWidth >= 1.0); - - switch (exploreModeTrigger) { - case ExploreModeTrigger.pressHold: - _listener = GestureListener(onLongPress: _toggleExploreMode); - break; - case ExploreModeTrigger.tap: - _listener = GestureListener(onTap: _toggleExploreMode); - break; - } - } - - bool _toggleExploreMode(_) { - if (_exploreModeOn) { - _exploreModeOn = false; - // Ask native platform to turn off explore mode. - _chart.context.disableA11yExploreMode( - announcement: exploreModeDisabledAnnouncement); - } else { - _exploreModeOn = true; - // Ask native platform to turn on explore mode. - _chart.context.enableA11yExploreMode(createA11yNodes(), - announcement: exploreModeEnabledAnnouncement); - } - - return true; - } - - /// Returns a list of A11yNodes for this chart. - List createA11yNodes(); - - @override - void attachTo(BaseChart chart) { - _chart = chart; - chart.addGestureListener(_listener); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeGestureListener(_listener); - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/a11y/a11y_node.dart b/web/charts/common/lib/src/chart/common/behavior/a11y/a11y_node.dart deleted file mode 100644 index 865b522f7..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/a11y/a11y_node.dart +++ /dev/null @@ -1,32 +0,0 @@ -// 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; - -typedef OnFocus = void Function(); - -/// Container for accessibility data. -class A11yNode { - /// The bounding box for this node. - final Rectangle boundingBox; - - /// The textual description of this node. - final String label; - - /// Callback when the A11yNode is focused by the native platform - OnFocus onFocus; - - A11yNode(this.label, this.boundingBox, {this.onFocus}); -} diff --git a/web/charts/common/lib/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart b/web/charts/common/lib/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart deleted file mode 100644 index 7238381b6..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart +++ /dev/null @@ -1,195 +0,0 @@ -// 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' show required; - -import '../../../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey; -import '../../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../../base_chart.dart' show BaseChart, LifecycleListener; -import '../../processed_series.dart' show MutableSeries; -import '../../selection_model/selection_model.dart' show SelectionModelType; -import '../../series_datum.dart' show SeriesDatum; -import 'a11y_explore_behavior.dart' - show A11yExploreBehavior, ExploreModeTrigger; -import 'a11y_node.dart' show A11yNode, OnFocus; - -/// Returns a string for a11y vocalization from a list of series datum. -typedef VocalizationCallback = String Function( - List> seriesDatums); - -/// A simple vocalization that returns the domain value to string. -String domainVocalization(List> seriesDatums) { - final datumIndex = seriesDatums.first.index; - final domainFn = seriesDatums.first.series.domainFn; - final domain = domainFn(datumIndex); - - return domain.toString(); -} - -/// Behavior that generates semantic nodes for each domain. -class DomainA11yExploreBehavior extends A11yExploreBehavior { - final VocalizationCallback _vocalizationCallback; - LifecycleListener _lifecycleListener; - CartesianChart _chart; - List> _seriesList; - - DomainA11yExploreBehavior( - {VocalizationCallback vocalizationCallback, - ExploreModeTrigger exploreModeTrigger, - double minimumWidth, - String exploreModeEnabledAnnouncement, - String exploreModeDisabledAnnouncement}) - : _vocalizationCallback = vocalizationCallback ?? domainVocalization, - super( - exploreModeTrigger: exploreModeTrigger, - minimumWidth: minimumWidth, - exploreModeEnabledAnnouncement: exploreModeEnabledAnnouncement, - exploreModeDisabledAnnouncement: exploreModeDisabledAnnouncement) { - _lifecycleListener = LifecycleListener(onPostprocess: _updateSeriesList); - } - - @override - List createA11yNodes() { - final nodes = <_DomainA11yNode>[]; - - // Update the selection model when the a11y node has focus. - final selectionModel = _chart.getSelectionModel(SelectionModelType.info); - - final domainSeriesDatum = >>{}; - - for (MutableSeries series in _seriesList) { - for (var index = 0; index < series.data.length; index++) { - final datum = series.data[index]; - D domain = series.domainFn(index); - - domainSeriesDatum[domain] ??= >[]; - domainSeriesDatum[domain].add(SeriesDatum(series, datum)); - } - } - - domainSeriesDatum.forEach((domain, seriesDatums) { - final a11yDescription = _vocalizationCallback(seriesDatums); - - final firstSeries = seriesDatums.first.series; - final domainAxis = firstSeries.getAttr(domainAxisKey) as ImmutableAxis; - final location = domainAxis.getLocation(domain); - - /// If the step size is smaller than the minimum width, use minimum. - final stepSize = (domainAxis.stepSize > minimumWidth) - ? domainAxis.stepSize - : minimumWidth; - - nodes.add(_DomainA11yNode(a11yDescription, - location: location, - stepSize: stepSize, - chartDrawBounds: _chart.drawAreaBounds, - isRtl: _chart.context.isRtl, - renderVertically: _chart.vertical, - onFocus: () => selectionModel.updateSelection(seriesDatums, []))); - }); - - // The screen reader navigates the nodes based on the order it is returned. - // So if the chart is RTL, then the nodes should be ordered with the right - // most domain first. - // - // If the chart has multiple series and one series is missing the domain - // and it was added later, we still want the domains to be in order. - nodes.sort(); - - return nodes; - } - - void _updateSeriesList(List> seriesList) { - _seriesList = seriesList; - } - - @override - void attachTo(BaseChart chart) { - // Domain selection behavior only works for cartesian charts. - assert(chart is CartesianChart); - _chart = chart as CartesianChart; - - chart.addLifecycleListener(_lifecycleListener); - - super.attachTo(chart); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeLifecycleListener(_lifecycleListener); - } - - @override - String get role => 'DomainA11yExplore-$exploreModeTrigger'; -} - -/// A11yNode with domain specific information. -class _DomainA11yNode extends A11yNode implements Comparable<_DomainA11yNode> { - // Save location, RTL, and is render vertically for sorting - final double location; - final bool isRtl; - final bool renderVertically; - - factory _DomainA11yNode(String label, - {@required double location, - @required double stepSize, - @required Rectangle chartDrawBounds, - @required bool isRtl, - @required bool renderVertically, - OnFocus onFocus}) { - Rectangle boundingBox; - if (renderVertically) { - var left = (location - stepSize / 2).round(); - var top = chartDrawBounds.top; - var width = stepSize.round(); - var height = chartDrawBounds.height; - boundingBox = Rectangle(left, top, width, height); - } else { - var left = chartDrawBounds.left; - var top = (location - stepSize / 2).round(); - var width = chartDrawBounds.width; - var height = stepSize.round(); - boundingBox = Rectangle(left, top, width, height); - } - - return _DomainA11yNode._internal(label, boundingBox, - location: location, - isRtl: isRtl, - renderVertically: renderVertically, - onFocus: onFocus); - } - - _DomainA11yNode._internal(String label, Rectangle boundingBox, - {@required this.location, - @required this.isRtl, - @required this.renderVertically, - OnFocus onFocus}) - : super(label, boundingBox, onFocus: onFocus); - - @override - int compareTo(_DomainA11yNode other) { - // Ordered by smaller location first, unless rendering vertically and RTL, - // then flip to sort by larger location first. - int result = location.compareTo(other.location); - - if (renderVertically && isRtl && result != 0) { - result = -result; - } - - return result; - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/calculation/percent_injector.dart b/web/charts/common/lib/src/chart/common/behavior/calculation/percent_injector.dart deleted file mode 100644 index a12023d03..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/calculation/percent_injector.dart +++ /dev/null @@ -1,235 +0,0 @@ -// 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 '../../../../data/series.dart' show AttributeKey; -import '../../base_chart.dart' show BaseChart, LifecycleListener; -import '../../behavior/chart_behavior.dart' show ChartBehavior; -import '../../processed_series.dart' show MutableSeries; - -const percentInjectedKey = - AttributeKey('PercentInjector.percentInjected'); - -/// Chart behavior that can inject series or domain percentages into each datum. -/// -/// [totalType] configures the type of total to be calculated. -/// -/// The measure values of each datum will be replaced by the percent of the -/// total measure value that each represents. The "raw" measure accessor -/// function on [MutableSeries] can still be used to get the original values. -/// -/// Note that the results for measureLowerBound and measureUpperBound are not -/// currently well defined when converted into percentage values. This behavior -/// will replace them as percents to prevent bad axis results, but no effort is -/// made to bound them to within a "0 to 100%" data range. -/// -/// Note that if the chart has a [Legend] that is capable of hiding series data, -/// then this behavior must be added after the [Legend] to ensure that it -/// calculates values after series have been potentially removed from the list. -class PercentInjector implements ChartBehavior { - LifecycleListener _lifecycleListener; - - /// The type of data total to be calculated. - final PercentInjectorTotalType totalType; - - /// Constructs a [PercentInjector]. - /// - /// [totalType] configures the type of data total to be calculated. - PercentInjector({this.totalType = PercentInjectorTotalType.domain}) { - // Set up chart draw cycle listeners. - _lifecycleListener = - LifecycleListener(onPreprocess: _preProcess, onData: _onData); - } - - @override - void attachTo(BaseChart chart) { - chart.addLifecycleListener(_lifecycleListener); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeLifecycleListener(_lifecycleListener); - } - - /// Resets the state of the behavior when new data is drawn on the chart. - void _onData(List> seriesList) { - // Reset tracking of percentage injection for new data. - seriesList.forEach((series) { - series.setAttr(percentInjectedKey, false); - }); - } - - /// Injects percent of domain and/or series accessor functions into each - /// series. - /// - /// These are injected in the preProcess phase in case other behaviors modify - /// the [seriesList] between chart redraws. - void _preProcess(List> seriesList) { - var percentInjected = true; - seriesList.forEach((series) { - percentInjected = percentInjected && series.getAttr(percentInjectedKey); - }); - - if (percentInjected) { - return; - } - - switch (totalType) { - case PercentInjectorTotalType.domain: - case PercentInjectorTotalType.domainBySeriesCategory: - final totalsByDomain = {}; - - final useSeriesCategory = - totalType == PercentInjectorTotalType.domainBySeriesCategory; - - // Walk the series and compute the domain total. Series total is - // automatically computed by [MutableSeries]. - seriesList.forEach((series) { - final seriesCategory = series.seriesCategory; - final rawMeasureFn = series.rawMeasureFn; - final domainFn = series.domainFn; - - for (var index = 0; index < series.data.length; index++) { - final domain = domainFn(index); - var measure = rawMeasureFn(index); - measure ??= 0.0; - - final key = useSeriesCategory - ? '${seriesCategory}__${domain.toString()}' - : '${domain.toString()}'; - - if (totalsByDomain[key] != null) { - totalsByDomain[key] = totalsByDomain[key] + measure; - } else { - totalsByDomain[key] = measure; - } - } - }); - - // Add percent of domain and series accessor functions. - seriesList.forEach((series) { - // Replace the default measure accessor with one that computes the - // percentage. - series.measureFn = (index) { - final measure = series.rawMeasureFn(index); - - if (measure == null || measure == 0.0) { - return 0.0; - } - - final domain = series.domainFn(index); - - final key = useSeriesCategory - ? '${series.seriesCategory}__${domain.toString()}' - : '${domain.toString()}'; - - return measure / totalsByDomain[key]; - }; - - // Replace the default measure lower bound accessor with one that - // computes the percentage. - if (series.measureLowerBoundFn != null) { - series.measureLowerBoundFn = (index) { - final measureLowerBound = series.rawMeasureLowerBoundFn(index); - - if (measureLowerBound == null || measureLowerBound == 0.0) { - return 0.0; - } - - final domain = series.domainFn(index); - - final key = useSeriesCategory - ? '${series.seriesCategory}__${domain.toString()}' - : '${domain.toString()}'; - - return measureLowerBound / totalsByDomain[key]; - }; - } - - // Replace the default measure upper bound accessor with one that - // computes the percentage. - if (series.measureUpperBoundFn != null) { - series.measureUpperBoundFn = (index) { - final measureUpperBound = series.rawMeasureUpperBoundFn(index); - - if (measureUpperBound == null || measureUpperBound == 0.0) { - return 0.0; - } - - final domain = series.domainFn(index); - - final key = useSeriesCategory - ? '${series.seriesCategory}__${domain.toString()}' - : '${domain.toString()}'; - - return measureUpperBound / totalsByDomain[key]; - }; - } - - series.setAttr(percentInjectedKey, true); - }); - - break; - - case PercentInjectorTotalType.series: - seriesList.forEach((series) { - // Replace the default measure accessor with one that computes the - // percentage. - series.measureFn = - (index) => series.rawMeasureFn(index) / series.seriesMeasureTotal; - - // Replace the default measure lower bound accessor with one that - // computes the percentage. - if (series.measureLowerBoundFn != null) { - series.measureLowerBoundFn = (index) => - series.rawMeasureLowerBoundFn(index) / - series.seriesMeasureTotal; - } - - // Replace the default measure upper bound accessor with one that - // computes the percentage. - if (series.measureUpperBoundFn != null) { - series.measureUpperBoundFn = (index) => - series.rawMeasureUpperBoundFn(index) / - series.seriesMeasureTotal; - } - - series.setAttr(percentInjectedKey, true); - }); - - break; - - default: - throw ArgumentError('Unsupported totalType: $totalType'); - } - } - - @override - String get role => 'PercentInjector'; -} - -/// Describes the type of data total that will be calculated by PercentInjector. -/// -/// [domain] calculates the percentage of each datum's measure value out of the -/// total measure values for all data that share the same domain value. -/// -/// [domainBySeriesCategory] calculates the percentage of each datum's measure -/// value out of the total measure values for all data that share the same -/// domain value and seriesCategory value. This should be enabled if the data -/// will be rendered by a series renderer that groups data by both domain and -/// series category, such as the "grouped stacked" mode of [BarRenderer]. -/// -/// [series] calculates the percentage of each datum's measure value out of the -/// total measure values for all data in that datum's series. -enum PercentInjectorTotalType { domain, domainBySeriesCategory, series } diff --git a/web/charts/common/lib/src/chart/common/behavior/chart_behavior.dart b/web/charts/common/lib/src/chart/common/behavior/chart_behavior.dart deleted file mode 100644 index f1460f5f5..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/chart_behavior.dart +++ /dev/null @@ -1,66 +0,0 @@ -// 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 '../base_chart.dart'; - -/// Interface for adding behavior to a chart. -/// -/// For example pan and zoom are implemented via behavior strategies. -abstract class ChartBehavior { - String get role; - - /// Injects the behavior into a chart. - void attachTo(BaseChart chart); - - /// Removes the behavior from a chart. - void removeFrom(BaseChart chart); -} - -/// Position of a component within the chart layout. -/// -/// Outside positions are [top], [bottom], [start], and [end]. -/// -/// [top] component positioned at the top, with the chart positioned below the -/// component and height reduced by the height of the component. -/// [bottom] component positioned below the chart, and the chart's height is -/// reduced by the height of the component. -/// [start] component is positioned at the left of the chart (or the right if -/// RTL), the chart's width is reduced by the width of the component. -/// [end] component is positioned at the right of the chart (or the left if -/// RTL), the chart's width is reduced by the width of the component. -/// [inside] component is layered on top of the chart. -enum BehaviorPosition { - top, - bottom, - start, - end, - inside, -} - -/// Justification for components positioned outside [BehaviorPosition]. -enum OutsideJustification { - startDrawArea, - start, - middleDrawArea, - middle, - endDrawArea, - end, -} - -/// Justification for components positioned [BehaviorPosition.inside]. -enum InsideJustification { - topStart, - topEnd, -} diff --git a/web/charts/common/lib/src/chart/common/behavior/chart_title/chart_title.dart b/web/charts/common/lib/src/chart/common/behavior/chart_title/chart_title.dart deleted file mode 100644 index 42b056724..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/chart_title/chart_title.dart +++ /dev/null @@ -1,828 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart'; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../../common/style/style_factory.dart' show StyleFactory; -import '../../../../common/text_element.dart' - show MaxWidthStrategy, TextDirection, TextElement; -import '../../../../common/text_style.dart' show TextStyle; -import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - ViewMeasuredSizes; -import '../../base_chart.dart' show BaseChart, LifecycleListener; -import '../../behavior/chart_behavior.dart' - show BehaviorPosition, ChartBehavior, OutsideJustification; -import '../../chart_canvas.dart' show ChartCanvas; - -/// Chart behavior that adds title text to a chart. An optional second line of -/// text may be rendered as a sub-title. -/// -/// Titles will by default be rendered as the outermost component in the chart -/// margin. -class ChartTitle implements ChartBehavior { - static const _defaultBehaviorPosition = BehaviorPosition.top; - static const _defaultMaxWidthStrategy = MaxWidthStrategy.ellipsize; - static const _defaultTitleDirection = ChartTitleDirection.auto; - static const _defaultTitleOutsideJustification = OutsideJustification.middle; - static final _defaultTitleStyle = - TextStyleSpec(fontSize: 18, color: StyleFactory.style.tickColor); - static final _defaultSubTitleStyle = - TextStyleSpec(fontSize: 14, color: StyleFactory.style.tickColor); - static const _defaultInnerPadding = 10; - static const _defaultTitlePadding = 18; - static const _defaultOuterPadding = 10; - - /// Stores all of the configured properties of the behavior. - _ChartTitleConfig _config; - - BaseChart _chart; - - _ChartTitleLayoutView _view; - - LifecycleListener _lifecycleListener; - - /// Constructs a [ChartTitle]. - /// - /// [title] contains the text for the chart title. - ChartTitle(String title, - {BehaviorPosition behaviorPosition, - int innerPadding, - int layoutMinSize, - int layoutPreferredSize, - int outerPadding, - MaxWidthStrategy maxWidthStrategy, - ChartTitleDirection titleDirection, - OutsideJustification titleOutsideJustification, - int titlePadding, - TextStyleSpec titleStyleSpec, - String subTitle, - TextStyleSpec subTitleStyleSpec}) { - _config = _ChartTitleConfig() - ..behaviorPosition = behaviorPosition ?? _defaultBehaviorPosition - ..innerPadding = innerPadding ?? _defaultInnerPadding - ..layoutMinSize = layoutMinSize - ..layoutPreferredSize = layoutPreferredSize - ..outerPadding = outerPadding ?? _defaultOuterPadding - ..maxWidthStrategy = maxWidthStrategy ?? _defaultMaxWidthStrategy - ..title = title - ..titleDirection = titleDirection ?? _defaultTitleDirection - ..titleOutsideJustification = - titleOutsideJustification ?? _defaultTitleOutsideJustification - ..titlePadding = titlePadding ?? _defaultTitlePadding - ..titleStyleSpec = titleStyleSpec ?? _defaultTitleStyle - ..subTitle = subTitle - ..subTitleStyleSpec = subTitleStyleSpec ?? _defaultSubTitleStyle; - - _lifecycleListener = - LifecycleListener(onAxisConfigured: _updateViewData); - } - - /// Layout position for the title. - BehaviorPosition get behaviorPosition => _config.behaviorPosition; - - set behaviorPosition(BehaviorPosition behaviorPosition) { - _config.behaviorPosition = behaviorPosition; - } - - /// Minimum size of the legend component. Optional. - /// - /// If the legend is positioned in the top or bottom margin, then this - /// configures the legend's height. If positioned in the start or end - /// position, this configures the legend's width. - int get layoutMinSize => _config.layoutMinSize; - - set layoutMinSize(int layoutMinSize) { - _config.layoutMinSize = layoutMinSize; - } - - /// Preferred size of the legend component. Defaults to 0. - /// - /// If the legend is positioned in the top or bottom margin, then this - /// configures the legend's height. If positioned in the start or end - /// position, this configures the legend's width. - int get layoutPreferredSize => _config.layoutPreferredSize; - - set layoutPreferredSize(int layoutPreferredSize) { - _config.layoutPreferredSize = layoutPreferredSize; - } - - /// Strategy for handling title text that is too large to fit. Defaults to - /// truncating the text with ellipses. - MaxWidthStrategy get maxWidthStrategy => _config.maxWidthStrategy; - - set maxWidthStrategy(MaxWidthStrategy maxWidthStrategy) { - _config.maxWidthStrategy = maxWidthStrategy; - } - - /// Primary text for the title. - String get title => _config.title; - - set title(String title) { - _config.title = title; - } - - /// Direction of the chart title text. - /// - /// This defaults to horizontal for a title in the top or bottom - /// [behaviorPosition], or vertical for start or end [behaviorPosition]. - ChartTitleDirection get titleDirection => _config.titleDirection; - - set titleDirection(ChartTitleDirection titleDirection) { - _config.titleDirection = titleDirection; - } - - /// Justification of the title text if it is positioned outside of the draw - /// area. - OutsideJustification get titleOutsideJustification => - _config.titleOutsideJustification; - - set titleOutsideJustification( - OutsideJustification titleOutsideJustification) { - _config.titleOutsideJustification = titleOutsideJustification; - } - - /// Space between the title and sub-title text, if defined. - /// - /// This padding is not used if no sub-title is provided. - int get titlePadding => _config.titlePadding; - - set titlePadding(int titlePadding) { - _config.titlePadding = titlePadding; - } - - /// Style of the [title] text. - TextStyleSpec get titleStyleSpec => _config.titleStyleSpec; - - set titleStyleSpec(TextStyleSpec titleStyleSpec) { - _config.titleStyleSpec = titleStyleSpec; - } - - /// Secondary text for the sub-title. - /// - /// [subTitle] is rendered on a second line below the [title], and may be - /// styled differently. - String get subTitle => _config.subTitle; - - set subTitle(String subTitle) { - _config.subTitle = subTitle; - } - - /// Style of the [subTitle] text. - TextStyleSpec get subTitleStyleSpec => _config.subTitleStyleSpec; - - set subTitleStyleSpec(TextStyleSpec subTitleStyleSpec) { - _config.subTitleStyleSpec = subTitleStyleSpec; - } - - /// Space between the "inside" of the chart, and the title behavior itself. - /// - /// This padding is applied to all the edge of the title that is in the - /// direction of the draw area. For a top positioned title, this is applied - /// to the bottom edge. [outerPadding] is applied to the top, left, and right - /// edges. - /// - /// If a sub-title is defined, this is the space between the sub-title text - /// and the inside of the chart. Otherwise, it is the space between the title - /// text and the inside of chart. - int get innerPadding => _config.innerPadding; - - set innerPadding(int innerPadding) { - _config.innerPadding = innerPadding; - } - - /// Space between the "outside" of the chart, and the title behavior itself. - /// - /// This padding is applied to all 3 edges of the title that are not in the - /// direction of the draw area. For a top positioned title, this is applied - /// to the top, left, and right edges. [innerPadding] is applied to the - /// bottom edge. - int get outerPadding => _config.outerPadding; - - set outerPadding(int outerPadding) { - _config.outerPadding = outerPadding; - } - - @override - void attachTo(BaseChart chart) { - _chart = chart; - - _view = _ChartTitleLayoutView( - layoutPaintOrder: LayoutViewPaintOrder.chartTitle, - config: _config, - chart: _chart); - - chart.addView(_view); - chart.addLifecycleListener(_lifecycleListener); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeView(_view); - chart.removeLifecycleListener(_lifecycleListener); - _chart = null; - } - - void _updateViewData() { - _view.config = _config; - } - - @override - String get role => 'ChartTitle-${_config?.behaviorPosition}'; - - bool get isRtl => _chart.context.isRtl; -} - -/// Layout view component for [ChartTitle]. -class _ChartTitleLayoutView extends LayoutView { - LayoutViewConfig _layoutConfig; - - LayoutViewConfig get layoutConfig => _layoutConfig; - - /// Stores all of the configured properties of the behavior. - _ChartTitleConfig _config; - - BaseChart chart; - - bool get isRtl => chart?.context?.isRtl ?? false; - - Rectangle _componentBounds; - Rectangle _drawAreaBounds; - - GraphicsFactory graphicsFactory; - - /// Cached layout element for the title text. - /// - /// This is used to prevent expensive Flutter painter layout calls on every - /// animation frame during the paint cycle. It should never be cached during - /// layout measurement. - TextElement _titleTextElement; - - /// Cached layout element for the sub-title text. - /// - /// This is used to prevent expensive Flutter painter layout calls on every - /// animation frame during the paint cycle. It should never be cached during - /// layout measurement. - TextElement _subTitleTextElement; - - _ChartTitleLayoutView( - {@required int layoutPaintOrder, - @required _ChartTitleConfig config, - @required this.chart}) - : this._config = config { - // Set inside body to resolve [_layoutPosition]. - _layoutConfig = LayoutViewConfig( - paintOrder: layoutPaintOrder, - position: _layoutPosition, - positionOrder: LayoutViewPositionOrder.chartTitle); - } - - /// Sets the configuration for the title behavior. - set config(_ChartTitleConfig config) { - _config = config; - layoutConfig.position = _layoutPosition; - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - int minWidth; - int minHeight; - int preferredWidth = 0; - int preferredHeight = 0; - - // Always assume that we need outer padding and title padding, but only add - // in the sub-title padding if we have one. Title is required, but sub-title - // is optional. - final totalPadding = _config.outerPadding + - _config.innerPadding + - (_config.subTitle != null ? _config.titlePadding : 0.0); - - // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. - // The [GraphicsFactory] is needed so it can't be created earlier. - final textStyle = _getTextStyle(graphicsFactory, _config.titleStyleSpec); - - final textElement = graphicsFactory.createTextElement(_config.title) - ..maxWidthStrategy = _config.maxWidthStrategy - ..textStyle = textStyle; - - final subTitleTextStyle = - _getTextStyle(graphicsFactory, _config.subTitleStyleSpec); - - final subTitleTextElement = - graphicsFactory.createTextElement(_config.subTitle) - ..maxWidthStrategy = _config.maxWidthStrategy - ..textStyle = subTitleTextStyle; - - final resolvedTitleDirection = _resolvedTitleDirection; - - switch (_config.behaviorPosition) { - case BehaviorPosition.bottom: - case BehaviorPosition.top: - final textHeight = - (resolvedTitleDirection == ChartTitleDirection.vertical - ? textElement.measurement.horizontalSliceWidth - : textElement.measurement.verticalSliceWidth) - .round(); - - final subTitleTextHeight = _config.subTitle != null - ? (resolvedTitleDirection == ChartTitleDirection.vertical - ? subTitleTextElement.measurement.horizontalSliceWidth - : subTitleTextElement.measurement.verticalSliceWidth) - .round() - : 0; - - final measuredHeight = - (textHeight + subTitleTextHeight + totalPadding).round(); - minHeight = _config.layoutMinSize != null - ? min(_config.layoutMinSize, measuredHeight) - : measuredHeight; - - preferredWidth = maxWidth; - - preferredHeight = _config.layoutPreferredSize != null - ? min(_config.layoutPreferredSize, maxHeight) - : measuredHeight; - break; - - case BehaviorPosition.end: - case BehaviorPosition.start: - final textWidth = - (resolvedTitleDirection == ChartTitleDirection.vertical - ? textElement.measurement.verticalSliceWidth - : textElement.measurement.horizontalSliceWidth) - .round(); - - final subTitleTextWidth = _config.subTitle != null - ? (resolvedTitleDirection == ChartTitleDirection.vertical - ? subTitleTextElement.measurement.verticalSliceWidth - : subTitleTextElement.measurement.horizontalSliceWidth) - .round() - : 0; - - final measuredWidth = - (textWidth + subTitleTextWidth + totalPadding).round(); - minWidth = _config.layoutMinSize != null - ? min(_config.layoutMinSize, measuredWidth) - : measuredWidth; - - preferredWidth = _config.layoutPreferredSize != null - ? min(_config.layoutPreferredSize, maxWidth) - : measuredWidth; - - preferredHeight = maxHeight; - break; - - case BehaviorPosition.inside: - preferredWidth = _drawAreaBounds != null - ? min(_drawAreaBounds.width, maxWidth) - : maxWidth; - - preferredHeight = _drawAreaBounds != null - ? min(_drawAreaBounds.height, maxHeight) - : maxHeight; - break; - } - - // Reset the cached text elements used during the paint step. - _resetTextElementCache(); - - return ViewMeasuredSizes( - minWidth: minWidth, - minHeight: minHeight, - preferredWidth: preferredWidth, - preferredHeight: preferredHeight); - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - this._componentBounds = componentBounds; - this._drawAreaBounds = drawAreaBounds; - - // Reset the cached text elements used during the paint step. - _resetTextElementCache(); - } - - @override - void paint(ChartCanvas canvas, double animationPercent) { - final resolvedTitleDirection = _resolvedTitleDirection; - - var titleHeight = 0.0; - var subTitleHeight = 0.0; - - // First, measure the height of the title and sub-title. - if (_config.title != null) { - // Chart titles do not animate. As an optimization for Flutter, cache the - // [TextElement] to avoid an expensive painter layout operation on - // subsequent animation frames. - if (_titleTextElement == null) { - // Create [TextStyle] from [TextStyleSpec] to be used by all the - // elements. The [GraphicsFactory] is needed so it can't be created - // earlier. - final textStyle = - _getTextStyle(graphicsFactory, _config.titleStyleSpec); - - _titleTextElement = graphicsFactory.createTextElement(_config.title) - ..maxWidthStrategy = _config.maxWidthStrategy - ..textStyle = textStyle; - - _titleTextElement.maxWidth = - resolvedTitleDirection == ChartTitleDirection.horizontal - ? _componentBounds.width - : _componentBounds.height; - } - - // Get the height of the title so that we can off-set both text elements. - titleHeight = _titleTextElement.measurement.verticalSliceWidth; - } - - if (_config.subTitle != null) { - // Chart titles do not animate. As an optimization for Flutter, cache the - // [TextElement] to avoid an expensive painter layout operation on - // subsequent animation frames. - if (_subTitleTextElement == null) { - // Create [TextStyle] from [TextStyleSpec] to be used by all the - // elements. The [GraphicsFactory] is needed so it can't be created - // earlier. - final textStyle = - _getTextStyle(graphicsFactory, _config.subTitleStyleSpec); - - _subTitleTextElement = - graphicsFactory.createTextElement(_config.subTitle) - ..maxWidthStrategy = _config.maxWidthStrategy - ..textStyle = textStyle; - - _subTitleTextElement.maxWidth = - resolvedTitleDirection == ChartTitleDirection.horizontal - ? _componentBounds.width - : _componentBounds.height; - } - - // Get the height of the sub-title so that we can off-set both text - // elements. - subTitleHeight = _subTitleTextElement.measurement.verticalSliceWidth; - } - - // Draw a title if the text is not empty. - if (_config.title != null) { - final labelPoint = _getLabelPosition( - true, - _componentBounds, - resolvedTitleDirection, - _titleTextElement, - titleHeight, - subTitleHeight); - - if (labelPoint != null) { - final rotation = resolvedTitleDirection == ChartTitleDirection.vertical - ? -pi / 2 - : 0.0; - - canvas.drawText(_titleTextElement, labelPoint.x, labelPoint.y, - rotation: rotation); - } - } - - // Draw a sub-title if the text is not empty. - if (_config.subTitle != null) { - final labelPoint = _getLabelPosition( - false, - _componentBounds, - resolvedTitleDirection, - _subTitleTextElement, - titleHeight, - subTitleHeight); - - if (labelPoint != null) { - final rotation = resolvedTitleDirection == ChartTitleDirection.vertical - ? -pi / 2 - : 0.0; - - canvas.drawText(_subTitleTextElement, labelPoint.x, labelPoint.y, - rotation: rotation); - } - } - } - - /// Resets the cached text elements used during the paint step. - void _resetTextElementCache() { - _titleTextElement = null; - _subTitleTextElement = null; - } - - /// Get the direction of the title, resolving "auto" position into the - /// appropriate direction for the position of the behavior. - ChartTitleDirection get _resolvedTitleDirection { - var resolvedTitleDirection = _config.titleDirection; - if (resolvedTitleDirection == ChartTitleDirection.auto) { - switch (_config.behaviorPosition) { - case BehaviorPosition.bottom: - case BehaviorPosition.inside: - case BehaviorPosition.top: - resolvedTitleDirection = ChartTitleDirection.horizontal; - break; - case BehaviorPosition.end: - case BehaviorPosition.start: - resolvedTitleDirection = ChartTitleDirection.vertical; - break; - } - } - - return resolvedTitleDirection; - } - - /// Get layout position from chart title position. - LayoutPosition get _layoutPosition { - LayoutPosition position; - switch (_config.behaviorPosition) { - case BehaviorPosition.bottom: - position = LayoutPosition.Bottom; - break; - case BehaviorPosition.end: - position = isRtl ? LayoutPosition.Left : LayoutPosition.Right; - break; - case BehaviorPosition.inside: - position = LayoutPosition.DrawArea; - break; - case BehaviorPosition.start: - position = isRtl ? LayoutPosition.Right : LayoutPosition.Left; - break; - case BehaviorPosition.top: - position = LayoutPosition.Top; - break; - } - - // If we have a "full" [OutsideJustification], convert the layout position - // to the "full" form. - if (_config.titleOutsideJustification == OutsideJustification.start || - _config.titleOutsideJustification == OutsideJustification.middle || - _config.titleOutsideJustification == OutsideJustification.end) { - switch (position) { - case LayoutPosition.Bottom: - position = LayoutPosition.FullBottom; - break; - case LayoutPosition.Left: - position = LayoutPosition.FullLeft; - break; - case LayoutPosition.Top: - position = LayoutPosition.FullTop; - break; - case LayoutPosition.Right: - position = LayoutPosition.FullRight; - break; - - // Ignore other positions, like DrawArea. - default: - break; - } - } - - return position; - } - - /// Gets the resolved location for a label element. - Point _getLabelPosition( - bool isPrimaryTitle, - Rectangle bounds, - ChartTitleDirection titleDirection, - TextElement textElement, - double titleHeight, - double subTitleHeight) { - switch (_config.behaviorPosition) { - case BehaviorPosition.bottom: - case BehaviorPosition.top: - return _getHorizontalLabelPosition(isPrimaryTitle, bounds, - titleDirection, textElement, titleHeight, subTitleHeight); - break; - - case BehaviorPosition.start: - case BehaviorPosition.end: - return _getVerticalLabelPosition(isPrimaryTitle, bounds, titleDirection, - textElement, titleHeight, subTitleHeight); - break; - - case BehaviorPosition.inside: - break; - } - return null; - } - - /// Gets the resolved location for a title in the top or bottom margin. - Point _getHorizontalLabelPosition( - bool isPrimaryTitle, - Rectangle bounds, - ChartTitleDirection titleDirection, - TextElement textElement, - double titleHeight, - double subTitleHeight) { - int labelX = 0; - int labelY = 0; - - switch (_config.titleOutsideJustification) { - case OutsideJustification.middle: - case OutsideJustification.middleDrawArea: - final textWidth = - (isRtl ? 1 : -1) * textElement.measurement.horizontalSliceWidth / 2; - labelX = (bounds.left + bounds.width / 2 + textWidth).round(); - - textElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case OutsideJustification.end: - case OutsideJustification.endDrawArea: - case OutsideJustification.start: - case OutsideJustification.startDrawArea: - final alignLeft = isRtl - ? (_config.titleOutsideJustification == OutsideJustification.end || - _config.titleOutsideJustification == - OutsideJustification.endDrawArea) - : (_config.titleOutsideJustification == - OutsideJustification.start || - _config.titleOutsideJustification == - OutsideJustification.startDrawArea); - - // Don't apply outer padding if we are aligned to the draw area. - final padding = (_config.titleOutsideJustification == - OutsideJustification.endDrawArea || - _config.titleOutsideJustification == - OutsideJustification.startDrawArea) - ? 0.0 - : _config.outerPadding; - - if (alignLeft) { - labelX = (bounds.left + padding).round(); - textElement.textDirection = TextDirection.ltr; - } else { - labelX = (bounds.right - padding).round(); - textElement.textDirection = TextDirection.rtl; - } - break; - } - - // labelY is always relative to the component bounds. - if (_config.behaviorPosition == BehaviorPosition.bottom) { - final padding = _config.innerPadding + - (isPrimaryTitle ? 0 : _config.titlePadding + titleHeight); - - labelY = (bounds.top + padding).round(); - } else { - var padding = 0.0 + _config.innerPadding; - if (isPrimaryTitle) { - padding += - ((subTitleHeight > 0 ? _config.titlePadding + subTitleHeight : 0) + - titleHeight); - } else { - padding += subTitleHeight; - } - - labelY = (bounds.bottom - padding).round(); - } - - return Point(labelX, labelY); - } - - /// Gets the resolved location for a title in the left or right margin. - Point _getVerticalLabelPosition( - bool isPrimaryTitle, - Rectangle bounds, - ChartTitleDirection titleDirection, - TextElement textElement, - double titleHeight, - double subTitleHeight) { - int labelX = 0; - int labelY = 0; - - switch (_config.titleOutsideJustification) { - case OutsideJustification.middle: - case OutsideJustification.middleDrawArea: - final textWidth = - (isRtl ? -1 : 1) * textElement.measurement.horizontalSliceWidth / 2; - labelY = (bounds.top + bounds.height / 2 + textWidth).round(); - - textElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case OutsideJustification.end: - case OutsideJustification.endDrawArea: - case OutsideJustification.start: - case OutsideJustification.startDrawArea: - final alignLeft = isRtl - ? (_config.titleOutsideJustification == OutsideJustification.end || - _config.titleOutsideJustification == - OutsideJustification.endDrawArea) - : (_config.titleOutsideJustification == - OutsideJustification.start || - _config.titleOutsideJustification == - OutsideJustification.startDrawArea); - - // Don't apply outer padding if we are aligned to the draw area. - final padding = (_config.titleOutsideJustification == - OutsideJustification.endDrawArea || - _config.titleOutsideJustification == - OutsideJustification.startDrawArea) - ? 0.0 - : _config.outerPadding; - - if (alignLeft) { - labelY = (bounds.bottom - padding).round(); - textElement.textDirection = TextDirection.ltr; - } else { - labelY = (bounds.top + padding).round(); - textElement.textDirection = TextDirection.rtl; - } - break; - } - - // labelX is always relative to the component bounds. - if (_layoutPosition == LayoutPosition.Right || - _layoutPosition == LayoutPosition.FullRight) { - final padding = _config.outerPadding + - (isPrimaryTitle ? 0 : _config.titlePadding + titleHeight); - - labelX = (bounds.left + padding).round(); - } else { - final padding = _config.outerPadding + - titleHeight + - (isPrimaryTitle - ? (subTitleHeight > 0 ? _config.titlePadding + subTitleHeight : 0) - : 0.0); - - labelX = (bounds.right - padding).round(); - } - - return Point(labelX, labelY); - } - - // Helper function that converts [TextStyleSpec] to [TextStyle]. - TextStyle _getTextStyle( - GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { - return graphicsFactory.createTextPaint() - ..color = labelSpec?.color ?? StyleFactory.style.tickColor - ..fontFamily = labelSpec?.fontFamily - ..fontSize = labelSpec?.fontSize ?? 18; - } - - @override - Rectangle get componentBounds => this._drawAreaBounds; - - @override - bool get isSeriesRenderer => false; -} - -/// Configuration object for [ChartTitle]. -class _ChartTitleConfig { - BehaviorPosition behaviorPosition; - - int layoutMinSize; - int layoutPreferredSize; - - MaxWidthStrategy maxWidthStrategy; - - String title; - ChartTitleDirection titleDirection; - OutsideJustification titleOutsideJustification; - TextStyleSpec titleStyleSpec; - - String subTitle; - TextStyleSpec subTitleStyleSpec; - - int innerPadding; - int titlePadding; - int outerPadding; -} - -/// Direction of the title text on the chart. -enum ChartTitleDirection { - /// Automatically assign a direction based on the [RangeAnnotationAxisType]. - /// - /// [horizontal] for measure axes, or [vertical] for domain axes. - auto, - - /// Text flows parallel to the x axis. - horizontal, - - /// Text flows parallel to the y axis. - vertical, -} diff --git a/web/charts/common/lib/src/chart/common/behavior/domain_highlighter.dart b/web/charts/common/lib/src/chart/common/behavior/domain_highlighter.dart deleted file mode 100644 index f7f9caee1..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/domain_highlighter.dart +++ /dev/null @@ -1,83 +0,0 @@ -// 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 '../base_chart.dart' show BaseChart, LifecycleListener; -import '../processed_series.dart' show MutableSeries; -import '../selection_model/selection_model.dart' - show SelectionModel, SelectionModelType; -import 'chart_behavior.dart' show ChartBehavior; - -/// Chart behavior that monitors the specified [SelectionModel] and darkens the -/// color for selected data. -/// -/// This is typically used for bars and pies to highlight segments. -/// -/// It is used in combination with SelectNearest to update the selection model -/// and expand selection out to the domain value. -class DomainHighlighter implements ChartBehavior { - final SelectionModelType selectionModelType; - - BaseChart _chart; - - LifecycleListener _lifecycleListener; - - DomainHighlighter([this.selectionModelType = SelectionModelType.info]) { - _lifecycleListener = - LifecycleListener(onPostprocess: _updateColorFunctions); - } - - void _selectionChanged(SelectionModel selectionModel) { - _chart.redraw(skipLayout: true, skipAnimation: true); - } - - void _updateColorFunctions(List> seriesList) { - SelectionModel selectionModel = - _chart.getSelectionModel(selectionModelType); - seriesList.forEach((series) { - final origColorFn = series.colorFn; - - if (origColorFn != null) { - series.colorFn = (index) { - final origColor = origColorFn(index); - if (selectionModel.isDatumSelected(series, index)) { - return origColor.darker; - } else { - return origColor; - } - }; - } - }); - } - - @override - void attachTo(BaseChart chart) { - _chart = chart; - chart.addLifecycleListener(_lifecycleListener); - chart - .getSelectionModel(selectionModelType) - .addSelectionChangedListener(_selectionChanged); - } - - @override - void removeFrom(BaseChart chart) { - chart - .getSelectionModel(selectionModelType) - .removeSelectionChangedListener(_selectionChanged); - chart.removeLifecycleListener(_lifecycleListener); - } - - @override - String get role => 'domainHighlight-${selectionModelType.toString()}'; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/initial_selection.dart b/web/charts/common/lib/src/chart/common/behavior/initial_selection.dart deleted file mode 100644 index f8b714922..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/initial_selection.dart +++ /dev/null @@ -1,74 +0,0 @@ -// 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 '../base_chart.dart' show BaseChart, LifecycleListener; -import '../processed_series.dart' show MutableSeries; -import '../selection_model/selection_model.dart' - show SelectionModel, SelectionModelType; -import '../series_datum.dart' show SeriesDatumConfig; -import 'chart_behavior.dart' show ChartBehavior; - -/// Behavior that sets initial selection. -class InitialSelection implements ChartBehavior { - final SelectionModelType selectionModelType; - - /// List of series id of initially selected series. - final List selectedSeriesConfig; - - /// List of [SeriesDatumConfig] that represents the initially selected datums. - final List selectedDataConfig; - - BaseChart _chart; - LifecycleListener _lifecycleListener; - bool _firstDraw = true; - - // TODO : When the series changes, if the user does not also - // change the index the wrong item could be highlighted. - InitialSelection( - {this.selectionModelType = SelectionModelType.info, - this.selectedDataConfig, - this.selectedSeriesConfig}) { - _lifecycleListener = LifecycleListener(onData: _setInitialSelection); - } - - void _setInitialSelection(List> seriesList) { - if (!_firstDraw) { - return; - } - _firstDraw = false; - - final immutableModel = SelectionModel.fromConfig( - selectedDataConfig, selectedSeriesConfig, seriesList); - - _chart.getSelectionModel(selectionModelType).updateSelection( - immutableModel.selectedDatum, immutableModel.selectedSeries, - notifyListeners: false); - } - - @override - void attachTo(BaseChart chart) { - _chart = chart; - chart.addLifecycleListener(_lifecycleListener); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeLifecycleListener(_lifecycleListener); - _chart = null; - } - - @override - String get role => 'InitialSelection-${selectionModelType.toString()}}'; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/datum_legend.dart b/web/charts/common/lib/src/chart/common/behavior/legend/datum_legend.dart deleted file mode 100644 index 9b0d62ad9..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/datum_legend.dart +++ /dev/null @@ -1,104 +0,0 @@ -// 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 '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../datum_details.dart' show MeasureFormatter; -import '../../selection_model/selection_model.dart' show SelectionModelType; -import 'legend.dart'; -import 'legend_entry_generator.dart'; -import 'per_datum_legend_entry_generator.dart'; - -/// Datum legend behavior for charts. -/// -/// By default this behavior creates one legend entry per datum in the first -/// series rendered on the chart. -/// -/// TODO: Allows for hovering over a datum in legend to highlight -/// corresponding datum in draw area. -/// -/// TODO: Implement tap to hide individual data in the series. -class DatumLegend extends Legend { - /// Whether or not the series legend should show measures on datum selection. - bool _showMeasures; - - DatumLegend({ - SelectionModelType selectionModelType, - LegendEntryGenerator legendEntryGenerator, - MeasureFormatter measureFormatter, - MeasureFormatter secondaryMeasureFormatter, - bool showMeasures, - LegendDefaultMeasure legendDefaultMeasure, - TextStyleSpec entryTextStyle, - }) : super( - selectionModelType: selectionModelType ?? SelectionModelType.info, - legendEntryGenerator: - legendEntryGenerator ?? PerDatumLegendEntryGenerator(), - entryTextStyle: entryTextStyle) { - // Call the setters that include the setting for default. - this.showMeasures = showMeasures; - this.legendDefaultMeasure = legendDefaultMeasure; - this.measureFormatter = measureFormatter; - this.secondaryMeasureFormatter = secondaryMeasureFormatter; - } - - /// Whether or not the legend should show measures. - /// - /// By default this is false, measures are not shown. When set to true, the - /// default behavior is to show measure only if there is selected data. - /// Please set [legendDefaultMeasure] to something other than none to enable - /// showing measures when there is no selection. - /// - /// If [showMeasure] is set to null, it is changed to the default of false. - bool get showMeasures => _showMeasures; - - set showMeasures(bool showMeasures) { - _showMeasures = showMeasures ?? false; - } - - /// Option to show measures when selection is null. - /// - /// By default this is set to none, so no measures are shown when there is - /// no selection. - /// - /// If [legendDefaultMeasure] is set to null, it is changed to the default of - /// none. - LegendDefaultMeasure get legendDefaultMeasure => - legendEntryGenerator.legendDefaultMeasure; - - set legendDefaultMeasure(LegendDefaultMeasure legendDefaultMeasure) { - legendEntryGenerator.legendDefaultMeasure = - legendDefaultMeasure ?? LegendDefaultMeasure.none; - } - - /// Formatter for measure values. - /// - /// This is optional. The default formatter formats measure values with - /// NumberFormat.decimalPattern. If the measure value is null, a dash is - /// returned. - set measureFormatter(MeasureFormatter formatter) { - legendEntryGenerator.measureFormatter = - formatter ?? defaultLegendMeasureFormatter; - } - - /// Formatter for measure values of series that uses the secondary axis. - /// - /// This is optional. The default formatter formats measure values with - /// NumberFormat.decimalPattern. If the measure value is null, a dash is - /// returned. - set secondaryMeasureFormatter(MeasureFormatter formatter) { - legendEntryGenerator.secondaryMeasureFormatter = - formatter ?? defaultLegendMeasureFormatter; - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/legend.dart b/web/charts/common/lib/src/chart/common/behavior/legend/legend.dart deleted file mode 100644 index ac4b3f7f5..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/legend.dart +++ /dev/null @@ -1,377 +0,0 @@ -// 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' show protected; -import 'package:intl/intl.dart'; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPositionOrder, - LayoutViewPaintOrder, - ViewMeasuredSizes; -import '../../base_chart.dart' show BaseChart, LifecycleListener; -import '../../chart_canvas.dart' show ChartCanvas; -import '../../chart_context.dart' show ChartContext; -import '../../processed_series.dart' show MutableSeries; -import '../../selection_model/selection_model.dart' - show SelectionModel, SelectionModelType; -import '../chart_behavior.dart' - show - BehaviorPosition, - ChartBehavior, - InsideJustification, - OutsideJustification; -import 'legend_entry.dart'; -import 'legend_entry_generator.dart'; - -/// Legend behavior for charts. -/// -/// Since legends are desired to be customizable, building and displaying the -/// visual content of legends is done on the native platforms. This allows users -/// to specify customized content for legends using the native platform (ex. for -/// Flutter, using widgets). -abstract class Legend implements ChartBehavior, LayoutView { - final SelectionModelType selectionModelType; - final legendState = LegendState(); - final LegendEntryGenerator legendEntryGenerator; - - /// Sets title text to display before legend entries. - String title; - - BaseChart _chart; - LifecycleListener _lifecycleListener; - - Rectangle _componentBounds; - Rectangle _drawAreaBounds; - GraphicsFactory graphicsFactory; - - BehaviorPosition behaviorPosition = BehaviorPosition.end; - OutsideJustification outsideJustification = - OutsideJustification.startDrawArea; - InsideJustification insideJustification = InsideJustification.topStart; - LegendCellPadding cellPadding; - LegendCellPadding legendPadding; - - /// Text style of the legend title text. - TextStyleSpec titleTextStyle; - - /// Configures the behavior of the legend when the user taps/clicks on an - /// entry. Defaults to no behavior. - /// - /// Tapping on a legend entry will update the data visible on the chart. For - /// example, when [LegendTapHandling.hide] is configured, the series or datum - /// associated with that entry will be removed from the chart. Tapping on that - /// entry a second time will make the data visible again. - LegendTapHandling legendTapHandling = LegendTapHandling.hide; - - List> _currentSeriesList; - - /// Save this in order to check if series list have changed and regenerate - /// the legend entries. - List> _postProcessSeriesList; - - static final _decimalPattern = NumberFormat.decimalPattern(); - - /// Default measure formatter for legends. - @protected - String defaultLegendMeasureFormatter(num value) { - return (value == null) ? '' : _decimalPattern.format(value); - } - - Legend({this.selectionModelType, this.legendEntryGenerator, entryTextStyle}) { - _lifecycleListener = LifecycleListener( - onPostprocess: _postProcess, onPreprocess: _preProcess, onData: onData); - legendEntryGenerator.entryTextStyle = entryTextStyle; - } - - /// Text style of the legend entry text. - TextStyleSpec get entryTextStyle => legendEntryGenerator.entryTextStyle; - - set entryTextStyle(TextStyleSpec entryTextStyle) { - legendEntryGenerator.entryTextStyle = entryTextStyle; - } - - /// Resets any hidden series data when new data is drawn on the chart. - @protected - void onData(List> seriesList) {} - - /// Store off a copy of the series list for use when we render the legend. - void _preProcess(List> seriesList) { - _currentSeriesList = List.from(seriesList); - preProcessSeriesList(seriesList); - } - - /// Overridable method that may be used by concrete [Legend] instances to - /// manipulate the series list. - @protected - void preProcessSeriesList(List> seriesList) {} - - /// Build LegendEntries from list of series. - void _postProcess(List> seriesList) { - // Get the selection model directly from chart on post process. - // - // This is because if initial selection is set as a behavior, it will be - // handled during onData. onData is prior to this behavior's postProcess - // call, so the selection will have changed prior to the entries being - // generated. - final selectionModel = chart.getSelectionModel(selectionModelType); - - // Update entries if the selection model is different because post - // process is called on each draw cycle, so this is called on each animation - // frame and we don't want to update and request the native platform to - // rebuild if nothing has changed. - // - // Also update legend entries if the series list has changed. - if (legendState._selectionModel != selectionModel || - _postProcessSeriesList != seriesList) { - legendState._legendEntries = - legendEntryGenerator.getLegendEntries(_currentSeriesList); - - legendState._selectionModel = selectionModel; - _postProcessSeriesList = seriesList; - _updateLegendEntries(); - } - } - - // need to handle when series data changes, selection should be reset - - /// Update the legend state with [selectionModel] and request legend update. - void _selectionChanged(SelectionModel selectionModel) { - legendState._selectionModel = selectionModel; - _updateLegendEntries(); - } - - ChartContext get chartContext => _chart.context; - - /// Internally update legend entries, before calling [updateLegend] that - /// notifies the native platform. - void _updateLegendEntries() { - legendEntryGenerator.updateLegendEntries(legendState._legendEntries, - legendState._selectionModel, chart.currentSeriesList); - - updateLegend(); - } - - /// Requires override to show in native platform - void updateLegend() {} - - @override - void attachTo(BaseChart chart) { - _chart = chart; - chart.addLifecycleListener(_lifecycleListener); - chart - .getSelectionModel(selectionModelType) - .addSelectionChangedListener(_selectionChanged); - - chart.addView(this); - } - - @override - void removeFrom(BaseChart chart) { - chart - .getSelectionModel(selectionModelType) - .removeSelectionChangedListener(_selectionChanged); - chart.removeLifecycleListener(_lifecycleListener); - - chart.removeView(this); - } - - @protected - BaseChart get chart => _chart; - - @override - String get role => 'legend-${selectionModelType.toString()}'; - - bool get isRtl => _chart.context.isRtl; - - @override - LayoutViewConfig get layoutConfig { - return LayoutViewConfig( - position: _layoutPosition, - positionOrder: LayoutViewPositionOrder.legend, - paintOrder: LayoutViewPaintOrder.legend); - } - - /// Get layout position from legend position. - LayoutPosition get _layoutPosition { - LayoutPosition position; - switch (behaviorPosition) { - case BehaviorPosition.bottom: - position = LayoutPosition.Bottom; - break; - case BehaviorPosition.end: - position = isRtl ? LayoutPosition.Left : LayoutPosition.Right; - break; - case BehaviorPosition.inside: - position = LayoutPosition.DrawArea; - break; - case BehaviorPosition.start: - position = isRtl ? LayoutPosition.Right : LayoutPosition.Left; - position = isRtl ? LayoutPosition.Right : LayoutPosition.Left; - break; - case BehaviorPosition.top: - position = LayoutPosition.Top; - break; - } - - return position; - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - // Native child classes should override this method to return real - // measurements. - return ViewMeasuredSizes(preferredWidth: 0, preferredHeight: 0); - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - _componentBounds = componentBounds; - _drawAreaBounds = drawAreaBounds; - - updateLegend(); - } - - @override - void paint(ChartCanvas canvas, double animationPercent) {} - - @override - Rectangle get componentBounds => _componentBounds; - - @override - bool get isSeriesRenderer => false; - - // Gets the draw area bounds for native legend content to position itself - // accordingly. - Rectangle get drawAreaBounds => _drawAreaBounds; -} - -/// Stores legend data used by native legend content builder. -class LegendState { - List> _legendEntries; - SelectionModel _selectionModel; - - List> get legendEntries => _legendEntries; - SelectionModel get selectionModel => _selectionModel; -} - -/// Stores legend cell padding, in percents or pixels. -/// -/// If a percent is specified, it takes precedence over a flat pixel value. -class LegendCellPadding { - final double bottomPct; - final double bottomPx; - final double leftPct; - final double leftPx; - final double rightPct; - final double rightPx; - final double topPct; - final double topPx; - - /// Creates padding in percents from the left, top, right, and bottom. - const LegendCellPadding.fromLTRBPct( - this.leftPct, this.topPct, this.rightPct, this.bottomPct) - : leftPx = null, - topPx = null, - rightPx = null, - bottomPx = null; - - /// Creates padding in pixels from the left, top, right, and bottom. - const LegendCellPadding.fromLTRBPx( - this.leftPx, this.topPx, this.rightPx, this.bottomPx) - : leftPct = null, - topPct = null, - rightPct = null, - bottomPct = null; - - /// Creates padding in percents from the top, right, bottom, and left. - const LegendCellPadding.fromTRBLPct( - this.topPct, this.rightPct, this.bottomPct, this.leftPct) - : topPx = null, - rightPx = null, - bottomPx = null, - leftPx = null; - - /// Creates padding in pixels from the top, right, bottom, and left. - const LegendCellPadding.fromTRBLPx( - this.topPx, this.rightPx, this.bottomPx, this.leftPx) - : topPct = null, - rightPct = null, - bottomPct = null, - leftPct = null; - - /// Creates cell padding where all the offsets are `value` in percent. - /// - /// ## Sample code - /// - /// Typical eight percent margin on all sides: - /// - /// ```dart - /// const LegendCellPadding.allPct(8.0) - /// ``` - const LegendCellPadding.allPct(double value) - : leftPct = value, - topPct = value, - rightPct = value, - bottomPct = value, - leftPx = null, - topPx = null, - rightPx = null, - bottomPx = null; - - /// Creates cell padding where all the offsets are `value` in pixels. - /// - /// ## Sample code - /// - /// Typical eight-pixel margin on all sides: - /// - /// ```dart - /// const LegendCellPadding.allPx(8.0) - /// ``` - const LegendCellPadding.allPx(double value) - : leftPx = value, - topPx = value, - rightPx = value, - bottomPx = value, - leftPct = null, - topPct = null, - rightPct = null, - bottomPct = null; - - double bottom(num height) => - bottomPct != null ? bottomPct * height : bottomPx; - - double left(num width) => leftPct != null ? leftPct * width : leftPx; - - double right(num width) => rightPct != null ? rightPct * width : rightPx; - - double top(num height) => topPct != null ? topPct * height : topPx; -} - -/// Options for behavior of tapping/clicking on entries in the legend. -enum LegendTapHandling { - /// No associated behavior. - none, - - /// Hide elements on the chart associated with this legend entry. - hide, -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/legend_entry.dart b/web/charts/common/lib/src/chart/common/behavior/legend/legend_entry.dart deleted file mode 100644 index b3824bfd6..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/legend_entry.dart +++ /dev/null @@ -1,85 +0,0 @@ -// 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 '../../../../common/color.dart'; -import '../../../../common/symbol_renderer.dart'; -import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../processed_series.dart' show ImmutableSeries; -import '../../series_renderer.dart' show rendererKey; - -/// Holder for the information used for a legend row. -/// -/// [T] the datum class type for the series passed in. -/// [D] the domain class type for the datum. -class LegendEntry { - final String label; - final ImmutableSeries series; - final dynamic datum; - final int datumIndex; - final D domain; - final Color color; - final TextStyleSpec textStyle; - double value; - String formattedValue; - bool isSelected; - - /// Zero based index for the row where this legend appears in the legend. - int rowNumber; - - /// Zero based index for the column where this legend appears in the legend. - int columnNumber; - - /// Total number of rows in the legend. - int rowCount; - - /// Total number of columns in the legend. - int columnCount; - - /// Indicates whether this is in the first row of a tabular layout. - bool inFirstRow; - - /// Indicates whether this is in the first column of a tabular layout. - bool inFirstColumn; - - /// Indicates whether this is in the last row of a tabular layout. - bool inLastRow; - - /// Indicates whether this is in the last column of a tabular layout. - bool inLastColumn; - - // TODO: Forward the default formatters from series and allow for - // native legends to provide separate formatters. - - LegendEntry(this.series, this.label, - {this.datum, - this.datumIndex, - this.domain, - this.value, - this.color, - this.textStyle, - this.isSelected = false, - this.rowNumber, - this.columnNumber, - this.rowCount, - this.columnCount, - this.inFirstRow, - this.inFirstColumn, - this.inLastRow, - this.inLastColumn}); - - /// Get the native symbol renderer stored in the series. - SymbolRenderer get symbolRenderer => - series.getAttr(rendererKey).symbolRenderer; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/legend_entry_generator.dart b/web/charts/common/lib/src/chart/common/behavior/legend/legend_entry_generator.dart deleted file mode 100644 index fa0a8a7ad..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/legend_entry_generator.dart +++ /dev/null @@ -1,68 +0,0 @@ -// 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 '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../datum_details.dart' show MeasureFormatter; -import '../../processed_series.dart' show MutableSeries; -import '../../selection_model/selection_model.dart'; -import 'legend_entry.dart'; - -/// A strategy for generating a list of [LegendEntry] based on the series drawn. -/// -/// [D] the domain class type for the datum. -abstract class LegendEntryGenerator { - /// Generates a list of legend entries based on the series drawn on the chart. - /// - /// [seriesList] Processed series list. - List> getLegendEntries(List> seriesList); - - /// Update the list of legend entries based on the selection model. - /// - /// [legendEntries] Existing legend entries to update. - /// [selectionModel] Selection model to query selected state. - /// [seriesList] Processed series list. - void updateLegendEntries(List> legendEntries, - SelectionModel selectionModel, List> seriesList); - - MeasureFormatter get measureFormatter; - - set measureFormatter(MeasureFormatter formatter); - - MeasureFormatter get secondaryMeasureFormatter; - - set secondaryMeasureFormatter(MeasureFormatter formatter); - - LegendDefaultMeasure get legendDefaultMeasure; - - set legendDefaultMeasure(LegendDefaultMeasure noSelectionMeasure); - - TextStyleSpec get entryTextStyle; - - set entryTextStyle(TextStyleSpec entryTextStyle); -} - -/// Options for calculating what measures are shown when there is no selection. -enum LegendDefaultMeasure { - // No measures are shown where there is no selection. - none, - // Sum of all measure values for the series. - sum, - // Average of all measure values for the series. - average, - // The first measure value of the series. - firstValue, - // The last measure value of the series. - lastValue, -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/per_datum_legend_entry_generator.dart b/web/charts/common/lib/src/chart/common/behavior/legend/per_datum_legend_entry_generator.dart deleted file mode 100644 index 50413e373..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/per_datum_legend_entry_generator.dart +++ /dev/null @@ -1,145 +0,0 @@ -// 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:collection' show HashSet; -import '../../../cartesian/axis/axis.dart' show Axis, measureAxisIdKey; -import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../datum_details.dart' show MeasureFormatter; -import '../../processed_series.dart' show ImmutableSeries, MutableSeries; -import '../../selection_model/selection_model.dart'; -import 'legend_entry.dart'; -import 'legend_entry_generator.dart'; - -/// A strategy for generating a list of [LegendEntry] per series data drawn. -/// -/// [D] the domain class type for the datum. -class PerDatumLegendEntryGenerator implements LegendEntryGenerator { - TextStyleSpec entryTextStyle; - MeasureFormatter measureFormatter; - MeasureFormatter secondaryMeasureFormatter; - - /// Option for showing measures when there is no selection. - LegendDefaultMeasure legendDefaultMeasure; - - @override - List> getLegendEntries(List> seriesList) { - final legendEntries = >[]; - - final series = seriesList[0]; - for (var i = 0; i < series.data.length; i++) { - legendEntries.add(LegendEntry(series, series.domainFn(i).toString(), - color: series.colorFn(i), - datum: series.data[i], - datumIndex: i, - textStyle: entryTextStyle)); - } - - // Update with measures only if showing measure on no selection. - if (legendDefaultMeasure != LegendDefaultMeasure.none) { - _updateFromSeriesList(legendEntries, seriesList); - } - - return legendEntries; - } - - @override - void updateLegendEntries(List> legendEntries, - SelectionModel selectionModel, List> seriesList) { - if (selectionModel.hasAnySelection) { - _updateFromSelection(legendEntries, selectionModel); - } else { - // Update with measures only if showing measure on no selection. - if (legendDefaultMeasure != LegendDefaultMeasure.none) { - _updateFromSeriesList(legendEntries, seriesList); - } else { - _resetLegendEntryMeasures(legendEntries); - } - } - } - - /// Update legend entries with measures of the selected datum - void _updateFromSelection( - List> legendEntries, SelectionModel selectionModel) { - // Given that each legend entry only has one datum associated with it, any - // option for [legendDefaultMeasure] essentially boils down to just showing - // the measure value. - if (legendDefaultMeasure != LegendDefaultMeasure.none) { - for (var entry in legendEntries) { - final series = entry.series; - final measure = series.measureFn(entry.datumIndex); - entry.value = measure.toDouble(); - entry.formattedValue = _getFormattedMeasureValue(series, measure); - - entry.isSelected = selectionModel.selectedSeries - .any((selectedSeries) => series.id == selectedSeries.id); - } - } - } - - void _resetLegendEntryMeasures(List> legendEntries) { - for (LegendEntry entry in legendEntries) { - entry.value = null; - entry.formattedValue = null; - entry.isSelected = false; - } - } - - /// Update each legend entry by calculating measure values in [seriesList]. - /// - /// This method calculates the legend's measure value to show when there is no - /// selection. The type of calculation is based on the [legendDefaultMeasure] - /// value. - void _updateFromSeriesList( - List> legendEntries, List> seriesList) { - // Given that each legend entry only has one datum associated with it, any - // option for [legendDefaultMeasure] essentially boils down to just showing - // the measure value. - if (legendDefaultMeasure != LegendDefaultMeasure.none) { - for (var entry in legendEntries) { - final series = entry.series; - final measure = series.measureFn(entry.datumIndex); - entry.value = measure.toDouble(); - entry.formattedValue = _getFormattedMeasureValue(series, measure); - entry.isSelected = false; - } - } - } - - /// Formats the measure value using the appropriate measure formatter - /// function for the series. - String _getFormattedMeasureValue(ImmutableSeries series, num measure) { - return (series.getAttr(measureAxisIdKey) == Axis.secondaryMeasureAxisId) - ? secondaryMeasureFormatter(measure) - : measureFormatter(measure); - } - - @override - bool operator ==(Object other) { - return other is PerDatumLegendEntryGenerator && - measureFormatter == other.measureFormatter && - secondaryMeasureFormatter == other.secondaryMeasureFormatter && - legendDefaultMeasure == other.legendDefaultMeasure && - entryTextStyle == other.entryTextStyle; - } - - @override - int get hashCode { - int hashcode = measureFormatter?.hashCode ?? 0; - hashcode = (hashcode * 37) + secondaryMeasureFormatter.hashCode; - hashcode = (hashcode * 37) + legendDefaultMeasure.hashCode; - hashcode = (hashcode * 37) + entryTextStyle.hashCode; - return hashcode; - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/per_series_legend_entry_generator.dart b/web/charts/common/lib/src/chart/common/behavior/legend/per_series_legend_entry_generator.dart deleted file mode 100644 index b34c47492..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/per_series_legend_entry_generator.dart +++ /dev/null @@ -1,190 +0,0 @@ -// 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:collection' show HashSet; - -import '../../../cartesian/axis/axis.dart' show Axis, measureAxisIdKey; -import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../datum_details.dart' show MeasureFormatter; -import '../../processed_series.dart' show MutableSeries; -import '../../selection_model/selection_model.dart'; -import '../../series_datum.dart' show SeriesDatum; -import 'legend_entry.dart'; -import 'legend_entry_generator.dart'; - -/// A strategy for generating a list of [LegendEntry] per series drawn. -/// -/// [D] the domain class type for the datum. -class PerSeriesLegendEntryGenerator implements LegendEntryGenerator { - TextStyleSpec entryTextStyle; - MeasureFormatter measureFormatter; - MeasureFormatter secondaryMeasureFormatter; - - /// Option for showing measures when there is no selection. - LegendDefaultMeasure legendDefaultMeasure; - - @override - List> getLegendEntries(List> seriesList) { - final legendEntries = seriesList - .map((series) => LegendEntry(series, series.displayName, - color: series.colorFn(0), textStyle: entryTextStyle)) - .toList(); - - // Update with measures only if showing measure on no selection. - if (legendDefaultMeasure != LegendDefaultMeasure.none) { - _updateFromSeriesList(legendEntries, seriesList); - } - - return legendEntries; - } - - @override - void updateLegendEntries(List> legendEntries, - SelectionModel selectionModel, List> seriesList) { - if (selectionModel.hasAnySelection) { - _updateFromSelection(legendEntries, selectionModel); - } else { - // Update with measures only if showing measure on no selection. - if (legendDefaultMeasure != LegendDefaultMeasure.none) { - _updateFromSeriesList(legendEntries, seriesList); - } else { - _resetLegendEntryMeasures(legendEntries); - } - } - } - - /// Update legend entries with measures of the selected datum - void _updateFromSelection( - List> legendEntries, SelectionModel selectionModel) { - // Map of series ID to the total selected measure value for that series. - final seriesAndMeasure = {}; - - // Hash set of series ID's that use the secondary measure axis - final secondaryAxisSeriesIDs = HashSet(); - - for (SeriesDatum selectedDatum in selectionModel.selectedDatum) { - final series = selectedDatum.series; - final seriesId = series.id; - final measure = series.measureFn(selectedDatum.index) ?? 0; - - seriesAndMeasure[seriesId] = seriesAndMeasure.containsKey(seriesId) - ? seriesAndMeasure[seriesId] + measure - : measure; - - if (series.getAttr(measureAxisIdKey) == Axis.secondaryMeasureAxisId) { - secondaryAxisSeriesIDs.add(seriesId); - } - } - - for (var entry in legendEntries) { - final seriesId = entry.series.id; - final measureValue = seriesAndMeasure[seriesId]?.toDouble(); - final formattedValue = secondaryAxisSeriesIDs.contains(seriesId) - ? secondaryMeasureFormatter(measureValue) - : measureFormatter(measureValue); - - entry.value = measureValue; - entry.formattedValue = formattedValue; - entry.isSelected = selectionModel.selectedSeries - .any((selectedSeries) => entry.series.id == selectedSeries.id); - } - } - - void _resetLegendEntryMeasures(List> legendEntries) { - for (LegendEntry entry in legendEntries) { - entry.value = null; - entry.formattedValue = null; - entry.isSelected = false; - } - } - - /// Update each legend entry by calculating measure values in [seriesList]. - /// - /// This method calculates the legend's measure value to show when there is no - /// selection. The type of calculation is based on the [legendDefaultMeasure] - /// value. - void _updateFromSeriesList( - List> legendEntries, List> seriesList) { - // Helper function to sum up the measure values - num getMeasureTotal(MutableSeries series) { - var measureTotal = 0.0; - for (var i = 0; i < series.data.length; i++) { - measureTotal += series.measureFn(i); - } - return measureTotal; - } - - // Map of series ID to the calculated measure for that series. - final seriesAndMeasure = {}; - // Map of series ID and the formatted measure for that series. - final seriesAndFormattedMeasure = {}; - - for (MutableSeries series in seriesList) { - final seriesId = series.id; - num calculatedMeasure; - - switch (legendDefaultMeasure) { - case LegendDefaultMeasure.sum: - calculatedMeasure = getMeasureTotal(series); - break; - case LegendDefaultMeasure.average: - calculatedMeasure = getMeasureTotal(series) / series.data.length; - break; - case LegendDefaultMeasure.firstValue: - calculatedMeasure = series.measureFn(0); - break; - case LegendDefaultMeasure.lastValue: - calculatedMeasure = series.measureFn(series.data.length - 1); - break; - case LegendDefaultMeasure.none: - // [calculatedMeasure] intentionally left null, since we do not want - // to show any measures. - break; - } - - seriesAndMeasure[seriesId] = calculatedMeasure?.toDouble(); - seriesAndFormattedMeasure[seriesId] = - (series.getAttr(measureAxisIdKey) == Axis.secondaryMeasureAxisId) - ? secondaryMeasureFormatter(calculatedMeasure) - : measureFormatter(calculatedMeasure); - } - - for (var entry in legendEntries) { - final seriesId = entry.series.id; - - entry.value = seriesAndMeasure[seriesId]; - entry.formattedValue = seriesAndFormattedMeasure[seriesId]; - entry.isSelected = false; - } - } - - @override - bool operator ==(Object other) { - return other is PerSeriesLegendEntryGenerator && - measureFormatter == other.measureFormatter && - secondaryMeasureFormatter == other.secondaryMeasureFormatter && - legendDefaultMeasure == other.legendDefaultMeasure && - entryTextStyle == other.entryTextStyle; - } - - @override - int get hashCode { - int hashcode = measureFormatter?.hashCode ?? 0; - hashcode = (hashcode * 37) + secondaryMeasureFormatter.hashCode; - hashcode = (hashcode * 37) + legendDefaultMeasure.hashCode; - hashcode = (hashcode * 37) + entryTextStyle.hashCode; - return hashcode; - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/legend/series_legend.dart b/web/charts/common/lib/src/chart/common/behavior/legend/series_legend.dart deleted file mode 100644 index db62e81e1..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/legend/series_legend.dart +++ /dev/null @@ -1,171 +0,0 @@ -// 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 'package:meta/meta.dart' show protected; - -import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../datum_details.dart' show MeasureFormatter; -import '../../processed_series.dart' show MutableSeries; -import '../../selection_model/selection_model.dart' show SelectionModelType; -import 'legend.dart'; -import 'legend_entry_generator.dart'; -import 'per_series_legend_entry_generator.dart'; - -// TODO: Allows for hovering over a series in legend to highlight -// corresponding series in draw area. - -/// Series legend behavior for charts. -/// -/// By default this behavior creates a legend entry per series. -class SeriesLegend extends Legend { - /// List of currently hidden series, by ID. - final _hiddenSeriesList = Set(); - - /// List of series IDs that should be hidden by default. - List _defaultHiddenSeries; - - /// Whether or not the series legend should show measures on datum selection. - bool _showMeasures; - - SeriesLegend({ - SelectionModelType selectionModelType, - LegendEntryGenerator legendEntryGenerator, - MeasureFormatter measureFormatter, - MeasureFormatter secondaryMeasureFormatter, - bool showMeasures, - LegendDefaultMeasure legendDefaultMeasure, - TextStyleSpec entryTextStyle, - }) : super( - selectionModelType: selectionModelType ?? SelectionModelType.info, - legendEntryGenerator: - legendEntryGenerator ?? PerSeriesLegendEntryGenerator(), - entryTextStyle: entryTextStyle) { - // Call the setters that include the setting for default. - this.showMeasures = showMeasures; - this.legendDefaultMeasure = legendDefaultMeasure; - this.measureFormatter = measureFormatter; - this.secondaryMeasureFormatter = secondaryMeasureFormatter; - } - - /// Sets a list of series IDs that should be hidden by default on first chart - /// draw. - /// - /// This will also reset the current list of hidden series, filling it in with - /// the new default list. - set defaultHiddenSeries(List defaultHiddenSeries) { - _defaultHiddenSeries = defaultHiddenSeries; - - _hiddenSeriesList.clear(); - - if (_defaultHiddenSeries != null) { - _defaultHiddenSeries.forEach(hideSeries); - } - } - - /// Gets a list of series IDs that should be hidden by default on first chart - /// draw. - List get defaultHiddenSeries => _defaultHiddenSeries; - - /// Whether or not the legend should show measures. - /// - /// By default this is false, measures are not shown. When set to true, the - /// default behavior is to show measure only if there is selected data. - /// Please set [legendDefaultMeasure] to something other than none to enable - /// showing measures when there is no selection. - /// - /// If [showMeasure] is set to null, it is changed to the default of false. - bool get showMeasures => _showMeasures; - - set showMeasures(bool showMeasures) { - _showMeasures = showMeasures ?? false; - } - - /// Option to show measures when selection is null. - /// - /// By default this is set to none, so no measures are shown when there is - /// no selection. - /// - /// If [legendDefaultMeasure] is set to null, it is changed to the default of - /// none. - LegendDefaultMeasure get legendDefaultMeasure => - legendEntryGenerator.legendDefaultMeasure; - - set legendDefaultMeasure(LegendDefaultMeasure legendDefaultMeasure) { - legendEntryGenerator.legendDefaultMeasure = - legendDefaultMeasure ?? LegendDefaultMeasure.none; - } - - /// Formatter for measure values. - /// - /// This is optional. The default formatter formats measure values with - /// NumberFormat.decimalPattern. If the measure value is null, a dash is - /// returned. - set measureFormatter(MeasureFormatter formatter) { - legendEntryGenerator.measureFormatter = - formatter ?? defaultLegendMeasureFormatter; - } - - /// Formatter for measure values of series that uses the secondary axis. - /// - /// This is optional. The default formatter formats measure values with - /// NumberFormat.decimalPattern. If the measure value is null, a dash is - /// returned. - set secondaryMeasureFormatter(MeasureFormatter formatter) { - legendEntryGenerator.secondaryMeasureFormatter = - formatter ?? defaultLegendMeasureFormatter; - } - - /// Remove series IDs from the currently hidden list if those series have been - /// removed from the chart data. The goal is to allow any metric that is - /// removed from a chart, and later re-added to it, to be visible to the user. - @override - void onData(List> seriesList) { - // If a series was removed from the chart, remove it from our current list - // of hidden series. - final seriesIds = seriesList.map((series) => series.id); - - _hiddenSeriesList.removeWhere((id) => !seriesIds.contains(id)); - } - - @override - void preProcessSeriesList(List> seriesList) { - seriesList.removeWhere((series) { - return _hiddenSeriesList.contains(series.id); - }); - } - - /// Hides the data for a series on the chart by [seriesId]. - /// - /// The entry in the legend for this series will be grayed out to indicate - /// that it is hidden. - @protected - void hideSeries(String seriesId) { - _hiddenSeriesList.add(seriesId); - } - - /// Shows the data for a series on the chart by [seriesId]. - /// - /// The entry in the legend for this series will be returned to its normal - /// color if it was previously hidden. - @protected - void showSeries(String seriesId) { - _hiddenSeriesList.removeWhere((id) => id == seriesId); - } - - /// Returns whether or not a given series [seriesId] is currently hidden. - bool isSeriesHidden(String seriesId) { - return _hiddenSeriesList.contains(seriesId); - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/line_point_highlighter.dart b/web/charts/common/lib/src/chart/common/behavior/line_point_highlighter.dart deleted file mode 100644 index 165bf8c39..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/line_point_highlighter.dart +++ /dev/null @@ -1,689 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show max, min, Point, Rectangle; - -import 'package:meta/meta.dart'; - -import '../../../common/color.dart' show Color; -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/style/style_factory.dart' show StyleFactory; -import '../../../common/symbol_renderer.dart' - show CircleSymbolRenderer, SymbolRenderer; -import '../../cartesian/axis/axis.dart' - show ImmutableAxis, domainAxisKey, measureAxisKey; -import '../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - ViewMeasuredSizes; -import '../base_chart.dart' show BaseChart, LifecycleListener; -import '../chart_canvas.dart' show ChartCanvas, getAnimatedColor; -import '../datum_details.dart' show DatumDetails; -import '../processed_series.dart' show ImmutableSeries; -import '../selection_model/selection_model.dart' - show SelectionModel, SelectionModelType; -import 'chart_behavior.dart' show ChartBehavior; - -/// Chart behavior that monitors the specified [SelectionModel] and renders a -/// dot for selected data. -/// -/// Vertical or horizontal follow lines can optionally be drawn underneath the -/// rendered dots. Follow lines will be drawn in the combined area of the chart -/// draw area, and the draw area for any layout components that provide a -/// series draw area (e.g. [SymbolAnnotationRenderer]). -/// -/// This is typically used for line charts to highlight segments. -/// -/// It is used in combination with SelectNearest to update the selection model -/// and expand selection out to the domain value. -class LinePointHighlighter implements ChartBehavior { - final SelectionModelType selectionModelType; - - /// Default radius of the dots if the series has no radius mapping function. - /// - /// When no radius mapping function is provided, this value will be used as - /// is. [radiusPaddingPx] will not be added to [defaultRadiusPx]. - final double defaultRadiusPx; - - /// Additional radius value added to the radius of the selected data. - /// - /// This value is only used when the series has a radius mapping function - /// defined. - final double radiusPaddingPx; - - /// Whether or not to draw horizontal follow lines through the selected - /// points. - /// - /// Defaults to drawing no horizontal follow lines. - final LinePointHighlighterFollowLineType showHorizontalFollowLine; - - /// Whether or not to draw vertical follow lines through the selected points. - /// - /// Defaults to drawing a vertical follow line only for the nearest datum. - final LinePointHighlighterFollowLineType showVerticalFollowLine; - - /// The dash pattern to be used for drawing the line. - /// - /// To disable dash pattern (to draw a solid line), pass in an empty list. - /// This is because if dashPattern is null or not set, it defaults to [1,3]. - final List dashPattern; - - /// Whether or not follow lines should be drawn across the entire chart draw - /// area, or just from the axis to the point. - /// - /// When disabled, measure follow lines will be drawn from the primary measure - /// axis to the point. In RTL mode, this means from the right-hand axis. In - /// LTR mode, from the left-hand axis. - final bool drawFollowLinesAcrossChart; - - /// Renderer used to draw the highlighted points. - final SymbolRenderer symbolRenderer; - - BaseChart _chart; - - _LinePointLayoutView _view; - - LifecycleListener _lifecycleListener; - - /// Store a map of data drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was provided by the selection model. - var _seriesPointMap = LinkedHashMap>(); - - // Store a list of points that exist in the series data. - // - // This list will be used to remove any [_AnimatedPoint] that were rendered in - // previous draw cycles, but no longer have a corresponding datum in the new - // data. - final _currentKeys = []; - - LinePointHighlighter( - {SelectionModelType selectionModelType, - double defaultRadiusPx, - double radiusPaddingPx, - LinePointHighlighterFollowLineType showHorizontalFollowLine, - LinePointHighlighterFollowLineType showVerticalFollowLine, - List dashPattern, - bool drawFollowLinesAcrossChart, - SymbolRenderer symbolRenderer}) - : selectionModelType = selectionModelType ?? SelectionModelType.info, - defaultRadiusPx = defaultRadiusPx ?? 4.0, - radiusPaddingPx = radiusPaddingPx ?? 2.0, - showHorizontalFollowLine = - showHorizontalFollowLine ?? LinePointHighlighterFollowLineType.none, - showVerticalFollowLine = showVerticalFollowLine ?? - LinePointHighlighterFollowLineType.nearest, - dashPattern = dashPattern ?? [1, 3], - drawFollowLinesAcrossChart = drawFollowLinesAcrossChart ?? true, - symbolRenderer = symbolRenderer ?? CircleSymbolRenderer() { - _lifecycleListener = - LifecycleListener(onAxisConfigured: _updateViewData); - } - - @override - void attachTo(BaseChart chart) { - _chart = chart; - - _view = _LinePointLayoutView( - chart: chart, - layoutPaintOrder: LayoutViewPaintOrder.linePointHighlighter, - showHorizontalFollowLine: showHorizontalFollowLine, - showVerticalFollowLine: showVerticalFollowLine, - dashPattern: dashPattern, - drawFollowLinesAcrossChart: drawFollowLinesAcrossChart, - symbolRenderer: symbolRenderer); - - if (chart is CartesianChart) { - // Only vertical rendering is supported by this behavior. - assert((chart as CartesianChart).vertical); - } - - chart.addView(_view); - - chart.addLifecycleListener(_lifecycleListener); - chart - .getSelectionModel(selectionModelType) - .addSelectionChangedListener(_selectionChanged); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeView(_view); - chart - .getSelectionModel(selectionModelType) - .removeSelectionChangedListener(_selectionChanged); - chart.removeLifecycleListener(_lifecycleListener); - } - - void _selectionChanged(SelectionModel selectionModel) { - _chart.redraw(skipLayout: true, skipAnimation: true); - } - - void _updateViewData() { - _currentKeys.clear(); - - final selectedDatumDetails = - _chart.getSelectedDatumDetails(selectionModelType); - - // Create a new map each time to ensure that we have it sorted in the - // selection model order. This preserves the "nearestDetail" ordering, so - // that we render follow lines in the proper place. - final newSeriesMap = >{}; - - for (DatumDetails detail in selectedDatumDetails) { - if (detail == null) { - continue; - } - - final series = detail.series; - final datum = detail.datum; - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final lineKey = series.id; - - double radiusPx = (detail.radiusPx != null) - ? detail.radiusPx.toDouble() + radiusPaddingPx - : defaultRadiusPx; - - final pointKey = '$lineKey::${detail.domain}'; - - // If we already have a point for that key, use it. - _AnimatedPoint animatingPoint; - if (_seriesPointMap.containsKey(pointKey)) { - animatingPoint = _seriesPointMap[pointKey]; - } else { - // Create a new point and have it animate in from axis. - final point = _DatumPoint( - datum: datum, - domain: detail.domain, - series: series, - x: domainAxis.getLocation(detail.domain), - y: measureAxis.getLocation(0.0)); - - animatingPoint = _AnimatedPoint( - key: pointKey, overlaySeries: series.overlaySeries) - ..setNewTarget(_PointRendererElement() - ..point = point - ..color = detail.color - ..fillColor = detail.fillColor - ..radiusPx = radiusPx - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..strokeWidthPx = detail.strokeWidthPx - ..symbolRenderer = detail.symbolRenderer); - } - - newSeriesMap[pointKey] = animatingPoint; - - // Create a new line using the final point locations. - final point = _DatumPoint( - datum: datum, - domain: detail.domain, - series: series, - x: detail.chartPosition.x, - y: detail.chartPosition.y); - - // Update the set of points that still exist in the series data. - _currentKeys.add(pointKey); - - // Get the point element we are going to setup. - final pointElement = _PointRendererElement() - ..point = point - ..color = detail.color - ..fillColor = detail.fillColor - ..radiusPx = radiusPx - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..strokeWidthPx = detail.strokeWidthPx - ..symbolRenderer = detail.symbolRenderer; - - animatingPoint.setNewTarget(pointElement); - } - - // Animate out points that don't exist anymore. - _seriesPointMap.forEach((key, point) { - if (_currentKeys.contains(point.key) != true) { - point.animateOut(); - newSeriesMap[point.key] = point; - } - }); - - _seriesPointMap = newSeriesMap; - _view.seriesPointMap = _seriesPointMap; - } - - @override - String get role => 'LinePointHighlighter-${selectionModelType.toString()}'; -} - -class _LinePointLayoutView extends LayoutView { - final LayoutViewConfig layoutConfig; - - final LinePointHighlighterFollowLineType showHorizontalFollowLine; - - final LinePointHighlighterFollowLineType showVerticalFollowLine; - - final BaseChart chart; - - final List dashPattern; - - Rectangle _drawAreaBounds; - - Rectangle get drawBounds => _drawAreaBounds; - - final bool drawFollowLinesAcrossChart; - - final SymbolRenderer symbolRenderer; - - GraphicsFactory graphicsFactory; - - /// Store a map of series drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was given to the chart. - LinkedHashMap> _seriesPointMap; - - _LinePointLayoutView({ - @required this.chart, - @required int layoutPaintOrder, - @required this.showHorizontalFollowLine, - @required this.showVerticalFollowLine, - @required this.symbolRenderer, - this.dashPattern, - this.drawFollowLinesAcrossChart, - }) : this.layoutConfig = LayoutViewConfig( - paintOrder: LayoutViewPaintOrder.linePointHighlighter, - position: LayoutPosition.DrawArea, - positionOrder: layoutPaintOrder); - - set seriesPointMap(LinkedHashMap> value) { - _seriesPointMap = value; - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - return null; - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - this._drawAreaBounds = drawAreaBounds; - } - - @override - void paint(ChartCanvas canvas, double animationPercent) { - if (_seriesPointMap == null) { - return; - } - - // Clean up the lines that no longer exist. - if (animationPercent == 1.0) { - final keysToRemove = []; - - _seriesPointMap.forEach((key, point) { - if (point.animatingOut) { - keysToRemove.add(key); - } - }); - - keysToRemove.forEach((key) => _seriesPointMap.remove(key)); - } - - final points = <_PointRendererElement>[]; - _seriesPointMap.forEach((key, point) { - points.add(point.getCurrentPoint(animationPercent)); - }); - - // Build maps of the position where the follow lines should stop for each - // selected data point. - final endPointPerValueVertical = {}; - final endPointPerValueHorizontal = {}; - - for (_PointRendererElement pointElement in points) { - if (pointElement.point.x == null || pointElement.point.y == null) { - continue; - } - - final roundedX = pointElement.point.x.round(); - final roundedY = pointElement.point.y.round(); - - // Get the Y value closest to the top of the chart for this X position. - if (endPointPerValueVertical[roundedX] == null) { - endPointPerValueVertical[roundedX] = roundedY; - } else { - // In the nearest case, we rely on the selected data always starting - // with the nearest point. In this case, we don't care about the rest of - // the selected data positions. - if (showVerticalFollowLine != - LinePointHighlighterFollowLineType.nearest) { - endPointPerValueVertical[roundedX] = - min(endPointPerValueVertical[roundedX], roundedY); - } - } - - // Get the X value closest to the "end" side of the chart for this Y - // position. - if (endPointPerValueHorizontal[roundedY] == null) { - endPointPerValueHorizontal[roundedY] = roundedX; - } else { - // In the nearest case, we rely on the selected data always starting - // with the nearest point. In this case, we don't care about the rest of - // the selected data positions. - if (showHorizontalFollowLine != - LinePointHighlighterFollowLineType.nearest) { - endPointPerValueHorizontal[roundedY] = - max(endPointPerValueHorizontal[roundedY], roundedX); - } - } - } - - var shouldShowHorizontalFollowLine = showHorizontalFollowLine == - LinePointHighlighterFollowLineType.all || - showHorizontalFollowLine == LinePointHighlighterFollowLineType.nearest; - - var shouldShowVerticalFollowLine = showVerticalFollowLine == - LinePointHighlighterFollowLineType.all || - showVerticalFollowLine == LinePointHighlighterFollowLineType.nearest; - - // Keep track of points for which we've already drawn lines. - final paintedHorizontalLinePositions = []; - final paintedVerticalLinePositions = []; - - final drawBounds = chart.drawableLayoutAreaBounds; - - final rtl = chart.context.isRtl; - - // Draw the follow lines first, below all of the highlight shapes. - for (_PointRendererElement pointElement in points) { - if (pointElement.point.x == null || pointElement.point.y == null) { - continue; - } - - final roundedX = pointElement.point.x.round(); - final roundedY = pointElement.point.y.round(); - - // Draw the horizontal follow line. - if (shouldShowHorizontalFollowLine && - !paintedHorizontalLinePositions.contains(roundedY)) { - int leftBound; - int rightBound; - - if (drawFollowLinesAcrossChart) { - // RTL and LTR both go across the whole draw area. - leftBound = drawBounds.left; - rightBound = drawBounds.left + drawBounds.width; - } else { - final x = endPointPerValueHorizontal[roundedY]; - - // RTL goes from the point to the right edge. LTR goes from the left - // edge to the point. - leftBound = rtl ? x : drawBounds.left; - rightBound = rtl ? drawBounds.left + drawBounds.width : x; - } - - canvas.drawLine( - points: [ - Point(leftBound, pointElement.point.y), - Point(rightBound, pointElement.point.y), - ], - stroke: StyleFactory.style.linePointHighlighterColor, - strokeWidthPx: 1.0, - dashPattern: [1, 3]); - - if (showHorizontalFollowLine == - LinePointHighlighterFollowLineType.nearest) { - shouldShowHorizontalFollowLine = false; - } - - paintedHorizontalLinePositions.add(roundedY); - } - - // Draw the vertical follow line. - if (shouldShowVerticalFollowLine && - !paintedVerticalLinePositions.contains(roundedX)) { - final topBound = drawFollowLinesAcrossChart - ? drawBounds.top - : endPointPerValueVertical[roundedX]; - - canvas.drawLine( - points: [ - Point(pointElement.point.x, topBound), - Point( - pointElement.point.x, drawBounds.top + drawBounds.height), - ], - stroke: StyleFactory.style.linePointHighlighterColor, - strokeWidthPx: 1.0, - dashPattern: dashPattern); - - if (showVerticalFollowLine == - LinePointHighlighterFollowLineType.nearest) { - shouldShowVerticalFollowLine = false; - } - - paintedVerticalLinePositions.add(roundedX); - } - - if (!shouldShowHorizontalFollowLine && !shouldShowVerticalFollowLine) { - break; - } - } - - // Draw the highlight shapes on top of all follow lines. - for (_PointRendererElement pointElement in points) { - if (pointElement.point.x == null || pointElement.point.y == null) { - continue; - } - - final bounds = Rectangle( - pointElement.point.x - pointElement.radiusPx, - pointElement.point.y - pointElement.radiusPx, - pointElement.radiusPx * 2, - pointElement.radiusPx * 2); - - // Draw the highlight dot. Use the [SymbolRenderer] from the datum if one - // is defined. - (pointElement.symbolRenderer ?? symbolRenderer).paint(canvas, bounds, - fillColor: pointElement.fillColor, - strokeColor: pointElement.color, - strokeWidthPx: pointElement.strokeWidthPx); - } - } - - @override - Rectangle get componentBounds => this._drawAreaBounds; - - @override - bool get isSeriesRenderer => false; -} - -class _DatumPoint extends Point { - final dynamic datum; - final D domain; - final ImmutableSeries series; - - _DatumPoint({this.datum, this.domain, this.series, double x, double y}) - : super(x, y); - - factory _DatumPoint.from(_DatumPoint other, [double x, double y]) { - return _DatumPoint( - datum: other.datum, - domain: other.domain, - series: other.series, - x: x ?? other.x, - y: y ?? other.y); - } -} - -class _PointRendererElement { - _DatumPoint point; - Color color; - Color fillColor; - double radiusPx; - double measureAxisPosition; - double strokeWidthPx; - SymbolRenderer symbolRenderer; - - _PointRendererElement clone() { - return _PointRendererElement() - ..point = this.point - ..color = this.color - ..fillColor = this.fillColor - ..measureAxisPosition = this.measureAxisPosition - ..radiusPx = this.radiusPx - ..strokeWidthPx = this.strokeWidthPx - ..symbolRenderer = this.symbolRenderer; - } - - void updateAnimationPercent(_PointRendererElement previous, - _PointRendererElement target, double animationPercent) { - final targetPoint = target.point; - final previousPoint = previous.point; - - final x = _lerpDouble(previousPoint.x, targetPoint.x, animationPercent); - - final y = _lerpDouble(previousPoint.y, targetPoint.y, animationPercent); - - point = _DatumPoint.from(targetPoint, x, y); - - color = getAnimatedColor(previous.color, target.color, animationPercent); - - fillColor = getAnimatedColor( - previous.fillColor, target.fillColor, animationPercent); - - radiusPx = - _lerpDouble(previous.radiusPx, target.radiusPx, animationPercent); - - if (target.strokeWidthPx != null && previous.strokeWidthPx != null) { - strokeWidthPx = (((target.strokeWidthPx - previous.strokeWidthPx) * - animationPercent) + - previous.strokeWidthPx); - } else { - strokeWidthPx = null; - } - } - - /// Linear interpolation for doubles. - /// - /// If either [a] or [b] is null, return null. - /// This is different than Flutter's lerpDouble method, we want to return null - /// instead of assuming it is 0.0. - double _lerpDouble(double a, double b, double t) { - if (a == null || b == null) return null; - return a + (b - a) * t; - } -} - -class _AnimatedPoint { - final String key; - final bool overlaySeries; - - _PointRendererElement _previousPoint; - _PointRendererElement _targetPoint; - _PointRendererElement _currentPoint; - - // Flag indicating whether this point is being animated out of the chart. - bool animatingOut = false; - - _AnimatedPoint({@required this.key, @required this.overlaySeries}); - - /// Animates a point that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for points that represent - /// data that has been removed from the series. - /// - /// Animates the height of the point down to the measure axis position - /// (position of 0). - void animateOut() { - final newTarget = _currentPoint.clone(); - - // Set the target measure value to the axis position for all points. - final targetPoint = newTarget.point; - - final newPoint = _DatumPoint.from(targetPoint, targetPoint.x, - newTarget.measureAxisPosition.roundToDouble()); - - newTarget.point = newPoint; - - // Animate the radius to 0 so that we don't get a lingering point after - // animation is done. - newTarget.radiusPx = 0.0; - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(_PointRendererElement newTarget) { - animatingOut = false; - _currentPoint ??= newTarget.clone(); - _previousPoint = _currentPoint.clone(); - _targetPoint = newTarget; - } - - _PointRendererElement getCurrentPoint(double animationPercent) { - if (animationPercent == 1.0 || _previousPoint == null) { - _currentPoint = _targetPoint; - _previousPoint = _targetPoint; - return _currentPoint; - } - - _currentPoint.updateAnimationPercent( - _previousPoint, _targetPoint, animationPercent); - - return _currentPoint; - } -} - -/// Type of follow line(s) to draw. -enum LinePointHighlighterFollowLineType { - /// Draw a follow line for only the nearest point in the selection. - nearest, - - /// Draw no follow lines. - none, - - /// Draw a follow line for every point in the selection. - all, -} - -/// Helper class that exposes fewer private internal properties for unit tests. -@visibleForTesting -class LinePointHighlighterTester { - final LinePointHighlighter behavior; - - LinePointHighlighterTester(this.behavior); - - int getSelectionLength() { - return behavior._seriesPointMap.length; - } - - bool isDatumSelected(D datum) { - var contains = false; - - behavior._seriesPointMap.forEach((key, point) { - if (point._currentPoint.point.datum == datum) { - contains = true; - return; - } - }); - - return contains; - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/range_annotation.dart b/web/charts/common/lib/src/chart/common/behavior/range_annotation.dart deleted file mode 100644 index 95b9de48c..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/range_annotation.dart +++ /dev/null @@ -1,1309 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show pi, Point, Rectangle; - -import 'package:meta/meta.dart'; - -import '../../../common/color.dart' show Color; -import '../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../common/style/style_factory.dart' show StyleFactory; -import '../../../common/text_element.dart' - show MaxWidthStrategy, TextDirection, TextElement; -import '../../../common/text_style.dart' show TextStyle; -import '../../cartesian/axis/axis.dart' show Axis, ImmutableAxis; -import '../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - ViewMeasuredSizes; -import '../base_chart.dart' show BaseChart, LifecycleListener; -import '../chart_canvas.dart' show ChartCanvas, getAnimatedColor; -import '../processed_series.dart' show MutableSeries; -import 'chart_behavior.dart' show ChartBehavior; - -/// Chart behavior that annotates domain ranges with a solid fill color. -/// -/// The annotations will be drawn underneath series data and chart axes. -/// -/// This is typically used for line charts to call out sections of the data -/// range. -/// -/// TODO: Support labels. -class RangeAnnotation implements ChartBehavior { - static const _defaultLabelAnchor = AnnotationLabelAnchor.end; - static const _defaultLabelDirection = AnnotationLabelDirection.auto; - static const _defaultLabelPosition = AnnotationLabelPosition.auto; - static const _defaultLabelPadding = 5; - static final _defaultLabelStyle = - TextStyleSpec(fontSize: 12, color: Color.black); - static const _defaultStrokeWidthPx = 2.0; - - /// List of annotations to render on the chart. - final List annotations; - - /// Default color for annotations. - final Color defaultColor; - - /// Configures where to anchor annotation label text. - final AnnotationLabelAnchor defaultLabelAnchor; - - /// Direction of label text on the annotations. - final AnnotationLabelDirection defaultLabelDirection; - - /// Configures where to place labels relative to the annotation. - final AnnotationLabelPosition defaultLabelPosition; - - /// Configures the style of label text. - final TextStyleSpec defaultLabelStyleSpec; - - /// Configures the stroke width for line annotations. - final double defaultStrokeWidthPx; - - /// Whether or not the range of the axis should be extended to include the - /// annotation start and end values. - final bool extendAxis; - - /// Space before and after label text. - final int labelPadding; - - CartesianChart _chart; - - _RangeAnnotationLayoutView _view; - - LifecycleListener _lifecycleListener; - - /// Store a map of data drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was given to the chart. - final _annotationMap = LinkedHashMap>(); - - // Store a list of annotations that exist in the current annotation list. - // - // This list will be used to remove any [_AnimatedAnnotation] that were - // rendered in previous draw cycles, but no longer have a corresponding datum - // in the new data. - final _currentKeys = []; - - RangeAnnotation(this.annotations, - {Color defaultColor, - AnnotationLabelAnchor defaultLabelAnchor, - AnnotationLabelDirection defaultLabelDirection, - AnnotationLabelPosition defaultLabelPosition, - TextStyleSpec defaultLabelStyleSpec, - bool extendAxis, - int labelPadding, - double defaultStrokeWidthPx}) - : defaultColor = StyleFactory.style.rangeAnnotationColor, - defaultLabelAnchor = defaultLabelAnchor ?? _defaultLabelAnchor, - defaultLabelDirection = defaultLabelDirection ?? _defaultLabelDirection, - defaultLabelPosition = defaultLabelPosition ?? _defaultLabelPosition, - defaultLabelStyleSpec = defaultLabelStyleSpec ?? _defaultLabelStyle, - extendAxis = extendAxis ?? true, - labelPadding = labelPadding ?? _defaultLabelPadding, - defaultStrokeWidthPx = defaultStrokeWidthPx ?? _defaultStrokeWidthPx { - _lifecycleListener = LifecycleListener( - onPostprocess: _updateAxisRange, onAxisConfigured: _updateViewData); - } - - @override - void attachTo(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError( - 'RangeAnnotation can only be attached to a CartesianChart'); - } - - _chart = chart; - - _view = _RangeAnnotationLayoutView( - defaultColor: defaultColor, labelPadding: labelPadding, chart: chart); - - chart.addView(_view); - - chart.addLifecycleListener(_lifecycleListener); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeView(_view); - chart.removeLifecycleListener(_lifecycleListener); - - _view.chart = null; - } - - void _updateAxisRange(List> seriesList) { - // Extend the axis range if enabled. - if (extendAxis) { - final domainAxis = _chart.domainAxis; - - annotations.forEach((annotation) { - Axis axis; - - switch (annotation.axisType) { - case RangeAnnotationAxisType.domain: - axis = domainAxis; - break; - - case RangeAnnotationAxisType.measure: - // We expect an empty axisId to get us the primary measure axis. - axis = _chart.getMeasureAxis(axisId: annotation.axisId); - break; - } - - if (annotation is RangeAnnotationSegment) { - axis.addDomainValue(annotation.startValue); - axis.addDomainValue(annotation.endValue); - } else if (annotation is LineAnnotationSegment) { - axis.addDomainValue(annotation.value); - } - }); - } - } - - void _updateViewData() { - _currentKeys.clear(); - - annotations.forEach((annotation) { - Axis axis; - - switch (annotation.axisType) { - case RangeAnnotationAxisType.domain: - axis = _chart.domainAxis; - break; - - case RangeAnnotationAxisType.measure: - // We expect an empty axisId to get us the primary measure axis. - axis = _chart.getMeasureAxis(axisId: annotation.axisId); - break; - } - - final key = annotation.key; - - final color = annotation.color ?? defaultColor; - - final startLabel = annotation.startLabel; - final endLabel = annotation.endLabel; - final labelAnchor = annotation.labelAnchor ?? defaultLabelAnchor; - var labelDirection = annotation.labelDirection ?? defaultLabelDirection; - - if (labelDirection == AnnotationLabelDirection.auto) { - switch (annotation.axisType) { - case RangeAnnotationAxisType.domain: - labelDirection = AnnotationLabelDirection.vertical; - break; - - case RangeAnnotationAxisType.measure: - labelDirection = AnnotationLabelDirection.horizontal; - break; - } - } - - final labelPosition = annotation.labelPosition ?? defaultLabelPosition; - final labelStyleSpec = annotation.labelStyleSpec ?? defaultLabelStyleSpec; - - // Add line annotation settings. - final dashPattern = - annotation is LineAnnotationSegment ? annotation.dashPattern : null; - final strokeWidthPx = annotation is LineAnnotationSegment - ? annotation.strokeWidthPx ?? defaultLabelStyleSpec - : 0.0; - - final isRange = annotation is RangeAnnotationSegment; - - // The values can match the data type of the domain (D) or measure axis - // (num). - dynamic startValue; - dynamic endValue; - - if (annotation is RangeAnnotationSegment) { - startValue = annotation.startValue; - endValue = annotation.endValue; - } else if (annotation is LineAnnotationSegment) { - startValue = annotation.value; - endValue = annotation.value; - } - - final annotationDatum = - _getAnnotationDatum(startValue, endValue, axis, annotation.axisType); - - // If we already have a animatingAnnotation for that index, use it. - _AnimatedAnnotation animatingAnnotation; - if (_annotationMap.containsKey(key)) { - animatingAnnotation = _annotationMap[key]; - } else { - // Create a new annotation, positioned at the start and end values. - animatingAnnotation = _AnimatedAnnotation(key: key) - ..setNewTarget(_AnnotationElement() - ..annotation = annotationDatum - ..color = color - ..dashPattern = dashPattern - ..startLabel = startLabel - ..endLabel = endLabel - ..isRange = isRange - ..labelAnchor = labelAnchor - ..labelDirection = labelDirection - ..labelPosition = labelPosition - ..labelStyleSpec = labelStyleSpec - ..strokeWidthPx = strokeWidthPx); - - _annotationMap[key] = animatingAnnotation; - } - - // Update the set of annotations that still exist in the series data. - _currentKeys.add(key); - - // Get the annotation element we are going to setup. - final annotationElement = _AnnotationElement() - ..annotation = annotationDatum - ..color = color - ..dashPattern = dashPattern - ..startLabel = startLabel - ..endLabel = endLabel - ..isRange = isRange - ..labelAnchor = labelAnchor - ..labelDirection = labelDirection - ..labelPosition = labelPosition - ..labelStyleSpec = labelStyleSpec - ..strokeWidthPx = strokeWidthPx; - - animatingAnnotation.setNewTarget(annotationElement); - }); - - // Animate out annotations that don't exist anymore. - _annotationMap.forEach((key, annotation) { - if (_currentKeys.contains(annotation.key) != true) { - annotation.animateOut(); - } - }); - - _view.annotationMap = _annotationMap; - } - - /// Generates a datum that describes an annotation. - /// - /// [startValue] and [endValue] are dynamic because they can be different data - /// types for domain and measure axes, e.g. DateTime and num for a TimeSeries - /// chart. - _DatumAnnotation _getAnnotationDatum(dynamic startValue, dynamic endValue, - ImmutableAxis axis, RangeAnnotationAxisType axisType) { - // Remove floating point rounding errors by rounding to 2 decimal places of - // precision. The difference in the canvas is negligible. - final startPosition = (axis.getLocation(startValue) * 100).round() / 100; - final endPosition = (axis.getLocation(endValue) * 100).round() / 100; - - return _DatumAnnotation( - startPosition: startPosition, - endPosition: endPosition, - axisType: axisType); - } - - @override - String get role => 'RangeAnnotation'; -} - -class _RangeAnnotationLayoutView extends LayoutView { - final LayoutViewConfig layoutConfig; - - final Color defaultColor; - - final int labelPadding; - - CartesianChart chart; - - bool get isRtl => chart.context.isRtl; - - Rectangle _drawAreaBounds; - - Rectangle get drawBounds => _drawAreaBounds; - - GraphicsFactory graphicsFactory; - - /// Store a map of series drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was given to the chart. - LinkedHashMap> _annotationMap; - - _RangeAnnotationLayoutView({ - @required this.defaultColor, - @required this.labelPadding, - @required this.chart, - }) : this.layoutConfig = LayoutViewConfig( - paintOrder: LayoutViewPaintOrder.rangeAnnotation, - position: LayoutPosition.DrawArea, - positionOrder: LayoutViewPositionOrder.drawArea); - - set annotationMap(LinkedHashMap> value) { - _annotationMap = value; - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - return null; - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - this._drawAreaBounds = drawAreaBounds; - } - - @override - void paint(ChartCanvas canvas, double animationPercent) { - if (_annotationMap == null) { - return; - } - - // Clean up the annotations that no longer exist. - if (animationPercent == 1.0) { - final keysToRemove = []; - - _annotationMap.forEach((key, annotation) { - if (annotation.animatingOut) { - keysToRemove.add(key); - } - }); - - keysToRemove.forEach((key) => _annotationMap.remove(key)); - } - - _annotationMap.forEach((key, annotation) { - final annotationElement = - annotation.getCurrentAnnotation(animationPercent); - - // Calculate the bounds of a range annotation. - // - // This will still be used for line annotations to compute the position of - // labels. We always expect those to end up outside, since the bounds will - // have zero width or height. - final bounds = _getAnnotationBounds(annotationElement); - - if (annotationElement.isRange) { - // Draw the annotation. - canvas.drawRect(bounds, fill: annotationElement.color); - } else { - // Calculate the points for a line annotation. - final points = _getLineAnnotationPoints(annotationElement); - - // Draw the annotation. - canvas.drawLine( - dashPattern: annotationElement.dashPattern, - points: points, - stroke: annotationElement.color, - strokeWidthPx: annotationElement.strokeWidthPx); - } - - // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. - // The [GraphicsFactory] is needed so it can't be created earlier. - final labelStyle = - _getTextStyle(graphicsFactory, annotationElement.labelStyleSpec); - - final rotation = - annotationElement.labelDirection == AnnotationLabelDirection.vertical - ? -pi / 2 - : 0.0; - - // Draw a start label if one is defined. - if (annotationElement.startLabel != null) { - final labelElement = - graphicsFactory.createTextElement(annotationElement.startLabel) - ..maxWidthStrategy = MaxWidthStrategy.ellipsize - ..textStyle = labelStyle; - - // Measure the label max width once if either type of label is defined. - labelElement.maxWidth = - _getLabelMaxWidth(bounds, annotationElement, labelElement); - - final labelPoint = - _getStartLabelPosition(bounds, annotationElement, labelElement); - - if (labelPoint != null) { - canvas.drawText(labelElement, labelPoint.x, labelPoint.y, - rotation: rotation); - } - } - - // Draw an end label if one is defined. - if (annotationElement.endLabel != null) { - final labelElement = - graphicsFactory.createTextElement(annotationElement.endLabel) - ..maxWidthStrategy = MaxWidthStrategy.ellipsize - ..textStyle = labelStyle; - - // Measure the label max width once if either type of label is defined. - labelElement.maxWidth = - _getLabelMaxWidth(bounds, annotationElement, labelElement); - - final labelPoint = - _getEndLabelPosition(bounds, annotationElement, labelElement); - - if (labelPoint != null) { - canvas.drawText(labelElement, labelPoint.x, labelPoint.y, - rotation: rotation); - } - } - }); - } - - /// Calculates the bounds of the annotation. - Rectangle _getAnnotationBounds(_AnnotationElement annotationElement) { - Rectangle bounds; - - switch (annotationElement.annotation.axisType) { - case RangeAnnotationAxisType.domain: - bounds = Rectangle( - annotationElement.annotation.startPosition, - _drawAreaBounds.top, - annotationElement.annotation.endPosition - - annotationElement.annotation.startPosition, - _drawAreaBounds.height); - break; - - case RangeAnnotationAxisType.measure: - bounds = Rectangle( - _drawAreaBounds.left, - annotationElement.annotation.endPosition, - _drawAreaBounds.width, - annotationElement.annotation.startPosition - - annotationElement.annotation.endPosition); - break; - } - - return bounds; - } - - /// Calculates the bounds of the annotation. - List _getLineAnnotationPoints( - _AnnotationElement annotationElement) { - final points = []; - - switch (annotationElement.annotation.axisType) { - case RangeAnnotationAxisType.domain: - points.add(Point( - annotationElement.annotation.startPosition, _drawAreaBounds.top)); - points.add(Point( - annotationElement.annotation.endPosition, _drawAreaBounds.bottom)); - break; - - case RangeAnnotationAxisType.measure: - points.add(Point( - _drawAreaBounds.left, annotationElement.annotation.startPosition)); - points.add(Point( - _drawAreaBounds.right, annotationElement.annotation.endPosition)); - break; - } - - return points; - } - - /// Measures the max label width of the annotation. - int _getLabelMaxWidth(Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - num maxWidth = 0; - - final calculatedLabelPosition = - _resolveAutoLabelPosition(bounds, annotationElement, labelElement); - - if (annotationElement.labelPosition == AnnotationLabelPosition.margin && - annotationElement.annotation.axisType == - RangeAnnotationAxisType.measure) { - switch (annotationElement.annotation.axisType) { - case RangeAnnotationAxisType.domain: - break; - - case RangeAnnotationAxisType.measure: - switch (annotationElement.labelAnchor) { - case AnnotationLabelAnchor.start: - maxWidth = chart.marginLeft - labelPadding; - break; - - case AnnotationLabelAnchor.end: - maxWidth = chart.marginRight - labelPadding; - break; - - case AnnotationLabelAnchor.middle: - break; - } - break; - } - } else { - if (calculatedLabelPosition == AnnotationLabelPosition.outside) { - maxWidth = annotationElement.labelDirection == - AnnotationLabelDirection.horizontal - ? drawBounds.width - : drawBounds.height; - } else { - maxWidth = annotationElement.labelDirection == - AnnotationLabelDirection.horizontal - ? bounds.width - : bounds.height; - } - } - - return (maxWidth).round(); - } - - /// Gets the resolved location for a start label element. - Point _getStartLabelPosition(Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - return _getLabelPosition(true, bounds, annotationElement, labelElement); - } - - /// Gets the resolved location for an end label element. - Point _getEndLabelPosition(Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - return _getLabelPosition(false, bounds, annotationElement, labelElement); - } - - /// Gets the resolved location for a label element. - Point _getLabelPosition(bool isStartLabel, Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - switch (annotationElement.annotation.axisType) { - case RangeAnnotationAxisType.domain: - return _getDomainLabelPosition( - isStartLabel, bounds, annotationElement, labelElement); - break; - - case RangeAnnotationAxisType.measure: - return _getMeasureLabelPosition( - isStartLabel, bounds, annotationElement, labelElement); - break; - } - return null; - } - - /// Gets the resolved location for a domain annotation label element. - Point _getDomainLabelPosition(bool isStartLabel, Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - if (annotationElement.labelDirection == AnnotationLabelDirection.vertical) { - return _getDomainLabelPositionVertical( - isStartLabel, bounds, annotationElement, labelElement); - } else { - return _getDomainLabelPositionHorizontal( - isStartLabel, bounds, annotationElement, labelElement); - } - } - - /// Gets the resolved location for a horizontal domain annotation label - /// element. - Point _getDomainLabelPositionHorizontal( - bool isStartLabel, - Rectangle bounds, - _AnnotationElement annotationElement, - TextElement labelElement) { - num labelX = 0; - num labelY = 0; - - final calculatedLabelPosition = - _resolveAutoLabelPosition(bounds, annotationElement, labelElement); - - switch (annotationElement.labelAnchor) { - case AnnotationLabelAnchor.middle: - labelY = bounds.top + - bounds.height / 2 - - labelElement.measurement.verticalSliceWidth / 2 - - labelPadding; - break; - - case AnnotationLabelAnchor.end: - if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { - labelY = bounds.top - - labelElement.measurement.verticalSliceWidth - - labelPadding; - } else { - labelY = bounds.top + labelPadding; - } - break; - - case AnnotationLabelAnchor.start: - if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { - labelY = bounds.bottom + labelPadding; - } else { - labelY = bounds.bottom - - labelElement.measurement.verticalSliceWidth - - labelPadding; - } - break; - } - - switch (calculatedLabelPosition) { - case AnnotationLabelPosition.margin: - case AnnotationLabelPosition.auto: - throw ArgumentError(_unresolvedAutoMessage); - break; - - case AnnotationLabelPosition.outside: - if (isStartLabel) { - labelX = bounds.left - - labelElement.measurement.horizontalSliceWidth - - labelPadding; - } else { - labelX = bounds.right + labelPadding; - } - - labelElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case AnnotationLabelPosition.inside: - if (isStartLabel) { - labelX = bounds.left + labelPadding; - } else { - labelX = bounds.right - - labelElement.measurement.horizontalSliceWidth - - labelPadding; - } - - labelElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - } - - return Point(labelX.round(), labelY.round()); - } - - /// Gets the resolved location for a vertical domain annotation label element. - Point _getDomainLabelPositionVertical( - bool isStartLabel, - Rectangle bounds, - _AnnotationElement annotationElement, - TextElement labelElement) { - num labelX = 0; - num labelY = 0; - - final calculatedLabelPosition = - _resolveAutoLabelPosition(bounds, annotationElement, labelElement); - - switch (annotationElement.labelAnchor) { - case AnnotationLabelAnchor.middle: - labelY = bounds.top + - bounds.height / 2 + - labelElement.measurement.horizontalSliceWidth / 2 + - labelPadding; - break; - - case AnnotationLabelAnchor.end: - if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { - labelY = bounds.top + - labelElement.measurement.horizontalSliceWidth + - labelPadding; - } else { - labelY = bounds.top + - labelElement.measurement.horizontalSliceWidth + - labelPadding; - } - break; - - case AnnotationLabelAnchor.start: - if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { - labelY = bounds.bottom + labelPadding; - } else { - labelY = bounds.bottom - - labelElement.measurement.horizontalSliceWidth - - labelPadding; - } - break; - } - - switch (calculatedLabelPosition) { - case AnnotationLabelPosition.margin: - case AnnotationLabelPosition.auto: - throw ArgumentError(_unresolvedAutoMessage); - break; - - case AnnotationLabelPosition.outside: - if (isStartLabel) { - labelX = bounds.left - - labelElement.measurement.verticalSliceWidth - - labelPadding; - } else { - labelX = bounds.right + labelPadding; - } - - labelElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case AnnotationLabelPosition.inside: - if (isStartLabel) { - labelX = bounds.left + labelPadding; - } else { - labelX = bounds.right - - labelElement.measurement.verticalSliceWidth - - labelPadding; - } - - labelElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - } - - return Point(labelX.round(), labelY.round()); - } - - /// Gets the resolved location for a measure annotation label element. - Point _getMeasureLabelPosition(bool isStartLabel, Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - if (annotationElement.labelDirection == AnnotationLabelDirection.vertical) { - return _getMeasureLabelPositionVertical( - isStartLabel, bounds, annotationElement, labelElement); - } else { - return _getMeasureLabelPositionHorizontal( - isStartLabel, bounds, annotationElement, labelElement); - } - } - - /// Gets the resolved location for a horizontal measure annotation label - /// element. - Point _getMeasureLabelPositionHorizontal( - bool isStartLabel, - Rectangle bounds, - _AnnotationElement annotationElement, - TextElement labelElement) { - num labelX = 0; - num labelY = 0; - - final calculatedLabelPosition = - _resolveAutoLabelPosition(bounds, annotationElement, labelElement); - - switch (annotationElement.labelAnchor) { - case AnnotationLabelAnchor.middle: - labelX = bounds.left + - bounds.width / 2 - - labelElement.measurement.horizontalSliceWidth / 2; - labelElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case AnnotationLabelAnchor.end: - case AnnotationLabelAnchor.start: - if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { - final alignLeft = isRtl - ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) - : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); - - if (alignLeft) { - labelX = bounds.left - labelPadding; - labelElement.textDirection = TextDirection.rtl; - } else { - labelX = bounds.right + labelPadding; - labelElement.textDirection = TextDirection.ltr; - } - } else { - final alignLeft = isRtl - ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) - : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); - - if (alignLeft) { - labelX = bounds.left + labelPadding; - labelElement.textDirection = TextDirection.ltr; - } else { - labelX = bounds.right - labelPadding; - labelElement.textDirection = TextDirection.rtl; - } - } - break; - } - - switch (calculatedLabelPosition) { - case AnnotationLabelPosition.margin: - case AnnotationLabelPosition.auto: - throw ArgumentError(_unresolvedAutoMessage); - break; - - case AnnotationLabelPosition.outside: - if (isStartLabel) { - labelY = bounds.bottom + labelPadding; - } else { - labelY = bounds.top - - labelElement.measurement.verticalSliceWidth - - labelPadding; - } - break; - - case AnnotationLabelPosition.inside: - if (isStartLabel) { - labelY = bounds.bottom - - labelElement.measurement.verticalSliceWidth - - labelPadding; - } else { - labelY = bounds.top + labelPadding; - } - break; - } - - return Point(labelX.round(), labelY.round()); - } - - /// Gets the resolved location for a vertical measure annotation label - /// element. - Point _getMeasureLabelPositionVertical( - bool isStartLabel, - Rectangle bounds, - _AnnotationElement annotationElement, - TextElement labelElement) { - num labelX = 0; - num labelY = 0; - - final calculatedLabelPosition = - _resolveAutoLabelPosition(bounds, annotationElement, labelElement); - - switch (annotationElement.labelAnchor) { - case AnnotationLabelAnchor.middle: - labelX = bounds.left + - bounds.width / 2 - - labelElement.measurement.verticalSliceWidth / 2; - labelElement.textDirection = - isRtl ? TextDirection.rtl : TextDirection.ltr; - break; - - case AnnotationLabelAnchor.end: - case AnnotationLabelAnchor.start: - if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { - final alignLeft = isRtl - ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) - : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); - - if (alignLeft) { - labelX = bounds.left - - labelElement.measurement.verticalSliceWidth - - labelPadding; - labelElement.textDirection = TextDirection.ltr; - } else { - labelX = bounds.right + labelPadding; - labelElement.textDirection = TextDirection.ltr; - } - } else { - final alignLeft = isRtl - ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) - : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); - - if (alignLeft) { - labelX = bounds.left + labelPadding; - labelElement.textDirection = TextDirection.ltr; - } else { - labelX = bounds.right - - labelElement.measurement.verticalSliceWidth - - labelPadding; - labelElement.textDirection = TextDirection.ltr; - } - } - break; - } - - switch (calculatedLabelPosition) { - case AnnotationLabelPosition.margin: - case AnnotationLabelPosition.auto: - throw ArgumentError(_unresolvedAutoMessage); - break; - - case AnnotationLabelPosition.outside: - if (isStartLabel) { - labelY = bounds.bottom + - labelElement.measurement.horizontalSliceWidth + - labelPadding; - } else { - labelY = bounds.top - labelPadding; - } - break; - - case AnnotationLabelPosition.inside: - if (isStartLabel) { - labelY = bounds.bottom - labelPadding; - } else { - labelY = bounds.top + - labelElement.measurement.horizontalSliceWidth + - labelPadding; - } - break; - } - - return Point(labelX.round(), labelY.round()); - } - - /// Resolves [AnnotationLabelPosition.auto] configuration for an annotation - /// into an inside or outside position, depending on the size of the - /// annotation and the chart draw area. - AnnotationLabelPosition _resolveAutoLabelPosition(Rectangle bounds, - _AnnotationElement annotationElement, TextElement labelElement) { - var calculatedLabelPosition = annotationElement.labelPosition; - if (calculatedLabelPosition == AnnotationLabelPosition.auto || - calculatedLabelPosition == AnnotationLabelPosition.margin) { - final isDomain = annotationElement.annotation.axisType == - RangeAnnotationAxisType.domain; - - final annotationBoundsSize = isDomain ? bounds.width : bounds.height; - - final drawBoundsSize = isDomain ? drawBounds.width : drawBounds.height; - - final isVertical = - annotationElement.labelDirection == AnnotationLabelDirection.vertical; - - final labelSize = isDomain && isVertical || !isDomain && !isVertical - ? labelElement.measurement.verticalSliceWidth - : labelElement.measurement.horizontalSliceWidth; - - // Get space available inside and outside the annotation. - final totalPadding = labelPadding * 2; - final insideBarWidth = annotationBoundsSize - totalPadding; - final outsideBarWidth = - drawBoundsSize - annotationBoundsSize - totalPadding; - - // A label fits if the space inside the annotation is >= outside - // annotation or if the length of the text fits and the space. This is - // because if the annotation has more space than the outside, it makes - // more sense to place the label inside the annotation, even if the - // entire label does not fit. - calculatedLabelPosition = - (insideBarWidth >= outsideBarWidth || labelSize < insideBarWidth) - ? AnnotationLabelPosition.inside - : AnnotationLabelPosition.outside; - } - - return calculatedLabelPosition; - } - - @override - Rectangle get componentBounds => this._drawAreaBounds; - - @override - bool get isSeriesRenderer => false; - - // Helper function that converts [TextStyleSpec] to [TextStyle]. - TextStyle _getTextStyle( - GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { - return graphicsFactory.createTextPaint() - ..color = labelSpec?.color ?? Color.black - ..fontFamily = labelSpec?.fontFamily - ..fontSize = labelSpec?.fontSize ?? 12; - } -} - -class _DatumAnnotation { - final double startPosition; - final double endPosition; - final RangeAnnotationAxisType axisType; - - _DatumAnnotation({this.startPosition, this.endPosition, this.axisType}); - - factory _DatumAnnotation.from(_DatumAnnotation other, - [double startPosition, double endPosition]) { - return _DatumAnnotation( - startPosition: startPosition ?? other.startPosition, - endPosition: endPosition ?? other.endPosition, - axisType: other.axisType); - } -} - -class _AnnotationElement { - _DatumAnnotation annotation; - Color color; - String startLabel; - String endLabel; - bool isRange; - AnnotationLabelAnchor labelAnchor; - AnnotationLabelDirection labelDirection; - AnnotationLabelPosition labelPosition; - TextStyleSpec labelStyleSpec; - List dashPattern; - double strokeWidthPx; - - _AnnotationElement clone() { - return _AnnotationElement() - ..annotation = _DatumAnnotation.from(annotation) - ..color = color != null ? Color.fromOther(color: color) : null - ..startLabel = this.startLabel - ..endLabel = this.endLabel - ..isRange = this.isRange - ..labelAnchor = this.labelAnchor - ..labelDirection = this.labelDirection - ..labelPosition = this.labelPosition - ..labelStyleSpec = this.labelStyleSpec - ..dashPattern = dashPattern - ..strokeWidthPx = this.strokeWidthPx; - } - - void updateAnimationPercent(_AnnotationElement previous, - _AnnotationElement target, double animationPercent) { - final targetAnnotation = target.annotation; - final previousAnnotation = previous.annotation; - - final startPosition = - ((targetAnnotation.startPosition - previousAnnotation.startPosition) * - animationPercent) + - previousAnnotation.startPosition; - - final endPosition = - ((targetAnnotation.endPosition - previousAnnotation.endPosition) * - animationPercent) + - previousAnnotation.endPosition; - - annotation = - _DatumAnnotation.from(targetAnnotation, startPosition, endPosition); - - color = getAnimatedColor(previous.color, target.color, animationPercent); - - strokeWidthPx = - (((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + - previous.strokeWidthPx); - } -} - -class _AnimatedAnnotation { - final String key; - - _AnnotationElement _previousAnnotation; - _AnnotationElement _targetAnnotation; - _AnnotationElement _currentAnnotation; - - // Flag indicating whether this annotation is being animated out of the chart. - bool animatingOut = false; - - _AnimatedAnnotation({@required this.key}); - - /// Animates an annotation that was removed from the list out of the view. - /// - /// This should be called in place of "setNewTarget" for annotations have been - /// removed from the list. - /// TODO: Needed? - void animateOut() { - final newTarget = _currentAnnotation.clone(); - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(_AnnotationElement newTarget) { - animatingOut = false; - _currentAnnotation ??= newTarget.clone(); - _previousAnnotation = _currentAnnotation.clone(); - _targetAnnotation = newTarget; - } - - _AnnotationElement getCurrentAnnotation(double animationPercent) { - if (animationPercent == 1.0 || _previousAnnotation == null) { - _currentAnnotation = _targetAnnotation; - _previousAnnotation = _targetAnnotation; - return _currentAnnotation; - } - - _currentAnnotation.updateAnimationPercent( - _previousAnnotation, _targetAnnotation, animationPercent); - - return _currentAnnotation; - } -} - -/// Helper class that exposes fewer private internal properties for unit tests. -@visibleForTesting -class RangeAnnotationTester { - final RangeAnnotation behavior; - - RangeAnnotationTester(this.behavior); - - set graphicsFactory(GraphicsFactory value) { - behavior._view.graphicsFactory = value; - } - - mockLayout(Rectangle bounds) { - behavior._view.layout(bounds, bounds); - } - - /// Checks if an annotation exists with the given position and color. - bool doesAnnotationExist( - {num startPosition, - num endPosition, - Color color, - List dashPattern, - String startLabel, - String endLabel, - AnnotationLabelAnchor labelAnchor, - AnnotationLabelDirection labelDirection, - AnnotationLabelPosition labelPosition}) { - var exists = false; - - behavior._annotationMap.forEach((key, a) { - final currentAnnotation = a._currentAnnotation; - final annotation = currentAnnotation.annotation; - - if (annotation.startPosition == startPosition && - annotation.endPosition == endPosition && - currentAnnotation.color == color && - currentAnnotation.startLabel == startLabel && - currentAnnotation.endLabel == endLabel && - currentAnnotation.labelAnchor == labelAnchor && - currentAnnotation.labelDirection == labelDirection && - currentAnnotation.labelPosition == labelPosition && - (!(currentAnnotation is LineAnnotationSegment) || - currentAnnotation.dashPattern == dashPattern)) { - exists = true; - return; - } - }); - - return exists; - } -} - -/// Base class for chart annotations. -abstract class AnnotationSegment { - final RangeAnnotationAxisType axisType; - final String axisId; - final Color color; - final String startLabel; - final String endLabel; - final AnnotationLabelAnchor labelAnchor; - final AnnotationLabelDirection labelDirection; - final AnnotationLabelPosition labelPosition; - final TextStyleSpec labelStyleSpec; - - String get key; - - AnnotationSegment(this.axisType, - {this.axisId, - this.color, - this.startLabel, - this.endLabel, - this.labelAnchor, - this.labelDirection, - this.labelPosition, - this.labelStyleSpec}); -} - -/// Data for a chart range annotation. -class RangeAnnotationSegment extends AnnotationSegment { - final D startValue; - final D endValue; - - RangeAnnotationSegment( - this.startValue, this.endValue, RangeAnnotationAxisType axisType, - {String axisId, - Color color, - String startLabel, - String endLabel, - AnnotationLabelAnchor labelAnchor, - AnnotationLabelDirection labelDirection, - AnnotationLabelPosition labelPosition, - TextStyleSpec labelStyleSpec}) - : super(axisType, - axisId: axisId, - color: color, - startLabel: startLabel, - endLabel: endLabel, - labelAnchor: labelAnchor, - labelDirection: labelDirection, - labelPosition: labelPosition, - labelStyleSpec: labelStyleSpec); - - @override - String get key => 'r::$axisType::$axisId::$startValue::$endValue'; -} - -/// Data for a chart line annotation. -class LineAnnotationSegment extends AnnotationSegment { - final D value; - final List dashPattern; - final double strokeWidthPx; - - LineAnnotationSegment(this.value, RangeAnnotationAxisType axisType, - {String axisId, - Color color, - String startLabel, - String endLabel, - AnnotationLabelAnchor labelAnchor, - AnnotationLabelDirection labelDirection, - AnnotationLabelPosition labelPosition, - TextStyleSpec labelStyleSpec, - this.dashPattern, - this.strokeWidthPx = 2.0}) - : super(axisType, - axisId: axisId, - color: color, - startLabel: startLabel, - endLabel: endLabel, - labelAnchor: labelAnchor, - labelDirection: labelDirection, - labelPosition: labelPosition, - labelStyleSpec: labelStyleSpec); - - @override - String get key => 'l::$axisType::$axisId::$value'; -} - -/// Axis type for an annotation. -enum RangeAnnotationAxisType { - domain, - measure, -} - -/// Configures where to anchor the label. -enum AnnotationLabelAnchor { - /// Anchor to the starting side of the annotation range. - start, - - /// Anchor to the middle of the annotation range. - middle, - - /// Anchor to the ending side of the annotation range. - end, -} - -/// Direction of the label text on the chart. -enum AnnotationLabelDirection { - /// Automatically assign a direction based on the [RangeAnnotationAxisType]. - /// - /// [horizontal] for measure axes, or [vertical] for domain axes. - auto, - - /// Text flows parallel to the x axis. - horizontal, - - /// Text flows parallel to the y axis. - /// TODO[b/112553019]: Implement vertical text rendering of labels. - vertical, -} - -/// Configures where to place the label relative to the annotation. -enum AnnotationLabelPosition { - /// Automatically try to place the label inside the bar first and place it on - /// the outside of the space available outside the bar is greater than space - /// available inside the bar. - auto, - - /// Always place label on the outside. - outside, - - /// Always place label on the inside. - inside, - - /// Place the label outside of the draw area, in the chart margin. - /// - /// Labels will be rendered on the opposite side of the chart from the primary - /// axis. For measure annotations, this means the "end" side, opposite from - /// the "start" side where the primary measure axis is located. - /// - /// This should not be used for measure annotations if the chart has a - /// secondary measure axis. The annotation behaviors do not perform collision - /// detection with tick labels. - margin, -} - -const String _unresolvedAutoMessage = 'Unresolved AnnotationLabelPosition.auto'; diff --git a/web/charts/common/lib/src/chart/common/behavior/selection/lock_selection.dart b/web/charts/common/lib/src/chart/common/behavior/selection/lock_selection.dart deleted file mode 100644 index d14b4d595..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/selection/lock_selection.dart +++ /dev/null @@ -1,126 +0,0 @@ -// 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'; - -import '../../../../common/gesture_listener.dart' show GestureListener; -import '../../base_chart.dart' show BaseChart; -import '../../behavior/chart_behavior.dart' show ChartBehavior; -import '../../selection_model/selection_model.dart' show SelectionModelType; -import 'selection_trigger.dart' show SelectionTrigger; - -/// Chart behavior that listens to tap event trigges and locks the specified -/// [SelectionModel]. This is used to prevent further updates to the selection -/// model, until it is unlocked again. -/// -/// SelectionModels that can be updated: -/// info - To view the details of the selected items (ie: hover for web). -/// action - To select an item as an input, drill, or other selection. -/// -/// You can add one LockSelection for each model type that you are updating. -/// Any previous LockSelection behavior for that selection model will be -/// removed. -class LockSelection implements ChartBehavior { - GestureListener _listener; - - /// Type of selection model that should be updated by input events. - final SelectionModelType selectionModelType; - - /// Type of input event that should trigger selection. - final SelectionTrigger eventTrigger = SelectionTrigger.tap; - - BaseChart _chart; - - LockSelection({this.selectionModelType = SelectionModelType.info}) { - // Setup the appropriate gesture listening. - switch (this.eventTrigger) { - case SelectionTrigger.tap: - _listener = GestureListener(onTapTest: _onTapTest, onTap: _onSelect); - break; - default: - throw ArgumentError('LockSelection does not support the event ' - 'trigger "${this.eventTrigger}"'); - break; - } - } - - bool _onTapTest(Point chartPoint) { - // If the tap is within the drawArea, then claim the event from others. - return _chart.pointWithinRenderer(chartPoint); - } - - bool _onSelect(Point chartPoint, [double ignored]) { - // Skip events that occur outside the drawArea for any series renderer. - if (!_chart.pointWithinRenderer(chartPoint)) { - return false; - } - - final selectionModel = _chart.getSelectionModel(selectionModelType); - - // Do nothing if the chart has no selection model. - if (selectionModel == null) { - return false; - } - - // Do not lock the selection model if there is no selection. Locking nothing - // would result in a very confusing user interface as the user tries to - // interact with content on the chart. - if (!selectionModel.locked && !selectionModel.hasAnySelection) { - return false; - } - - // Toggle the lock state. - selectionModel.locked = !selectionModel.locked; - - // If the model was just unlocked, clear the selection to dismiss any stale - // behavior elements. A new hovercard/etc. will appear after the user - // triggers a new gesture. - if (!selectionModel.locked) { - selectionModel.clearSelection(); - } - - return false; - } - - @override - void attachTo(BaseChart chart) { - _chart = chart; - chart.addGestureListener(_listener); - - // TODO: Update this dynamically based on tappable location. - switch (this.eventTrigger) { - case SelectionTrigger.tap: - case SelectionTrigger.tapAndDrag: - case SelectionTrigger.pressHold: - case SelectionTrigger.longPressHold: - chart.registerTappable(this); - break; - case SelectionTrigger.hover: - default: - chart.unregisterTappable(this); - break; - } - } - - @override - void removeFrom(BaseChart chart) { - chart.removeGestureListener(_listener); - chart.unregisterTappable(this); - _chart = null; - } - - @override - String get role => 'LockSelection-${selectionModelType.toString()}}'; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/selection/select_nearest.dart b/web/charts/common/lib/src/chart/common/behavior/selection/select_nearest.dart deleted file mode 100644 index 7270b9398..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/selection/select_nearest.dart +++ /dev/null @@ -1,300 +0,0 @@ -// 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'; - -import '../../../../common/gesture_listener.dart' show GestureListener; -import '../../base_chart.dart' show BaseChart; -import '../../behavior/chart_behavior.dart' show ChartBehavior; -import '../../datum_details.dart' show DatumDetails; -import '../../processed_series.dart' show ImmutableSeries; -import '../../selection_model/selection_model.dart' show SelectionModelType; -import '../../series_datum.dart' show SeriesDatum; -import 'selection_trigger.dart' show SelectionTrigger; - -/// Chart behavior that listens to the given eventTrigger and updates the -/// specified [SelectionModel]. This is used to pair input events to behaviors -/// that listen to selection changes. -/// -/// Input event types: -/// hover (default) - Mouse over/near data. -/// tap - Mouse/Touch on/near data. -/// pressHold - Mouse/Touch and drag across the data instead of panning. -/// longPressHold - Mouse/Touch for a while in one place then drag across the -/// data. -/// -/// SelectionModels that can be updated: -/// info - To view the details of the selected items (ie: hover for web). -/// action - To select an item as an input, drill, or other selection. -/// -/// Other options available -/// [expandToDomain] - All data points that match the domain value of the -/// closest data point from each Series will be included in the selection. -/// The selection is limited to the hovered component area unless -/// [selectAcrossAllSeriesRendererComponents] is set to true. (Default: -/// true) -/// [selectAcrossAllSeriesRendererComponents] - Events in any component that -/// draw Series data will propagate to other components that draw Series -/// data to get a union of points that match across all series renderer -/// components. This is useful when components in the margins draw series -/// data and a selection is supposed to bridge the two adjacent -/// components. (Default: true) -/// [selectClosestSeries] - If true, the closest Series itself will be marked -/// as selected in addition to the datum. This is useful for features like -/// highlighting the closest Series. (Default: true) -/// -/// You can add one SelectNearest for each model type that you are updating. -/// Any previous SelectNearest behavior for that selection model will be -/// removed. -class SelectNearest implements ChartBehavior { - GestureListener _listener; - - /// Type of selection model that should be updated by input events. - final SelectionModelType selectionModelType; - - /// Type of input event that should trigger selection. - final SelectionTrigger eventTrigger; - - /// Whether or not all data points that match the domain value of the closest - /// data point from each Series will be included in the selection. - /// - /// The selection is limited to the hovered component area unless - /// [selectAcrossAllSeriesRendererComponents] is set to true. - final bool expandToDomain; - - /// Whether or not events in any component that draw Series data will - /// propagate to other components that draw Series data to get a union of - /// points that match across all series renderer components. - /// - /// This is useful when components in the margins draw series data and a - /// selection is supposed to bridge the two adjacent components. - final bool selectAcrossAllSeriesRendererComponents; - - /// Whether or not the closest Series itself will be marked as selected in - /// addition to the datum. - final bool selectClosestSeries; - - /// The farthest away a domain value can be from the mouse position on the - /// domain axis before we'll ignore the datum. - /// - /// This allows sparse data to not get selected until the mouse is some - /// reasonable distance. Defaults to no maximum distance. - final int maximumDomainDistancePx; - - BaseChart _chart; - - bool _delaySelect = false; - - SelectNearest( - {this.selectionModelType = SelectionModelType.info, - this.expandToDomain = true, - this.selectAcrossAllSeriesRendererComponents = true, - this.selectClosestSeries = true, - this.eventTrigger = SelectionTrigger.hover, - this.maximumDomainDistancePx}) { - // Setup the appropriate gesture listening. - switch (this.eventTrigger) { - case SelectionTrigger.tap: - _listener = GestureListener(onTapTest: _onTapTest, onTap: _onSelect); - break; - case SelectionTrigger.tapAndDrag: - _listener = GestureListener( - onTapTest: _onTapTest, - onTap: _onSelect, - onDragStart: _onSelect, - onDragUpdate: _onSelect, - ); - break; - case SelectionTrigger.pressHold: - _listener = GestureListener( - onTapTest: _onTapTest, - onLongPress: _onSelect, - onDragStart: _onSelect, - onDragUpdate: _onSelect, - onDragEnd: _onDeselectAll); - break; - case SelectionTrigger.longPressHold: - _listener = GestureListener( - onTapTest: _onTapTest, - onLongPress: _onLongPressSelect, - onDragStart: _onSelect, - onDragUpdate: _onSelect, - onDragEnd: _onDeselectAll); - break; - case SelectionTrigger.hover: - default: - _listener = GestureListener(onHover: _onSelect); - break; - } - } - - bool _onTapTest(Point chartPoint) { - // If the tap is within the drawArea, then claim the event from others. - _delaySelect = eventTrigger == SelectionTrigger.longPressHold; - return _chart.pointWithinRenderer(chartPoint); - } - - bool _onLongPressSelect(Point chartPoint) { - _delaySelect = false; - return _onSelect(chartPoint); - } - - bool _onSelect(Point chartPoint, [double ignored]) { - // If the selection is delayed (waiting for long press), then quit early. - if (_delaySelect) { - return false; - } - - var details = _chart.getNearestDatumDetailPerSeries( - chartPoint, selectAcrossAllSeriesRendererComponents); - - final seriesList = >[]; - var seriesDatumList = >[]; - - if (details != null && details.isNotEmpty) { - details.sort((a, b) => a.domainDistance.compareTo(b.domainDistance)); - - if (maximumDomainDistancePx == null || - details[0].domainDistance <= maximumDomainDistancePx) { - seriesDatumList = expandToDomain - ? _expandToDomain(details.first) - : [SeriesDatum(details.first.series, details.first.datum)]; - - // Filter out points from overlay series. - seriesDatumList.removeWhere((datum) => datum.series.overlaySeries); - - if (selectClosestSeries && seriesList.isEmpty) { - if (details.first.series.overlaySeries) { - // If the closest "details" was from an overlay series, grab the - // closest remaining series instead. In this case, we need to sort a - // copy of the list by domain distance because we do not want to - // re-order the actual return values here. - final sortedSeriesDatumList = - List>.from(seriesDatumList); - sortedSeriesDatumList.sort((a, b) => - a.datum.domainDistance.compareTo(b.datum.domainDistance)); - seriesList.add(sortedSeriesDatumList.first.series); - } else { - seriesList.add(details.first.series); - } - } - } - } - - return _chart - .getSelectionModel(selectionModelType) - .updateSelection(seriesDatumList, seriesList); - } - - bool _onDeselectAll(_, __, ___) { - // If the selection is delayed (waiting for long press), then quit early. - if (_delaySelect) { - return false; - } - - _chart - .getSelectionModel(selectionModelType) - .updateSelection(>[], >[]); - return false; - } - - List> _expandToDomain(DatumDetails nearestDetails) { - // Make sure that the "nearest" datum is at the top of the list. - final data = >[ - SeriesDatum(nearestDetails.series, nearestDetails.datum) - ]; - final nearestDomain = nearestDetails.domain; - - for (ImmutableSeries series in _chart.currentSeriesList) { - final domainFn = series.domainFn; - final domainLowerBoundFn = series.domainLowerBoundFn; - final domainUpperBoundFn = series.domainUpperBoundFn; - final testBounds = - domainLowerBoundFn != null && domainUpperBoundFn != null; - - for (var i = 0; i < series.data.length; i++) { - final datum = series.data[i]; - final domain = domainFn(i); - - // Don't re-add the nearest details. - if (nearestDetails.series == series && nearestDetails.datum == datum) { - continue; - } - - if (domain == nearestDomain) { - data.add(SeriesDatum(series, datum)); - } else if (testBounds) { - final domainLowerBound = domainLowerBoundFn(i); - final domainUpperBound = domainUpperBoundFn(i); - - var addDatum = false; - if (domainLowerBound != null && domainUpperBound != null) { - if (domain is int) { - addDatum = (domainLowerBound as int) <= (nearestDomain as int) && - (nearestDomain as int) <= (domainUpperBound as int); - } else if (domain is double) { - addDatum = - (domainLowerBound as double) <= (nearestDomain as double) && - (nearestDomain as double) <= (domainUpperBound as double); - } else if (domain is DateTime) { - addDatum = domainLowerBound == nearestDomain || - domainUpperBound == nearestDomain || - ((domainLowerBound as DateTime) - .isBefore(nearestDomain as DateTime) && - (nearestDomain as DateTime) - .isBefore(domainUpperBound as DateTime)); - } - } - - if (addDatum) { - data.add(SeriesDatum(series, datum)); - } - } - } - } - - return data; - } - - @override - void attachTo(BaseChart chart) { - _chart = chart; - chart.addGestureListener(_listener); - - // TODO: Update this dynamically based on tappable location. - switch (this.eventTrigger) { - case SelectionTrigger.tap: - case SelectionTrigger.tapAndDrag: - case SelectionTrigger.pressHold: - case SelectionTrigger.longPressHold: - chart.registerTappable(this); - break; - case SelectionTrigger.hover: - default: - chart.unregisterTappable(this); - break; - } - } - - @override - void removeFrom(BaseChart chart) { - chart.removeGestureListener(_listener); - chart.unregisterTappable(this); - _chart = null; - } - - @override - String get role => 'SelectNearest-${selectionModelType.toString()}}'; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/selection/selection_trigger.dart b/web/charts/common/lib/src/chart/common/behavior/selection/selection_trigger.dart deleted file mode 100644 index 663c8d6ec..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/selection/selection_trigger.dart +++ /dev/null @@ -1,22 +0,0 @@ -// 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. - -enum SelectionTrigger { - hover, - tap, - tapAndDrag, - pressHold, - longPressHold, -} diff --git a/web/charts/common/lib/src/chart/common/behavior/slider/slider.dart b/web/charts/common/lib/src/chart/common/behavior/slider/slider.dart deleted file mode 100644 index 3083932d5..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/slider/slider.dart +++ /dev/null @@ -1,803 +0,0 @@ -// 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'; - -import 'package:meta/meta.dart'; - -import '../../../../common/color.dart' show Color; -import '../../../../common/gesture_listener.dart' show GestureListener; -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../../common/math.dart' show clamp; -import '../../../../common/style/style_factory.dart' show StyleFactory; -import '../../../../common/symbol_renderer.dart' - show RectSymbolRenderer, SymbolRenderer; -import '../../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../../../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - ViewMeasuredSizes; -import '../../base_chart.dart' show BaseChart, LifecycleListener; -import '../../behavior/chart_behavior.dart' show ChartBehavior; -import '../../chart_canvas.dart' show ChartCanvas, getAnimatedColor; -import '../selection/selection_trigger.dart' show SelectionTrigger; - -/// Chart behavior that adds a slider widget to a chart. When the slider is -/// dropped after drag, it will report its domain position and nearest datum -/// value. This behavior only supports charts that use continuous scales. -/// -/// Input event types: -/// tapAndDrag - Mouse/Touch on the handle and drag across the chart. -/// pressHold - Mouse/Touch on the handle and drag across the chart instead of -/// panning. -/// longPressHold - Mouse/Touch for a while on the handle, then drag across -/// the data. -class Slider implements ChartBehavior { - _SliderLayoutView _view; - - GestureListener _gestureListener; - - LifecycleListener _lifecycleListener; - - SliderEventListener _sliderEventListener; - - /// The order to paint slider on the canvas. - /// - /// The smaller number is drawn first. This value should be relative to - /// LayoutPaintViewOrder.slider (e.g. LayoutViewPaintOrder.slider + 1). - int layoutPaintOrder; - - /// Type of input event for the slider. - /// - /// Input event types: - /// tapAndDrag - Mouse/Touch on the handle and drag across the chart. - /// pressHold - Mouse/Touch on the handle and drag across the chart instead - /// of panning. - /// longPressHold - Mouse/Touch for a while on the handle, then drag across - /// the data. - final SelectionTrigger eventTrigger; - - /// Renderer for the handle. Defaults to a rectangle. - SymbolRenderer _handleRenderer; - - /// Custom role ID for this slider - String _roleId; - - /// Whether or not the slider will snap onto the nearest datum (by domain - /// distance) when dragged. - final bool snapToDatum; - - /// Color and size styles for the slider. - SliderStyle _style; - - CartesianChart _chart; - - /// Rendering data for the slider line and handle. - _AnimatedSlider _sliderHandle; - - bool _delaySelect = false; - - bool _handleDrag = false; - - /// Current location of the slider line. - Point _domainCenterPoint; - - /// Previous location of the slider line. - /// - /// This is used to track changes in the position of the slider caused by new - /// data being drawn on the chart. - Point _previousDomainCenterPoint; - - /// Bounding box for the slider drag handle. - Rectangle _handleBounds; - - /// Domain value of the current slider position. - /// - /// This is saved in terms of domain instead of chart position so that we can - /// adjust the slider automatically when the chart is resized. - D _domainValue; - - /// Event to fire during the chart's onPostrender event. - /// - /// This should be set any time the state of the slider has changed. - SliderListenerDragState _dragStateToFireOnPostRender; - - /// Constructs a [Slider]. - /// - /// [eventTrigger] sets the type of gesture handled by the slider. - /// - /// [handleRenderer] draws a handle for the slider. Defaults to a rectangle. - /// - /// [initialDomainValue] sets the initial position of the slider in domain - /// units. The default is the center of the chart. - /// - /// [onChangeCallback] will be called when the position of the slider - /// changes during a drag event. - /// - /// [roleId] optional custom role ID for the slider. This can be used to allow - /// multiple [Slider] behaviors on the same chart. Normally, there can only be - /// one slider (per event trigger type) on a chart. This setting allows for - /// configuring multiple independent sliders. - /// - /// [snapToDatum] configures the slider to snap snap onto the nearest datum - /// (by domain distance) when dragged. By default, the slider can be - /// positioned anywhere along the domain axis. - /// - /// [style] configures the color and sizing of the slider line and handle. - /// - /// [layoutPaintOrder] configures the order in which the behavior should be - /// painted. This value should be relative to LayoutPaintViewOrder.slider. - /// (e.g. LayoutViewPaintOrder.slider + 1). - Slider( - {this.eventTrigger = SelectionTrigger.tapAndDrag, - SymbolRenderer handleRenderer, - D initialDomainValue, - SliderListenerCallback onChangeCallback, - String roleId, - this.snapToDatum = false, - SliderStyle style, - this.layoutPaintOrder = LayoutViewPaintOrder.slider}) { - _handleRenderer = handleRenderer ?? RectSymbolRenderer(); - _roleId = roleId ?? ''; - _style = style ?? SliderStyle(); - - _domainValue = initialDomainValue; - if (_domainValue != null) { - _dragStateToFireOnPostRender = SliderListenerDragState.initial; - } - - // Setup the appropriate gesture listening. - switch (this.eventTrigger) { - case SelectionTrigger.tapAndDrag: - _gestureListener = GestureListener( - onTapTest: _onTapTest, - onTap: _onSelect, - onDragStart: _onSelect, - onDragUpdate: _onSelect, - onDragEnd: _onDragEnd); - break; - case SelectionTrigger.pressHold: - _gestureListener = GestureListener( - onTapTest: _onTapTest, - onLongPress: _onSelect, - onDragStart: _onSelect, - onDragUpdate: _onSelect, - onDragEnd: _onDragEnd); - break; - case SelectionTrigger.longPressHold: - _gestureListener = GestureListener( - onTapTest: _onTapTest, - onLongPress: _onLongPressSelect, - onDragStart: _onSelect, - onDragUpdate: _onSelect, - onDragEnd: _onDragEnd); - break; - default: - throw ArgumentError('Slider does not support the event trigger ' - '"${this.eventTrigger}"'); - break; - } - - // Set up chart draw cycle listeners. - _lifecycleListener = LifecycleListener( - onData: _setInitialDragState, - onAxisConfigured: _updateViewData, - onPostrender: _fireChangeEvent, - ); - - // Set up slider event listeners. - _sliderEventListener = SliderEventListener(onChange: onChangeCallback); - } - - bool _onTapTest(Point chartPoint) { - _delaySelect = eventTrigger == SelectionTrigger.longPressHold; - _handleDrag = _sliderContainsPoint(chartPoint); - return _handleDrag; - } - - bool _onLongPressSelect(Point chartPoint) { - _delaySelect = false; - return _onSelect(chartPoint); - } - - bool _onSelect(Point chartPoint, [double ignored]) { - // Skip events that occur outside the drawArea for any series renderer. - // If the selection is delayed (waiting for long press), then quit early. - if (!_handleDrag || _delaySelect) { - return false; - } - - // Move the slider line along the domain axis, without adjusting the measure - // position. - final positionChanged = _moveSliderToPoint(chartPoint); - - if (positionChanged) { - _dragStateToFireOnPostRender = SliderListenerDragState.drag; - - _chart.redraw(skipAnimation: true, skipLayout: true); - } - - return true; - } - - bool _onDragEnd(Point chartPoint, __, ___) { - // If the selection is delayed (waiting for long press), then quit early. - if (_delaySelect) { - return false; - } - - _handleDrag = false; - - // If snapToDatum is enabled, use the x position of the nearest datum - // instead of the mouse point. - if (snapToDatum) { - final details = _chart.getNearestDatumDetailPerSeries(chartPoint, true); - if (details.isNotEmpty && details[0].chartPosition.x != null) { - // Only trigger an animating draw cycle if we need to move the slider. - if (_domainValue != details[0].domain) { - _moveSliderToDomain(details[0].domain); - - // Always fire the end event to notify listeners that the gesture is - // over. - _dragStateToFireOnPostRender = SliderListenerDragState.end; - - _chart.redraw(skipAnimation: false, skipLayout: true); - } - } - } else { - // Move the slider line along the domain axis, without adjusting the - // measure position. - _moveSliderToPoint(chartPoint); - - // Always fire the end event to notify listeners that the gesture is - // over. - _dragStateToFireOnPostRender = SliderListenerDragState.end; - - _chart.redraw(skipAnimation: true, skipLayout: true); - } - - return false; - } - - bool _sliderContainsPoint(Point chartPoint) { - return _handleBounds.containsPoint(chartPoint); - } - - /// Sets the drag state to "initial" when new data is drawn on the chart. - void _setInitialDragState(_) { - _dragStateToFireOnPostRender = SliderListenerDragState.initial; - } - - void _updateViewData() { - _sliderHandle ??= _AnimatedSlider(); - - // If not set in the constructor, initial position for the handle is the - // center of the draw area. - _domainValue ??= _chart.domainAxis - .getDomain(_view.drawBounds.left + _view.drawBounds.width / 2) - .round(); - - // Possibly move the slider, if the axis values have changed since the last - // chart draw. - _moveSliderToDomain(_domainValue); - - // Move the handle to the current event position. - final element = _SliderElement() - ..domainCenterPoint = - Point(_domainCenterPoint.x, _domainCenterPoint.y) - ..buttonBounds = Rectangle(_handleBounds.left, _handleBounds.top, - _handleBounds.width, _handleBounds.height) - ..fill = _style.fillColor - ..stroke = _style.strokeColor - ..strokeWidthPx = _style.strokeWidthPx; - - _sliderHandle.setNewTarget(element); - - _view.sliderHandle = _sliderHandle; - } - - /// Fires a [SliderListenerDragState] change event if needed. - void _fireChangeEvent(_) { - if (SliderListenerDragState == null || - _sliderEventListener.onChange == null) { - return; - } - - SliderListenerDragState dragState = _dragStateToFireOnPostRender; - - // Initial drag state event should only be fired if the slider has moved - // since the last draw. We always set the initial drag state event when new - // data was drawn on the chart, since we might need to move the slider if - // the axis range changed. - if (dragState == SliderListenerDragState.initial && - _previousDomainCenterPoint == _domainCenterPoint) { - dragState = null; - } - - // Reset state. - _dragStateToFireOnPostRender = null; - _previousDomainCenterPoint = _domainCenterPoint; - - // Bail out if the event was cancelled. - if (dragState == null) { - return; - } - - // Fire the event. - _sliderEventListener.onChange( - Point(_domainCenterPoint.x, _domainCenterPoint.y), - _domainValue, - _roleId, - dragState); - } - - /// Moves the slider along the domain axis to [point]. - /// - /// If [point] exists beyond either edge of the draw area, it will be bound to - /// the nearest edge. - /// - /// Updates [_domainValue] with the domain value located at [point]. For - /// ordinal axes, this might technically result in a domain value whose center - /// point lies slightly outside the draw area. - /// - /// Updates [_domainCenterPoint] and [_handleBounds] with the new position of - /// the slider. - /// - /// Returns whether or not the position actually changed. This will generally - /// be false if the mouse was dragged outside of the domain axis viewport. - bool _moveSliderToPoint(Point point) { - var positionChanged = false; - - if (_chart != null) { - final viewBounds = _view.componentBounds; - - // Clamp the position to the edge of the viewport. - final position = clamp(point.x, viewBounds.left, viewBounds.right); - - positionChanged = (_previousDomainCenterPoint != null && - position != _previousDomainCenterPoint.x); - - // Reset the domain value if the position was outside of the chart. - _domainValue = _chart.domainAxis.getDomain(position.toDouble()); - - if (_domainCenterPoint != null) { - _domainCenterPoint = Point(position.round(), _domainCenterPoint.y); - } else { - _domainCenterPoint = Point( - position.round(), (viewBounds.top + viewBounds.height / 2).round()); - } - - num handleReferenceY; - switch (_style.handlePosition) { - case SliderHandlePosition.middle: - handleReferenceY = _domainCenterPoint.y; - break; - case SliderHandlePosition.top: - handleReferenceY = viewBounds.top; - break; - default: - throw ArgumentError('Slider does not support the handle position ' - '"${_style.handlePosition}"'); - } - - // Move the slider handle along the domain axis. - _handleBounds = Rectangle( - (_domainCenterPoint.x - - _style.handleSize.width / 2 + - _style.handleOffset.x) - .round(), - (handleReferenceY - - _style.handleSize.height / 2 + - _style.handleOffset.y) - .round(), - _style.handleSize.width, - _style.handleSize.height); - } - - return positionChanged; - } - - /// Moves the slider along the domain axis to the location of [domain]. - /// - /// If [domain] exists beyond either edge of the draw area, the position will - /// be bound to the nearest edge. - /// - /// Updates [_domainValue] with the location of [domain]. For ordinal axes, - /// this might result in a different domain value if the range band of - /// [domain] is completely outside of the viewport. - /// - /// Updates [_domainCenterPoint] and [_handleBounds] with the new position of - /// the slider. - /// - /// Returns whether or not the position actually changed. This will generally - /// be false if the mouse was dragged outside of the domain axis viewport. - bool _moveSliderToDomain(D domain) { - final x = _chart.domainAxis.getLocation(domain); - - return _moveSliderToPoint(Point(x, 0.0)); - } - - /// Programmatically moves the slider to the location of [domain] on the - /// domain axis. - /// - /// If [domain] exists beyond either edge of the draw area, the position will - /// be bound to the nearest edge of the chart. The slider's current domain - /// value state will reflect the domain value at the edge of the chart. For - /// ordinal axes, this might result in a domain value whose range band is - /// partially located beyond the edge of the chart. - /// - /// This does nothing if the domain matches the current domain location. - /// - /// [SliderEventListener] callbacks will be fired to indicate that the slider - /// has moved. - /// - /// [skipAnimation] controls whether or not the slider will animate. Animation - /// is disabled by default. - void moveSliderToDomain(D domain, {bool skipAnimation = true}) { - // Nothing to do if we are unattached to a chart or asked to move to the - // current location. - if (_chart == null || domain == _domainValue) { - return; - } - - final positionChanged = _moveSliderToDomain(domain); - - if (positionChanged) { - _dragStateToFireOnPostRender = SliderListenerDragState.end; - - _chart.redraw(skipAnimation: skipAnimation, skipLayout: true); - } - } - - @override - void attachTo(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError('Slider can only be attached to a cartesian chart.'); - } - - _chart = chart as CartesianChart; - - // Only vertical rendering is supported by this behavior. - assert(_chart.vertical); - - _view = _SliderLayoutView( - layoutPaintOrder: layoutPaintOrder, handleRenderer: _handleRenderer); - - chart.addView(_view); - chart.addGestureListener(_gestureListener); - chart.addLifecycleListener(_lifecycleListener); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeView(_view); - chart.removeGestureListener(_gestureListener); - chart.removeLifecycleListener(_lifecycleListener); - _chart = null; - } - - @override - String get role => 'Slider-${eventTrigger.toString()}-$_roleId'; -} - -/// Style configuration for a [Slider] behavior. -class SliderStyle { - /// Fill color of the handle of the slider. - Color fillColor; - - /// Allows users to specify both x-position and y-position offset values that - /// determines where the slider handle will be rendered. The offset will be - /// calculated relative to its default position at the vertical and horizontal - /// center of the slider line. - Point handleOffset; - - /// The vertical position for the slider handle. - SliderHandlePosition handlePosition; - - /// Specifies the size of the slider handle. - Rectangle handleSize; - - /// Stroke width of the slider line and the slider handle. - double strokeWidthPx; - - /// Stroke color of the slider line and hte slider handle - Color strokeColor = StyleFactory.style.sliderStrokeColor; - - SliderStyle( - {Color fillColor, - this.handleOffset = const Point(0.0, 0.0), - this.handleSize = const Rectangle(0, 0, 10, 20), - Color strokeColor, - this.handlePosition = SliderHandlePosition.middle, - this.strokeWidthPx = 2.0}) { - this.fillColor = fillColor ?? StyleFactory.style.sliderFillColor; - this.strokeColor = strokeColor ?? StyleFactory.style.sliderStrokeColor; - } - - @override - bool operator ==(Object o) { - return o is SliderStyle && - fillColor == o.fillColor && - handleOffset == o.handleOffset && - handleSize == o.handleSize && - strokeWidthPx == o.strokeWidthPx && - strokeColor == o.strokeColor; - } - - @override - int get hashCode { - int hashcode = fillColor?.hashCode ?? 0; - hashcode = (hashcode * 37) + handleOffset?.hashCode ?? 0; - hashcode = (hashcode * 37) + handleSize?.hashCode ?? 0; - hashcode = (hashcode * 37) + strokeWidthPx?.hashCode ?? 0; - hashcode = (hashcode * 37) + strokeColor?.hashCode ?? 0; - hashcode = (hashcode * 37) + handlePosition?.hashCode ?? 0; - return hashcode; - } -} - -/// Describes the vertical position of the slider handle on the slider. -/// -/// [middle] indicates the handle should be half-way between the top and bottom -/// of the chart in the middle of the slider line. -/// -/// [top] indicates the slider should be rendered relative to the top of the -/// chart. -enum SliderHandlePosition { middle, top } - -/// Layout view component for [Slider]. -class _SliderLayoutView extends LayoutView { - final LayoutViewConfig layoutConfig; - - Rectangle _drawAreaBounds; - - Rectangle get drawBounds => _drawAreaBounds; - - GraphicsFactory graphicsFactory; - - /// Renderer for the handle. Defaults to a rectangle. - SymbolRenderer _handleRenderer; - - /// Rendering data for the slider line and handle. - _AnimatedSlider _sliderHandle; - - _SliderLayoutView( - {@required int layoutPaintOrder, @required SymbolRenderer handleRenderer}) - : this.layoutConfig = LayoutViewConfig( - paintOrder: layoutPaintOrder, - position: LayoutPosition.DrawArea, - positionOrder: LayoutViewPositionOrder.drawArea), - _handleRenderer = handleRenderer; - - set sliderHandle(_AnimatedSlider value) { - _sliderHandle = value; - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - return null; - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - this._drawAreaBounds = drawAreaBounds; - } - - @override - void paint(ChartCanvas canvas, double animationPercent) { - final sliderElement = _sliderHandle.getCurrentSlider(animationPercent); - - canvas.drawLine( - points: [ - Point(sliderElement.domainCenterPoint.x, _drawAreaBounds.top), - Point(sliderElement.domainCenterPoint.x, _drawAreaBounds.bottom), - ], - stroke: sliderElement.stroke, - strokeWidthPx: sliderElement.strokeWidthPx); - - _handleRenderer.paint(canvas, sliderElement.buttonBounds, - fillColor: sliderElement.fill, - strokeColor: sliderElement.stroke, - strokeWidthPx: sliderElement.strokeWidthPx); - } - - @override - Rectangle get componentBounds => this._drawAreaBounds; - - @override - bool get isSeriesRenderer => false; -} - -/// Rendering information for a slider control element. -class _SliderElement { - Point domainCenterPoint; - Rectangle buttonBounds; - Color fill; - Color stroke; - double strokeWidthPx; - - _SliderElement clone() { - return _SliderElement() - ..domainCenterPoint = this.domainCenterPoint - ..buttonBounds = this.buttonBounds - ..fill = this.fill - ..stroke = this.stroke - ..strokeWidthPx = this.strokeWidthPx; - } - - void updateAnimationPercent( - _SliderElement previous, _SliderElement target, double animationPercent) { - final _SliderElement localPrevious = previous; - final _SliderElement localTarget = target; - - final previousPoint = localPrevious.domainCenterPoint; - final targetPoint = localTarget.domainCenterPoint; - - final x = ((targetPoint.x - previousPoint.x) * animationPercent) + - previousPoint.x; - - final y = ((targetPoint.y - previousPoint.y) * animationPercent) + - previousPoint.y; - - domainCenterPoint = Point(x.round(), y.round()); - - final previousBounds = localPrevious.buttonBounds; - final targetBounds = localTarget.buttonBounds; - - final top = ((targetBounds.top - previousBounds.top) * animationPercent) + - previousBounds.top; - final right = - ((targetBounds.right - previousBounds.right) * animationPercent) + - previousBounds.right; - final bottom = - ((targetBounds.bottom - previousBounds.bottom) * animationPercent) + - previousBounds.bottom; - final left = - ((targetBounds.left - previousBounds.left) * animationPercent) + - previousBounds.left; - - buttonBounds = Rectangle(left.round(), top.round(), - (right - left).round(), (bottom - top).round()); - - fill = getAnimatedColor(previous.fill, target.fill, animationPercent); - - stroke = getAnimatedColor(previous.stroke, target.stroke, animationPercent); - - strokeWidthPx = - (((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + - previous.strokeWidthPx); - } -} - -/// Animates the slider control element of the behavior between different -/// states. -class _AnimatedSlider { - _SliderElement _previousSlider; - _SliderElement _targetSlider; - _SliderElement _currentSlider; - - // Flag indicating whether this point is being animated out of the chart. - bool animatingOut = false; - - _AnimatedSlider(); - - /// Animates a point that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for points that represent - /// data that has been removed from the series. - /// - /// Animates the width of the slider down to 0. - void animateOut() { - final newTarget = _currentSlider.clone(); - - // Animate the button bounds inwards horizontally towards a 0 width box. - final targetBounds = newTarget.buttonBounds; - final top = targetBounds.top; - final right = targetBounds.left + targetBounds.width / 2; - final bottom = targetBounds.bottom; - final left = right; - - newTarget.buttonBounds = Rectangle(left.round(), top.round(), - (right - left).round(), (bottom - top).round()); - - // Animate the stroke width to 0 so that we don't get a lingering line after - // animation is done. - newTarget.strokeWidthPx = 0.0; - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(_SliderElement newTarget) { - animatingOut = false; - _currentSlider ??= newTarget.clone(); - _previousSlider = _currentSlider.clone(); - _targetSlider = newTarget; - } - - _SliderElement getCurrentSlider(double animationPercent) { - if (animationPercent == 1.0 || _previousSlider == null) { - _currentSlider = _targetSlider; - _previousSlider = _targetSlider; - return _currentSlider; - } - - _currentSlider.updateAnimationPercent( - _previousSlider, _targetSlider, animationPercent); - - return _currentSlider; - } -} - -/// Event handler for slider events. -class SliderEventListener { - /// Called when the position of the slider has changed during a drag event. - final SliderListenerCallback onChange; - - SliderEventListener({this.onChange}); -} - -/// Callback function for [Slider] drag events. -/// -/// [point] is the current position of the slider line. [point.x] is the domain -/// position, and [point.y] is the position of the center of the line on the -/// measure axis. -/// -/// [domain] is the domain value at the slider position. -/// -/// [dragState] indicates the current state of a drag event. -typedef SliderListenerCallback = Function(Point point, D domain, - String roleId, SliderListenerDragState dragState); - -/// Describes the current state of a slider change as a result of a drag event. -/// -/// [initial] indicates that the slider was set to an initial position when new -/// data was drawn on a chart. This will be fired if an initialDomainValue is -/// passed to [Slider]. It will also be fired if the position of the slider -/// changes as a result of new data being drawn on the chart. -/// -/// [drag] indicates that the slider is being moved as a result of drag events. -/// When this is passed, the drag event is still active. Once the drag event is -/// completed, an [end] event will be fired. -/// -/// [end] indicates that a drag event has been completed. This usually occurs -/// after one or more [drag] events. An [end] event will also be fired if -/// [Slider.moveSliderToDomain] is called, but there will be no preceding [drag] -/// events in this case. -enum SliderListenerDragState { initial, drag, end } - -/// Helper class that exposes fewer private internal properties for unit tests. -@visibleForTesting -class SliderTester { - final Slider behavior; - - SliderTester(this.behavior); - - Point get domainCenterPoint => behavior._domainCenterPoint; - - D get domainValue => behavior._domainValue; - - Rectangle get handleBounds => behavior._handleBounds; - - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - behavior._view.layout(componentBounds, drawAreaBounds); - } - - _SliderLayoutView get view => behavior._view; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/sliding_viewport.dart b/web/charts/common/lib/src/chart/common/behavior/sliding_viewport.dart deleted file mode 100644 index 633db1f66..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/sliding_viewport.dart +++ /dev/null @@ -1,75 +0,0 @@ -// 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 '../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../base_chart.dart' show BaseChart; -import '../selection_model/selection_model.dart' - show SelectionModel, SelectionModelType; -import 'chart_behavior.dart' show ChartBehavior; - -/// Chart behavior that centers the viewport on the selected domain. -/// -/// It is used in combination with SelectNearest to update the selection model -/// and notify this behavior to update the viewport on selection change. -/// -/// This behavior can only be used on [CartesianChart]. -class SlidingViewport implements ChartBehavior { - final SelectionModelType selectionModelType; - - CartesianChart _chart; - - SlidingViewport([this.selectionModelType = SelectionModelType.info]); - - void _selectionChanged(SelectionModel selectionModel) { - if (selectionModel.hasAnySelection == false) { - return; - } - - // Calculate current viewport center and determine the translate pixels - // needed based on the selected domain value's location and existing amount - // of translate pixels. - final domainAxis = _chart.domainAxis; - final selectedDatum = selectionModel.selectedDatum.first; - final domainLocation = domainAxis - .getLocation(selectedDatum.series.domainFn(selectedDatum.index)); - final viewportCenter = - domainAxis.range.start + (domainAxis.range.width / 2); - final translatePx = - domainAxis.viewportTranslatePx + (viewportCenter - domainLocation); - domainAxis.setViewportSettings( - domainAxis.viewportScalingFactor, translatePx); - - _chart.redraw(); - } - - @override - void attachTo(BaseChart chart) { - assert(chart is CartesianChart); - _chart = chart as CartesianChart; - chart - .getSelectionModel(selectionModelType) - .addSelectionChangedListener(_selectionChanged); - } - - @override - void removeFrom(BaseChart chart) { - chart - .getSelectionModel(selectionModelType) - .removeSelectionChangedListener(_selectionChanged); - } - - @override - String get role => 'slidingViewport-${selectionModelType.toString()}'; -} diff --git a/web/charts/common/lib/src/chart/common/behavior/zoom/initial_hint_behavior.dart b/web/charts/common/lib/src/chart/common/behavior/zoom/initial_hint_behavior.dart deleted file mode 100644 index 84b58a590..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/zoom/initial_hint_behavior.dart +++ /dev/null @@ -1,264 +0,0 @@ -// 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; - -import 'package:meta/meta.dart' show protected; - -import '../../../../common/gesture_listener.dart' show GestureListener; -import '../../../cartesian/axis/axis.dart' show Axis; -import '../../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../../base_chart.dart' show BaseChart, LifecycleListener; -import '../chart_behavior.dart' show ChartBehavior; - -/// Adds initial hint behavior for [CartesianChart]. -/// -/// This behavior animates to the final viewport from an initial translate and -/// or scale factor. -abstract class InitialHintBehavior implements ChartBehavior { - /// Listens for drag gestures. - GestureListener _listener; - - /// Chart lifecycle listener to setup hint animation. - LifecycleListener _lifecycleListener; - - @override - String get role => 'InitialHint'; - - /// The chart to which the behavior is attached. - CartesianChart _chart; - - @protected - CartesianChart get chart => _chart; - - Duration _hintDuration = Duration(milliseconds: 3000); - - /// The amount of time to animate to the desired viewport. - /// - /// If no duration is passed in, the default of 3000 ms is used. - @protected - Duration get hintDuration => _hintDuration; - - set hintDuration(Duration duration) { - _hintDuration = duration; - } - - double _maxHintTranslate = 0.0; - - // TODO: Translation animation only works for ordinal axis. - /// The maximum amount ordinal values to shift the viewport for the the hint - /// animation. - /// - /// Positive numbers shift the viewport to the right and negative to the left. - /// The default is no translation. - @protected - double get maxHintTranslate => _maxHintTranslate; - - set maxHintTranslate(double maxHintTranslate) { - _maxHintTranslate = maxHintTranslate; - } - - double _maxHintScaleFactor; - - /// The amount the domain axis will be scaled for the start of the hint. - /// - /// A value of 1.0 means the viewport is completely zoomed out (all domains - /// are in the viewport). If a value is provided, it cannot be less than 1.0. - /// - /// By default maxHintScaleFactor is not set. - @protected - double get maxHintScaleFactor => _maxHintScaleFactor; - - set maxHintScaleFactor(double maxHintScaleFactor) { - assert(maxHintScaleFactor != null && maxHintScaleFactor >= 1.0); - - _maxHintScaleFactor = maxHintScaleFactor; - } - - /// Flag to indicate that hint animation controller has already been set up. - /// - /// This is to ensure that the hint is only set up on the first draw. - bool _hintSetupCompleted = false; - - /// Flag to indicate that the first call to axis configured is completed. - /// - /// This is to ensure that the initial and target viewport translate and scale - /// factor is only calculated on the first axis configuration. - bool _firstAxisConfigured = false; - - double _initialViewportTranslatePx; - double _initialViewportScalingFactor; - double _targetViewportTranslatePx; - double _targetViewportScalingFactor; - - InitialHintBehavior() { - _listener = GestureListener(onTapTest: onTapTest); - - _lifecycleListener = LifecycleListener( - onAxisConfigured: _onAxisConfigured, - onAnimationComplete: _onAnimationComplete); - } - - @override - attachTo(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError( - 'InitialHintBehavior can only be attached to a CartesianChart'); - } - - _chart = chart; - - _chart.addGestureListener(_listener); - _chart.addLifecycleListener(_lifecycleListener); - } - - @override - removeFrom(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError( - 'InitialHintBehavior can only be removed from a CartesianChart'); - } - - stopHintAnimation(); - - _chart = chart; - _chart.removeGestureListener(_listener); - _chart.removeLifecycleListener(_lifecycleListener); - - _chart = null; - } - - @protected - bool onTapTest(Point localPosition) { - if (_chart == null) { - return false; - } - - // If the user taps the chart, stop the hint animation immediately. - stopHintAnimation(); - - return _chart.withinDrawArea(localPosition); - } - - /// Calculate the animation's initial and target viewport and scale factor - /// and shift the viewport to the start. - void _onAxisConfigured() { - if (_firstAxisConfigured == false) { - _firstAxisConfigured = true; - - final domainAxis = chart.domainAxis; - - // TODO: Translation animation only works for axis with a - // rangeband type that returns a non zero step size. If two rows have - // the same domain value, step size could also equal 0. - assert(domainAxis.stepSize != 0.0); - - // Save the target viewport and scale factor from axis, because the - // viewport can be set by the user using AxisSpec. - _targetViewportTranslatePx = domainAxis.viewportTranslatePx; - _targetViewportScalingFactor = domainAxis.viewportScalingFactor; - - // Calculate the amount to translate from the target viewport. - final translateAmount = domainAxis.stepSize * maxHintTranslate; - - _initialViewportTranslatePx = - _targetViewportTranslatePx - translateAmount; - - _initialViewportScalingFactor = - maxHintScaleFactor ?? _targetViewportScalingFactor; - - domainAxis.setViewportSettings( - _initialViewportScalingFactor, _initialViewportTranslatePx); - chart.redraw(skipAnimation: true, skipLayout: false); - } - } - - /// Start the hint animation, only start the animation on the very first draw. - void _onAnimationComplete() { - if (_hintSetupCompleted == false) { - _hintSetupCompleted = true; - - startHintAnimation(); - } - } - - /// Setup and start the hint animation. - /// - /// Animation controller to be handled by the native platform. - @protected - void startHintAnimation() { - // When panning starts, measure tick provider should not update ticks. - // This is still needed because axis internally updates the tick location - // after the tick provider generates the ticks. If we do not tell the axis - // not to update the location of the measure axes, the measure axis will - // change during the hint animation and make values jump back and forth. - _chart.getMeasureAxis().lockAxis = true; - _chart.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId)?.lockAxis = true; - } - - /// Stop hint animation - @protected - void stopHintAnimation() { - // When panning is completed, unlock the measure axis. - _chart.getMeasureAxis().lockAxis = false; - _chart.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId)?.lockAxis = - false; - } - - /// Animation hint percent, to be returned by the native platform. - @protected - double get hintAnimationPercent; - - /// Shift domain viewport on hint animation ticks. - @protected - void onHintTick() { - final percent = hintAnimationPercent; - - final scaleFactor = _lerpDouble( - _initialViewportScalingFactor, _targetViewportScalingFactor, percent); - - double translatePx = _lerpDouble( - _initialViewportTranslatePx, _targetViewportTranslatePx, percent); - - // If there is a scale factor animation, need to scale the translatePx so - // the animation appears to be zooming in on the viewport when there is no - // [maxHintTranslate] provided. - // - // If there is a translate hint, the animation will still first zoom in - // and then translate the [maxHintTranslate] amount. - if (_initialViewportScalingFactor != _targetViewportScalingFactor) { - translatePx = translatePx * percent; - } - - final domainAxis = chart.domainAxis; - domainAxis.setViewportSettings(scaleFactor, translatePx, - drawAreaWidth: chart.drawAreaBounds.width); - - if (percent >= 1.0) { - stopHintAnimation(); - chart.redraw(); - } else { - chart.redraw(skipAnimation: true, skipLayout: true); - } - } - - /// Linear interpolation for doubles. - double _lerpDouble(double a, double b, double t) { - if (a == null && b == null) return null; - a ??= 0.0; - b ??= 0.0; - return a + (b - a) * t; - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart b/web/charts/common/lib/src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart deleted file mode 100644 index 05dc5f310..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart +++ /dev/null @@ -1,119 +0,0 @@ -// 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 min, max, Point; - -import 'package:meta/meta.dart' show protected; - -import 'pan_behavior.dart'; -import 'panning_tick_provider.dart' show PanningTickProviderMode; - -/// Adds domain axis panning and zooming support to the chart. -/// -/// Zooming is supported for the web by mouse wheel events. Scrolling up zooms -/// the chart in, and scrolling down zooms the chart out. The chart can never be -/// zoomed out past the domain axis range. -/// -/// Zooming is supported by pinch gestures for mobile devices. -/// -/// Panning is supported by clicking and dragging the mouse for web, or tapping -/// and dragging on the chart for mobile devices. -class PanAndZoomBehavior extends PanBehavior { - @override - String get role => 'PanAndZoom'; - - /// Flag which is enabled to indicate that the user is "zooming" the chart. - bool _isZooming = false; - - @protected - bool get isZooming => _isZooming; - - /// Current zoom scaling factor for the behavior. - double _scalingFactor = 1.0; - - /// Minimum scalingFactor to prevent zooming out beyond the data range. - final _minScalingFactor = 1.0; - - /// Maximum scalingFactor to prevent zooming in so far that no data is - /// visible. - /// - /// TODO: Dynamic max based on data range? - final _maxScalingFactor = 5.0; - - @override - bool onDragStart(Point localPosition) { - if (chart == null) { - return false; - } - - super.onDragStart(localPosition); - - // Save the current scaling factor to make zoom events relative. - _scalingFactor = chart.domainAxis?.viewportScalingFactor; - _isZooming = true; - - return true; - } - - @override - bool onDragUpdate(Point localPosition, double scale) { - // Swipe gestures should be handled by the [PanBehavior]. - if (scale == 1.0) { - _isZooming = false; - return super.onDragUpdate(localPosition, scale); - } - - // No further events in this chain should be handled by [PanBehavior]. - cancelPanning(); - - if (!_isZooming || lastPosition == null || chart == null) { - return false; - } - - // Update the domain axis's viewport scale factor to zoom the chart. - final domainAxis = chart.domainAxis; - - if (domainAxis == null) { - return false; - } - - // This is set during onDragUpdate and NOT onDragStart because we don't yet - // know during onDragStart whether pan/zoom behavior is panning or zooming. - // During zoom in / zoom out, domain tick provider set to return existing - // cached ticks. - domainAxisTickProvider.mode = PanningTickProviderMode.useCachedTicks; - - // Clamp the scale to prevent zooming out beyond the range of the data, or - // zooming in so far that we show nothing useful. - final newScalingFactor = - min(max(_scalingFactor * scale, _minScalingFactor), _maxScalingFactor); - - domainAxis.setViewportSettings( - newScalingFactor, domainAxis.viewportTranslatePx, - drawAreaWidth: chart.drawAreaBounds.width); - - chart.redraw(skipAnimation: true, skipLayout: true); - - return true; - } - - @override - bool onDragEnd( - Point localPosition, double scale, double pixelsPerSec) { - _isZooming = false; - - return super.onDragEnd(localPosition, scale, pixelsPerSec); - } -} diff --git a/web/charts/common/lib/src/chart/common/behavior/zoom/pan_behavior.dart b/web/charts/common/lib/src/chart/common/behavior/zoom/pan_behavior.dart deleted file mode 100644 index aa5800241..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/zoom/pan_behavior.dart +++ /dev/null @@ -1,221 +0,0 @@ -// 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; - -import 'package:meta/meta.dart' show protected; - -import '../../../../common/gesture_listener.dart' show GestureListener; -import '../../../cartesian/axis/axis.dart' show Axis; -import '../../../cartesian/cartesian_chart.dart' show CartesianChart; -import '../../base_chart.dart' show BaseChart; -import '../chart_behavior.dart' show ChartBehavior; -import 'panning_tick_provider.dart'; - -/// Adds domain axis panning support to a chart. -/// -/// Panning is supported by clicking and dragging the mouse for web, or tapping -/// and dragging on the chart for mobile devices. -class PanBehavior implements ChartBehavior { - /// Listens for drag gestures. - GestureListener _listener; - - /// Wrapped domain tick provider for pan and zoom behavior. - PanningTickProvider _domainAxisTickProvider; - - @protected - PanningTickProvider get domainAxisTickProvider => _domainAxisTickProvider; - - @override - String get role => 'Pan'; - - /// The chart to which the behavior is attached. - CartesianChart _chart; - - @protected - CartesianChart get chart => _chart; - - /// Flag which is enabled to indicate that the user is "panning" the chart. - bool _isPanning = false; - - @protected - bool get isPanning => _isPanning; - - /// Last position of the mouse/tap that was used to adjust the scale translate - /// factor. - Point _lastPosition; - - @protected - Point get lastPosition => _lastPosition; - - /// Optional callback that is invoked at the end of panning ([onPanEnd]). - PanningCompletedCallback _panningCompletedCallback; - - set panningCompletedCallback(PanningCompletedCallback callback) { - _panningCompletedCallback = callback; - } - - PanBehavior() { - _listener = GestureListener( - onTapTest: onTapTest, - onDragStart: onDragStart, - onDragUpdate: onDragUpdate, - onDragEnd: onDragEnd); - } - - /// Injects the behavior into a chart. - @override - attachTo(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError( - 'PanBehavior can only be attached to a CartesianChart'); - } - - _chart = chart; - _chart.addGestureListener(_listener); - - // Disable the autoViewport feature to enable panning. - _chart.domainAxis?.autoViewport = false; - - // Wrap domain axis tick provider with the panning behavior one. - _domainAxisTickProvider = - PanningTickProvider(_chart.domainAxis.tickProvider); - _chart.domainAxis.tickProvider = _domainAxisTickProvider; - } - - /// Removes the behavior from a chart. - @override - removeFrom(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError( - 'PanBehavior can only be attached to a CartesianChart'); - } - - _chart = chart; - _chart.removeGestureListener(_listener); - - // Restore the default autoViewport state. - _chart.domainAxis?.autoViewport = true; - - // Restore the original tick providers - _chart.domainAxis.tickProvider = _domainAxisTickProvider.tickProvider; - - _chart = null; - } - - @protected - bool onTapTest(Point localPosition) { - if (_chart == null) { - return false; - } - - return _chart.withinDrawArea(localPosition); - } - - @protected - bool onDragStart(Point localPosition) { - if (_chart == null) { - return false; - } - - onPanStart(); - - _lastPosition = localPosition; - _isPanning = true; - return true; - } - - @protected - bool onDragUpdate(Point localPosition, double scale) { - if (!_isPanning || _lastPosition == null || _chart == null) { - return false; - } - - // Pinch gestures should be handled by the [PanAndZoomBehavior]. - if (scale != 1.0) { - _isPanning = false; - return false; - } - - // Update the domain axis's viewport translate to pan the chart. - final domainAxis = _chart.domainAxis; - - if (domainAxis == null) { - return false; - } - - // This is set during onDragUpdate and NOT onDragStart because we don't yet - // know during onDragStart whether pan/zoom behavior is panning or zooming. - // During panning, domain tick provider set to generate ticks with locked - // steps. - _domainAxisTickProvider.mode = PanningTickProviderMode.stepSizeLocked; - - double domainScalingFactor = domainAxis.viewportScalingFactor; - - double domainChange = - domainAxis.viewportTranslatePx + localPosition.x - _lastPosition.x; - - domainAxis.setViewportSettings(domainScalingFactor, domainChange, - drawAreaWidth: chart.drawAreaBounds.width); - - _lastPosition = localPosition; - - _chart.redraw(skipAnimation: true, skipLayout: true); - return true; - } - - @protected - bool onDragEnd( - Point localPosition, double scale, double pixelsPerSec) { - onPanEnd(); - return true; - } - - @protected - void onPanStart() { - // When panning starts, measure tick provider should not update ticks. - // This is still needed because axis internally updates the tick location - // after the tick provider generates the ticks. If we do not tell the axis - // not to update the location of the measure axes, we get a jittery effect - // as the measure axes location changes ever so slightly during pan/zoom. - _chart.getMeasureAxis().lockAxis = true; - _chart.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId)?.lockAxis = true; - } - - @protected - void onPanEnd() { - cancelPanning(); - - // When panning stops, allow tick provider to update ticks, and then - // request redraw. - _domainAxisTickProvider.mode = PanningTickProviderMode.passThrough; - _chart.getMeasureAxis().lockAxis = false; - _chart.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId)?.lockAxis = - false; - _chart.redraw(); - - if (_panningCompletedCallback != null) { - _panningCompletedCallback(); - } - } - - /// Cancels the handling of any current panning event. - void cancelPanning() { - _isPanning = false; - } -} - -/// Callback for when panning is completed. -typedef PanningCompletedCallback = void Function(); diff --git a/web/charts/common/lib/src/chart/common/behavior/zoom/panning_tick_provider.dart b/web/charts/common/lib/src/chart/common/behavior/zoom/panning_tick_provider.dart deleted file mode 100644 index d346a93fe..000000000 --- a/web/charts/common/lib/src/chart/common/behavior/zoom/panning_tick_provider.dart +++ /dev/null @@ -1,90 +0,0 @@ -// 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 'package:meta/meta.dart' show required; - -import '../../../../common/graphics_factory.dart' show GraphicsFactory; -import '../../../cartesian/axis/axis.dart' show AxisOrientation; -import '../../../cartesian/axis/draw_strategy/tick_draw_strategy.dart' - show TickDrawStrategy; -import '../../../cartesian/axis/scale.dart' show MutableScale; -import '../../../cartesian/axis/tick.dart' show Tick; -import '../../../cartesian/axis/tick_formatter.dart' show TickFormatter; -import '../../../cartesian/axis/tick_provider.dart' show TickProvider, TickHint; -import '../../../common/chart_context.dart' show ChartContext; - -enum PanningTickProviderMode { - /// Return cached ticks. - useCachedTicks, - - /// Request ticks with [TickHint] calculated from cached ticks. - stepSizeLocked, - - /// Request ticks directly from tick provider. - passThrough, -} - -/// Wraps an existing tick provider to be able to return cached ticks during -/// zoom in/out, return ticks calculated with locked step size during panning, -/// or just pass through to the existing tick provider. -class PanningTickProvider implements TickProvider { - final TickProvider tickProvider; - - PanningTickProviderMode _mode = PanningTickProviderMode.passThrough; - - List> _ticks; - - PanningTickProvider(this.tickProvider); - - set mode(PanningTickProviderMode mode) { - _mode = mode; - } - - List> getTicks({ - @required ChartContext context, - @required GraphicsFactory graphicsFactory, - @required MutableScale scale, - @required TickFormatter formatter, - @required Map formatterValueCache, - @required TickDrawStrategy tickDrawStrategy, - @required AxisOrientation orientation, - bool viewportExtensionEnabled = false, - TickHint tickHint, - }) { - if (_mode == PanningTickProviderMode.stepSizeLocked) { - tickHint = TickHint( - _ticks.first.value, - _ticks.last.value, - tickCount: _ticks.length, - ); - } - - if (_mode != PanningTickProviderMode.useCachedTicks) { - _ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: formatterValueCache, - tickDrawStrategy: tickDrawStrategy, - orientation: orientation, - viewportExtensionEnabled: viewportExtensionEnabled, - tickHint: tickHint, - ); - } - - return _ticks; - } -} diff --git a/web/charts/common/lib/src/chart/common/canvas_shapes.dart b/web/charts/common/lib/src/chart/common/canvas_shapes.dart deleted file mode 100644 index 009dc6fc6..000000000 --- a/web/charts/common/lib/src/chart/common/canvas_shapes.dart +++ /dev/null @@ -1,125 +0,0 @@ -// 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, min, max, Point; - -import '../../common/color.dart' show Color; -import 'chart_canvas.dart' show FillPatternType; - -/// A rectangle to be painted by [ChartCanvas]. -class CanvasRect { - final Rectangle bounds; - final List dashPattern; - final Color fill; - final FillPatternType pattern; - final Color stroke; - final double strokeWidthPx; - - CanvasRect(this.bounds, - {this.dashPattern, - this.fill, - this.pattern, - this.stroke, - this.strokeWidthPx}); -} - -/// A stack of [CanvasRect] to be painted by [ChartCanvas]. -class CanvasBarStack { - final List segments; - final int radius; - final int stackedBarPadding; - final bool roundTopLeft; - final bool roundTopRight; - final bool roundBottomLeft; - final bool roundBottomRight; - final Rectangle fullStackRect; - - factory CanvasBarStack(List segments, - {int radius, - int stackedBarPadding, - bool roundTopLeft, - bool roundTopRight, - bool roundBottomLeft, - bool roundBottomRight}) { - final firstBarBounds = segments.first.bounds; - - // Find the rectangle that would represent the full stack of bars. - var left = firstBarBounds.left; - var top = firstBarBounds.top; - var right = firstBarBounds.right; - var bottom = firstBarBounds.bottom; - - for (var barIndex = 1; barIndex < segments.length; barIndex++) { - final bounds = segments[barIndex].bounds; - - left = min(left, bounds.left); - top = min(top, bounds.top); - right = max(right, bounds.right); - bottom = max(bottom, bounds.bottom); - } - - final width = right - left; - final height = bottom - top; - final fullStackRect = Rectangle(left, top, width, height); - - return CanvasBarStack._internal( - segments, - radius: radius, - stackedBarPadding: stackedBarPadding, - roundTopLeft: roundTopLeft, - roundTopRight: roundTopRight, - roundBottomLeft: roundBottomLeft, - roundBottomRight: roundBottomRight, - fullStackRect: fullStackRect, - ); - } - - CanvasBarStack._internal( - this.segments, { - this.radius, - this.stackedBarPadding = 1, - this.roundTopLeft = false, - this.roundTopRight = false, - this.roundBottomLeft = false, - this.roundBottomRight = false, - this.fullStackRect, - }); -} - -/// A list of [CanvasPieSlice]s to be painted by [ChartCanvas]. -class CanvasPie { - final List slices; - Point center; - double radius; - double innerRadius; - - /// Color of separator lines between arcs. - final Color stroke; - - /// Stroke width of separator lines between arcs. - double strokeWidthPx; - - CanvasPie(this.slices, this.center, this.radius, this.innerRadius, - {this.stroke, this.strokeWidthPx = 0.0}); -} - -/// A circle sector to be painted by [ChartCanvas]. -class CanvasPieSlice { - double startAngle; - double endAngle; - Color fill; - - CanvasPieSlice(this.startAngle, this.endAngle, {this.fill}); -} diff --git a/web/charts/common/lib/src/chart/common/chart_canvas.dart b/web/charts/common/lib/src/chart/common/chart_canvas.dart deleted file mode 100644 index 566f21a56..000000000 --- a/web/charts/common/lib/src/chart/common/chart_canvas.dart +++ /dev/null @@ -1,163 +0,0 @@ -// 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 '../../common/color.dart' show Color; -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/text_element.dart' show TextElement; -import 'canvas_shapes.dart' show CanvasBarStack, CanvasPie; - -abstract class ChartCanvas { - /// Get [GraphicsFactory] for creating native graphics elements. - GraphicsFactory get graphicsFactory; - - /// Set the name of the view doing the rendering for debugging purposes, - /// or null when we believe rendering is complete. - set drawingView(String viewName); - - /// Renders a sector of a circle, with an optional hole in the center. - /// - /// [center] The x, y coordinates of the circle's center. - /// [radius] The radius of the circle. - /// [innerRadius] Optional radius of a hole in the center of the circle that - /// should not be filled in as part of the sector. - /// [startAngle] The angle at which the arc starts, measured clockwise from - /// the positive x axis and expressed in radians - /// [endAngle] The angle at which the arc ends, measured clockwise from the - /// positive x axis and expressed in radians. - /// [fill] Fill color for the sector. - /// [stroke] Stroke color of the arc and radius lines. - /// [strokeWidthPx] Stroke width of the arc and radius lines. - void drawCircleSector(Point center, double radius, double innerRadius, - double startAngle, double endAngle, - {Color fill, Color stroke, double strokeWidthPx}); - - /// Renders a simple line. - /// - /// [dashPattern] controls the pattern of dashes and gaps in a line. It is a - /// list of lengths of alternating dashes and gaps. The rendering is similar - /// to stroke-dasharray in SVG path elements. An odd number of values in the - /// pattern will be repeated to derive an even number of values. "1,2,3" is - /// equivalent to "1,2,3,1,2,3." - void drawLine( - {List points, - Rectangle clipBounds, - Color fill, - Color stroke, - bool roundEndCaps, - double strokeWidthPx, - List dashPattern}); - - /// Renders a pie, with an optional hole in the center. - void drawPie(CanvasPie canvasPie); - - /// Renders a simple point. - /// - /// [point] The x, y coordinates of the point. - /// - /// [radius] The radius of the point. - /// - /// [fill] Fill color for the point. - /// - /// [stroke] and [strokeWidthPx] configure the color and thickness of the - /// outer edge of the point. Both must be provided together for a line to - /// appear. - void drawPoint( - {Point point, - double radius, - Color fill, - Color stroke, - double strokeWidthPx}); - - /// Renders a polygon shape described by a set of points. - /// - /// [points] describes the vertices of the polygon. The last point will always - /// be connected to the first point to close the shape. - /// - /// [fill] configures the color inside the polygon. The shape will be - /// transparent if this is not provided. - /// - /// [stroke] and [strokeWidthPx] configure the color and thickness of the - /// edges of the polygon. Both must be provided together for a line to appear. - void drawPolygon( - {List points, - Rectangle clipBounds, - Color fill, - Color stroke, - double strokeWidthPx}); - - /// Renders a simple rectangle. - /// - /// [drawAreaBounds] if specified and if the bounds of the rectangle exceed - /// the draw area bounds on the top, the first x pixels (decided by the native - /// platform) exceeding the draw area will apply a gradient to transparent - /// with anything exceeding the x pixels to be transparent. - void drawRect(Rectangle bounds, - {Color fill, - Color stroke, - double strokeWidthPx, - Rectangle drawAreaBounds}); - - /// Renders a rounded rectangle. - void drawRRect(Rectangle bounds, - {Color fill, - Color stroke, - num radius, - bool roundTopLeft, - bool roundTopRight, - bool roundBottomLeft, - bool roundBottomRight}); - - /// Renders a stack of bars, rounding the last bar in the stack. - /// - /// The first bar of the stack is expected to be the "base" bar. This would - /// be the bottom most bar for a vertically rendered bar. - /// - /// [drawAreaBounds] if specified and if the bounds of the rectangle exceed - /// the draw area bounds on the top, the first x pixels (decided by the native - /// platform) exceeding the draw area will apply a gradient to transparent - /// with anything exceeding the x pixels to be transparent. - void drawBarStack(CanvasBarStack canvasBarStack, - {Rectangle drawAreaBounds}); - - void drawText(TextElement textElement, int offsetX, int offsetY, - {double rotation = 0.0}); - - /// Request the canvas to clip to [clipBounds]. - /// - /// Applies to all operations until [restClipBounds] is called. - void setClipBounds(Rectangle clipBounds); - - /// Restore - void resetClipBounds(); -} - -Color getAnimatedColor(Color previous, Color target, double animationPercent) { - var r = (((target.r - previous.r) * animationPercent) + previous.r).round(); - var g = (((target.g - previous.g) * animationPercent) + previous.g).round(); - var b = (((target.b - previous.b) * animationPercent) + previous.b).round(); - var a = (((target.a - previous.a) * animationPercent) + previous.a).round(); - - return Color(a: a, r: r, g: g, b: b); -} - -/// Defines the pattern for a color fill. -/// -/// * [forwardHatch] defines a pattern of white lines angled up and to the right -/// on top of a bar filled with the fill color. -/// * [solid] defines a simple bar filled with the fill color. This is the -/// default pattern for bars. -enum FillPatternType { forwardHatch, solid } diff --git a/web/charts/common/lib/src/chart/common/chart_context.dart b/web/charts/common/lib/src/chart/common/chart_context.dart deleted file mode 100644 index c382ab81e..000000000 --- a/web/charts/common/lib/src/chart/common/chart_context.dart +++ /dev/null @@ -1,61 +0,0 @@ -// 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 '../../common/date_time_factory.dart'; -import '../../common/rtl_spec.dart' show RTLSpec; -import '../common/behavior/a11y/a11y_node.dart' show A11yNode; - -abstract class ChartContext { - /// Flag indicating whether or not the chart's container was configured in - /// right to left mode. - /// - /// This should be set when the chart is created (or if its container ever - /// gets configured to the other direction setting). - /// - /// Any chart component that needs to know whether the chart axes should be - /// rendered right to left should read [isRtl]. - bool get chartContainerIsRtl; - - /// Configures the behavior of the chart when [chartContainerIsRtl] is true. - RTLSpec get rtlSpec; - - /// Gets whether or not the chart axes should be rendered in right to left - /// mode. - /// - /// This will only be true if the container for the chart component was - /// configured with the rtl direction setting ([chartContainerIsRtl] == true), and the chart's - /// [RTLSpec] is set to reverse the axis direction in rtl mode. - bool get isRtl; - - /// Whether or not the chart will respond to tap events. - /// - /// This will generally be true if there is a behavior attached to the chart - /// that does something with tap events, such as "click to select data." - bool get isTappable; - - double get pixelsPerDp; - - DateTimeFactory get dateTimeFactory; - - void requestRedraw(); - - void requestAnimation(Duration transition); - - void requestPaint(); - - void enableA11yExploreMode(List nodes, {String announcement}); - - void disableA11yExploreMode({String announcement}); -} diff --git a/web/charts/common/lib/src/chart/common/datum_details.dart b/web/charts/common/lib/src/chart/common/datum_details.dart deleted file mode 100644 index 18906e901..000000000 --- a/web/charts/common/lib/src/chart/common/datum_details.dart +++ /dev/null @@ -1,222 +0,0 @@ -// 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; - -import '../../common/color.dart' show Color; -import '../../common/symbol_renderer.dart' show SymbolRenderer; -import 'processed_series.dart' show ImmutableSeries; - -typedef DomainFormatter = String Function(D domain); -typedef MeasureFormatter = String Function(num measure); - -/// Represents processed rendering details for a data point from a series. -class DatumDetails { - final dynamic datum; - - /// The index of the datum in the series. - final int index; - - /// Domain value of [datum]. - final D domain; - - /// Domain lower bound value of [datum]. This may represent an error bound, or - /// a previous domain value. - final D domainLowerBound; - - /// Domain upper bound value of [datum]. This may represent an error bound, or - /// a target domain value. - final D domainUpperBound; - - /// Measure value of [datum]. - final num measure; - - /// Measure lower bound value of [datum]. This may represent an error bound, - /// or a previous value. - final num measureLowerBound; - - /// Measure upper bound value of [datum]. This may represent an error bound, - /// or a target measure value. - final num measureUpperBound; - - /// Measure offset value of [datum]. - final num measureOffset; - - /// Original measure value of [datum]. This may differ from [measure] if a - /// behavior attached to a chart automatically adjusts measure values. - final num rawMeasure; - - /// Original measure lower bound value of [datum]. This may differ from - /// [measureLowerBound] if a behavior attached to a chart automatically - /// adjusts measure values. - final num rawMeasureLowerBound; - - /// Original measure upper bound value of [datum]. This may differ from - /// [measureUpperBound] if a behavior attached to a chart automatically - /// adjusts measure values. - final num rawMeasureUpperBound; - - /// The series the [datum] is from. - final ImmutableSeries series; - - /// The color of this [datum]. - final Color color; - - /// Optional fill color of this [datum]. - /// - /// If this is defined, then [color] will be used as a stroke color. - /// Otherwise, [color] will be used for the fill color. - final Color fillColor; - - /// Optional area color of this [datum]. - /// - /// This color is used for supplemental information on the series, such as - /// confidence intervals or area skirts. If not provided, then some variation - /// of the main [color] will be used (e.g. 10% opacity). - final Color areaColor; - - /// Optional dash pattern of this [datum]. - final List dashPattern; - - /// The chart position of the (domain, measure) for the [datum] from a - /// renderer. - final Point chartPosition; - - /// The chart position of the (domainLowerBound, measureLowerBound) for the - /// [datum] from a renderer. - final Point chartPositionLower; - - /// The chart position of the (domainUpperBound, measureUpperBound) for the - /// [datum] from a renderer. - final Point chartPositionUpper; - - /// Distance of [domain] from a given (x, y) coordinate. - final double domainDistance; - - /// Distance of [measure] from a given (x, y) coordinate. - final double measureDistance; - - /// Relative Cartesian distance of ([domain], [measure]) from a given (x, y) - /// coordinate. - final double relativeDistance; - - /// The radius of this [datum]. - final double radiusPx; - - /// Renderer used to draw the shape of this datum. - /// - /// This is primarily used for point shapes on line and scatter plot charts. - final SymbolRenderer symbolRenderer; - - /// The stroke width of this [datum]. - final double strokeWidthPx; - - /// Optional formatter for [domain]. - DomainFormatter domainFormatter; - - /// Optional formatter for [measure]. - MeasureFormatter measureFormatter; - - DatumDetails( - {this.datum, - this.index, - this.domain, - this.domainLowerBound, - this.domainUpperBound, - this.measure, - this.measureLowerBound, - this.measureUpperBound, - this.measureOffset, - this.rawMeasure, - this.rawMeasureLowerBound, - this.rawMeasureUpperBound, - this.series, - this.color, - this.fillColor, - this.areaColor, - this.dashPattern, - this.chartPosition, - this.chartPositionLower, - this.chartPositionUpper, - this.domainDistance, - this.measureDistance, - this.relativeDistance, - this.radiusPx, - this.symbolRenderer, - this.strokeWidthPx}); - - factory DatumDetails.from(DatumDetails other, - {D datum, - int index, - D domain, - D domainLowerBound, - D domainUpperBound, - num measure, - num measureLowerBound, - num measureUpperBound, - num measureOffset, - num rawMeasure, - num rawMeasureLowerBound, - num rawMeasureUpperBound, - ImmutableSeries series, - Color color, - Color fillColor, - Color areaColor, - List dashPattern, - Point chartPosition, - Point chartPositionLower, - Point chartPositionUpper, - double domainDistance, - double measureDistance, - double radiusPx, - SymbolRenderer symbolRenderer, - double strokeWidthPx}) { - return DatumDetails( - datum: datum ?? other.datum, - index: index ?? other.index, - domain: domain ?? other.domain, - domainLowerBound: domainLowerBound ?? other.domainLowerBound, - domainUpperBound: domainUpperBound ?? other.domainUpperBound, - measure: measure ?? other.measure, - measureLowerBound: measureLowerBound ?? other.measureLowerBound, - measureUpperBound: measureUpperBound ?? other.measureUpperBound, - measureOffset: measureOffset ?? other.measureOffset, - rawMeasure: rawMeasure ?? other.rawMeasure, - rawMeasureLowerBound: - rawMeasureLowerBound ?? other.rawMeasureLowerBound, - rawMeasureUpperBound: - rawMeasureUpperBound ?? other.rawMeasureUpperBound, - series: series ?? other.series, - color: color ?? other.color, - fillColor: fillColor ?? other.fillColor, - areaColor: areaColor ?? other.areaColor, - dashPattern: dashPattern ?? other.dashPattern, - chartPosition: chartPosition ?? other.chartPosition, - chartPositionLower: chartPositionLower ?? other.chartPositionLower, - chartPositionUpper: chartPositionUpper ?? other.chartPositionUpper, - domainDistance: domainDistance ?? other.domainDistance, - measureDistance: measureDistance ?? other.measureDistance, - radiusPx: radiusPx ?? other.radiusPx, - symbolRenderer: symbolRenderer ?? other.symbolRenderer, - strokeWidthPx: radiusPx ?? other.strokeWidthPx); - } - - String get formattedDomain => - (domainFormatter != null) ? domainFormatter(domain) : domain.toString(); - - String get formattedMeasure => (measureFormatter != null) - ? measureFormatter(measure) - : measure.toString(); -} diff --git a/web/charts/common/lib/src/chart/common/processed_series.dart b/web/charts/common/lib/src/chart/common/processed_series.dart deleted file mode 100644 index f36826da1..000000000 --- a/web/charts/common/lib/src/chart/common/processed_series.dart +++ /dev/null @@ -1,232 +0,0 @@ -// 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 '../../common/color.dart' show Color; -import '../../data/series.dart' - show AccessorFn, Series, SeriesAttributes, AttributeKey; -import '../cartesian/axis/axis.dart' show Axis; -import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../common/chart_canvas.dart' show FillPatternType; - -class MutableSeries extends ImmutableSeries { - final String id; - String displayName; - String seriesCategory; - bool overlaySeries; - int seriesIndex; - - /// Sum of the measure values for the series. - num seriesMeasureTotal; - - List data; - - AccessorFn keyFn; - - AccessorFn domainFn; - AccessorFn domainLowerBoundFn; - AccessorFn domainUpperBoundFn; - AccessorFn measureFn; - AccessorFn measureLowerBoundFn; - AccessorFn measureUpperBoundFn; - AccessorFn measureOffsetFn; - AccessorFn rawMeasureFn; - AccessorFn rawMeasureLowerBoundFn; - AccessorFn rawMeasureUpperBoundFn; - - AccessorFn areaColorFn; - AccessorFn colorFn; - AccessorFn> dashPatternFn; - AccessorFn fillColorFn; - AccessorFn fillPatternFn; - AccessorFn radiusPxFn; - AccessorFn strokeWidthPxFn; - AccessorFn labelAccessorFn; - AccessorFn insideLabelStyleAccessorFn; - AccessorFn outsideLabelStyleAccessorFn; - - final _attrs = SeriesAttributes(); - - Axis measureAxis; - Axis domainAxis; - - MutableSeries(Series series) : this.id = series.id { - displayName = series.displayName ?? series.id; - seriesCategory = series.seriesCategory; - overlaySeries = series.overlaySeries; - - data = series.data; - keyFn = series.keyFn; - - domainFn = series.domainFn; - domainLowerBoundFn = series.domainLowerBoundFn; - domainUpperBoundFn = series.domainUpperBoundFn; - - measureFn = series.measureFn; - measureLowerBoundFn = series.measureLowerBoundFn; - measureUpperBoundFn = series.measureUpperBoundFn; - measureOffsetFn = series.measureOffsetFn; - - // Save the original measure functions in case they get replaced later. - rawMeasureFn = series.measureFn; - rawMeasureLowerBoundFn = series.measureLowerBoundFn; - rawMeasureUpperBoundFn = series.measureUpperBoundFn; - - // Pre-compute the sum of the measure values to make it available on demand. - seriesMeasureTotal = 0; - for (int i = 0; i < data.length; i++) { - final measure = measureFn(i); - if (measure != null) { - seriesMeasureTotal += measure; - } - } - - areaColorFn = series.areaColorFn; - colorFn = series.colorFn; - dashPatternFn = series.dashPatternFn; - fillColorFn = series.fillColorFn; - fillPatternFn = series.fillPatternFn; - labelAccessorFn = series.labelAccessorFn ?? (i) => domainFn(i).toString(); - insideLabelStyleAccessorFn = series.insideLabelStyleAccessorFn; - outsideLabelStyleAccessorFn = series.outsideLabelStyleAccessorFn; - - radiusPxFn = series.radiusPxFn; - strokeWidthPxFn = series.strokeWidthPxFn; - - _attrs.mergeFrom(series.attributes); - } - - MutableSeries.clone(MutableSeries other) : this.id = other.id { - displayName = other.displayName; - seriesCategory = other.seriesCategory; - overlaySeries = other.overlaySeries; - seriesIndex = other.seriesIndex; - - data = other.data; - keyFn = other.keyFn; - - domainFn = other.domainFn; - domainLowerBoundFn = other.domainLowerBoundFn; - domainUpperBoundFn = other.domainUpperBoundFn; - - measureFn = other.measureFn; - measureLowerBoundFn = other.measureLowerBoundFn; - measureUpperBoundFn = other.measureUpperBoundFn; - measureOffsetFn = other.measureOffsetFn; - - rawMeasureFn = other.rawMeasureFn; - rawMeasureLowerBoundFn = other.rawMeasureLowerBoundFn; - rawMeasureUpperBoundFn = other.rawMeasureUpperBoundFn; - - seriesMeasureTotal = other.seriesMeasureTotal; - - areaColorFn = other.areaColorFn; - colorFn = other.colorFn; - dashPatternFn = other.dashPatternFn; - fillColorFn = other.fillColorFn; - fillPatternFn = other.fillPatternFn; - labelAccessorFn = other.labelAccessorFn; - insideLabelStyleAccessorFn = other.insideLabelStyleAccessorFn; - outsideLabelStyleAccessorFn = other.outsideLabelStyleAccessorFn; - radiusPxFn = other.radiusPxFn; - strokeWidthPxFn = other.strokeWidthPxFn; - - _attrs.mergeFrom(other._attrs); - measureAxis = other.measureAxis; - domainAxis = other.domainAxis; - } - - void setAttr(AttributeKey key, R value) { - this._attrs.setAttr(key, value); - } - - R getAttr(AttributeKey key) { - return this._attrs.getAttr(key); - } - - bool operator ==(Object other) => - other is MutableSeries && data == other.data && id == other.id; - - @override - int get hashCode => data.hashCode * 31 + id.hashCode; -} - -abstract class ImmutableSeries { - String get id; - - String get displayName; - - String get seriesCategory; - - bool get overlaySeries; - - int get seriesIndex; - - /// Sum of the measure values for the series. - num get seriesMeasureTotal; - - List get data; - - /// [keyFn] defines a globally unique identifier for each datum. - /// - /// The key for each datum is used during chart animation to smoothly - /// transition data still in the series to its new state. - /// - /// Note: This is currently an optional function that is not fully used by all - /// series renderers yet. - AccessorFn keyFn; - - AccessorFn get domainFn; - - AccessorFn get domainLowerBoundFn; - - AccessorFn get domainUpperBoundFn; - - AccessorFn get measureFn; - - AccessorFn get measureLowerBoundFn; - - AccessorFn get measureUpperBoundFn; - - AccessorFn get measureOffsetFn; - - AccessorFn get rawMeasureFn; - - AccessorFn get rawMeasureLowerBoundFn; - - AccessorFn get rawMeasureUpperBoundFn; - - AccessorFn get areaColorFn; - - AccessorFn get colorFn; - - AccessorFn> get dashPatternFn; - - AccessorFn get fillColorFn; - - AccessorFn get fillPatternFn; - - AccessorFn get labelAccessorFn; - - AccessorFn insideLabelStyleAccessorFn; - AccessorFn outsideLabelStyleAccessorFn; - - AccessorFn get radiusPxFn; - - AccessorFn get strokeWidthPxFn; - - void setAttr(AttributeKey key, R value); - - R getAttr(AttributeKey key); -} diff --git a/web/charts/common/lib/src/chart/common/selection_model/selection_model.dart b/web/charts/common/lib/src/chart/common/selection_model/selection_model.dart deleted file mode 100644 index addd32b7b..000000000 --- a/web/charts/common/lib/src/chart/common/selection_model/selection_model.dart +++ /dev/null @@ -1,231 +0,0 @@ -// 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 'package:collection/collection.dart' show ListEquality; - -import '../processed_series.dart' show ImmutableSeries; -import '../series_datum.dart' show SeriesDatum, SeriesDatumConfig; - -/// Holds the state of interaction or selection for the chart to coordinate -/// between various event sources and things that wish to act upon the selection -/// state (highlight, drill, etc). -/// -/// There is one instance per interaction type (ex: info, action) with each -/// maintaining their own state. Info is typically used to update a hover/touch -/// card while action is used in case of a secondary selection/action. -/// -/// The series selection state is kept separate from datum selection state to -/// allow more complex highlighting. For example: a Hovercard that shows entries -/// for each datum for a given domain/time, but highlights the closest entry to -/// match up with highlighting/bolding of the line and legend. -class SelectionModel { - var _selectedDatum = >[]; - var _selectedSeries = >[]; - - /// Create selection model with the desired selection. - SelectionModel( - {List> selectedData, - List> selectedSeries}) { - if (selectedData != null) { - _selectedDatum = selectedData; - } - if (selectedSeries != null) { - _selectedSeries = selectedSeries; - } - } - - /// Create a deep copy of the selection model. - SelectionModel.fromOther(SelectionModel other) { - _selectedDatum = List.from(other._selectedDatum); - _selectedSeries = List.from(other._selectedSeries); - } - - /// Create selection model from configuration. - SelectionModel.fromConfig(List selectedDataConfig, - List selectedSeriesConfig, List> seriesList) { - final selectedDataMap = >{}; - - if (selectedDataConfig != null) { - for (SeriesDatumConfig config in selectedDataConfig) { - selectedDataMap[config.seriesId] ??= []; - selectedDataMap[config.seriesId].add(config.domainValue); - } - - // Add to list of selected series. - _selectedSeries.addAll(seriesList - .where((series) => selectedDataMap.keys.contains(series.id))); - - // Add to list of selected data. - for (ImmutableSeries series in seriesList) { - if (selectedDataMap.containsKey(series.id)) { - final domainFn = series.domainFn; - - for (var i = 0; i < series.data.length; i++) { - final datum = series.data[i]; - - if (selectedDataMap[series.id].contains(domainFn(i))) { - _selectedDatum.add(SeriesDatum(series, datum)); - } - } - } - } - } - - // Add to list of selected series, if it does not already exist. - if (selectedSeriesConfig != null) { - final remainingSeriesToAdd = selectedSeriesConfig - .where((seriesId) => !selectedSeries.contains(seriesId)) - .toList(); - - _selectedSeries.addAll(seriesList - .where((series) => remainingSeriesToAdd.contains(series.id))); - } - } - - /// Returns true if this [SelectionModel] has a selected datum. - bool get hasDatumSelection => _selectedDatum.isNotEmpty; - - bool isDatumSelected(ImmutableSeries series, int index) { - final datum = index == null ? null : series.data[index]; - return _selectedDatum.contains(SeriesDatum(series, datum)); - } - - /// Returns the selected [SeriesDatum] for this [SelectionModel]. - /// - /// This is empty by default. - List> get selectedDatum => List.unmodifiable(_selectedDatum); - - /// Returns true if this [SelectionModel] has a selected series. - bool get hasSeriesSelection => _selectedSeries.isNotEmpty; - - /// Returns the selected [ImmutableSeries] for this [SelectionModel]. - /// - /// This is empty by default. - List> get selectedSeries => - List.unmodifiable(_selectedSeries); - - /// Returns true if this [SelectionModel] has a selected datum or series. - bool get hasAnySelection => - _selectedDatum.isNotEmpty || selectedSeries.isNotEmpty; - - @override - bool operator ==(Object other) { - return other is SelectionModel && - ListEquality().equals(_selectedDatum, other.selectedDatum) && - ListEquality().equals(_selectedSeries, other.selectedSeries); - } - - @override - int get hashCode { - int hashcode = ListEquality().hash(_selectedDatum); - hashcode = hashcode * 37 + ListEquality().hash(_selectedSeries); - return hashcode; - } -} - -/// A [SelectionModel] that can be updated. -/// -/// This model will notify listeners subscribed to this model when the selection -/// is modified. -class MutableSelectionModel extends SelectionModel { - final _changedListeners = >[]; - final _updatedListeners = >[]; - - /// When set to true, prevents the model from being updated. - bool locked = false; - - /// Clears the selection state. - bool clearSelection({bool notifyListeners = true}) { - return updateSelection([], [], notifyListeners: notifyListeners); - } - - /// Updates the selection state. If mouse driven, [datumSelection] should be - /// ordered by distance from mouse, closest first. - bool updateSelection( - List> datumSelection, List> seriesList, - {bool notifyListeners = true}) { - if (locked) { - return false; - } - - final origSelectedDatum = _selectedDatum; - final origSelectedSeries = _selectedSeries; - - _selectedDatum = datumSelection; - _selectedSeries = seriesList; - - // Provide a copy, so listeners get an immutable model. - final copyOfSelectionModel = SelectionModel.fromOther(this); - _updatedListeners.forEach((listener) => listener(copyOfSelectionModel)); - - final changed = !ListEquality().equals(origSelectedDatum, _selectedDatum) || - !ListEquality().equals(origSelectedSeries, _selectedSeries); - if (notifyListeners && changed) { - _changedListeners.forEach((listener) => listener(copyOfSelectionModel)); - } - return changed; - } - - /// Add a listener to be notified when this [SelectionModel] changes. - /// - /// Note: the listener will not be triggered if [updateSelection] is called - /// resulting in the same selection state. - void addSelectionChangedListener(SelectionModelListener listener) { - _changedListeners.add(listener); - } - - /// Remove listener from being notified when this [SelectionModel] changes. - void removeSelectionChangedListener(SelectionModelListener listener) { - _changedListeners.remove(listener); - } - - /// Add a listener to be notified when [updateSelection] is called, even if - /// the selection state is the same. - /// - /// This is necessary in order to support programmatic selections in Flutter. - /// Due to the way widgets are constructed in Flutter, there currently isn't - /// a way for users to programmatically specify the selection. In order to - /// provide this support, the users who subscribe to the selection updated - /// event can keep a copy of the selection model and also decide if it should - /// be overwritten. - void addSelectionUpdatedListener(SelectionModelListener listener) { - _updatedListeners.add(listener); - } - - /// Remove listener from being notified when [updateSelection] is called. - void removeSelectionUpdatedListener(SelectionModelListener listener) { - _updatedListeners.remove(listener); - } - - /// Remove all listeners. - void clearAllListeners() { - _changedListeners.clear(); - _updatedListeners.clear(); - } -} - -/// Callback for SelectionModel. It is triggered when the selection state -/// changes. -typedef SelectionModelListener = Function(SelectionModel model); - -enum SelectionModelType { - /// Typical Hover or Details event for viewing the details of the selected - /// items. - info, - - /// Typical Selection, Drill or Input event likely updating some external - /// content. - action, -} diff --git a/web/charts/common/lib/src/chart/common/series_datum.dart b/web/charts/common/lib/src/chart/common/series_datum.dart deleted file mode 100644 index 12185b9ff..000000000 --- a/web/charts/common/lib/src/chart/common/series_datum.dart +++ /dev/null @@ -1,58 +0,0 @@ -// 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 'processed_series.dart' show ImmutableSeries; - -/// Stores datum and the series the datum originated. -class SeriesDatum { - final ImmutableSeries series; - final dynamic datum; - int _index; - - SeriesDatum(this.series, this.datum) { - _index = datum == null ? null : series.data.indexOf(datum); - } - - int get index => _index; - - @override - bool operator ==(Object other) => - other is SeriesDatum && other.series == series && other.datum == datum; - - @override - int get hashCode => series.hashCode * 31 + datum.hashCode; -} - -/// Represents a series datum based on series id and datum index. -class SeriesDatumConfig { - final String seriesId; - final D domainValue; - - SeriesDatumConfig(this.seriesId, this.domainValue); - - @override - bool operator ==(Object other) { - return other is SeriesDatumConfig && - seriesId == other.seriesId && - domainValue == other.domainValue; - } - - @override - int get hashCode { - int hashcode = seriesId.hashCode; - hashcode = hashcode * 37 + domainValue.hashCode; - return hashcode; - } -} diff --git a/web/charts/common/lib/src/chart/common/series_renderer.dart b/web/charts/common/lib/src/chart/common/series_renderer.dart deleted file mode 100644 index 95f9209bb..000000000 --- a/web/charts/common/lib/src/chart/common/series_renderer.dart +++ /dev/null @@ -1,387 +0,0 @@ -// 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'; - -import '../../common/color.dart' show Color; -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/style/style_factory.dart' show StyleFactory; -import '../../common/symbol_renderer.dart' show SymbolRenderer; -import '../../data/series.dart' show AttributeKey; -import '../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPositionOrder, - ViewMeasuredSizes; -import 'base_chart.dart' show BaseChart; -import 'chart_canvas.dart' show ChartCanvas; -import 'datum_details.dart' show DatumDetails; -import 'processed_series.dart' show ImmutableSeries, MutableSeries; -import 'series_datum.dart' show SeriesDatum; - -/// Unique identifier used to associate custom series renderers on a chart with -/// one or more series of data. -/// -/// [rendererIdKey] can be added as an attribute to user-defined [Series] -/// objects. -const AttributeKey rendererIdKey = - AttributeKey('SeriesRenderer.rendererId'); - -const AttributeKey rendererKey = - AttributeKey('SeriesRenderer.renderer'); - -/// A series renderer draws one or more series of data onto a chart canvas. -abstract class SeriesRenderer extends LayoutView { - static const defaultRendererId = 'default'; - - /// Symbol renderer for this renderer. - /// - /// The default is set natively by the platform. This is because in Flutter, - /// the [SymbolRenderer] has to be a Flutter wrapped version to support - /// building widget based symbols. - SymbolRenderer get symbolRenderer; - - set symbolRenderer(SymbolRenderer symbolRenderer); - - /// Unique identifier for this renderer. Any [Series] on a chart with a - /// matching [rendererIdKey] will be drawn by this renderer. - String get rendererId; - - set rendererId(String rendererId); - - /// Handles any setup of the renderer that needs to be deferred until it is - /// attached to a chart. - void onAttach(BaseChart chart); - - /// Handles any clean-up of the renderer that needs to be performed when it is - /// detached from a chart. - void onDetach(BaseChart chart); - - /// Performs basic configuration for the series, before it is pre-processed. - /// - /// Typically, a series renderer should assign color mapping functions to - /// series that do not have them. - void configureSeries(List> seriesList); - - /// Pre-calculates some details for the series that will be needed later - /// during the drawing phase. - void preprocessSeries(List> seriesList); - - /// Adds the domain values for the given series to the chart's domain axis. - void configureDomainAxes(List> seriesList); - - /// Adds the measure values for the given series to the chart's measure axes. - void configureMeasureAxes(List> seriesList); - - /// Generates rendering data needed to paint the data on the chart. - /// - /// This is called during the post layout phase of the chart draw cycle. - void update(List> seriesList, bool isAnimating); - - /// Renders the series data on the canvas, using the data generated during the - /// [update] call. - void paint(ChartCanvas canvas, double animationPercent); - - /// Gets a list the data from each series that is closest to a given point. - /// - /// [chartPoint] represents a point in the chart, such as a point that was - /// clicked/tapped on by a user. - /// - /// [byDomain] specifies whether the nearest data should be defined by domain - /// distance, or relative Cartesian distance. - /// - /// [boundsOverride] optionally specifies a bounding box for the selection - /// event. If specified, then no data should be returned if [chartPoint] lies - /// outside the box. If not specified, then each series renderer on the chart - /// will use its own component bounds for filtering out selection events - /// (usually the chart draw area). - List> getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride); - - /// Get an expanded set of processed [DatumDetails] for a given [SeriesDatum]. - /// - /// This is typically called by chart behaviors that need to get full details - /// on selected data. - DatumDetails getDetailsForSeriesDatum(SeriesDatum seriesDatum); - - /// Adds chart position data to [details]. - /// - /// This is a helper function intended to be called from - /// [getDetailsForSeriesDatum]. Every concrete [SeriesRenderer] needs to - /// implement custom logic for setting location data. - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum); -} - -/// Concrete base class for [SeriesRenderer]s that implements common -/// functionality. -abstract class BaseSeriesRenderer implements SeriesRenderer { - final LayoutViewConfig layoutConfig; - - String rendererId; - - SymbolRenderer symbolRenderer; - - Rectangle _drawAreaBounds; - - Rectangle get drawBounds => _drawAreaBounds; - - GraphicsFactory graphicsFactory; - - BaseSeriesRenderer({ - @required this.rendererId, - @required int layoutPaintOrder, - this.symbolRenderer, - }) : this.layoutConfig = LayoutViewConfig( - paintOrder: layoutPaintOrder, - position: LayoutPosition.DrawArea, - positionOrder: LayoutViewPositionOrder.drawArea); - - @override - void onAttach(BaseChart chart) {} - - @override - void onDetach(BaseChart chart) {} - - /// Assigns colors to series that are missing their colorFn. - /// - /// [emptyCategoryUsesSinglePalette] Flag indicating whether having all - /// series with no categories will use the same or separate palettes. - /// Setting it to true uses various Blues for each series. - /// Setting it to false used different palettes (ie: s1 uses Blue500, - /// s2 uses Red500), - @protected - assignMissingColors(Iterable> seriesList, - {@required bool emptyCategoryUsesSinglePalette}) { - const defaultCategory = '__default__'; - - // Count up the number of missing series per category, keeping a max across - // categories. - final missingColorCountPerCategory = {}; - int maxMissing = 0; - bool hasSpecifiedCategory = false; - - seriesList.forEach((series) { - if (series.colorFn == null) { - // If there is no category, give it a default category to match logic. - String category = series.seriesCategory; - if (category == null) { - category = defaultCategory; - } else { - hasSpecifiedCategory = true; - } - - // Increment the missing counts for the category. - final missingCnt = (missingColorCountPerCategory[category] ?? 0) + 1; - missingColorCountPerCategory[category] = missingCnt; - maxMissing = max(maxMissing, missingCnt); - } - }); - - if (maxMissing > 0) { - // Special handling of only series with empty categories when we want - // to use different palettes. - if (!emptyCategoryUsesSinglePalette && !hasSpecifiedCategory) { - final palettes = StyleFactory.style.getOrderedPalettes(maxMissing); - int index = 0; - seriesList.forEach((series) { - if (series.colorFn == null) { - final color = palettes[index % palettes.length].shadeDefault; - index++; - series.colorFn = (_) => color; - } - }); - return; - } - - // Get a list of palettes to use given the number of categories we've - // seen. One palette per category (but might need to repeat). - final colorPalettes = StyleFactory.style - .getOrderedPalettes(missingColorCountPerCategory.length); - - // Create a map of Color palettes for each category. Each Palette uses - // the max for any category to ensure that the gradients look appropriate. - final colorsByCategory = >{}; - int index = 0; - missingColorCountPerCategory.keys.forEach((category) { - colorsByCategory[category] = - colorPalettes[index % colorPalettes.length].makeShades(maxMissing); - index++; - - // Reset the count so we can use it to count as we set the colorFn. - missingColorCountPerCategory[category] = 0; - }); - - seriesList.forEach((series) { - if (series.colorFn == null) { - final category = series.seriesCategory ?? defaultCategory; - - // Get the current index into the color list. - final colorIndex = missingColorCountPerCategory[category]; - missingColorCountPerCategory[category] = colorIndex + 1; - - final color = colorsByCategory[category][colorIndex]; - series.colorFn = (_) => color; - } - - // Fill color defaults to the series color if no accessor is provided. - series.fillColorFn ??= (index) => series.colorFn(index); - }); - } else { - seriesList.forEach((series) { - // Fill color defaults to the series color if no accessor is provided. - series.fillColorFn ??= (index) => series.colorFn(index); - }); - } - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - return null; - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - this._drawAreaBounds = drawAreaBounds; - } - - @override - Rectangle get componentBounds => this._drawAreaBounds; - - @override - bool get isSeriesRenderer => true; - - @override - void configureSeries(List> seriesList) {} - - @override - void preprocessSeries(List> seriesList) {} - - @override - void configureDomainAxes(List> seriesList) {} - - @override - void configureMeasureAxes(List> seriesList) {} - - @override - DatumDetails getDetailsForSeriesDatum(SeriesDatum seriesDatum) { - // Generate details relevant to every type of series renderer. Position - // details are left as an exercise for every renderer that extends this - // class. - final series = seriesDatum.series; - final index = seriesDatum.index; - final domainFn = series.domainFn; - final domainLowerBoundFn = series.domainLowerBoundFn; - final domainUpperBoundFn = series.domainUpperBoundFn; - final measureFn = series.measureFn; - final measureLowerBoundFn = series.measureLowerBoundFn; - final measureUpperBoundFn = series.measureUpperBoundFn; - final measureOffsetFn = series.measureOffsetFn; - final rawMeasureFn = series.rawMeasureFn; - final rawMeasureLowerBoundFn = series.rawMeasureLowerBoundFn; - final rawMeasureUpperBoundFn = series.rawMeasureUpperBoundFn; - final colorFn = series.colorFn; - final areaColorFn = series.areaColorFn ?? colorFn; - final fillColorFn = series.fillColorFn ?? colorFn; - final radiusPxFn = series.radiusPxFn; - final strokeWidthPxFn = series.strokeWidthPxFn; - - final domainValue = domainFn(index); - final domainLowerBoundValue = - domainLowerBoundFn != null ? domainLowerBoundFn(index) : null; - final domainUpperBoundValue = - domainUpperBoundFn != null ? domainUpperBoundFn(index) : null; - - final measureValue = measureFn(index); - final measureLowerBoundValue = - measureLowerBoundFn != null ? measureLowerBoundFn(index) : null; - final measureUpperBoundValue = - measureUpperBoundFn != null ? measureUpperBoundFn(index) : null; - final measureOffsetValue = - measureOffsetFn != null ? measureOffsetFn(index) : null; - - final rawMeasureValue = rawMeasureFn(index); - final rawMeasureLowerBoundValue = - rawMeasureLowerBoundFn != null ? rawMeasureLowerBoundFn(index) : null; - final rawMeasureUpperBoundValue = - rawMeasureUpperBoundFn != null ? rawMeasureUpperBoundFn(index) : null; - - final color = colorFn(index); - - // Fill color is an optional override for color. Make sure we get a value if - // the series doesn't define anything specific. - var fillColor = fillColorFn(index); - fillColor ??= color; - - // Area color is entirely optional. - final areaColor = areaColorFn(index); - - var radiusPx = radiusPxFn != null ? radiusPxFn(index) : null; - radiusPx = radiusPx?.toDouble(); - - var strokeWidthPx = strokeWidthPxFn != null ? strokeWidthPxFn(index) : null; - strokeWidthPx = strokeWidthPx?.toDouble(); - - final details = DatumDetails( - datum: seriesDatum.datum, - index: seriesDatum.index, - domain: domainValue, - domainLowerBound: domainLowerBoundValue, - domainUpperBound: domainUpperBoundValue, - measure: measureValue, - measureLowerBound: measureLowerBoundValue, - measureUpperBound: measureUpperBoundValue, - measureOffset: measureOffsetValue, - rawMeasure: rawMeasureValue, - rawMeasureLowerBound: rawMeasureLowerBoundValue, - rawMeasureUpperBound: rawMeasureUpperBoundValue, - series: series, - color: color, - fillColor: fillColor, - areaColor: areaColor, - radiusPx: radiusPx, - strokeWidthPx: strokeWidthPx); - - // chartPosition depends on the shape of the rendered elements, and must be - // added by concrete [SeriesRenderer] classes. - return addPositionToDetailsForSeriesDatum(details, seriesDatum); - } - - /// Returns true of [chartPoint] is within the component bounds for this - /// renderer. - /// - /// [chartPoint] a point to test. - /// - /// [bounds] optional override for component bounds. If this is passed, then - /// we will check whether the point is within these bounds instead of the - /// component bounds. - bool isPointWithinBounds(Point chartPoint, Rectangle bounds) { - // Was it even in the drawArea? - if (bounds != null) { - if (!bounds.containsPoint(chartPoint)) { - return false; - } - } else if (componentBounds == null || - !componentBounds.containsPoint(chartPoint)) { - return false; - } - - return true; - } -} diff --git a/web/charts/common/lib/src/chart/common/series_renderer_config.dart b/web/charts/common/lib/src/chart/common/series_renderer_config.dart deleted file mode 100644 index 87bf19701..000000000 --- a/web/charts/common/lib/src/chart/common/series_renderer_config.dart +++ /dev/null @@ -1,40 +0,0 @@ -// 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 '../../common/symbol_renderer.dart'; -import '../../common/typed_registry.dart'; -import 'series_renderer.dart' show SeriesRenderer; - -/// Interface for series renderer configuration. -abstract class SeriesRendererConfig { - /// Stores typed renderer attributes - /// - /// This is useful for storing attributes that is used on the native platform. - /// Such as the SymbolRenderer that is associated with each renderer but is - /// a native builder since legend is built natively. - RendererAttributes get rendererAttributes; - - String get customRendererId; - - SymbolRenderer get symbolRenderer; - - SeriesRenderer build(); -} - -class RendererAttributeKey extends TypedKey { - const RendererAttributeKey(String uniqueKey) : super(uniqueKey); -} - -class RendererAttributes extends TypedRegistry {} diff --git a/web/charts/common/lib/src/chart/common/unitconverter/identity_converter.dart b/web/charts/common/lib/src/chart/common/unitconverter/identity_converter.dart deleted file mode 100644 index 599d77237..000000000 --- a/web/charts/common/lib/src/chart/common/unitconverter/identity_converter.dart +++ /dev/null @@ -1,27 +0,0 @@ -// 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 'unit_converter.dart' show UnitConverter; - -/// A No op unit converter. -class IdentityConverter implements UnitConverter { - const IdentityConverter(); - - @override - convert(U value) => value; - - @override - invert(U value) => value; -} diff --git a/web/charts/common/lib/src/chart/common/unitconverter/unit_converter.dart b/web/charts/common/lib/src/chart/common/unitconverter/unit_converter.dart deleted file mode 100644 index e1317f20f..000000000 --- a/web/charts/common/lib/src/chart/common/unitconverter/unit_converter.dart +++ /dev/null @@ -1,26 +0,0 @@ -// 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. - -/// Converts a num value in the 'from' unit to a num value in the 'to' unit. -/// -/// [F] Type of the value in the 'from' units. -/// [T] Type of the value in 'to' units. -abstract class UnitConverter { - /// Converts 'from' unit value to the 'to' unit value. - T convert(F value); - - /// Converts 'to' unit value back to the 'from' unit value. - F invert(T value); -} diff --git a/web/charts/common/lib/src/chart/layout/layout_config.dart b/web/charts/common/lib/src/chart/layout/layout_config.dart deleted file mode 100644 index 1ef635e39..000000000 --- a/web/charts/common/lib/src/chart/layout/layout_config.dart +++ /dev/null @@ -1,121 +0,0 @@ -// 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 = 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 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 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 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; - } - } -} diff --git a/web/charts/common/lib/src/chart/layout/layout_manager.dart b/web/charts/common/lib/src/chart/layout/layout_manager.dart deleted file mode 100644 index 0ea88cd10..000000000 --- a/web/charts/common/lib/src/chart/layout/layout_manager.dart +++ /dev/null @@ -1,70 +0,0 @@ -// 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 get drawAreaBounds; - - /// Returns the combined bounds of the drawArea, and all components that - /// function as series draw areas. Must be called after layout(). - Rectangle 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 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 get paintOrderedViews; - - /// Return the child views in the order that they should be positioned within - /// chart margins. - List get positionOrderedViews; -} diff --git a/web/charts/common/lib/src/chart/layout/layout_manager_impl.dart b/web/charts/common/lib/src/chart/layout/layout_manager_impl.dart deleted file mode 100644 index 863243b16..000000000 --- a/web/charts/common/lib/src/chart/layout/layout_manager_impl.dart +++ /dev/null @@ -1,366 +0,0 @@ -// 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 = []; - - /// List of views in the order they should be drawn on the canvas. - /// - /// First element is painted first. - List _paintOrderedViews; - - /// List of vies in the order they should be positioned in a chart margin. - /// - /// First element is closest to the draw area. - List _positionOrderedViews; - - _MeasuredSizes _measurements; - - Rectangle _drawAreaBounds; - bool _drawAreaBoundsOutdated = true; - bool _viewsNeedPaintSort = true; - bool _viewsNeedPositionSort = true; - - /// Create a new [LayoutManager]. - LayoutManagerImpl({LayoutConfig config}) - : this.config = config ?? 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 get paintOrderedViews { - if (_viewsNeedPaintSort) { - _paintOrderedViews = List.from(_views); - - _paintOrderedViews.sort((v1, v2) => - v1.layoutConfig.paintOrder.compareTo(v2.layoutConfig.paintOrder)); - - _viewsNeedPaintSort = false; - } - return _paintOrderedViews; - } - - /// Get all layout components in the order to be visited. - @override - List get positionOrderedViews { - if (_viewsNeedPositionSort) { - _positionOrderedViews = List.from(_views); - - _positionOrderedViews.sort((v1, v2) => v1.layoutConfig.positionOrder - .compareTo(v2.layoutConfig.positionOrder)); - - _viewsNeedPositionSort = false; - } - return _positionOrderedViews; - } - - @override - Rectangle get drawAreaBounds { - assert(_drawAreaBoundsOutdated == false); - return _drawAreaBounds; - } - - @override - Rectangle get drawableLayoutAreaBounds { - assert(_drawAreaBoundsOutdated == false); - - final drawableViews = _views.where((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 = 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 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 = 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 = Rectangle(0, 0, width, height); - - // Layout the margins. - LeftMarginLayoutStrategy() - .layout(leftViews, _measurements.leftSizes, fullBounds, drawAreaBounds); - RightMarginLayoutStrategy().layout( - rightViews, _measurements.rightSizes, fullBounds, drawAreaBounds); - BottomMarginLayoutStrategy().layout( - bottomViews, _measurements.bottomSizes, fullBounds, drawAreaBounds); - TopMarginLayoutStrategy() - .layout(topViews, _measurements.topSizes, fullBounds, drawAreaBounds); - - // Layout the drawArea. - drawAreaViews - .forEach((view) => view.layout(_drawAreaBounds, _drawAreaBounds)); - } - - Iterable _viewsForPositions(LayoutPosition p1, - [LayoutPosition p2]) { - return positionOrderedViews.where((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 topViews, - Iterable rightViews, - Iterable bottomViews, - Iterable 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 = LeftMarginLayoutStrategy().measure(leftViews, - maxWidth: useMax ? maxLeftWidth : leftWidth, - height: adjustedHeight, - fullHeight: height); - - leftWidth = max(leftSizes.total, config.leftSpec.getMinPixels(width)); - - var rightSizes = 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 = BottomMarginLayoutStrategy().measure(bottomViews, - maxHeight: useMax ? maxBottomHeight : bottomHeight, - width: adjustedWidth, - fullWidth: width); - bottomHeight = - max(bottomSizes.total, config.bottomSpec.getMinPixels(height)); - - var topSizes = TopMarginLayoutStrategy().measure(topViews, - maxHeight: useMax ? maxTopHeight : topHeight, - width: adjustedWidth, - fullWidth: width); - topHeight = max(topSizes.total, config.topSpec.getMinPixels(height)); - - return _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}); -} diff --git a/web/charts/common/lib/src/chart/layout/layout_margin_strategy.dart b/web/charts/common/lib/src/chart/layout/layout_margin_strategy.dart deleted file mode 100644 index 12155861e..000000000 --- a/web/charts/common/lib/src/chart/layout/layout_margin_strategy.dart +++ /dev/null @@ -1,273 +0,0 @@ -// 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 _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 = SizeList(); - final minimumSizes = 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 views, - {@required int maxWidth, - @required int height, - @required int fullHeight}) { - final measuredWidths = _DesiredViewSizes(); - int remainingWidth = maxWidth; - - views.forEach((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 views, SizeList measuredSizes, - Rectangle fullBounds, Rectangle drawAreaBounds); -} - -/// A strategy for calculating size and bounds of left margins. -class LeftMarginLayoutStrategy extends VerticalMarginStrategy { - @override - void layout(Iterable views, SizeList measuredSizes, - Rectangle fullBounds, Rectangle drawAreaBounds) { - var prevBoundsRight = drawAreaBounds.left; - - int i = 0; - views.forEach((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(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 views, SizeList measuredSizes, - Rectangle fullBounds, Rectangle drawAreaBounds) { - var prevBoundsLeft = drawAreaBounds.right; - - int i = 0; - views.forEach((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(Rectangle(left, top, width, height), drawAreaBounds); - - i++; - }); - } -} - -/// A strategy for calculating size of horizontal margins (TOP & BOTTOM). -abstract class HorizontalMarginStrategy { - SizeList measure(Iterable views, - {@required int maxHeight, @required int width, @required int fullWidth}) { - final measuredHeights = _DesiredViewSizes(); - int remainingHeight = maxHeight; - - views.forEach((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 views, SizeList measuredSizes, - Rectangle fullBounds, Rectangle drawAreaBounds); -} - -/// A strategy for calculating size and bounds of top margins. -class TopMarginLayoutStrategy extends HorizontalMarginStrategy { - @override - void layout(Iterable views, SizeList measuredSizes, - Rectangle fullBounds, Rectangle drawAreaBounds) { - var prevBoundsBottom = drawAreaBounds.top; - - int i = 0; - views.forEach((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(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 views, SizeList measuredSizes, - Rectangle fullBounds, Rectangle drawAreaBounds) { - var prevBoundsTop = drawAreaBounds.bottom; - - int i = 0; - views.forEach((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(Rectangle(left, top, width, height), drawAreaBounds); - - i++; - }); - } -} diff --git a/web/charts/common/lib/src/chart/layout/layout_view.dart b/web/charts/common/lib/src/chart/layout/layout_view.dart deleted file mode 100644 index 373dc178a..000000000 --- a/web/charts/common/lib/src/chart/layout/layout_view.dart +++ /dev/null @@ -1,207 +0,0 @@ -// 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 = 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 = 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 componentBounds, Rectangle drawAreaBounds); - - /// Draw this component on the canvas. - void paint(ChartCanvas canvas, double animationPercent); - - /// Bounding box for drawing this component. - Rectangle 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; -} diff --git a/web/charts/common/lib/src/chart/line/line_chart.dart b/web/charts/common/lib/src/chart/line/line_chart.dart deleted file mode 100644 index 65335b63c..000000000 --- a/web/charts/common/lib/src/chart/line/line_chart.dart +++ /dev/null @@ -1,42 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import '../cartesian/axis/axis.dart' show NumericAxis; -import '../cartesian/cartesian_chart.dart' show NumericCartesianChart; -import '../common/series_renderer.dart' show SeriesRenderer; -import '../layout/layout_config.dart' show LayoutConfig; -import '../line/line_renderer.dart' show LineRenderer; - -class LineChart extends NumericCartesianChart { - LineChart( - {bool vertical, - LayoutConfig layoutConfig, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes}) - : super( - vertical: vertical, - layoutConfig: layoutConfig, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes); - - @override - SeriesRenderer makeDefaultRenderer() { - return LineRenderer()..rendererId = SeriesRenderer.defaultRendererId; - } -} diff --git a/web/charts/common/lib/src/chart/line/line_renderer.dart b/web/charts/common/lib/src/chart/line/line_renderer.dart deleted file mode 100644 index 80fa01f94..000000000 --- a/web/charts/common/lib/src/chart/line/line_renderer.dart +++ /dev/null @@ -1,1583 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show Rectangle, Point; - -import 'package:meta/meta.dart' show required, visibleForTesting; - -import '../../common/color.dart' show Color; -import '../../common/math.dart' show clamp; -import '../../data/series.dart' show AttributeKey; -import '../cartesian/axis/axis.dart' - show ImmutableAxis, OrdinalAxis, domainAxisKey, measureAxisKey; -import '../cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; -import '../common/base_chart.dart' show BaseChart; -import '../common/chart_canvas.dart' show ChartCanvas, getAnimatedColor; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import '../common/series_datum.dart' show SeriesDatum; -import '../scatter_plot/point_renderer.dart' show PointRenderer; -import '../scatter_plot/point_renderer_config.dart' show PointRendererConfig; -import 'line_renderer_config.dart' show LineRendererConfig; - -const styleSegmentsKey = - AttributeKey>('LineRenderer.styleSegments'); - -const lineStackIndexKey = AttributeKey('LineRenderer.lineStackIndex'); - -class LineRenderer extends BaseCartesianRenderer { - // Configuration used to extend the clipping area to extend the draw bounds. - static const drawBoundTopExtensionPx = 5; - static const drawBoundBottomExtensionPx = 5; - - final LineRendererConfig config; - - PointRenderer _pointRenderer; - - BaseChart _chart; - - /// True if any series has a measureUpperBoundFn and measureLowerBoundFn. - /// - /// Used to enable drawing confidence interval areas segments. - bool _hasMeasureBounds; - - /// Store a map of series drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was given to the chart. - final _seriesLineMap = >>{}; - - // Store a list of lines that exist in the series data. - // - // This list will be used to remove any [_AnimatedLine] that were rendered in - // previous draw cycles, but no longer have a corresponding datum in the new - // data. - final _currentKeys = []; - - factory LineRenderer({String rendererId, LineRendererConfig config}) { - return LineRenderer._internal( - rendererId: rendererId ?? 'line', - config: config ?? LineRendererConfig()); - } - - LineRenderer._internal({String rendererId, this.config}) - : super( - rendererId: rendererId, - layoutPaintOrder: config.layoutPaintOrder, - symbolRenderer: config.symbolRenderer) { - _pointRenderer = PointRenderer( - config: PointRendererConfig(radiusPx: this.config.radiusPx)); - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - super.layout(componentBounds, drawAreaBounds); - - if (config.includePoints) { - _pointRenderer.layout(componentBounds, drawAreaBounds); - } - } - - @override - void configureSeries(List> seriesList) { - assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: false); - - seriesList.forEach((series) { - // Add a default area color function which applies the configured - // areaOpacity value to the datum's current color. - series.areaColorFn ??= (index) { - final color = series.colorFn(index); - - return Color( - r: color.r, - g: color.g, - b: color.b, - a: (color.a * config.areaOpacity).round()); - }; - }); - - if (config.includePoints) { - _pointRenderer.configureSeries(seriesList); - } - } - - @override - void preprocessSeries(List> seriesList) { - var stackIndex = 0; - - _hasMeasureBounds = seriesList.any((series) => - series.measureUpperBoundFn != null && - series.measureLowerBoundFn != null); - - seriesList.forEach((series) { - final colorFn = series.colorFn; - final areaColorFn = series.areaColorFn; - final domainFn = series.domainFn; - final measureFn = series.measureFn; - final strokeWidthPxFn = series.strokeWidthPxFn; - - series.dashPatternFn ??= (_) => config.dashPattern; - final dashPatternFn = series.dashPatternFn; - - final styleSegments = <_LineRendererElement>[]; - var styleSegmentsIndex = 0; - - final usedKeys = Set(); - - // Configure style segments for each series. - String previousSegmentKey; - _LineRendererElement currentDetails; - - for (var index = 0; index < series.data.length; index++) { - final domain = domainFn(index); - final measure = measureFn(index); - - if (domain == null || measure == null) { - continue; - } - - final color = colorFn(index); - final areaColor = areaColorFn(index); - final dashPattern = dashPatternFn(index); - final strokeWidthPx = strokeWidthPxFn != null - ? strokeWidthPxFn(index).toDouble() - : config.strokeWidthPx; - - // Create a style key for this datum, and then compare it to the - // previous datum. - // - // Compare strokeWidthPx to 2 decimals of precision. Any less and you - // can't see any difference in the canvas anyways. - final strokeWidthPxRounded = (strokeWidthPx * 100).round() / 100; - var styleKey = '${series.id}__${styleSegmentsIndex}__$color' - '__${dashPattern}__$strokeWidthPxRounded'; - - if (styleKey != previousSegmentKey) { - // If we have a repeated style segment, update the repeat index and - // create a new key. - // TODO: Paint repeated styles with multiple clip regions. - if (usedKeys.isNotEmpty && usedKeys.contains(styleKey)) { - styleSegmentsIndex++; - - styleKey = '${series.id}__${styleSegmentsIndex}__$color' - '__${dashPattern}__$strokeWidthPxRounded'; - } - - // Make sure that the previous style segment extends to the current - // domain value. This will ensure that the style of the line changes - // right at the point of the datum that changes the style. - if (currentDetails != null) { - currentDetails.domainExtent.includePoint(domain); - } - - // Create a new style segment. - currentDetails = _LineRendererElement() - ..color = color - ..areaColor = areaColor - ..dashPattern = dashPattern - ..domainExtent = _Range(domain, domain) - ..strokeWidthPx = strokeWidthPx - ..styleKey = styleKey - ..roundEndCaps = config.roundEndCaps; - - styleSegments.add(currentDetails); - usedKeys.add(styleKey); - - previousSegmentKey = styleKey; - } else { - // Extend the range of the current segment to include the current - // domain value. - currentDetails.domainExtent.includePoint(domain); - } - } - - series.setAttr(styleSegmentsKey, styleSegments); - series.setAttr(lineStackIndexKey, stackIndex); - - if (config.stacked) { - stackIndex++; - } - }); - - if (config.includePoints) { - _pointRenderer.preprocessSeries(seriesList); - } - - // If we are stacking, generate new stacking measure offset functions for - // each series. Each datum should have a measure offset consisting of the - // sum of the measure and measure offsets of each datum with the same domain - // value in series below it in the stack. The first series will be treated - // as the bottom of the stack. - if (config.stacked && seriesList.isNotEmpty) { - var curOffsets = _createInitialOffsetMap(seriesList[0]); - var nextOffsets = {}; - - for (var i = 0; i < seriesList.length; i++) { - final series = seriesList[i]; - final measureOffsetFn = _createStackedMeasureOffsetFunction( - series, curOffsets, nextOffsets); - - if (i > 0) { - series.measureOffsetFn = measureOffsetFn; - } - - curOffsets = nextOffsets; - nextOffsets = {}; - } - } - } - - /// Creates the initial offsets for the series given the measureOffset values. - Map _createInitialOffsetMap(MutableSeries series) { - final domainFn = series.domainFn; - final measureOffsetFn = series.measureOffsetFn; - final initialOffsets = {}; - - for (var index = 0; index < series.data.length; index++) { - initialOffsets[domainFn(index)] = measureOffsetFn(index); - } - - return initialOffsets; - } - - /// Function needed to create a closure preserving the previous series - /// information. y0 for this series is just y + y0 for previous series as long - /// as both y and y0 are not null. If they are null propagate up the - /// missing/null data. - Function _createStackedMeasureOffsetFunction(MutableSeries series, - Map curOffsets, Map nextOffsets) { - final domainFn = series.domainFn; - final measureFn = series.measureFn; - - for (var index = 0; index < series.data.length; index++) { - final domainValue = domainFn(index); - final measure = measureFn(index); - final prevOffset = curOffsets[domainValue]; - - if (measure != null && prevOffset != null) { - nextOffsets[domainValue] = measure + prevOffset; - } - } - - return (i) => curOffsets[domainFn(i)]; - } - - /// Merge the line map and the new series so that the new elements are mixed - /// with the previous ones. - /// - /// This is to deal with the issue that every new series added after the fact - /// would be be rendered on top of the old ones, no matter the order of the - /// new series list. - void _mergeIntoSeriesMap(List> seriesList) { - List>>> newLineMap = []; - - seriesList.forEach((series) { - final key = series.id; - - // First, add all the series from the old map that have been removed from - // the new seriesList in the same order they appear, stopping at the first - // series that is still in the list. We need to maintain them in the same - // order animate them out smoothly. - bool checkNext = true; - while (checkNext && _seriesLineMap.isNotEmpty) { - final firstKey = _seriesLineMap.keys.first; - if (!seriesList.any((s) => s.id == firstKey)) { - newLineMap.add(MapEntry(firstKey, _seriesLineMap.remove(firstKey))); - checkNext = true; - } else { - checkNext = false; - } - } - - // If it's a new key, we add it and move to the next one. If not, we - // remove it from the current list and add it to the new one. - if (!_seriesLineMap.containsKey(key)) { - newLineMap.add(MapEntry(key, [])); - } else { - newLineMap.add(MapEntry(key, _seriesLineMap.remove(key))); - } - }); - - // Now whatever is left is stuff that has been removed. We still add it to - // the end and removed them as the map is modified in place. - newLineMap.addAll(_seriesLineMap.entries); - _seriesLineMap.clear(); - - _seriesLineMap.addEntries(newLineMap); - } - - void update(List> seriesList, bool isAnimatingThisDraw) { - _currentKeys.clear(); - - // List of final points for the previous line in a stack. - List>> previousPointList = []; - - // List of initial points for the previous line in a stack, animated in from - // the measure axis. - List>> previousInitialPointList = []; - - _mergeIntoSeriesMap(seriesList); - - seriesList.forEach((series) { - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final lineKey = series.id; - final stackIndex = series.getAttr(lineStackIndexKey); - - previousPointList.add([]); - previousInitialPointList.add([]); - - final elementsList = _seriesLineMap[lineKey]; - - final styleSegments = series.getAttr(styleSegmentsKey); - - // Include the end points of the domain axis range in the first and last - // style segments to avoid clipping everything when the domain range of - // the data is very small. Doing this after [preProcess] handles invalid - // data (e.g. null measure) at the ends of the series data. - // - // TODO: Handle ordinal axes by looking at the next domains. - if (styleSegments.isNotEmpty && !(domainAxis is OrdinalAxis)) { - final startPx = (isRtl ? drawBounds.right : drawBounds.left).toDouble(); - final endPx = (isRtl ? drawBounds.left : drawBounds.right).toDouble(); - - final startDomain = domainAxis.getDomain(startPx); - final endDomain = domainAxis.getDomain(endPx); - - styleSegments.first.domainExtent.includePoint(startDomain); - styleSegments.last.domainExtent.includePoint(endDomain); - } - - // Create a set of animated line and area elements for each style segment. - // - // If the series contains null measure values, then multiple animated line - // and area objects will be created to represent the isolated sections of - // the series. - // - // The full set of line and area elements will be rendered on the canvas - // for each style segment, with a clip region added in the [paint] process - // later to display only the relevant parts of data. This ensures that - // styles that visually depend on the start location, such as dash - // patterns, are not disrupted by other changes in style. - styleSegments.forEach((styleSegment) { - final styleKey = styleSegment.styleKey; - - // If we already have an AnimatingPoint for that index, use it. - var animatingElements = elementsList.firstWhere( - (elements) => elements.styleKey == styleKey, - orElse: () => null); - - if (animatingElements != null) { - previousInitialPointList[stackIndex] = animatingElements.allPoints; - } else { - // Create a new line and have it animate in from axis. - final lineAndArea = _createLineAndAreaElements( - series, - styleSegment, - stackIndex > 0 ? previousInitialPointList[stackIndex - 1] : null, - true); - final lineElementList = lineAndArea[0]; - final areaElementList = lineAndArea[1]; - final allPointList = lineAndArea[2]; - final boundsElementList = lineAndArea[3]; - - // Create the line elements. - final animatingLines = <_AnimatedLine>[]; - - for (var index = 0; index < lineElementList.length; index++) { - animatingLines.add(_AnimatedLine( - key: lineElementList[index].styleKey, - overlaySeries: series.overlaySeries) - ..setNewTarget(lineElementList[index])); - } - - // Create the area elements. - List<_AnimatedArea> animatingAreas; - if (config.includeArea) { - animatingAreas = <_AnimatedArea>[]; - - for (var index = 0; index < areaElementList.length; index++) { - animatingAreas.add(_AnimatedArea( - key: areaElementList[index].styleKey, - overlaySeries: series.overlaySeries) - ..setNewTarget(areaElementList[index])); - } - } - - // Create the bound elements separately from area elements, because - // it needs to be rendered on top of the area elements. - List<_AnimatedArea> animatingBounds; - if (_hasMeasureBounds) { - animatingBounds ??= <_AnimatedArea>[]; - - for (var index = 0; index < boundsElementList.length; index++) { - animatingBounds.add(_AnimatedArea( - key: boundsElementList[index].styleKey, - overlaySeries: series.overlaySeries) - ..setNewTarget(boundsElementList[index])); - } - } - - animatingElements = _AnimatedElements() - ..styleKey = styleSegment.styleKey - ..allPoints = allPointList - ..lines = animatingLines - ..areas = animatingAreas - ..bounds = animatingBounds; - - elementsList.add(animatingElements); - - previousInitialPointList[stackIndex] = allPointList; - } - - // Create a new line using the final point locations. - final lineAndArea = _createLineAndAreaElements(series, styleSegment, - stackIndex > 0 ? previousPointList[stackIndex - 1] : null, false); - final lineElementList = lineAndArea[0]; - final areaElementList = lineAndArea[1]; - final allPointList = lineAndArea[2]; - final boundsElementList = lineAndArea[3]; - - for (var index = 0; index < lineElementList.length; index++) { - final lineElement = lineElementList[index]; - - // Add a new animated line if we have more segments in this draw cycle - // than we did in the previous chart draw cycle. - // TODO: Nicer animations for incoming segments. - if (index >= animatingElements.lines.length) { - animatingElements.lines.add(_AnimatedLine( - key: lineElement.styleKey, - overlaySeries: series.overlaySeries)); - } - animatingElements.lines[index].setNewTarget(lineElement); - } - - if (config.includeArea) { - for (var index = 0; index < areaElementList.length; index++) { - final areaElement = areaElementList[index]; - - // Add a new animated area if we have more segments in this draw - // cycle than we did in the previous chart draw cycle. - // TODO: Nicer animations for incoming segments. - if (index >= animatingElements.areas.length) { - animatingElements.areas.add(_AnimatedArea( - key: areaElement.styleKey, - overlaySeries: series.overlaySeries)); - } - animatingElements.areas[index].setNewTarget(areaElement); - } - } - - if (_hasMeasureBounds) { - for (var index = 0; index < boundsElementList.length; index++) { - final boundElement = boundsElementList[index]; - - // Add a new animated bound if we have more segments in this draw - // cycle than we did in the previous chart draw cycle. - // TODO: Nicer animations for incoming segments. - if (index >= animatingElements.bounds.length) { - animatingElements.bounds.add(_AnimatedArea( - key: boundElement.styleKey, - overlaySeries: series.overlaySeries)); - } - animatingElements.bounds[index].setNewTarget(boundElement); - } - } - - animatingElements.allPoints = allPointList; - - // Save the line points for the current series so that we can use them - // in the area skirt for the next stacked series. - previousPointList[stackIndex] = allPointList; - }); - }); - - // Animate out lines that don't exist anymore. - _seriesLineMap.forEach((key, elements) { - for (var element in elements) { - if (element.lines != null) { - for (var line in element.lines) { - if (_currentKeys.contains(line.key) != true) { - line.animateOut(); - } - } - } - if (element.areas != null) { - for (var area in element.areas) { - if (_currentKeys.contains(area.key) != true) { - area.animateOut(); - } - } - } - if (element.bounds != null) { - for (var bound in element.bounds) { - if (_currentKeys.contains(bound.key) != true) { - bound.animateOut(); - } - } - } - } - }); - - if (config.includePoints) { - _pointRenderer.update(seriesList, isAnimatingThisDraw); - } - } - - /// Creates a tuple of lists of [_LineRendererElement]s, - /// [_AreaRendererElement]s, [_DatumPoint]s for a given style segment of a - /// series. - /// - /// The first element in the returned array is a list of line elements, broken - /// apart by null data. - /// - /// The second element in the returned array is a list of area elements, - /// broken apart by null data. - /// - /// The third element in the returned array is a list of all of the points for - /// the entire series. This is intended to be used as the [previousPointList] - /// for the next series. - /// - /// [series] the series that this line represents. - /// - /// [styleSegment] represents the rendering style for a subset of the series - /// data, bounded by its domainExtent. - /// - /// [previousPointList] contains the points for the line below this series in - /// the stack, if stacking is enabled. It forms the bottom edges for the area - /// skirt. - /// - /// [initializeFromZero] controls whether we generate elements with measure - /// values of 0, or using series data. This should be true when calculating - /// point positions to animate in from the measure axis. - List _createLineAndAreaElements( - ImmutableSeries series, - _LineRendererElement styleSegment, - List<_DatumPoint> previousPointList, - bool initializeFromZero) { - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final color = styleSegment.color; - final areaColor = styleSegment.areaColor; - final dashPattern = styleSegment.dashPattern; - final domainExtent = styleSegment.domainExtent; - final strokeWidthPx = styleSegment.strokeWidthPx; - final styleKey = styleSegment.styleKey; - final roundEndCaps = styleSegment.roundEndCaps; - - // Get a list of all positioned points for this series. - final pointList = _createPointListForSeries(series, initializeFromZero); - - // Break pointList up into sets of line and area segments, divided by null - // measure values in the series data. - final segmentsList = _createLineAndAreaSegmentsForSeries( - pointList, previousPointList, series, initializeFromZero); - final lineSegments = segmentsList[0]; - final areaSegments = segmentsList[1]; - final boundsSegment = segmentsList[2]; - - _currentKeys.add(styleKey); - - final positionExtent = _createPositionExtent(series, styleSegment); - - // Get the line elements we are going to to set up. - final lineElements = <_LineRendererElement>[]; - for (var index = 0; index < lineSegments.length; index++) { - final linePointList = lineSegments[index]; - - // Update the set of areas that still exist in the series data. - final lineStyleKey = '${styleKey}__line__$index'; - _currentKeys.add(lineStyleKey); - - lineElements.add(_LineRendererElement() - ..points = linePointList - ..color = color - ..areaColor = areaColor - ..dashPattern = dashPattern - ..domainExtent = domainExtent - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..positionExtent = positionExtent - ..strokeWidthPx = strokeWidthPx - ..styleKey = lineStyleKey - ..roundEndCaps = roundEndCaps); - } - - // Get the area elements we are going to set up. - final areaElements = <_AreaRendererElement>[]; - if (config.includeArea) { - for (var index = 0; index < areaSegments.length; index++) { - final areaPointList = areaSegments[index]; - - // Update the set of areas that still exist in the series data. - final areaStyleKey = '${styleKey}__area_$index'; - _currentKeys.add(areaStyleKey); - - areaElements.add(_AreaRendererElement() - ..points = areaPointList - ..color = color - ..areaColor = areaColor - ..domainExtent = domainExtent - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..positionExtent = positionExtent - ..styleKey = areaStyleKey); - } - } - - // Create the bounds element - final boundsElements = <_AreaRendererElement>[]; - if (_hasMeasureBounds) { - // Update the set of bounds that still exist in the series data. - for (var index = 0; index < boundsSegment.length; index++) { - final boundsPointList = boundsSegment[index]; - - final boundsStyleKey = '${styleKey}__bounds_$index'; - _currentKeys.add(boundsStyleKey); - - boundsElements.add(_AreaRendererElement() - ..points = boundsPointList - ..color = color - ..areaColor = areaColor - ..domainExtent = domainExtent - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..positionExtent = positionExtent - ..styleKey = boundsStyleKey); - } - } - - return [lineElements, areaElements, pointList, boundsElements]; - } - - /// Builds a list of data points for the entire series. - /// - /// [series] the series that this line represents. - /// - /// [initializeFromZero] controls whether we generate elements with measure - /// values of 0, or using series data. This should be true when calculating - /// point positions to animate in from the measure axis. - List<_DatumPoint> _createPointListForSeries( - ImmutableSeries series, bool initializeFromZero) { - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final domainFn = series.domainFn; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - final measureFn = series.measureFn; - final measureOffsetFn = series.measureOffsetFn; - - final pointList = <_DatumPoint>[]; - - // Generate [_DatumPoints]s for the series data. - for (var index = 0; index < series.data.length; index++) { - final datum = series.data[index]; - - // TODO: Animate from the nearest lines in the stack. - var measure = measureFn(index); - if (measure != null && initializeFromZero) { - measure = 0.0; - } - - var measureOffset = measureOffsetFn(index); - if (measureOffset != null && initializeFromZero) { - measureOffset = 0.0; - } - - pointList.add(_getPoint(datum, domainFn(index), series, domainAxis, - measure, measureOffset, measureAxis, - index: index)); - } - - return pointList; - } - - /// Builds a list of line and area segments for a series. - /// - /// This method returns a list of two elements. The first is a list of line - /// segments, and the second is a list of area segments. Both sets of segments - /// are broken up by null measure values in the series data. - /// - /// [pointList] list of all points in the line. - /// - /// [previousPointList] list of all points in the line below this one in the - /// stack. - /// - /// [series] the series that this line represents. - List _createLineAndAreaSegmentsForSeries( - List<_DatumPoint> pointList, - List<_DatumPoint> previousPointList, - ImmutableSeries series, - bool initializeFromZero) { - final lineSegments = >>[]; - final areaSegments = >>[]; - final boundsSegments = >>[]; - - int startPointIndex; - int endPointIndex; - - // Only build bound segments for this series if it has bounds functions. - final seriesHasMeasureBounds = series.measureUpperBoundFn != null && - series.measureLowerBoundFn != null; - - for (var index = 0; index < pointList.length; index++) { - final point = pointList[index]; - - if (point.y == null) { - if (startPointIndex == null) { - continue; - } - - lineSegments - .add(_createLineSegment(startPointIndex, endPointIndex, pointList)); - - // Isolated data points are handled by the line painter. Do not add an - // area segment for them. - if (startPointIndex != endPointIndex) { - if (config.includeArea) { - areaSegments.add(_createAreaSegment(startPointIndex, endPointIndex, - pointList, previousPointList, series, initializeFromZero)); - } - if (seriesHasMeasureBounds) { - boundsSegments.add(_createBoundsSegment( - pointList.sublist(startPointIndex, endPointIndex + 1), - series, - initializeFromZero)); - } - } - - startPointIndex = null; - endPointIndex = null; - continue; - } - - startPointIndex ??= index; - endPointIndex = index; - } - - // Create an area point list for the final segment. This will be the only - // segment if no null measure values were found in the series. - if (startPointIndex != null && endPointIndex != null) { - lineSegments - .add(_createLineSegment(startPointIndex, endPointIndex, pointList)); - - // Isolated data points are handled by the line painter. Do not add an - // area segment for them. - if (startPointIndex != endPointIndex) { - if (config.includeArea) { - areaSegments.add(_createAreaSegment(startPointIndex, endPointIndex, - pointList, previousPointList, series, initializeFromZero)); - } - - if (seriesHasMeasureBounds) { - boundsSegments.add(_createBoundsSegment( - pointList.sublist(startPointIndex, endPointIndex + 1), - series, - initializeFromZero)); - } - } - } - - return [lineSegments, areaSegments, boundsSegments]; - } - - /// Builds a list of data points for a line segment. - /// - /// For a line, this is effectively just a sub list of [pointList]. - /// - /// [start] index of the first point in the segment. - /// - /// [end] index of the last point in the segment. - /// - /// [pointList] list of all points in the line. - List<_DatumPoint> _createLineSegment( - int start, int end, List<_DatumPoint> pointList) => - pointList.sublist(start, end + 1); - - /// Builds a list of data points for an area segment. - /// - /// The list of points will include a baseline at the domain axis if there was - /// no previous line in the stack. Otherwise, the bottom of the shape will - /// consist of the points from the previous series that line up with the - /// current series. - /// - /// [start] index of the first point in the segment. - /// - /// [end] index of the last point in the segment. - /// - /// [pointList] list of all points in the line. - /// - /// [previousPointList] list of all points in the line below this one in the - /// stack. - /// - /// [series] the series that this line represents. - List<_DatumPoint> _createAreaSegment( - int start, - int end, - List<_DatumPoint> pointList, - List<_DatumPoint> previousPointList, - ImmutableSeries series, - bool initializeFromZero) { - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final domainFn = series.domainFn; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final areaPointList = <_DatumPoint>[]; - - if (!config.stacked || previousPointList == null) { - // Start area segments at the bottom of a stack by adding a bottom line - // segment along the measure axis. - areaPointList.add(_getPoint( - null, domainFn(end), series, domainAxis, 0.0, 0.0, measureAxis)); - - areaPointList.add(_getPoint( - null, domainFn(start), series, domainAxis, 0.0, 0.0, measureAxis)); - } else { - // Start subsequent area segments in a stack by adding the previous - // points in reverse order, so that we can get a properly closed - // polygon. - areaPointList.addAll(previousPointList.sublist(start, end + 1).reversed); - } - - areaPointList.addAll(pointList.sublist(start, end + 1)); - - return areaPointList; - } - - List<_DatumPoint> _createBoundsSegment(List<_DatumPoint> pointList, - ImmutableSeries series, bool initializeFromZero) { - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - final areaPointList = <_DatumPoint>[]; - - // Add all points for upper bounds. - areaPointList.addAll(pointList.map((datumPoint) => _DatumPoint.from( - datumPoint, - datumPoint.x, - initializeFromZero - ? datumPoint.y - : measureAxis.getLocation( - series.measureUpperBoundFn(datumPoint.index) + - series.measureOffsetFn(datumPoint.index))))); - - // Add all points for lower bounds, in reverse order. - areaPointList.addAll(pointList.reversed.map((datumPoint) => - _DatumPoint.from( - datumPoint, - datumPoint.x, - initializeFromZero - ? datumPoint.y - : measureAxis.getLocation( - series.measureLowerBoundFn(datumPoint.index) + - series.measureOffsetFn(datumPoint.index))))); - - return areaPointList; - } - - /// Converts the domain value extent for the series into axis positions, - /// clamped to the edges of the draw area. - /// - /// [series] the series that this line represents. - /// - /// [details] represents the element details for a line segment. - _Range _createPositionExtent( - ImmutableSeries series, _LineRendererElement details) { - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - - // Convert the domain extent into axis positions. - // Clamp start position to the beginning of the draw area if it is outside - // the domain viewport range. - final startPosition = domainAxis.getLocation(details.domainExtent.start) ?? - drawBounds.left.toDouble(); - - // Clamp end position to the end of the draw area if it is outside the - // domain viewport range. - final endPosition = domainAxis.getLocation(details.domainExtent.end) ?? - drawBounds.right.toDouble(); - - return _Range(startPosition, endPosition); - } - - @override - void onAttach(BaseChart chart) { - super.onAttach(chart); - // We only need the chart.context.isRtl setting, but context is not yet - // available when the default renderer is attached to the chart on chart - // creation time, since chart onInit is called after the chart is created. - _chart = chart; - } - - void paint(ChartCanvas canvas, double animationPercent) { - // Clean up the lines that no longer exist. - if (animationPercent == 1.0) { - final keysToRemove = []; - - _seriesLineMap.forEach((key, elements) { - elements.removeWhere((element) => element.animatingOut); - - if (elements.isEmpty) { - keysToRemove.add(key); - } - }); - - keysToRemove.forEach(_seriesLineMap.remove); - } - - _seriesLineMap.forEach((key, elements) { - if (config.includeArea) { - elements - .map>>( - (animatingElement) => animatingElement.areas) - .expand<_AnimatedArea>((areas) => areas) - .map<_AreaRendererElement>((animatingArea) => - animatingArea?.getCurrentArea(animationPercent)) - .forEach((area) { - if (area != null) { - canvas.drawPolygon( - clipBounds: _getClipBoundsForExtent(area.positionExtent), - fill: area.areaColor != null ? area.areaColor : area.color, - points: area.points); - } - }); - } - - if (_hasMeasureBounds) { - elements - .map>>( - (animatingElement) => animatingElement.bounds) - .expand<_AnimatedArea>((bounds) => bounds) - .map<_AreaRendererElement>((animatingBounds) => - animatingBounds?.getCurrentArea(animationPercent)) - .forEach((bound) { - if (bound != null) { - canvas.drawPolygon( - clipBounds: _getClipBoundsForExtent(bound.positionExtent), - fill: bound.areaColor != null ? bound.areaColor : bound.color, - points: bound.points); - } - }); - } - - if (config.includeLine) { - elements - .map>>( - (animatingElement) => animatingElement.lines) - .expand<_AnimatedLine>((lines) => lines) - .map<_LineRendererElement>((animatingLine) => - animatingLine?.getCurrentLine(animationPercent)) - .forEach((line) { - if (line != null) { - canvas.drawLine( - clipBounds: _getClipBoundsForExtent(line.positionExtent), - dashPattern: line.dashPattern, - points: line.points, - stroke: line.color, - strokeWidthPx: line.strokeWidthPx, - roundEndCaps: line.roundEndCaps); - } - }); - } - }); - - if (config.includePoints) { - _pointRenderer.paint(canvas, animationPercent); - } - } - - /// Builds a clip region bounding box within the component [drawBounds] for a - /// given domain range [extent]. - Rectangle _getClipBoundsForExtent(_Range extent) { - // In RTL mode, the domain range extent has start on the right side of the - // chart. Adjust the calculated positions to define a regular left-anchored - // [Rectangle]. Clamp both ends to be within the draw area. - final left = isRtl - ? clamp(extent.end, drawBounds.left, drawBounds.right) - : clamp(extent.start, drawBounds.left, drawBounds.right); - - final right = isRtl - ? clamp((extent.start), drawBounds.left, drawBounds.right) - : clamp((extent.end), drawBounds.left, drawBounds.right); - - return Rectangle( - left, - drawBounds.top - drawBoundTopExtensionPx, - right - left, - drawBounds.height + - drawBoundTopExtensionPx + - drawBoundBottomExtensionPx); - } - - bool get isRtl => _chart?.context?.isRtl ?? false; - - _DatumPoint _getPoint( - dynamic datum, - D domainValue, - ImmutableSeries series, - ImmutableAxis domainAxis, - num measureValue, - num measureOffsetValue, - ImmutableAxis measureAxis, - {int index}) { - final domainPosition = domainAxis.getLocation(domainValue); - - final measurePosition = measureValue != null && measureOffsetValue != null - ? measureAxis.getLocation(measureValue + measureOffsetValue) - : null; - - return _DatumPoint( - datum: datum, - domain: domainValue, - series: series, - x: domainPosition, - y: measurePosition, - index: index); - } - - @override - List> getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride) { - final nearest = >[]; - - // Was it even in the component bounds? - if (!isPointWithinBounds(chartPoint, boundsOverride)) { - return nearest; - } - - _seriesLineMap.values.forEach((seriesSegments) { - _DatumPoint nearestPoint; - double nearestDomainDistance = 10000.0; - double nearestMeasureDistance = 10000.0; - double nearestRelativeDistance = 10000.0; - - seriesSegments.forEach((segment) { - if (segment.overlaySeries) { - return; - } - - segment.allPoints.forEach((p) { - // Don't look at points not in the drawArea. - if (p.x < componentBounds.left || p.x > componentBounds.right) { - return; - } - - final domainDistance = (p.x - chartPoint.x).abs(); - - double measureDistance; - double relativeDistance; - - if (p.y != null) { - measureDistance = (p.y - chartPoint.y).abs(); - relativeDistance = chartPoint.distanceTo(p); - } else { - // Null measures have no real position, so make them the farthest - // away by real distance. - measureDistance = double.infinity; - relativeDistance = byDomain ? domainDistance : double.infinity; - } - - if (byDomain) { - if ((domainDistance < nearestDomainDistance) || - ((domainDistance == nearestDomainDistance && - measureDistance < nearestMeasureDistance))) { - nearestPoint = p; - nearestDomainDistance = domainDistance; - nearestMeasureDistance = measureDistance; - nearestRelativeDistance = relativeDistance; - } - } else { - if (relativeDistance < nearestRelativeDistance) { - nearestPoint = p; - nearestDomainDistance = domainDistance; - nearestMeasureDistance = measureDistance; - nearestRelativeDistance = relativeDistance; - } - } - }); - }); - - // Found a point, add it to the list. - if (nearestPoint != null) { - nearest.add(DatumDetails( - chartPosition: Point(nearestPoint.x, nearestPoint.y), - datum: nearestPoint.datum, - domain: nearestPoint.domain, - series: nearestPoint.series, - domainDistance: nearestDomainDistance, - measureDistance: nearestMeasureDistance, - relativeDistance: nearestRelativeDistance)); - } - }); - - // Note: the details are already sorted by domain & measure distance in - // base chart. - - return nearest; - } - - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - final series = details.series; - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final point = _getPoint(seriesDatum.datum, details.domain, series, - domainAxis, details.measure, details.measureOffset, measureAxis); - final chartPosition = Point(point.x, point.y); - - return DatumDetails.from(details, chartPosition: chartPosition); - } -} - -class _DatumPoint extends Point { - final dynamic datum; - final D domain; - final ImmutableSeries series; - final int index; - - _DatumPoint( - {this.datum, this.domain, this.series, this.index, double x, double y}) - : super(x, y); - - factory _DatumPoint.from(_DatumPoint other, [double x, double y]) { - return _DatumPoint( - datum: other.datum, - domain: other.domain, - series: other.series, - index: other.index, - x: x ?? other.x, - y: y ?? other.y); - } -} - -/// Rendering information for the line portion of a series. -class _LineRendererElement { - List<_DatumPoint> points; - Color color; - Color areaColor; - List dashPattern; - _Range domainExtent; - double measureAxisPosition; - _Range positionExtent; - double strokeWidthPx; - String styleKey; - bool roundEndCaps; - - _LineRendererElement clone() { - return _LineRendererElement() - ..points = List<_DatumPoint>.from(points) - ..color = color != null ? Color.fromOther(color: color) : null - ..areaColor = areaColor != null ? Color.fromOther(color: areaColor) : null - ..dashPattern = dashPattern != null ? List.from(dashPattern) : null - ..domainExtent = domainExtent - ..measureAxisPosition = measureAxisPosition - ..positionExtent = positionExtent - ..strokeWidthPx = strokeWidthPx - ..styleKey = styleKey - ..roundEndCaps = roundEndCaps; - } - - void updateAnimationPercent(_LineRendererElement previous, - _LineRendererElement target, double animationPercent) { - Point lastPoint; - - int pointIndex; - for (pointIndex = 0; pointIndex < target.points.length; pointIndex++) { - final targetPoint = target.points[pointIndex]; - - // If we have more points than the previous line, animate in the new point - // by starting its measure position at the last known official point. - // TODO: Can this be done in setNewTarget instead? - _DatumPoint previousPoint; - if (previous.points.length - 1 >= pointIndex) { - previousPoint = previous.points[pointIndex]; - lastPoint = previousPoint; - } else { - previousPoint = - _DatumPoint.from(targetPoint, targetPoint.x, lastPoint.y); - } - - final x = ((targetPoint.x - previousPoint.x) * animationPercent) + - previousPoint.x; - - double y; - if (targetPoint.y != null && previousPoint.y != null) { - y = ((targetPoint.y - previousPoint.y) * animationPercent) + - previousPoint.y; - } else if (targetPoint.y != null) { - y = targetPoint.y; - } else { - y = null; - } - - if (points.length - 1 >= pointIndex) { - points[pointIndex] = _DatumPoint.from(targetPoint, x, y); - } else { - points.add(_DatumPoint.from(targetPoint, x, y)); - } - } - - // Removing extra points that don't exist anymore. - if (pointIndex < points.length) { - points.removeRange(pointIndex, points.length); - } - - color = getAnimatedColor(previous.color, target.color, animationPercent); - - if (areaColor != null) { - areaColor = getAnimatedColor( - previous.areaColor, target.areaColor, animationPercent); - } - - strokeWidthPx = - (((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + - previous.strokeWidthPx); - } -} - -/// Animates the line element of a series between different states. -class _AnimatedLine { - final String key; - final bool overlaySeries; - - _LineRendererElement _previousLine; - _LineRendererElement _targetLine; - _LineRendererElement _currentLine; - - // Flag indicating whether this line is being animated out of the chart. - bool animatingOut = false; - - _AnimatedLine({@required this.key, @required this.overlaySeries}); - - /// Animates a line that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for lines that represent - /// data that has been removed from the series. - /// - /// Animates the height of the line down to the measure axis position - /// (position of 0). - void animateOut() { - var newTarget = _currentLine.clone(); - - // Set the target measure value to the axis position for all points. - // TODO: Animate to the nearest lines in the stack. - var newPoints = <_DatumPoint>[]; - for (var index = 0; index < newTarget.points.length; index++) { - var targetPoint = newTarget.points[index]; - - newPoints.add(_DatumPoint.from(targetPoint, targetPoint.x, - newTarget.measureAxisPosition.roundToDouble())); - } - - newTarget.points = newPoints; - - // Animate the stroke width to 0 so that we don't get a lingering line after - // animation is done. - newTarget.strokeWidthPx = 0.0; - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(_LineRendererElement newTarget) { - animatingOut = false; - _currentLine ??= newTarget.clone(); - _previousLine = _currentLine.clone(); - _targetLine = newTarget; - } - - _LineRendererElement getCurrentLine(double animationPercent) { - if (animationPercent == 1.0 || _previousLine == null) { - _currentLine = _targetLine; - _previousLine = _targetLine; - return _currentLine; - } - - _currentLine.updateAnimationPercent( - _previousLine, _targetLine, animationPercent); - - return _currentLine; - } - - /// Returns the [points] of the current target element, without updating - /// animation state. - List<_DatumPoint> get currentPoints => _currentLine?.points; -} - -/// Rendering information for the area skirt portion of a series. -class _AreaRendererElement { - List<_DatumPoint> points; - Color color; - Color areaColor; - _Range domainExtent; - double measureAxisPosition; - _Range positionExtent; - String styleKey; - - _AreaRendererElement clone() { - return _AreaRendererElement() - ..points = List<_DatumPoint>.from(points) - ..color = color != null ? Color.fromOther(color: color) : null - ..areaColor = areaColor != null ? Color.fromOther(color: areaColor) : null - ..domainExtent = domainExtent - ..measureAxisPosition = measureAxisPosition - ..positionExtent = positionExtent - ..styleKey = styleKey; - } - - void updateAnimationPercent(_AreaRendererElement previous, - _AreaRendererElement target, double animationPercent) { - Point lastPoint; - - int pointIndex; - for (pointIndex = 0; pointIndex < target.points.length; pointIndex++) { - var targetPoint = target.points[pointIndex]; - - // If we have more points than the previous line, animate in the new point - // by starting its measure position at the last known official point. - // TODO: Can this be done in setNewTarget instead? - _DatumPoint previousPoint; - if (previous.points.length - 1 >= pointIndex) { - previousPoint = previous.points[pointIndex]; - lastPoint = previousPoint; - } else { - previousPoint = - _DatumPoint.from(targetPoint, targetPoint.x, lastPoint.y); - } - - final x = ((targetPoint.x - previousPoint.x) * animationPercent) + - previousPoint.x; - - double y; - if (targetPoint.y != null && previousPoint.y != null) { - y = ((targetPoint.y - previousPoint.y) * animationPercent) + - previousPoint.y; - } else if (targetPoint.y != null) { - y = targetPoint.y; - } else { - y = null; - } - - if (points.length - 1 >= pointIndex) { - points[pointIndex] = _DatumPoint.from(targetPoint, x, y); - } else { - points.add(_DatumPoint.from(targetPoint, x, y)); - } - } - - // Removing extra points that don't exist anymore. - if (pointIndex < points.length) { - points.removeRange(pointIndex, points.length); - } - - color = getAnimatedColor(previous.color, target.color, animationPercent); - - if (areaColor != null) { - areaColor = getAnimatedColor( - previous.areaColor, target.areaColor, animationPercent); - } - } -} - -/// Animates the area element of a series between different states. -class _AnimatedArea { - final String key; - final bool overlaySeries; - - _AreaRendererElement _previousArea; - _AreaRendererElement _targetArea; - _AreaRendererElement _currentArea; - - // Flag indicating whether this line is being animated out of the chart. - bool animatingOut = false; - - _AnimatedArea({@required this.key, @required this.overlaySeries}); - - /// Animates a line that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for lines that represent - /// data that has been removed from the series. - /// - /// Animates the height of the line down to the measure axis position - /// (position of 0). - void animateOut() { - var newTarget = _currentArea.clone(); - - // Set the target measure value to the axis position for all points. - // TODO: Animate to the nearest areas in the stack. - var newPoints = <_DatumPoint>[]; - for (var index = 0; index < newTarget.points.length; index++) { - var targetPoint = newTarget.points[index]; - - newPoints.add(_DatumPoint.from(targetPoint, targetPoint.x, - newTarget.measureAxisPosition.roundToDouble())); - } - - newTarget.points = newPoints; - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(_AreaRendererElement newTarget) { - animatingOut = false; - _currentArea ??= newTarget.clone(); - _previousArea = _currentArea.clone(); - _targetArea = newTarget; - } - - _AreaRendererElement getCurrentArea(double animationPercent) { - if (animationPercent == 1.0 || _previousArea == null) { - _currentArea = _targetArea; - _previousArea = _targetArea; - return _currentArea; - } - - _currentArea.updateAnimationPercent( - _previousArea, _targetArea, animationPercent); - - return _currentArea; - } -} - -class _AnimatedElements { - List<_DatumPoint> allPoints; - List<_AnimatedArea> areas; - List<_AnimatedLine> lines; - List<_AnimatedArea> bounds; - String styleKey; - - bool get animatingOut { - var areasAnimatingOut = true; - if (areas != null) { - for (_AnimatedArea area in areas) { - areasAnimatingOut = areasAnimatingOut && area.animatingOut; - } - } - - var linesAnimatingOut = true; - if (lines != null) { - for (_AnimatedLine line in lines) { - linesAnimatingOut = linesAnimatingOut && line.animatingOut; - } - } - - var boundsAnimatingOut = true; - if (bounds != null) { - for (_AnimatedArea bound in bounds) { - boundsAnimatingOut = boundsAnimatingOut && bound.animatingOut; - } - } - - return areasAnimatingOut && linesAnimatingOut && boundsAnimatingOut; - } - - bool get overlaySeries { - var areasOverlaySeries = true; - if (areas != null) { - for (_AnimatedArea area in areas) { - areasOverlaySeries = areasOverlaySeries && area.overlaySeries; - } - } - - var linesOverlaySeries = true; - if (lines != null) { - for (_AnimatedLine line in lines) { - linesOverlaySeries = linesOverlaySeries && line.overlaySeries; - } - } - - var boundsOverlaySeries = true; - if (bounds != null) { - for (_AnimatedArea bound in bounds) { - boundsOverlaySeries = boundsOverlaySeries && bound.overlaySeries; - } - } - - return areasOverlaySeries && linesOverlaySeries && boundsOverlaySeries; - } -} - -/// Describes a numeric range with a start and end value. -/// -/// [start] must always be less than [end]. -class _Range { - D _start; - D _end; - - _Range(D start, D end) { - _start = start; - _end = end; - } - - /// Gets the start of the range. - D get start => _start; - - /// Gets the end of the range. - D get end => _end; - - /// Extends the range to include [value]. - void includePoint(D value) { - if (value == null) { - return; - } else if (value is num || value is double || value is int) { - _includePointAsNum(value); - } else if (value is DateTime) { - _includePointAsDateTime(value); - } else if (value is String) { - _includePointAsString(value); - } else { - throw ('Unsupported object type for LineRenderer domain value: ' - '${value.runtimeType}'); - } - } - - /// Extends the range to include value by casting as numbers. - void _includePointAsNum(D value) { - if ((value as num) < (_start as num)) { - _start = value; - } else if ((value as num) > (_end as num)) { - _end = value; - } - } - - /// Extends the range to include value by casting as DateTime objects. - void _includePointAsDateTime(D value) { - if ((value as DateTime).isBefore(_start as DateTime)) { - _start = value; - } else if ((value as DateTime).isAfter(_end as DateTime)) { - _end = value; - } - } - - /// Extends the range to include value by casting as String objects. - /// - /// In this case, we assume that the data is ordered in the same order as the - /// axis. - void _includePointAsString(D value) { - _end = value; - } -} - -@visibleForTesting -class LineRendererTester { - final LineRenderer renderer; - - LineRendererTester(this.renderer); - - Iterable get seriesKeys => renderer._seriesLineMap.keys; - - void setSeriesKeys(List keys) { - renderer._seriesLineMap.addEntries(keys.map((key) => MapEntry(key, []))); - } - - void merge(List> series) { - renderer._mergeIntoSeriesMap(series); - } -} diff --git a/web/charts/common/lib/src/chart/line/line_renderer_config.dart b/web/charts/common/lib/src/chart/line/line_renderer_config.dart deleted file mode 100644 index 72825840e..000000000 --- a/web/charts/common/lib/src/chart/line/line_renderer_config.dart +++ /dev/null @@ -1,92 +0,0 @@ -// 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 '../../common/symbol_renderer.dart'; -import '../common/series_renderer_config.dart' - show RendererAttributes, SeriesRendererConfig; -import '../layout/layout_view.dart' show LayoutViewConfig, LayoutViewPaintOrder; -import 'line_renderer.dart' show LineRenderer; - -/// Configuration for a line renderer. -class LineRendererConfig extends LayoutViewConfig - implements SeriesRendererConfig { - final String customRendererId; - - final SymbolRenderer symbolRenderer; - - final rendererAttributes = RendererAttributes(); - - /// Radius of points on the line, if [includePoints] is enabled. - final double radiusPx; - - /// Whether or not series should be rendered in a stack. - /// - /// This is typically enabled when including area skirts. - final bool stacked; - - /// Stroke width of the line. - final double strokeWidthPx; - - /// Dash pattern for the line. - final List dashPattern; - - /// Configures whether a line representing the data will be drawn. - final bool includeLine; - - /// Configures whether points representing the data will be drawn. - final bool includePoints; - - /// Configures whether an area skirt representing the data will be drawn. - /// - /// An area skirt will be drawn from the line for each series, down to the - /// domain axis. It will be layered underneath the primary line on the chart. - /// - /// The area skirt color will be a semi-transparent version of the series - /// color, using [areaOpacity] as the opacity. - /// - /// When stacking is enabled, the bottom of each area skirt will instead be - /// the previous line in the stack. The bottom area will be drawn down to the - /// domain axis. - final bool includeArea; - - /// The order to paint this renderer on the canvas. - final int layoutPaintOrder; - - /// Configures the opacity of the area skirt on the chart. - final double areaOpacity; - - /// Whether lines should have round end caps, or square if false. - final bool roundEndCaps; - - LineRendererConfig( - {this.customRendererId, - this.radiusPx = 3.5, - this.stacked = false, - this.strokeWidthPx = 2.0, - this.dashPattern, - this.includeLine = true, - this.includePoints = false, - this.includeArea = false, - this.layoutPaintOrder = LayoutViewPaintOrder.line, - this.areaOpacity = 0.1, - this.roundEndCaps = false, - SymbolRenderer symbolRenderer}) - : this.symbolRenderer = symbolRenderer ?? LineSymbolRenderer(); - - @override - LineRenderer build() { - return LineRenderer(config: this, rendererId: customRendererId); - } -} diff --git a/web/charts/common/lib/src/chart/pie/arc_label_decorator.dart b/web/charts/common/lib/src/chart/pie/arc_label_decorator.dart deleted file mode 100644 index e0ef3f7f6..000000000 --- a/web/charts/common/lib/src/chart/pie/arc_label_decorator.dart +++ /dev/null @@ -1,408 +0,0 @@ -// 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 cos, min, sin, pi, Point, Rectangle; - -import 'package:meta/meta.dart' show immutable, required; - -import '../../common/color.dart' show Color; -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/style/style_factory.dart' show StyleFactory; -import '../../common/text_element.dart' - show MaxWidthStrategy, TextDirection, TextElement; -import '../../common/text_style.dart' show TextStyle; -import '../../data/series.dart' show AccessorFn; -import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../common/chart_canvas.dart' show ChartCanvas; -import 'arc_renderer.dart' show ArcRendererElementList; -import 'arc_renderer_decorator.dart' show ArcRendererDecorator; - -/// Renders labels for arc renderers. -/// -/// This decorator performs very basic label collision detection. If the y -/// position of a label positioned outside collides with the previously drawn -/// label (on the same side of the chart), then that label will be skipped. -class ArcLabelDecorator extends ArcRendererDecorator { - // Default configuration - static const _defaultLabelPosition = ArcLabelPosition.auto; - static const _defaultLabelPadding = 5; - static final _defaultInsideLabelStyle = - TextStyleSpec(fontSize: 12, color: Color.white); - static final _defaultOutsideLabelStyle = - TextStyleSpec(fontSize: 12, color: Color.black); - static final _defaultLeaderLineStyle = ArcLabelLeaderLineStyleSpec( - length: 20.0, - thickness: 1.0, - color: StyleFactory.style.arcLabelOutsideLeaderLine); - static const _defaultShowLeaderLines = true; - - /// Configures [TextStyleSpec] for labels placed inside the arcs. - final TextStyleSpec insideLabelStyleSpec; - - /// Configures [TextStyleSpec] for labels placed outside the arcs. - final TextStyleSpec outsideLabelStyleSpec; - - /// Configures [ArcLabelLeaderLineStyleSpec] for leader lines for labels - /// placed outside the arcs. - final ArcLabelLeaderLineStyleSpec leaderLineStyleSpec; - - /// Configures where to place the label relative to the arcs. - final ArcLabelPosition labelPosition; - - /// Space before and after the label text. - final int labelPadding; - - /// Whether or not to draw leader lines for labels placed outside the arcs. - final bool showLeaderLines; - - /// Render the labels on top of series data. - final bool renderAbove = true; - - ArcLabelDecorator( - {TextStyleSpec insideLabelStyleSpec, - TextStyleSpec outsideLabelStyleSpec, - ArcLabelLeaderLineStyleSpec leaderLineStyleSpec, - this.labelPosition = _defaultLabelPosition, - this.labelPadding = _defaultLabelPadding, - this.showLeaderLines = _defaultShowLeaderLines, - Color leaderLineColor}) - : insideLabelStyleSpec = insideLabelStyleSpec ?? _defaultInsideLabelStyle, - outsideLabelStyleSpec = - outsideLabelStyleSpec ?? _defaultOutsideLabelStyle, - leaderLineStyleSpec = leaderLineStyleSpec ?? _defaultLeaderLineStyle; - - @override - void decorate(ArcRendererElementList arcElements, ChartCanvas canvas, - GraphicsFactory graphicsFactory, - {@required Rectangle drawBounds, - @required double animationPercent, - bool rtl = false}) { - // Only decorate the arcs when animation is at 100%. - if (animationPercent != 1.0) { - return; - } - - // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. - // The [GraphicsFactory] is needed so it can't be created earlier. - final insideLabelStyle = - _getTextStyle(graphicsFactory, insideLabelStyleSpec); - final outsideLabelStyle = - _getTextStyle(graphicsFactory, outsideLabelStyleSpec); - - // Track the Y position of the previous outside label for collision - // detection purposes. - num previousOutsideLabelY; - bool previousLabelLeftOfChart; - - for (var element in arcElements.arcs) { - final labelFn = element.series.labelAccessorFn; - final datumIndex = element.index; - final label = (labelFn != null) ? labelFn(datumIndex) : null; - - // If there are custom styles, use that instead of the default or the - // style defined for the entire decorator. - final datumInsideLabelStyle = _getDatumStyle( - element.series.insideLabelStyleAccessorFn, - datumIndex, - graphicsFactory, - defaultStyle: insideLabelStyle); - final datumOutsideLabelStyle = _getDatumStyle( - element.series.outsideLabelStyleAccessorFn, - datumIndex, - graphicsFactory, - defaultStyle: outsideLabelStyle); - - // Skip calculation and drawing for this element if no label. - if (label == null || label.isEmpty) { - continue; - } - - final arcAngle = element.endAngle - element.startAngle; - - final centerAngle = element.startAngle + (arcAngle / 2); - - final centerRadius = arcElements.innerRadius + - ((arcElements.radius - arcElements.innerRadius) / 2); - - final innerPoint = Point( - arcElements.center.x + arcElements.innerRadius * cos(centerAngle), - arcElements.center.y + arcElements.innerRadius * sin(centerAngle)); - - final outerPoint = Point( - arcElements.center.x + arcElements.radius * cos(centerAngle), - arcElements.center.y + arcElements.radius * sin(centerAngle)); - - //final bounds = element.bounds; - final bounds = Rectangle.fromPoints(innerPoint, outerPoint); - - // Get space available inside and outside the arc. - final totalPadding = labelPadding * 2; - final insideArcWidth = (min( - (((arcAngle * 180 / pi) / 360) * (2 * pi * centerRadius)).round(), - (arcElements.radius - arcElements.innerRadius) - labelPadding) - .round()); - - final leaderLineLength = showLeaderLines ? leaderLineStyleSpec.length : 0; - - final outsideArcWidth = ((drawBounds.width / 2) - - bounds.width - - totalPadding - - leaderLineLength) - .round(); - - final labelElement = graphicsFactory.createTextElement(label) - ..maxWidthStrategy = MaxWidthStrategy.ellipsize; - - var calculatedLabelPosition = labelPosition; - if (calculatedLabelPosition == ArcLabelPosition.auto) { - // For auto, first try to fit the text inside the arc. - labelElement.textStyle = datumInsideLabelStyle; - - // A label fits if the space inside the arc is >= outside arc or if the - // length of the text fits and the space. This is because if the arc has - // more space than the outside, it makes more sense to place the label - // inside the arc, even if the entire label does not fit. - calculatedLabelPosition = (insideArcWidth >= outsideArcWidth || - labelElement.measurement.horizontalSliceWidth < insideArcWidth) - ? ArcLabelPosition.inside - : ArcLabelPosition.outside; - } - - // Set the max width and text style. - if (calculatedLabelPosition == ArcLabelPosition.inside) { - labelElement.textStyle = datumInsideLabelStyle; - labelElement.maxWidth = insideArcWidth; - } else { - // calculatedLabelPosition == LabelPosition.outside - labelElement.textStyle = datumOutsideLabelStyle; - labelElement.maxWidth = outsideArcWidth; - } - - // Only calculate and draw label if there's actually space for the label. - if (labelElement.maxWidth > 0) { - // Calculate the start position of label based on [labelAnchor]. - if (calculatedLabelPosition == ArcLabelPosition.inside) { - _drawInsideLabel(canvas, arcElements, labelElement, centerAngle); - } else { - final l = _drawOutsideLabel( - canvas, - drawBounds, - arcElements, - labelElement, - centerAngle, - previousOutsideLabelY, - previousLabelLeftOfChart); - - // List destructuring.. - if (l != null) { - previousLabelLeftOfChart = l[0]; - previousOutsideLabelY = l[1]; - } - } - } - } - } - - /// Helper function that converts [TextStyleSpec] to [TextStyle]. - TextStyle _getTextStyle( - GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { - return graphicsFactory.createTextPaint() - ..color = labelSpec?.color ?? Color.black - ..fontFamily = labelSpec?.fontFamily - ..fontSize = labelSpec?.fontSize ?? 12; - } - - /// Helper function to get datum specific style - TextStyle _getDatumStyle(AccessorFn labelFn, int datumIndex, - GraphicsFactory graphicsFactory, - {TextStyle defaultStyle}) { - final styleSpec = (labelFn != null) ? labelFn(datumIndex) : null; - return (styleSpec != null) - ? _getTextStyle(graphicsFactory, styleSpec) - : defaultStyle; - } - - /// Draws a label inside of an arc. - void _drawInsideLabel( - ChartCanvas canvas, - ArcRendererElementList arcElements, - TextElement labelElement, - double centerAngle) { - // Center the label inside the arc. - final labelRadius = arcElements.innerRadius + - (arcElements.radius - arcElements.innerRadius) / 2; - - final labelX = - (arcElements.center.x + labelRadius * cos(centerAngle)).round(); - - final labelY = (arcElements.center.y + - labelRadius * sin(centerAngle) - - insideLabelStyleSpec.fontSize / 2) - .round(); - - labelElement.textDirection = TextDirection.center; - - canvas.drawText(labelElement, labelX, labelY); - } - - /// Draws a label outside of an arc. - List _drawOutsideLabel( - ChartCanvas canvas, - Rectangle drawBounds, - ArcRendererElementList arcElements, - TextElement labelElement, - double centerAngle, - num previousOutsideLabelY, - bool previousLabelLeftOfChart) { - final labelRadius = arcElements.radius + leaderLineStyleSpec.length / 2; - - final labelPoint = Point( - arcElements.center.x + labelRadius * cos(centerAngle), - arcElements.center.y + labelRadius * sin(centerAngle)); - - // Use the label's chart quandrant to determine whether it's rendered to the - // right or left. - final centerAbs = centerAngle.abs() % (2 * pi); - final labelLeftOfChart = pi / 2 < centerAbs && centerAbs < pi * 3 / 2; - - // Shift the label horizontally away from the center of the chart. - var labelX = labelLeftOfChart - ? (labelPoint.x - labelPadding).round() - : (labelPoint.x + labelPadding).round(); - - // Shift the label up by the size of the font. - final labelY = (labelPoint.y - outsideLabelStyleSpec.fontSize / 2).round(); - - // Outside labels should flow away from the center of the chart - labelElement.textDirection = - labelLeftOfChart ? TextDirection.rtl : TextDirection.ltr; - - // Skip this label if it collides with the previously drawn label. - if (_detectOutsideLabelCollision(labelY, labelLeftOfChart, - previousOutsideLabelY, previousLabelLeftOfChart)) { - return null; - } - - if (showLeaderLines) { - final tailX = _drawLeaderLine(canvas, labelLeftOfChart, labelPoint, - arcElements.radius, arcElements.center, centerAngle); - - // Shift the label horizontally by the length of the leader line. - labelX = (labelX + tailX).round(); - - labelElement.maxWidth = (labelElement.maxWidth - tailX).round(); - } - - canvas.drawText(labelElement, labelX, labelY); - - // Return a structured list of values. - return [labelLeftOfChart, labelY]; - } - - /// Detects whether the current outside label collides with the previous label. - bool _detectOutsideLabelCollision(num labelY, bool labelLeftOfChart, - num previousOutsideLabelY, bool previousLabelLeftOfChart) { - bool collides = false; - - // Given that labels are vertically centered, we can assume they will - // collide if the current label's Y coordinate +/- the font size - // crosses past the Y coordinate of the previous label drawn on the - // same side of the chart. - if (previousOutsideLabelY != null && - labelLeftOfChart == previousLabelLeftOfChart) { - if (labelY > previousOutsideLabelY) { - if (labelY - outsideLabelStyleSpec.fontSize <= previousOutsideLabelY) { - collides = true; - } - } else { - if (labelY + outsideLabelStyleSpec.fontSize >= previousOutsideLabelY) { - collides = true; - } - } - } - - return collides; - } - - /// Draws a leader line for the current arc. - double _drawLeaderLine( - ChartCanvas canvas, - bool labelLeftOfChart, - Point labelPoint, - double radius, - Point arcCenterPoint, - double centerAngle) { - final tailX = (labelLeftOfChart ? -1 : 1) * leaderLineStyleSpec.length; - - final leaderLineTailPoint = - Point(labelPoint.x + tailX, labelPoint.y); - - final centerRadius = radius - leaderLineStyleSpec.length / 2; - final leaderLineStartPoint = Point( - arcCenterPoint.x + centerRadius * cos(centerAngle), - arcCenterPoint.y + centerRadius * sin(centerAngle)); - - canvas.drawLine( - points: [ - leaderLineStartPoint, - labelPoint, - leaderLineTailPoint, - ], - stroke: leaderLineStyleSpec.color, - strokeWidthPx: leaderLineStyleSpec.thickness); - - return tailX; - } -} - -/// Configures where to place the label relative to the arcs. -enum ArcLabelPosition { - /// Automatically try to place the label inside the arc first and place it on - /// the outside of the space available outside the arc is greater than space - /// available inside the arc. - auto, - - /// Always place label on the outside. - outside, - - /// Always place label on the inside. - inside, -} - -/// Style configuration for leader lines. -@immutable -class ArcLabelLeaderLineStyleSpec { - final Color color; - final double length; - final double thickness; - - ArcLabelLeaderLineStyleSpec({this.color, this.length, this.thickness}); - - @override - bool operator ==(Object other) { - return other is ArcLabelLeaderLineStyleSpec && - color == other.color && - thickness == other.thickness && - length == other.length; - } - - @override - int get hashCode { - int hashcode = color?.hashCode ?? 0; - hashcode = (hashcode * 37) + thickness?.hashCode ?? 0; - hashcode = (hashcode * 37) + length?.hashCode ?? 0; - return hashcode; - } -} diff --git a/web/charts/common/lib/src/chart/pie/arc_renderer.dart b/web/charts/common/lib/src/chart/pie/arc_renderer.dart deleted file mode 100644 index 69cef8f46..000000000 --- a/web/charts/common/lib/src/chart/pie/arc_renderer.dart +++ /dev/null @@ -1,709 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show atan2, cos, max, sin, pi, Point, Rectangle; - -import 'package:meta/meta.dart' show required; - -import '../../common/color.dart' show Color; -import '../../common/style/style_factory.dart' show StyleFactory; -import '../../data/series.dart' show AttributeKey; -import '../common/base_chart.dart' show BaseChart; -import '../common/canvas_shapes.dart' show CanvasPieSlice, CanvasPie; -import '../common/chart_canvas.dart' show ChartCanvas, getAnimatedColor; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import '../common/series_datum.dart' show SeriesDatum; -import '../common/series_renderer.dart' show BaseSeriesRenderer; -import 'arc_renderer_config.dart' show ArcRendererConfig; -import 'arc_renderer_decorator.dart' show ArcRendererDecorator; - -const arcElementsKey = - AttributeKey>('ArcRenderer.elements'); - -class ArcRenderer extends BaseSeriesRenderer { - // Constant used in the calculation of [centerContentBounds], calculated once - // to save runtime cost. - static final _cosPIOver4 = cos(pi / 4); - - final ArcRendererConfig config; - - final List arcRendererDecorators; - - BaseChart _chart; - - /// Store a map of series drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was given to the chart. - final _seriesArcMap = LinkedHashMap>(); - - // Store a list of arcs that exist in the series data. - // - // This list will be used to remove any [_AnimatedArc] that were rendered in - // previous draw cycles, but no longer have a corresponding datum in the new - // data. - final _currentKeys = []; - - factory ArcRenderer({String rendererId, ArcRendererConfig config}) { - return ArcRenderer._internal( - rendererId: rendererId ?? 'line', - config: config ?? ArcRendererConfig()); - } - - ArcRenderer._internal({String rendererId, this.config}) - : arcRendererDecorators = config?.arcRendererDecorators ?? [], - super( - rendererId: rendererId, - layoutPaintOrder: config.layoutPaintOrder, - symbolRenderer: config.symbolRenderer); - - @override - void onAttach(BaseChart chart) { - super.onAttach(chart); - _chart = chart; - } - - @override - void configureSeries(List> seriesList) { - assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: false); - } - - @override - void preprocessSeries(List> seriesList) { - seriesList.forEach((series) { - var elements = >[]; - - var domainFn = series.domainFn; - var measureFn = series.measureFn; - - final seriesMeasureTotal = series.seriesMeasureTotal; - - // On the canvas, arc measurements are defined as angles from the positive - // x axis. Start our first slice at the positive y axis instead. - var startAngle = config.startAngle; - var arcLength = config.arcLength; - - var totalAngle = 0.0; - - var measures = []; - - if (series.data.isEmpty) { - // If the series has no data, generate an empty arc element that - // occupies the entire chart. - // - // Use a tiny epsilon difference to ensure that the canvas renders a - // "full" circle, in the correct direction. - var angle = arcLength == 2 * pi ? arcLength * .999999 : arcLength; - var endAngle = startAngle + angle; - - var details = ArcRendererElement(); - details.startAngle = startAngle; - details.endAngle = endAngle; - details.index = 0; - details.key = 0; - details.series = series; - - elements.add(details); - } else { - // Otherwise, generate an arc element per datum. - for (var arcIndex = 0; arcIndex < series.data.length; arcIndex++) { - var domain = domainFn(arcIndex); - var measure = measureFn(arcIndex); - measures.add(measure); - if (measure == null) { - continue; - } - - final percentOfSeries = (measure / seriesMeasureTotal); - var angle = arcLength * percentOfSeries; - var endAngle = startAngle + angle; - - var details = ArcRendererElement(); - details.startAngle = startAngle; - details.endAngle = endAngle; - details.index = arcIndex; - details.key = arcIndex; - details.domain = domain; - details.series = series; - - elements.add(details); - - // Update the starting angle for the next datum in the series. - startAngle = endAngle; - - totalAngle = totalAngle + angle; - } - } - - series.setAttr(arcElementsKey, elements); - }); - } - - void update(List> seriesList, bool isAnimatingThisDraw) { - _currentKeys.clear(); - - final bounds = _chart.drawAreaBounds; - - final center = Point((bounds.left + bounds.width / 2).toDouble(), - (bounds.top + bounds.height / 2).toDouble()); - - final radius = bounds.height < bounds.width - ? (bounds.height / 2).toDouble() - : (bounds.width / 2).toDouble(); - - if (config.arcRatio != null) { - if (0 < config.arcRatio || config.arcRatio > 1) { - throw ArgumentError('arcRatio must be between 0 and 1'); - } - } - - final innerRadius = _calculateInnerRadius(radius); - - seriesList.forEach((series) { - var colorFn = series.colorFn; - var arcListKey = series.id; - - var arcList = - _seriesArcMap.putIfAbsent(arcListKey, () => _AnimatedArcList()); - - var elementsList = series.getAttr(arcElementsKey); - - if (series.data.isEmpty) { - // If the series is empty, set up the "no data" arc element. This should - // occupy the entire chart, and use the chart style's no data color. - final details = elementsList[0]; - - var arcKey = '__no_data__'; - - // If we already have an AnimatingArc for that index, use it. - var animatingArc = arcList.arcs - .firstWhere((arc) => arc.key == arcKey, orElse: () => null); - - arcList.center = center; - arcList.radius = radius; - arcList.innerRadius = innerRadius; - arcList.series = series; - arcList.stroke = config.noDataColor; - arcList.strokeWidthPx = 0.0; - - // If we don't have any existing arc element, create a new arc. Unlike - // real arcs, we should not animate the no data state in from 0. - if (animatingArc == null) { - animatingArc = _AnimatedArc(arcKey, null, null); - arcList.arcs.add(animatingArc); - } else { - animatingArc.datum = null; - animatingArc.domain = null; - } - - // Update the set of arcs that still exist in the series data. - _currentKeys.add(arcKey); - - // Get the arcElement we are going to setup. - // Optimization to prevent allocation in non-animating case. - final arcElement = ArcRendererElement() - ..color = config.noDataColor - ..startAngle = details.startAngle - ..endAngle = details.endAngle - ..series = series; - - animatingArc.setNewTarget(arcElement); - } else { - var previousEndAngle = config.startAngle; - - for (var arcIndex = 0; arcIndex < series.data.length; arcIndex++) { - final datum = series.data[arcIndex]; - final details = elementsList[arcIndex]; - D domainValue = details.domain; - - var arcKey = domainValue.toString(); - - // If we already have an AnimatingArc for that index, use it. - var animatingArc = arcList.arcs - .firstWhere((arc) => arc.key == arcKey, orElse: () => null); - - arcList.center = center; - arcList.radius = radius; - arcList.innerRadius = innerRadius; - arcList.series = series; - arcList.stroke = config.stroke; - arcList.strokeWidthPx = config.strokeWidthPx; - - // If we don't have any existing arc element, create a new arc and - // have it animate in from the position of the previous arc's end - // angle. If there were no previous arcs, then animate everything in - // from 0. - if (animatingArc == null) { - animatingArc = _AnimatedArc(arcKey, datum, domainValue) - ..setNewTarget(ArcRendererElement() - ..color = colorFn(arcIndex) - ..startAngle = previousEndAngle - ..endAngle = previousEndAngle - ..index = arcIndex - ..series = series); - - arcList.arcs.add(animatingArc); - } else { - animatingArc.datum = datum; - - previousEndAngle = animatingArc.previousArcEndAngle ?? 0.0; - } - - animatingArc.domain = domainValue; - - // Update the set of arcs that still exist in the series data. - _currentKeys.add(arcKey); - - // Get the arcElement we are going to setup. - // Optimization to prevent allocation in non-animating case. - final arcElement = ArcRendererElement() - ..color = colorFn(arcIndex) - ..startAngle = details.startAngle - ..endAngle = details.endAngle - ..index = arcIndex - ..series = series; - - animatingArc.setNewTarget(arcElement); - } - } - }); - - // Animate out arcs that don't exist anymore. - _seriesArcMap.forEach((key, arcList) { - for (var arcIndex = 0; arcIndex < arcList.arcs.length; arcIndex++) { - final arc = arcList.arcs[arcIndex]; - final arcStartAngle = arc.previousArcStartAngle; - - if (_currentKeys.contains(arc.key) != true) { - // Default to animating out to the top of the chart, clockwise, if - // there are no arcs that start past this arc. - var targetArcAngle = (2 * pi) + config.startAngle; - - // Find the nearest start angle of the next arc that still exists in - // the data. - for (_AnimatedArc nextArc - in arcList.arcs.where((arc) => _currentKeys.contains(arc.key))) { - final nextArcStartAngle = nextArc.newTargetArcStartAngle; - - if (arcStartAngle < nextArcStartAngle && - nextArcStartAngle < targetArcAngle) { - targetArcAngle = nextArcStartAngle; - } - } - - arc.animateOut(targetArcAngle); - } - } - }); - } - - void paint(ChartCanvas canvas, double animationPercent) { - // Clean up the arcs that no longer exist. - if (animationPercent == 1.0) { - final keysToRemove = []; - - _seriesArcMap.forEach((key, arcList) { - arcList.arcs.removeWhere((arc) => arc.animatingOut); - - if (arcList.arcs.isEmpty) { - keysToRemove.add(key); - } - }); - - keysToRemove.forEach(_seriesArcMap.remove); - } - - _seriesArcMap.forEach((key, arcList) { - final circleSectors = []; - final arcElementsList = ArcRendererElementList() - ..arcs = >[] - ..center = arcList.center - ..innerRadius = arcList.innerRadius - ..radius = arcList.radius - ..startAngle = config.startAngle - ..stroke = arcList.stroke - ..strokeWidthPx = arcList.strokeWidthPx; - - arcList.arcs - .map>( - (animatingArc) => animatingArc.getCurrentArc(animationPercent)) - .forEach((arc) { - circleSectors - .add(CanvasPieSlice(arc.startAngle, arc.endAngle, fill: arc.color)); - - arcElementsList.arcs.add(arc); - }); - - // Decorate the arcs with decorators that should appear below the main - // series data. - arcRendererDecorators - .where((decorator) => !decorator.renderAbove) - .forEach((decorator) { - decorator.decorate(arcElementsList, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: animationPercent, - rtl: isRtl); - }); - - // Draw the arcs. - canvas.drawPie(CanvasPie( - circleSectors, arcList.center, arcList.radius, arcList.innerRadius, - stroke: arcList.stroke, strokeWidthPx: arcList.strokeWidthPx)); - - // Decorate the arcs with decorators that should appear above the main - // series data. This is the typical place for labels. - arcRendererDecorators - .where((decorator) => decorator.renderAbove) - .forEach((decorator) { - decorator.decorate(arcElementsList, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: animationPercent, - rtl: isRtl); - }); - }); - } - - bool get isRtl => _chart?.context?.isRtl ?? false; - - /// Gets a bounding box for the largest center content card that can fit - /// inside the hole of the chart. - /// - /// If the inner radius of the arcs is smaller than - /// [ArcRendererConfig.minHoleWidthForCenterContent], this will return a - /// rectangle of 0 width and height to indicate that no card can fit inside - /// the chart. - Rectangle get centerContentBounds { - // Grab the first arcList from the animated set. - var arcList = _seriesArcMap.isNotEmpty ? _seriesArcMap.values.first : null; - - // No card should be visible if the hole in the chart is too small. - if (arcList == null || - arcList.innerRadius < config.minHoleWidthForCenterContent) { - // Return default bounds of 0 size. - final bounds = _chart.drawAreaBounds; - return Rectangle((bounds.left + bounds.width / 2).round(), - (bounds.top + bounds.height / 2).round(), 0, 0); - } - - // Fix the height and width of the center content div to the maximum box - // size that will fit within the pie's inner radius. - final width = (_cosPIOver4 * arcList.innerRadius).floor(); - - return Rectangle((arcList.center.x - width).round(), - (arcList.center.y - width).round(), width * 2, width * 2); - } - - /// Returns an expanded [DatumDetails] object that contains location data. - DatumDetails getExpandedDatumDetails(SeriesDatum seriesDatum) { - final series = seriesDatum.series; - final datum = seriesDatum.datum; - final datumIndex = seriesDatum.index; - - final domain = series.domainFn(datumIndex); - final measure = series.measureFn(datumIndex); - final color = series.colorFn(datumIndex); - - final chartPosition = _getChartPosition(series.id, domain.toString()); - - return DatumDetails( - datum: datum, - domain: domain, - measure: measure, - series: series, - color: color, - chartPosition: chartPosition); - } - - /// Returns the chart position for a given datum by series ID and domain - /// value. - /// - /// [seriesId] the series ID. - /// - /// [key] the key in the current animated arc list. - Point _getChartPosition(String seriesId, String key) { - Point chartPosition; - - final arcList = _seriesArcMap[seriesId]; - - if (arcList == null) { - return chartPosition; - } - - for (_AnimatedArc arc in arcList.arcs) { - if (arc.key == key) { - // Now that we have found the matching arc, calculate the center point - // halfway between the inner and outer radius, and the start and end - // angles. - final centerAngle = arc.currentArcStartAngle + - (arc.currentArcEndAngle - arc.currentArcStartAngle) / 2; - - final centerPointRadius = - arcList.innerRadius + (arcList.radius - arcList.innerRadius) / 2; - - chartPosition = Point( - centerPointRadius * cos(centerAngle) + arcList.center.x, - centerPointRadius * sin(centerAngle) + arcList.center.y); - - break; - } - } - - return chartPosition; - } - - @override - List> getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride) { - final nearest = >[]; - - // Was it even in the component bounds? - if (!isPointWithinBounds(chartPoint, boundsOverride)) { - return nearest; - } - - _seriesArcMap.forEach((key, arcList) { - if (arcList.series.overlaySeries) { - return; - } - - final center = arcList.center; - final innerRadius = arcList.innerRadius; - final radius = arcList.radius; - - final distance = center.distanceTo(chartPoint); - - // Calculate the angle of [chartPoint] from the center of the arcs. - var chartPointAngle = - atan2(chartPoint.y - center.y, chartPoint.x - center.x); - - // atan2 returns NaN if we are at the exact center of the circle. - if (chartPointAngle.isNaN) { - chartPointAngle = config.startAngle; - } - - // atan2 returns an angle in the range -PI..PI, from the positive x-axis. - // Our arcs start at the positive y-axis, in the range -PI/2..3PI/2. Thus, - // if angle is in the -x, +y section of the circle, we need to adjust the - // angle into our range. - if (chartPointAngle < config.startAngle && chartPointAngle < 0) { - chartPointAngle = 2 * pi + chartPointAngle; - } - - arcList.arcs.forEach((arc) { - if (innerRadius <= distance && distance <= radius) { - if (arc.currentArcStartAngle <= chartPointAngle && - chartPointAngle <= arc.currentArcEndAngle) { - nearest.add(DatumDetails( - series: arcList.series, - datum: arc.datum, - domain: arc.domain, - domainDistance: 0.0, - measureDistance: 0.0, - )); - } - } - }); - }); - - return nearest; - } - - @override - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - final chartPosition = - _getChartPosition(details.series.id, details.domain.toString()); - - return DatumDetails.from(details, chartPosition: chartPosition); - } - - /// Assigns colors to series that are missing their colorFn. - @override - assignMissingColors(Iterable seriesList, - {@required bool emptyCategoryUsesSinglePalette}) { - int maxMissing = 0; - - seriesList.forEach((series) { - if (series.colorFn == null) { - maxMissing = max(maxMissing, series.data.length); - } - }); - - if (maxMissing > 0) { - final colorPalettes = StyleFactory.style.getOrderedPalettes(1); - final colorPalette = colorPalettes[0].makeShades(maxMissing); - - seriesList.forEach((series) { - series.colorFn ??= (index) => colorPalette[index]; - }); - } - } - - /// Calculates the size of the inner pie radius given the outer radius. - double _calculateInnerRadius(double radius) { - // arcRatio trumps arcWidth. If neither is defined, then inner radius is 0. - if (config.arcRatio != null) { - return max(radius - radius * config.arcRatio, 0.0).toDouble(); - } else if (config.arcWidth != null) { - return max(radius - config.arcWidth, 0.0).toDouble(); - } else { - return 0.0; - } - } -} - -class ArcRendererElementList { - List> arcs; - Point center; - double innerRadius; - double radius; - double startAngle; - - /// Color of separator lines between arcs. - Color stroke; - - /// Stroke width of separator lines between arcs. - double strokeWidthPx; -} - -class ArcRendererElement { - double startAngle; - double endAngle; - Color color; - int index; - num key; - D domain; - ImmutableSeries series; - - ArcRendererElement clone() { - return ArcRendererElement() - ..startAngle = startAngle - ..endAngle = endAngle - ..color = Color.fromOther(color: color) - ..index = index - ..key = key - ..series = series; - } - - void updateAnimationPercent(ArcRendererElement previous, - ArcRendererElement target, double animationPercent) { - startAngle = - ((target.startAngle - previous.startAngle) * animationPercent) + - previous.startAngle; - - endAngle = ((target.endAngle - previous.endAngle) * animationPercent) + - previous.endAngle; - - color = getAnimatedColor(previous.color, target.color, animationPercent); - } -} - -class _AnimatedArcList { - final arcs = <_AnimatedArc>[]; - Point center; - double innerRadius; - double radius; - ImmutableSeries series; - - /// Color of separator lines between arcs. - Color stroke; - - /// Stroke width of separator lines between arcs. - double strokeWidthPx; -} - -class _AnimatedArc { - final String key; - dynamic datum; - D domain; - - ArcRendererElement _previousArc; - ArcRendererElement _targetArc; - ArcRendererElement _currentArc; - - // Flag indicating whether this arc is being animated out of the chart. - bool animatingOut = false; - - _AnimatedArc(this.key, this.datum, this.domain); - - /// Animates a arc that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for arcs that represent - /// data that has been removed from the series. - /// - /// Animates the angle of the arc to [endAngle], in radians. - void animateOut(endAngle) { - var newTarget = _currentArc.clone(); - - // Animate the arc out by setting the angles to 0. - newTarget.startAngle = endAngle; - newTarget.endAngle = endAngle; - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(ArcRendererElement newTarget) { - animatingOut = false; - _currentArc ??= newTarget.clone(); - _previousArc = _currentArc.clone(); - _targetArc = newTarget; - } - - ArcRendererElement getCurrentArc(double animationPercent) { - if (animationPercent == 1.0 || _previousArc == null) { - _currentArc = _targetArc; - _previousArc = _targetArc; - return _currentArc; - } - - _currentArc.updateAnimationPercent( - _previousArc, _targetArc, animationPercent); - - return _currentArc; - } - - /// Returns the [startAngle] of the new target element, without updating - /// animation state. - double get newTargetArcStartAngle { - return _targetArc != null ? _targetArc.startAngle : null; - } - - /// Returns the [endAngle] of the new target element, without updating - /// animation state. - double get currentArcEndAngle { - return _currentArc != null ? _currentArc.endAngle : null; - } - - /// Returns the [startAngle] of the currently rendered element, without - /// updating animation state. - double get currentArcStartAngle { - return _currentArc != null ? _currentArc.startAngle : null; - } - - /// Returns the [endAngle] of the new target element, without updating - /// animation state. - double get previousArcEndAngle { - return _previousArc != null ? _previousArc.endAngle : null; - } - - /// Returns the [startAngle] of the previously rendered element, without - /// updating animation state. - double get previousArcStartAngle { - return _previousArc != null ? _previousArc.startAngle : null; - } -} diff --git a/web/charts/common/lib/src/chart/pie/arc_renderer_config.dart b/web/charts/common/lib/src/chart/pie/arc_renderer_config.dart deleted file mode 100644 index 3844c0530..000000000 --- a/web/charts/common/lib/src/chart/pie/arc_renderer_config.dart +++ /dev/null @@ -1,94 +0,0 @@ -// 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 pi; - -import '../../common/color.dart' show Color; -import '../../common/style/style_factory.dart' show StyleFactory; -import '../../common/symbol_renderer.dart'; -import '../common/series_renderer_config.dart' - show RendererAttributes, SeriesRendererConfig; -import '../layout/layout_view.dart' show LayoutViewConfig, LayoutViewPaintOrder; -import 'arc_renderer.dart' show ArcRenderer; -import 'arc_renderer_decorator.dart' show ArcRendererDecorator; - -/// Configuration for an [ArcRenderer]. -class ArcRendererConfig extends LayoutViewConfig - implements SeriesRendererConfig { - final String customRendererId; - - /// List of decorators applied to rendered arcs. - final List arcRendererDecorators; - - final SymbolRenderer symbolRenderer; - - final rendererAttributes = RendererAttributes(); - - /// Total arc length, in radians. - /// - /// The default arcLength is 2π. - final double arcLength; - - /// If set, configures the arcWidth to be a percentage of the radius. - final double arcRatio; - - /// Fixed width of the arc within the radius. - /// - /// If arcRatio is set, this value will be ignored. - final int arcWidth; - - /// The order to paint this renderer on the canvas. - final int layoutPaintOrder; - - /// Minimum radius in pixels of the hole in a donut chart for center content - /// to appear. - final int minHoleWidthForCenterContent; - - /// Start angle for pie slices, in radians. - /// - /// Angles are defined from the positive x axis in Cartesian space. The - /// default startAngle is -π/2. - final double startAngle; - - /// Stroke width of the border of the arcs. - final double strokeWidthPx; - - /// Stroke color of the border of the arcs. - final Color stroke; - - /// Color of the "no data" state for the chart, used when an empty series is - /// drawn. - final Color noDataColor; - - ArcRendererConfig( - {this.customRendererId, - this.arcLength = 2 * pi, - this.arcRendererDecorators = const [], - this.arcRatio, - this.arcWidth, - this.layoutPaintOrder = LayoutViewPaintOrder.arc, - this.minHoleWidthForCenterContent = 30, - this.startAngle = -pi / 2, - this.strokeWidthPx = 2.0, - SymbolRenderer symbolRenderer}) - : this.noDataColor = StyleFactory.style.noDataColor, - this.stroke = StyleFactory.style.white, - this.symbolRenderer = symbolRenderer ?? CircleSymbolRenderer(); - - @override - ArcRenderer build() { - return ArcRenderer(config: this, rendererId: customRendererId); - } -} diff --git a/web/charts/common/lib/src/chart/pie/arc_renderer_decorator.dart b/web/charts/common/lib/src/chart/pie/arc_renderer_decorator.dart deleted file mode 100644 index 4d2286d3d..000000000 --- a/web/charts/common/lib/src/chart/pie/arc_renderer_decorator.dart +++ /dev/null @@ -1,37 +0,0 @@ -// 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' show required; - -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../common/chart_canvas.dart' show ChartCanvas; -import 'arc_renderer.dart' show ArcRendererElementList; - -/// Decorates arcs after the arcs have already been painted. -abstract class ArcRendererDecorator { - const ArcRendererDecorator(); - - /// Configures whether the decorator should be rendered on top of or below - /// series data elements. - bool get renderAbove; - - void decorate(ArcRendererElementList arcElements, ChartCanvas canvas, - GraphicsFactory graphicsFactory, - {@required Rectangle drawBounds, - @required double animationPercent, - bool rtl = false}); -} diff --git a/web/charts/common/lib/src/chart/pie/pie_chart.dart b/web/charts/common/lib/src/chart/pie/pie_chart.dart deleted file mode 100644 index 934105fce..000000000 --- a/web/charts/common/lib/src/chart/pie/pie_chart.dart +++ /dev/null @@ -1,84 +0,0 @@ -// 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 '../common/base_chart.dart' show BaseChart; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show MutableSeries; -import '../common/selection_model/selection_model.dart' show SelectionModelType; -import '../common/series_renderer.dart' show rendererIdKey, SeriesRenderer; -import '../layout/layout_config.dart' show LayoutConfig, MarginSpec; -import 'arc_renderer.dart' show ArcRenderer; - -class PieChart extends BaseChart { - static final _defaultLayoutConfig = LayoutConfig( - topSpec: MarginSpec.fromPixel(minPixel: 20), - bottomSpec: MarginSpec.fromPixel(minPixel: 20), - leftSpec: MarginSpec.fromPixel(minPixel: 20), - rightSpec: MarginSpec.fromPixel(minPixel: 20), - ); - - PieChart({LayoutConfig layoutConfig}) - : super(layoutConfig: layoutConfig ?? _defaultLayoutConfig); - - @override - void drawInternal(List> seriesList, - {bool skipAnimation, bool skipLayout}) { - if (seriesList.length > 1) { - throw ArgumentError('PieChart can only render a single series'); - } - super.drawInternal(seriesList, - skipAnimation: skipAnimation, skipLayout: skipLayout); - } - - @override - SeriesRenderer makeDefaultRenderer() { - return ArcRenderer()..rendererId = SeriesRenderer.defaultRendererId; - } - - /// Returns a list of datum details from selection model of [type]. - @override - List> getDatumDetails(SelectionModelType type) { - final entries = >[]; - - getSelectionModel(type).selectedDatum.forEach((seriesDatum) { - final rendererId = seriesDatum.series.getAttr(rendererIdKey); - final renderer = getSeriesRenderer(rendererId); - - // This should never happen. - if (!(renderer is ArcRenderer)) { - return; - } - - final details = - (renderer as ArcRenderer).getExpandedDatumDetails(seriesDatum); - - if (details != null) { - entries.add(details); - } - }); - - return entries; - } - - Rectangle get centerContentBounds { - if (defaultRenderer is ArcRenderer) { - return (defaultRenderer as ArcRenderer).centerContentBounds; - } else { - return null; - } - } -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/comparison_points_decorator.dart b/web/charts/common/lib/src/chart/scatter_plot/comparison_points_decorator.dart deleted file mode 100644 index 834a1accc..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/comparison_points_decorator.dart +++ /dev/null @@ -1,239 +0,0 @@ -// 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 'package:meta/meta.dart' show protected, required; - -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../../common/symbol_renderer.dart'; -import '../common/chart_canvas.dart' show ChartCanvas; -import 'point_renderer.dart' show PointRendererElement; -import 'point_renderer_decorator.dart' show PointRendererDecorator; - -/// Decorates a point chart by drawing a shape connecting the domain and measure -/// data bounds. -/// -/// The line will connect the point (domainLowerBound, measureLowerBound) to the -/// point (domainUpperBound, measureUpperBound). -class ComparisonPointsDecorator extends PointRendererDecorator { - /// Renderer used to draw the points. Defaults to a line with circular end - /// caps. - final PointSymbolRenderer symbolRenderer; - - /// Render the bounds shape underneath series data. - final bool renderAbove = false; - - ComparisonPointsDecorator({PointSymbolRenderer symbolRenderer}) - : this.symbolRenderer = symbolRenderer ?? CylinderSymbolRenderer(); - - @override - void decorate(PointRendererElement pointElement, ChartCanvas canvas, - GraphicsFactory graphicsFactory, - {@required Rectangle drawBounds, - @required double animationPercent, - bool rtl = false}) { - final points = computeBoundedPointsForElement(pointElement, drawBounds); - - if (points == null) { - return; - } - - final color = pointElement.color.lighter; - - symbolRenderer.paint(canvas, points[0], pointElement.boundsLineRadiusPx, - fillColor: color, strokeColor: color, p2: points[1]); - } - - /// Computes end points for the [pointElement]'s lower and upper data bounds. - /// - /// This will compute two points representing the end points of the symbol, - /// from (xLower, yLower) to (xUpper, yUpper). The end points will be clamped - /// along the line so that it is fully contained within [drawBounds]. - /// - /// Returns null if [pointElement] is missing any of the data bounds, or if - /// the line connecting them is located entirely outside of [drawBounds]. - @protected - List> computeBoundedPointsForElement( - PointRendererElement pointElement, Rectangle drawBounds) { - // All bounds points must be defined for a valid comparison point to be - // drawn. - if (pointElement.point.xLower == null || - pointElement.point.xUpper == null || - pointElement.point.yLower == null || - pointElement.point.yUpper == null) { - return null; - } - - // Construct the points that describe our line p1p2. - var p1 = - Point(pointElement.point.xLower, pointElement.point.yLower); - var p2 = - Point(pointElement.point.xUpper, pointElement.point.yUpper); - - // First check to see if there is no intersection at all between the line - // p1p2 and [drawBounds]. - final dataBoundsRect = Rectangle.fromPoints(p1, p2); - if (!drawBounds.intersects(dataBoundsRect)) { - return null; - } - - // Line with end points [p1] and [p2]. - final p1p2 = _Line.fromPoints(p1, p2); - - // Next, slide p1 along the line p1p2 towards the edge of the draw area if - // the point is located outside of it. - if (!drawBounds.containsPoint(p1)) { - final p = _clampPointAlongLineToBoundingBox(p1, p1p2, drawBounds); - if (p != null) { - p1 = p; - } - } - - // Next, slide p2 along the line p1p2 towards the edge of the draw area if - // the point is located outside of it. - if (!drawBounds.containsPoint(p2)) { - final p = _clampPointAlongLineToBoundingBox(p2, p1p2, drawBounds); - if (p != null) { - p2 = p; - } - } - - return [p1, p2]; - } - - /// Slide the given point [p1] along the line [line], such that it intersects - /// the nearest edge of [bounds]. - /// - /// This method assumes that we have already verified that the [line] - /// intercepts the [bounds] somewhere. - Point _clampPointAlongLineToBoundingBox( - Point p1, _Line line, Rectangle bounds) { - // The top and bottom edges of the bounds box describe two horizontal lines, - // with equations y = bounds.top and y = bounds.bottom. We can pass these - // into a standard line interception method to find our point. - if (p1.y < bounds.top) { - final p = line.intersection(_Line(0.0, bounds.top.toDouble())); - if (p != null && bounds.containsPoint(p)) { - return p; - } - } - - if (p1.y > bounds.bottom) { - final p = line.intersection(_Line(0.0, bounds.bottom.toDouble())); - if (p != null && bounds.containsPoint(p)) { - return p; - } - } - - // The left and right edges of the bounds box describe two vertical lines, - // with equations x = bounds.right and x = bounds.left. To find the - // intersection, we just need to solve for y in our line described by - // [slope] and [yIntercept]: - // - // y = slope * x + yIntercept - if (p1.x < bounds.left) { - final p = line.intersection(_Line.fromVertical(bounds.left.toDouble())); - if (p != null && bounds.containsPoint(p)) { - return p; - } - } - - if (p1.x > bounds.right) { - final p = line.intersection(_Line.fromVertical(bounds.right.toDouble())); - if (p != null && bounds.containsPoint(p)) { - return p; - } - } - - return null; - } -} - -/// Describes a simple line with the equation y = slope * x + yIntercept. -class _Line { - /// Slope of the line. - double slope; - - /// y-intercept of the line (i.e. the y value of the point where the line - /// intercepts the y axis). - double yIntercept; - - /// x-intercept of the line (i.e. the x value of the point where the line - /// intercepts the x axis). This is normally only needed for vertical lines, - /// which have no slope. - double xIntercept; - - /// True if this line is a vertical line, of the form x = [xIntercept]. - bool get vertical => slope == null && xIntercept != null; - - _Line(this.slope, this.yIntercept, [this.xIntercept]); - - /// Creates a line with end points [p1] and [p2]. - factory _Line.fromPoints(Point p1, Point p2) { - // Handle vertical lines. - if (p1.x == p2.x) { - return _Line.fromVertical(p1.x); - } - - // Slope of the line p1p2. - double m = ((p2.y - p1.y) / (p2.x - p1.x)).toDouble(); - - // y-intercept of the line p1p2. - double b = (p1.y - (m * p1.x)).toDouble(); - - return _Line(m, b); - } - - /// Creates a vertical line, with the question x = [xIntercept]. - factory _Line.fromVertical(num xIntercept) { - return _Line(null, null, xIntercept.toDouble()); - } - - /// Computes the intersection of `this` and [other]. - /// - /// Returns the intersection of this and `other`, or `null` if they don't - /// intersect. - Point intersection(_Line other) { - // Parallel lines have no intersection. - if (slope == other.slope || (vertical && other.vertical)) { - return null; - } - - // If the other line is a vertical line (has undefined slope), then we can - // just plug its xIntercept value into the line equation as x and solve for - // y. - if (other.vertical) { - return Point( - other.xIntercept, slope * other.xIntercept + yIntercept); - } - - // If this line is a vertical line (has undefined slope), then we can just - // plug its xIntercept value into the line equation as x and solve for y. - if (vertical) { - return Point( - xIntercept, other.slope * xIntercept + other.yIntercept); - } - - // Now that we know that we have intersecting, non-vertical lines, compute - // the intersection. - final x = (other.yIntercept - yIntercept) / (slope - other.slope); - - final y = slope * (other.yIntercept - yIntercept) / (slope - other.slope) + - yIntercept; - - return Point(x, y); - } -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/point_renderer.dart b/web/charts/common/lib/src/chart/scatter_plot/point_renderer.dart deleted file mode 100644 index ae9cc171b..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/point_renderer.dart +++ /dev/null @@ -1,860 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show min, Point, Rectangle; - -import 'package:meta/meta.dart' show protected, required; -import 'package:vector_math/vector_math.dart' show Vector2; - -import '../../common/color.dart' show Color; -import '../../common/math.dart' show distanceBetweenPointAndLineSegment; -import '../../common/symbol_renderer.dart' - show CircleSymbolRenderer, SymbolRenderer; -import '../../data/series.dart' show AccessorFn, AttributeKey, TypedAccessorFn; -import '../cartesian/axis/axis.dart' - show ImmutableAxis, domainAxisKey, measureAxisKey; -import '../cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; -import '../common/base_chart.dart' show BaseChart; -import '../common/chart_canvas.dart' show ChartCanvas, getAnimatedColor; -import '../common/datum_details.dart' show DatumDetails; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import '../common/series_datum.dart' show SeriesDatum; -import '../layout/layout_view.dart' show LayoutViewPaintOrder; -import 'comparison_points_decorator.dart' show ComparisonPointsDecorator; -import 'point_renderer_config.dart' show PointRendererConfig; -import 'point_renderer_decorator.dart' show PointRendererDecorator; - -const pointElementsKey = - AttributeKey>('PointRenderer.elements'); - -const pointSymbolRendererFnKey = - AttributeKey>('PointRenderer.symbolRendererFn'); - -const pointSymbolRendererIdKey = - AttributeKey('PointRenderer.symbolRendererId'); - -/// Defines a fixed radius for data bounds lines (typically drawn by attaching a -/// [ComparisonPointsDecorator] to the renderer. -const boundsLineRadiusPxKey = - AttributeKey('SymbolAnnotationRenderer.boundsLineRadiusPx'); - -/// Defines an [AccessorFn] for the radius for data bounds lines (typically -/// drawn by attaching a [ComparisonPointsDecorator] to the renderer. -const boundsLineRadiusPxFnKey = AttributeKey>( - 'SymbolAnnotationRenderer.boundsLineRadiusPxFn'); - -const defaultSymbolRendererId = '__default__'; - -/// Large number used as a starting sentinel for data distance comparisons. -/// -/// This is generally larger than the distance from any datum to the mouse. -const _maxInitialDistance = 10000.0; - -class PointRenderer extends BaseCartesianRenderer { - final PointRendererConfig config; - - final List pointRendererDecorators; - - BaseChart _chart; - - /// Store a map of series drawn on the chart, mapped by series name. - /// - /// [LinkedHashMap] is used to render the series on the canvas in the same - /// order as the data was given to the chart. - @protected - var seriesPointMap = LinkedHashMap>>(); - - // Store a list of lines that exist in the series data. - // - // This list will be used to remove any [_AnimatedPoint] that were rendered in - // previous draw cycles, but no longer have a corresponding datum in the new - // data. - final _currentKeys = []; - - PointRenderer({String rendererId, PointRendererConfig config}) - : this.config = config ?? PointRendererConfig(), - pointRendererDecorators = config?.pointRendererDecorators ?? [], - super( - rendererId: rendererId ?? 'point', - layoutPaintOrder: - config?.layoutPaintOrder ?? LayoutViewPaintOrder.point, - symbolRenderer: config?.symbolRenderer ?? CircleSymbolRenderer()); - - @override - void configureSeries(List> seriesList) { - assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: false); - } - - @override - void preprocessSeries(List> seriesList) { - seriesList.forEach((series) { - final elements = >[]; - - // Default to the configured radius if none was defined by the series. - series.radiusPxFn ??= (_) => config.radiusPx; - - // Create an accessor function for the bounds line radius, if needed. If - // the series doesn't define an accessor function, then each datum's - // boundsLineRadiusPx value will be filled in by using the following - // values, in order of what is defined: - // - // 1) boundsLineRadiusPx defined on the series. - // 2) boundsLineRadiusPx defined on the renderer config. - // 3) Final fallback is to use the point radiusPx for this datum. - var boundsLineRadiusPxFn = series.getAttr(boundsLineRadiusPxFnKey); - - if (boundsLineRadiusPxFn == null) { - var boundsLineRadiusPx = series.getAttr(boundsLineRadiusPxKey); - boundsLineRadiusPx ??= config.boundsLineRadiusPx; - if (boundsLineRadiusPx != null) { - boundsLineRadiusPxFn = (_) => boundsLineRadiusPx.toDouble(); - series.setAttr(boundsLineRadiusPxFnKey, boundsLineRadiusPxFn); - } - } - - final symbolRendererFn = series.getAttr(pointSymbolRendererFnKey); - - // Add a key function to help animate points moved in position in the - // series data between chart draw cycles. Ideally we should require the - // user to provide a key function, but this at least provides some - // smoothing when adding/removing data. - series.keyFn ??= (index) => '${series.id}__${series.domainFn(index)}__' - '${series.measureFn(index)}'; - - for (var index = 0; index < series.data.length; index++) { - // Default to the configured radius if none was returned by the - // accessor function. - var radiusPx = series.radiusPxFn(index); - radiusPx ??= config.radiusPx; - - num boundsLineRadiusPx; - if (boundsLineRadiusPxFn != null) { - boundsLineRadiusPx = (boundsLineRadiusPxFn is TypedAccessorFn) - ? (boundsLineRadiusPxFn as TypedAccessorFn)( - series.data[index], index) - : boundsLineRadiusPxFn(index); - } - boundsLineRadiusPx ??= config.boundsLineRadiusPx; - boundsLineRadiusPx ??= radiusPx; - - // Default to the configured stroke width if none was returned by the - // accessor function. - var strokeWidthPx = series.strokeWidthPxFn != null - ? series.strokeWidthPxFn(index) - : null; - strokeWidthPx ??= config.strokeWidthPx; - - // Get the ID of the [SymbolRenderer] for this point. An ID may be - // specified on the datum, or on the series. If neither is specified, - // fall back to the default. - String symbolRendererId; - if (symbolRendererFn != null) { - symbolRendererId = symbolRendererFn(index); - } - symbolRendererId ??= series.getAttr(pointSymbolRendererIdKey); - symbolRendererId ??= defaultSymbolRendererId; - - // Get the colors. If no fill color is provided, default it to the - // primary data color. - final colorFn = series.colorFn; - final fillColorFn = series.fillColorFn ?? colorFn; - - final color = colorFn(index); - - // Fill color is an optional override for color. Make sure we get a - // value if the series doesn't define anything specific. - var fillColor = fillColorFn(index); - fillColor ??= color; - - final details = PointRendererElement() - ..color = color - ..fillColor = fillColor - ..radiusPx = radiusPx.toDouble() - ..boundsLineRadiusPx = boundsLineRadiusPx.toDouble() - ..strokeWidthPx = strokeWidthPx.toDouble() - ..symbolRendererId = symbolRendererId; - - elements.add(details); - } - - series.setAttr(pointElementsKey, elements); - }); - } - - void update(List> seriesList, bool isAnimatingThisDraw) { - _currentKeys.clear(); - - // Build a list of sorted series IDs as we iterate through the list, used - // later for sorting. - final sortedSeriesIds = []; - - seriesList.forEach((series) { - sortedSeriesIds.add(series.id); - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final domainFn = series.domainFn; - final domainLowerBoundFn = series.domainLowerBoundFn; - final domainUpperBoundFn = series.domainUpperBoundFn; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - final measureFn = series.measureFn; - final measureLowerBoundFn = series.measureLowerBoundFn; - final measureUpperBoundFn = series.measureUpperBoundFn; - final measureOffsetFn = series.measureOffsetFn; - final seriesKey = series.id; - final keyFn = series.keyFn; - - var pointList = seriesPointMap.putIfAbsent(seriesKey, () => []); - - var elementsList = series.getAttr(pointElementsKey); - - for (var index = 0; index < series.data.length; index++) { - final datum = series.data[index]; - final details = elementsList[index]; - - D domainValue = domainFn(index); - D domainLowerBoundValue = - domainLowerBoundFn != null ? domainLowerBoundFn(index) : null; - D domainUpperBoundValue = - domainUpperBoundFn != null ? domainUpperBoundFn(index) : null; - - num measureValue = measureFn(index); - num measureLowerBoundValue = - measureLowerBoundFn != null ? measureLowerBoundFn(index) : null; - num measureUpperBoundValue = - measureUpperBoundFn != null ? measureUpperBoundFn(index) : null; - num measureOffsetValue = measureOffsetFn(index); - - // Create a new point using the final location. - final point = getPoint( - datum, - domainValue, - domainLowerBoundValue, - domainUpperBoundValue, - series, - domainAxis, - measureValue, - measureLowerBoundValue, - measureUpperBoundValue, - measureOffsetValue, - measureAxis); - - final pointKey = keyFn(index); - - // If we already have an AnimatingPoint for that index, use it. - var animatingPoint = pointList - .firstWhere((point) => point.key == pointKey, orElse: () => null); - - // If we don't have any existing arc element, create a new arc and - // have it animate in from the position of the previous arc's end - // angle. If there were no previous arcs, then animate everything in - // from 0. - if (animatingPoint == null) { - // Create a new point and have it animate in from axis. - final point = getPoint( - datum, - domainValue, - domainLowerBoundValue, - domainUpperBoundValue, - series, - domainAxis, - 0.0, - 0.0, - 0.0, - 0.0, - measureAxis); - - animatingPoint = AnimatedPoint( - key: pointKey, overlaySeries: series.overlaySeries) - ..setNewTarget(PointRendererElement() - ..color = details.color - ..fillColor = details.fillColor - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..point = point - ..radiusPx = details.radiusPx - ..boundsLineRadiusPx = details.boundsLineRadiusPx - ..strokeWidthPx = details.strokeWidthPx - ..symbolRendererId = details.symbolRendererId); - - pointList.add(animatingPoint); - } - - // Update the set of arcs that still exist in the series data. - _currentKeys.add(pointKey); - - // Get the pointElement we are going to setup. - final pointElement = PointRendererElement() - ..color = details.color - ..fillColor = details.fillColor - ..measureAxisPosition = measureAxis.getLocation(0.0) - ..point = point - ..radiusPx = details.radiusPx - ..boundsLineRadiusPx = details.boundsLineRadiusPx - ..strokeWidthPx = details.strokeWidthPx - ..symbolRendererId = details.symbolRendererId; - - animatingPoint.setNewTarget(pointElement); - } - }); - - // Sort the renderer elements to be in the same order as the series list. - // They may get disordered between chart draw cycles if a behavior adds or - // removes series from the list (e.g. click to hide on legends). - seriesPointMap = LinkedHashMap.fromIterable(sortedSeriesIds, - key: (k) => k, value: (k) => seriesPointMap[k]); - - // Animate out points that don't exist anymore. - seriesPointMap.forEach((key, points) { - for (var point in points) { - if (_currentKeys.contains(point.key) != true) { - point.animateOut(); - } - } - }); - } - - @override - void onAttach(BaseChart chart) { - super.onAttach(chart); - // We only need the chart.context.isRtl setting, but context is not yet - // available when the default renderer is attached to the chart on chart - // creation time, since chart onInit is called after the chart is created. - _chart = chart; - } - - void paint(ChartCanvas canvas, double animationPercent) { - // Clean up the points that no longer exist. - if (animationPercent == 1.0) { - final keysToRemove = []; - - seriesPointMap.forEach((key, points) { - points.removeWhere((point) => point.animatingOut); - - if (points.isEmpty) { - keysToRemove.add(key); - } - }); - - keysToRemove.forEach((key) => seriesPointMap.remove(key)); - } - - seriesPointMap.forEach((key, points) { - points - .map>((animatingPoint) => - animatingPoint.getCurrentPoint(animationPercent)) - .forEach((point) { - // Decorate the points with decorators that should appear below the main - // series data. - pointRendererDecorators - .where((decorator) => !decorator.renderAbove) - .forEach((decorator) { - decorator.decorate(point, canvas, graphicsFactory, - drawBounds: componentBounds, - animationPercent: animationPercent, - rtl: isRtl); - }); - - // Skip points whose center lies outside the draw bounds. Those that lie - // near the edge will be allowed to render partially outside. This - // prevents harshly clipping off half of the shape. - if (point.point.y != null && - componentBounds.containsPoint(point.point)) { - final bounds = Rectangle( - point.point.x - point.radiusPx, - point.point.y - point.radiusPx, - point.radiusPx * 2, - point.radiusPx * 2); - - if (point.symbolRendererId == defaultSymbolRendererId) { - symbolRenderer.paint(canvas, bounds, - fillColor: point.fillColor, - strokeColor: point.color, - strokeWidthPx: point.strokeWidthPx); - } else { - final id = point.symbolRendererId; - if (!config.customSymbolRenderers.containsKey(id)) { - throw ArgumentError('Invalid custom symbol renderer id "$id"'); - } - - final customRenderer = config.customSymbolRenderers[id]; - customRenderer.paint(canvas, bounds, - fillColor: point.fillColor, - strokeColor: point.color, - strokeWidthPx: point.strokeWidthPx); - } - } - - // Decorate the points with decorators that should appear above the main - // series data. This is the typical place for labels. - pointRendererDecorators - .where((decorator) => decorator.renderAbove) - .forEach((decorator) { - decorator.decorate(point, canvas, graphicsFactory, - drawBounds: componentBounds, - animationPercent: animationPercent, - rtl: isRtl); - }); - }); - }); - } - - bool get isRtl => _chart?.context?.isRtl ?? false; - - @protected - DatumPoint getPoint( - final datum, - D domainValue, - D domainLowerBoundValue, - D domainUpperBoundValue, - ImmutableSeries series, - ImmutableAxis domainAxis, - num measureValue, - num measureLowerBoundValue, - num measureUpperBoundValue, - num measureOffsetValue, - ImmutableAxis measureAxis) { - final domainPosition = domainAxis.getLocation(domainValue); - - final domainLowerBoundPosition = domainLowerBoundValue != null - ? domainAxis.getLocation(domainLowerBoundValue) - : null; - - final domainUpperBoundPosition = domainUpperBoundValue != null - ? domainAxis.getLocation(domainUpperBoundValue) - : null; - - final measurePosition = - measureAxis.getLocation(measureValue + measureOffsetValue); - - final measureLowerBoundPosition = measureLowerBoundValue != null - ? measureAxis.getLocation(measureLowerBoundValue + measureOffsetValue) - : null; - - final measureUpperBoundPosition = measureUpperBoundValue != null - ? measureAxis.getLocation(measureUpperBoundValue + measureOffsetValue) - : null; - - return DatumPoint( - datum: datum, - domain: domainValue, - series: series, - x: domainPosition, - xLower: domainLowerBoundPosition, - xUpper: domainUpperBoundPosition, - y: measurePosition, - yLower: measureLowerBoundPosition, - yUpper: measureUpperBoundPosition); - } - - @override - List> getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride) { - final nearest = >[]; - - // Was it even in the component bounds? - if (!isPointWithinBounds(chartPoint, boundsOverride)) { - return nearest; - } - - seriesPointMap.values.forEach((points) { - PointRendererElement nearestPoint; - double nearestDomainDistance = _maxInitialDistance; - double nearestMeasureDistance = _maxInitialDistance; - double nearestRelativeDistance = _maxInitialDistance; - - points.forEach((point) { - if (point.overlaySeries) { - return; - } - - Point p = point._currentPoint.point; - - // Don't look at points not in the drawArea. - if (p.x < componentBounds.left || p.x > componentBounds.right) { - return; - } - - final distances = _getDatumDistance(point, chartPoint); - - if (byDomain) { - if ((distances.domainDistance < nearestDomainDistance) || - ((distances.domainDistance == nearestDomainDistance && - distances.measureDistance < nearestMeasureDistance))) { - nearestPoint = point._currentPoint; - nearestDomainDistance = distances.domainDistance; - nearestMeasureDistance = distances.measureDistance; - nearestRelativeDistance = distances.relativeDistance; - } - } else { - if (distances.relativeDistance < nearestRelativeDistance) { - nearestPoint = point._currentPoint; - nearestDomainDistance = distances.domainDistance; - nearestMeasureDistance = distances.measureDistance; - nearestRelativeDistance = distances.relativeDistance; - } - } - }); - - // Found a point, add it to the list. - if (nearestPoint != null) { - SymbolRenderer nearestSymbolRenderer; - if (nearestPoint.symbolRendererId == defaultSymbolRendererId) { - nearestSymbolRenderer = symbolRenderer; - } else { - final id = nearestPoint.symbolRendererId; - if (!config.customSymbolRenderers.containsKey(id)) { - throw ArgumentError('Invalid custom symbol renderer id "$id"'); - } - - nearestSymbolRenderer = config.customSymbolRenderers[id]; - } - - nearest.add(DatumDetails( - datum: nearestPoint.point.datum, - domain: nearestPoint.point.domain, - series: nearestPoint.point.series, - domainDistance: nearestDomainDistance, - measureDistance: nearestMeasureDistance, - relativeDistance: nearestRelativeDistance, - symbolRenderer: nearestSymbolRenderer)); - } - }); - - // Note: the details are already sorted by domain & measure distance in - // base chart. - - return nearest; - } - - /// Returns a struct containing domain, measure, and relative distance between - /// a datum and a point within the chart. - _Distances _getDatumDistance( - AnimatedPoint point, Point chartPoint) { - final datumPoint = point._currentPoint.point; - final radiusPx = point._currentPoint.radiusPx; - final boundsLineRadiusPx = point._currentPoint.boundsLineRadiusPx; - - // Compute distances from [chartPoint] to the primary point of the datum. - final domainDistance = (chartPoint.x - datumPoint.x).abs(); - - final measureDistance = datumPoint.y != null - ? (chartPoint.y - datumPoint.y).abs() - : _maxInitialDistance; - - var relativeDistance = datumPoint.y != null - ? chartPoint.distanceTo(datumPoint) - : _maxInitialDistance; - - var insidePoint = false; - - if (datumPoint.xLower != null && - datumPoint.xUpper != null && - datumPoint.yLower != null && - datumPoint.yUpper != null) { - // If we have data bounds, compute the relative distance between - // [chartPoint] and the nearest point of the data bounds element. We will - // use the smaller of this distance and the distance from the primary - // point as the relativeDistance from this datum. - final num relativeDistanceBounds = distanceBetweenPointAndLineSegment( - Vector2(chartPoint.x, chartPoint.y), - Vector2(datumPoint.xLower, datumPoint.yLower), - Vector2(datumPoint.xUpper, datumPoint.yUpper)); - - insidePoint = (relativeDistance < radiusPx) || - (boundsLineRadiusPx != null && - // This may be inaccurate if the symbol is drawn without end caps. - relativeDistanceBounds < boundsLineRadiusPx); - - // Keep the smaller relative distance after we have determined whether - // [chartPoint] is located inside the datum. - relativeDistance = min(relativeDistance, relativeDistanceBounds); - } else { - insidePoint = (relativeDistance < radiusPx); - } - - return _Distances( - domainDistance: domainDistance, - measureDistance: measureDistance, - relativeDistance: relativeDistance, - insidePoint: insidePoint, - ); - } - - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - final series = details.series; - - final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; - final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; - - final point = getPoint( - seriesDatum.datum, - details.domain, - details.domainLowerBound, - details.domainUpperBound, - series, - domainAxis, - details.measure, - details.measureLowerBound, - details.measureUpperBound, - details.measureOffset, - measureAxis); - - final symbolRendererFn = series.getAttr(pointSymbolRendererFnKey); - - // Get the ID of the [SymbolRenderer] for this point. An ID may be - // specified on the datum, or on the series. If neither is specified, - // fall back to the default. - String symbolRendererId; - if (symbolRendererFn != null) { - symbolRendererId = symbolRendererFn(details.index); - } - symbolRendererId ??= series.getAttr(pointSymbolRendererIdKey); - symbolRendererId ??= defaultSymbolRendererId; - - // Now that we have the ID, get the configured [SymbolRenderer]. - SymbolRenderer nearestSymbolRenderer; - if (symbolRendererId == defaultSymbolRendererId) { - nearestSymbolRenderer = symbolRenderer; - } else { - final id = symbolRendererId; - if (!config.customSymbolRenderers.containsKey(id)) { - throw ArgumentError('Invalid custom symbol renderer id "$id"'); - } - - nearestSymbolRenderer = config.customSymbolRenderers[id]; - } - - return DatumDetails.from(details, - chartPosition: Point(point.x, point.y), - chartPositionLower: Point(point.xLower, point.yLower), - chartPositionUpper: Point(point.xUpper, point.yUpper), - symbolRenderer: nearestSymbolRenderer); - } -} - -class DatumPoint extends Point { - final Object datum; - final D domain; - final ImmutableSeries series; - - // Coordinates for domain bounds. - final double xLower; - final double xUpper; - - // Coordinates for measure bounds. - final double yLower; - final double yUpper; - - DatumPoint( - {this.datum, - this.domain, - this.series, - double x, - this.xLower, - this.xUpper, - double y, - this.yLower, - this.yUpper}) - : super(x, y); - - factory DatumPoint.from(DatumPoint other, - {double x, - double xLower, - double xUpper, - double y, - double yLower, - double yUpper}) { - return DatumPoint( - datum: other.datum, - domain: other.domain, - series: other.series, - x: x ?? other.x, - xLower: xLower ?? other.xLower, - xUpper: xUpper ?? other.xUpper, - y: y ?? other.y, - yLower: yLower ?? other.yLower, - yUpper: yUpper ?? other.yUpper); - } -} - -class PointRendererElement { - DatumPoint point; - Color color; - Color fillColor; - double measureAxisPosition; - double radiusPx; - double boundsLineRadiusPx; - double strokeWidthPx; - String symbolRendererId; - - PointRendererElement clone() { - return PointRendererElement() - ..point = DatumPoint.from(point) - ..color = color != null ? Color.fromOther(color: color) : null - ..fillColor = fillColor != null ? Color.fromOther(color: fillColor) : null - ..measureAxisPosition = measureAxisPosition - ..radiusPx = radiusPx - ..boundsLineRadiusPx = boundsLineRadiusPx - ..strokeWidthPx = strokeWidthPx - ..symbolRendererId = symbolRendererId; - } - - void updateAnimationPercent(PointRendererElement previous, - PointRendererElement target, double animationPercent) { - final targetPoint = target.point; - final previousPoint = previous.point; - - final x = ((targetPoint.x - previousPoint.x) * animationPercent) + - previousPoint.x; - - final xLower = targetPoint.xLower != null && previousPoint.xLower != null - ? ((targetPoint.xLower - previousPoint.xLower) * animationPercent) + - previousPoint.xLower - : null; - - final xUpper = targetPoint.xUpper != null && previousPoint.xUpper != null - ? ((targetPoint.xUpper - previousPoint.xUpper) * animationPercent) + - previousPoint.xUpper - : null; - - double y; - if (targetPoint.y != null && previousPoint.y != null) { - y = ((targetPoint.y - previousPoint.y) * animationPercent) + - previousPoint.y; - } else if (targetPoint.y != null) { - y = targetPoint.y; - } else { - y = null; - } - - final yLower = targetPoint.yLower != null && previousPoint.yLower != null - ? ((targetPoint.yLower - previousPoint.yLower) * animationPercent) + - previousPoint.yLower - : null; - - final yUpper = targetPoint.yUpper != null && previousPoint.yUpper != null - ? ((targetPoint.yUpper - previousPoint.yUpper) * animationPercent) + - previousPoint.yUpper - : null; - - point = DatumPoint.from(targetPoint, - x: x, - xLower: xLower, - xUpper: xUpper, - y: y, - yLower: yLower, - yUpper: yUpper); - - color = getAnimatedColor(previous.color, target.color, animationPercent); - - fillColor = getAnimatedColor( - previous.fillColor, target.fillColor, animationPercent); - - radiusPx = (((target.radiusPx - previous.radiusPx) * animationPercent) + - previous.radiusPx); - - boundsLineRadiusPx = - (((target.boundsLineRadiusPx - previous.boundsLineRadiusPx) * - animationPercent) + - previous.boundsLineRadiusPx); - - strokeWidthPx = - (((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + - previous.strokeWidthPx); - } -} - -class AnimatedPoint { - final String key; - final bool overlaySeries; - - PointRendererElement _previousPoint; - PointRendererElement _targetPoint; - PointRendererElement _currentPoint; - - // Flag indicating whether this point is being animated out of the chart. - bool animatingOut = false; - - AnimatedPoint({@required this.key, @required this.overlaySeries}); - - /// Animates a point that was removed from the series out of the view. - /// - /// This should be called in place of "setNewTarget" for points that represent - /// data that has been removed from the series. - /// - /// Animates the height of the point down to the measure axis position - /// (position of 0). - void animateOut() { - var newTarget = _currentPoint.clone(); - - // Set the target measure value to the axis position. - var targetPoint = newTarget.point; - newTarget.point = DatumPoint.from(targetPoint, - x: targetPoint.x, - y: newTarget.measureAxisPosition.roundToDouble(), - yLower: newTarget.measureAxisPosition.roundToDouble(), - yUpper: newTarget.measureAxisPosition.roundToDouble()); - - // Animate the radius and stroke width to 0 so that we don't get a lingering - // point after animation is done. - newTarget.radiusPx = 0.0; - newTarget.strokeWidthPx = 0.0; - - setNewTarget(newTarget); - animatingOut = true; - } - - void setNewTarget(PointRendererElement newTarget) { - animatingOut = false; - _currentPoint ??= newTarget.clone(); - _previousPoint = _currentPoint.clone(); - _targetPoint = newTarget; - } - - PointRendererElement getCurrentPoint(double animationPercent) { - if (animationPercent == 1.0 || _previousPoint == null) { - _currentPoint = _targetPoint; - _previousPoint = _targetPoint; - return _currentPoint; - } - - _currentPoint.updateAnimationPercent( - _previousPoint, _targetPoint, animationPercent); - - return _currentPoint; - } -} - -/// Struct of distances between a datum and a point in the chart. -class _Distances { - /// Distance between two points along the domain axis. - final double domainDistance; - - /// Distance between two points along the measure axis. - final double measureDistance; - - /// Cartesian distance between the two points. - final double relativeDistance; - - /// Whether or not the point was located inside the datum. - final bool insidePoint; - - _Distances( - {this.domainDistance, - this.measureDistance, - this.relativeDistance, - this.insidePoint}); -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/point_renderer_config.dart b/web/charts/common/lib/src/chart/scatter_plot/point_renderer_config.dart deleted file mode 100644 index 57f1fdc2b..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/point_renderer_config.dart +++ /dev/null @@ -1,80 +0,0 @@ -// 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 '../../common/symbol_renderer.dart'; -import '../common/series_renderer_config.dart' - show RendererAttributes, SeriesRendererConfig; -import '../layout/layout_view.dart' show LayoutViewConfig, LayoutViewPaintOrder; -import 'point_renderer.dart' show PointRenderer, pointSymbolRendererIdKey; -import 'point_renderer_decorator.dart' show PointRendererDecorator; - -/// Configuration for a line renderer. -class PointRendererConfig extends LayoutViewConfig - implements SeriesRendererConfig { - final String customRendererId; - - /// The order to paint this renderer on the canvas. - final int layoutPaintOrder; - - /// List of decorators applied to rendered points. - final List pointRendererDecorators; - - /// Renderer used to draw the points. Defaults to a circle. - final SymbolRenderer symbolRenderer; - - /// Map of custom symbol renderers used to draw points. - /// - /// Each series or point can be associated with a custom renderer by - /// specifying a [pointSymbolRendererIdKey] matching a key in the map. Any - /// point that doesn't define one will fall back to the default - /// [symbolRenderer]. - final Map customSymbolRenderers; - - final rendererAttributes = RendererAttributes(); - - /// Default radius of the points, used if a series does not define a radiusPx - /// accessor function. - final double radiusPx; - - /// Stroke width of the target line. - final double strokeWidthPx; - - /// Optional default radius of data bounds lines, used if a series does not - /// define a boundsLineRadiusPx accessor function. - /// - /// If the series does not define a boundsLineRadiusPx accessor function, then - /// each datum's boundsLineRadiusPx value will be filled in by using the - /// following values, in order of what is defined: - /// - /// 1) boundsLineRadiusPx property defined on the series. - /// 2) boundsLineRadiusPx property defined on this renderer config. - /// 3) Final fallback is to use the point radiusPx for the datum. - final double boundsLineRadiusPx; - - PointRendererConfig( - {this.customRendererId, - this.layoutPaintOrder = LayoutViewPaintOrder.point, - this.pointRendererDecorators = const [], - this.radiusPx = 3.5, - this.boundsLineRadiusPx, - this.strokeWidthPx = 0.0, - this.symbolRenderer, - this.customSymbolRenderers}); - - @override - PointRenderer build() { - return PointRenderer(config: this, rendererId: customRendererId); - } -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/point_renderer_decorator.dart b/web/charts/common/lib/src/chart/scatter_plot/point_renderer_decorator.dart deleted file mode 100644 index b9a4e415c..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/point_renderer_decorator.dart +++ /dev/null @@ -1,37 +0,0 @@ -// 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' show required; - -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../common/chart_canvas.dart' show ChartCanvas; -import 'point_renderer.dart' show PointRendererElement; - -/// Decorates points after the points have already been painted. -abstract class PointRendererDecorator { - const PointRendererDecorator(); - - /// Configures whether the decorator should be rendered on top of or below - /// series data elements. - bool get renderAbove; - - void decorate(PointRendererElement pointElement, ChartCanvas canvas, - GraphicsFactory graphicsFactory, - {@required Rectangle drawBounds, - @required double animationPercent, - bool rtl = false}); -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/scatter_plot_chart.dart b/web/charts/common/lib/src/chart/scatter_plot/scatter_plot_chart.dart deleted file mode 100644 index b50bde99a..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/scatter_plot_chart.dart +++ /dev/null @@ -1,66 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import '../cartesian/axis/axis.dart' show NumericAxis; -import '../cartesian/axis/draw_strategy/gridline_draw_strategy.dart' - show GridlineRendererSpec; -import '../cartesian/cartesian_chart.dart' show NumericCartesianChart; -import '../common/series_renderer.dart' show SeriesRenderer; -import '../layout/layout_config.dart' show LayoutConfig; -import 'point_renderer.dart' show PointRenderer; - -/// A scatter plot draws series data as a collection of points in a two -/// dimensional Cartesian space, plotting two variables from each datum at a -/// point represented by (domain, measure). -/// -/// A third and fourth metric can be represented by configuring the color and -/// radius of each datum. -/// -/// Scatter plots render grid lines along both the domain and measure axes by -/// default. -class ScatterPlotChart extends NumericCartesianChart { - /// Select data by relative Cartesian distance. Scatter plots draw potentially - /// overlapping data in an arbitrary (x, y) space, and do not consider the - /// domain axis to be more or less important for data selection than the - /// measure axis. - @override - bool get selectNearestByDomain => false; - - ScatterPlotChart( - {bool vertical, - LayoutConfig layoutConfig, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes}) - : super( - vertical: vertical, - layoutConfig: layoutConfig, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes); - - @override - SeriesRenderer makeDefaultRenderer() { - return PointRenderer()..rendererId = SeriesRenderer.defaultRendererId; - } - - @override - void initDomainAxis() { - domainAxis.tickDrawStrategy = GridlineRendererSpec() - .createDrawStrategy(context, graphicsFactory); - } -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer.dart b/web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer.dart deleted file mode 100644 index 178680375..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer.dart +++ /dev/null @@ -1,262 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'dart:math' show max, Rectangle; - -import 'package:meta/meta.dart' show required; - -import '../../common/graphics_factory.dart' show GraphicsFactory; -import '../cartesian/axis/axis.dart' show ImmutableAxis; -import '../cartesian/cartesian_chart.dart' show CartesianChart; -import '../common/base_chart.dart' show BaseChart; -import '../common/chart_canvas.dart' show ChartCanvas; -import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; -import '../layout/layout_view.dart' - show - LayoutPosition, - LayoutView, - LayoutViewConfig, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - ViewMeasuredSizes; -import 'point_renderer.dart' show DatumPoint, PointRenderer; -import 'symbol_annotation_renderer_config.dart' - show SymbolAnnotationRendererConfig; - -/// Series renderer which draws a row of symbols for each series below the -/// drawArea but above the bottom axis. -/// -/// This renderer can draw point annotations and range annotations. Point -/// annotations are drawn at the location of the domain along the chart's domain -/// axis, in the row for its series. Range annotations are drawn as a range -/// shape between the domainLowerBound and domainUpperBound positions along the -/// chart's domain axis. Point annotations are drawn on top of range -/// annotations. -/// -/// Limitations: -/// Does not handle horizontal bars. -class SymbolAnnotationRenderer extends PointRenderer - implements LayoutView { - Rectangle _componentBounds; - GraphicsFactory graphicsFactory; - - CartesianChart _chart; - - var _currentHeight = 0; - - final _seriesInfo = LinkedHashMap>(); - - SymbolAnnotationRenderer( - {String rendererId, SymbolAnnotationRendererConfig config}) - : super(rendererId: rendererId ?? 'symbolAnnotation', config: config); - - // - // Renderer methods - // - /// Symbol annotations do not use any measure axes, or draw anything in the - /// main draw area associated with them. - @override - void configureMeasureAxes(List> seriesList) {} - - @override - void preprocessSeries(List> seriesList) { - var localConfig = (config as SymbolAnnotationRendererConfig); - - _seriesInfo.clear(); - - double offset = 0.0; - - seriesList.forEach((series) { - final seriesKey = series.id; - - // Default to the configured radius if none was defined by the series. - series.radiusPxFn ??= (_) => config.radiusPx; - - var maxRadius = 0.0; - for (var index = 0; index < series.data.length; index++) { - // Default to the configured radius if none was returned by the - // accessor function. - var radiusPx = series.radiusPxFn(index); - radiusPx ??= config.radiusPx; - - maxRadius = max(maxRadius, radiusPx); - } - - final rowInnerHeight = maxRadius * 2; - - final rowHeight = localConfig.verticalSymbolBottomPaddingPx + - localConfig.verticalSymbolTopPaddingPx + - rowInnerHeight; - - final symbolCenter = offset + - localConfig.verticalSymbolTopPaddingPx + - (rowInnerHeight / 2); - - series.measureFn = (index) => 0; - series.measureOffsetFn = (index) => 0; - - // Override the key function to allow for range annotations that start at - // the same point. This is a necessary hack because every annotation has a - // measure value of 0, so the key generated in [PointRenderer] is not - // unique enough. - series.keyFn ??= (index) => '${series.id}__${series.domainFn(index)}__' - '${series.domainLowerBoundFn(index)}__' - '${series.domainUpperBoundFn(index)}'; - - _seriesInfo[seriesKey] = _SeriesInfo( - rowHeight: rowHeight, - rowStart: offset, - symbolCenter: symbolCenter, - ); - - offset += rowHeight; - }); - - _currentHeight = offset.ceil(); - - super.preprocessSeries(seriesList); - } - - @override - DatumPoint getPoint( - final datum, - D domainValue, - D domainLowerBoundValue, - D domainUpperBoundValue, - ImmutableSeries series, - ImmutableAxis domainAxis, - num measureValue, - num measureLowerBoundValue, - num measureUpperBoundValue, - num measureOffsetValue, - ImmutableAxis measureAxis) { - final domainPosition = domainAxis.getLocation(domainValue); - - final domainLowerBoundPosition = domainLowerBoundValue != null - ? domainAxis.getLocation(domainLowerBoundValue) - : null; - - final domainUpperBoundPosition = domainUpperBoundValue != null - ? domainAxis.getLocation(domainUpperBoundValue) - : null; - - final seriesKey = series.id; - final seriesInfo = _seriesInfo[seriesKey]; - - final measurePosition = _componentBounds.top + seriesInfo.symbolCenter; - - final measureLowerBoundPosition = - domainLowerBoundPosition != null ? measurePosition : null; - - final measureUpperBoundPosition = - domainUpperBoundPosition != null ? measurePosition : null; - - return DatumPoint( - datum: datum, - domain: domainValue, - series: series, - x: domainPosition, - xLower: domainLowerBoundPosition, - xUpper: domainUpperBoundPosition, - y: measurePosition, - yLower: measureLowerBoundPosition, - yUpper: measureUpperBoundPosition); - } - - @override - void onAttach(BaseChart chart) { - if (!(chart is CartesianChart)) { - throw ArgumentError( - 'SymbolAnnotationRenderer can only be attached to a CartesianChart'); - } - - _chart = chart as CartesianChart; - - // Only vertical rendering is supported by this behavior. - assert(_chart.vertical); - - super.onAttach(chart); - _chart.addView(this); - } - - @override - void onDetach(BaseChart chart) { - chart.removeView(this); - } - - @override - void paint(ChartCanvas canvas, double animationPercent) { - super.paint(canvas, animationPercent); - - // Use the domain axis of the attached chart to render the separator lines - // to keep the same overall style. - if ((config as SymbolAnnotationRendererConfig).showSeparatorLines) { - seriesPointMap.forEach((key, points) { - final seriesInfo = _seriesInfo[key]; - - final y = componentBounds.top + seriesInfo.rowStart; - - final domainAxis = _chart.domainAxis; - final bounds = Rectangle( - componentBounds.left, y.round(), componentBounds.width, 0); - domainAxis.tickDrawStrategy - .drawAxisLine(canvas, domainAxis.axisOrientation, bounds); - }); - } - } - - // - // Layout methods - // - - @override - LayoutViewConfig get layoutConfig { - return LayoutViewConfig( - paintOrder: LayoutViewPaintOrder.point, - position: LayoutPosition.Bottom, - positionOrder: LayoutViewPositionOrder.symbolAnnotation); - } - - @override - ViewMeasuredSizes measure(int maxWidth, int maxHeight) { - // The sizing of component is not flexible. It's height is always a multiple - // of the number of series rendered, even if that ends up taking all of the - // available margin space. - return ViewMeasuredSizes( - preferredWidth: maxWidth, preferredHeight: _currentHeight); - } - - @override - void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { - _componentBounds = componentBounds; - - super.layout(componentBounds, drawAreaBounds); - } - - @override - Rectangle get componentBounds => _componentBounds; -} - -class _SeriesInfo { - double rowHeight; - double rowStart; - double symbolCenter; - - _SeriesInfo( - {@required this.rowHeight, - @required this.rowStart, - @required this.symbolCenter}); -} diff --git a/web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer_config.dart b/web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer_config.dart deleted file mode 100644 index 08c5bf035..000000000 --- a/web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer_config.dart +++ /dev/null @@ -1,72 +0,0 @@ -// 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 '../../common/symbol_renderer.dart'; -import 'comparison_points_decorator.dart' show ComparisonPointsDecorator; -import 'point_renderer_config.dart' show PointRendererConfig; -import 'point_renderer_decorator.dart' show PointRendererDecorator; -import 'symbol_annotation_renderer.dart' show SymbolAnnotationRenderer; - -/// Configuration for [SymbolAnnotationRenderer]. -/// -/// This renderer is configured with a [ComparisonPointsDecorator] by default, -/// used to draw domain ranges. This decorator will draw a rectangular shape -/// between the points (domainLowerBound, measureLowerBound) and -/// (domainUpperBound, measureUpperBound), beneath the primary point for each -/// series. -class SymbolAnnotationRendererConfig extends PointRendererConfig { - /// Whether a separator line should be drawn between the bottom row of - /// rendered symbols and the axis ticks/labels. - final bool showBottomSeparatorLine; - - /// Whether or not separator lines will be rendered between rows of rendered - /// symbols. - final bool showSeparatorLines; - - /// Space reserved at the bottom of each row where the symbol should not - /// render into. - final double verticalSymbolBottomPaddingPx; - - /// Space reserved at the top of each row where the symbol should not render - /// into. - final double verticalSymbolTopPaddingPx; - - SymbolAnnotationRendererConfig( - {String customRendererId, - List pointRendererDecorators, - double radiusPx = 5.0, - SymbolRenderer symbolRenderer, - Map customSymbolRenderers, - this.showBottomSeparatorLine = false, - this.showSeparatorLines = true, - this.verticalSymbolBottomPaddingPx = 5.0, - this.verticalSymbolTopPaddingPx = 5.0}) - : super( - customRendererId: customRendererId, - pointRendererDecorators: pointRendererDecorators ?? - [ - ComparisonPointsDecorator( - symbolRenderer: RectangleRangeSymbolRenderer()) - ], - radiusPx: radiusPx, - symbolRenderer: symbolRenderer, - customSymbolRenderers: customSymbolRenderers); - - @override - SymbolAnnotationRenderer build() { - return SymbolAnnotationRenderer( - config: this, rendererId: customRendererId); - } -} diff --git a/web/charts/common/lib/src/chart/time_series/time_series_chart.dart b/web/charts/common/lib/src/chart/time_series/time_series_chart.dart deleted file mode 100644 index f101b174e..000000000 --- a/web/charts/common/lib/src/chart/time_series/time_series_chart.dart +++ /dev/null @@ -1,65 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import '../../common/date_time_factory.dart' - show DateTimeFactory, LocalDateTimeFactory; -import '../cartesian/axis/axis.dart' show Axis, NumericAxis; -import '../cartesian/axis/draw_strategy/small_tick_draw_strategy.dart' - show SmallTickRendererSpec; -import '../cartesian/axis/spec/axis_spec.dart' show AxisSpec; -import '../cartesian/axis/spec/date_time_axis_spec.dart' show DateTimeAxisSpec; -import '../cartesian/axis/time/date_time_axis.dart' show DateTimeAxis; -import '../cartesian/cartesian_chart.dart' show CartesianChart; -import '../common/series_renderer.dart' show SeriesRenderer; -import '../layout/layout_config.dart' show LayoutConfig; -import '../line/line_renderer.dart' show LineRenderer; - -class TimeSeriesChart extends CartesianChart { - final DateTimeFactory dateTimeFactory; - - TimeSeriesChart( - {bool vertical, - LayoutConfig layoutConfig, - NumericAxis primaryMeasureAxis, - NumericAxis secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes, - this.dateTimeFactory = const LocalDateTimeFactory()}) - : super( - vertical: vertical, - layoutConfig: layoutConfig, - domainAxis: DateTimeAxis(dateTimeFactory), - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes); - - @override - void initDomainAxis() { - domainAxis.tickDrawStrategy = SmallTickRendererSpec() - .createDrawStrategy(context, graphicsFactory); - } - - @override - SeriesRenderer makeDefaultRenderer() { - return LineRenderer() - ..rendererId = SeriesRenderer.defaultRendererId; - } - - @override - Axis createDomainAxisFromSpec(AxisSpec axisSpec) { - return (axisSpec as DateTimeAxisSpec).createDateTimeAxis(dateTimeFactory); - } -} diff --git a/web/charts/common/lib/src/common/color.dart b/web/charts/common/lib/src/common/color.dart deleted file mode 100644 index eb29a050e..000000000 --- a/web/charts/common/lib/src/common/color.dart +++ /dev/null @@ -1,113 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; - -@immutable -class Color { - static const black = Color(r: 0, g: 0, b: 0); - static const white = Color(r: 255, g: 255, b: 255); - static const transparent = Color(r: 0, g: 0, b: 0, a: 0); - - static const _darkerPercentOfOrig = 0.7; - static const _lighterPercentOfOrig = 0.1; - - final int r; - final int g; - final int b; - final int a; - - final Color _darker; - final Color _lighter; - - const Color( - {this.r, this.g, this.b, this.a = 255, Color darker, Color lighter}) - : _darker = darker, - _lighter = lighter; - - Color.fromOther({Color color, Color darker, Color lighter}) - : r = color.r, - g = color.g, - b = color.b, - a = color.a, - _darker = darker ?? color._darker, - _lighter = lighter ?? color._lighter; - - /// Construct the color from a hex code string, of the format #RRGGBB. - factory Color.fromHex({String code}) { - var str = code.substring(1, 7); - var bigint = int.parse(str, radix: 16); - final r = (bigint >> 16) & 255; - final g = (bigint >> 8) & 255; - final b = bigint & 255; - final a = 255; - return Color(r: r, g: g, b: b, a: a); - } - - Color get darker => - _darker ?? - Color( - r: (r * _darkerPercentOfOrig).round(), - g: (g * _darkerPercentOfOrig).round(), - b: (b * _darkerPercentOfOrig).round(), - a: a); - - Color get lighter => - _lighter ?? - Color( - r: r + ((255 - r) * _lighterPercentOfOrig).round(), - g: g + ((255 - g) * _lighterPercentOfOrig).round(), - b: b + ((255 - b) * _lighterPercentOfOrig).round(), - a: a); - - @override - bool operator ==(Object other) => - other is Color && - other.r == r && - other.g == g && - other.b == b && - other.a == a; - - @override - int get hashCode { - var hashcode = r.hashCode; - hashcode = hashcode * 37 + g.hashCode; - hashcode = hashcode * 37 + b.hashCode; - hashcode = hashcode * 37 + a.hashCode; - return hashcode; - } - - @override - String toString() => rgbaHexString; - - /// Converts the character into a #RGBA hex string. - String get rgbaHexString => '#${_get2CharHex(r)}${_get2CharHex(g)}' - '${_get2CharHex(b)}${_get2CharHex(a)}'; - - /// Converts the character into a #RGB hex string. - String get hexString { - // Alpha is not included in the hex string. - assert(a == 255); - return '#${_get2CharHex(r)}${_get2CharHex(g)}${_get2CharHex(b)}'; - } - - String _get2CharHex(int num) { - var str = num.toRadixString(16); - while (str.length < 2) { - str = '0' + str; - } - return str; - } -} diff --git a/web/charts/common/lib/src/common/date_time_factory.dart b/web/charts/common/lib/src/common/date_time_factory.dart deleted file mode 100644 index 50ed24547..000000000 --- a/web/charts/common/lib/src/common/date_time_factory.dart +++ /dev/null @@ -1,98 +0,0 @@ -// 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 'package:intl/intl.dart' show DateFormat; - -/// Interface for factory that creates [DateTime] and [DateFormat]. -/// -/// This allows for creating of locale specific date time and date format. -abstract class DateTimeFactory { - // TODO: Per cbraun@, we need to allow setting the timezone that - // is used globally (along with other settings like which day the week starts - // on. Use DateTimeFactory - either return a local DateTime or a UTC date time - // based on the setting. - - // TODO: We need to incorporate the time zoned calendar here - // because Dart DateTime doesn't do this. TZDateTime implements DateTime, so - // we can use DateTime as the interface. - DateTime createDateTimeFromMilliSecondsSinceEpoch(int millisecondsSinceEpoch); - - DateTime createDateTime(int year, - [int month = 1, - int day = 1, - int hour = 0, - int minute = 0, - int second = 0, - int millisecond = 0, - int microsecond = 0]); - - /// Returns a [DateFormat]. - DateFormat createDateFormat(String pattern); -} - -/// A local time [DateTimeFactory]. -class LocalDateTimeFactory implements DateTimeFactory { - const LocalDateTimeFactory(); - - DateTime createDateTimeFromMilliSecondsSinceEpoch( - int millisecondsSinceEpoch) { - return DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch); - } - - DateTime createDateTime(int year, - [int month = 1, - int day = 1, - int hour = 0, - int minute = 0, - int second = 0, - int millisecond = 0, - int microsecond = 0]) { - return DateTime( - year, month, day, hour, minute, second, millisecond, microsecond); - } - - /// Returns a [DateFormat]. - DateFormat createDateFormat(String pattern) { - return DateFormat(pattern); - } -} - -/// An UTC time [DateTimeFactory]. -class UTCDateTimeFactory implements DateTimeFactory { - const UTCDateTimeFactory(); - - DateTime createDateTimeFromMilliSecondsSinceEpoch( - int millisecondsSinceEpoch) { - return DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, - isUtc: true); - } - - DateTime createDateTime(int year, - [int month = 1, - int day = 1, - int hour = 0, - int minute = 0, - int second = 0, - int millisecond = 0, - int microsecond = 0]) { - return DateTime.utc( - year, month, day, hour, minute, second, millisecond, microsecond); - } - - /// Returns a [DateFormat]. - DateFormat createDateFormat(String pattern) { - return DateFormat(pattern); - } -} diff --git a/web/charts/common/lib/src/common/gesture_listener.dart b/web/charts/common/lib/src/common/gesture_listener.dart deleted file mode 100644 index bc77af4c5..000000000 --- a/web/charts/common/lib/src/common/gesture_listener.dart +++ /dev/null @@ -1,105 +0,0 @@ -// 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; - -/// Listener to touch gestures. -/// -/// [GestureListeners] can override only the gestures it is interested in. -/// -/// Each gesture returns true if the event is consumed or false if it should -/// continue to alert other listeners. -class GestureListener { - static final GestureCancelCallback defaultTapCancel = () {}; - static final GestureSinglePointCallback defaultTapTest = (_) => false; - - /// Called before all gestures (except onHover) as a preliminary test to - /// see who is interested in an event. - /// - /// All listeners that return true will get the next gesture event. - /// - /// Any listener that returns false will only get the next gesture event if - /// no one returned true. - /// - /// This is useful for figuring out who is claiming a gesture event. - /// Example: SelectNearest returns true for onTapTest if the point is within - /// the drawArea. SeriesLegend returns true for onTapTest if the point is - /// within the legend. If the tap occurs in either of those places the - /// corresponding listener. If the tap occurs outside of both targets, then - /// both will be given the event so they can deselect everything in the - /// selection model. - /// - /// Defaults to function that returns false allowing other listeners to preempt. - final GestureSinglePointCallback onTapTest; - - /// Called if onTapTest was previously called, but listener is being preempted. - final GestureCancelCallback onTapCancel; - - /// Called after the tap event has been going on for a period of time (500ms) - /// without moving much (20px). - /// The onTap or onDragStart gestures can still trigger after this gesture. - final GestureSinglePointCallback onLongPress; - - /// Called on tap up if not dragging. - final GestureSinglePointCallback onTap; - - /// Called when a mouse hovers over the chart. (No tap event). - final GestureSinglePointCallback onHover; - - /// Called when the tap event has moved beyond a threshold indicating that - /// the user is dragging. - /// - /// This will only be called once per drag gesture independent of how many - /// touches are going on until the last touch is complete. onDragUpdate is - /// called as touches move updating the scale as determined by the first - /// two points. onDragEnd is called when the last touch event lifts and the - /// velocity is calculated from the final movement. - /// - /// onDragStart, onDragUpdate, and onDragEnd are also called for mouse wheel - /// with the scale and point updated given the WheelEvent (deltaY updates the - /// scale, deltaX updates the event point/pans). - /// - /// TODO: Add a "discrete" flag that tells drag listeners whether - /// they should be expecting a series of continuous updates, or one large - /// update. This will mostly be used to control whether we animate the chart - /// between onDragUpdate calls. - /// - /// TODO: Investigate low performance of chart rendering from - /// flutter when animation is enabled and we pinch to zoom on the chart. - final GestureDragStartCallback onDragStart; - final GestureDragUpdateCallback onDragUpdate; - final GestureDragEndCallback onDragEnd; - - GestureListener( - {GestureSinglePointCallback onTapTest, - GestureCancelCallback onTapCancel, - this.onLongPress, - this.onTap, - this.onHover, - this.onDragStart, - this.onDragUpdate, - this.onDragEnd}) - : this.onTapTest = onTapTest ?? defaultTapTest, - this.onTapCancel = onTapCancel ?? defaultTapCancel; -} - -typedef GestureCancelCallback = Function(); -typedef GestureSinglePointCallback = bool Function(Point localPosition); - -typedef GestureDragStartCallback = bool Function(Point localPosition); -typedef GestureDragUpdateCallback = Function( - Point localPosition, double scale); -typedef GestureDragEndCallback = Function( - Point localPosition, double scale, double pixelsPerSec); diff --git a/web/charts/common/lib/src/common/graphics_factory.dart b/web/charts/common/lib/src/common/graphics_factory.dart deleted file mode 100644 index 7bce54a56..000000000 --- a/web/charts/common/lib/src/common/graphics_factory.dart +++ /dev/null @@ -1,29 +0,0 @@ -// 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 'line_style.dart' show LineStyle; -import 'text_element.dart' show TextElement; -import 'text_style.dart' show TextStyle; - -/// Interface to native platform graphics functions. -abstract class GraphicsFactory { - LineStyle createLinePaint(); - - /// Returns a [TextStyle] object. - TextStyle createTextPaint(); - - /// Returns a text element from [text] and [style]. - TextElement createTextElement(String text); -} diff --git a/web/charts/common/lib/src/common/line_style.dart b/web/charts/common/lib/src/common/line_style.dart deleted file mode 100644 index 38d8e8572..000000000 --- a/web/charts/common/lib/src/common/line_style.dart +++ /dev/null @@ -1,24 +0,0 @@ -// 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 'paint_style.dart' show PaintStyle; - -abstract class LineStyle extends PaintStyle { - List get dashPattern; - set dashPattern(List dashPattern); - - int get strokeWidth; - set strokeWidth(int strokeWidth); -} diff --git a/web/charts/common/lib/src/common/material_palette.dart b/web/charts/common/lib/src/common/material_palette.dart deleted file mode 100644 index 3f099420e..000000000 --- a/web/charts/common/lib/src/common/material_palette.dart +++ /dev/null @@ -1,232 +0,0 @@ -// 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 'color.dart' show Color; -import 'palette.dart' show Palette; - -/// A canonical palette of colors from material.io. -/// -/// @link https://material.io/guidelines/style/color.html#color-color-palette -class MaterialPalette { - static const black = Color(r: 0, g: 0, b: 0); - static const transparent = Color(r: 0, g: 0, b: 0, a: 0); - static const white = Color(r: 255, g: 255, b: 255); - - static Palette get blue => const MaterialBlue(); - static Palette get red => const MaterialRed(); - static Palette get yellow => const MaterialYellow(); - static Palette get green => const MaterialGreen(); - static Palette get purple => const MaterialPurple(); - static Palette get cyan => const MaterialCyan(); - static Palette get deepOrange => const MaterialDeepOrange(); - static Palette get lime => const MaterialLime(); - static Palette get indigo => const MaterialIndigo(); - static Palette get pink => const MaterialPink(); - static Palette get teal => const MaterialTeal(); - static MaterialGray get gray => const MaterialGray(); - - static List getOrderedPalettes(int count) { - final orderedPalettes = []; - if (orderedPalettes.length < count) { - orderedPalettes.add(blue); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(red); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(yellow); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(green); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(purple); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(cyan); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(deepOrange); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(lime); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(indigo); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(pink); - } - if (orderedPalettes.length < count) { - orderedPalettes.add(teal); - } - return orderedPalettes; - } -} - -class MaterialBlue extends Palette { - static const _shade200 = Color(r: 0x90, g: 0xCA, b: 0xF9); //#90CAF9 - static const _shade500 = - Color(r: 0x21, g: 0x96, b: 0xF3, darker: _shade700, lighter: _shade200); - static const _shade700 = Color(r: 0x19, g: 0x76, b: 0xD2); //#1976D2 - - const MaterialBlue(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialRed extends Palette { - static const _shade200 = Color(r: 0xEF, g: 0x9A, b: 0x9A); //#EF9A9A - static const _shade700 = Color(r: 0xD3, g: 0x2F, b: 0x2F); //#D32F2F - static const _shade500 = - Color(r: 0xF4, g: 0x43, b: 0x36, darker: _shade700, lighter: _shade200); - - const MaterialRed(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialYellow extends Palette { - static const _shade200 = Color(r: 0xFF, g: 0xF5, b: 0x9D); //#FFF59D - static const _shade700 = Color(r: 0xFB, g: 0xC0, b: 0x2D); //#FBC02D - static const _shade500 = - Color(r: 0xFF, g: 0xEB, b: 0x3B, darker: _shade700, lighter: _shade200); - - const MaterialYellow(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialGreen extends Palette { - static const _shade200 = Color(r: 0xA5, g: 0xD6, b: 0xA7); //#A5D6A7 - static const _shade700 = Color(r: 0x38, g: 0x8E, b: 0x3C); //#388E3C; - static const _shade500 = - Color(r: 0x4C, g: 0xAF, b: 0x50, darker: _shade700, lighter: _shade200); - - const MaterialGreen(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialPurple extends Palette { - static const _shade200 = Color(r: 0xCE, g: 0x93, b: 0xD8); //#CE93D8 - static const _shade700 = Color(r: 0x7B, g: 0x1F, b: 0xA2); //#7B1FA2 - static const _shade500 = - Color(r: 0x9C, g: 0x27, b: 0xB0, darker: _shade700, lighter: _shade200); - - const MaterialPurple(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialCyan extends Palette { - static const _shade200 = Color(r: 0x80, g: 0xDE, b: 0xEA); //#80DEEA - static const _shade700 = Color(r: 0x00, g: 0x97, b: 0xA7); //#0097A7 - static const _shade500 = - Color(r: 0x00, g: 0xBC, b: 0xD4, darker: _shade700, lighter: _shade200); - - const MaterialCyan(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialDeepOrange extends Palette { - static const _shade200 = Color(r: 0xFF, g: 0xAB, b: 0x91); //#FFAB91 - static const _shade700 = Color(r: 0xE6, g: 0x4A, b: 0x19); //#E64A19 - static const _shade500 = - Color(r: 0xFF, g: 0x57, b: 0x22, darker: _shade700, lighter: _shade200); - - const MaterialDeepOrange(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialLime extends Palette { - static const _shade200 = Color(r: 0xE6, g: 0xEE, b: 0x9C); //#E6EE9C - static const _shade700 = Color(r: 0xAF, g: 0xB4, b: 0x2B); //#AFB42B - static const _shade500 = - Color(r: 0xCD, g: 0xDC, b: 0x39, darker: _shade700, lighter: _shade200); - - const MaterialLime(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialIndigo extends Palette { - static const _shade200 = Color(r: 0x9F, g: 0xA8, b: 0xDA); //#9FA8DA - static const _shade700 = Color(r: 0x30, g: 0x3F, b: 0x9F); //#303F9F - static const _shade500 = - Color(r: 0x3F, g: 0x51, b: 0xB5, darker: _shade700, lighter: _shade200); - - const MaterialIndigo(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialPink extends Palette { - static const _shade200 = Color(r: 0xF4, g: 0x8F, b: 0xB1); //#F48FB1 - static const _shade700 = Color(r: 0xC2, g: 0x18, b: 0x5B); //#C2185B - static const _shade500 = - Color(r: 0xE9, g: 0x1E, b: 0x63, darker: _shade700, lighter: _shade200); - - const MaterialPink(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialTeal extends Palette { - static const _shade200 = Color(r: 0x80, g: 0xCB, b: 0xC4); //#80CBC4 - static const _shade700 = Color(r: 0x00, g: 0x79, b: 0x6B); //#00796B - static const _shade500 = - Color(r: 0x00, g: 0x96, b: 0x88, darker: _shade700, lighter: _shade200); - - const MaterialTeal(); - - @override - Color get shadeDefault => _shade500; -} - -class MaterialGray extends Palette { - static const _shade200 = Color(r: 0xEE, g: 0xEE, b: 0xEE); //#EEEEEE - static const _shade700 = Color(r: 0x61, g: 0x61, b: 0x61); //#616161 - static const _shade500 = - Color(r: 0x9E, g: 0x9E, b: 0x9E, darker: _shade700, lighter: _shade200); - - const MaterialGray(); - - @override - Color get shadeDefault => _shade500; - - Color get shade50 => const Color(r: 0xFA, g: 0xFA, b: 0xFA); //#FAFAFA - Color get shade100 => const Color(r: 0xF5, g: 0xF5, b: 0xF5); //#F5F5F5 - Color get shade200 => _shade200; - Color get shade300 => const Color(r: 0xE0, g: 0xE0, b: 0xE0); //#E0E0E0 - Color get shade400 => const Color(r: 0xBD, g: 0xBD, b: 0xBD); //#BDBDBD - Color get shade500 => _shade500; - Color get shade600 => const Color(r: 0x75, g: 0x75, b: 0x75); //#757575 - Color get shade700 => _shade700; - Color get shade800 => const Color(r: 0x42, g: 0x42, b: 0x42); //#424242 - Color get shade900 => const Color(r: 0x21, g: 0x21, b: 0xA1); //#212121 -} diff --git a/web/charts/common/lib/src/common/math.dart b/web/charts/common/lib/src/common/math.dart deleted file mode 100644 index 0e6c4b7e1..000000000 --- a/web/charts/common/lib/src/common/math.dart +++ /dev/null @@ -1,60 +0,0 @@ -// 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 max, min, sqrt; -import 'package:vector_math/vector_math.dart' show Vector2; - -/// Takes a number and clamps it to within the provided bounds. -/// -/// Returns the input number if it is within bounds, or the nearest number -/// within the bounds. -/// -/// [value] The input number. -/// [minValue] The minimum value to return. -/// [maxValue] The maximum value to return. -num clamp(num value, num minValue, num maxValue) { - return min(max(value, minValue), maxValue); -} - -/// Returns the minimum distance between point p and the line segment vw. -/// -/// [p] The point. -/// [v] Start point for the line segment. -/// [w] End point for the line segment. -double distanceBetweenPointAndLineSegment(Vector2 p, Vector2 v, Vector2 w) { - return sqrt(distanceBetweenPointAndLineSegmentSquared(p, v, w)); -} - -/// Returns the squared minimum distance between point p and the line segment -/// vw. -/// -/// [p] The point. -/// [v] Start point for the line segment. -/// [w] End point for the line segment. -double distanceBetweenPointAndLineSegmentSquared( - Vector2 p, Vector2 v, Vector2 w) { - final lineLength = v.distanceToSquared(w); - - if (lineLength == 0) { - return p.distanceToSquared(v); - } - - var t0 = (p - v).dot(w - v) / lineLength; - t0 = max(0.0, min(1.0, t0)); - - final projection = v + ((w - v) * t0); - - return p.distanceToSquared(projection); -} diff --git a/web/charts/common/lib/src/common/paint_style.dart b/web/charts/common/lib/src/common/paint_style.dart deleted file mode 100644 index 047f3e929..000000000 --- a/web/charts/common/lib/src/common/paint_style.dart +++ /dev/null @@ -1,23 +0,0 @@ -// 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 'color.dart'; - -/// Style properties of a paintable object. -abstract class PaintStyle { - Color get color; - - set color(Color value); -} diff --git a/web/charts/common/lib/src/common/palette.dart b/web/charts/common/lib/src/common/palette.dart deleted file mode 100644 index 85d9987d2..000000000 --- a/web/charts/common/lib/src/common/palette.dart +++ /dev/null @@ -1,58 +0,0 @@ -// 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 'color.dart' show Color; - -/// A color palette. -abstract class Palette { - const Palette(); - - /// The default shade. - Color get shadeDefault; - - /// Returns a list of colors for this color palette. - List makeShades(int colorCnt) { - final colors = [shadeDefault]; - - // If we need more than 2 colors, then [unselected] collides with one of the - // generated colors. Otherwise divide the space between the top color - // and white in half. - final lighterColor = colorCnt < 3 - ? shadeDefault.lighter - : _getSteppedColor(shadeDefault, (colorCnt * 2) - 1, colorCnt * 2); - - // Divide the space between 255 and c500 evenly according to the colorCnt. - for (int i = 1; i < colorCnt; i++) { - colors.add(_getSteppedColor(shadeDefault, i, colorCnt, - darker: shadeDefault.darker, lighter: lighterColor)); - } - - colors.add(Color.fromOther(color: shadeDefault, lighter: lighterColor)); - return colors; - } - - Color _getSteppedColor(Color color, int index, int steps, - {Color darker, Color lighter}) { - final fraction = index / steps; - return Color( - r: color.r + ((255 - color.r) * fraction).round(), - g: color.g + ((255 - color.g) * fraction).round(), - b: color.b + ((255 - color.b) * fraction).round(), - a: color.a + ((255 - color.a) * fraction).round(), - darker: darker, - lighter: lighter, - ); - } -} diff --git a/web/charts/common/lib/src/common/performance.dart b/web/charts/common/lib/src/common/performance.dart deleted file mode 100644 index 7a3bdb2d6..000000000 --- a/web/charts/common/lib/src/common/performance.dart +++ /dev/null @@ -1,21 +0,0 @@ -// 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. - -typedef PerformanceCallback = Function(String tag); - -class Performance { - static PerformanceCallback time = (_) {}; - static PerformanceCallback timeEnd = (_) {}; -} diff --git a/web/charts/common/lib/src/common/proxy_gesture_listener.dart b/web/charts/common/lib/src/common/proxy_gesture_listener.dart deleted file mode 100644 index 659498ecf..000000000 --- a/web/charts/common/lib/src/common/proxy_gesture_listener.dart +++ /dev/null @@ -1,143 +0,0 @@ -// 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; - -import 'gesture_listener.dart' show GestureListener; - -/// Listens to all gestures and proxies to child listeners. -class ProxyGestureListener { - final _listeners = []; - var _activeListeners = []; - - void add(GestureListener listener) { - _listeners.add(listener); - _activeListeners.clear(); - } - - void remove(GestureListener listener) { - _listeners.remove(listener); - _activeListeners.clear(); - } - - bool onTapTest(Point localPosition) { - _activeListeners.clear(); - return _populateActiveListeners(localPosition); - } - - bool onLongPress(Point localPosition) { - // Walk through listeners stopping at the first handled listener. - final claimingListener = _activeListeners.firstWhere( - (listener) => - listener.onLongPress != null && listener.onLongPress(localPosition), - orElse: () => null); - - // If someone claims the long press, then cancel everyone else. - if (claimingListener != null) { - _activeListeners = - _cancel(all: _activeListeners, keep: [claimingListener]); - return true; - } - return false; - } - - bool onTap(Point localPosition) { - // Walk through listeners stopping at the first handled listener. - final claimingListener = _activeListeners.firstWhere( - (listener) => listener.onTap != null && listener.onTap(localPosition), - orElse: () => null); - - // If someone claims the tap, then cancel everyone else. - // This should hopefully be rare, like for drilling. - if (claimingListener != null) { - _activeListeners = - _cancel(all: _activeListeners, keep: [claimingListener]); - return true; - } - return false; - } - - bool onHover(Point localPosition) { - // Cancel any previously active long lived gestures. - _activeListeners = []; - - // Walk through listeners stopping at the first handled listener. - return _listeners.any((listener) => - listener.onHover != null && listener.onHover(localPosition)); - } - - bool onDragStart(Point localPosition) { - // In Flutter, a tap test may not be triggered because a tap down event - // may not be registered if the the drag gesture happens without any pause. - if (_activeListeners.isEmpty) { - _populateActiveListeners(localPosition); - } - - // Walk through listeners stopping at the first handled listener. - final claimingListener = _activeListeners.firstWhere( - (listener) => - listener.onDragStart != null && listener.onDragStart(localPosition), - orElse: () => null); - - if (claimingListener != null) { - _activeListeners = - _cancel(all: _activeListeners, keep: [claimingListener]); - return true; - } - return false; - } - - bool onDragUpdate(Point localPosition, double scale) { - return _activeListeners.any((listener) => - listener.onDragUpdate != null && - listener.onDragUpdate(localPosition, scale)); - } - - bool onDragEnd( - Point localPosition, double scale, double pixelsPerSecond) { - return _activeListeners.any((listener) => - listener.onDragEnd != null && - listener.onDragEnd(localPosition, scale, pixelsPerSecond)); - } - - List _cancel( - {List all, List keep}) { - all.forEach((listener) { - if (!keep.contains(listener)) { - listener.onTapCancel(); - } - }); - return keep; - } - - bool _populateActiveListeners(Point localPosition) { - var localListeners = List.from(_listeners); - - var previouslyClaimed = false; - localListeners.forEach((listener) { - var claimed = listener.onTapTest(localPosition); - if (claimed && !previouslyClaimed) { - // Cancel any already added non-claiming listeners now that someone is - // claiming it. - _activeListeners = _cancel(all: _activeListeners, keep: [listener]); - previouslyClaimed = true; - } else if (claimed || !previouslyClaimed) { - _activeListeners.add(listener); - } - }); - - return previouslyClaimed; - } -} diff --git a/web/charts/common/lib/src/common/rtl_spec.dart b/web/charts/common/lib/src/common/rtl_spec.dart deleted file mode 100644 index fbdc845c6..000000000 --- a/web/charts/common/lib/src/common/rtl_spec.dart +++ /dev/null @@ -1,47 +0,0 @@ -// 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. - -/// Defines the behavior of the chart if it is RTL. -class RTLSpec { - /// Creates [RTLSpec]. If no parameters are specified, the defaults are used. - const RTLSpec({ - this.axisDirection = AxisDirection.reversed, - }); - - /// Direction of the domain axis when the chart container is configured for - /// RTL mode. - final AxisDirection axisDirection; -} - -/// Direction of the domain axis when the chart container is configured for -/// RTL mode. -/// -/// [normal] Vertically rendered charts will have the primary measure axis on -/// the left and secondary measure axis on the right. Domain axis is on the left -/// and the domain output range starts from the left and grows to the right. -/// Horizontally rendered charts will have the primary measure axis on the -/// bottom and secondary measure axis on the right. Measure output range starts -/// from the left and grows to the right. -/// -/// [reversed] Vertically rendered charts will have the primary measure axis on -/// the right and secondary measure axis on the left. Domain axis is on the -/// right and domain values grows from the right to the left. Horizontally -/// rendered charts will have the primary measure axis on the top and secondary -/// measure axis on the left. Measure output range is flipped and grows from the -/// right to the left. -enum AxisDirection { - normal, - reversed, -} diff --git a/web/charts/common/lib/src/common/style/material_style.dart b/web/charts/common/lib/src/common/style/material_style.dart deleted file mode 100644 index 23615acf9..000000000 --- a/web/charts/common/lib/src/common/style/material_style.dart +++ /dev/null @@ -1,99 +0,0 @@ -// 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 '../../chart/cartesian/axis/spec/axis_spec.dart' show LineStyleSpec; -import '../color.dart' show Color; -import '../graphics_factory.dart' show GraphicsFactory; -import '../line_style.dart' show LineStyle; -import '../material_palette.dart' show MaterialPalette; -import '../palette.dart' show Palette; -import 'style.dart' show Style; - -class MaterialStyle implements Style { - const MaterialStyle(); - - @override - Color get black => MaterialPalette.black; - - @override - Color get transparent => MaterialPalette.transparent; - - @override - Color get white => MaterialPalette.white; - - @override - List getOrderedPalettes(int count) => - MaterialPalette.getOrderedPalettes(count); - - @override - LineStyle createAxisLineStyle( - GraphicsFactory graphicsFactory, LineStyleSpec spec) { - return graphicsFactory.createLinePaint() - ..color = spec?.color ?? MaterialPalette.gray.shadeDefault - ..dashPattern = spec?.dashPattern - ..strokeWidth = spec?.thickness ?? 1; - } - - @override - LineStyle createTickLineStyle( - GraphicsFactory graphicsFactory, LineStyleSpec spec) { - return graphicsFactory.createLinePaint() - ..color = spec?.color ?? MaterialPalette.gray.shadeDefault - ..dashPattern = spec?.dashPattern - ..strokeWidth = spec?.thickness ?? 1; - } - - @override - int get tickLength => 3; - - @override - Color get tickColor => MaterialPalette.gray.shade800; - - @override - LineStyle createGridlineStyle( - GraphicsFactory graphicsFactory, LineStyleSpec spec) { - return graphicsFactory.createLinePaint() - ..color = spec?.color ?? MaterialPalette.gray.shade300 - ..dashPattern = spec?.dashPattern - ..strokeWidth = spec?.thickness ?? 1; - } - - @override - Color get arcLabelOutsideLeaderLine => MaterialPalette.gray.shade600; - - @override - Color get legendEntryTextColor => MaterialPalette.gray.shade800; - - @override - Color get legendTitleTextColor => MaterialPalette.gray.shade800; - - @override - Color get linePointHighlighterColor => MaterialPalette.gray.shade600; - - @override - Color get noDataColor => MaterialPalette.gray.shade200; - - @override - Color get rangeAnnotationColor => MaterialPalette.gray.shade100; - - @override - Color get sliderFillColor => MaterialPalette.white; - - @override - Color get sliderStrokeColor => MaterialPalette.gray.shade600; - - @override - Color get chartBackgroundColor => MaterialPalette.white; -} diff --git a/web/charts/common/lib/src/common/style/style.dart b/web/charts/common/lib/src/common/style/style.dart deleted file mode 100644 index d055e4a83..000000000 --- a/web/charts/common/lib/src/common/style/style.dart +++ /dev/null @@ -1,90 +0,0 @@ -// 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 '../../chart/cartesian/axis/spec/axis_spec.dart' show LineStyleSpec; -import '../color.dart' show Color; -import '../graphics_factory.dart' show GraphicsFactory; -import '../line_style.dart' show LineStyle; -import '../palette.dart'; - -// TODO: Implementation of style will change drastically, see bug -// for more details. This is an intermediate step in order to allow overriding -// the default style using style factory. - -/// A set of styling rules that determines the default look and feel of charts. -/// -/// Get or set the [Style] that is used for the app using [StyleFactory.style]. -abstract class Style { - Color get black; - - Color get transparent; - - Color get white; - - /// Gets list with [count] of palettes. - List getOrderedPalettes(int count); - - /// Creates [LineStyleSpec] for axis line from spec. - /// - /// Fill missing value(s) with default. - LineStyle createAxisLineStyle( - GraphicsFactory graphicsFactory, LineStyleSpec spec); - - /// Creates [LineStyleSpec] for tick lines from spec. - /// - /// Fill missing value(s) with default. - LineStyle createTickLineStyle( - GraphicsFactory graphicsFactory, LineStyleSpec spec); - - /// Default tick length. - int get tickLength; - - /// Default tick color. - Color get tickColor; - - /// - /// Creates [LineStyle] for axis gridlines from spec. - /// - /// Fill missing value(s) with default. - LineStyle createGridlineStyle( - GraphicsFactory graphicsFactory, LineStyleSpec spec); - - /// Default color for outside label leader lines for [ArcLabelDecorator]. - Color get arcLabelOutsideLeaderLine; - - /// Default color for entry text for [Legend]. - Color get legendEntryTextColor; - - /// Default color for title text for [Legend]. - Color get legendTitleTextColor; - - /// Default color for [LinePointHighlighter]. - Color get linePointHighlighterColor; - - /// Default color for "no data" states on charts. - Color get noDataColor; - - /// Default color for [RangeAnnotation]. - Color get rangeAnnotationColor; - - /// Default fill color for [Slider]. - Color get sliderFillColor; - - /// Default stroke color for [Slider]. - Color get sliderStrokeColor; - - /// Default background color for the chart. - Color get chartBackgroundColor; -} diff --git a/web/charts/common/lib/src/common/style/style_factory.dart b/web/charts/common/lib/src/common/style/style_factory.dart deleted file mode 100644 index e1ec3f8a1..000000000 --- a/web/charts/common/lib/src/common/style/style_factory.dart +++ /dev/null @@ -1,32 +0,0 @@ -// 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 'material_style.dart' show MaterialStyle; -import 'style.dart' show Style; - -class StyleFactory { - static final StyleFactory _styleFactory = StyleFactory._internal(); - - Style _style = const MaterialStyle(); - - /// The [Style] that is used for all the charts in this application. - static Style get style => _styleFactory._style; - - static set style(Style value) { - _styleFactory._style = value; - } - - StyleFactory._internal(); -} diff --git a/web/charts/common/lib/src/common/symbol_renderer.dart b/web/charts/common/lib/src/common/symbol_renderer.dart deleted file mode 100644 index ab810b72a..000000000 --- a/web/charts/common/lib/src/common/symbol_renderer.dart +++ /dev/null @@ -1,348 +0,0 @@ -// 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, Point, min; - -import 'package:meta/meta.dart' show protected; - -import '../chart/common/chart_canvas.dart' show ChartCanvas; -import 'color.dart' show Color; -import 'style/style_factory.dart' show StyleFactory; - -/// Strategy for rendering a symbol. -abstract class BaseSymbolRenderer { - bool shouldRepaint(covariant BaseSymbolRenderer oldRenderer); -} - -/// Strategy for rendering a symbol bounded within a box. -abstract class SymbolRenderer extends BaseSymbolRenderer { - /// Whether the symbol should be rendered as a solid shape, or a hollow shape. - /// - /// If this is true, then fillColor and strokeColor will be used to fill in - /// the shape, and draw a border, respectively. The stroke (border) will only - /// be visible if a non-zero strokeWidthPx is configured. - /// - /// If this is false, then the shape will be filled in with a white color - /// (overriding fillColor). strokeWidthPx will default to 2 if none was - /// configured. - final bool isSolid; - - SymbolRenderer({this.isSolid}); - - void paint(ChartCanvas canvas, Rectangle bounds, - {List dashPattern, - Color fillColor, - Color strokeColor, - double strokeWidthPx}); - - @protected - double getSolidStrokeWidthPx(double strokeWidthPx) { - return isSolid ? strokeWidthPx : strokeWidthPx ?? 2.0; - } - - @protected - Color getSolidFillColor(Color fillColor) { - return isSolid ? fillColor : StyleFactory.style.white; - } - - @override - bool operator ==(Object other) { - return other is SymbolRenderer && other.isSolid == isSolid; - } - - @override - int get hashCode => isSolid.hashCode; -} - -/// Strategy for rendering a symbol centered around a point. -/// -/// An optional second point can describe an extended symbol. -abstract class PointSymbolRenderer extends BaseSymbolRenderer { - void paint(ChartCanvas canvas, Point p1, double radius, - {Point p2, Color fillColor, strokeColor}); -} - -/// Rounded rectangular symbol with corners having [radius]. -class RoundedRectSymbolRenderer extends SymbolRenderer { - final double radius; - - RoundedRectSymbolRenderer({bool isSolid = true, double radius}) - : radius = radius ?? 1.0, - super(isSolid: isSolid); - - @override - void paint(ChartCanvas canvas, Rectangle bounds, - {List dashPattern, - Color fillColor, - Color strokeColor, - double strokeWidthPx}) { - canvas.drawRRect(bounds, - fill: getSolidFillColor(fillColor), - stroke: strokeColor, - radius: radius, - roundTopLeft: true, - roundTopRight: true, - roundBottomRight: true, - roundBottomLeft: true); - } - - @override - bool shouldRepaint(RoundedRectSymbolRenderer oldRenderer) { - return this != oldRenderer; - } - - @override - bool operator ==(Object other) { - return other is RoundedRectSymbolRenderer && - other.radius == radius && - super == (other); - } - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + radius.hashCode; - return hashcode; - } -} - -/// Line symbol renderer. -class LineSymbolRenderer extends SymbolRenderer { - static const roundEndCapsPixels = 2; - static const minLengthToRoundCaps = (roundEndCapsPixels * 2) + 1; - static const strokeWidthForRoundEndCaps = 4.0; - static const strokeWidthForNonRoundedEndCaps = 2.0; - - /// Thickness of the line stroke. - final double strokeWidth; - - /// Dash pattern for the line. - final List _dashPattern; - - LineSymbolRenderer( - {List dashPattern, bool isSolid = true, double strokeWidth}) - : strokeWidth = strokeWidth ?? strokeWidthForRoundEndCaps, - _dashPattern = dashPattern, - super(isSolid: isSolid); - - @override - void paint(ChartCanvas canvas, Rectangle bounds, - {List dashPattern, - Color fillColor, - Color strokeColor, - double strokeWidthPx}) { - final centerHeight = (bounds.bottom - bounds.top) / 2; - - // If we have a dash pattern, do not round the end caps, and set - // strokeWidthPx to a smaller value. Using round end caps makes smaller - // patterns blurry. - final localDashPattern = dashPattern ?? _dashPattern; - final roundEndCaps = localDashPattern == null; - - // If we have a dash pattern, the normal stroke width makes them look - // strangely tall. - final localStrokeWidthPx = localDashPattern == null - ? getSolidStrokeWidthPx(strokeWidthPx ?? strokeWidth) - : strokeWidthForNonRoundedEndCaps; - - // Adjust the length so the total width includes the rounded pixels. - // Otherwise the cap is drawn past the bounds and appears to be cut off. - // If bounds is not long enough to accommodate the line, do not adjust. - var left = bounds.left; - var right = bounds.right; - - if (roundEndCaps && bounds.width >= minLengthToRoundCaps) { - left += roundEndCapsPixels; - right -= roundEndCapsPixels; - } - - // TODO: Pass in strokeWidth, roundEndCaps, and dashPattern from - // line renderer config. - canvas.drawLine( - points: [Point(left, centerHeight), Point(right, centerHeight)], - dashPattern: localDashPattern, - fill: getSolidFillColor(fillColor), - roundEndCaps: roundEndCaps, - stroke: strokeColor, - strokeWidthPx: localStrokeWidthPx, - ); - } - - @override - bool shouldRepaint(LineSymbolRenderer oldRenderer) { - return this != oldRenderer; - } - - @override - bool operator ==(Object other) { - return other is LineSymbolRenderer && - other.strokeWidth == strokeWidth && - super == (other); - } - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + strokeWidth.hashCode; - return hashcode; - } -} - -/// Circle symbol renderer. -class CircleSymbolRenderer extends SymbolRenderer { - CircleSymbolRenderer({bool isSolid = true}) : super(isSolid: isSolid); - - @override - void paint(ChartCanvas canvas, Rectangle bounds, - {List dashPattern, - Color fillColor, - Color strokeColor, - double strokeWidthPx}) { - final center = Point( - bounds.left + (bounds.width / 2), - bounds.top + (bounds.height / 2), - ); - final radius = min(bounds.width, bounds.height) / 2; - canvas.drawPoint( - point: center, - radius: radius, - fill: getSolidFillColor(fillColor), - stroke: strokeColor, - strokeWidthPx: getSolidStrokeWidthPx(strokeWidthPx)); - } - - @override - bool shouldRepaint(CircleSymbolRenderer oldRenderer) { - return this != oldRenderer; - } - - @override - bool operator ==(Object other) => - other is CircleSymbolRenderer && super == (other); - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + runtimeType.hashCode; - return hashcode; - } -} - -/// Rectangle symbol renderer. -class RectSymbolRenderer extends SymbolRenderer { - RectSymbolRenderer({bool isSolid = true}) : super(isSolid: isSolid); - - @override - void paint(ChartCanvas canvas, Rectangle bounds, - {List dashPattern, - Color fillColor, - Color strokeColor, - double strokeWidthPx}) { - canvas.drawRect(bounds, - fill: getSolidFillColor(fillColor), - stroke: strokeColor, - strokeWidthPx: getSolidStrokeWidthPx(strokeWidthPx)); - } - - @override - bool shouldRepaint(RectSymbolRenderer oldRenderer) { - return this != oldRenderer; - } - - @override - bool operator ==(Object other) => - other is RectSymbolRenderer && super == (other); - - @override - int get hashCode { - int hashcode = super.hashCode; - hashcode = (hashcode * 37) + runtimeType.hashCode; - return hashcode; - } -} - -/// Draws a cylindrical shape connecting two points. -class CylinderSymbolRenderer extends PointSymbolRenderer { - CylinderSymbolRenderer(); - - @override - void paint(ChartCanvas canvas, Point p1, double radius, - {Point p2, Color fillColor, strokeColor, double strokeWidthPx}) { - if (p1 == null) { - throw ArgumentError('Invalid point p1 "$p1"'); - } - - if (p2 == null) { - throw ArgumentError('Invalid point p2 "$p2"'); - } - - final adjustedP1 = Point(p1.x, p1.y); - final adjustedP2 = Point(p2.x, p2.y); - - canvas.drawLine( - points: [adjustedP1, adjustedP2], - stroke: strokeColor, - roundEndCaps: true, - strokeWidthPx: radius * 2); - } - - @override - bool shouldRepaint(CylinderSymbolRenderer oldRenderer) { - return this != oldRenderer; - } - - @override - bool operator ==(Object other) => other is CylinderSymbolRenderer; - - @override - int get hashCode => runtimeType.hashCode; -} - -/// Draws a rectangular shape connecting two points. -class RectangleRangeSymbolRenderer extends PointSymbolRenderer { - RectangleRangeSymbolRenderer(); - - @override - void paint(ChartCanvas canvas, Point p1, double radius, - {Point p2, Color fillColor, strokeColor, double strokeWidthPx}) { - if (p1 == null) { - throw ArgumentError('Invalid point p1 "$p1"'); - } - - if (p2 == null) { - throw ArgumentError('Invalid point p2 "$p2"'); - } - - final adjustedP1 = Point(p1.x, p1.y); - final adjustedP2 = Point(p2.x, p2.y); - - canvas.drawLine( - points: [adjustedP1, adjustedP2], - stroke: strokeColor, - roundEndCaps: false, - strokeWidthPx: radius * 2); - } - - @override - bool shouldRepaint(RectangleRangeSymbolRenderer oldRenderer) { - return this != oldRenderer; - } - - @override - bool operator ==(Object other) => other is RectangleRangeSymbolRenderer; - - @override - int get hashCode => runtimeType.hashCode; -} diff --git a/web/charts/common/lib/src/common/text_element.dart b/web/charts/common/lib/src/common/text_element.dart deleted file mode 100644 index a25714563..000000000 --- a/web/charts/common/lib/src/common/text_element.dart +++ /dev/null @@ -1,80 +0,0 @@ -// 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 'text_measurement.dart' show TextMeasurement; -import 'text_style.dart' show TextStyle; - -/// Interface for accessing text measurement and painter. -abstract class TextElement { - /// The [TextStyle] of this [TextElement]. - TextStyle get textStyle; - - set textStyle(TextStyle value); - - /// The max width of this [TextElement] during measure and layout. - /// - /// If the text exceeds maxWidth, the [maxWidthStrategy] is used. - int get maxWidth; - - set maxWidth(int value); - - /// The strategy to use if this [TextElement] exceeds the [maxWidth]. - MaxWidthStrategy get maxWidthStrategy; - - set maxWidthStrategy(MaxWidthStrategy maxWidthStrategy); - - /// The opacity of this element, in addition to the alpha set on the color - /// of this element. - set opacity(double opacity); - - // The text of this [TextElement]. - String get text; - - /// The [TextMeasurement] of this [TextElement] as an approximate of what - /// is actually printed. - /// - /// Will return the [maxWidth] if set and the actual text width is larger. - TextMeasurement get measurement; - - /// The direction to render the text relative to the coordinate. - TextDirection get textDirection; - set textDirection(TextDirection direction); - - /// Return true if settings are all the same. - /// - /// Purposely excludes measurement because the measurement will request the - /// native [TextElement] to layout, which is expensive. We want to avoid the - /// layout by comparing with another [TextElement] to see if they have the - /// same settings. - static bool elementSettingsSame(TextElement a, TextElement b) { - return a.textStyle == b.textStyle && - a.maxWidth == b.maxWidth && - a.maxWidthStrategy == b.maxWidthStrategy && - a.text == b.text && - a.textDirection == b.textDirection; - } -} - -enum TextDirection { - ltr, - rtl, - center, -} - -/// The strategy to use if a [TextElement] exceeds the [maxWidth]. -enum MaxWidthStrategy { - truncate, - ellipsize, -} diff --git a/web/charts/common/lib/src/common/text_measurement.dart b/web/charts/common/lib/src/common/text_measurement.dart deleted file mode 100644 index fb419a05b..000000000 --- a/web/charts/common/lib/src/common/text_measurement.dart +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -/// A measurement result for rendering text. -class TextMeasurement { - /// Rendered width of the text. - final double horizontalSliceWidth; - - /// Vertical slice is likely based off the rendered text. - /// - /// This means that 'mo' and 'My' will have different heights so do not use - /// this for centering vertical text. - final double verticalSliceWidth; - - /// Baseline of the text for text vertical alignment. - final double baseline; - - TextMeasurement( - {this.horizontalSliceWidth, this.verticalSliceWidth, this.baseline}); -} diff --git a/web/charts/common/lib/src/common/text_style.dart b/web/charts/common/lib/src/common/text_style.dart deleted file mode 100644 index b88dbd1ac..000000000 --- a/web/charts/common/lib/src/common/text_style.dart +++ /dev/null @@ -1,25 +0,0 @@ -// 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 'paint_style.dart' show PaintStyle; - -/// Paint properties of a text. -abstract class TextStyle extends PaintStyle { - int get fontSize; - set fontSize(int value); - - String get fontFamily; - set fontFamily(String fontFamily); -} diff --git a/web/charts/common/lib/src/common/typed_registry.dart b/web/charts/common/lib/src/common/typed_registry.dart deleted file mode 100644 index 0fc7051c8..000000000 --- a/web/charts/common/lib/src/common/typed_registry.dart +++ /dev/null @@ -1,41 +0,0 @@ -// 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. - -class TypedRegistry { - final Map _registry = {}; - - R getAttr(TypedKey key) { - return _registry[key] as R; - } - - void setAttr(TypedKey key, R value) { - _registry[key] = value; - } - - void mergeFrom(TypedRegistry other) { - _registry.addAll(other._registry); - } -} - -class TypedKey { - final String uniqueKey; - const TypedKey(this.uniqueKey); - - @override - int get hashCode => uniqueKey.hashCode; - - @override - bool operator ==(other) => other is TypedKey && uniqueKey == other.uniqueKey; -} diff --git a/web/charts/common/lib/src/data/series.dart b/web/charts/common/lib/src/data/series.dart deleted file mode 100644 index 12a7078b9..000000000 --- a/web/charts/common/lib/src/data/series.dart +++ /dev/null @@ -1,222 +0,0 @@ -// 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 'package:meta/meta.dart'; - -import '../chart/cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; -import '../chart/common/chart_canvas.dart' show FillPatternType; -import '../common/color.dart' show Color; -import '../common/typed_registry.dart' show TypedRegistry, TypedKey; - -class Series { - final String id; - final String displayName; - final String seriesCategory; - final bool overlaySeries; - - final List data; - - /// [keyFn] defines a globally unique identifier for each datum. - /// - /// The key for each datum is used during chart animation to smoothly - /// transition data still in the series to its new state. - /// - /// Note: This is currently an optional function that is not fully used by all - /// series renderers yet. - final AccessorFn keyFn; - - final AccessorFn domainFn; - final AccessorFn domainLowerBoundFn; - final AccessorFn domainUpperBoundFn; - final AccessorFn measureFn; - final AccessorFn measureLowerBoundFn; - final AccessorFn measureUpperBoundFn; - final AccessorFn measureOffsetFn; - - /// [areaColorFn] returns the area color for a given data value. If not - /// provided, then some variation of the main [colorFn] will be used (e.g. - /// 10% opacity). - /// - /// This color is used for supplemental information on the series, such as - /// confidence intervals or area skirts. - final AccessorFn areaColorFn; - - /// [colorFn] returns the rendered stroke color for a given data value. - final AccessorFn colorFn; - - /// [dashPatternFn] returns the dash pattern for a given data value. - final AccessorFn> dashPatternFn; - - /// [fillColorFn] returns the rendered fill color for a given data value. If - /// not provided, then [colorFn] will be used as a fallback. - final AccessorFn fillColorFn; - - final AccessorFn fillPatternFn; - final AccessorFn radiusPxFn; - final AccessorFn strokeWidthPxFn; - final AccessorFn labelAccessorFn; - final AccessorFn insideLabelStyleAccessorFn; - final AccessorFn outsideLabelStyleAccessorFn; - - // TODO: should this be immutable as well? If not, should any of - // the non-required ones be final? - final SeriesAttributes attributes = SeriesAttributes(); - - factory Series( - {@required String id, - @required List data, - @required TypedAccessorFn domainFn, - @required TypedAccessorFn measureFn, - String displayName, - TypedAccessorFn areaColorFn, - TypedAccessorFn colorFn, - TypedAccessorFn> dashPatternFn, - TypedAccessorFn domainLowerBoundFn, - TypedAccessorFn domainUpperBoundFn, - TypedAccessorFn fillColorFn, - TypedAccessorFn fillPatternFn, - TypedAccessorFn keyFn, - TypedAccessorFn labelAccessorFn, - TypedAccessorFn insideLabelStyleAccessorFn, - TypedAccessorFn outsideLabelStyleAccessorFn, - TypedAccessorFn measureLowerBoundFn, - TypedAccessorFn measureUpperBoundFn, - TypedAccessorFn measureOffsetFn, - bool overlaySeries = false, - TypedAccessorFn radiusPxFn, - String seriesCategory, - TypedAccessorFn strokeWidthPxFn}) { - // Wrap typed accessors. - final _domainFn = (index) => domainFn(data[index], index); - final _measureFn = (index) => measureFn(data[index], index); - final _areaColorFn = - areaColorFn == null ? null : (index) => areaColorFn(data[index], index); - final _colorFn = - colorFn == null ? null : (index) => colorFn(data[index], index); - final _dashPatternFn = dashPatternFn == null - ? null - : (index) => dashPatternFn(data[index], index); - final _domainLowerBoundFn = domainLowerBoundFn == null - ? null - : (index) => domainLowerBoundFn(data[index], index); - final _domainUpperBoundFn = domainUpperBoundFn == null - ? null - : (index) => domainUpperBoundFn(data[index], index); - final _fillColorFn = - fillColorFn == null ? null : (index) => fillColorFn(data[index], index); - final _fillPatternFn = fillPatternFn == null - ? null - : (index) => fillPatternFn(data[index], index); - final _labelAccessorFn = labelAccessorFn == null - ? null - : (index) => labelAccessorFn(data[index], index); - final _insideLabelStyleAccessorFn = insideLabelStyleAccessorFn == null - ? null - : (index) => insideLabelStyleAccessorFn(data[index], index); - final _outsideLabelStyleAccessorFn = outsideLabelStyleAccessorFn == null - ? null - : (index) => outsideLabelStyleAccessorFn(data[index], index); - final _measureLowerBoundFn = measureLowerBoundFn == null - ? null - : (index) => measureLowerBoundFn(data[index], index); - final _measureUpperBoundFn = measureUpperBoundFn == null - ? null - : (index) => measureUpperBoundFn(data[index], index); - final _measureOffsetFn = measureOffsetFn == null - ? null - : (index) => measureOffsetFn(data[index], index); - final _radiusPxFn = - radiusPxFn == null ? null : (index) => radiusPxFn(data[index], index); - final _strokeWidthPxFn = strokeWidthPxFn == null - ? null - : (index) => strokeWidthPxFn(data[index], index); - - return Series._internal( - id: id, - data: data, - domainFn: _domainFn, - measureFn: _measureFn, - displayName: displayName, - areaColorFn: _areaColorFn, - colorFn: _colorFn, - dashPatternFn: _dashPatternFn, - domainLowerBoundFn: _domainLowerBoundFn, - domainUpperBoundFn: _domainUpperBoundFn, - fillColorFn: _fillColorFn, - fillPatternFn: _fillPatternFn, - labelAccessorFn: _labelAccessorFn, - insideLabelStyleAccessorFn: _insideLabelStyleAccessorFn, - outsideLabelStyleAccessorFn: _outsideLabelStyleAccessorFn, - measureLowerBoundFn: _measureLowerBoundFn, - measureUpperBoundFn: _measureUpperBoundFn, - measureOffsetFn: _measureOffsetFn, - overlaySeries: overlaySeries, - radiusPxFn: _radiusPxFn, - seriesCategory: seriesCategory, - strokeWidthPxFn: _strokeWidthPxFn, - ); - } - - Series._internal({ - @required this.id, - @required this.data, - @required this.domainFn, - @required this.measureFn, - this.displayName, - this.areaColorFn, - this.colorFn, - this.dashPatternFn, - this.domainLowerBoundFn, - this.domainUpperBoundFn, - this.fillColorFn, - this.fillPatternFn, - this.keyFn, - this.labelAccessorFn, - this.insideLabelStyleAccessorFn, - this.outsideLabelStyleAccessorFn, - this.measureLowerBoundFn, - this.measureUpperBoundFn, - this.measureOffsetFn, - this.overlaySeries = false, - this.radiusPxFn, - this.seriesCategory, - this.strokeWidthPxFn, - }); - - void setAttribute(AttributeKey key, R value) { - this.attributes.setAttr(key, value); - } - - R getAttribute(AttributeKey key) { - return this.attributes.getAttr(key); - } -} - -/// Computed property on series. -/// -/// If the [index] argument is `null`, the accessor is asked to provide a -/// property of [series] as a whole. Accessors are not required to support -/// such usage. -/// -/// Otherwise, [index] must be a valid subscript into a list of `series.length`. -typedef AccessorFn = R Function(int index); - -typedef TypedAccessorFn = R Function(T datum, int index); - -class AttributeKey extends TypedKey { - const AttributeKey(String uniqueKey) : super(uniqueKey); -} - -class SeriesAttributes extends TypedRegistry {} diff --git a/web/charts/common/pubspec.lock b/web/charts/common/pubspec.lock deleted file mode 100644 index 9966fb912..000000000 --- a/web/charts/common/pubspec.lock +++ /dev/null @@ -1,376 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - collection: - dependency: "direct main" - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: "direct main" - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: "direct main" - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" - meta: - dependency: "direct main" - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - mockito: - dependency: "direct dev" - description: - name: mockito - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.0" - multi_server_socket: - dependency: transitive - description: - name: multi_server_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.6" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: - dependency: "direct dev" - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.8" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - test: - dependency: "direct dev" - description: - name: test - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.6" - test_core: - dependency: transitive - description: - name: test_core - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.7" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" - vector_math: - dependency: "direct main" - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - vm_service_lib: - dependency: transitive - description: - name: vm_service_lib - url: "https://pub.dartlang.org" - source: hosted - version: "3.22.2+1" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" -sdks: - dart: ">=2.3.0-dev <3.0.0" diff --git a/web/charts/common/pubspec.yaml b/web/charts/common/pubspec.yaml deleted file mode 100644 index 9ea2e7210..000000000 --- a/web/charts/common/pubspec.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: charts_common -version: 0.6.0 -description: A common library for charting packages. -author: Charts Team -homepage: https://github.com/google/charts - -environment: - sdk: '>=2.3.0-dev <3.0.0' - -dependencies: - collection: ^1.14.5 - intl: ^0.15.2 - logging: any - meta: ^1.1.1 - vector_math: ^2.0.8 - -dev_dependencies: - mockito: ^4.0.0 - pedantic: ^1.5.0 - test: ^1.5.3 diff --git a/web/charts/common/test/chart/bar/bar_label_decorator_test.dart b/web/charts/common/test/chart/bar/bar_label_decorator_test.dart deleted file mode 100644 index e21fd9840..000000000 --- a/web/charts/common/test/chart/bar/bar_label_decorator_test.dart +++ /dev/null @@ -1,400 +0,0 @@ -// 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:charts_common/src/chart/common/processed_series.dart' - show ImmutableSeries; -import 'package:charts_common/src/common/color.dart' show Color; -import 'package:charts_common/src/common/graphics_factory.dart' - show GraphicsFactory; -import 'package:charts_common/src/common/line_style.dart' show LineStyle; -import 'package:charts_common/src/common/text_element.dart' - show TextDirection, TextElement, MaxWidthStrategy; -import 'package:charts_common/src/common/text_measurement.dart' - show TextMeasurement; -import 'package:charts_common/src/common/text_style.dart' show TextStyle; -import 'package:charts_common/src/chart/bar/bar_renderer.dart' - show ImmutableBarRendererElement; -import 'package:charts_common/src/chart/cartesian/axis/spec/axis_spec.dart' - show TextStyleSpec; -import 'package:charts_common/src/chart/common/chart_canvas.dart' - show ChartCanvas; -import 'package:charts_common/src/chart/bar/bar_label_decorator.dart' - show BarLabelDecorator, BarLabelAnchor, BarLabelPosition; -import 'package:charts_common/src/data/series.dart' show AccessorFn; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockCanvas extends Mock implements ChartCanvas {} - -/// A fake [GraphicsFactory] that returns [FakeTextStyle] and [FakeTextElement]. -class FakeGraphicsFactory extends GraphicsFactory { - @override - TextStyle createTextPaint() => FakeTextStyle(); - - @override - TextElement createTextElement(String text) => FakeTextElement(text); - - @override - LineStyle createLinePaint() => MockLinePaint(); -} - -/// Stores [TextStyle] properties for test to verify. -class FakeTextStyle implements TextStyle { - Color color; - int fontSize; - String fontFamily; -} - -/// Fake [TextElement] which returns text length as [horizontalSliceWidth]. -/// -/// Font size is returned for [verticalSliceWidth] and [baseline]. -class FakeTextElement implements TextElement { - final String text; - TextStyle textStyle; - int maxWidth; - MaxWidthStrategy maxWidthStrategy; - TextDirection textDirection; - double opacity; - - FakeTextElement(this.text); - - TextMeasurement get measurement => TextMeasurement( - horizontalSliceWidth: text.length.toDouble(), - verticalSliceWidth: textStyle.fontSize.toDouble(), - baseline: textStyle.fontSize.toDouble()); -} - -class MockLinePaint extends Mock implements LineStyle {} - -class FakeBarRendererElement implements ImmutableBarRendererElement { - final _series = MockImmutableSeries(); - final AccessorFn labelAccessor; - final String datum; - final Rectangle bounds; - final List data; - int index; - - FakeBarRendererElement( - this.datum, this.bounds, this.labelAccessor, this.data) { - index = data.indexOf(datum); - when(_series.labelAccessorFn).thenReturn(labelAccessor); - when(_series.data).thenReturn(data); - } - - ImmutableSeries get series => _series; -} - -class MockImmutableSeries extends Mock implements ImmutableSeries {} - -void main() { - ChartCanvas canvas; - GraphicsFactory graphicsFactory; - Rectangle drawBounds; - - setUpAll(() { - canvas = MockCanvas(); - graphicsFactory = FakeGraphicsFactory(); - drawBounds = Rectangle(0, 0, 200, 100); - }); - - group('horizontal bar chart', () { - test('Paint labels with default settings', () { - final data = ['A', 'B']; - final barElements = [ - // 'LabelA' and 'LabelB' both have lengths of 6. - // 'LabelB' would not fit inside the bar in auto setting because it has - // width of 5. - FakeBarRendererElement( - 'A', Rectangle(0, 20, 50, 20), (_) => 'LabelA', data), - FakeBarRendererElement( - 'B', Rectangle(0, 70, 5, 20), (_) => 'LabelB', data) - ]; - final decorator = BarLabelDecorator(); - - decorator.decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - // Draw text is called twice (once for each bar) and all 3 parameters were - // captured. Total parameters captured expected to be 6. - expect(captured, hasLength(6)); - // For bar 'A'. - expect(captured[0].maxWidth, equals(50 - decorator.labelPadding * 2)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect(captured[1], equals(decorator.labelPadding)); - expect(captured[2], - equals(30 - decorator.insideLabelStyleSpec.fontSize ~/ 2)); - // For bar 'B'. - expect( - captured[3].maxWidth, equals(200 - 5 - decorator.labelPadding * 2)); - expect(captured[3].textDirection, equals(TextDirection.ltr)); - expect(captured[4], equals(5 + decorator.labelPadding)); - expect(captured[5], - equals(80 - decorator.outsideLabelStyleSpec.fontSize ~/ 2)); - }); - - test('LabelPosition.auto paints inside bar if outside bar has less width', - () { - final barElements = [ - // 'LabelABC' would not fit inside the bar in auto setting because it - // has a width of 8. - FakeBarRendererElement( - 'A', Rectangle(0, 0, 6, 20), (_) => 'LabelABC', ['A']), - ]; - // Draw bounds with width of 10 means that space inside the bar is larger. - final smallDrawBounds = Rectangle(0, 0, 10, 20); - - BarLabelDecorator( - labelPadding: 0, // Turn off label padding for testing. - insideLabelStyleSpec: TextStyleSpec(fontSize: 10)) - .decorate(barElements, canvas, graphicsFactory, - drawBounds: smallDrawBounds, - animationPercent: 1.0, - renderingVertically: false); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(6)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect(captured[1], equals(0)); - expect(captured[2], equals(5)); - }); - - test('LabelPosition.inside always paints inside the bar', () { - final barElements = [ - // 'LabelABC' would not fit inside the bar in auto setting because it - // has a width of 8. - FakeBarRendererElement( - 'A', Rectangle(0, 0, 6, 20), (_) => 'LabelABC', ['A']), - ]; - - BarLabelDecorator( - labelPosition: BarLabelPosition.inside, - labelPadding: 0, // Turn off label padding for testing. - insideLabelStyleSpec: TextStyleSpec(fontSize: 10)) - .decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(6)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect(captured[1], equals(0)); - expect(captured[2], equals(5)); - }); - - test('LabelPosition.outside always paints outside the bar', () { - final barElements = [ - FakeBarRendererElement( - 'A', Rectangle(0, 0, 10, 20), (_) => 'Label', ['A']), - ]; - - BarLabelDecorator( - labelPosition: BarLabelPosition.outside, - labelPadding: 0, // Turn off label padding for testing. - outsideLabelStyleSpec: TextStyleSpec(fontSize: 10)) - .decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(190)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect(captured[1], equals(10)); - expect(captured[2], equals(5)); - }); - - test('Inside and outside label styles are applied', () { - final data = ['A', 'B']; - final barElements = [ - // 'LabelA' and 'LabelB' both have lengths of 6. - // 'LabelB' would not fit inside the bar in auto setting because it has - // width of 5. - FakeBarRendererElement( - 'A', Rectangle(0, 20, 50, 20), (_) => 'LabelA', data), - FakeBarRendererElement( - 'B', Rectangle(0, 70, 5, 20), (_) => 'LabelB', data) - ]; - final insideColor = Color(r: 0, g: 0, b: 0); - final outsideColor = Color(r: 255, g: 255, b: 255); - final decorator = BarLabelDecorator( - labelPadding: 0, - insideLabelStyleSpec: TextStyleSpec( - fontSize: 10, fontFamily: 'insideFont', color: insideColor), - outsideLabelStyleSpec: TextStyleSpec( - fontSize: 8, fontFamily: 'outsideFont', color: outsideColor)); - - decorator.decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - // Draw text is called twice (once for each bar) and all 3 parameters were - // captured. Total parameters captured expected to be 6. - expect(captured, hasLength(6)); - // For bar 'A'. - expect(captured[0].maxWidth, equals(50)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect(captured[0].textStyle.fontFamily, equals('insideFont')); - expect(captured[0].textStyle.color, equals(insideColor)); - expect(captured[1], equals(0)); - expect(captured[2], equals(30 - 5)); - // For bar 'B'. - expect(captured[3].maxWidth, equals(200 - 5)); - expect(captured[3].textDirection, equals(TextDirection.ltr)); - expect(captured[3].textStyle.fontFamily, equals('outsideFont')); - expect(captured[3].textStyle.color, equals(outsideColor)); - expect(captured[4], equals(5)); - expect(captured[5], equals(80 - 4)); - }); - - test('TextAnchor.end starts on the right most of bar', () { - final barElements = [ - FakeBarRendererElement( - 'A', Rectangle(0, 0, 10, 20), (_) => 'LabelA', ['A']) - ]; - - BarLabelDecorator( - labelAnchor: BarLabelAnchor.end, - labelPosition: BarLabelPosition.inside, - labelPadding: 0, // Turn off label padding for testing. - insideLabelStyleSpec: TextStyleSpec(fontSize: 10)) - .decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(10)); - expect(captured[0].textDirection, equals(TextDirection.rtl)); - expect(captured[1], equals(10)); - expect(captured[2], equals(5)); - }); - - test('RTL TextAnchor.start starts on the right', () { - final barElements = [ - FakeBarRendererElement( - 'A', Rectangle(0, 0, 10, 20), (_) => 'LabelA', ['A']) - ]; - - BarLabelDecorator( - labelAnchor: BarLabelAnchor.start, - labelPosition: BarLabelPosition.inside, - labelPadding: 0, // Turn off label padding for testing. - insideLabelStyleSpec: TextStyleSpec(fontSize: 10)) - .decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false, - rtl: true); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(10)); - expect(captured[0].textDirection, equals(TextDirection.rtl)); - expect(captured[1], equals(10)); - expect(captured[2], equals(5)); - }); - - test('RTL TextAnchor.end starts on the left', () { - final barElements = [ - FakeBarRendererElement( - 'A', Rectangle(0, 0, 10, 20), (_) => 'LabelA', ['A']) - ]; - - BarLabelDecorator( - labelAnchor: BarLabelAnchor.end, - labelPosition: BarLabelPosition.inside, - labelPadding: 0, // Turn off label padding for testing. - insideLabelStyleSpec: TextStyleSpec(fontSize: 10)) - .decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false, - rtl: true); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(10)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect(captured[1], equals(0)); - expect(captured[2], equals(5)); - }); - }); - - group('Null and empty label scenarios', () { - test('Skip label if label accessor does not exist', () { - final barElements = [ - FakeBarRendererElement('A', Rectangle(0, 0, 10, 20), null, ['A']) - ]; - - BarLabelDecorator().decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - verifyNever(canvas.drawText(any, any, any)); - }); - - test('Skip label if label is null or empty', () { - final data = ['A', 'B']; - final barElements = [ - FakeBarRendererElement('A', Rectangle(0, 0, 10, 20), null, data), - FakeBarRendererElement('B', Rectangle(0, 50, 10, 20), (_) => '', data), - ]; - - BarLabelDecorator().decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - verifyNever(canvas.drawText(any, any, any)); - }); - - test('Skip label if no width available', () { - final barElements = [ - FakeBarRendererElement('A', Rectangle(0, 0, 200, 20), (_) => 'a', ['A']) - ]; - - BarLabelDecorator( - labelPadding: 0, - labelPosition: BarLabelPosition.outside, - ).decorate(barElements, canvas, graphicsFactory, - drawBounds: drawBounds, - animationPercent: 1.0, - renderingVertically: false); - - verifyNever(canvas.drawText(any, any, any)); - }); - }); -} diff --git a/web/charts/common/test/chart/bar/bar_renderer_test.dart b/web/charts/common/test/chart/bar/bar_renderer_test.dart deleted file mode 100644 index d34d07650..000000000 --- a/web/charts/common/test/chart/bar/bar_renderer_test.dart +++ /dev/null @@ -1,881 +0,0 @@ -// 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 'package:charts_common/src/chart/bar/bar_renderer.dart'; -import 'package:charts_common/src/chart/bar/bar_renderer_config.dart'; -import 'package:charts_common/src/chart/bar/base_bar_renderer.dart'; -import 'package:charts_common/src/chart/bar/base_bar_renderer_config.dart'; -import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart' - show MutableSeries; -import 'package:charts_common/src/common/material_palette.dart' - show MaterialPalette; -import 'package:charts_common/src/common/color.dart'; -import 'package:charts_common/src/data/series.dart' show Series; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaign; - final int clickCount; - MyRow(this.campaign, this.clickCount); -} - -class MockAxis extends Mock implements Axis {} - -class MockCanvas extends Mock implements ChartCanvas {} - -class MockContext extends Mock implements ChartContext {} - -class MockChart extends Mock implements CartesianChart {} - -class FakeBarRenderer extends BarRenderer { - int paintBarCallCount = 0; - - factory FakeBarRenderer({BarRendererConfig config, String rendererId}) { - return FakeBarRenderer._internal(config: config, rendererId: rendererId); - } - - FakeBarRenderer._internal({BarRendererConfig config, String rendererId}) - : super.internal(config: config, rendererId: rendererId); - - @override - void paintBar(ChartCanvas canvas, double animationPercent, - Iterable> barElements) { - paintBarCallCount += 1; - } -} - -void main() { - BarRenderer renderer; - List> seriesList; - List> groupedStackedSeriesList; - - ///////////////////////////////////////// - // Convenience methods for creating mocks. - ///////////////////////////////////////// - _configureBaseRenderer(BaseBarRenderer renderer, bool vertical) { - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - final verticalChart = MockChart(); - when(verticalChart.vertical).thenReturn(vertical); - when(verticalChart.context).thenReturn(context); - renderer.onAttach(verticalChart); - - return renderer; - } - - BarRenderer makeRenderer({BarRendererConfig config}) { - final renderer = BarRenderer(config: config); - _configureBaseRenderer(renderer, true); - return renderer; - } - - FakeBarRenderer makeFakeRenderer({BarRendererConfig config}) { - final renderer = FakeBarRenderer(config: config); - _configureBaseRenderer(renderer, true); - return renderer; - } - - setUp(() { - var myFakeDesktopAData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeTabletAData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeMobileAData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeDesktopBData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeTabletBData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeMobileBData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - seriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (_, __) => MaterialPalette.blue.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeDesktopAData)), - MutableSeries(Series( - id: 'Tablet', - colorFn: (_, __) => MaterialPalette.red.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeTabletAData)), - MutableSeries(Series( - id: 'Mobile', - colorFn: (_, __) => MaterialPalette.green.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeMobileAData)) - ]; - - groupedStackedSeriesList = [ - MutableSeries(Series( - id: 'Desktop A', - seriesCategory: 'A', - colorFn: (_, __) => MaterialPalette.blue.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeDesktopAData)), - MutableSeries(Series( - id: 'Tablet A', - seriesCategory: 'A', - colorFn: (_, __) => MaterialPalette.red.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeTabletAData)), - MutableSeries(Series( - id: 'Mobile A', - seriesCategory: 'A', - colorFn: (_, __) => MaterialPalette.green.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeMobileAData)), - MutableSeries(Series( - id: 'Desktop B', - seriesCategory: 'B', - colorFn: (_, __) => MaterialPalette.blue.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeDesktopBData)), - MutableSeries(Series( - id: 'Tablet B', - seriesCategory: 'B', - colorFn: (_, __) => MaterialPalette.red.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeTabletBData)), - MutableSeries(Series( - id: 'Mobile B', - seriesCategory: 'B', - colorFn: (_, __) => MaterialPalette.green.shadeDefault, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeMobileBData)) - ]; - }); - - group('preprocess', () { - test('with grouped bars', () { - renderer = makeRenderer( - config: BarRendererConfig(groupingType: BarGroupingType.grouped)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(2)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - }); - - test('with grouped stacked bars', () { - renderer = makeRenderer( - config: - BarRendererConfig(groupingType: BarGroupingType.groupedStacked)); - - renderer.preprocessSeries(groupedStackedSeriesList); - - expect(groupedStackedSeriesList.length, equals(6)); - - // Validate Desktop A series. - var series = groupedStackedSeriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('A')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - - // Validate Tablet A series. - series = groupedStackedSeriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('A')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - // Validate Mobile A series. - series = groupedStackedSeriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('A')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - - // Validate Desktop B series. - series = groupedStackedSeriesList[3]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('B')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - - // Validate Tablet B series. - series = groupedStackedSeriesList[4]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('B')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - // Validate Mobile B series. - series = groupedStackedSeriesList[5]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('B')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - }); - - test('with stacked bars', () { - renderer = makeRenderer( - config: BarRendererConfig(groupingType: BarGroupingType.stacked)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - }); - - test('with stacked bars containing zero and null', () { - // Set up some nulls and zeros in the data. - seriesList[2].data[0] = MyRow('MyCampaign1', null); - seriesList[2].data[2] = MyRow('MyCampaign3', 0); - - seriesList[1].data[1] = MyRow('MyCampaign2', null); - seriesList[1].data[3] = MyRow('MyOtherCampaign', 0); - - seriesList[0].data[2] = MyRow('MyCampaign3', 0); - - renderer = makeRenderer( - config: BarRendererConfig(groupingType: BarGroupingType.stacked)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - var elementsList = series.getAttr(barElementsKey); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - element = elementsList[1]; - expect(element.measureOffset, equals(25)); - expect(element.measureOffsetPlusMeasure, equals(50)); - expect(series.measureOffsetFn(1), equals(25)); - - element = elementsList[2]; - expect(element.measureOffset, equals(100)); - expect(element.measureOffsetPlusMeasure, equals(100)); - expect(series.measureOffsetFn(2), equals(100)); - - element = elementsList[3]; - expect(element.measureOffset, equals(75)); - expect(element.measureOffsetPlusMeasure, equals(150)); - expect(series.measureOffsetFn(3), equals(75)); - - // Validate Tablet series. - series = seriesList[1]; - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - - element = elementsList[1]; - expect(element.measureOffset, equals(25)); - expect(element.measureOffsetPlusMeasure, equals(25)); - expect(series.measureOffsetFn(1), equals(25)); - - element = elementsList[2]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(100)); - expect(series.measureOffsetFn(2), equals(0)); - - element = elementsList[3]; - expect(element.measureOffset, equals(75)); - expect(element.measureOffsetPlusMeasure, equals(75)); - expect(series.measureOffsetFn(3), equals(75)); - - // Validate Mobile series. - series = seriesList[2]; - elementsList = series.getAttr(barElementsKey); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(0)); - expect(series.measureOffsetFn(0), equals(0)); - - element = elementsList[1]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(25)); - expect(series.measureOffsetFn(1), equals(0)); - - element = elementsList[2]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(0)); - expect(series.measureOffsetFn(2), equals(0)); - - element = elementsList[3]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(75)); - expect(series.measureOffsetFn(3), equals(0)); - }); - }); - - group('preprocess weight pattern', () { - test('with grouped bars', () { - renderer = makeRenderer( - config: BarRendererConfig( - groupingType: BarGroupingType.grouped, weightPattern: [3, 2, 1])); - - renderer.preprocessSeries(seriesList); - - // Verify that bar group weights are proportional to the sum of the used - // segments of weightPattern. The weightPattern should be distributed - // amongst bars that share the same domain value. - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(2)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5 + 1 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 6)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - }); - - test('with grouped stacked bars', () { - renderer = makeRenderer( - config: BarRendererConfig( - groupingType: BarGroupingType.groupedStacked, - weightPattern: [2, 1])); - - renderer.preprocessSeries(groupedStackedSeriesList); - - // Verify that bar group weights are proportional to the sum of the used - // segments of weightPattern. The weightPattern should be distributed - // amongst bars that share the same domain and series category values. - - expect(groupedStackedSeriesList.length, equals(6)); - - // Validate Desktop A series. - var series = groupedStackedSeriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(stackKeyKey), equals('A')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - - // Validate Tablet A series. - series = groupedStackedSeriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(stackKeyKey), equals('A')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - // Validate Mobile A series. - series = groupedStackedSeriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(stackKeyKey), equals('A')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - - // Validate Desktop B series. - series = groupedStackedSeriesList[3]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('B')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - - // Validate Tablet B series. - series = groupedStackedSeriesList[4]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('B')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - // Validate Mobile B series. - series = groupedStackedSeriesList[5]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(2)); - expect(series.getAttr(previousBarGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('B')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - }); - - test('with stacked bars - weightPattern not used', () { - renderer = makeRenderer( - config: BarRendererConfig( - groupingType: BarGroupingType.stacked, weightPattern: [2, 1])); - - renderer.preprocessSeries(seriesList); - - // Verify that weightPattern is not used, since stacked bars have only a - // single group per domain value. - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - }); - }); - - group('null measure', () { - test('only include null in draw if animating from a non null measure', () { - // Helper to create series list for this test only. - List> _createSeriesList(List data) { - final domainAxis = MockAxis(); - when(domainAxis.rangeBand).thenReturn(100.0); - when(domainAxis.getLocation('MyCampaign1')).thenReturn(20.0); - when(domainAxis.getLocation('MyCampaign2')).thenReturn(40.0); - when(domainAxis.getLocation('MyCampaign3')).thenReturn(60.0); - when(domainAxis.getLocation('MyOtherCampaign')).thenReturn(80.0); - final measureAxis = MockAxis(); - when(measureAxis.getLocation(0)).thenReturn(0.0); - when(measureAxis.getLocation(5)).thenReturn(5.0); - when(measureAxis.getLocation(75)).thenReturn(75.0); - when(measureAxis.getLocation(100)).thenReturn(100.0); - - final color = Color.fromHex(code: '#000000'); - - final series = MutableSeries(Series( - id: 'Desktop', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - colorFn: (_, __) => color, - fillColorFn: (_, __) => color, - dashPatternFn: (_, __) => [1], - data: data)) - ..setAttr(domainAxisKey, domainAxis) - ..setAttr(measureAxisKey, measureAxis); - - return [series]; - } - - final canvas = MockCanvas(); - - final myDataWithNull = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', null), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - final seriesListWithNull = _createSeriesList(myDataWithNull); - - final myDataWithMeasures = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 0), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - final seriesListWithMeasures = _createSeriesList(myDataWithMeasures); - - final renderer = makeFakeRenderer( - config: BarRendererConfig(groupingType: BarGroupingType.grouped)); - - // Verify that only 3 bars are drawn for an initial draw with null data. - renderer.preprocessSeries(seriesListWithNull); - renderer.update(seriesListWithNull, true); - renderer.paintBarCallCount = 0; - renderer.paint(canvas, 0.5); - expect(renderer.paintBarCallCount, equals(3)); - - // On animation complete, verify that only 3 bars are drawn. - renderer.paintBarCallCount = 0; - renderer.paint(canvas, 1.0); - expect(renderer.paintBarCallCount, equals(3)); - - // Change series list where there are measures on all values, verify all - // 4 bars were drawn - renderer.preprocessSeries(seriesListWithMeasures); - renderer.update(seriesListWithMeasures, true); - renderer.paintBarCallCount = 0; - renderer.paint(canvas, 0.5); - expect(renderer.paintBarCallCount, equals(4)); - - // Change series to one with null measures, verifies all 4 bars drawn - renderer.preprocessSeries(seriesListWithNull); - renderer.update(seriesListWithNull, true); - renderer.paintBarCallCount = 0; - renderer.paint(canvas, 0.5); - expect(renderer.paintBarCallCount, equals(4)); - - // On animation complete, verify that only 3 bars are drawn. - renderer.paintBarCallCount = 0; - renderer.paint(canvas, 1.0); - expect(renderer.paintBarCallCount, equals(3)); - }); - }); -} diff --git a/web/charts/common/test/chart/bar/bar_target_line_renderer_test.dart b/web/charts/common/test/chart/bar/bar_target_line_renderer_test.dart deleted file mode 100644 index dc34d1f9b..000000000 --- a/web/charts/common/test/chart/bar/bar_target_line_renderer_test.dart +++ /dev/null @@ -1,653 +0,0 @@ -// 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 'package:charts_common/src/chart/bar/bar_target_line_renderer.dart'; -import 'package:charts_common/src/chart/bar/bar_target_line_renderer_config.dart'; -import 'package:charts_common/src/chart/bar/base_bar_renderer.dart'; -import 'package:charts_common/src/chart/bar/base_bar_renderer_config.dart'; -import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart' - show MutableSeries; -import 'package:charts_common/src/common/color.dart'; -import 'package:charts_common/src/data/series.dart' show Series; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaign; - final int clickCount; - MyRow(this.campaign, this.clickCount); -} - -class MockAxis extends Mock implements Axis {} - -class MockCanvas extends Mock implements ChartCanvas { - final drawLinePointsList = >[]; - - void drawLine( - {List points, - Rectangle clipBounds, - Color fill, - Color stroke, - bool roundEndCaps, - double strokeWidthPx, - List dashPattern}) { - drawLinePointsList.add(points); - } -} - -class MockContext extends Mock implements ChartContext {} - -class MockChart extends Mock implements CartesianChart {} - -void main() { - BarTargetLineRenderer renderer; - List> seriesList; - - ///////////////////////////////////////// - // Convenience methods for creating mocks. - ///////////////////////////////////////// - _configureBaseRenderer(BaseBarRenderer renderer, bool vertical) { - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - final verticalChart = MockChart(); - when(verticalChart.vertical).thenReturn(vertical); - when(verticalChart.context).thenReturn(context); - renderer.onAttach(verticalChart); - - return renderer; - } - - BarTargetLineRenderer makeRenderer({BarTargetLineRendererConfig config}) { - final renderer = BarTargetLineRenderer(config: config); - _configureBaseRenderer(renderer, true); - return renderer; - } - - setUp(() { - var myFakeDesktopData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeTabletData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - var myFakeMobileData = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 25), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - - seriesList = [ - MutableSeries(Series( - id: 'Desktop', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeDesktopData)), - MutableSeries(Series( - id: 'Tablet', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeTabletData)), - MutableSeries(Series( - id: 'Mobile', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeMobileData)) - ]; - }); - - group('preprocess', () { - test('with grouped bar target lines', () { - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.grouped)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(2)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(2 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - }); - - test('with stacked bar target lines', () { - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.stacked)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - }); - - test('with stacked bar target lines containing zero and null', () { - // Set up some nulls and zeros in the data. - seriesList[2].data[0] = MyRow('MyCampaign1', null); - seriesList[2].data[2] = MyRow('MyCampaign3', 0); - - seriesList[1].data[1] = MyRow('MyCampaign2', null); - seriesList[1].data[3] = MyRow('MyOtherCampaign', 0); - - seriesList[0].data[2] = MyRow('MyCampaign3', 0); - - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.stacked)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - var elementsList = series.getAttr(barElementsKey); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[1]; - expect(element.measureOffset, equals(25)); - expect(element.measureOffsetPlusMeasure, equals(50)); - expect(series.measureOffsetFn(1), equals(25)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[2]; - expect(element.measureOffset, equals(100)); - expect(element.measureOffsetPlusMeasure, equals(100)); - expect(series.measureOffsetFn(2), equals(100)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[3]; - expect(element.measureOffset, equals(75)); - expect(element.measureOffsetPlusMeasure, equals(150)); - expect(series.measureOffsetFn(3), equals(75)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Tablet series. - series = seriesList[1]; - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[1]; - expect(element.measureOffset, equals(25)); - expect(element.measureOffsetPlusMeasure, equals(25)); - expect(series.measureOffsetFn(1), equals(25)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[2]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(100)); - expect(series.measureOffsetFn(2), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[3]; - expect(element.measureOffset, equals(75)); - expect(element.measureOffsetPlusMeasure, equals(75)); - expect(series.measureOffsetFn(3), equals(75)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Mobile series. - series = seriesList[2]; - elementsList = series.getAttr(barElementsKey); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(0)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[1]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(25)); - expect(series.measureOffsetFn(1), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[2]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(0)); - expect(series.measureOffsetFn(2), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - element = elementsList[3]; - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(75)); - expect(series.measureOffsetFn(3), equals(0)); - expect(element.strokeWidthPx, equals(3)); - }); - }); - - test('with stroke width target lines', () { - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.grouped, strokeWidthPx: 5.0)); - - renderer.preprocessSeries(seriesList); - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - var elementsList = series.getAttr(barElementsKey); - - var element = elementsList[0]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[1]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[2]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[3]; - expect(element.strokeWidthPx, equals(5)); - - // Validate Tablet series. - series = seriesList[1]; - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[1]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[2]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[3]; - expect(element.strokeWidthPx, equals(5)); - - // Validate Mobile series. - series = seriesList[2]; - elementsList = series.getAttr(barElementsKey); - - element = elementsList[0]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[1]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[2]; - expect(element.strokeWidthPx, equals(5)); - - element = elementsList[3]; - expect(element.strokeWidthPx, equals(5)); - }); - - group('preprocess with weight pattern', () { - test('with grouped bar target lines', () { - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.grouped, weightPattern: [3, 2, 1])); - - renderer.preprocessSeries(seriesList); - - // Verify that bar group weights are proportional to the sum of the used - // segments of weightPattern. The weightPattern should be distributed - // amongst bars that share the same domain value. - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(0.5)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(1)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 3)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(2)); - expect(series.getAttr(barGroupCountKey), equals(3)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.5 + 1 / 3)); - expect(series.getAttr(barGroupWeightKey), equals(1 / 6)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(null)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - }); - - test('with stacked bar target lines - weightPattern not used', () { - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.stacked, weightPattern: [2, 1])); - - renderer.preprocessSeries(seriesList); - - // Verify that weightPattern is not used, since stacked bars have only a - // single group per domain value. - - expect(seriesList.length, equals(3)); - - // Validate Desktop series. - var series = seriesList[0]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - var elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - var element = elementsList[0]; - expect(element.barStackIndex, equals(2)); - expect(element.measureOffset, equals(10)); - expect(element.measureOffsetPlusMeasure, equals(15)); - expect(series.measureOffsetFn(0), equals(10)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Tablet series. - series = seriesList[1]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(1)); - expect(element.measureOffset, equals(5)); - expect(element.measureOffsetPlusMeasure, equals(10)); - expect(series.measureOffsetFn(0), equals(5)); - expect(element.strokeWidthPx, equals(3)); - - // Validate Mobile series. - series = seriesList[2]; - expect(series.getAttr(barGroupIndexKey), equals(0)); - expect(series.getAttr(barGroupCountKey), equals(1)); - expect(series.getAttr(previousBarGroupWeightKey), equals(0.0)); - expect(series.getAttr(barGroupWeightKey), equals(1)); - expect(series.getAttr(stackKeyKey), equals('__defaultKey__')); - - elementsList = series.getAttr(barElementsKey); - expect(elementsList.length, equals(4)); - - element = elementsList[0]; - expect(element.barStackIndex, equals(0)); - expect(element.measureOffset, equals(0)); - expect(element.measureOffsetPlusMeasure, equals(5)); - expect(series.measureOffsetFn(0), equals(0)); - expect(element.strokeWidthPx, equals(3)); - }); - }); - - group('null measure', () { - test('only include null in draw if animating from a non null measure', () { - // Helper to create series list for this test only. - List> _createSeriesList(List data) { - final domainAxis = MockAxis(); - when(domainAxis.rangeBand).thenReturn(100.0); - when(domainAxis.getLocation('MyCampaign1')).thenReturn(20.0); - when(domainAxis.getLocation('MyCampaign2')).thenReturn(40.0); - when(domainAxis.getLocation('MyCampaign3')).thenReturn(60.0); - when(domainAxis.getLocation('MyOtherCampaign')).thenReturn(80.0); - final measureAxis = MockAxis(); - when(measureAxis.getLocation(0)).thenReturn(0.0); - when(measureAxis.getLocation(5)).thenReturn(5.0); - when(measureAxis.getLocation(75)).thenReturn(75.0); - when(measureAxis.getLocation(100)).thenReturn(100.0); - - final color = Color.fromHex(code: '#000000'); - - final series = MutableSeries(Series( - id: 'Desktop', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - colorFn: (_, __) => color, - fillColorFn: (_, __) => color, - dashPatternFn: (_, __) => [1], - data: data)) - ..setAttr(domainAxisKey, domainAxis) - ..setAttr(measureAxisKey, measureAxis); - - return [series]; - } - - final canvas = MockCanvas(); - - final myDataWithNull = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', null), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - final seriesListWithNull = _createSeriesList(myDataWithNull); - - final myDataWithMeasures = [ - MyRow('MyCampaign1', 5), - MyRow('MyCampaign2', 0), - MyRow('MyCampaign3', 100), - MyRow('MyOtherCampaign', 75), - ]; - final seriesListWithMeasures = _createSeriesList(myDataWithMeasures); - - renderer = makeRenderer( - config: BarTargetLineRendererConfig( - groupingType: BarGroupingType.grouped)); - - // Verify that only 3 lines are drawn for an initial draw with null data. - renderer.preprocessSeries(seriesListWithNull); - renderer.update(seriesListWithNull, true); - canvas.drawLinePointsList.clear(); - renderer.paint(canvas, 0.5); - expect(canvas.drawLinePointsList, hasLength(3)); - - // On animation complete, verify that only 3 lines are drawn. - canvas.drawLinePointsList.clear(); - renderer.paint(canvas, 1.0); - expect(canvas.drawLinePointsList, hasLength(3)); - - // Change series list where there are measures on all values, verify all - // 4 lines were drawn - renderer.preprocessSeries(seriesListWithMeasures); - renderer.update(seriesListWithMeasures, true); - canvas.drawLinePointsList.clear(); - renderer.paint(canvas, 0.5); - expect(canvas.drawLinePointsList, hasLength(4)); - - // Change series to one with null measures, verifies all 4 lines drawn - renderer.preprocessSeries(seriesListWithNull); - renderer.update(seriesListWithNull, true); - canvas.drawLinePointsList.clear(); - renderer.paint(canvas, 0.5); - expect(canvas.drawLinePointsList, hasLength(4)); - - // On animation complete, verify that only 3 lines are drawn. - canvas.drawLinePointsList.clear(); - renderer.paint(canvas, 1.0); - expect(canvas.drawLinePointsList, hasLength(3)); - }); - }); -} diff --git a/web/charts/common/test/chart/bar/renderer_nearest_detail_test.dart b/web/charts/common/test/chart/bar/renderer_nearest_detail_test.dart deleted file mode 100644 index 9d25f0929..000000000 --- a/web/charts/common/test/chart/bar/renderer_nearest_detail_test.dart +++ /dev/null @@ -1,1418 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/bar/bar_renderer.dart'; -import 'package:charts_common/src/chart/bar/bar_renderer_config.dart'; -import 'package:charts_common/src/chart/bar/bar_target_line_renderer.dart'; -import 'package:charts_common/src/chart/bar/bar_target_line_renderer_config.dart'; -import 'package:charts_common/src/chart/bar/base_bar_renderer.dart'; -import 'package:charts_common/src/chart/bar/base_bar_renderer_config.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/common/color.dart'; -import 'package:charts_common/src/data/series.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaign; - final int clickCount; - MyRow(this.campaign, this.clickCount); -} - -/// Datum for the time series chart -class MyDateTimeRow { - final DateTime time; - final int clickCount; - MyDateTimeRow(this.time, this.clickCount); -} - -// TODO: Test in RTL context as well. - -class MockContext extends Mock implements ChartContext {} - -class MockChart extends Mock implements CartesianChart {} - -class MockOrdinalAxis extends Mock implements OrdinalAxis {} - -class MockNumericAxis extends Mock implements Axis {} - -class MockDateTimeAxis extends Mock implements Axis {} - -class MockCanvas extends Mock implements ChartCanvas {} - -void main() { - final date0 = DateTime(2018, 2, 1); - final date1 = DateTime(2018, 2, 7); - final dateOutsideViewport = DateTime(2018, 1, 1); - - ///////////////////////////////////////// - // Convenience methods for creating mocks. - ///////////////////////////////////////// - _configureBaseRenderer(BaseBarRenderer renderer, bool vertical) { - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - final verticalChart = MockChart(); - when(verticalChart.vertical).thenReturn(vertical); - when(verticalChart.context).thenReturn(context); - renderer.onAttach(verticalChart); - - final layoutBounds = vertical - ? Rectangle(70, 20, 230, 100) - : Rectangle(70, 20, 100, 230); - renderer.layout(layoutBounds, layoutBounds); - return renderer; - } - - BaseBarRenderer _makeBarRenderer({bool vertical, BarGroupingType groupType}) { - final renderer = - BarRenderer(config: BarRendererConfig(groupingType: groupType)); - _configureBaseRenderer(renderer, vertical); - return renderer; - } - - BaseBarRenderer _makeBarTargetRenderer( - {bool vertical, BarGroupingType groupType}) { - final renderer = BarTargetLineRenderer( - config: BarTargetLineRendererConfig(groupingType: groupType)); - _configureBaseRenderer(renderer, vertical); - return renderer; - } - - MutableSeries _makeSeries( - {String id, String seriesCategory, bool vertical = true}) { - final data = [ - MyRow('camp0', 10), - MyRow('camp1', 10), - ]; - - final series = MutableSeries(Series( - id: id, - data: data, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - seriesCategory: seriesCategory, - )); - - series.measureOffsetFn = (_) => 0.0; - series.colorFn = (_) => Color.fromHex(code: '#000000'); - - // Mock the Domain axis results. - final domainAxis = MockOrdinalAxis(); - when(domainAxis.rangeBand).thenReturn(100.0); - final domainOffset = vertical ? 70.0 : 20.0; - when(domainAxis.getLocation('camp0')) - .thenReturn(domainOffset + 10.0 + 50.0); - when(domainAxis.getLocation('camp1')) - .thenReturn(domainOffset + 10.0 + 100.0 + 10.0 + 50.0); - when(domainAxis.getLocation('outsideViewport')).thenReturn(-51.0); - - if (vertical) { - when(domainAxis.getDomain(100.0)).thenReturn('camp0'); - when(domainAxis.getDomain(93.0)).thenReturn('camp0'); - when(domainAxis.getDomain(130.0)).thenReturn('camp0'); - when(domainAxis.getDomain(65.0)).thenReturn('outsideViewport'); - } else { - when(domainAxis.getDomain(50.0)).thenReturn('camp0'); - when(domainAxis.getDomain(43.0)).thenReturn('camp0'); - when(domainAxis.getDomain(80.0)).thenReturn('camp0'); - } - series.setAttr(domainAxisKey, domainAxis); - - // Mock the Measure axis results. - final measureAxis = MockNumericAxis(); - if (vertical) { - when(measureAxis.getLocation(0.0)).thenReturn(20.0 + 100.0); - when(measureAxis.getLocation(10.0)).thenReturn(20.0 + 100.0 - 10.0); - when(measureAxis.getLocation(20.0)).thenReturn(20.0 + 100.0 - 20.0); - } else { - when(measureAxis.getLocation(0.0)).thenReturn(70.0); - when(measureAxis.getLocation(10.0)).thenReturn(70.0 + 10.0); - when(measureAxis.getLocation(20.0)).thenReturn(70.0 + 20.0); - } - series.setAttr(measureAxisKey, measureAxis); - - return series; - } - - MutableSeries _makeDateTimeSeries( - {String id, String seriesCategory, bool vertical = true}) { - final data = [ - MyDateTimeRow(date0, 10), - MyDateTimeRow(date1, 10), - ]; - - final series = MutableSeries(Series( - id: id, - data: data, - domainFn: (dynamic row, _) => row.time, - measureFn: (dynamic row, _) => row.clickCount, - seriesCategory: seriesCategory, - )); - - series.measureOffsetFn = (_) => 0.0; - series.colorFn = (_) => Color.fromHex(code: '#000000'); - - // Mock the Domain axis results. - final domainAxis = MockDateTimeAxis(); - when(domainAxis.rangeBand).thenReturn(100.0); - final domainOffset = vertical ? 70.0 : 20.0; - when(domainAxis.getLocation(date0)).thenReturn(domainOffset + 10.0 + 50.0); - when(domainAxis.getLocation(date1)) - .thenReturn(domainOffset + 10.0 + 100.0 + 10.0 + 50.0); - when(domainAxis.getLocation(dateOutsideViewport)).thenReturn(-51.0); - - series.setAttr(domainAxisKey, domainAxis); - - // Mock the Measure axis results. - final measureAxis = MockNumericAxis(); - if (vertical) { - when(measureAxis.getLocation(0.0)).thenReturn(20.0 + 100.0); - when(measureAxis.getLocation(10.0)).thenReturn(20.0 + 100.0 - 10.0); - when(measureAxis.getLocation(20.0)).thenReturn(20.0 + 100.0 - 20.0); - } else { - when(measureAxis.getLocation(0.0)).thenReturn(70.0); - when(measureAxis.getLocation(10.0)).thenReturn(70.0 + 10.0); - when(measureAxis.getLocation(20.0)).thenReturn(70.0 + 20.0); - } - series.setAttr(measureAxisKey, measureAxis); - - return series; - } - - bool selectNearestByDomain; - - setUp(() { - selectNearestByDomain = true; - }); - - ///////////////////////////////////////// - // Additional edge test cases - ///////////////////////////////////////// - group('edge cases', () { - test('hit target on missing data in group should highlight group', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo')..data.clear(), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(31)); // 2 + 49 - 20 - expect(closest.measureDistance, equals(0)); - }); - - test('all series without data is skipped', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo')..data.clear(), - _makeSeries(id: 'bar')..data.clear(), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(0)); - }); - - test('single overlay series is skipped', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo')..overlaySeries = true, - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(31)); // 2 + 49 - 20 - expect(closest.measureDistance, equals(0)); - }); - - test('all overlay series is skipped', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo')..overlaySeries = true, - _makeSeries(id: 'bar')..overlaySeries = true, - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(0)); - }); - }); - - ///////////////////////////////////////// - // Vertical BarRenderer - ///////////////////////////////////////// - group('Vertical BarRenderer', () { - test('hit test works on bar', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [_makeSeries(id: 'foo')]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - }); - - test('hit test expands to grouped bars', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(31)); // 2 + 49 - 20 - expect(next.measureDistance, equals(0)); - }); - - test('hit test expands to stacked bars', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - // For vertical stacked bars, the first series is at the top of the stack. - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('foo')); - expect(next.datum, equals(seriesList[0].data[0])); - expect(next.domainDistance, equals(0)); - expect(next.measureDistance, equals(5.0)); - }); - - test('hit test expands to grouped stacked', () { - // Setup - final renderer = _makeBarRenderer( - vertical: true, groupType: BarGroupingType.groupedStacked); - final seriesList = [ - _makeSeries(id: 'foo0', seriesCategory: 'c0'), - _makeSeries(id: 'bar0', seriesCategory: 'c0'), - _makeSeries(id: 'foo1', seriesCategory: 'c1'), - _makeSeries(id: 'bar1', seriesCategory: 'c1'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(4)); - - // For vertical stacked bars, the first series is at the top of the stack. - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('bar0')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final other1 = details[1]; - expect(other1.domain, equals('camp0')); - expect(other1.series.id, equals('foo0')); - expect(other1.datum, equals(seriesList[0].data[0])); - expect(other1.domainDistance, equals(0)); - expect(other1.measureDistance, equals(5)); - - var other2 = details[2]; - expect(other2.domain, equals('camp0')); - expect(other2.series.id, equals('bar1')); - expect(other2.datum, equals(seriesList[3].data[0])); - expect(other2.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other2.measureDistance, equals(0)); - - var other3 = details[3]; - expect(other3.domain, equals('camp0')); - expect(other3.series.id, equals('foo1')); - expect(other3.datum, equals(seriesList[2].data[0])); - expect(other3.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other3.measureDistance, equals(5)); - }); - - test('hit test works above bar', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [_makeSeries(id: 'foo')]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0), selectNearestByDomain, null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(90)); - }); - - test('hit test works between bars in a group', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 50.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(1)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(1)); - expect(next.measureDistance, equals(0)); - }); - - test('no selection for bars outside of viewport', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo')..data.add(MyRow('outsideViewport', 20)) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - // Note: point is in the axis, over a bar outside of the viewport. - final details = renderer.getNearestDatumDetailPerSeries( - Point(65.0, 20.0 + 100.0 - 5.0), selectNearestByDomain, null); - - // Verify - expect(details.length, equals(0)); - }); - }); - - ///////////////////////////////////////// - // Horizontal BarRenderer - ///////////////////////////////////////// - group('Horizontal BarRenderer', () { - test('hit test works on bar', () { - // Setup - final renderer = - _makeBarRenderer(vertical: false, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 13.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - }); - - test('hit test expands to grouped bars', () { - // Setup - final renderer = - _makeBarRenderer(vertical: false, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false), - _makeSeries(id: 'bar', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(31)); // 2 + 49 - 20 - expect(next.measureDistance, equals(0)); - }); - - test('hit test expands to stacked bars', () { - // Setup - final renderer = - _makeBarRenderer(vertical: false, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false), - _makeSeries(id: 'bar', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(0)); - expect(next.measureDistance, equals(5.0)); - }); - - test('hit test expands to grouped stacked', () { - // Setup - final renderer = _makeBarRenderer( - vertical: false, groupType: BarGroupingType.groupedStacked); - final seriesList = [ - _makeSeries(id: 'foo0', seriesCategory: 'c0', vertical: false), - _makeSeries(id: 'bar0', seriesCategory: 'c0', vertical: false), - _makeSeries(id: 'foo1', seriesCategory: 'c1', vertical: false), - _makeSeries(id: 'bar1', seriesCategory: 'c1', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(4)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo0')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final other1 = details[1]; - expect(other1.domain, equals('camp0')); - expect(other1.series.id, equals('bar0')); - expect(other1.datum, equals(seriesList[1].data[0])); - expect(other1.domainDistance, equals(0)); - expect(other1.measureDistance, equals(5)); - - var other2 = details[2]; - expect(other2.domain, equals('camp0')); - expect(other2.series.id, equals('foo1')); - expect(other2.datum, equals(seriesList[2].data[0])); - expect(other2.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other2.measureDistance, equals(0)); - - var other3 = details[3]; - expect(other3.domain, equals('camp0')); - expect(other3.series.id, equals('bar1')); - expect(other3.datum, equals(seriesList[3].data[0])); - expect(other3.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other3.measureDistance, equals(5)); - }); - - test('hit test works above bar', () { - // Setup - final renderer = - _makeBarRenderer(vertical: false, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 100.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(90)); - }); - - test('hit test works between bars in a group', () { - // Setup - final renderer = - _makeBarRenderer(vertical: false, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false), - _makeSeries(id: 'bar', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 50.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(1)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(1)); - expect(next.measureDistance, equals(0)); - }); - }); - - ///////////////////////////////////////// - // Vertical BarTargetRenderer - ///////////////////////////////////////// - group('Vertical BarTargetRenderer', () { - test('hit test works above target', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [_makeSeries(id: 'foo')]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0), selectNearestByDomain, null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(90)); - }); - - test('hit test expands to grouped bar targets', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(31)); // 2 + 49 - 20 - expect(next.measureDistance, equals(5)); - }); - - test('hit test expands to stacked bar targets', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - // For vertical stacked bars, the first series is at the top of the stack. - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('foo')); - expect(next.datum, equals(seriesList[0].data[0])); - expect(next.domainDistance, equals(0)); - expect(next.measureDistance, equals(15.0)); - }); - - test('hit test expands to grouped stacked', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.groupedStacked); - final seriesList = [ - _makeSeries(id: 'foo0', seriesCategory: 'c0'), - _makeSeries(id: 'bar0', seriesCategory: 'c0'), - _makeSeries(id: 'foo1', seriesCategory: 'c1'), - _makeSeries(id: 'bar1', seriesCategory: 'c1'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(4)); - - // For vertical stacked bars, the first series is at the top of the stack. - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('bar0')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final other1 = details[1]; - expect(other1.domain, equals('camp0')); - expect(other1.series.id, equals('foo0')); - expect(other1.datum, equals(seriesList[0].data[0])); - expect(other1.domainDistance, equals(0)); - expect(other1.measureDistance, equals(15)); - - var other2 = details[2]; - expect(other2.domain, equals('camp0')); - expect(other2.series.id, equals('bar1')); - expect(other2.datum, equals(seriesList[3].data[0])); - expect(other2.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other2.measureDistance, equals(5)); - - var other3 = details[3]; - expect(other3.domain, equals('camp0')); - expect(other3.series.id, equals('foo1')); - expect(other3.datum, equals(seriesList[2].data[0])); - expect(other3.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other3.measureDistance, equals(15)); - }); - - test('hit test works between targets in a group', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 50.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(1)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(1)); - expect(next.measureDistance, equals(5)); - }); - - test('no selection for targets outside of viewport', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo')..data.add(MyRow('outsideViewport', 20)) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - // Note: point is in the axis, over a bar outside of the viewport. - final details = renderer.getNearestDatumDetailPerSeries( - Point(65.0, 20.0 + 100.0 - 5.0), selectNearestByDomain, null); - - // Verify - expect(details.length, equals(0)); - }); - }); - - ///////////////////////////////////////// - // Horizontal BarTargetRenderer - ///////////////////////////////////////// - group('Horizontal BarTargetRenderer', () { - test('hit test works above target', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: false, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 100.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(90)); - }); - - test('hit test expands to grouped bar targets', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: false, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false), - _makeSeries(id: 'bar', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(31)); // 2 + 49 - 20 - expect(next.measureDistance, equals(5)); - }); - - test('hit test expands to stacked bar targets', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: false, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false), - _makeSeries(id: 'bar', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(0)); - expect(next.measureDistance, equals(15)); - }); - - test('hit test expands to grouped stacked', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: false, groupType: BarGroupingType.groupedStacked); - final seriesList = [ - _makeSeries(id: 'foo0', seriesCategory: 'c0', vertical: false), - _makeSeries(id: 'bar0', seriesCategory: 'c0', vertical: false), - _makeSeries(id: 'foo1', seriesCategory: 'c1', vertical: false), - _makeSeries(id: 'bar1', seriesCategory: 'c1', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 20.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(4)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo0')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final other1 = details[1]; - expect(other1.domain, equals('camp0')); - expect(other1.series.id, equals('bar0')); - expect(other1.datum, equals(seriesList[1].data[0])); - expect(other1.domainDistance, equals(0)); - expect(other1.measureDistance, equals(15)); - - var other2 = details[2]; - expect(other2.domain, equals('camp0')); - expect(other2.series.id, equals('foo1')); - expect(other2.datum, equals(seriesList[2].data[0])); - expect(other2.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other2.measureDistance, equals(5)); - - var other3 = details[3]; - expect(other3.domain, equals('camp0')); - expect(other3.series.id, equals('bar1')); - expect(other3.datum, equals(seriesList[3].data[0])); - expect(other3.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other3.measureDistance, equals(15)); - }); - - test('hit test works between bars in a group', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: false, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeSeries(id: 'foo', vertical: false), - _makeSeries(id: 'bar', vertical: false), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 5.0, 20.0 + 10.0 + 50.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals('camp0')); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(1)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals('camp0')); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(1)); - expect(next.measureDistance, equals(5)); - }); - }); - - ///////////////////////////////////////// - // Bar renderer with datetime axis - ///////////////////////////////////////// - group('with date time axis and vertical bar', () { - test('hit test works on bar', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [_makeDateTimeSeries(id: 'foo')]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals(date0)); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - }); - - test('hit test expands to grouped bars', () { - // Setup - final renderer = - _makeBarRenderer(vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeDateTimeSeries(id: 'foo'), - _makeDateTimeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals(date0)); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(0)); - - final next = details[1]; - expect(next.domain, equals(date0)); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(31)); // 2 + 49 - 20 - expect(next.measureDistance, equals(0)); - }); - - test('hit test expands to stacked bar targets', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.stacked); - final seriesList = [ - _makeDateTimeSeries(id: 'foo'), - _makeDateTimeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 13.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - // For vertical stacked bars, the first series is at the top of the stack. - final closest = details[0]; - expect(closest.domain, equals(date0)); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals(date0)); - expect(next.series.id, equals('foo')); - expect(next.datum, equals(seriesList[0].data[0])); - expect(next.domainDistance, equals(0)); - expect(next.measureDistance, equals(15.0)); - }); - - test('hit test expands to grouped stacked', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.groupedStacked); - final seriesList = [ - _makeDateTimeSeries(id: 'foo0', seriesCategory: 'c0'), - _makeDateTimeSeries(id: 'bar0', seriesCategory: 'c0'), - _makeDateTimeSeries(id: 'foo1', seriesCategory: 'c1'), - _makeDateTimeSeries(id: 'bar1', seriesCategory: 'c1'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 20.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(4)); - - // For vertical stacked bars, the first series is at the top of the stack. - final closest = details[0]; - expect(closest.domain, equals(date0)); - expect(closest.series.id, equals('bar0')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(0)); - expect(closest.measureDistance, equals(5)); - - final other1 = details[1]; - expect(other1.domain, equals(date0)); - expect(other1.series.id, equals('foo0')); - expect(other1.datum, equals(seriesList[0].data[0])); - expect(other1.domainDistance, equals(0)); - expect(other1.measureDistance, equals(15)); - - var other2 = details[2]; - expect(other2.domain, equals(date0)); - expect(other2.series.id, equals('bar1')); - expect(other2.datum, equals(seriesList[3].data[0])); - expect(other2.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other2.measureDistance, equals(5)); - - var other3 = details[3]; - expect(other3.domain, equals(date0)); - expect(other3.series.id, equals('foo1')); - expect(other3.datum, equals(seriesList[2].data[0])); - expect(other3.domainDistance, equals(31)); // 2 + 49 - 20 - expect(other3.measureDistance, equals(15)); - }); - - test('hit test works between targets in a group', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeDateTimeSeries(id: 'foo'), - _makeDateTimeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0 + 50.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals(date0)); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(1)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals(date0)); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(1)); - expect(next.measureDistance, equals(5)); - }); - - test('no selection for targets outside of viewport', () { - // Setup - final renderer = _makeBarTargetRenderer( - vertical: true, groupType: BarGroupingType.grouped); - final seriesList = [ - _makeDateTimeSeries(id: 'foo') - ..data.add(MyDateTimeRow(dateOutsideViewport, 20)) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - // Note: point is in the axis, over a bar outside of the viewport. - final details = renderer.getNearestDatumDetailPerSeries( - Point(65.0, 20.0 + 100.0 - 5.0), selectNearestByDomain, null); - - // Verify - expect(details.length, equals(0)); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/axis_test.dart b/web/charts/common/test/chart/cartesian/axis/axis_test.dart deleted file mode 100644 index 7f1496030..000000000 --- a/web/charts/common/test/chart/cartesian/axis/axis_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart'; -import 'package:charts_common/src/chart/cartesian/axis/scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/spec/tick_spec.dart'; -import 'package:charts_common/src/chart/cartesian/axis/static_tick_provider.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; -import 'package:charts_common/src/common/text_element.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockTickDrawStrategy extends Mock implements TickDrawStrategy {} - -class MockGraphicsFactory extends Mock implements GraphicsFactory { - TextElement createTextElement(String _) { - return MockTextElement(); - } -} - -class MockTextElement extends Mock implements TextElement {} - -StaticTickProvider _createProvider(List values) => - StaticTickProvider(values.map((v) => TickSpec(v)).toList()); - -void main() { - test('changing first tick only', () { - var axis = NumericAxis( - tickProvider: _createProvider([1, 10]), - ); - - var tester = AxisTester(axis); - axis.tickDrawStrategy = MockTickDrawStrategy(); - axis.graphicsFactory = MockGraphicsFactory(); - tester.scale.range = ScaleOutputExtent(0, 300); - - axis.updateTicks(); - - axis.tickProvider = _createProvider([5, 10]); - axis.updateTicks(); - - // The old value should still be there as it gets animated out, but the - // values should be sorted by their position. - expect(tester.axisValues, equals([1, 5, 10])); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/axis_tick_test.dart b/web/charts/common/test/chart/cartesian/axis/axis_tick_test.dart deleted file mode 100644 index dd0e348a3..000000000 --- a/web/charts/common/test/chart/cartesian/axis/axis_tick_test.dart +++ /dev/null @@ -1,185 +0,0 @@ -// 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 'package:charts_common/src/common/text_element.dart'; -import 'package:charts_common/src/common/text_measurement.dart'; -import 'package:charts_common/src/common/text_style.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis_tick.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick.dart'; -import 'package:test/test.dart'; - -/// Fake [TextElement] for testing. -class FakeTextElement implements TextElement { - final String text; - double opacity; - - TextMeasurement measurement; - TextStyle textStyle; - int maxWidth; - MaxWidthStrategy maxWidthStrategy; - TextDirection textDirection; - - FakeTextElement(this.text); -} - -/// Helper to create a tick for testing. -Tick _createTestTick(String value, double locationPx) { - return Tick( - value: value, - textElement: FakeTextElement(value), - locationPx: locationPx); -} - -void _verify(Tick tick, {double location, double opacity}) { - expect(tick.locationPx, equals(location)); - expect((tick.textElement as FakeTextElement).opacity, equals(opacity)); -} - -void main() { - // Tick first render. - test('tick created for the first time', () { - final tick = AxisTicks(_createTestTick('a', 100.0)); - - // Animate in the tick, there was no previous position to animated in from - // so the tick appears in the target immediately. - tick.setCurrentTick(0.0); - _verify(tick, location: 100.0, opacity: 1.0); - - tick.setCurrentTick(0.25); - _verify(tick, location: 100.0, opacity: 1.0); - - tick.setCurrentTick(0.75); - _verify(tick, location: 100.0, opacity: 1.0); - - tick.setCurrentTick(1.0); - _verify(tick, location: 100.0, opacity: 1.0); - }); - - // Tick that is animated in. - test('tick created with a previous location', () { - final tick = AxisTicks(_createTestTick('a', 200.0))..animateInFrom(100.0); - - tick.setCurrentTick(0.0); - _verify(tick, location: 100.0, opacity: 0.0); - - tick.setCurrentTick(0.25); - _verify(tick, location: 125.0, opacity: 0.25); - - tick.setCurrentTick(0.75); - _verify(tick, location: 175.0, opacity: 0.75); - - tick.setCurrentTick(1.0); - _verify(tick, location: 200.0, opacity: 1.0); - }); - - // Tick that is being animated out. - test('tick is animated in and then out', () { - final tick = AxisTicks(_createTestTick('a', 100.0)); - - // Animate in the tick, there was no previous position to animated in from - // so the tick appears in the target immediately. - tick.setCurrentTick(0.25); - _verify(tick, location: 100.0, opacity: 1.0); - - // Change to animate the tick out. - tick.animateOut(0.0); - - expect(tick.markedForRemoval, isTrue); - - tick.setCurrentTick(0.0); - _verify(tick, location: 100.0, opacity: 1.0); - - tick.setCurrentTick(0.25); - _verify(tick, location: 75.0, opacity: 0.75); - - tick.setCurrentTick(0.75); - _verify(tick, location: 25.0, opacity: 0.25); - - tick.setCurrentTick(1.0); - _verify(tick, location: 0.0, opacity: 0.0); - }); - - test('tick target change after reaching target', () { - final tick = AxisTicks(_createTestTick('a', 100.0)); - - // Animate in the tick. - tick.setCurrentTick(1.0); - _verify(tick, location: 100.0, opacity: 1.0); - - tick.setNewTarget(200.0); - - expect(tick.markedForRemoval, isFalse); - - tick.setCurrentTick(0.0); - _verify(tick, location: 100.0, opacity: 1.0); - - tick.setCurrentTick(0.25); - _verify(tick, location: 125.0, opacity: 1.0); - - tick.setCurrentTick(0.75); - _verify(tick, location: 175.0, opacity: 1.0); - - tick.setCurrentTick(1.0); - _verify(tick, location: 200.0, opacity: 1.0); - }); - - test('tick target change before reaching initial target', () { - final tick = AxisTicks(_createTestTick('a', 400.0))..animateInFrom(0.0); - - // Animate in the tick. - tick.setCurrentTick(0.25); - _verify(tick, location: 100.0, opacity: 0.25); - - tick.setNewTarget(200.0); - - expect(tick.markedForRemoval, isFalse); - - tick.setCurrentTick(0.0); - _verify(tick, location: 100.0, opacity: 0.25); - - tick.setCurrentTick(0.25); - _verify(tick, location: 125.0, opacity: 0.4375); - - tick.setCurrentTick(0.75); - _verify(tick, location: 175.0, opacity: 0.8125); - - tick.setCurrentTick(1.0); - _verify(tick, location: 200.0, opacity: 1.0); - }); - - test('tick target animate out before reaching initial target', () { - final tick = AxisTicks(_createTestTick('a', 400.0))..animateInFrom(0.0); - - // Animate in the tick. - tick.setCurrentTick(0.25); - _verify(tick, location: 100.0, opacity: 0.25); - - tick.animateOut(200.0); - - expect(tick.markedForRemoval, isTrue); - - tick.setCurrentTick(0.0); - _verify(tick, location: 100.0, opacity: 0.25); - - tick.setCurrentTick(0.25); - _verify(tick, location: 125.0, opacity: 0.1875); - - tick.setCurrentTick(0.75); - _verify(tick, location: 175.0, opacity: 0.0625); - - tick.setCurrentTick(1.0); - _verify(tick, location: 200.0, opacity: 0.0); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/bucketing_numeric_tick_provider_test.dart b/web/charts/common/test/chart/cartesian/axis/bucketing_numeric_tick_provider_test.dart deleted file mode 100644 index cdd678c58..000000000 --- a/web/charts/common/test/chart/cartesian/axis/bucketing_numeric_tick_provider_test.dart +++ /dev/null @@ -1,179 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart'; -import 'package:charts_common/src/chart/cartesian/axis/collision_report.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart'; -import 'package:charts_common/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/unitconverter/unit_converter.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; -import 'package:charts_common/src/common/line_style.dart'; -import 'package:charts_common/src/common/text_style.dart'; -import 'package:charts_common/src/common/text_element.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockNumericScale extends Mock implements NumericScale {} - -/// A fake draw strategy that reports collision and alternate ticks -/// -/// Reports collision when the tick count is greater than or equal to -/// [collidesAfterTickCount]. -/// -/// Reports alternate rendering after tick count is greater than or equal to -/// [alternateRenderingAfterTickCount]. -class FakeDrawStrategy extends BaseTickDrawStrategy { - final int collidesAfterTickCount; - final int alternateRenderingAfterTickCount; - - FakeDrawStrategy( - this.collidesAfterTickCount, this.alternateRenderingAfterTickCount) - : super(null, FakeGraphicsFactory()); - - @override - CollisionReport collides(List> ticks, _) { - final ticksCollide = ticks.length >= collidesAfterTickCount; - final alternateTicksUsed = ticks.length >= alternateRenderingAfterTickCount; - - return CollisionReport( - ticksCollide: ticksCollide, - ticks: ticks, - alternateTicksUsed: alternateTicksUsed); - } - - @override - void draw(ChartCanvas canvas, Tick tick, - {AxisOrientation orientation, - Rectangle axisBounds, - Rectangle drawAreaBounds, - bool isFirst, - bool isLast}) {} -} - -/// A fake [GraphicsFactory] that returns [MockTextStyle] and [MockTextElement]. -class FakeGraphicsFactory extends GraphicsFactory { - @override - TextStyle createTextPaint() => MockTextStyle(); - - @override - TextElement createTextElement(String text) => MockTextElement(text); - - @override - LineStyle createLinePaint() => MockLinePaint(); -} - -class MockTextStyle extends Mock implements TextStyle {} - -class MockTextElement extends Mock implements TextElement { - String text; - - MockTextElement(this.text); -} - -class MockLinePaint extends Mock implements LineStyle {} - -class MockChartContext extends Mock implements ChartContext {} - -/// A celsius to fahrenheit converter for testing axis with unit converter. -class CelsiusToFahrenheitConverter implements UnitConverter { - const CelsiusToFahrenheitConverter(); - - @override - num convert(num value) => (value * 1.8) + 32.0; - - @override - num invert(num value) => (value - 32.0) / 1.8; -} - -void main() { - FakeGraphicsFactory graphicsFactory; - MockNumericScale scale; - BucketingNumericTickProvider tickProvider; - TickFormatter formatter; - ChartContext context; - - setUp(() { - graphicsFactory = FakeGraphicsFactory(); - scale = MockNumericScale(); - tickProvider = BucketingNumericTickProvider(); - formatter = NumericTickFormatter(); - context = MockChartContext(); - }); - - group('threshold', () { - test('tick generated correctly with no ticks between it and zero', () { - tickProvider - ..dataIsInWholeNumbers = false - ..threshold = 0.1 - ..showBucket = true - ..setFixedTickCount(21) - ..allowedSteps = [1.0, 2.5, 5.0]; - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(0.1, 0.7)); - when(scale.rangeWidth).thenReturn(1000); - when(scale[0.1]).thenReturn(90.0); - when(scale[0]).thenReturn(100.0); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - // Verify. - // We expect to have 20 ticks, because the expected tick at 0.05 should be - // removed from the list. - expect(ticks, hasLength(20)); - - // Verify that we still have a 0 tick with an empty label. - expect(ticks[0].labelOffsetPx, isNull); - expect(ticks[0].locationPx, equals(100.0)); - expect(ticks[0].value, equals(0.0)); - expect(ticks[0].textElement.text, equals('')); - - // Verify that we have a threshold tick. - expect(ticks[1].labelOffsetPx, equals(5.0)); - expect(ticks[1].locationPx, equals(90.0)); - expect(ticks[1].value, equals(0.10)); - expect(ticks[1].textElement.text, equals('< 0.1')); - - // Verify that the rest of the ticks are all above the threshold in value - // and have normal labels. - var aboveThresholdTicks = ticks.sublist(2); - aboveThresholdTicks.retainWhere((tick) => tick.value > 0.1); - expect(aboveThresholdTicks, hasLength(18)); - - aboveThresholdTicks = ticks.sublist(2); - aboveThresholdTicks.retainWhere((tick) => - tick.textElement.text != '' && !tick.textElement.text.contains('<')); - expect(aboveThresholdTicks, hasLength(18)); - - aboveThresholdTicks = ticks.sublist(2); - aboveThresholdTicks.retainWhere((tick) => tick.labelOffsetPx == null); - expect(aboveThresholdTicks, hasLength(18)); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/draw_strategy/tick_draw_strategy_test.dart b/web/charts/common/test/chart/cartesian/axis/draw_strategy/tick_draw_strategy_test.dart deleted file mode 100644 index 97816eab1..000000000 --- a/web/charts/common/test/chart/cartesian/axis/draw_strategy/tick_draw_strategy_test.dart +++ /dev/null @@ -1,408 +0,0 @@ -// 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'; -import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/axis/spec/axis_spec.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; -import 'package:charts_common/src/common/line_style.dart'; -import 'package:charts_common/src/common/text_element.dart'; -import 'package:charts_common/src/common/text_measurement.dart'; -import 'package:charts_common/src/common/text_style.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockContext extends Mock implements ChartContext {} - -/// Implementation of [BaseTickDrawStrategy] that does nothing on draw. -class BaseTickDrawStrategyImpl extends BaseTickDrawStrategy { - BaseTickDrawStrategyImpl( - ChartContext chartContext, GraphicsFactory graphicsFactory, - {TextStyleSpec labelStyleSpec, - LineStyleSpec axisLineStyleSpec, - TickLabelAnchor labelAnchor, - TickLabelJustification labelJustification, - int labelOffsetFromAxisPx, - int labelOffsetFromTickPx, - int minimumPaddingBetweenLabelsPx}) - : super(chartContext, graphicsFactory, - labelStyleSpec: labelStyleSpec, - axisLineStyleSpec: axisLineStyleSpec, - labelAnchor: labelAnchor, - labelJustification: labelJustification, - labelOffsetFromAxisPx: labelOffsetFromAxisPx, - labelOffsetFromTickPx: labelOffsetFromTickPx, - minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx); - - void draw(ChartCanvas canvas, Tick tick, - {AxisOrientation orientation, - Rectangle axisBounds, - Rectangle drawAreaBounds, - bool isFirst, - bool isLast}) {} -} - -/// Fake [TextElement] for testing. -/// -/// [baseline] returns the same value as the [verticalSliceWidth] specified. -class FakeTextElement implements TextElement { - final String text; - final TextMeasurement measurement; - TextStyle textStyle; - int maxWidth; - MaxWidthStrategy maxWidthStrategy; - TextDirection textDirection; - double opacity; - - FakeTextElement( - this.text, - this.textDirection, - double horizontalSliceWidth, - double verticalSliceWidth, - ) : measurement = TextMeasurement( - horizontalSliceWidth: horizontalSliceWidth, - verticalSliceWidth: verticalSliceWidth); -} - -class MockGraphicsFactory extends Mock implements GraphicsFactory {} - -class MockLineStyle extends Mock implements LineStyle {} - -class MockTextStyle extends Mock implements TextStyle {} - -/// Helper function to create [Tick] for testing. -Tick createTick(String value, double locationPx, - {double horizontalWidth, - double verticalWidth, - TextDirection textDirection}) { - return Tick( - value: value, - locationPx: locationPx, - textElement: FakeTextElement( - value, textDirection, horizontalWidth, verticalWidth)); -} - -void main() { - GraphicsFactory graphicsFactory; - ChartContext chartContext; - - setUpAll(() { - graphicsFactory = MockGraphicsFactory(); - when(graphicsFactory.createLinePaint()).thenReturn(MockLineStyle()); - when(graphicsFactory.createTextPaint()).thenReturn(MockTextStyle()); - - chartContext = MockContext(); - when(chartContext.chartContainerIsRtl).thenReturn(false); - when(chartContext.isRtl).thenReturn(false); - }); - - group('collision detection - vertically drawn axis', () { - test('ticks do not collide', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2) - createTick('B', 20.0, verticalWidth: 8.0), // 20.0 - 30.0 (28.0 + 2) - createTick('C', 30.0, verticalWidth: 8.0), // 30.0 - 40.0 (38.0 + 2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isFalse); - }); - - test('ticks collide because it does not have minimum padding', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2) - createTick('B', 20.0, verticalWidth: 9.0), // 20.0 - 31.0 (28.0 + 3) - createTick('C', 30.0, verticalWidth: 8.0), // 30.0 - 40.0 (38.0 + 2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isTrue); - }); - - test('first tick causes collision', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 11.0), // 10.0 - 21.0 - createTick('B', 20.0, verticalWidth: 10.0), // 20.0 - 30.0 - createTick('C', 30.0, verticalWidth: 10.0), // 30.0 - 40.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isTrue); - }); - - test('last tick causes collision', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 10.0), // 10.0 - 20.0 - createTick('B', 20.0, verticalWidth: 10.0), // 20.0 - 30.0 - createTick('C', 29.0, verticalWidth: 10.0), // 29.0 - 40.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isTrue); - }); - - test('ticks do not collide for inside tick label anchor', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2) - createTick('B', 25.0, verticalWidth: 8.0), // 20.0 - 30.0 (25 + 2 + 1) - createTick('C', 40.0, verticalWidth: 8.0), // 30.0 - 40.0 (40-8-2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isFalse); - }); - - test('ticks collide for inside anchor - first tick too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 9.0), // 10.0 - 21.0 (19.0 + 2) - createTick('B', 25.0, verticalWidth: 8.0), // 20.0 - 30.0 (25 + 2 + 1) - createTick('C', 40.0, verticalWidth: 8.0), // 30.0 - 40.0 (40-8-2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isTrue); - }); - - test('ticks collide for inside anchor - center tick too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2) - createTick('B', 25.0, verticalWidth: 9.0), // 19.5 - 30.5 (25 + 2.5 + 1) - createTick('C', 40.0, verticalWidth: 8.0), // 30.0 - 40.0 (40-8-2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isTrue); - }); - - test('ticks collide for inside anchor - last tick too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, verticalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2) - createTick('B', 25.0, verticalWidth: 8.0), // 20.0 - 30.0 (25 + 2 + 1) - createTick('C', 40.0, verticalWidth: 9.0), // 29.0 - 40.0 (40-9-2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.left); - - expect(report.ticksCollide, isTrue); - }); - }); - - group('collision detection - horizontally drawn axis', () { - test('ticks do not collide for TickLabelAnchor.before', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 2, - labelAnchor: TickLabelAnchor.before); - - final ticks = [ - createTick('A', 10.0, horizontalWidth: 8.0), // 10.0 - 20.0 (18.0 + 2) - createTick('B', 20.0, horizontalWidth: 8.0), // 20.0 - 30.0 (28.0 + 2) - createTick('C', 30.0, horizontalWidth: 8.0), // 30.0 - 40.0 (38.0 + 2) - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isFalse); - }); - - test('ticks do not collide for TickLabelAnchor.inside', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, - horizontalWidth: 10.0, - textDirection: TextDirection.ltr), // 10.0 - 20.0 - createTick('B', 25.0, - horizontalWidth: 10.0, - textDirection: TextDirection.center), // 20.0 - 30.0 - createTick('C', 40.0, - horizontalWidth: 10.0, - textDirection: TextDirection.rtl), // 30.0 - 40.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isFalse); - }); - - test('ticks collide - first tick too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, horizontalWidth: 11.0), // 10.0 - 21.0 - createTick('B', 25.0, horizontalWidth: 10.0), // 20.0 - 30.0 - createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isTrue); - }); - - test('ticks collide - middle tick too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0 - createTick('B', 25.0, horizontalWidth: 11.0), // 19.5 - 30.5 - createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isTrue); - }); - - test('ticks collide - last tick too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0 - createTick('B', 25.0, horizontalWidth: 10.0), // 20.0 - 30.0 - createTick('C', 40.0, horizontalWidth: 11.0), // 29.0 - 40.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isTrue); - }); - }); - - group('collision detection - unsorted ticks', () { - test('ticks do not collide', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0 - createTick('B', 25.0, horizontalWidth: 10.0), // 20.0 - 30.0 - createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isFalse); - }); - - test('ticks collide - tick B is too large', () { - final drawStrategy = BaseTickDrawStrategyImpl( - chartContext, graphicsFactory, - minimumPaddingBetweenLabelsPx: 0, - labelAnchor: TickLabelAnchor.inside); - - final ticks = [ - createTick('A', 10.0, horizontalWidth: 10.0), // 10.0 - 20.0 - createTick('C', 40.0, horizontalWidth: 10.0), // 30.0 - 40.0 - createTick('B', 25.0, horizontalWidth: 11.0), // 19.5 - 30.5 - ]; - - final report = drawStrategy.collides(ticks, AxisOrientation.bottom); - - expect(report.ticksCollide, isTrue); - }); - }); - - group('collision detection - corner cases', () { - test('null ticks do not collide', () { - final drawStrategy = - BaseTickDrawStrategyImpl(chartContext, graphicsFactory); - - final report = drawStrategy.collides(null, AxisOrientation.left); - - expect(report.ticksCollide, isFalse); - }); - - test('empty tick list do not collide', () { - final drawStrategy = - BaseTickDrawStrategyImpl(chartContext, graphicsFactory); - - final report = drawStrategy.collides([], AxisOrientation.left); - - expect(report.ticksCollide, isFalse); - }); - - test('single tick does not collide', () { - final drawStrategy = - BaseTickDrawStrategyImpl(chartContext, graphicsFactory); - - final report = drawStrategy.collides( - [createTick('A', 10.0, horizontalWidth: 10.0)], - AxisOrientation.bottom); - - expect(report.ticksCollide, isFalse); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/end_points_tick_provider_test.dart b/web/charts/common/test/chart/cartesian/axis/end_points_tick_provider_test.dart deleted file mode 100644 index eb3449236..000000000 --- a/web/charts/common/test/chart/cartesian/axis/end_points_tick_provider_test.dart +++ /dev/null @@ -1,237 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; -import 'package:charts_common/src/common/line_style.dart'; -import 'package:charts_common/src/common/text_style.dart'; -import 'package:charts_common/src/common/text_element.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/cartesian/axis/collision_report.dart'; -import 'package:charts_common/src/chart/cartesian/axis/end_points_tick_provider.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/simple_ordinal_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/date_time_extents.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/date_time_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/date_time_tick_formatter.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'time/simple_date_time_factory.dart' show SimpleDateTimeFactory; - -class MockDateTimeScale extends Mock implements DateTimeScale {} - -class MockNumericScale extends Mock implements NumericScale {} - -class MockOrdinalScale extends Mock implements SimpleOrdinalScale {} - -/// A fake draw strategy that reports collision and alternate ticks -/// -/// Reports collision when the tick count is greater than or equal to -/// [collidesAfterTickCount]. -/// -/// Reports alternate rendering after tick count is greater than or equal to -/// [alternateRenderingAfterTickCount]. -class FakeDrawStrategy extends BaseTickDrawStrategy { - final int collidesAfterTickCount; - final int alternateRenderingAfterTickCount; - - FakeDrawStrategy( - this.collidesAfterTickCount, this.alternateRenderingAfterTickCount) - : super(null, FakeGraphicsFactory()); - - @override - CollisionReport collides(List> ticks, _) { - final ticksCollide = ticks.length >= collidesAfterTickCount; - final alternateTicksUsed = ticks.length >= alternateRenderingAfterTickCount; - - return CollisionReport( - ticksCollide: ticksCollide, - ticks: ticks, - alternateTicksUsed: alternateTicksUsed); - } - - @override - void draw(ChartCanvas canvas, Tick tick, - {AxisOrientation orientation, - Rectangle axisBounds, - Rectangle drawAreaBounds, - bool isFirst, - bool isLast}) {} -} - -/// A fake [GraphicsFactory] that returns [MockTextStyle] and [MockTextElement]. -class FakeGraphicsFactory extends GraphicsFactory { - @override - TextStyle createTextPaint() => MockTextStyle(); - - @override - TextElement createTextElement(String text) => MockTextElement(); - - @override - LineStyle createLinePaint() => MockLinePaint(); -} - -class MockTextStyle extends Mock implements TextStyle {} - -class MockTextElement extends Mock implements TextElement {} - -class MockLinePaint extends Mock implements LineStyle {} - -class MockChartContext extends Mock implements ChartContext {} - -void main() { - const dateTimeFactory = SimpleDateTimeFactory(); - FakeGraphicsFactory graphicsFactory; - EndPointsTickProvider tickProvider; - ChartContext context; - - setUp(() { - graphicsFactory = FakeGraphicsFactory(); - context = MockChartContext(); - }); - - test('dateTime_choosesEndPointTicks', () { - final formatter = DateTimeTickFormatter(dateTimeFactory); - final scale = MockDateTimeScale(); - tickProvider = EndPointsTickProvider(); - - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(DateTimeExtents( - start: DateTime(2018, 8, 1), end: DateTime(2018, 8, 11))); - when(scale.rangeWidth).thenReturn(1000); - when(scale.domainStepSize).thenReturn(1000.0); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(2)); - expect(ticks[0].value, equals(DateTime(2018, 8, 1))); - expect(ticks[1].value, equals(DateTime(2018, 8, 11))); - }); - - test('numeric_choosesEndPointTicks', () { - final formatter = NumericTickFormatter(); - final scale = MockNumericScale(); - tickProvider = EndPointsTickProvider(); - - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(10.0, 70.0)); - when(scale.rangeWidth).thenReturn(1000); - when(scale.domainStepSize).thenReturn(1000.0); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(2)); - expect(ticks[0].value, equals(10)); - expect(ticks[1].value, equals(70)); - }); - - test('ordinal_choosesEndPointTicks', () { - final formatter = OrdinalTickFormatter(); - final scale = SimpleOrdinalScale(); - scale.addDomain('A'); - scale.addDomain('B'); - scale.addDomain('C'); - scale.addDomain('D'); - tickProvider = EndPointsTickProvider(); - - final drawStrategy = FakeDrawStrategy(10, 10); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(2)); - expect(ticks[0].value, equals('A')); - expect(ticks[1].value, equals('D')); - }); - - test('dateTime_emptySeriesChoosesNoTicks', () { - final formatter = DateTimeTickFormatter(dateTimeFactory); - final scale = MockDateTimeScale(); - tickProvider = EndPointsTickProvider(); - - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(DateTimeExtents( - start: DateTime(2018, 8, 1), end: DateTime(2018, 8, 11))); - when(scale.rangeWidth).thenReturn(1000); - - // An un-configured axis has no domain step size, and its scale defaults to - // infinity. - when(scale.domainStepSize).thenReturn(double.infinity); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(0)); - }); - - test('numeric_emptySeriesChoosesNoTicks', () { - final formatter = NumericTickFormatter(); - final scale = MockNumericScale(); - tickProvider = EndPointsTickProvider(); - - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(10.0, 70.0)); - when(scale.rangeWidth).thenReturn(1000); - - // An un-configured axis has no domain step size, and its scale defaults to - // infinity. - when(scale.domainStepSize).thenReturn(double.infinity); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(0)); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/linear/linear_scale_test.dart b/web/charts/common/test/chart/cartesian/axis/linear/linear_scale_test.dart deleted file mode 100644 index 4dbd113b7..000000000 --- a/web/charts/common/test/chart/cartesian/axis/linear/linear_scale_test.dart +++ /dev/null @@ -1,307 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart' - show NumericExtents; -import 'package:charts_common/src/chart/cartesian/axis/linear/linear_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/scale.dart' - show RangeBandConfig, ScaleOutputExtent, StepSizeConfig; - -import 'package:test/test.dart'; - -const EPSILON = 0.001; - -void main() { - group('Stacking bars', () { - test('basic apply survives copy and reset', () { - LinearScale scale = LinearScale(); - scale.addDomain(100.0); - scale.addDomain(130.0); - scale.addDomain(200.0); - scale.addDomain(170.0); - scale.range = ScaleOutputExtent(2000, 1000); - - expect(scale.range.start, equals(2000)); - expect(scale.range.end, equals(1000)); - expect(scale.range.diff, equals(-1000)); - - expect(scale.dataExtent.min, equals(100.0)); - expect(scale.dataExtent.max, equals(200.0)); - - expect(scale[100.0], closeTo(2000, EPSILON)); - expect(scale[200.0], closeTo(1000, EPSILON)); - expect(scale[166.0], closeTo(1340, EPSILON)); - expect(scale[0.0], closeTo(3000, EPSILON)); - expect(scale[300.0], closeTo(0, EPSILON)); - - // test copy - LinearScale other = scale.copy(); - expect(other[166.0], closeTo(1340, EPSILON)); - expect(other.range.start, equals(2000)); - expect(other.range.end, equals(1000)); - - // test reset - other.resetDomain(); - other.resetViewportSettings(); - other.addDomain(10.0); - other.addDomain(20.0); - expect(other.dataExtent.min, equals(10.0)); - expect(other.dataExtent.max, equals(20.0)); - expect(other.viewportDomain.min, equals(10.0)); - expect(other.viewportDomain.max, equals(20.0)); - - expect(other[15.0], closeTo(1500, EPSILON)); - // original scale shouldn't have been touched. - expect(scale[166.0], closeTo(1340, EPSILON)); - - // should always return true. - expect(scale.canTranslate(3.14), isTrue); - }); - - test('viewport assigned domain extent applies to scale', () { - LinearScale scale = LinearScale()..keepViewportWithinData = false; - scale.addDomain(50.0); - scale.addDomain(70.0); - scale.viewportDomain = NumericExtents(100.0, 200.0); - scale.range = ScaleOutputExtent(0, 200); - - expect(scale[200.0], closeTo(200, EPSILON)); - expect(scale[100.0], closeTo(0, EPSILON)); - expect(scale[50.0], closeTo(-100, EPSILON)); - expect(scale[150.0], closeTo(100, EPSILON)); - - scale.resetDomain(); - scale.resetViewportSettings(); - scale.addDomain(50.0); - scale.addDomain(100.0); - scale.viewportDomain = NumericExtents(0.0, 100.0); - scale.range = ScaleOutputExtent(0, 200); - - expect(scale[0.0], closeTo(0, EPSILON)); - expect(scale[100.0], closeTo(200, EPSILON)); - expect(scale[50.0], closeTo(100, EPSILON)); - expect(scale[200.0], closeTo(400, EPSILON)); - }); - - test('comparing domain and range to viewport handles extent edges', () { - LinearScale scale = LinearScale(); - scale.range = ScaleOutputExtent(1000, 1400); - scale.domainOverride = NumericExtents(100.0, 300.0); - scale.viewportDomain = NumericExtents(200.0, 300.0); - - expect(scale.viewportDomain, equals(NumericExtents(200.0, 300.0))); - - expect(scale[210.0], closeTo(1040, EPSILON)); - expect(scale[400.0], closeTo(1800, EPSILON)); - expect(scale[100.0], closeTo(600, EPSILON)); - - expect(scale.compareDomainValueToViewport(199.0), equals(-1)); - expect(scale.compareDomainValueToViewport(200.0), equals(0)); - expect(scale.compareDomainValueToViewport(201.0), equals(0)); - expect(scale.compareDomainValueToViewport(299.0), equals(0)); - expect(scale.compareDomainValueToViewport(300.0), equals(0)); - expect(scale.compareDomainValueToViewport(301.0), equals(1)); - - expect(scale.isRangeValueWithinViewport(999.0), isFalse); - expect(scale.isRangeValueWithinViewport(1100.0), isTrue); - expect(scale.isRangeValueWithinViewport(1401.0), isFalse); - }); - - test('scale applies in reverse', () { - LinearScale scale = LinearScale(); - scale.range = ScaleOutputExtent(1000, 1400); - scale.domainOverride = NumericExtents(100.0, 300.0); - scale.viewportDomain = NumericExtents(200.0, 300.0); - - expect(scale.reverse(1040.0), closeTo(210.0, EPSILON)); - expect(scale.reverse(1800.0), closeTo(400.0, EPSILON)); - expect(scale.reverse(600.0), closeTo(100.0, EPSILON)); - }); - - test('scale works with a range from larger to smaller', () { - LinearScale scale = LinearScale(); - scale.range = ScaleOutputExtent(1400, 1000); - scale.domainOverride = NumericExtents(100.0, 300.0); - scale.viewportDomain = NumericExtents(200.0, 300.0); - - expect(scale[200.0], closeTo(1400.0, EPSILON)); - expect(scale[250.0], closeTo(1200.0, EPSILON)); - expect(scale[300.0], closeTo(1000.0, EPSILON)); - }); - - test('scaleFactor and translate applies to scale', () { - LinearScale scale = LinearScale(); - scale.range = ScaleOutputExtent(1000, 1200); - scale.domainOverride = NumericExtents(100.0, 200.0); - scale.setViewportSettings(4.0, -50.0); - - expect(scale[100.0], closeTo(950.0, EPSILON)); - expect(scale[200.0], closeTo(1750.0, EPSILON)); - expect(scale[150.0], closeTo(1350.0, EPSILON)); - expect(scale[106.25], closeTo(1000.0, EPSILON)); - expect(scale[131.25], closeTo(1200.0, EPSILON)); - - expect(scale.compareDomainValueToViewport(106.0), equals(-1)); - expect(scale.compareDomainValueToViewport(106.25), equals(0)); - expect(scale.compareDomainValueToViewport(107.0), equals(0)); - - expect(scale.compareDomainValueToViewport(131.0), equals(0)); - expect(scale.compareDomainValueToViewport(131.25), equals(0)); - expect(scale.compareDomainValueToViewport(132.0), equals(1)); - - expect(scale.isRangeValueWithinViewport(999.0), isFalse); - expect(scale.isRangeValueWithinViewport(1100.0), isTrue); - expect(scale.isRangeValueWithinViewport(1201.0), isFalse); - }); - - test('scale handles single point', () { - LinearScale domainScale = LinearScale(); - domainScale.range = ScaleOutputExtent(1000, 1200); - domainScale.addDomain(50.0); - - // A single point should render in the middle of the scale. - expect(domainScale[50.0], closeTo(1100.0, EPSILON)); - }); - - test('testAllZeros', () { - LinearScale measureScale = LinearScale(); - measureScale.range = ScaleOutputExtent(1000, 1200); - measureScale.addDomain(0.0); - - expect(measureScale[0.0], closeTo(1100.0, EPSILON)); - }); - - test('scale calculates step size', () { - LinearScale scale = LinearScale(); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.addDomain(1.0); - scale.addDomain(3.0); - scale.addDomain(11.0); - scale.range = ScaleOutputExtent(100, 200); - - // 1 - 11 has 6 steps of size 2, 0 - 12 - expect(scale.rangeBand, closeTo(100.0 / 6.0, EPSILON)); - }); - - test('scale applies rangeBand to detected step size', () { - LinearScale scale = LinearScale(); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(0.5); - scale.addDomain(1.0); - scale.addDomain(2.0); - scale.addDomain(10.0); - scale.range = ScaleOutputExtent(100, 200); - - // 100 range / 10 steps * 0.5percentStep = 5 - expect(scale.rangeBand, closeTo(5.0, EPSILON)); - }); - - test('scale stepSize calculation survives copy', () { - LinearScale scale = LinearScale(); - scale.stepSizeConfig = StepSizeConfig.fixedDomain(1.0); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.addDomain(1.0); - scale.addDomain(3.0); - scale.range = ScaleOutputExtent(100, 200); - expect(scale.copy().rangeBand, closeTo(100.0 / 3.0, EPSILON)); - }); - - test('scale rangeBand calculation survives copy', () { - LinearScale scale = LinearScale(); - scale.rangeBandConfig = RangeBandConfig.fixedPixel(123.0); - scale.addDomain(1.0); - scale.addDomain(3.0); - scale.range = ScaleOutputExtent(100, 200); - - expect(scale.copy().rangeBand, closeTo(123, EPSILON)); - }); - - test('scale rangeBand works for single domain value', () { - LinearScale scale = LinearScale(); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.addDomain(1.0); - scale.range = ScaleOutputExtent(100, 200); - - expect(scale.rangeBand, closeTo(100, EPSILON)); - }); - - test('scale rangeBand works for multiple domains of the same value', () { - LinearScale scale = LinearScale(); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.addDomain(1.0); - scale.addDomain(1.0); - scale.range = ScaleOutputExtent(100, 200); - - expect(scale.rangeBand, closeTo(100.0, EPSILON)); - }); - - test('scale rangeBand is zero when no domains are added', () { - LinearScale scale = LinearScale(); - scale.range = ScaleOutputExtent(100, 200); - - expect(scale.rangeBand, closeTo(0.0, EPSILON)); - }); - - test('scale domain info reset on resetDomain', () { - LinearScale scale = LinearScale(); - scale.addDomain(1.0); - scale.addDomain(3.0); - scale.range = ScaleOutputExtent(100, 200); - scale.setViewportSettings(1000.0, 2000.0); - - scale.resetDomain(); - scale.resetViewportSettings(); - expect(scale.viewportScalingFactor, closeTo(1.0, EPSILON)); - expect(scale.viewportTranslatePx, closeTo(0, EPSILON)); - expect(scale.range, equals(ScaleOutputExtent(100, 200))); - }); - - test('scale handles null domain values', () { - LinearScale scale = LinearScale(); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.addDomain(1.0); - scale.addDomain(null); - scale.addDomain(3.0); - scale.addDomain(11.0); - scale.range = ScaleOutputExtent(100, 200); - - expect(scale.rangeBand, closeTo(100.0 / 6.0, EPSILON)); - }); - - test('scale domainOverride survives copy', () { - LinearScale scale = LinearScale()..keepViewportWithinData = false; - scale.addDomain(1.0); - scale.addDomain(3.0); - scale.range = ScaleOutputExtent(100, 200); - scale.setViewportSettings(2.0, 10.0); - scale.domainOverride = NumericExtents(0.0, 100.0); - - LinearScale other = scale.copy(); - - expect(other.domainOverride, equals(NumericExtents(0.0, 100.0))); - expect(other[5.0], closeTo(120.0, EPSILON)); - }); - - test('scale calculates a scaleFactor given a domain window', () { - LinearScale scale = LinearScale(); - scale.addDomain(100.0); - scale.addDomain(130.0); - scale.addDomain(200.0); - scale.addDomain(170.0); - - expect(scale.computeViewportScaleFactor(10.0), closeTo(10, EPSILON)); - expect(scale.computeViewportScaleFactor(100.0), closeTo(1, EPSILON)); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/numeric_tick_provider_test.dart b/web/charts/common/test/chart/cartesian/axis/numeric_tick_provider_test.dart deleted file mode 100644 index bbcb0d6e0..000000000 --- a/web/charts/common/test/chart/cartesian/axis/numeric_tick_provider_test.dart +++ /dev/null @@ -1,496 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; -import 'package:charts_common/src/common/line_style.dart'; -import 'package:charts_common/src/common/text_style.dart'; -import 'package:charts_common/src/common/text_element.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/unitconverter/unit_converter.dart'; -import 'package:charts_common/src/chart/cartesian/axis/collision_report.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick_provider.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_extents.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_tick_provider.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockNumericScale extends Mock implements NumericScale {} - -/// A fake draw strategy that reports collision and alternate ticks -/// -/// Reports collision when the tick count is greater than or equal to -/// [collidesAfterTickCount]. -/// -/// Reports alternate rendering after tick count is greater than or equal to -/// [alternateRenderingAfterTickCount]. -class FakeDrawStrategy extends BaseTickDrawStrategy { - final int collidesAfterTickCount; - final int alternateRenderingAfterTickCount; - - FakeDrawStrategy( - this.collidesAfterTickCount, this.alternateRenderingAfterTickCount) - : super(null, FakeGraphicsFactory()); - - @override - CollisionReport collides(List> ticks, _) { - final ticksCollide = ticks.length >= collidesAfterTickCount; - final alternateTicksUsed = ticks.length >= alternateRenderingAfterTickCount; - - return CollisionReport( - ticksCollide: ticksCollide, - ticks: ticks, - alternateTicksUsed: alternateTicksUsed); - } - - @override - void draw(ChartCanvas canvas, Tick tick, - {AxisOrientation orientation, - Rectangle axisBounds, - Rectangle drawAreaBounds, - bool isFirst, - bool isLast}) {} -} - -/// A fake [GraphicsFactory] that returns [MockTextStyle] and [MockTextElement]. -class FakeGraphicsFactory extends GraphicsFactory { - @override - TextStyle createTextPaint() => MockTextStyle(); - - @override - TextElement createTextElement(String text) => MockTextElement(); - - @override - LineStyle createLinePaint() => MockLinePaint(); -} - -class MockTextStyle extends Mock implements TextStyle {} - -class MockTextElement extends Mock implements TextElement {} - -class MockLinePaint extends Mock implements LineStyle {} - -class MockChartContext extends Mock implements ChartContext {} - -/// A celsius to fahrenheit converter for testing axis with unit converter. -class CelsiusToFahrenheitConverter implements UnitConverter { - const CelsiusToFahrenheitConverter(); - - @override - num convert(num value) => (value * 1.8) + 32.0; - - @override - num invert(num value) => (value - 32.0) / 1.8; -} - -void main() { - FakeGraphicsFactory graphicsFactory; - MockNumericScale scale; - NumericTickProvider tickProvider; - TickFormatter formatter; - ChartContext context; - - setUp(() { - graphicsFactory = FakeGraphicsFactory(); - scale = MockNumericScale(); - tickProvider = NumericTickProvider(); - formatter = NumericTickFormatter(); - context = MockChartContext(); - }); - - test('singleTickCount_choosesTicksWithSmallestStepCoveringDomain', () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = false - ..setFixedTickCount(4) - ..allowedSteps = [1.0, 2.5, 5.0]; - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(10.0, 70.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(4)); - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(25)); - expect(ticks[2].value, equals(50)); - expect(ticks[3].value, equals(75)); - }); - - test( - 'tickCountRangeChoosesTicksWithMostTicksAndSmallestIntervalCoveringDomain', - () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = false - ..setTickCount(5, 3) - ..allowedSteps = [1.0, 2.5, 5.0]; - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(10.0, 80.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(5)); - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(25)); - expect(ticks[2].value, equals(50)); - expect(ticks[3].value, equals(75)); - expect(ticks[4].value, equals(100)); - }); - - test('choosesNonAlternateRenderingTicksEvenIfIntervalIsLarger', () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = false - ..setTickCount(5, 3) - ..allowedSteps = [1.0, 2.5, 6.0]; - final drawStrategy = FakeDrawStrategy(10, 5); - when(scale.viewportDomain).thenReturn(NumericExtents(10.0, 80.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(3)); - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(60)); - expect(ticks[2].value, equals(120)); - }); - - test('choosesNonCollidingTicksEvenIfIntervalIsLarger', () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = false - ..setTickCount(5, 3) - ..allowedSteps = [1.0, 2.5, 6.0]; - final drawStrategy = FakeDrawStrategy(5, 5); - when(scale.viewportDomain).thenReturn(NumericExtents(10.0, 80.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks, hasLength(3)); - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(60)); - expect(ticks[2].value, equals(120)); - }); - - test('zeroBound_alwaysReturnsZeroTick', () { - tickProvider - ..zeroBound = true - ..dataIsInWholeNumbers = false - ..setFixedTickCount(3) - ..allowedSteps = [1.0, 2.5, 5.0]; - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(55.0, 135.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - final tickValues = ticks.map((tick) => tick.value).toList(); - - expect(tickValues, contains(0.0)); - }); - - test('boundsCrossOrigin_alwaysReturnsZeroTick', () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = false - ..setFixedTickCount(3) - ..allowedSteps = [1.0, 2.5, 5.0]; - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(-55.0, 135.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - final tickValues = ticks.map((tick) => tick.value).toList(); - - expect(tickValues, contains(0.0)); - }); - - test('boundsCrossOrigin_returnsValidTickRange', () { - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(-55.0, 135.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - final tickValues = ticks.map((tick) => tick.value).toList(); - - // We expect to see a range of ticks that crosses zero. - expect(tickValues, - equals([-60.0, -30.0, 0.0, 30.0, 60.0, 90.0, 120.0, 150.0])); - }); - - test('dataIsWholeNumbers_returnsWholeNumberTicks', () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = true - ..setFixedTickCount(3) - ..allowedSteps = [1.0, 2.5, 5.0]; - final drawStrategy = FakeDrawStrategy(10, 10); - - when(scale.viewportDomain).thenReturn(NumericExtents(0.25, 0.75)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(1)); - expect(ticks[2].value, equals(2)); - }); - - test('choosesTicksBasedOnPreferredAxisUnits', () { - tickProvider - ..zeroBound = true - ..dataIsInWholeNumbers = false - ..setFixedTickCount(3) - ..allowedSteps = [5.0] - ..dataToAxisUnitConverter = const CelsiusToFahrenheitConverter(); - - final drawStrategy = FakeDrawStrategy(10, 10); - - when(scale.viewportDomain).thenReturn(NumericExtents(0.0, 20.0)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks[0].value, closeTo(-17.8, 0.1)); // 0 in axis units - expect(ticks[1].value, closeTo(10, 0.1)); // 50 in axis units - expect(ticks[2].value, closeTo(37.8, 0.1)); // 100 in axis units - }); - - test('handlesVerySmallMeasures', () { - tickProvider - ..zeroBound = true - ..dataIsInWholeNumbers = false - ..setFixedTickCount(5); - - final drawStrategy = FakeDrawStrategy(10, 10); - - when(scale.viewportDomain).thenReturn(NumericExtents(0.000001, 0.000002)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks.length, equals(5)); - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(0.0000005)); - expect(ticks[2].value, equals(0.0000010)); - expect(ticks[3].value, equals(0.0000015)); - expect(ticks[4].value, equals(0.000002)); - }); - - test('handlesVerySmallMeasuresForWholeNumbers', () { - tickProvider - ..zeroBound = true - ..dataIsInWholeNumbers = true - ..setFixedTickCount(5); - - final drawStrategy = FakeDrawStrategy(10, 10); - - when(scale.viewportDomain).thenReturn(NumericExtents(0.000001, 0.000002)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks.length, equals(5)); - expect(ticks[0].value, equals(0)); - expect(ticks[1].value, equals(1)); - expect(ticks[2].value, equals(2)); - expect(ticks[3].value, equals(3)); - expect(ticks[4].value, equals(4)); - }); - - test('handlesVerySmallMeasuresForWholeNumbersWithoutZero', () { - tickProvider - ..zeroBound = false - ..dataIsInWholeNumbers = true - ..setFixedTickCount(5); - - final drawStrategy = FakeDrawStrategy(10, 10); - - when(scale.viewportDomain) - .thenReturn(NumericExtents(101.000001, 101.000002)); - when(scale.rangeWidth).thenReturn(1000); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(ticks.length, equals(5)); - expect(ticks[0].value, equals(101)); - expect(ticks[1].value, equals(102)); - expect(ticks[2].value, equals(103)); - expect(ticks[3].value, equals(104)); - expect(ticks[4].value, equals(105)); - }); - - test('handles tick hint for non zero ticks', () { - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(20.0, 35.0)); - when(scale.rangeWidth).thenReturn(1000); - - // Step Size: 3, - // Previous start tick: 10 - // Previous window: 10 - 25 - // Previous ticks: 10, 13, 16, 19, 22, 25 - final tickHint = TickHint(10, 25, tickCount: 6); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null, - tickHint: tickHint, - ); - - // New adjusted ticks for window 20 - 35 - // Should have ticks 22, 25, 28, 31, 34, 37 - expect(ticks, hasLength(6)); - expect(ticks[0].value, equals(22)); - expect(ticks[1].value, equals(25)); - expect(ticks[2].value, equals(28)); - expect(ticks[3].value, equals(31)); - expect(ticks[4].value, equals(34)); - expect(ticks[5].value, equals(37)); - }); - - test('handles tick hint for negative starting ticks', () { - final drawStrategy = FakeDrawStrategy(10, 10); - when(scale.viewportDomain).thenReturn(NumericExtents(-35.0, -20.0)); - when(scale.rangeWidth).thenReturn(1000); - - // Step Size: 3, - // Previous start tick: -25 - // Previous window: -25 to -10 - // Previous ticks: -25, -22, -19, -16, -13, -10 - final tickHint = TickHint(-25, -10, tickCount: 6); - - final ticks = tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null, - tickHint: tickHint, - ); - - // New adjusted ticks for window -35 to -20 - // Should have ticks -34, -31, -28, -25, -22, -19 - expect(ticks, hasLength(6)); - expect(ticks[0].value, equals(-34)); - expect(ticks[1].value, equals(-31)); - expect(ticks[2].value, equals(-28)); - expect(ticks[3].value, equals(-25)); - expect(ticks[4].value, equals(-22)); - expect(ticks[5].value, equals(-19)); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/ordinal_scale_test.dart b/web/charts/common/test/chart/cartesian/axis/ordinal_scale_test.dart deleted file mode 100644 index f4ef7fb25..000000000 --- a/web/charts/common/test/chart/cartesian/axis/ordinal_scale_test.dart +++ /dev/null @@ -1,249 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/simple_ordinal_scale.dart'; - -import 'package:test/test.dart'; - -const EPSILON = 0.001; - -void main() { - SimpleOrdinalScale scale; - - setUp(() { - scale = SimpleOrdinalScale(); - scale.addDomain('a'); - scale.addDomain('b'); - scale.addDomain('c'); - scale.addDomain('d'); - - scale.range = ScaleOutputExtent(2000, 1000); - }); - - group('conversion', () { - test('with duplicate keys', () { - scale.addDomain('c'); - scale.addDomain('a'); - - // Current RangeBandConfig.styleAssignedPercent sets size to 0.65 percent. - expect(scale.rangeBand, closeTo(250 * 0.65, EPSILON)); - expect(scale['a'], closeTo(2000 - 125, EPSILON)); - expect(scale['b'], closeTo(2000 - 375, EPSILON)); - expect(scale['c'], closeTo(2000 - 625, EPSILON)); - }); - - test('invalid domain does not throw exception', () { - expect(scale['e'], 0); - }); - - test('invalid domain can translate is false', () { - expect(scale.canTranslate('e'), isFalse); - }); - }); - - group('copy', () { - test('can convert domain', () { - final copied = scale.copy(); - expect(copied['c'], closeTo(2000 - 625, EPSILON)); - }); - - test('does not affect original', () { - final copied = scale.copy(); - copied.addDomain('bar'); - - expect(copied.canTranslate('bar'), isTrue); - expect(scale.canTranslate('bar'), isFalse); - }); - }); - - group('reset', () { - test('clears domains', () { - scale.resetDomain(); - scale.addDomain('foo'); - scale.addDomain('bar'); - - expect(scale['foo'], closeTo(2000 - 250, EPSILON)); - }); - }); - - group('set RangeBandConfig', () { - test('fixed pixel range band changes range band', () { - scale.rangeBandConfig = RangeBandConfig.fixedPixel(123.0); - - expect(scale.rangeBand, closeTo(123.0, EPSILON)); - - // Adding another domain to ensure it still doesn't change. - scale.addDomain('foo'); - expect(scale.rangeBand, closeTo(123.0, EPSILON)); - }); - - test('percent range band changes range band', () { - scale.rangeBandConfig = RangeBandConfig.percentOfStep(0.5); - // 125 = 0.5f * 1000pixels / 4domains - expect(scale.rangeBand, closeTo(125.0, EPSILON)); - }); - - test('space from step changes range band', () { - scale.rangeBandConfig = RangeBandConfig.fixedPixelSpaceBetweenStep(50.0); - // 200 = 1000pixels / 4domains) - 50 - expect(scale.rangeBand, closeTo(200.0, EPSILON)); - }); - - test('fixed domain throws argument exception', () { - expect(() => scale.rangeBandConfig = RangeBandConfig.fixedDomain(5.0), - throwsArgumentError); - }); - - test('type of none throws argument exception', () { - expect(() => scale.rangeBandConfig = RangeBandConfig.none(), - throwsArgumentError); - }); - - test('set to null throws argument exception', () { - expect(() => scale.rangeBandConfig = null, throwsArgumentError); - }); - }); - - group('set step size config', () { - test('to null does not throw', () { - scale.stepSizeConfig = null; - }); - - test('to auto does not throw', () { - scale.stepSizeConfig = StepSizeConfig.auto(); - }); - - test('to fixed domain throw arugment exception', () { - expect(() => scale.stepSizeConfig = StepSizeConfig.fixedDomain(1.0), - throwsArgumentError); - }); - - test('to fixed pixel throw arugment exception', () { - expect(() => scale.stepSizeConfig = StepSizeConfig.fixedPixels(1.0), - throwsArgumentError); - }); - }); - - group('set range persists', () { - test('', () { - expect(scale.range.start, equals(2000)); - expect(scale.range.end, equals(1000)); - expect(scale.range.min, equals(1000)); - expect(scale.range.max, equals(2000)); - expect(scale.rangeWidth, equals(1000)); - - expect(scale.isRangeValueWithinViewport(1500.0), isTrue); - expect(scale.isRangeValueWithinViewport(1000.0), isTrue); - expect(scale.isRangeValueWithinViewport(2000.0), isTrue); - - expect(scale.isRangeValueWithinViewport(500.0), isFalse); - expect(scale.isRangeValueWithinViewport(2500.0), isFalse); - }); - }); - - group('scale factor', () { - test('sets', () { - scale.setViewportSettings(2.0, -700.0); - - expect(scale.viewportScalingFactor, closeTo(2.0, EPSILON)); - expect(scale.viewportTranslatePx, closeTo(-700.0, EPSILON)); - }); - - test('rangeband is scaled', () { - scale.setViewportSettings(2.0, -700.0); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - - expect(scale.rangeBand, closeTo(500.0, EPSILON)); - }); - - test('translate to pixels is scaled', () { - scale.setViewportSettings(2.0, -700.0); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.range = ScaleOutputExtent(1000, 2000); - - final scaledStepWidth = 500.0; - final scaledInitialShift = 250.0; - - expect(scale['a'], closeTo(1000 + scaledInitialShift - 700, EPSILON)); - - expect(scale['b'], - closeTo(1000 + scaledInitialShift - 700 + scaledStepWidth, EPSILON)); - }); - - test('only b and c should be within the viewport', () { - scale.setViewportSettings(2.0, -700.0); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(1.0); - scale.range = ScaleOutputExtent(1000, 2000); - - expect(scale.compareDomainValueToViewport('a'), equals(-1)); - expect(scale.compareDomainValueToViewport('c'), equals(0)); - expect(scale.compareDomainValueToViewport('d'), equals(1)); - expect(scale.compareDomainValueToViewport('f'), isNot(0)); - }); - }); - - group('viewport', () { - test('set adjust scale to show viewport', () { - scale.range = ScaleOutputExtent(1000, 2000); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(0.5); - scale.setViewport(2, 'b'); - - expect(scale['a'], closeTo(750, EPSILON)); - expect(scale['b'], closeTo(1250, EPSILON)); - expect(scale['c'], closeTo(1750, EPSILON)); - expect(scale['d'], closeTo(2250, EPSILON)); - expect(scale.compareDomainValueToViewport('a'), equals(-1)); - expect(scale.compareDomainValueToViewport('b'), equals(0)); - expect(scale.compareDomainValueToViewport('c'), equals(0)); - expect(scale.compareDomainValueToViewport('d'), equals(1)); - }); - - test('illegal to set window size less than one', () { - expect(() => scale.setViewport(0, 'b'), throwsArgumentError); - }); - - test('set starting value if starting domain is not in domain list', () { - scale.range = ScaleOutputExtent(1000, 2000); - scale.rangeBandConfig = RangeBandConfig.percentOfStep(0.5); - scale.setViewport(2, 'f'); - - expect(scale['a'], closeTo(1250, EPSILON)); - expect(scale['b'], closeTo(1750, EPSILON)); - expect(scale['c'], closeTo(2250, EPSILON)); - expect(scale['d'], closeTo(2750, EPSILON)); - }); - - test('get size returns number of full steps that fit scale range', () { - scale.range = ScaleOutputExtent(1000, 2000); - - scale.setViewportSettings(2.0, 0.0); - expect(scale.viewportDataSize, equals(2)); - - scale.setViewportSettings(5.0, 0.0); - expect(scale.viewportDataSize, equals(0)); - }); - - test('get starting viewport gets first fully visible domain', () { - scale.range = ScaleOutputExtent(1000, 2000); - - scale.setViewportSettings(2.0, -500.0); - expect(scale.viewportStartingDomain, equals('b')); - - scale.setViewportSettings(2.0, -100.0); - expect(scale.viewportStartingDomain, equals('b')); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/static_tick_provider_test.dart b/web/charts/common/test/chart/cartesian/axis/static_tick_provider_test.dart deleted file mode 100644 index ec0fb2a50..000000000 --- a/web/charts/common/test/chart/cartesian/axis/static_tick_provider_test.dart +++ /dev/null @@ -1,180 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/static_tick_provider.dart'; -import 'package:charts_common/src/chart/cartesian/axis/linear/linear_scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/cartesian/axis/scale.dart'; -import 'package:charts_common/src/chart/cartesian/axis/spec/tick_spec.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockChartContext extends Mock implements ChartContext {} - -class MockGraphicsFactory extends Mock implements GraphicsFactory {} - -class MockNumericTickFormatter extends Mock implements TickFormatter {} - -class FakeNumericTickFormatter implements TickFormatter { - int calledTimes = 0; - - @override - List format(List tickValues, Map cache, - {num stepSize}) { - calledTimes += 1; - - return tickValues.map((value) => value.toString()).toList(); - } -} - -class MockDrawStrategy extends Mock implements BaseTickDrawStrategy {} - -void main() { - ChartContext context; - GraphicsFactory graphicsFactory; - TickFormatter formatter; - BaseTickDrawStrategy drawStrategy; - LinearScale scale; - - setUp(() { - context = MockChartContext(); - graphicsFactory = MockGraphicsFactory(); - formatter = MockNumericTickFormatter(); - drawStrategy = MockDrawStrategy(); - scale = LinearScale()..range = ScaleOutputExtent(0, 300); - }); - - group('scale is extended with static tick values', () { - test('values extend existing domain values', () { - final tickProvider = StaticTickProvider([ - TickSpec(50, label: '50'), - TickSpec(75, label: '75'), - TickSpec(100, label: '100'), - ]); - - scale.addDomain(60); - scale.addDomain(80); - - expect(scale.dataExtent.min, equals(60)); - expect(scale.dataExtent.max, equals(80)); - - tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(scale.dataExtent.min, equals(50)); - expect(scale.dataExtent.max, equals(100)); - }); - - test('values within data extent', () { - final tickProvider = StaticTickProvider([ - TickSpec(50, label: '50'), - TickSpec(75, label: '75'), - TickSpec(100, label: '100'), - ]); - - scale.addDomain(0); - scale.addDomain(150); - - expect(scale.dataExtent.min, equals(0)); - expect(scale.dataExtent.max, equals(150)); - - tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: formatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(scale.dataExtent.min, equals(0)); - expect(scale.dataExtent.max, equals(150)); - }); - }); - - group('formatter', () { - test('is not called when all ticks have labels', () { - final tickProvider = StaticTickProvider([ - TickSpec(50, label: '50'), - TickSpec(75, label: '75'), - TickSpec(100, label: '100'), - ]); - - final fakeFormatter = FakeNumericTickFormatter(); - - tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: fakeFormatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(fakeFormatter.calledTimes, equals(0)); - }); - - test('is called when one ticks does not have label', () { - final tickProvider = StaticTickProvider([ - TickSpec(50, label: '50'), - TickSpec(75), - TickSpec(100, label: '100'), - ]); - - final fakeFormatter = FakeNumericTickFormatter(); - - tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: fakeFormatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(fakeFormatter.calledTimes, equals(1)); - }); - - test('is called when all ticks do not have labels', () { - final tickProvider = StaticTickProvider([ - TickSpec(50), - TickSpec(75), - TickSpec(100), - ]); - - final fakeFormatter = FakeNumericTickFormatter(); - - tickProvider.getTicks( - context: context, - graphicsFactory: graphicsFactory, - scale: scale, - formatter: fakeFormatter, - formatterValueCache: {}, - tickDrawStrategy: drawStrategy, - orientation: null); - - expect(fakeFormatter.calledTimes, equals(1)); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/time/date_time_tick_formatter_test.dart b/web/charts/common/test/chart/cartesian/axis/time/date_time_tick_formatter_test.dart deleted file mode 100644 index ca05efe42..000000000 --- a/web/charts/common/test/chart/cartesian/axis/time/date_time_tick_formatter_test.dart +++ /dev/null @@ -1,254 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/time/time_tick_formatter.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/date_time_tick_formatter.dart'; -import 'package:test/test.dart'; - -const EPSILON = 0.001; - -typedef IsTransitionFunction = bool Function( - DateTime tickValue, DateTime prevTickValue); - -class FakeTimeTickFormatter implements TimeTickFormatter { - static const firstTick = '-firstTick-'; - static const simpleTick = '-simpleTick-'; - static const transitionTick = '-transitionTick-'; - static final transitionAlwaysFalse = (_, __) => false; - - final String id; - final IsTransitionFunction isTransitionFunction; - - FakeTimeTickFormatter(this.id, {IsTransitionFunction isTransitionFunction}) - : isTransitionFunction = isTransitionFunction ?? transitionAlwaysFalse; - - @override - String formatFirstTick(DateTime date) => - id + firstTick + date.millisecondsSinceEpoch.toString(); - - @override - String formatSimpleTick(DateTime date) => - id + simpleTick + date.millisecondsSinceEpoch.toString(); - - @override - String formatTransitionTick(DateTime date) => - id + transitionTick + date.millisecondsSinceEpoch.toString(); - - @override - bool isTransition(DateTime tickValue, DateTime prevTickValue) => - isTransitionFunction(tickValue, prevTickValue); -} - -void main() { - TimeTickFormatter timeFormatter1; - TimeTickFormatter timeFormatter2; - TimeTickFormatter timeFormatter3; - - setUp(() { - timeFormatter1 = FakeTimeTickFormatter('fake1'); - timeFormatter2 = FakeTimeTickFormatter('fake2'); - timeFormatter3 = FakeTimeTickFormatter('fake3'); - }); - - group('Uses formatter', () { - test('with largest interval less than diff between tickValues', () { - final formatter = DateTimeTickFormatter.withFormatters( - {10: timeFormatter1, 100: timeFormatter2, 1000: timeFormatter3}); - final formatterCache = {}; - - final ticksWith10Diff = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(10), - DateTime.fromMillisecondsSinceEpoch(20) - ]; - final ticksWith20Diff = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(20), - DateTime.fromMillisecondsSinceEpoch(40) - ]; - final ticksWith100Diff = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(100), - DateTime.fromMillisecondsSinceEpoch(200) - ]; - final ticksWith200Diff = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(200), - DateTime.fromMillisecondsSinceEpoch(400) - ]; - final ticksWith1000Diff = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(1000), - DateTime.fromMillisecondsSinceEpoch(2000) - ]; - - final expectedLabels10Diff = [ - 'fake1-firstTick-0', - 'fake1-simpleTick-10', - 'fake1-simpleTick-20' - ]; - final expectedLabels20Diff = [ - 'fake1-firstTick-0', - 'fake1-simpleTick-20', - 'fake1-simpleTick-40' - ]; - final expectedLabels100Diff = [ - 'fake2-firstTick-0', - 'fake2-simpleTick-100', - 'fake2-simpleTick-200' - ]; - final expectedLabels200Diff = [ - 'fake2-firstTick-0', - 'fake2-simpleTick-200', - 'fake2-simpleTick-400' - ]; - final expectedLabels1000Diff = [ - 'fake3-firstTick-0', - 'fake3-simpleTick-1000', - 'fake3-simpleTick-2000' - ]; - - final actualLabelsWith10Diff = - formatter.format(ticksWith10Diff, formatterCache, stepSize: 10); - final actualLabelsWith20Diff = - formatter.format(ticksWith20Diff, formatterCache, stepSize: 20); - final actualLabelsWith100Diff = - formatter.format(ticksWith100Diff, formatterCache, stepSize: 100); - final actualLabelsWith200Diff = - formatter.format(ticksWith200Diff, formatterCache, stepSize: 200); - final actualLabelsWith1000Diff = - formatter.format(ticksWith1000Diff, formatterCache, stepSize: 1000); - - expect(actualLabelsWith10Diff, equals(expectedLabels10Diff)); - expect(actualLabelsWith20Diff, equals(expectedLabels20Diff)); - - expect(actualLabelsWith100Diff, equals(expectedLabels100Diff)); - expect(actualLabelsWith200Diff, equals(expectedLabels200Diff)); - expect(actualLabelsWith1000Diff, equals(expectedLabels1000Diff)); - }); - - test('with smallest interval when no smaller one exists', () { - final formatter = DateTimeTickFormatter.withFormatters( - {10: timeFormatter1, 100: timeFormatter2}); - final formatterCache = {}; - - final ticks = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(1), - DateTime.fromMillisecondsSinceEpoch(2) - ]; - final expectedLabels = [ - 'fake1-firstTick-0', - 'fake1-simpleTick-1', - 'fake1-simpleTick-2' - ]; - final actualLabels = formatter.format(ticks, formatterCache, stepSize: 1); - - expect(actualLabels, equals(expectedLabels)); - }); - - test('with smallest interval for single tick input', () { - final formatter = DateTimeTickFormatter.withFormatters( - {10: timeFormatter1, 100: timeFormatter2}); - final formatterCache = {}; - - final ticks = [DateTime.fromMillisecondsSinceEpoch(5000)]; - final expectedLabels = ['fake1-firstTick-5000']; - final actualLabels = formatter.format(ticks, formatterCache, stepSize: 0); - expect(actualLabels, equals(expectedLabels)); - }); - - test('on empty input doesnt break', () { - final formatter = - DateTimeTickFormatter.withFormatters({10: timeFormatter1}); - final formatterCache = {}; - - final actualLabels = - formatter.format([], formatterCache, stepSize: 10); - expect(actualLabels, isEmpty); - }); - - test('that formats transition tick with transition format', () { - final timeFormatter = FakeTimeTickFormatter('fake', - isTransitionFunction: (tickValue, _) => - tickValue.millisecondsSinceEpoch == 20); - final formatterCache = {}; - - final formatter = - DateTimeTickFormatter.withFormatters({10: timeFormatter}); - - final ticks = [ - DateTime.fromMillisecondsSinceEpoch(0), - DateTime.fromMillisecondsSinceEpoch(10), - DateTime.fromMillisecondsSinceEpoch(20), - DateTime.fromMillisecondsSinceEpoch(30) - ]; - - final expectedLabels = [ - 'fake-firstTick-0', - 'fake-simpleTick-10', - 'fake-transitionTick-20', - 'fake-simpleTick-30' - ]; - final actualLabels = - formatter.format(ticks, formatterCache, stepSize: 10); - - expect(actualLabels, equals(expectedLabels)); - }); - }); - - group('check custom time tick formatters', () { - test('throws arugment error if time resolution key is not positive', () { - // -1 is reserved for any, if there is only one formatter, -1 is allowed. - expect( - () => DateTimeTickFormatter.withFormatters( - {-1: timeFormatter1, 2: timeFormatter2}), - throwsArgumentError); - }); - - test('throws argument error if formatters is null or empty', () { - expect(() => DateTimeTickFormatter.withFormatters(null), - throwsArgumentError); - expect( - () => DateTimeTickFormatter.withFormatters({}), throwsArgumentError); - }); - - test('throws arugment error if formatters are not sorted', () { - expect( - () => DateTimeTickFormatter.withFormatters({ - 3: timeFormatter1, - 1: timeFormatter2, - 2: timeFormatter3, - }), - throwsArgumentError); - - expect( - () => DateTimeTickFormatter.withFormatters({ - 1: timeFormatter1, - 3: timeFormatter2, - 2: timeFormatter3, - }), - throwsArgumentError); - - expect( - () => DateTimeTickFormatter.withFormatters({ - 2: timeFormatter1, - 3: timeFormatter2, - 1: timeFormatter3, - }), - throwsArgumentError); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/time/simple_date_time_factory.dart b/web/charts/common/test/chart/cartesian/axis/time/simple_date_time_factory.dart deleted file mode 100644 index d35839c09..000000000 --- a/web/charts/common/test/chart/cartesian/axis/time/simple_date_time_factory.dart +++ /dev/null @@ -1,42 +0,0 @@ -// 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 'package:charts_common/src/common/date_time_factory.dart'; -import 'package:intl/intl.dart' show DateFormat; - -/// Returns DateTime for testing. -class SimpleDateTimeFactory implements DateTimeFactory { - const SimpleDateTimeFactory(); - - @override - DateTime createDateTimeFromMilliSecondsSinceEpoch( - int millisecondsSinceEpoch) => - DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch); - - @override - DateTime createDateTime(int year, - [int month = 1, - int day = 1, - int hour = 0, - int minute = 0, - int second = 0, - int millisecond = 0, - int microsecond = 0]) => - DateTime( - year, month, day, hour, minute, second, millisecond, microsecond); - - @override - DateFormat createDateFormat(String pattern) => DateFormat(pattern); -} diff --git a/web/charts/common/test/chart/cartesian/axis/time/time_stepper_test.dart b/web/charts/common/test/chart/cartesian/axis/time/time_stepper_test.dart deleted file mode 100644 index 846fc9e4f..000000000 --- a/web/charts/common/test/chart/cartesian/axis/time/time_stepper_test.dart +++ /dev/null @@ -1,483 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/time/date_time_extents.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/day_time_stepper.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/hour_time_stepper.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/minute_time_stepper.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/month_time_stepper.dart'; -import 'package:charts_common/src/chart/cartesian/axis/time/year_time_stepper.dart'; -import 'package:test/test.dart'; -import 'simple_date_time_factory.dart' show SimpleDateTimeFactory; - -const EPSILON = 0.001; - -void main() { - const dateTimeFactory = SimpleDateTimeFactory(); - const millisecondsInHour = 3600 * 1000; - - setUp(() {}); - - group('Day time stepper', () { - test('get steps with 1 day increments', () { - final stepper = DayTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 8, 20), end: DateTime(2017, 8, 25)); - final stepIterable = stepper.getSteps(extent)..iterator.reset(1); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - DateTime(2017, 8, 20), - DateTime(2017, 8, 21), - DateTime(2017, 8, 22), - DateTime(2017, 8, 23), - DateTime(2017, 8, 24), - DateTime(2017, 8, 25), - ])); - }); - - test('get steps with 5 day increments', () { - final stepper = DayTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 8, 10), - end: DateTime(2017, 8, 26), - ); - - final stepIterable = stepper.getSteps(extent)..iterator.reset(5); - final steps = stepIterable.toList(); - - expect(steps.length, equals(4)); - // Note, this is because 5 day increments in a month is 1,6,11,16,21,26,31 - expect( - steps, - equals([ - DateTime(2017, 8, 11), - DateTime(2017, 8, 16), - DateTime(2017, 8, 21), - DateTime(2017, 8, 26), - ])); - }); - - test('step through daylight saving forward change', () { - final stepper = DayTimeStepper(dateTimeFactory); - // DST for PST 2017 begin on March 12 - final extent = DateTimeExtents( - start: DateTime(2017, 3, 11), - end: DateTime(2017, 3, 13), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(1); - final steps = stepIterable.toList(); - - expect(steps.length, equals(3)); - expect( - steps, - equals([ - DateTime(2017, 3, 11), - DateTime(2017, 3, 12), - DateTime(2017, 3, 13), - ])); - }); - - test('step through daylight saving backward change', () { - final stepper = DayTimeStepper(dateTimeFactory); - // DST for PST 2017 end on November 5 - final extent = DateTimeExtents( - start: DateTime(2017, 11, 4), - end: DateTime(2017, 11, 6), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(1); - final steps = stepIterable.toList(); - - expect(steps.length, equals(3)); - expect( - steps, - equals([ - DateTime(2017, 11, 4), - DateTime(2017, 11, 5), - DateTime(2017, 11, 6), - ])); - }); - }); - - group('Hour time stepper', () { - test('gets steps in 1 hour increments', () { - final stepper = HourTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 8, 20, 10), - end: DateTime(2017, 8, 20, 15), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(1); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - DateTime(2017, 8, 20, 10), - DateTime(2017, 8, 20, 11), - DateTime(2017, 8, 20, 12), - DateTime(2017, 8, 20, 13), - DateTime(2017, 8, 20, 14), - DateTime(2017, 8, 20, 15), - ])); - }); - - test('gets steps in 4 hour increments', () { - final stepper = HourTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 8, 20, 10), - end: DateTime(2017, 8, 21, 10), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(4); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - DateTime(2017, 8, 20, 12), - DateTime(2017, 8, 20, 16), - DateTime(2017, 8, 20, 20), - DateTime(2017, 8, 21, 0), - DateTime(2017, 8, 21, 4), - DateTime(2017, 8, 21, 8), - ])); - }); - - test('step through daylight saving forward change in 1 hour increments', - () { - final stepper = HourTimeStepper(dateTimeFactory); - // DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am. - final extent = DateTimeExtents( - start: DateTime(2017, 3, 12, 0), - end: DateTime(2017, 3, 12, 5), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(1); - final steps = stepIterable.toList(); - - expect(steps.length, equals(5)); - expect( - steps, - equals([ - DateTime(2017, 3, 12, 0), - DateTime(2017, 3, 12, 1), - DateTime(2017, 3, 12, 3), - DateTime(2017, 3, 12, 4), - DateTime(2017, 3, 12, 5), - ])); - }); - - test('step through daylight saving backward change in 1 hour increments', - () { - final stepper = HourTimeStepper(dateTimeFactory); - // DST for PST 2017 end on November 5. At 2am, clocks are turned to 1am. - final extent = DateTimeExtents( - start: DateTime(2017, 11, 5, 0), - end: DateTime(2017, 11, 5, 4), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(1); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - DateTime(2017, 11, 5, 0), - DateTime(2017, 11, 5, 0) - .add(Duration(milliseconds: millisecondsInHour)), - DateTime(2017, 11, 5, 0) - .add(Duration(milliseconds: millisecondsInHour * 2)), - DateTime(2017, 11, 5, 2), - DateTime(2017, 11, 5, 3), - DateTime(2017, 11, 5, 4), - ])); - }); - - test('step through daylight saving forward change in 4 hour increments', - () { - final stepper = HourTimeStepper(dateTimeFactory); - // DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am. - final extent = DateTimeExtents( - start: DateTime(2017, 3, 12, 0), - end: DateTime(2017, 3, 13, 0), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(4); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - DateTime(2017, 3, 12, 4), - DateTime(2017, 3, 12, 8), - DateTime(2017, 3, 12, 12), - DateTime(2017, 3, 12, 16), - DateTime(2017, 3, 12, 20), - DateTime(2017, 3, 13, 0), - ])); - }); - - test('step through daylight saving backward change in 4 hour increments', - () { - final stepper = HourTimeStepper(dateTimeFactory); - // DST for PST 2017 end on November 5. - // At 2am, clocks are turned to 1am. - final extent = DateTimeExtents( - start: DateTime(2017, 11, 5, 0), - end: DateTime(2017, 11, 6, 0), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(4); - final steps = stepIterable.toList(); - - expect(steps.length, equals(7)); - expect( - steps, - equals([ - DateTime(2017, 11, 5, 0) - .add(Duration(milliseconds: millisecondsInHour)), - DateTime(2017, 11, 5, 4), - DateTime(2017, 11, 5, 8), - DateTime(2017, 11, 5, 12), - DateTime(2017, 11, 5, 16), - DateTime(2017, 11, 5, 20), - DateTime(2017, 11, 6, 0), - ])); - }); - }); - - group('Minute time stepper', () { - test('gets steps with 5 minute increments', () { - final stepper = MinuteTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 8, 20, 3, 46), - end: DateTime(2017, 8, 20, 4, 02), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(5); - final steps = stepIterable.toList(); - - expect(steps.length, equals(3)); - expect( - steps, - equals([ - DateTime(2017, 8, 20, 3, 50), - DateTime(2017, 8, 20, 3, 55), - DateTime(2017, 8, 20, 4), - ])); - }); - - test('step through daylight saving forward change', () { - final stepper = MinuteTimeStepper(dateTimeFactory); - // DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am. - final extent = DateTimeExtents( - start: DateTime(2017, 3, 12, 1, 40), - end: DateTime(2017, 3, 12, 4, 02), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(15); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - DateTime(2017, 3, 12, 1, 45), - DateTime(2017, 3, 12, 3), - DateTime(2017, 3, 12, 3, 15), - DateTime(2017, 3, 12, 3, 30), - DateTime(2017, 3, 12, 3, 45), - DateTime(2017, 3, 12, 4), - ])); - }); - - test('steps correctly after daylight saving forward change', () { - final stepper = MinuteTimeStepper(dateTimeFactory); - // DST for PST 2017 begin on March 12. At 2am clocks are turned to 3am. - final extent = DateTimeExtents( - start: DateTime(2017, 3, 12, 3, 02), - end: DateTime(2017, 3, 12, 4, 02), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(30); - final steps = stepIterable.toList(); - - expect(steps.length, equals(2)); - expect( - steps, - equals([ - DateTime(2017, 3, 12, 3, 30), - DateTime(2017, 3, 12, 4), - ])); - }); - - test('step through daylight saving backward change', () { - final stepper = MinuteTimeStepper(dateTimeFactory); - // DST for PST 2017 end on November 5. - // At 2am, clocks are turned to 1am. - final extent = DateTimeExtents( - start: DateTime(2017, 11, 5).add(Duration(hours: 1, minutes: 29)), - end: DateTime(2017, 11, 5, 3, 02)); - final stepIterable = stepper.getSteps(extent)..iterator.reset(30); - final steps = stepIterable.toList(); - - expect(steps.length, equals(6)); - expect( - steps, - equals([ - // The first 1:30am - DateTime(2017, 11, 5).add(Duration(hours: 1, minutes: 30)), - // The 2nd 1am. - DateTime(2017, 11, 5).add(Duration(hours: 2)), - // The 2nd 1:30am - DateTime(2017, 11, 5).add(Duration(hours: 2, minutes: 30)), - // 2am - DateTime(2017, 11, 5).add(Duration(hours: 3)), - // 2:30am - DateTime(2017, 11, 5).add(Duration(hours: 3, minutes: 30)), - // 3am - DateTime(2017, 11, 5, 3) - ])); - }); - }); - - group('Month time stepper', () { - test('steps crosses the year', () { - final stepper = MonthTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 5), - end: DateTime(2018, 9), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(4); - final steps = stepIterable.toList(); - - expect(steps.length, equals(4)); - expect( - steps, - equals([ - DateTime(2017, 8), - DateTime(2017, 12), - DateTime(2018, 4), - DateTime(2018, 8), - ])); - }); - - test('steps within one year', () { - final stepper = MonthTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017, 1), - end: DateTime(2017, 5), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(2); - final steps = stepIterable.toList(); - - expect(steps.length, equals(2)); - expect( - steps, - equals([ - DateTime(2017, 2), - DateTime(2017, 4), - ])); - }); - - test('step before would allow ticks to include last month of the year', () { - final stepper = MonthTimeStepper(dateTimeFactory); - final time = DateTime(2017, 10); - - expect(stepper.getStepTimeBeforeInclusive(time, 1), - equals(DateTime(2017, 10))); - - // Months - 3, 6, 9, 12 - expect(stepper.getStepTimeBeforeInclusive(time, 3), - equals(DateTime(2017, 9))); - - // Months - 6, 12 - expect(stepper.getStepTimeBeforeInclusive(time, 6), - equals(DateTime(2017, 6))); - }); - - test('step before for January', () { - final stepper = MonthTimeStepper(dateTimeFactory); - final time = DateTime(2017, 1); - - expect(stepper.getStepTimeBeforeInclusive(time, 1), - equals(DateTime(2017, 1))); - - // Months - 3, 6, 9, 12 - expect(stepper.getStepTimeBeforeInclusive(time, 3), - equals(DateTime(2016, 12))); - - // Months - 6, 12 - expect(stepper.getStepTimeBeforeInclusive(time, 6), - equals(DateTime(2016, 12))); - }); - - test('step before for December', () { - final stepper = MonthTimeStepper(dateTimeFactory); - final time = DateTime(2017, 12); - - expect(stepper.getStepTimeBeforeInclusive(time, 1), - equals(DateTime(2017, 12))); - - // Months - 3, 6, 9, 12 - expect(stepper.getStepTimeBeforeInclusive(time, 3), - equals(DateTime(2017, 12))); - - // Months - 6, 12 - expect(stepper.getStepTimeBeforeInclusive(time, 6), - equals(DateTime(2017, 12))); - }); - }); - - group('Year stepper', () { - test('steps in 10 year increments', () { - final stepper = YearTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(2017), - end: DateTime(2042), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(10); - final steps = stepIterable.toList(); - - expect(steps.length, equals(3)); - expect( - steps, - equals([ - DateTime(2020), - DateTime(2030), - DateTime(2040), - ])); - }); - - test('steps through negative year', () { - final stepper = YearTimeStepper(dateTimeFactory); - final extent = DateTimeExtents( - start: DateTime(-420), - end: DateTime(240), - ); - final stepIterable = stepper.getSteps(extent)..iterator.reset(200); - final steps = stepIterable.toList(); - - expect(steps.length, equals(4)); - expect( - steps, - equals([ - DateTime(-400), - DateTime(-200), - DateTime(0), - DateTime(200), - ])); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/axis/time/time_tick_provider_test.dart b/web/charts/common/test/chart/cartesian/axis/time/time_tick_provider_test.dart deleted file mode 100644 index d87aa04a9..000000000 --- a/web/charts/common/test/chart/cartesian/axis/time/time_tick_provider_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart'; -import 'package:test/test.dart'; -import 'simple_date_time_factory.dart' show SimpleDateTimeFactory; - -const EPSILON = 0.001; - -void main() { - const dateTimeFactory = SimpleDateTimeFactory(); - - group('Find closest step size from stepper', () { - test('from exactly matching step size', () { - final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider( - dateTimeFactory); - final oneHourMs = (Duration(hours: 1)).inMilliseconds; - final closestStepSize = stepper.getClosestStepSize(oneHourMs); - - expect(closestStepSize, equals(oneHourMs)); - }); - - test('choose smallest increment if step is smaller than smallest increment', - () { - final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider( - dateTimeFactory); - final oneHourMs = (Duration(hours: 1)).inMilliseconds; - final closestStepSize = - stepper.getClosestStepSize((Duration(minutes: 56)).inMilliseconds); - - expect(closestStepSize, equals(oneHourMs)); - }); - - test('choose largest increment if step is larger than largest increment', - () { - final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider( - dateTimeFactory); - final oneDayMs = (Duration(hours: 24)).inMilliseconds; - final closestStepSize = - stepper.getClosestStepSize((Duration(hours: 25)).inMilliseconds); - - expect(closestStepSize, equals(oneDayMs)); - }); - - test('choose closest increment if exact not found', () { - final stepper = AutoAdjustingDateTimeTickProvider.createHourTickProvider( - dateTimeFactory); - final threeHoursMs = (Duration(hours: 3)).inMilliseconds; - final closestStepSize = stepper - .getClosestStepSize((Duration(hours: 3, minutes: 28)).inMilliseconds); - - expect(closestStepSize, equals(threeHoursMs)); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/cartesian_chart_test.dart b/web/charts/common/test/chart/cartesian/cartesian_chart_test.dart deleted file mode 100644 index b462c2576..000000000 --- a/web/charts/common/test/chart/cartesian/cartesian_chart_test.dart +++ /dev/null @@ -1,105 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/cartesian/axis/spec/date_time_axis_spec.dart'; -import 'package:charts_common/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart'; -import 'package:charts_common/src/chart/cartesian/axis/spec/numeric_axis_spec.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/time_series/time_series_chart.dart'; -import 'package:charts_common/src/common/graphics_factory.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockContext extends Mock implements ChartContext {} - -class MockGraphicsFactory extends Mock implements GraphicsFactory {} - -class FakeNumericChart extends NumericCartesianChart { - FakeNumericChart() { - context = MockContext(); - graphicsFactory = MockGraphicsFactory(); - } - - @override - void initDomainAxis() { - // Purposely bypass the renderer code. - } -} - -class FakeOrdinalChart extends OrdinalCartesianChart { - FakeOrdinalChart() { - context = MockContext(); - graphicsFactory = MockGraphicsFactory(); - } - - @override - void initDomainAxis() { - // Purposely bypass the renderer code. - } -} - -class FakeTimeSeries extends TimeSeriesChart { - FakeTimeSeries() { - context = MockContext(); - graphicsFactory = MockGraphicsFactory(); - } - - @override - void initDomainAxis() { - // Purposely bypass the renderer code. - } -} - -void main() { - group('Axis reset with new axis spec', () { - test('for ordinal chart', () { - final chart = FakeOrdinalChart(); - chart.configurationChanged(); - final domainAxis = chart.domainAxis; - expect(domainAxis, isNotNull); - - chart.domainAxisSpec = OrdinalAxisSpec(); - chart.configurationChanged(); - - expect(domainAxis, isNot(chart.domainAxis)); - }); - - test('for numeric chart', () { - final chart = FakeNumericChart(); - chart.configurationChanged(); - final domainAxis = chart.domainAxis; - expect(domainAxis, isNotNull); - - chart.domainAxisSpec = NumericAxisSpec(); - chart.configurationChanged(); - - expect(domainAxis, isNot(chart.domainAxis)); - }); - - test('for time series chart', () { - final chart = FakeTimeSeries(); - chart.configurationChanged(); - final domainAxis = chart.domainAxis; - expect(domainAxis, isNotNull); - - chart.domainAxisSpec = DateTimeAxisSpec(); - chart.configurationChanged(); - - expect(domainAxis, isNot(chart.domainAxis)); - }); - }); -} diff --git a/web/charts/common/test/chart/cartesian/cartesian_renderer_test.dart b/web/charts/common/test/chart/cartesian/cartesian_renderer_test.dart deleted file mode 100644 index 500e8e5af..000000000 --- a/web/charts/common/test/chart/cartesian/cartesian_renderer_test.dart +++ /dev/null @@ -1,295 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/cartesian_renderer.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/series_datum.dart'; -import 'package:charts_common/src/common/symbol_renderer.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// For testing viewport start / end. -class FakeCartesianRenderer extends BaseCartesianRenderer { - @override - List getNearestDatumDetailPerSeries(Point chartPoint, - bool byDomain, Rectangle boundsOverride) => - null; - - @override - void paint(ChartCanvas canvas, double animationPercent) {} - - @override - void update(List seriesList, bool isAnimating) {} - - @override - SymbolRenderer get symbolRenderer => null; - - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - return details; - } -} - -class MockAxis extends Mock implements Axis {} - -void main() { - BaseCartesianRenderer renderer; - - setUp(() { - renderer = FakeCartesianRenderer(); - }); - - group('find viewport start', () { - test('several domains are in the viewport', () { - final data = [0, 1, 2, 3, 4, 5, 6]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(0); - when(axis.compareDomainValueToViewport(3)).thenReturn(0); - when(axis.compareDomainValueToViewport(4)).thenReturn(0); - when(axis.compareDomainValueToViewport(5)).thenReturn(1); - when(axis.compareDomainValueToViewport(6)).thenReturn(1); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(2)); - }); - - test('extents are all in the viewport, use the first domain', () { - // Start of viewport is the same as the start of the domain. - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(any)).thenReturn(0); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(0)); - }); - - test('is the first domain', () { - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(0); - when(axis.compareDomainValueToViewport(1)).thenReturn(1); - when(axis.compareDomainValueToViewport(2)).thenReturn(1); - when(axis.compareDomainValueToViewport(3)).thenReturn(1); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(0)); - }); - - test('is the last domain', () { - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(-1); - when(axis.compareDomainValueToViewport(3)).thenReturn(0); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(3)); - }); - - test('is the middle', () { - final data = [0, 1, 2, 3, 4, 5, 6]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(-1); - when(axis.compareDomainValueToViewport(3)).thenReturn(0); - when(axis.compareDomainValueToViewport(4)).thenReturn(1); - when(axis.compareDomainValueToViewport(5)).thenReturn(1); - when(axis.compareDomainValueToViewport(6)).thenReturn(1); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(3)); - }); - - test('viewport is between data', () { - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(1); - when(axis.compareDomainValueToViewport(3)).thenReturn(1); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(1)); - }); - - // Error case, viewport shouldn't be set to the outside of the extents. - // We still want to provide a value. - test('all extents greater than viewport ', () { - // Return the right most value as start of viewport. - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(any)).thenReturn(1); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(3)); - }); - - // Error case, viewport shouldn't be set to the outside of the extents. - // We still want to provide a value. - test('all extents less than viewport ', () { - // Return the left most value as the start of the viewport. - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(any)).thenReturn(-1); - - final start = renderer.findNearestViewportStart(axis, domainFn, data); - - expect(start, equals(0)); - }); - }); - - group('find viewport end', () { - test('several domains are in the viewport', () { - final data = [0, 1, 2, 3, 4, 5, 6]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(0); - when(axis.compareDomainValueToViewport(3)).thenReturn(0); - when(axis.compareDomainValueToViewport(4)).thenReturn(0); - when(axis.compareDomainValueToViewport(5)).thenReturn(1); - when(axis.compareDomainValueToViewport(6)).thenReturn(1); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(4)); - }); - - test('extents are all in the viewport, use the last domain', () { - // Start of viewport is the same as the end of the domain. - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(any)).thenReturn(0); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(3)); - }); - - test('is the first domain', () { - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(0); - when(axis.compareDomainValueToViewport(1)).thenReturn(1); - when(axis.compareDomainValueToViewport(2)).thenReturn(1); - when(axis.compareDomainValueToViewport(3)).thenReturn(1); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(0)); - }); - - test('is the last domain', () { - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(-1); - when(axis.compareDomainValueToViewport(3)).thenReturn(0); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(3)); - }); - - test('is the middle', () { - final data = [0, 1, 2, 3, 4, 5, 6]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(-1); - when(axis.compareDomainValueToViewport(3)).thenReturn(0); - when(axis.compareDomainValueToViewport(4)).thenReturn(1); - when(axis.compareDomainValueToViewport(5)).thenReturn(1); - when(axis.compareDomainValueToViewport(6)).thenReturn(1); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(3)); - }); - - test('viewport is between data', () { - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(0)).thenReturn(-1); - when(axis.compareDomainValueToViewport(1)).thenReturn(-1); - when(axis.compareDomainValueToViewport(2)).thenReturn(1); - when(axis.compareDomainValueToViewport(3)).thenReturn(1); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(2)); - }); - - // Error case, viewport shouldn't be set to the outside of the extents. - // We still want to provide a value. - test('all extents greater than viewport ', () { - // Return the right most value as start of viewport. - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(any)).thenReturn(1); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(3)); - }); - - // Error case, viewport shouldn't be set to the outside of the extents. - // We still want to provide a value. - test('all extents less than viewport ', () { - // Return the left most value as the start of the viewport. - final data = [0, 1, 2, 3]; - final domainFn = (index) => data[index]; - final axis = MockAxis(); - when(axis.compareDomainValueToViewport(any)).thenReturn(-1); - - final start = renderer.findNearestViewportEnd(axis, domainFn, data); - - expect(start, equals(0)); - }); - }); -} diff --git a/web/charts/common/test/chart/common/behavior/a11y/domain_a11y_explore_behavior_test.dart b/web/charts/common/test/chart/common/behavior/a11y/domain_a11y_explore_behavior_test.dart deleted file mode 100644 index 07cf576d9..000000000 --- a/web/charts/common/test/chart/common/behavior/a11y/domain_a11y_explore_behavior_test.dart +++ /dev/null @@ -1,252 +0,0 @@ -// 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:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart'; -import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockContext extends Mock implements ChartContext {} - -class MockAxis extends Mock implements Axis {} - -class FakeCartesianChart extends CartesianChart { - @override - Rectangle drawAreaBounds; - - void callFireOnPostprocess(List> seriesList) { - fireOnPostprocess(seriesList); - } - - @override - initDomainAxis() {} -} - -void main() { - FakeCartesianChart chart; - DomainA11yExploreBehavior behavior; - MockAxis domainAxis; - - MutableSeries _series1; - final _s1D1 = MyRow('s1d1', 11, 'a11yd1'); - final _s1D2 = MyRow('s1d2', 12, 'a11yd2'); - final _s1D3 = MyRow('s1d3', 13, 'a11yd3'); - - setUp(() { - chart = FakeCartesianChart()..drawAreaBounds = Rectangle(50, 20, 150, 80); - - behavior = DomainA11yExploreBehavior( - vocalizationCallback: domainVocalization); - behavior.attachTo(chart); - - domainAxis = MockAxis(); - _series1 = MutableSeries(Series( - id: 's1', - data: [_s1D1, _s1D2, _s1D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - )) - ..setAttr(domainAxisKey, domainAxis); - }); - - test('creates nodes for vertically drawn charts', () { - // A LTR chart - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - chart.context = context; - // Drawn vertically - chart.vertical = true; - // Set step size of 50, which should be the width of the bounding box - when(domainAxis.stepSize).thenReturn(50.0); - when(domainAxis.getLocation('s1d1')).thenReturn(75.0); - when(domainAxis.getLocation('s1d2')).thenReturn(125.0); - when(domainAxis.getLocation('s1d3')).thenReturn(175.0); - // Call fire on post process for the behavior to get the series list. - chart.callFireOnPostprocess([_series1]); - - final nodes = behavior.createA11yNodes(); - - expect(nodes, hasLength(3)); - expect(nodes[0].label, equals('s1d1')); - expect(nodes[0].boundingBox, equals(Rectangle(50, 20, 50, 80))); - expect(nodes[1].label, equals('s1d2')); - expect(nodes[1].boundingBox, equals(Rectangle(100, 20, 50, 80))); - expect(nodes[2].label, equals('s1d3')); - expect(nodes[2].boundingBox, equals(Rectangle(150, 20, 50, 80))); - }); - - test('creates nodes for vertically drawn RTL charts', () { - // A RTL chart - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(true); - when(context.isRtl).thenReturn(true); - chart.context = context; - // Drawn vertically - chart.vertical = true; - // Set step size of 50, which should be the width of the bounding box - when(domainAxis.stepSize).thenReturn(50.0); - when(domainAxis.getLocation('s1d1')).thenReturn(175.0); - when(domainAxis.getLocation('s1d2')).thenReturn(125.0); - when(domainAxis.getLocation('s1d3')).thenReturn(75.0); - // Call fire on post process for the behavior to get the series list. - chart.callFireOnPostprocess([_series1]); - - final nodes = behavior.createA11yNodes(); - - expect(nodes, hasLength(3)); - expect(nodes[0].label, equals('s1d1')); - expect(nodes[0].boundingBox, equals(Rectangle(150, 20, 50, 80))); - expect(nodes[1].label, equals('s1d2')); - expect(nodes[1].boundingBox, equals(Rectangle(100, 20, 50, 80))); - expect(nodes[2].label, equals('s1d3')); - expect(nodes[2].boundingBox, equals(Rectangle(50, 20, 50, 80))); - }); - - test('creates nodes for horizontally drawn charts', () { - // A LTR chart - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - chart.context = context; - // Drawn horizontally - chart.vertical = false; - // Set step size of 20, which should be the height of the bounding box - when(domainAxis.stepSize).thenReturn(20.0); - when(domainAxis.getLocation('s1d1')).thenReturn(30.0); - when(domainAxis.getLocation('s1d2')).thenReturn(50.0); - when(domainAxis.getLocation('s1d3')).thenReturn(70.0); - // Call fire on post process for the behavior to get the series list. - chart.callFireOnPostprocess([_series1]); - - final nodes = behavior.createA11yNodes(); - - expect(nodes, hasLength(3)); - expect(nodes[0].label, equals('s1d1')); - expect(nodes[0].boundingBox, equals(Rectangle(50, 20, 150, 20))); - expect(nodes[1].label, equals('s1d2')); - expect(nodes[1].boundingBox, equals(Rectangle(50, 40, 150, 20))); - expect(nodes[2].label, equals('s1d3')); - expect(nodes[2].boundingBox, equals(Rectangle(50, 60, 150, 20))); - }); - - test('creates nodes for horizontally drawn RTL charts', () { - // A LTR chart - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(true); - when(context.isRtl).thenReturn(true); - chart.context = context; - // Drawn horizontally - chart.vertical = false; - // Set step size of 20, which should be the height of the bounding box - when(domainAxis.stepSize).thenReturn(20.0); - when(domainAxis.getLocation('s1d1')).thenReturn(30.0); - when(domainAxis.getLocation('s1d2')).thenReturn(50.0); - when(domainAxis.getLocation('s1d3')).thenReturn(70.0); - // Call fire on post process for the behavior to get the series list. - chart.callFireOnPostprocess([_series1]); - - final nodes = behavior.createA11yNodes(); - - expect(nodes, hasLength(3)); - expect(nodes[0].label, equals('s1d1')); - expect(nodes[0].boundingBox, equals(Rectangle(50, 20, 150, 20))); - expect(nodes[1].label, equals('s1d2')); - expect(nodes[1].boundingBox, equals(Rectangle(50, 40, 150, 20))); - expect(nodes[2].label, equals('s1d3')); - expect(nodes[2].boundingBox, equals(Rectangle(50, 60, 150, 20))); - }); - - test('nodes ordered correctly with a series missing a domain', () { - // A LTR chart - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - chart.context = context; - // Drawn vertically - chart.vertical = true; - // Set step size of 50, which should be the width of the bounding box - when(domainAxis.stepSize).thenReturn(50.0); - when(domainAxis.getLocation('s1d1')).thenReturn(75.0); - when(domainAxis.getLocation('s1d2')).thenReturn(125.0); - when(domainAxis.getLocation('s1d3')).thenReturn(175.0); - // Create a series with a missing domain - final seriesWithMissingDomain = MutableSeries(Series( - id: 'm1', - data: [_s1D1, _s1D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - )) - ..setAttr(domainAxisKey, domainAxis); - - // Call fire on post process for the behavior to get the series list. - chart.callFireOnPostprocess([seriesWithMissingDomain, _series1]); - - final nodes = behavior.createA11yNodes(); - - expect(nodes, hasLength(3)); - expect(nodes[0].label, equals('s1d1')); - expect(nodes[0].boundingBox, equals(Rectangle(50, 20, 50, 80))); - expect(nodes[1].label, equals('s1d2')); - expect(nodes[1].boundingBox, equals(Rectangle(100, 20, 50, 80))); - expect(nodes[2].label, equals('s1d3')); - expect(nodes[2].boundingBox, equals(Rectangle(150, 20, 50, 80))); - }); - - test('creates nodes with minimum width', () { - // A behavior with minimum width of 50 - final behaviorWithMinWidth = - DomainA11yExploreBehavior(minimumWidth: 50.0); - behaviorWithMinWidth.attachTo(chart); - - // A LTR chart - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - chart.context = context; - // Drawn vertically - chart.vertical = true; - // Return a step size of 20, which is less than the minimum width. - // Expect the results to use the minimum width of 50 instead. - when(domainAxis.stepSize).thenReturn(20.0); - when(domainAxis.getLocation('s1d1')).thenReturn(75.0); - when(domainAxis.getLocation('s1d2')).thenReturn(125.0); - when(domainAxis.getLocation('s1d3')).thenReturn(175.0); - // Call fire on post process for the behavior to get the series list. - chart.callFireOnPostprocess([_series1]); - - final nodes = behaviorWithMinWidth.createA11yNodes(); - - expect(nodes, hasLength(3)); - expect(nodes[0].label, equals('s1d1')); - expect(nodes[0].boundingBox, equals(Rectangle(50, 20, 50, 80))); - expect(nodes[1].label, equals('s1d2')); - expect(nodes[1].boundingBox, equals(Rectangle(100, 20, 50, 80))); - expect(nodes[2].label, equals('s1d3')); - expect(nodes[2].boundingBox, equals(Rectangle(150, 20, 50, 80))); - }); -} - -class MyRow { - final String campaign; - final int count; - final String a11yDescription; - MyRow(this.campaign, this.count, this.a11yDescription); -} diff --git a/web/charts/common/test/chart/common/behavior/calculation/percent_injector_test.dart b/web/charts/common/test/chart/common/behavior/calculation/percent_injector_test.dart deleted file mode 100644 index a190059fc..000000000 --- a/web/charts/common/test/chart/common/behavior/calculation/percent_injector_test.dart +++ /dev/null @@ -1,593 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart' - show MutableSeries; -import 'package:charts_common/src/chart/common/behavior/calculation/percent_injector.dart'; -import 'package:charts_common/src/data/series.dart' show Series; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaign; - final int clickCount; - final int clickCountLower; - final int clickCountUpper; - MyRow(this.campaign, this.clickCount, this.clickCountLower, - this.clickCountUpper); -} - -class MockChart extends Mock implements CartesianChart { - LifecycleListener lastLifecycleListener; - - bool vertical = true; - - @override - addLifecycleListener(LifecycleListener listener) => - lastLifecycleListener = listener; - - @override - removeLifecycleListener(LifecycleListener listener) { - expect(listener, equals(lastLifecycleListener)); - lastLifecycleListener = null; - return true; - } -} - -void main() { - MockChart _chart; - List> seriesList; - - PercentInjector _makeBehavior( - {PercentInjectorTotalType totalType = PercentInjectorTotalType.domain}) { - final behavior = PercentInjector(totalType: totalType); - - behavior.attachTo(_chart); - - return behavior; - } - - setUp(() { - _chart = MockChart(); - - final myFakeDesktopAData = [ - MyRow('MyCampaign1', 1, 1, 1), - MyRow('MyCampaign2', 2, 2, 2), - MyRow('MyCampaign3', 3, 3, 3), - ]; - - final myFakeTabletAData = [ - MyRow('MyCampaign1', 2, 2, 2), - MyRow('MyCampaign2', 3, 3, 3), - MyRow('MyCampaign3', 4, 4, 4), - ]; - - final myFakeMobileAData = [ - MyRow('MyCampaign1', 3, 3, 3), - MyRow('MyCampaign2', 4, 4, 4), - MyRow('MyCampaign3', 5, 5, 5), - ]; - - final myFakeDesktopBData = [ - MyRow('MyCampaign1', 10, 8, 12), - MyRow('MyCampaign2', 20, 18, 22), - MyRow('MyCampaign3', 30, 28, 32), - ]; - - final myFakeTabletBData = [ - MyRow('MyCampaign1', 20, 18, 22), - MyRow('MyCampaign2', 30, 28, 32), - MyRow('MyCampaign3', 40, 38, 42), - ]; - - final myFakeMobileBData = [ - MyRow('MyCampaign1', 30, 28, 32), - MyRow('MyCampaign2', 40, 38, 42), - MyRow('MyCampaign3', 50, 48, 52), - ]; - - seriesList = [ - MutableSeries(Series( - id: 'Desktop A', - seriesCategory: 'A', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeDesktopAData)), - MutableSeries(Series( - id: 'Tablet A', - seriesCategory: 'A', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeTabletAData)), - MutableSeries(Series( - id: 'Mobile A', - seriesCategory: 'A', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - data: myFakeMobileAData)), - MutableSeries(Series( - id: 'Desktop B', - seriesCategory: 'B', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureLowerBoundFn: (row, _) => row.clickCountLower, - measureUpperBoundFn: (row, _) => row.clickCountUpper, - measureOffsetFn: (row, _) => 0, - data: myFakeDesktopBData)), - MutableSeries(Series( - id: 'Tablet B', - seriesCategory: 'B', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureLowerBoundFn: (row, _) => row.clickCountLower, - measureUpperBoundFn: (row, _) => row.clickCountUpper, - measureOffsetFn: (row, _) => 0, - data: myFakeTabletBData)), - MutableSeries(Series( - id: 'Mobile B', - seriesCategory: 'B', - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureLowerBoundFn: (row, _) => row.clickCountLower, - measureUpperBoundFn: (row, _) => row.clickCountUpper, - measureOffsetFn: (row, _) => 0, - data: myFakeMobileBData)) - ]; - }); - - group('Inject', () { - test('percent of domain', () { - // Setup behavior. - _makeBehavior(totalType: PercentInjectorTotalType.domain); - - // Act - _chart.lastLifecycleListener.onData(seriesList); - _chart.lastLifecycleListener.onPreprocess(seriesList); - - // Verify first series. - var series = seriesList[0]; - - expect(series.measureFn(0), equals(1 / 66)); - expect(series.measureFn(1), equals(2 / 99)); - expect(series.measureFn(2), equals(3 / 132)); - - expect(series.rawMeasureFn(0), equals(1)); - expect(series.rawMeasureFn(1), equals(2)); - expect(series.rawMeasureFn(2), equals(3)); - - // Verify second series. - series = seriesList[1]; - - expect(series.measureFn(0), equals(2 / 66)); - expect(series.measureFn(1), equals(3 / 99)); - expect(series.measureFn(2), equals(4 / 132)); - - expect(series.rawMeasureFn(0), equals(2)); - expect(series.rawMeasureFn(1), equals(3)); - expect(series.rawMeasureFn(2), equals(4)); - - // Verify third series. - series = seriesList[2]; - - expect(series.measureFn(0), equals(3 / 66)); - expect(series.measureFn(1), equals(4 / 99)); - expect(series.measureFn(2), equals(5 / 132)); - - expect(series.rawMeasureFn(0), equals(3)); - expect(series.rawMeasureFn(1), equals(4)); - expect(series.rawMeasureFn(2), equals(5)); - - // Verify fourth series. - series = seriesList[3]; - - expect(series.measureFn(0), equals(10 / 66)); - expect(series.measureFn(1), equals(20 / 99)); - expect(series.measureFn(2), equals(30 / 132)); - - expect(series.rawMeasureFn(0), equals(10)); - expect(series.rawMeasureFn(1), equals(20)); - expect(series.rawMeasureFn(2), equals(30)); - - expect(series.measureLowerBoundFn(0), equals(8 / 66)); - expect(series.measureLowerBoundFn(1), equals(18 / 99)); - expect(series.measureLowerBoundFn(2), equals(28 / 132)); - - expect(series.rawMeasureLowerBoundFn(0), equals(8)); - expect(series.rawMeasureLowerBoundFn(1), equals(18)); - expect(series.rawMeasureLowerBoundFn(2), equals(28)); - - expect(series.measureUpperBoundFn(0), equals(12 / 66)); - expect(series.measureUpperBoundFn(1), equals(22 / 99)); - expect(series.measureUpperBoundFn(2), equals(32 / 132)); - - expect(series.rawMeasureUpperBoundFn(0), equals(12)); - expect(series.rawMeasureUpperBoundFn(1), equals(22)); - expect(series.rawMeasureUpperBoundFn(2), equals(32)); - - // Verify fifth series. - series = seriesList[4]; - - expect(series.measureFn(0), equals(20 / 66)); - expect(series.measureFn(1), equals(30 / 99)); - expect(series.measureFn(2), equals(40 / 132)); - - expect(series.rawMeasureFn(0), equals(20)); - expect(series.rawMeasureFn(1), equals(30)); - expect(series.rawMeasureFn(2), equals(40)); - - expect(series.measureLowerBoundFn(0), equals(18 / 66)); - expect(series.measureLowerBoundFn(1), equals(28 / 99)); - expect(series.measureLowerBoundFn(2), equals(38 / 132)); - - expect(series.rawMeasureLowerBoundFn(0), equals(18)); - expect(series.rawMeasureLowerBoundFn(1), equals(28)); - expect(series.rawMeasureLowerBoundFn(2), equals(38)); - - expect(series.measureUpperBoundFn(0), equals(22 / 66)); - expect(series.measureUpperBoundFn(1), equals(32 / 99)); - expect(series.measureUpperBoundFn(2), equals(42 / 132)); - - expect(series.rawMeasureUpperBoundFn(0), equals(22)); - expect(series.rawMeasureUpperBoundFn(1), equals(32)); - expect(series.rawMeasureUpperBoundFn(2), equals(42)); - - // Verify sixth series. - series = seriesList[5]; - - expect(series.measureFn(0), equals(30 / 66)); - expect(series.measureFn(1), equals(40 / 99)); - expect(series.measureFn(2), equals(50 / 132)); - - expect(series.rawMeasureFn(0), equals(30)); - expect(series.rawMeasureFn(1), equals(40)); - expect(series.rawMeasureFn(2), equals(50)); - - expect(series.measureLowerBoundFn(0), equals(28 / 66)); - expect(series.measureLowerBoundFn(1), equals(38 / 99)); - expect(series.measureLowerBoundFn(2), equals(48 / 132)); - - expect(series.rawMeasureLowerBoundFn(0), equals(28)); - expect(series.rawMeasureLowerBoundFn(1), equals(38)); - expect(series.rawMeasureLowerBoundFn(2), equals(48)); - - expect(series.measureUpperBoundFn(0), equals(32 / 66)); - expect(series.measureUpperBoundFn(1), equals(42 / 99)); - expect(series.measureUpperBoundFn(2), equals(52 / 132)); - - expect(series.rawMeasureUpperBoundFn(0), equals(32)); - expect(series.rawMeasureUpperBoundFn(1), equals(42)); - expect(series.rawMeasureUpperBoundFn(2), equals(52)); - }); - - test('percent of domain, grouped by series category', () { - // Setup behavior. - _makeBehavior(totalType: PercentInjectorTotalType.domainBySeriesCategory); - - // Act - _chart.lastLifecycleListener.onData(seriesList); - _chart.lastLifecycleListener.onPreprocess(seriesList); - - // Verify first series. - var series = seriesList[0]; - - expect(series.measureFn(0), equals(1 / 6)); - expect(series.measureFn(1), equals(2 / 9)); - expect(series.measureFn(2), equals(3 / 12)); - - expect(series.rawMeasureFn(0), equals(1)); - expect(series.rawMeasureFn(1), equals(2)); - expect(series.rawMeasureFn(2), equals(3)); - - // Verify second series. - series = seriesList[1]; - - expect(series.measureFn(0), equals(2 / 6)); - expect(series.measureFn(1), equals(3 / 9)); - expect(series.measureFn(2), equals(4 / 12)); - - expect(series.rawMeasureFn(0), equals(2)); - expect(series.rawMeasureFn(1), equals(3)); - expect(series.rawMeasureFn(2), equals(4)); - - // Verify third series. - series = seriesList[2]; - - expect(series.measureFn(0), equals(3 / 6)); - expect(series.measureFn(1), equals(4 / 9)); - expect(series.measureFn(2), equals(5 / 12)); - - expect(series.rawMeasureFn(0), equals(3)); - expect(series.rawMeasureFn(1), equals(4)); - expect(series.rawMeasureFn(2), equals(5)); - - // Verify fourth series. - series = seriesList[3]; - - expect(series.measureFn(0), equals(10 / 60)); - expect(series.measureFn(1), equals(20 / 90)); - expect(series.measureFn(2), equals(30 / 120)); - - expect(series.rawMeasureFn(0), equals(10)); - expect(series.rawMeasureFn(1), equals(20)); - expect(series.rawMeasureFn(2), equals(30)); - - expect(series.measureLowerBoundFn(0), equals(8 / 60)); - expect(series.measureLowerBoundFn(1), equals(18 / 90)); - expect(series.measureLowerBoundFn(2), equals(28 / 120)); - - expect(series.rawMeasureLowerBoundFn(0), equals(8)); - expect(series.rawMeasureLowerBoundFn(1), equals(18)); - expect(series.rawMeasureLowerBoundFn(2), equals(28)); - - expect(series.measureUpperBoundFn(0), equals(12 / 60)); - expect(series.measureUpperBoundFn(1), equals(22 / 90)); - expect(series.measureUpperBoundFn(2), equals(32 / 120)); - - expect(series.rawMeasureUpperBoundFn(0), equals(12)); - expect(series.rawMeasureUpperBoundFn(1), equals(22)); - expect(series.rawMeasureUpperBoundFn(2), equals(32)); - - // Verify fifth series. - series = seriesList[4]; - - expect(series.measureFn(0), equals(20 / 60)); - expect(series.measureFn(1), equals(30 / 90)); - expect(series.measureFn(2), equals(40 / 120)); - - expect(series.rawMeasureFn(0), equals(20)); - expect(series.rawMeasureFn(1), equals(30)); - expect(series.rawMeasureFn(2), equals(40)); - - expect(series.measureLowerBoundFn(0), equals(18 / 60)); - expect(series.measureLowerBoundFn(1), equals(28 / 90)); - expect(series.measureLowerBoundFn(2), equals(38 / 120)); - - expect(series.rawMeasureLowerBoundFn(0), equals(18)); - expect(series.rawMeasureLowerBoundFn(1), equals(28)); - expect(series.rawMeasureLowerBoundFn(2), equals(38)); - - expect(series.measureUpperBoundFn(0), equals(22 / 60)); - expect(series.measureUpperBoundFn(1), equals(32 / 90)); - expect(series.measureUpperBoundFn(2), equals(42 / 120)); - - expect(series.rawMeasureUpperBoundFn(0), equals(22)); - expect(series.rawMeasureUpperBoundFn(1), equals(32)); - expect(series.rawMeasureUpperBoundFn(2), equals(42)); - - // Verify sixth series. - series = seriesList[5]; - - expect(series.measureFn(0), equals(30 / 60)); - expect(series.measureFn(1), equals(40 / 90)); - expect(series.measureFn(2), equals(50 / 120)); - - expect(series.rawMeasureFn(0), equals(30)); - expect(series.rawMeasureFn(1), equals(40)); - expect(series.rawMeasureFn(2), equals(50)); - - expect(series.measureLowerBoundFn(0), equals(28 / 60)); - expect(series.measureLowerBoundFn(1), equals(38 / 90)); - expect(series.measureLowerBoundFn(2), equals(48 / 120)); - - expect(series.rawMeasureLowerBoundFn(0), equals(28)); - expect(series.rawMeasureLowerBoundFn(1), equals(38)); - expect(series.rawMeasureLowerBoundFn(2), equals(48)); - - expect(series.measureUpperBoundFn(0), equals(32 / 60)); - expect(series.measureUpperBoundFn(1), equals(42 / 90)); - expect(series.measureUpperBoundFn(2), equals(52 / 120)); - - expect(series.rawMeasureUpperBoundFn(0), equals(32)); - expect(series.rawMeasureUpperBoundFn(1), equals(42)); - expect(series.rawMeasureUpperBoundFn(2), equals(52)); - }); - - test('percent of series', () { - // Setup behavior. - _makeBehavior(totalType: PercentInjectorTotalType.series); - - // Act - _chart.lastLifecycleListener.onData(seriesList); - _chart.lastLifecycleListener.onPreprocess(seriesList); - - // Verify that every series has a total measure value. Technically this is - // handled in MutableSeries, but it is a pre-condition for this behavior - // functioning properly. - expect(seriesList[0].seriesMeasureTotal, equals(6)); - expect(seriesList[1].seriesMeasureTotal, equals(9)); - expect(seriesList[2].seriesMeasureTotal, equals(12)); - expect(seriesList[3].seriesMeasureTotal, equals(60)); - expect(seriesList[4].seriesMeasureTotal, equals(90)); - expect(seriesList[5].seriesMeasureTotal, equals(120)); - - // Verify first series. - var series = seriesList[0]; - - expect(series.measureFn(0), equals(1 / 6)); - expect(series.measureFn(1), equals(2 / 6)); - expect(series.measureFn(2), equals(3 / 6)); - - expect(series.rawMeasureFn(0), equals(1)); - expect(series.rawMeasureFn(1), equals(2)); - expect(series.rawMeasureFn(2), equals(3)); - - // Verify second series. - series = seriesList[1]; - - expect(series.measureFn(0), equals(2 / 9)); - expect(series.measureFn(1), equals(3 / 9)); - expect(series.measureFn(2), equals(4 / 9)); - - expect(series.rawMeasureFn(0), equals(2)); - expect(series.rawMeasureFn(1), equals(3)); - expect(series.rawMeasureFn(2), equals(4)); - - // Verify third series. - series = seriesList[2]; - - expect(series.measureFn(0), equals(3 / 12)); - expect(series.measureFn(1), equals(4 / 12)); - expect(series.measureFn(2), equals(5 / 12)); - - expect(series.rawMeasureFn(0), equals(3)); - expect(series.rawMeasureFn(1), equals(4)); - expect(series.rawMeasureFn(2), equals(5)); - - // Verify fourth series. - series = seriesList[3]; - - expect(series.measureFn(0), equals(10 / 60)); - expect(series.measureFn(1), equals(20 / 60)); - expect(series.measureFn(2), equals(30 / 60)); - - expect(series.rawMeasureFn(0), equals(10)); - expect(series.rawMeasureFn(1), equals(20)); - expect(series.rawMeasureFn(2), equals(30)); - - expect(series.measureLowerBoundFn(0), equals(8 / 60)); - expect(series.measureLowerBoundFn(1), equals(18 / 60)); - expect(series.measureLowerBoundFn(2), equals(28 / 60)); - - expect(series.rawMeasureLowerBoundFn(0), equals(8)); - expect(series.rawMeasureLowerBoundFn(1), equals(18)); - expect(series.rawMeasureLowerBoundFn(2), equals(28)); - - expect(series.measureUpperBoundFn(0), equals(12 / 60)); - expect(series.measureUpperBoundFn(1), equals(22 / 60)); - expect(series.measureUpperBoundFn(2), equals(32 / 60)); - - expect(series.rawMeasureUpperBoundFn(0), equals(12)); - expect(series.rawMeasureUpperBoundFn(1), equals(22)); - expect(series.rawMeasureUpperBoundFn(2), equals(32)); - - // Verify fifth series. - series = seriesList[4]; - - expect(series.measureFn(0), equals(20 / 90)); - expect(series.measureFn(1), equals(30 / 90)); - expect(series.measureFn(2), equals(40 / 90)); - - expect(series.rawMeasureFn(0), equals(20)); - expect(series.rawMeasureFn(1), equals(30)); - expect(series.rawMeasureFn(2), equals(40)); - - expect(series.measureLowerBoundFn(0), equals(18 / 90)); - expect(series.measureLowerBoundFn(1), equals(28 / 90)); - expect(series.measureLowerBoundFn(2), equals(38 / 90)); - - expect(series.rawMeasureLowerBoundFn(0), equals(18)); - expect(series.rawMeasureLowerBoundFn(1), equals(28)); - expect(series.rawMeasureLowerBoundFn(2), equals(38)); - - expect(series.measureUpperBoundFn(0), equals(22 / 90)); - expect(series.measureUpperBoundFn(1), equals(32 / 90)); - expect(series.measureUpperBoundFn(2), equals(42 / 90)); - - expect(series.rawMeasureUpperBoundFn(0), equals(22)); - expect(series.rawMeasureUpperBoundFn(1), equals(32)); - expect(series.rawMeasureUpperBoundFn(2), equals(42)); - - // Verify sixth series. - series = seriesList[5]; - - expect(series.measureFn(0), equals(30 / 120)); - expect(series.measureFn(1), equals(40 / 120)); - expect(series.measureFn(2), equals(50 / 120)); - - expect(series.rawMeasureFn(0), equals(30)); - expect(series.rawMeasureFn(1), equals(40)); - expect(series.rawMeasureFn(2), equals(50)); - - expect(series.measureLowerBoundFn(0), equals(28 / 120)); - expect(series.measureLowerBoundFn(1), equals(38 / 120)); - expect(series.measureLowerBoundFn(2), equals(48 / 120)); - - expect(series.rawMeasureLowerBoundFn(0), equals(28)); - expect(series.rawMeasureLowerBoundFn(1), equals(38)); - expect(series.rawMeasureLowerBoundFn(2), equals(48)); - - expect(series.measureUpperBoundFn(0), equals(32 / 120)); - expect(series.measureUpperBoundFn(1), equals(42 / 120)); - expect(series.measureUpperBoundFn(2), equals(52 / 120)); - - expect(series.rawMeasureUpperBoundFn(0), equals(32)); - expect(series.rawMeasureUpperBoundFn(1), equals(42)); - expect(series.rawMeasureUpperBoundFn(2), equals(52)); - }); - }); - - group('Life cycle', () { - test('sets injected flag for percent of domain', () { - // Setup behavior. - _makeBehavior(totalType: PercentInjectorTotalType.domain); - - // Act - _chart.lastLifecycleListener.onData(seriesList); - - // Verify that each series has an initially false flag. - expect(seriesList[0].getAttr(percentInjectedKey), isFalse); - expect(seriesList[1].getAttr(percentInjectedKey), isFalse); - expect(seriesList[2].getAttr(percentInjectedKey), isFalse); - expect(seriesList[3].getAttr(percentInjectedKey), isFalse); - expect(seriesList[4].getAttr(percentInjectedKey), isFalse); - expect(seriesList[5].getAttr(percentInjectedKey), isFalse); - - // Act - _chart.lastLifecycleListener.onPreprocess(seriesList); - - // Verify that each series has a true flag. - expect(seriesList[0].getAttr(percentInjectedKey), isTrue); - expect(seriesList[1].getAttr(percentInjectedKey), isTrue); - expect(seriesList[2].getAttr(percentInjectedKey), isTrue); - expect(seriesList[3].getAttr(percentInjectedKey), isTrue); - expect(seriesList[4].getAttr(percentInjectedKey), isTrue); - expect(seriesList[5].getAttr(percentInjectedKey), isTrue); - }); - - test('sets injected flag for percent of series', () { - // Setup behavior. - _makeBehavior(totalType: PercentInjectorTotalType.series); - - // Act - _chart.lastLifecycleListener.onData(seriesList); - - // Verify that each series has an initially false flag. - expect(seriesList[0].getAttr(percentInjectedKey), isFalse); - expect(seriesList[1].getAttr(percentInjectedKey), isFalse); - expect(seriesList[2].getAttr(percentInjectedKey), isFalse); - expect(seriesList[3].getAttr(percentInjectedKey), isFalse); - expect(seriesList[4].getAttr(percentInjectedKey), isFalse); - expect(seriesList[5].getAttr(percentInjectedKey), isFalse); - - // Act - _chart.lastLifecycleListener.onPreprocess(seriesList); - - // Verify that each series has a true flag. - expect(seriesList[0].getAttr(percentInjectedKey), isTrue); - expect(seriesList[1].getAttr(percentInjectedKey), isTrue); - expect(seriesList[2].getAttr(percentInjectedKey), isTrue); - expect(seriesList[3].getAttr(percentInjectedKey), isTrue); - expect(seriesList[4].getAttr(percentInjectedKey), isTrue); - expect(seriesList[5].getAttr(percentInjectedKey), isTrue); - }); - }); -} diff --git a/web/charts/common/test/chart/common/behavior/chart_behavior_test.dart b/web/charts/common/test/chart/common/behavior/chart_behavior_test.dart deleted file mode 100644 index e343c3edd..000000000 --- a/web/charts/common/test/chart/common/behavior/chart_behavior_test.dart +++ /dev/null @@ -1,153 +0,0 @@ -// 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 'package:charts_common/src/chart/common/series_renderer.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/behavior/chart_behavior.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; - -import 'package:test/test.dart'; - -class MockBehavior extends Mock implements ChartBehavior {} - -class ParentBehavior implements ChartBehavior { - final ChartBehavior child; - - ParentBehavior(this.child); - - String get role => null; - - @override - void attachTo(BaseChart chart) { - chart.addBehavior(child); - } - - @override - void removeFrom(BaseChart chart) { - chart.removeBehavior(child); - } -} - -class ConcreteChart extends BaseChart { - @override - SeriesRenderer makeDefaultRenderer() => null; - - @override - List> getDatumDetails(SelectionModelType _) => null; -} - -void main() { - ConcreteChart chart; - MockBehavior namedBehavior; - MockBehavior unnamedBehavior; - - setUp(() { - chart = ConcreteChart(); - - namedBehavior = MockBehavior(); - when(namedBehavior.role).thenReturn('foo'); - - unnamedBehavior = MockBehavior(); - when(unnamedBehavior.role).thenReturn(null); - }); - - group('Attach & Detach', () { - test('attach is called once', () { - chart.addBehavior(namedBehavior); - verify(namedBehavior.attachTo(chart)).called(1); - - verify(namedBehavior.role); - verifyNoMoreInteractions(namedBehavior); - }); - - test('deteach is called once', () { - chart.addBehavior(namedBehavior); - verify(namedBehavior.attachTo(chart)).called(1); - - chart.removeBehavior(namedBehavior); - verify(namedBehavior.removeFrom(chart)).called(1); - - verify(namedBehavior.role); - verifyNoMoreInteractions(namedBehavior); - }); - - test('detach is called when name is reused', () { - final otherBehavior = MockBehavior(); - when(otherBehavior.role).thenReturn('foo'); - - chart.addBehavior(namedBehavior); - verify(namedBehavior.attachTo(chart)).called(1); - - chart.addBehavior(otherBehavior); - verify(namedBehavior.removeFrom(chart)).called(1); - verify(otherBehavior.attachTo(chart)).called(1); - - verify(namedBehavior.role); - verify(otherBehavior.role); - verifyNoMoreInteractions(namedBehavior); - verifyNoMoreInteractions(otherBehavior); - }); - - test('detach is not called when name is null', () { - chart.addBehavior(namedBehavior); - verify(namedBehavior.attachTo(chart)).called(1); - - chart.addBehavior(unnamedBehavior); - verify(unnamedBehavior.attachTo(chart)).called(1); - - verify(namedBehavior.role); - verify(unnamedBehavior.role); - verifyNoMoreInteractions(namedBehavior); - verifyNoMoreInteractions(unnamedBehavior); - }); - - test('detach is not called when name is different', () { - final otherBehavior = MockBehavior(); - when(otherBehavior.role).thenReturn('bar'); - - chart.addBehavior(namedBehavior); - verify(namedBehavior.attachTo(chart)).called(1); - - chart.addBehavior(otherBehavior); - verify(otherBehavior.attachTo(chart)).called(1); - - verify(namedBehavior.role); - verify(otherBehavior.role); - verifyNoMoreInteractions(namedBehavior); - verifyNoMoreInteractions(otherBehavior); - }); - - test('behaviors are removed when chart is destroyed', () { - final parentBehavior = ParentBehavior(unnamedBehavior); - - chart.addBehavior(parentBehavior); - // The parent should add the child behavoir. - verify(unnamedBehavior.attachTo(chart)).called(1); - - chart.destroy(); - - // The parent should remove the child behavior. - verify(unnamedBehavior.removeFrom(chart)).called(1); - - // Remove should only be called once and shouldn't trigger a concurrent - // modification exception. - verify(unnamedBehavior.role); - verifyNoMoreInteractions(unnamedBehavior); - }); - }); -} diff --git a/web/charts/common/test/chart/common/behavior/domain_highlighter_test.dart b/web/charts/common/test/chart/common/behavior/domain_highlighter_test.dart deleted file mode 100644 index 4a7499a25..000000000 --- a/web/charts/common/test/chart/common/behavior/domain_highlighter_test.dart +++ /dev/null @@ -1,185 +0,0 @@ -// 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 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/behavior/domain_highlighter.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/common/material_palette.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockChart extends Mock implements BaseChart { - LifecycleListener lastListener; - - @override - addLifecycleListener(LifecycleListener listener) => lastListener = listener; - - @override - removeLifecycleListener(LifecycleListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - return true; - } -} - -class MockSelectionModel extends Mock implements MutableSelectionModel { - SelectionModelListener lastListener; - - @override - addSelectionChangedListener(SelectionModelListener listener) => - lastListener = listener; - - @override - removeSelectionChangedListener(SelectionModelListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - } -} - -void main() { - MockChart _chart; - MockSelectionModel _selectionModel; - - MutableSeries _series1; - final _s1D1 = MyRow('s1d1', 11); - final _s1D2 = MyRow('s1d2', 12); - final _s1D3 = MyRow('s1d3', 13); - - MutableSeries _series2; - final _s2D1 = MyRow('s2d1', 21); - final _s2D2 = MyRow('s2d2', 22); - final _s2D3 = MyRow('s2d3', 23); - - _setupSelection(List selected) { - for (var i = 0; i < _series1.data.length; i++) { - when(_selectionModel.isDatumSelected(_series1, i)) - .thenReturn(selected.contains(_series1.data[i])); - } - for (var i = 0; i < _series2.data.length; i++) { - when(_selectionModel.isDatumSelected(_series2, i)) - .thenReturn(selected.contains(_series2.data[i])); - } - } - - setUp(() { - _chart = MockChart(); - - _selectionModel = MockSelectionModel(); - when(_chart.getSelectionModel(SelectionModelType.info)) - .thenReturn(_selectionModel); - - _series1 = MutableSeries(Series( - id: 's1', - data: [_s1D1, _s1D2, _s1D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - colorFn: (_, __) => MaterialPalette.blue.shadeDefault)) - ..measureFn = (_) => 0.0; - - _series2 = MutableSeries(Series( - id: 's2', - data: [_s2D1, _s2D2, _s2D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - colorFn: (_, __) => MaterialPalette.red.shadeDefault)) - ..measureFn = (_) => 0.0; - }); - - group('DomainHighligher', () { - test('darkens the selected bars', () { - // Setup - final behavior = DomainHighlighter(SelectionModelType.info); - behavior.attachTo(_chart); - _setupSelection([_s1D2, _s2D2]); - final seriesList = [_series1, _series2]; - - // Act - _selectionModel.lastListener(_selectionModel); - verify(_chart.redraw(skipAnimation: true, skipLayout: true)); - _chart.lastListener.onPostprocess(seriesList); - - // Verify - final s1ColorFn = _series1.colorFn; - expect(s1ColorFn(0), equals(MaterialPalette.blue.shadeDefault)); - expect(s1ColorFn(1), equals(MaterialPalette.blue.shadeDefault.darker)); - expect(s1ColorFn(2), equals(MaterialPalette.blue.shadeDefault)); - - final s2ColorFn = _series2.colorFn; - expect(s2ColorFn(0), equals(MaterialPalette.red.shadeDefault)); - expect(s2ColorFn(1), equals(MaterialPalette.red.shadeDefault.darker)); - expect(s2ColorFn(2), equals(MaterialPalette.red.shadeDefault)); - }); - - test('listens to other selection models', () { - // Setup - final behavior = DomainHighlighter(SelectionModelType.action); - when(_chart.getSelectionModel(SelectionModelType.action)) - .thenReturn(_selectionModel); - - // Act - behavior.attachTo(_chart); - - // Verify - verify(_chart.getSelectionModel(SelectionModelType.action)); - verifyNever(_chart.getSelectionModel(SelectionModelType.info)); - }); - - test('leaves everything alone with no selection', () { - // Setup - final behavior = DomainHighlighter(SelectionModelType.info); - behavior.attachTo(_chart); - _setupSelection([]); - final seriesList = [_series1, _series2]; - - // Act - _selectionModel.lastListener(_selectionModel); - verify(_chart.redraw(skipAnimation: true, skipLayout: true)); - _chart.lastListener.onPostprocess(seriesList); - - // Verify - final s1ColorFn = _series1.colorFn; - expect(s1ColorFn(0), equals(MaterialPalette.blue.shadeDefault)); - expect(s1ColorFn(1), equals(MaterialPalette.blue.shadeDefault)); - expect(s1ColorFn(2), equals(MaterialPalette.blue.shadeDefault)); - - final s2ColorFn = _series2.colorFn; - expect(s2ColorFn(0), equals(MaterialPalette.red.shadeDefault)); - expect(s2ColorFn(1), equals(MaterialPalette.red.shadeDefault)); - expect(s2ColorFn(2), equals(MaterialPalette.red.shadeDefault)); - }); - - test('cleans up', () { - // Setup - final behavior = DomainHighlighter(SelectionModelType.info); - behavior.attachTo(_chart); - _setupSelection([_s1D2, _s2D2]); - - // Act - behavior.removeFrom(_chart); - - // Verify - expect(_chart.lastListener, isNull); - expect(_selectionModel.lastListener, isNull); - }); - }); -} - -class MyRow { - final String campaign; - final int count; - MyRow(this.campaign, this.count); -} diff --git a/web/charts/common/test/chart/common/behavior/initial_selection_test.dart b/web/charts/common/test/chart/common/behavior/initial_selection_test.dart deleted file mode 100644 index 24ece386c..000000000 --- a/web/charts/common/test/chart/common/behavior/initial_selection_test.dart +++ /dev/null @@ -1,212 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/behavior/initial_selection.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/chart/common/series_datum.dart'; -import 'package:charts_common/src/chart/common/series_renderer.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:test/test.dart'; - -class FakeRenderer extends BaseSeriesRenderer { - @override - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - return null; - } - - @override - List getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride) { - return null; - } - - @override - void paint(ChartCanvas canvas, double animationPercent) {} - - @override - void update(List seriesList, bool isAnimating) {} -} - -class FakeChart extends BaseChart { - @override - List getDatumDetails(SelectionModelType type) => []; - - @override - SeriesRenderer makeDefaultRenderer() => FakeRenderer(); - - void requestOnDraw(List seriesList) { - fireOnDraw(seriesList); - } -} - -void main() { - FakeChart _chart; - ImmutableSeries _series1; - ImmutableSeries _series2; - ImmutableSeries _series3; - ImmutableSeries _series4; - final infoSelectionType = SelectionModelType.info; - - InitialSelection _makeBehavior(SelectionModelType selectionModelType, - {List selectedSeries, List selectedData}) { - InitialSelection behavior = InitialSelection( - selectionModelType: selectionModelType, - selectedSeriesConfig: selectedSeries, - selectedDataConfig: selectedData); - - behavior.attachTo(_chart); - - return behavior; - } - - setUp(() { - _chart = FakeChart(); - - _series1 = MutableSeries(Series( - id: 'mySeries1', - data: ['A', 'B', 'C', 'D'], - domainFn: (dynamic datum, __) => datum, - measureFn: (_, __) => 0)); - - _series2 = MutableSeries(Series( - id: 'mySeries2', - data: ['W', 'X', 'Y', 'Z'], - domainFn: (dynamic datum, __) => datum, - measureFn: (_, __) => 0)); - - _series3 = MutableSeries(Series( - id: 'mySeries3', - data: ['W', 'X', 'Y', 'Z'], - domainFn: (dynamic datum, __) => datum, - measureFn: (_, __) => 0)); - - _series4 = MutableSeries(Series( - id: 'mySeries4', - data: ['W', 'X', 'Y', 'Z'], - domainFn: (dynamic datum, __) => datum, - measureFn: (_, __) => 0)); - }); - - test('selects initial datum', () { - _makeBehavior(infoSelectionType, - selectedData: [SeriesDatumConfig('mySeries1', 'C')]); - - _chart.requestOnDraw([_series1, _series2]); - - final model = _chart.getSelectionModel(infoSelectionType); - - expect(model.selectedSeries, hasLength(1)); - expect(model.selectedSeries[0], equals(_series1)); - expect(model.selectedDatum, hasLength(1)); - expect(model.selectedDatum[0].series, equals(_series1)); - expect(model.selectedDatum[0].datum, equals('C')); - }); - - test('selects multiple initial data', () { - _makeBehavior(infoSelectionType, selectedData: [ - SeriesDatumConfig('mySeries1', 'C'), - SeriesDatumConfig('mySeries1', 'D') - ]); - - _chart.requestOnDraw([_series1, _series2]); - - final model = _chart.getSelectionModel(infoSelectionType); - - expect(model.selectedSeries, hasLength(1)); - expect(model.selectedSeries[0], equals(_series1)); - expect(model.selectedDatum, hasLength(2)); - expect(model.selectedDatum[0].series, equals(_series1)); - expect(model.selectedDatum[0].datum, equals('C')); - expect(model.selectedDatum[1].series, equals(_series1)); - expect(model.selectedDatum[1].datum, equals('D')); - }); - - test('selects initial series', () { - _makeBehavior(infoSelectionType, selectedSeries: ['mySeries2']); - - _chart.requestOnDraw([_series1, _series2, _series3, _series4]); - - final model = _chart.getSelectionModel(infoSelectionType); - - expect(model.selectedSeries, hasLength(1)); - expect(model.selectedSeries[0], equals(_series2)); - expect(model.selectedDatum, isEmpty); - }); - - test('selects multiple series', () { - _makeBehavior(infoSelectionType, - selectedSeries: ['mySeries2', 'mySeries4']); - - _chart.requestOnDraw([_series1, _series2, _series3, _series4]); - - final model = _chart.getSelectionModel(infoSelectionType); - - expect(model.selectedSeries, hasLength(2)); - expect(model.selectedSeries[0], equals(_series2)); - expect(model.selectedSeries[1], equals(_series4)); - expect(model.selectedDatum, isEmpty); - }); - - test('selects series and datum', () { - _makeBehavior(infoSelectionType, - selectedData: [SeriesDatumConfig('mySeries1', 'C')], - selectedSeries: ['mySeries4']); - - _chart.requestOnDraw([_series1, _series2, _series3, _series4]); - - final model = _chart.getSelectionModel(infoSelectionType); - - expect(model.selectedSeries, hasLength(2)); - expect(model.selectedSeries[0], equals(_series1)); - expect(model.selectedSeries[1], equals(_series4)); - expect(model.selectedDatum[0].series, equals(_series1)); - expect(model.selectedDatum[0].datum, equals('C')); - }); - - test('selection model is reset when a new series is drawn', () { - _makeBehavior(infoSelectionType, selectedSeries: ['mySeries2']); - - _chart.requestOnDraw([_series1, _series2, _series3, _series4]); - - final model = _chart.getSelectionModel(infoSelectionType); - - // Verify initial selection is selected on first draw - expect(model.selectedSeries, hasLength(1)); - expect(model.selectedSeries[0], equals(_series2)); - expect(model.selectedDatum, isEmpty); - - // Request a draw with a new series list. - _chart.draw( - [ - Series( - id: 'mySeries2', - data: ['W', 'X', 'Y', 'Z'], - domainFn: (dynamic datum, __) => datum, - measureFn: (_, __) => 0) - ], - ); - - // Verify selection is cleared. - expect(model.selectedSeries, isEmpty); - expect(model.selectedDatum, isEmpty); - }); -} diff --git a/web/charts/common/test/chart/common/behavior/line_point_highlighter_test.dart b/web/charts/common/test/chart/common/behavior/line_point_highlighter_test.dart deleted file mode 100644 index b762f6893..000000000 --- a/web/charts/common/test/chart/common/behavior/line_point_highlighter_test.dart +++ /dev/null @@ -1,269 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/behavior/line_point_highlighter.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/series_datum.dart'; -import 'package:charts_common/src/chart/common/series_renderer.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/common/material_palette.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockChart extends Mock implements CartesianChart { - LifecycleListener lastListener; - - @override - addLifecycleListener(LifecycleListener listener) => lastListener = listener; - - @override - removeLifecycleListener(LifecycleListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - return true; - } - - @override - bool get vertical => true; -} - -class MockSelectionModel extends Mock implements MutableSelectionModel { - SelectionModelListener lastListener; - - @override - addSelectionChangedListener(SelectionModelListener listener) => - lastListener = listener; - - @override - removeSelectionChangedListener(SelectionModelListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - } -} - -class MockNumericAxis extends Mock implements NumericAxis { - @override - getLocation(num domain) { - return 10.0; - } -} - -class MockSeriesRenderer extends BaseSeriesRenderer { - @override - void update(_, __) {} - - @override - void paint(_, __) {} - - List getNearestDatumDetailPerSeries( - Point chartPoint, bool byDomain, Rectangle boundsOverride) { - return null; - } - - DatumDetails addPositionToDetailsForSeriesDatum( - DatumDetails details, SeriesDatum seriesDatum) { - return DatumDetails.from(details, chartPosition: Point(0.0, 0.0)); - } -} - -void main() { - MockChart _chart; - MockSelectionModel _selectionModel; - MockSeriesRenderer _seriesRenderer; - - MutableSeries _series1; - final _s1D1 = MyRow(1, 11); - final _s1D2 = MyRow(2, 12); - final _s1D3 = MyRow(3, 13); - - MutableSeries _series2; - final _s2D1 = MyRow(4, 21); - final _s2D2 = MyRow(5, 22); - final _s2D3 = MyRow(6, 23); - - List _mockGetSelectedDatumDetails(List selection) { - final details = []; - - for (SeriesDatum seriesDatum in selection) { - details.add(_seriesRenderer.getDetailsForSeriesDatum(seriesDatum)); - } - - return details; - } - - _setupSelection(List selection) { - final selected = []; - - for (var i = 0; i < selection.length; i++) { - selected.add(selection[0].datum); - } - - for (int i = 0; i < _series1.data.length; i++) { - when(_selectionModel.isDatumSelected(_series1, i)) - .thenReturn(selected.contains(_series1.data[i])); - } - for (int i = 0; i < _series2.data.length; i++) { - when(_selectionModel.isDatumSelected(_series2, i)) - .thenReturn(selected.contains(_series2.data[i])); - } - - when(_selectionModel.selectedDatum).thenReturn(selection); - - final selectedDetails = _mockGetSelectedDatumDetails(selection); - - when(_chart.getSelectedDatumDetails(SelectionModelType.info)) - .thenReturn(selectedDetails); - } - - setUp(() { - _chart = MockChart(); - - _seriesRenderer = MockSeriesRenderer(); - - _selectionModel = MockSelectionModel(); - when(_chart.getSelectionModel(SelectionModelType.info)) - .thenReturn(_selectionModel); - - _series1 = MutableSeries(Series( - id: 's1', - data: [_s1D1, _s1D2, _s1D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - colorFn: (_, __) => MaterialPalette.blue.shadeDefault)) - ..measureFn = (_) => 0.0; - - _series2 = MutableSeries(Series( - id: 's2', - data: [_s2D1, _s2D2, _s2D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - colorFn: (_, __) => MaterialPalette.red.shadeDefault)) - ..measureFn = (_) => 0.0; - }); - - group('LinePointHighlighter', () { - test('highlights the selected points', () { - // Setup - final behavior = - LinePointHighlighter(selectionModelType: SelectionModelType.info); - final tester = LinePointHighlighterTester(behavior); - behavior.attachTo(_chart); - _setupSelection([ - SeriesDatum(_series1, _s1D2), - SeriesDatum(_series2, _s2D2), - ]); - - // Mock axes for returning fake domain locations. - Axis domainAxis = MockNumericAxis(); - Axis primaryMeasureAxis = MockNumericAxis(); - - _series1.setAttr(domainAxisKey, domainAxis); - _series1.setAttr(measureAxisKey, primaryMeasureAxis); - _series1.measureOffsetFn = (_) => 0.0; - - _series2.setAttr(domainAxisKey, domainAxis); - _series2.setAttr(measureAxisKey, primaryMeasureAxis); - _series2.measureOffsetFn = (_) => 0.0; - - // Act - _selectionModel.lastListener(_selectionModel); - verify(_chart.redraw(skipAnimation: true, skipLayout: true)); - - _chart.lastListener.onAxisConfigured(); - - // Verify - expect(tester.getSelectionLength(), equals(2)); - - expect(tester.isDatumSelected(_series1.data[0]), equals(false)); - expect(tester.isDatumSelected(_series1.data[1]), equals(true)); - expect(tester.isDatumSelected(_series1.data[2]), equals(false)); - - expect(tester.isDatumSelected(_series2.data[0]), equals(false)); - expect(tester.isDatumSelected(_series2.data[1]), equals(true)); - expect(tester.isDatumSelected(_series2.data[2]), equals(false)); - }); - - test('listens to other selection models', () { - // Setup - final behavior = - LinePointHighlighter(selectionModelType: SelectionModelType.action); - when(_chart.getSelectionModel(SelectionModelType.action)) - .thenReturn(_selectionModel); - - // Act - behavior.attachTo(_chart); - - // Verify - verify(_chart.getSelectionModel(SelectionModelType.action)); - verifyNever(_chart.getSelectionModel(SelectionModelType.info)); - }); - - test('leaves everything alone with no selection', () { - // Setup - final behavior = - LinePointHighlighter(selectionModelType: SelectionModelType.info); - final tester = LinePointHighlighterTester(behavior); - behavior.attachTo(_chart); - _setupSelection([]); - - // Act - _selectionModel.lastListener(_selectionModel); - verify(_chart.redraw(skipAnimation: true, skipLayout: true)); - _chart.lastListener.onAxisConfigured(); - - // Verify - expect(tester.getSelectionLength(), equals(0)); - - expect(tester.isDatumSelected(_series1.data[0]), equals(false)); - expect(tester.isDatumSelected(_series1.data[1]), equals(false)); - expect(tester.isDatumSelected(_series1.data[2]), equals(false)); - - expect(tester.isDatumSelected(_series2.data[0]), equals(false)); - expect(tester.isDatumSelected(_series2.data[1]), equals(false)); - expect(tester.isDatumSelected(_series2.data[2]), equals(false)); - }); - - test('cleans up', () { - // Setup - final behavior = - LinePointHighlighter(selectionModelType: SelectionModelType.info); - behavior.attachTo(_chart); - _setupSelection([ - SeriesDatum(_series1, _s1D2), - SeriesDatum(_series2, _s2D2), - ]); - - // Act - behavior.removeFrom(_chart); - - // Verify - expect(_chart.lastListener, isNull); - expect(_selectionModel.lastListener, isNull); - }); - }); -} - -class MyRow { - final int campaign; - final int count; - MyRow(this.campaign, this.count); -} diff --git a/web/charts/common/test/chart/common/behavior/range_annotation_test.dart b/web/charts/common/test/chart/common/behavior/range_annotation_test.dart deleted file mode 100644 index 309be54f1..000000000 --- a/web/charts/common/test/chart/common/behavior/range_annotation_test.dart +++ /dev/null @@ -1,351 +0,0 @@ -// 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:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/axis/numeric_tick_provider.dart'; -import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; -import 'package:charts_common/src/chart/cartesian/axis/linear/linear_scale.dart'; -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/chart_context.dart'; -import 'package:charts_common/src/chart/common/behavior/range_annotation.dart'; -import 'package:charts_common/src/chart/line/line_chart.dart'; -import 'package:charts_common/src/common/material_palette.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockContext extends Mock implements ChartContext {} - -class ConcreteChart extends LineChart { - LifecycleListener lastListener; - - final _domainAxis = ConcreteNumericAxis(); - - final _primaryMeasureAxis = ConcreteNumericAxis(); - - @override - addLifecycleListener(LifecycleListener listener) { - lastListener = listener; - return super.addLifecycleListener(listener); - } - - @override - removeLifecycleListener(LifecycleListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - return super.removeLifecycleListener(listener); - } - - @override - Axis get domainAxis => _domainAxis; - - @override - Axis getMeasureAxis({String axisId}) => _primaryMeasureAxis; -} - -class ConcreteNumericAxis extends Axis { - ConcreteNumericAxis() - : super( - tickProvider: MockTickProvider(), - tickFormatter: NumericTickFormatter(), - scale: LinearScale(), - ); -} - -class MockTickProvider extends Mock implements NumericTickProvider {} - -void main() { - Rectangle drawBounds; - Rectangle domainAxisBounds; - Rectangle measureAxisBounds; - - ConcreteChart _chart; - - Series _series1; - final _s1D1 = MyRow(0, 11); - final _s1D2 = MyRow(1, 12); - final _s1D3 = MyRow(2, 13); - - Series _series2; - final _s2D1 = MyRow(3, 21); - final _s2D2 = MyRow(4, 22); - final _s2D3 = MyRow(5, 23); - - const _dashPattern = [2, 3]; - - List> _annotations1; - - List> _annotations2; - - List> _annotations3; - - ConcreteChart _makeChart() { - final chart = ConcreteChart(); - - final context = MockContext(); - when(context.chartContainerIsRtl).thenReturn(false); - when(context.isRtl).thenReturn(false); - chart.context = context; - - return chart; - } - - /// Initializes the [chart], draws the [seriesList], and configures mock axis - /// layout bounds. - _drawSeriesList(ConcreteChart chart, List> seriesList) { - _chart.domainAxis.autoViewport = true; - _chart.domainAxis.resetDomains(); - - _chart.getMeasureAxis().autoViewport = true; - _chart.getMeasureAxis().resetDomains(); - - _chart.draw(seriesList); - - _chart.domainAxis.layout(domainAxisBounds, drawBounds); - - _chart.getMeasureAxis().layout(measureAxisBounds, drawBounds); - - _chart.lastListener.onAxisConfigured(); - } - - setUpAll(() { - drawBounds = Rectangle(0, 0, 100, 100); - domainAxisBounds = Rectangle(0, 0, 100, 100); - measureAxisBounds = Rectangle(0, 0, 100, 100); - }); - - setUp(() { - _chart = _makeChart(); - - _series1 = Series( - id: 's1', - data: [_s1D1, _s1D2, _s1D3], - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.count, - colorFn: (_, __) => MaterialPalette.blue.shadeDefault); - - _series2 = Series( - id: 's2', - data: [_s2D1, _s2D2, _s2D3], - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.count, - colorFn: (_, __) => MaterialPalette.red.shadeDefault); - - _annotations1 = [ - RangeAnnotationSegment(1, 2, RangeAnnotationAxisType.domain, - startLabel: 'Ann 1'), - RangeAnnotationSegment(4, 5, RangeAnnotationAxisType.domain, - color: MaterialPalette.gray.shade200, endLabel: 'Ann 2'), - RangeAnnotationSegment(5, 5.5, RangeAnnotationAxisType.measure, - startLabel: 'Really long tick start label', - endLabel: 'Really long tick end label'), - RangeAnnotationSegment(10, 15, RangeAnnotationAxisType.measure, - startLabel: 'Ann 4 Start', endLabel: 'Ann 4 End'), - RangeAnnotationSegment(16, 22, RangeAnnotationAxisType.measure, - startLabel: 'Ann 5 Start', endLabel: 'Ann 5 End'), - ]; - - _annotations2 = [ - RangeAnnotationSegment(1, 2, RangeAnnotationAxisType.domain), - RangeAnnotationSegment(4, 5, RangeAnnotationAxisType.domain, - color: MaterialPalette.gray.shade200), - RangeAnnotationSegment(8, 10, RangeAnnotationAxisType.domain, - color: MaterialPalette.gray.shade300), - ]; - - _annotations3 = [ - LineAnnotationSegment(1, RangeAnnotationAxisType.measure, - startLabel: 'Ann 1 Start', endLabel: 'Ann 1 End'), - LineAnnotationSegment(4, RangeAnnotationAxisType.measure, - startLabel: 'Ann 2 Start', - endLabel: 'Ann 2 End', - color: MaterialPalette.gray.shade200, - dashPattern: _dashPattern), - ]; - }); - - group('RangeAnnotation', () { - test('renders the annotations', () { - // Setup - final behavior = RangeAnnotation(_annotations1); - final tester = RangeAnnotationTester(behavior); - behavior.attachTo(_chart); - - final seriesList = [_series1, _series2]; - - // Act - _drawSeriesList(_chart, seriesList); - - // Verify - expect(_chart.domainAxis.getLocation(2), equals(40.0)); - expect( - tester.doesAnnotationExist( - startPosition: 20.0, - endPosition: 40.0, - color: MaterialPalette.gray.shade100, - startLabel: 'Ann 1', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.vertical, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - expect( - tester.doesAnnotationExist( - startPosition: 80.0, - endPosition: 100.0, - color: MaterialPalette.gray.shade200, - endLabel: 'Ann 2', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.vertical, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - - // Verify measure annotations - expect(_chart.getMeasureAxis().getLocation(11).round(), equals(33)); - expect( - tester.doesAnnotationExist( - startPosition: 0.0, - endPosition: 2.78, - color: MaterialPalette.gray.shade100, - startLabel: 'Really long tick start label', - endLabel: 'Really long tick end label', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.horizontal, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - expect( - tester.doesAnnotationExist( - startPosition: 27.78, - endPosition: 55.56, - color: MaterialPalette.gray.shade100, - startLabel: 'Ann 4 Start', - endLabel: 'Ann 4 End', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.horizontal, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - expect( - tester.doesAnnotationExist( - startPosition: 61.11, - endPosition: 94.44, - color: MaterialPalette.gray.shade100, - startLabel: 'Ann 5 Start', - endLabel: 'Ann 5 End', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.horizontal, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - }); - - test('extends the domain axis when annotations fall outside the range', () { - // Setup - final behavior = RangeAnnotation(_annotations2); - final tester = RangeAnnotationTester(behavior); - behavior.attachTo(_chart); - - final seriesList = [_series1, _series2]; - - // Act - _drawSeriesList(_chart, seriesList); - - // Verify - expect(_chart.domainAxis.getLocation(2), equals(20.0)); - expect( - tester.doesAnnotationExist( - startPosition: 10.0, - endPosition: 20.0, - color: MaterialPalette.gray.shade100, - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.vertical, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - expect( - tester.doesAnnotationExist( - startPosition: 40.0, - endPosition: 50.0, - color: MaterialPalette.gray.shade200, - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.vertical, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - expect( - tester.doesAnnotationExist( - startPosition: 80.0, - endPosition: 100.0, - color: MaterialPalette.gray.shade300, - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.vertical, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - }); - - test('test dash pattern equality', () { - // Setup - final behavior = RangeAnnotation(_annotations3); - final tester = RangeAnnotationTester(behavior); - behavior.attachTo(_chart); - - final seriesList = [_series1, _series2]; - - // Act - _drawSeriesList(_chart, seriesList); - - // Verify - expect(_chart.domainAxis.getLocation(2), equals(40.0)); - expect( - tester.doesAnnotationExist( - startPosition: 0.0, - endPosition: 0.0, - color: MaterialPalette.gray.shade100, - startLabel: 'Ann 1 Start', - endLabel: 'Ann 1 End', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.horizontal, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - expect( - tester.doesAnnotationExist( - startPosition: 13.64, - endPosition: 13.64, - color: MaterialPalette.gray.shade200, - dashPattern: _dashPattern, - startLabel: 'Ann 2 Start', - endLabel: 'Ann 2 End', - labelAnchor: AnnotationLabelAnchor.end, - labelDirection: AnnotationLabelDirection.horizontal, - labelPosition: AnnotationLabelPosition.auto), - equals(true)); - }); - - test('cleans up', () { - // Setup - final behavior = RangeAnnotation(_annotations2); - behavior.attachTo(_chart); - - // Act - behavior.removeFrom(_chart); - - // Verify - expect(_chart.lastListener, isNull); - }); - }); -} - -class MyRow { - final int campaign; - final int count; - MyRow(this.campaign, this.count); -} diff --git a/web/charts/common/test/chart/common/behavior/selection/lock_selection_test.dart b/web/charts/common/test/chart/common/behavior/selection/lock_selection_test.dart deleted file mode 100644 index 1c4f49b78..000000000 --- a/web/charts/common/test/chart/common/behavior/selection/lock_selection_test.dart +++ /dev/null @@ -1,160 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/behavior/selection/lock_selection.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/common/gesture_listener.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockChart extends Mock implements BaseChart { - GestureListener lastListener; - - @override - GestureListener addGestureListener(GestureListener listener) { - lastListener = listener; - return listener; - } - - @override - void removeGestureListener(GestureListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - } -} - -class MockSelectionModel extends Mock implements MutableSelectionModel { - bool locked = false; -} - -void main() { - MockChart _chart; - MockSelectionModel _hoverSelectionModel; - MockSelectionModel _clickSelectionModel; - - LockSelection _makeLockSelectionBehavior( - SelectionModelType selectionModelType) { - LockSelection behavior = - LockSelection(selectionModelType: selectionModelType); - - behavior.attachTo(_chart); - - return behavior; - } - - _setupChart({Point forPoint, bool isWithinRenderer}) { - if (isWithinRenderer != null) { - when(_chart.pointWithinRenderer(forPoint)).thenReturn(isWithinRenderer); - } - } - - setUp(() { - _hoverSelectionModel = MockSelectionModel(); - _clickSelectionModel = MockSelectionModel(); - - _chart = MockChart(); - when(_chart.getSelectionModel(SelectionModelType.info)) - .thenReturn(_hoverSelectionModel); - when(_chart.getSelectionModel(SelectionModelType.action)) - .thenReturn(_clickSelectionModel); - }); - - group('LockSelection trigger handling', () { - test('can lock model with a selection', () { - // Setup chart matches point with single domain single series. - _makeLockSelectionBehavior(SelectionModelType.info); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true); - - when(_hoverSelectionModel.hasAnySelection).thenReturn(true); - - // Act - _chart.lastListener.onTapTest(point); - _chart.lastListener.onTap(point); - - // Validate - verify(_hoverSelectionModel.hasAnySelection); - expect(_hoverSelectionModel.locked, equals(true)); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('can lock and unlock model', () { - // Setup chart matches point with single domain single series. - _makeLockSelectionBehavior(SelectionModelType.info); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true); - - when(_hoverSelectionModel.hasAnySelection).thenReturn(true); - - // Act - _chart.lastListener.onTapTest(point); - _chart.lastListener.onTap(point); - - // Validate - verify(_hoverSelectionModel.hasAnySelection); - expect(_hoverSelectionModel.locked, equals(true)); - - // Act - _chart.lastListener.onTapTest(point); - _chart.lastListener.onTap(point); - - // Validate - verify(_hoverSelectionModel.clearSelection()); - expect(_hoverSelectionModel.locked, equals(false)); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('does not lock model with empty selection', () { - // Setup chart matches point with single domain single series. - _makeLockSelectionBehavior(SelectionModelType.info); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true); - - when(_hoverSelectionModel.hasAnySelection).thenReturn(false); - - // Act - _chart.lastListener.onTapTest(point); - _chart.lastListener.onTap(point); - - // Validate - verify(_hoverSelectionModel.hasAnySelection); - expect(_hoverSelectionModel.locked, equals(false)); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - }); - - group('Cleanup', () { - test('detach removes listener', () { - // Setup - final behavior = _makeLockSelectionBehavior(SelectionModelType.info); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true); - expect(_chart.lastListener, isNotNull); - - // Act - behavior.removeFrom(_chart); - - // Validate - expect(_chart.lastListener, isNull); - }); - }); -} diff --git a/web/charts/common/test/chart/common/behavior/selection/select_nearest_test.dart b/web/charts/common/test/chart/common/behavior/selection/select_nearest_test.dart deleted file mode 100644 index a083b37e8..000000000 --- a/web/charts/common/test/chart/common/behavior/selection/select_nearest_test.dart +++ /dev/null @@ -1,491 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/behavior/selection/select_nearest.dart'; -import 'package:charts_common/src/chart/common/behavior/selection/selection_trigger.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/series_datum.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/common/gesture_listener.dart'; -import 'package:charts_common/src/data/series.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockChart extends Mock implements BaseChart { - GestureListener lastListener; - - @override - GestureListener addGestureListener(GestureListener listener) { - lastListener = listener; - return listener; - } - - @override - void removeGestureListener(GestureListener listener) { - expect(listener, equals(lastListener)); - lastListener = null; - } -} - -class MockSelectionModel extends Mock implements MutableSelectionModel { -} - -void main() { - MockChart _chart; - MockSelectionModel _hoverSelectionModel; - MockSelectionModel _clickSelectionModel; - List _series1Data; - List _series2Data; - MutableSeries _series1; - MutableSeries _series2; - DatumDetails _details1; - DatumDetails _details1Series2; - DatumDetails _details2; - DatumDetails _details3; - - SelectNearest _makeBehavior( - SelectionModelType selectionModelType, SelectionTrigger eventTrigger, - {bool expandToDomain, - bool selectClosestSeries, - int maximumDomainDistancePx}) { - SelectNearest behavior = SelectNearest( - selectionModelType: selectionModelType, - expandToDomain: expandToDomain, - selectClosestSeries: selectClosestSeries, - eventTrigger: eventTrigger, - maximumDomainDistancePx: maximumDomainDistancePx); - - behavior.attachTo(_chart); - - return behavior; - } - - _setupChart( - {Point forPoint, - bool isWithinRenderer, - List> respondWithDetails, - List> seriesList}) { - if (isWithinRenderer != null) { - when(_chart.pointWithinRenderer(forPoint)).thenReturn(isWithinRenderer); - } - if (respondWithDetails != null) { - when(_chart.getNearestDatumDetailPerSeries(forPoint, true)) - .thenReturn(respondWithDetails); - } - if (seriesList != null) { - when(_chart.currentSeriesList).thenReturn(seriesList); - } - } - - setUp(() { - _hoverSelectionModel = MockSelectionModel(); - _clickSelectionModel = MockSelectionModel(); - - _chart = MockChart(); - when(_chart.getSelectionModel(SelectionModelType.info)) - .thenReturn(_hoverSelectionModel); - when(_chart.getSelectionModel(SelectionModelType.action)) - .thenReturn(_clickSelectionModel); - - _series1Data = ['myDomain1', 'myDomain2', 'myDomain3']; - - _series1 = MutableSeries(Series( - id: 'mySeries1', - data: ['myDatum1', 'myDatum2', 'myDatum3'], - domainFn: (_, i) => _series1Data[i], - measureFn: (_, __) => 0)); - - _details1 = DatumDetails( - datum: 'myDatum1', - domain: 'myDomain1', - series: _series1, - domainDistance: 10.0, - measureDistance: 20.0); - _details2 = DatumDetails( - datum: 'myDatum2', - domain: 'myDomain2', - series: _series1, - domainDistance: 10.0, - measureDistance: 20.0); - _details3 = DatumDetails( - datum: 'myDatum3', - domain: 'myDomain3', - series: _series1, - domainDistance: 10.0, - measureDistance: 20.0); - - _series2Data = ['myDomain1']; - - _series2 = MutableSeries(Series( - id: 'mySeries2', - data: ['myDatum1s2'], - domainFn: (_, i) => _series2Data[i], - measureFn: (_, __) => 0)); - - _details1Series2 = DatumDetails( - datum: 'myDatum1s2', - domain: 'myDomain1', - series: _series2, - domainDistance: 10.0, - measureDistance: 20.0); - }); - - tearDown(resetMockitoState); - - group('SelectNearest trigger handling', () { - test('single series selects detail', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: true, selectClosestSeries: true); - Point point = Point(100.0, 100.0); - _setupChart( - forPoint: point, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - - // Act - _chart.lastListener.onHover(point); - - // Validate - verify(_hoverSelectionModel.updateSelection( - [SeriesDatum(_series1, _details1.datum)], [_series1])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - // Shouldn't be listening to anything else. - expect(_chart.lastListener.onTap, isNull); - expect(_chart.lastListener.onDragStart, isNull); - }); - - test('can listen to tap', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.action, SelectionTrigger.tap, - expandToDomain: true, selectClosestSeries: true); - Point point = Point(100.0, 100.0); - _setupChart( - forPoint: point, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - - // Act - _chart.lastListener.onTapTest(point); - _chart.lastListener.onTap(point); - - // Validate - verify(_clickSelectionModel.updateSelection( - [SeriesDatum(_series1, _details1.datum)], [_series1])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('can listen to drag', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.pressHold, - expandToDomain: true, selectClosestSeries: true); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - - Point updatePoint1 = Point(200.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - - Point updatePoint2 = Point(300.0, 100.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details2], - seriesList: [_series1]); - - Point endPoint = Point(400.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3], - seriesList: [_series1]); - - // Act - _chart.lastListener.onTapTest(startPoint); - _chart.lastListener.onDragStart(startPoint); - _chart.lastListener.onDragUpdate(updatePoint1, 1.0); - _chart.lastListener.onDragUpdate(updatePoint2, 1.0); - _chart.lastListener.onDragEnd(endPoint, 1.0, 0.0); - - // Validate - // details1 was tripped 2 times (startPoint & updatePoint1) - verify(_hoverSelectionModel.updateSelection( - [SeriesDatum(_series1, _details1.datum)], [_series1])).called(2); - // details2 was tripped for updatePoint2 - verify(_hoverSelectionModel.updateSelection( - [SeriesDatum(_series1, _details2.datum)], [_series1])); - // dragEnd deselects even though we are over details3. - verify(_hoverSelectionModel.updateSelection([], [])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('can listen to drag after long press', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.longPressHold, - expandToDomain: true, selectClosestSeries: true); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - - Point updatePoint1 = Point(200.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2], - seriesList: [_series1]); - - Point endPoint = Point(400.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3], - seriesList: [_series1]); - - // Act 1 - _chart.lastListener.onTapTest(startPoint); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - - // Act 2 - // verify no interaction yet. - _chart.lastListener.onLongPress(startPoint); - _chart.lastListener.onDragStart(startPoint); - _chart.lastListener.onDragUpdate(updatePoint1, 1.0); - _chart.lastListener.onDragEnd(endPoint, 1.0, 0.0); - - // Validate - // details1 was tripped 2 times (longPress & dragStart) - verify(_hoverSelectionModel.updateSelection( - [SeriesDatum(_series1, _details1.datum)], [_series1])).called(2); - verify(_hoverSelectionModel.updateSelection( - [SeriesDatum(_series1, _details2.datum)], [_series1])); - // dragEnd deselects even though we are over details3. - verify(_hoverSelectionModel.updateSelection([], [])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('no trigger before long press', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.longPressHold, - expandToDomain: true, selectClosestSeries: true); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - - Point updatePoint1 = Point(200.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2], - seriesList: [_series1]); - - Point endPoint = Point(400.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3], - seriesList: [_series1]); - - // Act - _chart.lastListener.onTapTest(startPoint); - _chart.lastListener.onDragStart(startPoint); - _chart.lastListener.onDragUpdate(updatePoint1, 1.0); - _chart.lastListener.onDragEnd(endPoint, 1.0, 0.0); - - // Validate - // No interaction, didn't long press first. - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - }); - - group('Details', () { - test('expands to domain and includes closest series', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: true, selectClosestSeries: true); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [ - _details1, - _details1Series2, - ], seriesList: [ - _series1, - _series2 - ]); - - // Act - _chart.lastListener.onHover(point); - - // Validate - verify(_hoverSelectionModel.updateSelection([ - SeriesDatum(_series1, _details1.datum), - SeriesDatum(_series2, _details1Series2.datum) - ], [ - _series1 - ])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('does not expand to domain', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: false, selectClosestSeries: true); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [ - _details1, - _details1Series2, - ], seriesList: [ - _series1, - _series2 - ]); - - // Act - _chart.lastListener.onHover(point); - - // Validate - verify(_hoverSelectionModel.updateSelection( - [SeriesDatum(_series1, _details1.datum)], [_series1])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('does not include closest series', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: true, selectClosestSeries: false); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [ - _details1, - _details1Series2, - ], seriesList: [ - _series1, - _series2 - ]); - - // Act - _chart.lastListener.onHover(point); - - // Validate - verify(_hoverSelectionModel.updateSelection([ - SeriesDatum(_series1, _details1.datum), - SeriesDatum(_series2, _details1Series2.datum) - ], [])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('does not include overlay series', () { - // Setup chart with an overlay series. - _series2.overlaySeries = true; - - _makeBehavior(SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: true, selectClosestSeries: true); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [ - _details1, - _details1Series2, - ], seriesList: [ - _series1, - _series2 - ]); - - // Act - _chart.lastListener.onHover(point); - - // Validate - verify(_hoverSelectionModel.updateSelection([ - SeriesDatum(_series1, _details1.datum), - ], [ - _series1 - ])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - - test('selection does not exceed maximumDomainDistancePx', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: true, - selectClosestSeries: true, - maximumDomainDistancePx: 1); - Point point = Point(100.0, 100.0); - _setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [ - _details1, - _details1Series2, - ], seriesList: [ - _series1, - _series2 - ]); - - // Act - _chart.lastListener.onHover(point); - - // Validate - verify(_hoverSelectionModel.updateSelection([], [])); - verifyNoMoreInteractions(_hoverSelectionModel); - verifyNoMoreInteractions(_clickSelectionModel); - }); - }); - - group('Cleanup', () { - test('detach removes listener', () { - // Setup - SelectNearest behavior = _makeBehavior( - SelectionModelType.info, SelectionTrigger.hover, - expandToDomain: true, selectClosestSeries: true); - Point point = Point(100.0, 100.0); - _setupChart( - forPoint: point, - isWithinRenderer: true, - respondWithDetails: [_details1], - seriesList: [_series1]); - expect(_chart.lastListener, isNotNull); - - // Act - behavior.removeFrom(_chart); - - // Validate - expect(_chart.lastListener, isNull); - }); - }); -} diff --git a/web/charts/common/test/chart/common/behavior/series_legend_behavior_test.dart b/web/charts/common/test/chart/common/behavior/series_legend_behavior_test.dart deleted file mode 100644 index 3a5cdf200..000000000 --- a/web/charts/common/test/chart/common/behavior/series_legend_behavior_test.dart +++ /dev/null @@ -1,473 +0,0 @@ -// 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 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/series_datum.dart'; -import 'package:charts_common/src/chart/common/series_renderer.dart'; -import 'package:charts_common/src/chart/common/behavior/legend/legend_entry_generator.dart'; -import 'package:charts_common/src/chart/common/behavior/legend/series_legend.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/common/color.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:test/test.dart'; - -class ConcreteChart extends BaseChart { - List> _seriesList; - - ConcreteChart(this._seriesList); - - @override - SeriesRenderer makeDefaultRenderer() => null; - - @override - List> get currentSeriesList => _seriesList; - - @override - List> getDatumDetails(SelectionModelType _) => null; - - set seriesList(List> seriesList) { - _seriesList = seriesList; - } - - void callOnDraw() { - fireOnDraw(_seriesList); - } - - void callOnPreProcess() { - fireOnPreprocess(_seriesList); - } - - void callOnPostProcess() { - fireOnPostprocess(_seriesList); - } -} - -class ConcreteSeriesLegend extends SeriesLegend { - ConcreteSeriesLegend( - {SelectionModelType selectionModelType, - LegendEntryGenerator legendEntryGenerator}) - : super( - selectionModelType: selectionModelType, - legendEntryGenerator: legendEntryGenerator); - - @override - bool isSeriesRenderer = false; - - @override - void hideSeries(String seriesId) { - super.hideSeries(seriesId); - } - - @override - void showSeries(String seriesId) { - super.showSeries(seriesId); - } - - @override - bool isSeriesHidden(String seriesId) { - return super.isSeriesHidden(seriesId); - } -} - -void main() { - MutableSeries series1; - final s1D1 = MyRow('s1d1', 11); - final s1D2 = MyRow('s1d2', 12); - final s1D3 = MyRow('s1d3', 13); - - MutableSeries series2; - final s2D1 = MyRow('s2d1', 21); - final s2D2 = MyRow('s2d2', 22); - final s2D3 = MyRow('s2d3', 23); - - final blue = Color(r: 0x21, g: 0x96, b: 0xF3); - final red = Color(r: 0xF4, g: 0x43, b: 0x36); - - ConcreteChart chart; - - setUp(() { - series1 = MutableSeries(Series( - id: 's1', - data: [s1D1, s1D2, s1D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - colorFn: (_, __) => blue)); - - series2 = MutableSeries(Series( - id: 's2', - data: [s2D1, s2D2, s2D3], - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.count, - colorFn: (_, __) => red)); - }); - - test('Legend entries created on chart post process', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final legend = SeriesLegend(selectionModelType: selectionType); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].color, equals(blue)); - expect(legendEntries[0].isSelected, isFalse); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].color, equals(red)); - expect(legendEntries[1].isSelected, isFalse); - }); - - test('default hidden series are removed from list during pre process', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final legend = - ConcreteSeriesLegend(selectionModelType: selectionType); - - legend.defaultHiddenSeries = ['s2']; - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isFalse); - expect(legend.isSeriesHidden('s2'), isTrue); - - expect(seriesList, hasLength(1)); - expect(seriesList[0].id, equals('s1')); - }); - - test('hidden series are removed from list after chart pre process', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final legend = - ConcreteSeriesLegend(selectionModelType: selectionType); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - legend.hideSeries('s1'); - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isTrue); - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList, hasLength(1)); - expect(seriesList[0].id, equals('s2')); - }); - - test('hidden and re-shown series is in the list after chart pre process', () { - final seriesList = [series1, series2]; - final seriesList2 = [series1, series2]; - final selectionType = SelectionModelType.info; - final legend = - ConcreteSeriesLegend(selectionModelType: selectionType); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - - // First hide the series. - legend.hideSeries('s1'); - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isTrue); - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList, hasLength(1)); - expect(seriesList[0].id, equals('s2')); - - // Then un-hide the series. This second list imitates the behavior of the - // chart, which creates a fresh copy of the original data from the user - // during each draw cycle. - legend.showSeries('s1'); - chart.seriesList = seriesList2; - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isFalse); - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList2, hasLength(2)); - expect(seriesList2[0].id, equals('s1')); - expect(seriesList2[1].id, equals('s2')); - }); - - test('selected series legend entry is updated', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final legend = SeriesLegend(selectionModelType: selectionType); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - chart.getSelectionModel(selectionType).updateSelection([], [series1]); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].color, equals(blue)); - expect(legendEntries[0].isSelected, isTrue); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].color, equals(red)); - expect(legendEntries[1].isSelected, isFalse); - }); - - test('hidden series removed from chart and later readded is visible', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final legend = - ConcreteSeriesLegend(selectionModelType: selectionType); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - - // First hide the series. - legend.hideSeries('s1'); - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isTrue); - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList, hasLength(1)); - expect(seriesList[0].id, equals('s2')); - - // Validate that drawing the same set of series again maintains the hidden - // states. - final seriesList2 = [series1, series2]; - chart.seriesList = seriesList2; - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isTrue); - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList2, hasLength(1)); - expect(seriesList2[0].id, equals('s2')); - - // Next, redraw the chart with only the visible series2. - final seriesList3 = [series2]; - - chart.seriesList = seriesList3; - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList3, hasLength(1)); - expect(seriesList3[0].id, equals('s2')); - - // Finally, add series1 back to the chart, and validate that it is not - // hidden. - final seriesList4 = [series1, series2]; - chart.seriesList = seriesList4; - chart.callOnDraw(); - chart.callOnPreProcess(); - - expect(legend.isSeriesHidden('s1'), isFalse); - expect(legend.isSeriesHidden('s2'), isFalse); - - expect(seriesList4, hasLength(2)); - expect(seriesList4[0].id, equals('s1')); - expect(seriesList4[1].id, equals('s2')); - }); - - test('generated legend entries use provided formatters', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final measureFormatter = (value) => 'measure ${value?.toStringAsFixed(0)}'; - final secondaryMeasureFormatter = - (value) => 'secondary ${value?.toStringAsFixed(0)}'; - final legend = SeriesLegend( - selectionModelType: selectionType, - measureFormatter: measureFormatter, - secondaryMeasureFormatter: secondaryMeasureFormatter); - - series2.setAttr(measureAxisIdKey, 'secondaryMeasureAxisId'); - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - chart.getSelectionModel(selectionType).updateSelection( - [SeriesDatum(series1, s1D1), SeriesDatum(series2, s2D1)], - [series1, series2]); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].isSelected, isTrue); - expect(legendEntries[0].value, equals(11.0)); - expect(legendEntries[0].formattedValue, equals('measure 11')); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].isSelected, isTrue); - expect(legendEntries[1].value, equals(21.0)); - expect(legendEntries[1].formattedValue, equals('secondary 21')); - }); - - test('series legend - show measure sum when there is no selection', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final measureFormatter = (value) => '${value?.toStringAsFixed(0)}'; - final legend = SeriesLegend( - selectionModelType: selectionType, - legendDefaultMeasure: LegendDefaultMeasure.sum, - measureFormatter: measureFormatter); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].color, equals(blue)); - expect(legendEntries[0].isSelected, isFalse); - expect(legendEntries[0].value, equals(36.0)); - expect(legendEntries[0].formattedValue, equals('36')); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].color, equals(red)); - expect(legendEntries[1].isSelected, isFalse); - expect(legendEntries[1].value, equals(66.0)); - expect(legendEntries[1].formattedValue, equals('66')); - }); - - test('series legend - show measure average when there is no selection', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final measureFormatter = (value) => '${value?.toStringAsFixed(0)}'; - final legend = SeriesLegend( - selectionModelType: selectionType, - legendDefaultMeasure: LegendDefaultMeasure.average, - measureFormatter: measureFormatter); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].color, equals(blue)); - expect(legendEntries[0].isSelected, isFalse); - expect(legendEntries[0].value, equals(12.0)); - expect(legendEntries[0].formattedValue, equals('12')); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].color, equals(red)); - expect(legendEntries[1].isSelected, isFalse); - expect(legendEntries[1].value, equals(22.0)); - expect(legendEntries[1].formattedValue, equals('22')); - }); - - test('series legend - show first measure when there is no selection', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final measureFormatter = (value) => '${value?.toStringAsFixed(0)}'; - final legend = SeriesLegend( - selectionModelType: selectionType, - legendDefaultMeasure: LegendDefaultMeasure.firstValue, - measureFormatter: measureFormatter); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].color, equals(blue)); - expect(legendEntries[0].isSelected, isFalse); - expect(legendEntries[0].value, equals(11.0)); - expect(legendEntries[0].formattedValue, equals('11')); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].color, equals(red)); - expect(legendEntries[1].isSelected, isFalse); - expect(legendEntries[1].value, equals(21.0)); - expect(legendEntries[1].formattedValue, equals('21')); - }); - - test('series legend - show last measure when there is no selection', () { - final seriesList = [series1, series2]; - final selectionType = SelectionModelType.info; - final measureFormatter = (value) => '${value?.toStringAsFixed(0)}'; - final legend = SeriesLegend( - selectionModelType: selectionType, - legendDefaultMeasure: LegendDefaultMeasure.lastValue, - measureFormatter: measureFormatter); - - chart = ConcreteChart(seriesList); - legend.attachTo(chart); - chart.callOnDraw(); - chart.callOnPreProcess(); - chart.callOnPostProcess(); - - final legendEntries = legend.legendState.legendEntries; - expect(legendEntries, hasLength(2)); - expect(legendEntries[0].series, equals(series1)); - expect(legendEntries[0].label, equals('s1')); - expect(legendEntries[0].color, equals(blue)); - expect(legendEntries[0].isSelected, isFalse); - expect(legendEntries[0].value, equals(13.0)); - expect(legendEntries[0].formattedValue, equals('13')); - - expect(legendEntries[1].series, equals(series2)); - expect(legendEntries[1].label, equals('s2')); - expect(legendEntries[1].color, equals(red)); - expect(legendEntries[1].isSelected, isFalse); - expect(legendEntries[1].value, equals(23.0)); - expect(legendEntries[1].formattedValue, equals('23')); - }); -} - -class MyRow { - final String campaign; - final int count; - MyRow(this.campaign, this.count); -} diff --git a/web/charts/common/test/chart/common/behavior/slider/slider_test.dart b/web/charts/common/test/chart/common/behavior/slider/slider_test.dart deleted file mode 100644 index d61eb5b87..000000000 --- a/web/charts/common/test/chart/common/behavior/slider/slider_test.dart +++ /dev/null @@ -1,611 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/common/base_chart.dart'; -import 'package:charts_common/src/chart/common/datum_details.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/behavior/slider/slider.dart'; -import 'package:charts_common/src/chart/common/behavior/selection/selection_trigger.dart'; -import 'package:charts_common/src/common/gesture_listener.dart'; -import 'package:charts_common/src/data/series.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockChart extends Mock implements CartesianChart { - GestureListener lastGestureListener; - - LifecycleListener lastLifecycleListener; - - bool vertical = true; - - @override - GestureListener addGestureListener(GestureListener listener) { - lastGestureListener = listener; - return listener; - } - - @override - void removeGestureListener(GestureListener listener) { - expect(listener, equals(lastGestureListener)); - lastGestureListener = null; - } - - @override - addLifecycleListener(LifecycleListener listener) => - lastLifecycleListener = listener; - - @override - removeLifecycleListener(LifecycleListener listener) { - expect(listener, equals(lastLifecycleListener)); - lastLifecycleListener = null; - return true; - } -} - -class MockDomainAxis extends Mock implements NumericAxis { - @override - double getDomain(num location) { - return (location / 20.0).toDouble(); - } - - @override - double getLocation(num domain) { - return (domain * 20.0).toDouble(); - } -} - -void main() { - MockChart _chart; - MockDomainAxis _domainAxis; - ImmutableSeries _series1; - DatumDetails _details1; - DatumDetails _details2; - DatumDetails _details3; - - SliderTester tester; - - Slider _makeBehavior(SelectionTrigger eventTrigger, - {Point handleOffset, - Rectangle handleSize, - double initialDomainValue, - SliderListenerCallback onChangeCallback, - bool snapToDatum = false, - SliderHandlePosition handlePosition = SliderHandlePosition.middle}) { - Slider behavior = Slider( - eventTrigger: eventTrigger, - initialDomainValue: initialDomainValue, - onChangeCallback: onChangeCallback, - snapToDatum: snapToDatum, - style: SliderStyle( - handleOffset: handleOffset, handlePosition: handlePosition)); - - behavior.attachTo(_chart); - - tester = SliderTester(behavior); - - // Mock out chart layout by assigning bounds to the layout view. - tester.layout( - Rectangle(0, 0, 200, 200), Rectangle(0, 0, 200, 200)); - - return behavior; - } - - _setupChart( - {Point forPoint, - bool isWithinRenderer, - List respondWithDetails}) { - when(_chart.domainAxis).thenReturn(_domainAxis); - - if (isWithinRenderer != null) { - when(_chart.pointWithinRenderer(forPoint)).thenReturn(isWithinRenderer); - } - if (respondWithDetails != null) { - when(_chart.getNearestDatumDetailPerSeries(forPoint, true)) - .thenReturn(respondWithDetails); - } - } - - setUp(() { - _chart = MockChart(); - - _domainAxis = MockDomainAxis(); - - _series1 = MutableSeries(Series( - id: 'mySeries1', - data: [], - domainFn: (_, __) {}, - measureFn: (_, __) => 0)); - - _details1 = DatumDetails( - chartPosition: Point(20.0, 80.0), - datum: 'myDatum1', - domain: 1.0, - series: _series1, - domainDistance: 10.0, - measureDistance: 20.0); - _details2 = DatumDetails( - chartPosition: Point(40.0, 80.0), - datum: 'myDatum2', - domain: 2.0, - series: _series1, - domainDistance: 10.0, - measureDistance: 20.0); - _details3 = DatumDetails( - chartPosition: Point(90.0, 80.0), - datum: 'myDatum3', - domain: 4.5, - series: _series1, - domainDistance: 10.0, - measureDistance: 20.0); - }); - - group('Slider trigger handling', () { - test('can listen to tap and drag', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionTrigger.tapAndDrag, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20)); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1]); - - Point updatePoint1 = Point(50.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2]); - - Point updatePoint2 = Point(100.0, 100.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - Point endPoint = Point(120.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - _chart.lastGestureListener.onTapTest(startPoint); - _chart.lastGestureListener.onTap(startPoint); - - // Start the drag. - _chart.lastGestureListener.onDragStart(startPoint); - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint1)); - expect(tester.domainValue, equals(2.5)); - expect(tester.handleBounds, equals(Rectangle(45, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint2)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag the point to the end point. - _chart.lastGestureListener.onDragUpdate(endPoint, 1.0); - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - - // Simulate onDragEnd. - _chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0); - - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - }); - - test('slider handle can render at top', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionTrigger.tapAndDrag, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20), - handlePosition: SliderHandlePosition.top); - - Point startPoint = Point(100.0, 0.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1]); - - Point updatePoint1 = Point(50.0, 0.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2]); - - Point updatePoint2 = Point(100.0, 0.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - Point endPoint = Point(120.0, 0.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - _chart.lastGestureListener.onTapTest(startPoint); - _chart.lastGestureListener.onTap(startPoint); - - // Start the drag. - _chart.lastGestureListener.onDragStart(startPoint); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, -10, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0); - expect(tester.domainValue, equals(2.5)); - expect(tester.handleBounds, equals(Rectangle(45, -10, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, -10, 10, 20))); - - // Drag the point to the end point. - _chart.lastGestureListener.onDragUpdate(endPoint, 1.0); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, -10, 10, 20))); - - // Simulate onDragEnd. - _chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0); - - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, -10, 10, 20))); - }); - - test('can listen to press hold', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionTrigger.pressHold, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20)); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1]); - - Point updatePoint1 = Point(50.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2]); - - Point updatePoint2 = Point(100.0, 100.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - Point endPoint = Point(120.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - _chart.lastGestureListener.onTapTest(startPoint); - _chart.lastGestureListener.onLongPress(startPoint); - - // Start the drag. - _chart.lastGestureListener.onDragStart(startPoint); - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint1)); - expect(tester.domainValue, equals(2.5)); - expect(tester.handleBounds, equals(Rectangle(45, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint2)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag the point to the end point. - _chart.lastGestureListener.onDragUpdate(endPoint, 1.0); - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - - // Simulate onDragEnd. - _chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0); - - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - }); - - test('can listen to long press hold', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionTrigger.longPressHold, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20)); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1]); - - Point updatePoint1 = Point(50.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2]); - - Point updatePoint2 = Point(100.0, 100.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - Point endPoint = Point(120.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - _chart.lastGestureListener.onTapTest(startPoint); - _chart.lastGestureListener.onLongPress(startPoint); - - // Start the drag. - _chart.lastGestureListener.onDragStart(startPoint); - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint1)); - expect(tester.domainValue, equals(2.5)); - expect(tester.handleBounds, equals(Rectangle(45, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint2)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag the point to the end point. - _chart.lastGestureListener.onDragUpdate(endPoint, 1.0); - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - - // Simulate onDragEnd. - _chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0); - - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - }); - - test('no position update before long press', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionTrigger.longPressHold, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20)); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1]); - - Point updatePoint1 = Point(50.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2]); - - Point updatePoint2 = Point(100.0, 100.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - Point endPoint = Point(120.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - _chart.lastGestureListener.onTapTest(startPoint); - - // Start the drag. - _chart.lastGestureListener.onDragStart(startPoint); - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag the point to the end point. - _chart.lastGestureListener.onDragUpdate(endPoint, 1.0); - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Simulate onDragEnd. - _chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0); - - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - }); - - test('can snap to datum', () { - // Setup chart matches point with single domain single series. - _makeBehavior(SelectionTrigger.tapAndDrag, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20), - snapToDatum: true); - - Point startPoint = Point(100.0, 100.0); - _setupChart( - forPoint: startPoint, - isWithinRenderer: true, - respondWithDetails: [_details1]); - - Point updatePoint1 = Point(50.0, 100.0); - _setupChart( - forPoint: updatePoint1, - isWithinRenderer: true, - respondWithDetails: [_details2]); - - Point updatePoint2 = Point(100.0, 100.0); - _setupChart( - forPoint: updatePoint2, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - Point endPoint = Point(120.0, 100.0); - _setupChart( - forPoint: endPoint, - isWithinRenderer: true, - respondWithDetails: [_details3]); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - _chart.lastGestureListener.onTapTest(startPoint); - _chart.lastGestureListener.onTap(startPoint); - - // Start the drag. - _chart.lastGestureListener.onDragStart(startPoint); - expect(tester.domainCenterPoint, equals(startPoint)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag to first update point. The slider should follow the mouse during - // each drag update. - _chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint1)); - expect(tester.domainValue, equals(2.5)); - expect(tester.handleBounds, equals(Rectangle(45, 90, 10, 20))); - - // Drag to first update point. - _chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0); - expect(tester.domainCenterPoint, equals(updatePoint2)); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Drag the point to the end point. - _chart.lastGestureListener.onDragUpdate(endPoint, 1.0); - expect(tester.domainCenterPoint, equals(endPoint)); - expect(tester.domainValue, equals(6.0)); - expect(tester.handleBounds, equals(Rectangle(115, 90, 10, 20))); - - // Simulate onDragEnd. This is where we expect the snap to occur. - _chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0); - - expect(tester.domainCenterPoint, equals(Point(90, 100))); - expect(tester.domainValue, equals(4.5)); - expect(tester.handleBounds, equals(Rectangle(85, 90, 10, 20))); - }); - }); - - group('Slider manual control', () { - test('can set domain position', () { - // Setup chart matches point with single domain single series. - final slider = _makeBehavior(SelectionTrigger.tapAndDrag, - handleOffset: Point(0.0, 0.0), - handleSize: Rectangle(0, 0, 10, 20), - initialDomainValue: 1.0); - - _setupChart(); - - // Act - _chart.lastLifecycleListener.onAxisConfigured(); - - // Verify initial position. - expect(tester.domainCenterPoint, equals(Point(20.0, 100.0))); - expect(tester.domainValue, equals(1.0)); - expect(tester.handleBounds, equals(Rectangle(15, 90, 10, 20))); - - // Move to first domain value. - slider.moveSliderToDomain(2); - expect(tester.domainCenterPoint, equals(Point(40.0, 100.0))); - expect(tester.domainValue, equals(2.0)); - expect(tester.handleBounds, equals(Rectangle(35, 90, 10, 20))); - - // Move to second domain value. - slider.moveSliderToDomain(5); - expect(tester.domainCenterPoint, equals(Point(100.0, 100.0))); - expect(tester.domainValue, equals(5.0)); - expect(tester.handleBounds, equals(Rectangle(95, 90, 10, 20))); - - // Move to second domain value. - slider.moveSliderToDomain(7.5); - expect(tester.domainCenterPoint, equals(Point(150.0, 100.0))); - expect(tester.domainValue, equals(7.5)); - expect(tester.handleBounds, equals(Rectangle(145, 90, 10, 20))); - }); - }); - - group('Cleanup', () { - test('detach removes listener', () { - // Setup - Slider behavior = _makeBehavior(SelectionTrigger.tapAndDrag); - - Point point = Point(100.0, 100.0); - _setupChart( - forPoint: point, - isWithinRenderer: true, - respondWithDetails: [_details1]); - expect(_chart.lastGestureListener, isNotNull); - - // Act - behavior.removeFrom(_chart); - - // Validate - expect(_chart.lastGestureListener, isNull); - }); - }); -} diff --git a/web/charts/common/test/chart/common/gesture_listener_test.dart b/web/charts/common/test/chart/common/gesture_listener_test.dart deleted file mode 100644 index 14427a629..000000000 --- a/web/charts/common/test/chart/common/gesture_listener_test.dart +++ /dev/null @@ -1,249 +0,0 @@ -// 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; -import 'package:charts_common/src/common/gesture_listener.dart'; -import 'package:charts_common/src/common/proxy_gesture_listener.dart'; -import 'package:test/test.dart'; - -void main() { - ProxyGestureListener _proxy; - Point _point; - setUp(() { - _proxy = ProxyGestureListener(); - _point = Point(10.0, 12.0); - }); - - group('Tap gesture', () { - test('notified for simple case', () { - // Setup - final tapListener = MockListener(consumeEvent: true); - _proxy.add(GestureListener(onTap: tapListener.callback)); - - // Act - _proxy.onTapTest(_point); - _proxy.onTap(_point); - - // Verify - tapListener.verify(arg1: _point); - }); - - test('notifies new listener for second event', () { - // Setup - final tapListener1 = MockListener(); - _proxy.add(GestureListener( - onTap: tapListener1.callback, - )); - - // Act - _proxy.onTapTest(_point); - _proxy.onTap(_point); - - // Verify - tapListener1.verify(arg1: _point); - - // Setup Another - final tapListener2 = MockListener(); - _proxy.add(GestureListener( - onTap: tapListener2.callback, - )); - - // Act - _proxy.onTapTest(_point); - _proxy.onTap(_point); - - // Verify - tapListener1.verify(callCount: 2, arg1: _point); - tapListener2.verify(arg1: _point); - }); - - test('notifies claiming listener registered first', () { - // Setup - final claimingTapDownListener = MockListener(consumeEvent: true); - final claimingTapListener = MockListener(consumeEvent: true); - - _proxy.add(GestureListener( - onTapTest: claimingTapDownListener.callback, - onTap: claimingTapListener.callback, - )); - - final nonclaimingTapDownListener = MockListener(consumeEvent: false); - final nonclaimingTapListener = MockListener(consumeEvent: false); - - _proxy.add(GestureListener( - onTapTest: nonclaimingTapDownListener.callback, - onTap: nonclaimingTapListener.callback, - )); - - // Act - _proxy.onTapTest(_point); - _proxy.onTap(_point); - - // Verify - claimingTapDownListener.verify(arg1: _point); - claimingTapListener.verify(arg1: _point); - nonclaimingTapDownListener.verify(arg1: _point); - nonclaimingTapListener.verify(callCount: 0); - }); - - test('notifies claiming listener registered second', () { - // Setup - final nonclaimingTapDownListener = MockListener(consumeEvent: false); - final nonclaimingTapListener = MockListener(consumeEvent: false); - - _proxy.add(GestureListener( - onTapTest: nonclaimingTapDownListener.callback, - onTap: nonclaimingTapListener.callback, - )); - - final claimingTapDownListener = MockListener(consumeEvent: true); - final claimingTapListener = MockListener(consumeEvent: true); - - _proxy.add(GestureListener( - onTapTest: claimingTapDownListener.callback, - onTap: claimingTapListener.callback, - )); - - // Act - _proxy.onTapTest(_point); - _proxy.onTap(_point); - - // Verify - nonclaimingTapDownListener.verify(arg1: _point); - nonclaimingTapListener.verify(callCount: 0); - claimingTapDownListener.verify(arg1: _point); - claimingTapListener.verify(arg1: _point); - }); - }); - - group('LongPress gesture', () { - test('notifies with tap', () { - // Setup - final tapDown = MockListener(consumeEvent: true); - final tap = MockListener(consumeEvent: true); - final tapCancel = MockListener(consumeEvent: true); - - _proxy.add(GestureListener( - onTapTest: tapDown.callback, - onTap: tap.callback, - onTapCancel: tapCancel.callback, - )); - - final pressTapDown = MockListener(consumeEvent: true); - final longPress = MockListener(consumeEvent: true); - final pressCancel = MockListener(consumeEvent: true); - - _proxy.add(GestureListener( - onTapTest: pressTapDown.callback, - onLongPress: longPress.callback, - onTapCancel: pressCancel.callback, - )); - - // Act - _proxy.onTapTest(_point); - _proxy.onLongPress(_point); - _proxy.onTap(_point); - - // Verify - tapDown.verify(arg1: _point); - tap.verify(callCount: 0); - tapCancel.verify(callCount: 1); - - pressTapDown.verify(arg1: _point); - longPress.verify(arg1: _point); - pressCancel.verify(callCount: 0); - }); - }); - - group('Drag gesture', () { - test('wins over tap', () { - // Setup - final tapDown = MockListener(consumeEvent: true); - final tap = MockListener(consumeEvent: true); - final tapCancel = MockListener(consumeEvent: true); - - _proxy.add(GestureListener( - onTapTest: tapDown.callback, - onTap: tap.callback, - onTapCancel: tapCancel.callback, - )); - - final dragTapDown = MockListener(consumeEvent: true); - final dragStart = MockListener(consumeEvent: true); - final dragUpdate = MockListener(consumeEvent: true); - final dragEnd = MockListener(consumeEvent: true); - final dragCancel = MockListener(consumeEvent: true); - - _proxy.add(GestureListener( - onTapTest: dragTapDown.callback, - onDragStart: dragStart.callback, - onDragUpdate: dragUpdate.callback, - onDragEnd: dragEnd.callback, - onTapCancel: dragCancel.callback, - )); - - // Act - _proxy.onTapTest(_point); - _proxy.onDragStart(_point); - _proxy.onDragUpdate(_point, 1.0); - _proxy.onDragUpdate(_point, 1.0); - _proxy.onDragEnd(_point, 2.0, 3.0); - _proxy.onTap(_point); - - // Verify - tapDown.verify(arg1: _point); - tap.verify(callCount: 0); - tapCancel.verify(callCount: 1); - - dragTapDown.verify(arg1: _point); - dragStart.verify(arg1: _point); - dragUpdate.verify(callCount: 2, arg1: _point, arg2: 1.0); - dragEnd.verify(arg1: _point, arg2: 2.0, arg3: 3.0); - dragCancel.verify(callCount: 0); - }); - }); -} - -class MockListener { - Object _arg1; - Object _arg2; - Object _arg3; - int _callCount = 0; - - final bool consumeEvent; - - MockListener({this.consumeEvent = false}); - - bool callback([Object arg1, Object arg2, Object arg3]) { - _arg1 = arg1; - _arg2 = arg2; - _arg3 = arg3; - - _callCount++; - - return consumeEvent; - } - - verify({int callCount = 1, Object arg1, Object arg2, Object arg3}) { - if (callCount != any) { - expect(_callCount, equals(callCount)); - } - expect(_arg1, equals(arg1)); - expect(_arg2, equals(arg2)); - expect(_arg3, equals(arg3)); - } -} - -const any = -1; diff --git a/web/charts/common/test/chart/common/selection_model/selection_model_test.dart b/web/charts/common/test/chart/common/selection_model/selection_model_test.dart deleted file mode 100644 index c17f4429e..000000000 --- a/web/charts/common/test/chart/common/selection_model/selection_model_test.dart +++ /dev/null @@ -1,327 +0,0 @@ -// 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 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/common/series_datum.dart'; -import 'package:charts_common/src/data/series.dart'; -import 'package:test/test.dart'; - -void main() { - MutableSelectionModel _selectionModel; - - ImmutableSeries _closestSeries; - MyDatum _closestDatumClosestSeries; - SeriesDatum _closestDatumClosestSeriesPair; - MyDatum _otherDatumClosestSeries; - SeriesDatum _otherDatumClosestSeriesPair; - - ImmutableSeries _otherSeries; - MyDatum _closestDatumOtherSeries; - SeriesDatum _closestDatumOtherSeriesPair; - MyDatum _otherDatumOtherSeries; - SeriesDatum _otherDatumOtherSeriesPair; - - setUp(() { - _selectionModel = MutableSelectionModel(); - - _closestDatumClosestSeries = MyDatum('cDcS'); - _otherDatumClosestSeries = MyDatum('oDcS'); - _closestSeries = MutableSeries(Series( - id: 'closest', - data: [_closestDatumClosestSeries, _otherDatumClosestSeries], - domainFn: (dynamic d, _) => d.id, - measureFn: (_, __) => 0)); - _closestDatumClosestSeriesPair = - SeriesDatum(_closestSeries, _closestDatumClosestSeries); - _otherDatumClosestSeriesPair = - SeriesDatum(_closestSeries, _otherDatumClosestSeries); - - _closestDatumOtherSeries = MyDatum('cDoS'); - _otherDatumOtherSeries = MyDatum('oDoS'); - _otherSeries = MutableSeries(Series( - id: 'other', - data: [_closestDatumOtherSeries, _otherDatumOtherSeries], - domainFn: (dynamic d, _) => d.id, - measureFn: (_, __) => 0)); - _closestDatumOtherSeriesPair = - SeriesDatum(_otherSeries, _closestDatumOtherSeries); - _otherDatumOtherSeriesPair = - SeriesDatum(_otherSeries, _otherDatumOtherSeries); - }); - - group('SelectionModel persists values', () { - test('selection model is empty by default', () { - expect(_selectionModel.hasDatumSelection, isFalse); - expect(_selectionModel.hasSeriesSelection, isFalse); - }); - - test('all datum are selected but only the first Series is', () { - // Select the 'closest' datum for each Series. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - SeriesDatum(_otherSeries, _closestDatumOtherSeries), - ], [ - _closestSeries - ]); - - expect(_selectionModel.hasDatumSelection, isTrue); - expect(_selectionModel.selectedDatum, hasLength(2)); - expect(_selectionModel.selectedDatum, - contains(_closestDatumClosestSeriesPair)); - expect(_selectionModel.selectedDatum, - contains(_closestDatumOtherSeriesPair)); - expect( - _selectionModel.selectedDatum.contains(_otherDatumClosestSeriesPair), - isFalse); - expect(_selectionModel.selectedDatum.contains(_otherDatumOtherSeriesPair), - isFalse); - - expect(_selectionModel.hasSeriesSelection, isTrue); - expect(_selectionModel.selectedSeries, hasLength(1)); - expect(_selectionModel.selectedSeries, contains(_closestSeries)); - expect(_selectionModel.selectedSeries.contains(_otherSeries), isFalse); - }); - - test('selection can change', () { - // Select the 'closest' datum for each Series. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - SeriesDatum(_otherSeries, _closestDatumOtherSeries), - ], [ - _closestSeries - ]); - - // Change selection to just the other datum on the other series. - _selectionModel.updateSelection([ - SeriesDatum(_otherSeries, _otherDatumOtherSeries), - ], [ - _otherSeries - ]); - - expect(_selectionModel.selectedDatum, hasLength(1)); - expect( - _selectionModel.selectedDatum, contains(_otherDatumOtherSeriesPair)); - - expect(_selectionModel.selectedSeries, hasLength(1)); - expect(_selectionModel.selectedSeries, contains(_otherSeries)); - }); - - test('selection can be series only', () { - // Select the 'closest' Series without datum to simulate legend hovering. - _selectionModel.updateSelection([], [_closestSeries]); - - expect(_selectionModel.hasDatumSelection, isFalse); - expect(_selectionModel.selectedDatum, hasLength(0)); - - expect(_selectionModel.hasSeriesSelection, isTrue); - expect(_selectionModel.selectedSeries, hasLength(1)); - expect(_selectionModel.selectedSeries, contains(_closestSeries)); - }); - - test('selection lock prevents change', () { - // Prevent selection changes. - _selectionModel.locked = true; - - // Try to the 'closest' datum for each Series. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - SeriesDatum(_otherSeries, _closestDatumOtherSeries), - ], [ - _closestSeries - ]); - - expect(_selectionModel.hasDatumSelection, isFalse); - expect(_selectionModel.hasSeriesSelection, isFalse); - - // Allow selection changes. - _selectionModel.locked = false; - - // Try to the 'closest' datum for each Series. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - SeriesDatum(_otherSeries, _closestDatumOtherSeries), - ], [ - _closestSeries - ]); - - expect(_selectionModel.hasDatumSelection, isTrue); - expect(_selectionModel.hasSeriesSelection, isTrue); - - // Prevent selection changes. - _selectionModel.locked = true; - - // Attempt to change selection - _selectionModel.updateSelection([ - SeriesDatum(_otherSeries, _otherDatumOtherSeries), - ], [ - _otherSeries - ]); - - // Previous selection should still be set. - expect(_selectionModel.selectedDatum, hasLength(2)); - expect(_selectionModel.selectedDatum, - contains(_closestDatumClosestSeriesPair)); - expect(_selectionModel.selectedDatum, - contains(_closestDatumOtherSeriesPair)); - - expect(_selectionModel.selectedSeries, hasLength(1)); - expect(_selectionModel.selectedSeries, contains(_closestSeries)); - }); - }); - - group('SelectionModel changed listeners', () { - test('listener triggered for change', () { - SelectionModel triggeredModel; - // Listen - _selectionModel.addSelectionChangedListener((model) { - triggeredModel = model; - }); - - // Set the selection to closest datum. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Callback should have been triggered. - expect(triggeredModel, equals(_selectionModel)); - }); - - test('listener not triggered for no change', () { - SelectionModel triggeredModel; - // Set the selection to closest datum. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Listen - _selectionModel.addSelectionChangedListener((model) { - triggeredModel = model; - }); - - // Try to update the model with the same value. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Callback should not have been triggered. - expect(triggeredModel, isNull); - }); - - test('removed listener not triggered for change', () { - SelectionModel triggeredModel; - - Function cb = (model) { - triggeredModel = model; - }; - - // Listen - _selectionModel.addSelectionChangedListener(cb); - - // Unlisten - _selectionModel.removeSelectionChangedListener(cb); - - // Set the selection to closest datum. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Callback should not have been triggered. - expect(triggeredModel, isNull); - }); - }); - - group('SelectionModel updated listeners', () { - test('listener triggered for change', () { - SelectionModel triggeredModel; - // Listen - _selectionModel.addSelectionUpdatedListener((model) { - triggeredModel = model; - }); - - // Set the selection to closest datum. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Callback should have been triggered. - expect(triggeredModel, equals(_selectionModel)); - }); - - test('listener triggered for no change', () { - SelectionModel triggeredModel; - // Set the selection to closest datum. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Listen - _selectionModel.addSelectionUpdatedListener((model) { - triggeredModel = model; - }); - - // Try to update the model with the same value. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Callback should have been triggered. - expect(triggeredModel, equals(_selectionModel)); - }); - - test('removed listener not triggered for change', () { - SelectionModel triggeredModel; - - Function cb = (model) { - triggeredModel = model; - }; - - // Listen - _selectionModel.addSelectionUpdatedListener(cb); - - // Unlisten - _selectionModel.removeSelectionUpdatedListener(cb); - - // Set the selection to closest datum. - _selectionModel.updateSelection([ - SeriesDatum(_closestSeries, _closestDatumClosestSeries), - ], [ - _closestSeries - ]); - - // Callback should not have been triggered. - expect(triggeredModel, isNull); - }); - }); -} - -class MyDatum { - final String id; - MyDatum(this.id); -} diff --git a/web/charts/common/test/chart/layout/layout_manager_impl_test.dart b/web/charts/common/test/chart/layout/layout_manager_impl_test.dart deleted file mode 100644 index 88f0a8a99..000000000 --- a/web/charts/common/test/chart/layout/layout_manager_impl_test.dart +++ /dev/null @@ -1,48 +0,0 @@ -// 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 'package:charts_common/src/chart/layout/layout_config.dart'; -import 'package:charts_common/src/chart/layout/layout_manager_impl.dart'; - -import 'package:test/test.dart'; - -void main() { - test('default layout', () { - var layout = LayoutManagerImpl(); - layout.measure(400, 300); - - expect(layout.marginTop, equals(0)); - expect(layout.marginRight, equals(0)); - expect(layout.marginBottom, equals(0)); - expect(layout.marginLeft, equals(0)); - }); - - test('all fixed margin', () { - var layout = LayoutManagerImpl( - config: LayoutConfig( - topSpec: MarginSpec.fixedPixel(12), - rightSpec: MarginSpec.fixedPixel(11), - bottomSpec: MarginSpec.fixedPixel(10), - leftSpec: MarginSpec.fixedPixel(9), - ), - ); - layout.measure(400, 300); - - expect(layout.marginTop, equals(12)); - expect(layout.marginRight, equals(11)); - expect(layout.marginBottom, equals(10)); - expect(layout.marginLeft, equals(9)); - }); -} diff --git a/web/charts/common/test/chart/line/line_renderer_test.dart b/web/charts/common/test/chart/line/line_renderer_test.dart deleted file mode 100644 index cc19c9ff5..000000000 --- a/web/charts/common/test/chart/line/line_renderer_test.dart +++ /dev/null @@ -1,640 +0,0 @@ -// 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 'package:charts_common/src/chart/line/line_renderer.dart'; -import 'package:charts_common/src/chart/line/line_renderer_config.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart' - show MutableSeries, ImmutableSeries; -import 'package:charts_common/src/common/color.dart'; -import 'package:charts_common/src/common/material_palette.dart' - show MaterialPalette; -import 'package:charts_common/src/data/series.dart' show Series; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaignString; - final int campaign; - final int clickCount; - final Color color; - final List dashPattern; - final double strokeWidthPx; - MyRow(this.campaignString, this.campaign, this.clickCount, this.color, - this.dashPattern, this.strokeWidthPx); -} - -class MockImmutableSeries extends Mock implements ImmutableSeries { - String _id; - MockImmutableSeries(this._id); - - @override - String get id => _id; -} - -void main() { - LineRenderer renderer; - List> numericSeriesList; - List> ordinalSeriesList; - - List myFakeDesktopData; - List myFakeTabletData; - List myFakeMobileData; - - setUp(() { - myFakeDesktopData = [ - MyRow('MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, null, 2.0), - MyRow( - 'MyCampaign2', 2, 25, MaterialPalette.green.shadeDefault, null, 2.0), - MyRow('MyCampaign3', 3, 100, MaterialPalette.red.shadeDefault, null, 2.0), - MyRow('MyOtherCampaign', 4, 75, MaterialPalette.red.shadeDefault, null, - 2.0), - ]; - - myFakeTabletData = [ - MyRow( - 'MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, [2, 2], 2.0), - MyRow( - 'MyCampaign2', 2, 25, MaterialPalette.blue.shadeDefault, [3, 3], 2.0), - MyRow('MyCampaign3', 3, 100, MaterialPalette.blue.shadeDefault, [4, 4], - 2.0), - MyRow('MyOtherCampaign', 4, 75, MaterialPalette.blue.shadeDefault, [4, 4], - 2.0), - ]; - - myFakeMobileData = [ - MyRow('MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, null, 2.0), - MyRow('MyCampaign2', 2, 25, MaterialPalette.blue.shadeDefault, null, 3.0), - MyRow( - 'MyCampaign3', 3, 100, MaterialPalette.blue.shadeDefault, null, 4.0), - MyRow('MyOtherCampaign', 4, 75, MaterialPalette.blue.shadeDefault, null, - 4.0), - ]; - - numericSeriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (_, __) => MaterialPalette.blue.shadeDefault, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - data: myFakeDesktopData)), - MutableSeries(Series( - id: 'Tablet', - colorFn: (_, __) => MaterialPalette.red.shadeDefault, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - strokeWidthPxFn: (_, __) => 1.25, - data: myFakeTabletData)), - MutableSeries(Series( - id: 'Mobile', - colorFn: (_, __) => MaterialPalette.green.shadeDefault, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - strokeWidthPxFn: (_, __) => 3.0, - data: myFakeMobileData)) - ]; - - ordinalSeriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (_, __) => MaterialPalette.blue.shadeDefault, - domainFn: (dynamic row, _) => row.campaignString, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - data: myFakeDesktopData)), - MutableSeries(Series( - id: 'Tablet', - colorFn: (_, __) => MaterialPalette.red.shadeDefault, - domainFn: (dynamic row, _) => row.campaignString, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - strokeWidthPxFn: (_, __) => 1.25, - data: myFakeTabletData)), - MutableSeries(Series( - id: 'Mobile', - colorFn: (_, __) => MaterialPalette.green.shadeDefault, - domainFn: (dynamic row, _) => row.campaignString, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - strokeWidthPxFn: (_, __) => 3.0, - data: myFakeMobileData)) - ]; - }); - - group('preprocess', () { - test('with numeric data and simple lines', () { - renderer = - LineRenderer(config: LineRendererConfig(strokeWidthPx: 2.0)); - - renderer.configureSeries(numericSeriesList); - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(3)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - var segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(2.0)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - - // Validate Tablet series. - series = numericSeriesList[1]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.red.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(1.25)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - - // Validate Mobile series. - series = numericSeriesList[2]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(3.0)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - }); - - test('with numeric data and stacked lines', () { - renderer = LineRenderer( - config: LineRendererConfig(stacked: true, strokeWidthPx: 2.0)); - - renderer.configureSeries(numericSeriesList); - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(3)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - var segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(2.0)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - - // Validate Tablet series. - series = numericSeriesList[1]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.red.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(1.25)); - - expect(series.measureOffsetFn(0), 5); - expect(series.measureOffsetFn(1), 25); - expect(series.measureOffsetFn(2), 100); - expect(series.measureOffsetFn(3), 75); - - // Validate Mobile series. - series = numericSeriesList[2]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(3.0)); - - expect(series.measureOffsetFn(0), 10); - expect(series.measureOffsetFn(1), 50); - expect(series.measureOffsetFn(2), 200); - expect(series.measureOffsetFn(3), 150); - }); - - test('with numeric data and changes in style', () { - numericSeriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (row, _) => row.color, - dashPatternFn: (row, _) => row.dashPattern, - strokeWidthPxFn: (row, _) => row.strokeWidthPx, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - data: myFakeDesktopData)), - MutableSeries(Series( - id: 'Tablet', - colorFn: (row, _) => row.color, - dashPatternFn: (row, _) => row.dashPattern, - strokeWidthPxFn: (row, _) => row.strokeWidthPx, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - data: myFakeTabletData)), - MutableSeries(Series( - id: 'Mobile', - colorFn: (row, _) => row.color, - dashPatternFn: (row, _) => row.dashPattern, - strokeWidthPxFn: (row, _) => row.strokeWidthPx, - domainFn: (dynamic row, _) => row.campaign, - measureFn: (dynamic row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - data: myFakeMobileData)) - ]; - - renderer = - LineRenderer(config: LineRendererConfig(strokeWidthPx: 2.0)); - - renderer.configureSeries(numericSeriesList); - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(3)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(3)); - - var segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(2)); - expect(segment.strokeWidthPx, equals(2.0)); - - segment = styleSegments[1]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(2)); - expect(segment.domainExtent.end, equals(3)); - expect(segment.strokeWidthPx, equals(2.0)); - - segment = styleSegments[2]; - expect(segment.color, equals(MaterialPalette.red.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(3)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(2.0)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - - // Validate Tablet series. - series = numericSeriesList[1]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(3)); - - segment = segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, equals([2, 2])); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(2)); - expect(segment.strokeWidthPx, equals(2.0)); - - segment = styleSegments[1]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, equals([3, 3])); - expect(segment.domainExtent.start, equals(2)); - expect(segment.domainExtent.end, equals(3)); - expect(segment.strokeWidthPx, equals(2.0)); - - segment = styleSegments[2]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, equals([4, 4])); - expect(segment.domainExtent.start, equals(3)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(2.0)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - - // Validate Mobile series. - series = numericSeriesList[2]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(3)); - - segment = segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(2)); - expect(segment.strokeWidthPx, equals(2.0)); - - segment = styleSegments[1]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(2)); - expect(segment.domainExtent.end, equals(3)); - expect(segment.strokeWidthPx, equals(3.0)); - - segment = styleSegments[2]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals(3)); - expect(segment.domainExtent.end, equals(4)); - expect(segment.strokeWidthPx, equals(4.0)); - - expect(series.measureOffsetFn(0), 0); - expect(series.measureOffsetFn(1), 0); - expect(series.measureOffsetFn(2), 0); - expect(series.measureOffsetFn(3), 0); - }); - - test('with numeric data and repeats in style', () { - var myFakeData = [ - MyRow( - 'MyCampaign1', 1, 5, MaterialPalette.blue.shadeDefault, null, 2.0), - MyRow('MyCampaign2', 2, 25, MaterialPalette.green.shadeDefault, null, - 2.0), - MyRow('MyCampaign3', 3, 100, MaterialPalette.blue.shadeDefault, null, - 2.0), - MyRow('MyCampaign4', 4, 75, MaterialPalette.green.shadeDefault, null, - 2.0), - MyRow( - 'MyCampaign1', 5, 5, MaterialPalette.blue.shadeDefault, null, 2.0), - MyRow('MyCampaign2', 6, 25, MaterialPalette.green.shadeDefault, null, - 2.0), - MyRow('MyCampaign3', 7, 100, MaterialPalette.blue.shadeDefault, null, - 2.0), - MyRow('MyCampaign4', 8, 75, MaterialPalette.green.shadeDefault, null, - 2.0), - ]; - - numericSeriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (row, _) => row.color, - dashPatternFn: (row, _) => row.dashPattern, - strokeWidthPxFn: (row, _) => row.strokeWidthPx, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (_, __) => 0, - data: myFakeData)), - ]; - - renderer = - LineRenderer(config: LineRendererConfig(strokeWidthPx: 2.0)); - - renderer.configureSeries(numericSeriesList); - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(1)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(8)); - - var segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.domainExtent.start, equals(1)); - expect(segment.domainExtent.end, equals(2)); - - segment = styleSegments[1]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.domainExtent.start, equals(2)); - expect(segment.domainExtent.end, equals(3)); - - segment = styleSegments[2]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.domainExtent.start, equals(3)); - expect(segment.domainExtent.end, equals(4)); - - segment = styleSegments[3]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.domainExtent.start, equals(4)); - expect(segment.domainExtent.end, equals(5)); - - segment = styleSegments[4]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.domainExtent.start, equals(5)); - expect(segment.domainExtent.end, equals(6)); - - segment = styleSegments[5]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.domainExtent.start, equals(6)); - expect(segment.domainExtent.end, equals(7)); - - segment = styleSegments[6]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.domainExtent.start, equals(7)); - expect(segment.domainExtent.end, equals(8)); - - segment = styleSegments[7]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.domainExtent.start, equals(8)); - expect(segment.domainExtent.end, equals(8)); - }); - - test('with ordinal data and simple lines', () { - renderer = - LineRenderer(config: LineRendererConfig(strokeWidthPx: 2.0)); - - renderer.configureSeries(ordinalSeriesList); - renderer.preprocessSeries(ordinalSeriesList); - - expect(ordinalSeriesList.length, equals(3)); - - // Validate Desktop series. - var series = ordinalSeriesList[0]; - - var styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - var segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.blue.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals('MyCampaign1')); - expect(segment.domainExtent.end, equals('MyOtherCampaign')); - expect(segment.strokeWidthPx, equals(2.0)); - - // Validate Tablet series. - series = ordinalSeriesList[1]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.red.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals('MyCampaign1')); - expect(segment.domainExtent.end, equals('MyOtherCampaign')); - expect(segment.strokeWidthPx, equals(1.25)); - - // Validate Mobile series. - series = ordinalSeriesList[2]; - - styleSegments = series.getAttr(styleSegmentsKey); - expect(styleSegments.length, equals(1)); - - segment = styleSegments[0]; - expect(segment.color, equals(MaterialPalette.green.shadeDefault)); - expect(segment.dashPattern, isNull); - expect(segment.domainExtent.start, equals('MyCampaign1')); - expect(segment.domainExtent.end, equals('MyOtherCampaign')); - expect(segment.strokeWidthPx, equals(3.0)); - }); - }); - - group('Line merging', () { - List> series(List keys) { - return keys.map((key) => MockImmutableSeries(key)).toList(); - } - - test('simple beginning removal', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['b', 'c'])); - - // The series should still be there so that it can be animated out. - expect(tester.seriesKeys, equals(['a', 'b', 'c'])); - }); - - test('simple middle removal', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['a', 'c'])); - - // The series should still be there so that it can be animated out. - expect(tester.seriesKeys, equals(['a', 'b', 'c'])); - }); - - test('simple end removal', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['a', 'b'])); - - // The series should still be there so that it can be animated out. - expect(tester.seriesKeys, equals(['a', 'b', 'c'])); - }); - - test('simple beginning addition', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['d', 'a', 'b', 'c'])); - - expect(tester.seriesKeys, equals(['d', 'a', 'b', 'c'])); - }); - - test('simple middle addition', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['a', 'd', 'b', 'c'])); - - expect(tester.seriesKeys, equals(['a', 'd', 'b', 'c'])); - }); - - test('simple end addition', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['a', 'b', 'c', 'd'])); - - expect(tester.seriesKeys, equals(['a', 'b', 'c', 'd'])); - }); - - test('replacement begining', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['d', 'b', 'c'])); - - expect(tester.seriesKeys, equals(['a', 'd', 'b', 'c'])); - }); - - test('replacement end', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['a', 'b', 'd'])); - - expect(tester.seriesKeys, equals(['a', 'b', 'c', 'd'])); - }); - - test('full replacement', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c']); - tester.merge(series(['d', 'e', 'f'])); - - expect(tester.seriesKeys, equals(['a', 'b', 'c', 'd', 'e', 'f'])); - }); - - test('mixed replacement', () { - final tester = LineRendererTester(LineRenderer()); - - tester.setSeriesKeys(['a', 'b', 'c', 'd']); - tester.merge(series(['d', 'a', 'f', 'c'])); - - expect(tester.seriesKeys, equals(['d', 'a', 'b', 'f', 'c'])); - }); - }); -} diff --git a/web/charts/common/test/chart/line/renderer_nearest_detail_test.dart b/web/charts/common/test/chart/line/renderer_nearest_detail_test.dart deleted file mode 100644 index 0004e9247..000000000 --- a/web/charts/common/test/chart/line/renderer_nearest_detail_test.dart +++ /dev/null @@ -1,352 +0,0 @@ -// 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'; - -import 'package:charts_common/src/chart/cartesian/axis/axis.dart'; -import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart'; -import 'package:charts_common/src/chart/common/chart_canvas.dart'; -import 'package:charts_common/src/chart/common/processed_series.dart'; -import 'package:charts_common/src/chart/line/line_renderer.dart'; -import 'package:charts_common/src/chart/line/line_renderer_config.dart'; -import 'package:charts_common/src/common/color.dart'; -import 'package:charts_common/src/data/series.dart'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final int timestamp; - int clickCount; - MyRow(this.timestamp, this.clickCount); -} - -// TODO: Test in RTL context as well. - -class MockChart extends Mock implements CartesianChart {} - -class MockDomainAxis extends Mock implements Axis {} - -class MockMeasureAxis extends Mock implements Axis {} - -class MockCanvas extends Mock implements ChartCanvas {} - -void main() { - ///////////////////////////////////////// - // Convenience methods for creating mocks. - ///////////////////////////////////////// - MutableSeries _makeSeries({String id, int measureOffset = 0}) { - final data = [ - MyRow(1000, measureOffset + 10), - MyRow(2000, measureOffset + 20), - MyRow(3000, measureOffset + 30), - ]; - - final series = MutableSeries(Series( - id: id, - data: data, - domainFn: (row, _) => row.timestamp, - measureFn: (row, _) => row.clickCount, - )); - - series.measureOffsetFn = (_) => 0.0; - series.colorFn = (_) => Color.fromHex(code: '#000000'); - - // Mock the Domain axis results. - final domainAxis = MockDomainAxis(); - when(domainAxis.rangeBand).thenReturn(100.0); - when(domainAxis.getLocation(1000)).thenReturn(70.0); - when(domainAxis.getLocation(2000)).thenReturn(70.0 + 100); - when(domainAxis.getLocation(3000)).thenReturn(70.0 + 200.0); - series.setAttr(domainAxisKey, domainAxis); - - // Mock the Measure axis results. - final measureAxis = MockMeasureAxis(); - for (var i = 0; i <= 100; i++) { - when(measureAxis.getLocation(i.toDouble())) - .thenReturn(20.0 + 100.0 - i.toDouble()); - } - // Special case where measure is above drawArea. - when(measureAxis.getLocation(500)).thenReturn(20.0 + 100.0 - 500); - - series.setAttr(measureAxisKey, measureAxis); - - return series; - } - - LineRenderer renderer; - - bool selectNearestByDomain; - - setUp(() { - selectNearestByDomain = true; - - renderer = - LineRenderer(config: LineRendererConfig(strokeWidthPx: 1.0)); - final layoutBounds = Rectangle(70, 20, 200, 100); - renderer.layout(layoutBounds, layoutBounds); - return renderer; - }); - - ///////////////////////////////////////// - // Additional edge test cases - ///////////////////////////////////////// - group('edge cases', () { - test('hit target with missing data in series still selects others', () { - // Setup - final seriesList = >[ - _makeSeries(id: 'foo')..data.clear(), - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act Point just below barSeries.data[0] - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - - final closest = details[0]; - expect(closest.domain, equals(1000)); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(10)); - expect(closest.measureDistance, equals(5)); - }); - - test('all series without data is skipped', () { - // Setup - final seriesList = >[ - _makeSeries(id: 'foo')..data.clear(), - _makeSeries(id: 'bar')..data.clear(), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(0)); - }); - - test('single overlay series is skipped', () { - // Setup - final seriesList = >[ - _makeSeries(id: 'foo')..overlaySeries = true, - _makeSeries(id: 'bar'), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - - final closest = details[0]; - expect(closest.domain, equals(1000)); - expect(closest.series.id, equals('bar')); - expect(closest.datum, equals(seriesList[1].data[0])); - expect(closest.domainDistance, equals(10)); - expect(closest.measureDistance, equals(5)); - }); - - test('all overlay series is skipped', () { - // Setup - final seriesList = >[ - _makeSeries(id: 'foo')..overlaySeries = true, - _makeSeries(id: 'bar')..overlaySeries = true, - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(0)); - }); - }); - - ///////////////////////////////////////// - // Vertical BarRenderer - ///////////////////////////////////////// - group('LineRenderer', () { - test('hit test works', () { - // Setup - final seriesList = >[_makeSeries(id: 'foo')]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals(1000)); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(10)); - expect(closest.measureDistance, equals(5)); - }); - - test('hit test expands to multiple series', () { - // Setup bar series is 20 measure higher than foo. - final seriesList = >[ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar', measureOffset: 20), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals(1000)); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[0])); - expect(closest.domainDistance, equals(10)); - expect(closest.measureDistance, equals(5)); - - final next = details[1]; - expect(next.domain, equals(1000)); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[0])); - expect(next.domainDistance, equals(10)); - expect(next.measureDistance, equals(25)); // 20offset + 10measure - 5pt - }); - - test('hit test expands with missing data in series', () { - // Setup bar series is 20 measure higher than foo and is missing the - // middle point. - final seriesList = >[ - _makeSeries(id: 'foo'), - _makeSeries(id: 'bar', measureOffset: 20)..data.removeAt(1), - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 100.0 + 10.0, 20.0 + 100.0 - 5.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(2)); - - final closest = details[0]; - expect(closest.domain, equals(2000)); - expect(closest.series.id, equals('foo')); - expect(closest.datum, equals(seriesList[0].data[1])); - expect(closest.domainDistance, equals(10)); - expect(closest.measureDistance, equals(15)); - - // bar series jumps to last point since it is missing middle. - final next = details[1]; - expect(next.domain, equals(3000)); - expect(next.series.id, equals('bar')); - expect(next.datum, equals(seriesList[1].data[1])); - expect(next.domainDistance, equals(90)); - expect(next.measureDistance, equals(45.0)); - }); - - test('hit test works for points above drawArea', () { - // Setup - final seriesList = >[ - _makeSeries(id: 'foo')..data[1].clickCount = 500 - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - final details = renderer.getNearestDatumDetailPerSeries( - Point(70.0 + 100.0 + 10.0, 20.0 + 10.0), - selectNearestByDomain, - null); - - // Verify - expect(details.length, equals(1)); - final closest = details[0]; - expect(closest.domain, equals(2000)); - expect(closest.series, equals(seriesList[0])); - expect(closest.datum, equals(seriesList[0].data[1])); - expect(closest.domainDistance, equals(10)); - expect(closest.measureDistance, equals(410)); // 500 - 100 + 10 - }); - - test('no selection for points outside of viewport', () { - // Setup - final seriesList = >[ - _makeSeries(id: 'foo')..data.add(MyRow(-1000, 20)) - ]; - renderer.configureSeries(seriesList); - renderer.preprocessSeries(seriesList); - renderer.update(seriesList, false); - renderer.paint(MockCanvas(), 1.0); - - // Act - // Note: point is in the axis, over a bar outside of the viewport. - final details = renderer.getNearestDatumDetailPerSeries( - Point(-0.0, 20.0 + 100.0 - 5.0), selectNearestByDomain, null); - - // Verify - expect(details.length, equals(0)); - }); - }); -} diff --git a/web/charts/common/test/chart/pie/arc_label_decorator_test.dart b/web/charts/common/test/chart/pie/arc_label_decorator_test.dart deleted file mode 100644 index 50485891d..000000000 --- a/web/charts/common/test/chart/pie/arc_label_decorator_test.dart +++ /dev/null @@ -1,323 +0,0 @@ -// 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 pi, Point, Rectangle; -import 'package:charts_common/src/chart/common/processed_series.dart' - show ImmutableSeries; -import 'package:charts_common/src/common/color.dart' show Color; -import 'package:charts_common/src/common/graphics_factory.dart' - show GraphicsFactory; -import 'package:charts_common/src/common/line_style.dart' show LineStyle; -import 'package:charts_common/src/common/text_element.dart' - show TextDirection, TextElement, MaxWidthStrategy; -import 'package:charts_common/src/common/text_measurement.dart' - show TextMeasurement; -import 'package:charts_common/src/common/text_style.dart' show TextStyle; -import 'package:charts_common/src/chart/cartesian/axis/spec/axis_spec.dart' - show TextStyleSpec; -import 'package:charts_common/src/chart/common/chart_canvas.dart' - show ChartCanvas; -import 'package:charts_common/src/chart/pie/arc_label_decorator.dart' - show ArcLabelDecorator, ArcLabelPosition; -import 'package:charts_common/src/chart/pie/arc_renderer.dart' - show ArcRendererElement, ArcRendererElementList; -import 'package:charts_common/src/data/series.dart' show AccessorFn; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class MockCanvas extends Mock implements ChartCanvas {} - -/// A fake [GraphicsFactory] that returns [FakeTextStyle] and [FakeTextElement]. -class FakeGraphicsFactory extends GraphicsFactory { - @override - TextStyle createTextPaint() => FakeTextStyle(); - - @override - TextElement createTextElement(String text) => FakeTextElement(text); - - @override - LineStyle createLinePaint() => MockLinePaint(); -} - -/// Stores [TextStyle] properties for test to verify. -class FakeTextStyle implements TextStyle { - Color color; - int fontSize; - String fontFamily; -} - -/// Fake [TextElement] which returns text length as [horizontalSliceWidth]. -/// -/// Font size is returned for [verticalSliceWidth] and [baseline]. -class FakeTextElement implements TextElement { - final String text; - TextStyle textStyle; - int maxWidth; - MaxWidthStrategy maxWidthStrategy; - TextDirection textDirection; - double opacity; - - FakeTextElement(this.text); - - TextMeasurement get measurement => TextMeasurement( - horizontalSliceWidth: text.length.toDouble(), - verticalSliceWidth: textStyle.fontSize.toDouble(), - baseline: textStyle.fontSize.toDouble()); -} - -class MockLinePaint extends Mock implements LineStyle {} - -class FakeArcRendererElement extends ArcRendererElement { - final _series = MockImmutableSeries(); - final AccessorFn labelAccessor; - final List data; - - FakeArcRendererElement(this.labelAccessor, this.data) { - when(_series.labelAccessorFn).thenReturn(labelAccessor); - when(_series.data).thenReturn(data); - } - - ImmutableSeries get series => _series; -} - -class MockImmutableSeries extends Mock implements ImmutableSeries {} - -void main() { - ChartCanvas canvas; - GraphicsFactory graphicsFactory; - Rectangle drawBounds; - - setUpAll(() { - canvas = MockCanvas(); - graphicsFactory = FakeGraphicsFactory(); - drawBounds = Rectangle(0, 0, 200, 200); - }); - - group('pie chart', () { - test('Paint labels with default settings', () { - final data = ['A', 'B']; - final arcElements = ArcRendererElementList() - ..arcs = [ - // 'A' is small enough to fit inside the arc. - // 'LongLabelB' should not fit inside the arc because it has length - // greater than 10. - FakeArcRendererElement((_) => 'A', data) - ..startAngle = -pi / 2 - ..endAngle = pi / 2, - FakeArcRendererElement((_) => 'LongLabelB', data) - ..startAngle = pi / 2 - ..endAngle = 3 * pi / 2, - ] - ..center = Point(100.0, 100.0) - ..innerRadius = 30.0 - ..radius = 40.0 - ..startAngle = -pi / 2; - - final decorator = ArcLabelDecorator(); - - decorator.decorate(arcElements, canvas, graphicsFactory, - drawBounds: drawBounds, animationPercent: 1.0); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - // Draw text is called twice (once for each arc) and all 3 parameters were - // captured. Total parameters captured expected to be 6. - expect(captured, hasLength(6)); - // For arc 'A'. - expect(captured[0].maxWidth, equals(10 - decorator.labelPadding)); - expect(captured[0].textDirection, equals(TextDirection.center)); - expect(captured[1], equals(135)); - expect(captured[2], - equals(100 - decorator.insideLabelStyleSpec.fontSize ~/ 2)); - // For arc 'B'. - expect(captured[3].maxWidth, equals(80)); - expect(captured[3].textDirection, equals(TextDirection.rtl)); - expect( - captured[4], - equals(60 - - decorator.leaderLineStyleSpec.length - - decorator.labelPadding * 3)); - expect(captured[5], - equals(100 - decorator.outsideLabelStyleSpec.fontSize ~/ 2)); - }); - - test('LabelPosition.inside always paints inside the arc', () { - final arcElements = ArcRendererElementList() - ..arcs = [ - // 'LongLabelABC' would not fit inside the arc because it has length - // greater than 10. [ArcLabelPosition.inside] should override this. - FakeArcRendererElement((_) => 'LongLabelABC', ['A']) - ..startAngle = -pi / 2 - ..endAngle = pi / 2, - ] - ..center = Point(100.0, 100.0) - ..innerRadius = 30.0 - ..radius = 40.0 - ..startAngle = -pi / 2; - - final decorator = ArcLabelDecorator( - labelPosition: ArcLabelPosition.inside, - insideLabelStyleSpec: TextStyleSpec(fontSize: 10)); - - decorator.decorate(arcElements, canvas, graphicsFactory, - drawBounds: drawBounds, animationPercent: 1.0); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(10 - decorator.labelPadding)); - expect(captured[0].textDirection, equals(TextDirection.center)); - expect(captured[1], equals(135)); - expect(captured[2], - equals(100 - decorator.insideLabelStyleSpec.fontSize ~/ 2)); - }); - - test('LabelPosition.outside always paints outside the arc', () { - final arcElements = ArcRendererElementList() - ..arcs = [ - // 'A' will fit inside the arc because it has length less than 10. - // [ArcLabelPosition.outside] should override this. - FakeArcRendererElement((_) => 'A', ['A']) - ..startAngle = -pi / 2 - ..endAngle = pi / 2, - ] - ..center = Point(100.0, 100.0) - ..innerRadius = 30.0 - ..radius = 40.0 - ..startAngle = -pi / 2; - - final decorator = ArcLabelDecorator( - labelPosition: ArcLabelPosition.outside, - outsideLabelStyleSpec: TextStyleSpec(fontSize: 10)); - - decorator.decorate(arcElements, canvas, graphicsFactory, - drawBounds: drawBounds, animationPercent: 1.0); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - expect(captured, hasLength(3)); - expect(captured[0].maxWidth, equals(40)); - expect(captured[0].textDirection, equals(TextDirection.ltr)); - expect( - captured[1], - equals(140 + - decorator.leaderLineStyleSpec.length + - decorator.labelPadding * 3)); - expect(captured[2], - equals(100 - decorator.outsideLabelStyleSpec.fontSize ~/ 2)); - }); - - test('Inside and outside label styles are applied', () { - final data = ['A', 'B']; - final arcElements = ArcRendererElementList() - ..arcs = [ - // 'A' is small enough to fit inside the arc. - // 'LongLabelB' should not fit inside the arc because it has length - // greater than 10. - FakeArcRendererElement((_) => 'A', data) - ..startAngle = -pi / 2 - ..endAngle = pi / 2, - FakeArcRendererElement((_) => 'LongLabelB', data) - ..startAngle = pi / 2 - ..endAngle = 3 * pi / 2, - ] - ..center = Point(100.0, 100.0) - ..innerRadius = 30.0 - ..radius = 40.0 - ..startAngle = -pi / 2; - - final insideColor = Color(r: 0, g: 0, b: 0); - final outsideColor = Color(r: 255, g: 255, b: 255); - final decorator = ArcLabelDecorator( - labelPadding: 0, - insideLabelStyleSpec: TextStyleSpec( - fontSize: 10, fontFamily: 'insideFont', color: insideColor), - outsideLabelStyleSpec: TextStyleSpec( - fontSize: 8, fontFamily: 'outsideFont', color: outsideColor)); - - decorator.decorate(arcElements, canvas, graphicsFactory, - drawBounds: drawBounds, animationPercent: 1.0); - - final captured = - verify(canvas.drawText(captureAny, captureAny, captureAny)).captured; - // Draw text is called twice (once for each arc) and all 3 parameters were - // captured. Total parameters captured expected to be 6. - expect(captured, hasLength(6)); - // For arc 'A'. - expect(captured[0].maxWidth, equals(10 - decorator.labelPadding)); - expect(captured[0].textDirection, equals(TextDirection.center)); - expect(captured[0].textStyle.fontFamily, equals('insideFont')); - expect(captured[0].textStyle.color, equals(insideColor)); - expect(captured[1], equals(135)); - expect(captured[2], - equals(100 - decorator.insideLabelStyleSpec.fontSize ~/ 2)); - // For arc 'B'. - expect(captured[3].maxWidth, equals(90)); - expect(captured[3].textDirection, equals(TextDirection.rtl)); - expect(captured[3].textStyle.fontFamily, equals('outsideFont')); - expect(captured[3].textStyle.color, equals(outsideColor)); - expect( - captured[4], - equals(50 - - decorator.leaderLineStyleSpec.length - - decorator.labelPadding * 3)); - expect(captured[5], - equals(100 - decorator.outsideLabelStyleSpec.fontSize ~/ 2)); - }); - }); - - group('Null and empty label scenarios', () { - test('Skip label if label accessor does not exist', () { - final arcElements = ArcRendererElementList() - ..arcs = [ - FakeArcRendererElement(null, ['A']) - ..startAngle = -pi / 2 - ..endAngle = pi / 2, - ] - ..center = Point(100.0, 100.0) - ..innerRadius = 30.0 - ..radius = 40.0 - ..startAngle = -pi / 2; - - ArcLabelDecorator().decorate(arcElements, canvas, graphicsFactory, - drawBounds: drawBounds, animationPercent: 1.0); - - verifyNever(canvas.drawText(any, any, any)); - }); - - test('Skip label if label is null or empty', () { - final data = ['A', 'B']; - final arcElements = ArcRendererElementList() - ..arcs = [ - FakeArcRendererElement(null, data) - ..startAngle = -pi / 2 - ..endAngle = pi / 2, - FakeArcRendererElement((_) => '', data) - ..startAngle = pi / 2 - ..endAngle = 3 * pi / 2, - ] - ..center = Point(100.0, 100.0) - ..innerRadius = 30.0 - ..radius = 40.0 - ..startAngle = -pi / 2; - - ArcLabelDecorator().decorate(arcElements, canvas, graphicsFactory, - drawBounds: drawBounds, animationPercent: 1.0); - - verifyNever(canvas.drawText(any, any, any)); - }); - }); -} diff --git a/web/charts/common/test/chart/scatter_plot/comparison_points_decorator_test.dart b/web/charts/common/test/chart/scatter_plot/comparison_points_decorator_test.dart deleted file mode 100644 index 5a48653a8..000000000 --- a/web/charts/common/test/chart/scatter_plot/comparison_points_decorator_test.dart +++ /dev/null @@ -1,220 +0,0 @@ -// 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 'package:charts_common/src/chart/scatter_plot/comparison_points_decorator.dart'; -import 'package:charts_common/src/chart/scatter_plot/point_renderer.dart'; - -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final int campaign; - final int clickCount; - MyRow(this.campaign, this.clickCount); -} - -class TestComparisonPointsDecorator extends ComparisonPointsDecorator { - List> testComputeBoundedPointsForElement( - PointRendererElement pointElement, Rectangle drawBounds) { - return computeBoundedPointsForElement(pointElement, drawBounds); - } -} - -void main() { - TestComparisonPointsDecorator decorator; - Rectangle bounds; - - setUp(() { - decorator = TestComparisonPointsDecorator(); - bounds = Rectangle(0, 0, 100, 100); - }); - - group('compute bounded points', () { - test('with line inside bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 10.0, - xLower: 5.0, - xUpper: 50.0, - y: 20.0, - yLower: 20.0, - yUpper: 20.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points.length, equals(2)); - - expect(points[0].x, equals(5.0)); - expect(points[0].y, equals(20.0)); - - expect(points[1].x, equals(50.0)); - expect(points[1].y, equals(20.0)); - }); - - test('with line entirely above bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 10.0, - xLower: 5.0, - xUpper: 50.0, - y: -20.0, - yLower: -20.0, - yUpper: -20.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points, isNull); - }); - - test('with line entirely below bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 10.0, - xLower: 5.0, - xUpper: 50.0, - y: 120.0, - yLower: 120.0, - yUpper: 120.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points, isNull); - }); - - test('with line entirely left of bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: -10.0, - xLower: -5.0, - xUpper: -50.0, - y: 20.0, - yLower: 20.0, - yUpper: 50.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points, isNull); - }); - - test('with line entirely right of bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 110.0, - xLower: 105.0, - xUpper: 150.0, - y: 20.0, - yLower: 20.0, - yUpper: 50.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points, isNull); - }); - - test('with horizontal line extending beyond bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 10.0, - xLower: -10.0, - xUpper: 110.0, - y: 20.0, - yLower: 20.0, - yUpper: 20.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points.length, equals(2)); - - expect(points[0].x, equals(0.0)); - expect(points[0].y, equals(20.0)); - - expect(points[1].x, equals(100.0)); - expect(points[1].y, equals(20.0)); - }); - - test('with vertical line extending beyond bounds', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 20.0, - xLower: 20.0, - xUpper: 20.0, - y: 10.0, - yLower: -10.0, - yUpper: 110.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points.length, equals(2)); - - expect(points[0].x, equals(20.0)); - expect(points[0].y, equals(0.0)); - - expect(points[1].x, equals(20.0)); - expect(points[1].y, equals(100.0)); - }); - - test('with diagonal from top left to bottom right', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 50.0, - xLower: -50.0, - xUpper: 150.0, - y: 50.0, - yLower: -50.0, - yUpper: 150.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points.length, equals(2)); - - expect(points[0].x, equals(0.0)); - expect(points[0].y, equals(0.0)); - - expect(points[1].x, equals(100.0)); - expect(points[1].y, equals(100.0)); - }); - - test('with diagonal from bottom left to top right', () { - final element = PointRendererElement() - ..point = DatumPoint( - x: 50.0, - xLower: -50.0, - xUpper: 150.0, - y: 50.0, - yLower: 150.0, - yUpper: -50.0); - - final points = - decorator.testComputeBoundedPointsForElement(element, bounds); - - expect(points.length, equals(2)); - - expect(points[0].x, equals(0.0)); - expect(points[0].y, equals(100.0)); - - expect(points[1].x, equals(100.0)); - expect(points[1].y, equals(0.0)); - }); - }); -} diff --git a/web/charts/common/test/chart/scatter_plot/point_renderer_test.dart b/web/charts/common/test/chart/scatter_plot/point_renderer_test.dart deleted file mode 100644 index ab6aaa359..000000000 --- a/web/charts/common/test/chart/scatter_plot/point_renderer_test.dart +++ /dev/null @@ -1,191 +0,0 @@ -// 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 'package:charts_common/src/chart/common/processed_series.dart' - show MutableSeries; -import 'package:charts_common/src/chart/scatter_plot/point_renderer.dart'; -import 'package:charts_common/src/chart/scatter_plot/point_renderer_config.dart'; -import 'package:charts_common/src/common/material_palette.dart' - show MaterialPalette; -import 'package:charts_common/src/data/series.dart' show Series; - -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaignString; - final int campaign; - final int clickCount; - final double radius; - final double boundsRadius; - final String shape; - MyRow(this.campaignString, this.campaign, this.clickCount, this.radius, - this.boundsRadius, this.shape); -} - -void main() { - PointRenderer renderer; - List> numericSeriesList; - - setUp(() { - var myFakeDesktopData = [ - // This datum should get a default bounds line radius value. - MyRow('MyCampaign1', 0, 5, 3.0, null, null), - MyRow('MyCampaign2', 10, 25, 5.0, 4.0, 'shape 1'), - MyRow('MyCampaign3', 12, 75, 4.0, 4.0, 'shape 2'), - // This datum should always get default radius values. - MyRow('MyCampaign4', 13, 225, null, null, null), - ]; - - final maxMeasure = 300; - - numericSeriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (row, _) { - // Color bucket the measure column value into 3 distinct colors. - final bucket = row.clickCount / maxMeasure; - - if (bucket < 1 / 3) { - return MaterialPalette.blue.shadeDefault; - } else if (bucket < 2 / 3) { - return MaterialPalette.red.shadeDefault; - } else { - return MaterialPalette.green.shadeDefault; - } - }, - domainFn: (row, _) => row.campaign, - measureFn: (row, _) => row.clickCount, - measureOffsetFn: (row, _) => 0, - radiusPxFn: (row, _) => row.radius, - data: myFakeDesktopData) - // Define a bounds line radius function. - ..setAttribute(boundsLineRadiusPxFnKey, - (index) => myFakeDesktopData[index].boundsRadius)) - ]; - }); - - group('preprocess', () { - test('with numeric data and simple points', () { - renderer = PointRenderer(config: PointRendererConfig()); - - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(1)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var keyFn = series.keyFn; - - var elementsList = series.getAttr(pointElementsKey); - expect(elementsList.length, equals(4)); - - expect(elementsList[0].radiusPx, equals(3.0)); - expect(elementsList[1].radiusPx, equals(5.0)); - expect(elementsList[2].radiusPx, equals(4.0)); - expect(elementsList[3].radiusPx, equals(3.5)); - - expect(elementsList[0].boundsLineRadiusPx, equals(3.0)); - expect(elementsList[1].boundsLineRadiusPx, equals(4.0)); - expect(elementsList[2].boundsLineRadiusPx, equals(4.0)); - expect(elementsList[3].boundsLineRadiusPx, equals(3.5)); - - expect(elementsList[0].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[1].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[2].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[3].symbolRendererId, equals(defaultSymbolRendererId)); - - expect(keyFn(0), equals('Desktop__0__5')); - expect(keyFn(1), equals('Desktop__10__25')); - expect(keyFn(2), equals('Desktop__12__75')); - expect(keyFn(3), equals('Desktop__13__225')); - }); - - test('with numeric data and missing radiusPxFn', () { - renderer = PointRenderer( - config: PointRendererConfig(radiusPx: 2.0, boundsLineRadiusPx: 1.5)); - - // Remove the radius functions to test configured defaults. - numericSeriesList[0].radiusPxFn = null; - numericSeriesList[0].setAttr(boundsLineRadiusPxFnKey, null); - - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(1)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var elementsList = series.getAttr(pointElementsKey); - expect(elementsList.length, equals(4)); - - expect(elementsList[0].radiusPx, equals(2.0)); - expect(elementsList[1].radiusPx, equals(2.0)); - expect(elementsList[2].radiusPx, equals(2.0)); - expect(elementsList[3].radiusPx, equals(2.0)); - - expect(elementsList[0].boundsLineRadiusPx, equals(1.5)); - expect(elementsList[1].boundsLineRadiusPx, equals(1.5)); - expect(elementsList[2].boundsLineRadiusPx, equals(1.5)); - expect(elementsList[3].boundsLineRadiusPx, equals(1.5)); - }); - - test('with custom symbol renderer ID in data', () { - renderer = PointRenderer(config: PointRendererConfig()); - - numericSeriesList[0].setAttr(pointSymbolRendererFnKey, - (index) => numericSeriesList[0].data[index].shape as String); - - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(1)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var elementsList = series.getAttr(pointElementsKey); - expect(elementsList.length, equals(4)); - - expect(elementsList[0].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[1].symbolRendererId, equals('shape 1')); - expect(elementsList[2].symbolRendererId, equals('shape 2')); - expect(elementsList[3].symbolRendererId, equals(defaultSymbolRendererId)); - }); - - test('with custom symbol renderer ID in series and data', () { - renderer = PointRenderer(config: PointRendererConfig()); - - numericSeriesList[0].setAttr(pointSymbolRendererFnKey, - (index) => numericSeriesList[0].data[index].shape as String); - numericSeriesList[0].setAttr(pointSymbolRendererIdKey, 'shape 0'); - - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(1)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var elementsList = series.getAttr(pointElementsKey); - expect(elementsList.length, equals(4)); - - expect(elementsList[0].symbolRendererId, equals('shape 0')); - expect(elementsList[1].symbolRendererId, equals('shape 1')); - expect(elementsList[2].symbolRendererId, equals('shape 2')); - expect(elementsList[3].symbolRendererId, equals('shape 0')); - }); - }); -} diff --git a/web/charts/common/test/chart/scatter_plot/symbol_annotation_renderer_test.dart b/web/charts/common/test/chart/scatter_plot/symbol_annotation_renderer_test.dart deleted file mode 100644 index 35a1898fd..000000000 --- a/web/charts/common/test/chart/scatter_plot/symbol_annotation_renderer_test.dart +++ /dev/null @@ -1,109 +0,0 @@ -// 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 'package:charts_common/src/chart/common/processed_series.dart' - show MutableSeries; -import 'package:charts_common/src/chart/scatter_plot/point_renderer.dart'; -import 'package:charts_common/src/chart/scatter_plot/symbol_annotation_renderer.dart'; -import 'package:charts_common/src/chart/scatter_plot/symbol_annotation_renderer_config.dart'; -import 'package:charts_common/src/common/material_palette.dart' - show MaterialPalette; -import 'package:charts_common/src/data/series.dart' show Series; - -import 'package:test/test.dart'; - -/// Datum/Row for the chart. -class MyRow { - final String campaignString; - final int campaign; - final int campaignLower; - final int campaignUpper; - final double radius; - final double boundsRadius; - final String shape; - MyRow(this.campaignString, this.campaign, this.campaignLower, - this.campaignUpper, this.radius, this.boundsRadius, this.shape); -} - -void main() { - SymbolAnnotationRenderer renderer; - List> numericSeriesList; - - setUp(() { - var myFakeDesktopData = [ - // This datum should get a default bounds line radius value. - MyRow('MyCampaign1', 0, 0, 0, 3.0, null, null), - MyRow('MyCampaign2', 10, 10, 12, 5.0, 4.0, 'shape 1'), - MyRow('MyCampaign3', 10, 10, 14, 4.0, 4.0, 'shape 2'), - // This datum should always get default radius values. - MyRow('MyCampaign4', 13, 12, 15, null, null, null), - ]; - - numericSeriesList = [ - MutableSeries(Series( - id: 'Desktop', - colorFn: (row, _) => MaterialPalette.blue.shadeDefault, - domainFn: (row, _) => row.campaign, - domainLowerBoundFn: (row, _) => row.campaignLower, - domainUpperBoundFn: (row, _) => row.campaignUpper, - measureFn: (row, _) => 0, - measureOffsetFn: (row, _) => 0, - radiusPxFn: (row, _) => row.radius, - data: myFakeDesktopData) - // Define a bounds line radius function. - ..setAttribute(boundsLineRadiusPxFnKey, - (index) => myFakeDesktopData[index].boundsRadius)) - ]; - }); - - group('preprocess', () { - test('with numeric data and simple points', () { - renderer = SymbolAnnotationRenderer( - config: SymbolAnnotationRendererConfig()); - - renderer.preprocessSeries(numericSeriesList); - - expect(numericSeriesList.length, equals(1)); - - // Validate Desktop series. - var series = numericSeriesList[0]; - - var keyFn = series.keyFn; - - var elementsList = series.getAttr(pointElementsKey); - expect(elementsList.length, equals(4)); - - expect(elementsList[0].radiusPx, equals(3.0)); - expect(elementsList[1].radiusPx, equals(5.0)); - expect(elementsList[2].radiusPx, equals(4.0)); - expect(elementsList[3].radiusPx, equals(5.0)); - - expect(elementsList[0].boundsLineRadiusPx, equals(3.0)); - expect(elementsList[1].boundsLineRadiusPx, equals(4.0)); - expect(elementsList[2].boundsLineRadiusPx, equals(4.0)); - expect(elementsList[3].boundsLineRadiusPx, equals(5.0)); - - expect(elementsList[0].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[1].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[2].symbolRendererId, equals(defaultSymbolRendererId)); - expect(elementsList[3].symbolRendererId, equals(defaultSymbolRendererId)); - - expect(keyFn(0), equals('Desktop__0__0__0')); - expect(keyFn(1), equals('Desktop__10__10__12')); - expect(keyFn(2), equals('Desktop__10__10__14')); - expect(keyFn(3), equals('Desktop__13__12__15')); - }); - }); -} diff --git a/web/charts/example/pubspec.lock b/web/charts/example/pubspec.lock deleted file mode 100644 index ba1a84395..000000000 --- a/web/charts/example/pubspec.lock +++ /dev/null @@ -1,492 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - charts_common: - dependency: transitive - description: - path: "../common" - relative: true - source: path - version: "0.6.0" - charts_flutter: - dependency: "direct main" - description: - path: "../flutter" - relative: true - source: path - version: "0.6.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: - dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - flutter_web_ui: - dependency: "direct overridden" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: "direct main" - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" - meta: - dependency: "direct main" - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" -sdks: - dart: ">=2.3.0 <3.0.0" diff --git a/web/charts/example/pubspec.yaml b/web/charts/example/pubspec.yaml deleted file mode 100644 index 32987efdf..000000000 --- a/web/charts/example/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: example -description: Charts-Flutter Demo -dependencies: - charts_flutter: - path: ../flutter - flutter_web: any - meta: ^1.1.1 - intl: ^0.15.2 - -dev_dependencies: - build_runner: any - build_web_compilers: any - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui diff --git a/web/charts/example/web/assets/FontManifest.json b/web/charts/example/web/assets/FontManifest.json deleted file mode 100644 index 43fa68900..000000000 --- a/web/charts/example/web/assets/FontManifest.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "family": "MaterialIcons", - "fonts": [ - { - "asset": "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" - } - ] - } -] diff --git a/web/charts/example/web/main.dart b/web/charts/example/web/main.dart deleted file mode 100644 index f12316cdf..000000000 --- a/web/charts/example/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:example/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/charts/flutter/CHANGELOG.md b/web/charts/flutter/CHANGELOG.md deleted file mode 100644 index 331518adc..000000000 --- a/web/charts/flutter/CHANGELOG.md +++ /dev/null @@ -1,53 +0,0 @@ -# 0.6.0 -* Bars can now be rendered on line charts. -* Negative measure values will now be rendered on bar charts as a separate stack from the positive -values. -* Added a Datum Legend, which displays one entry per value in the first series on the chart. This is - useful for pie and scatter plot charts. -* The AxisPosition enum in RTLSpec was refactored to AxisDirection to better reflect its effect on -swapping the positions of all start and end components, and not just positioning the measure axes. -* Added custom colors for line renderer area skirts and confidence intervals. A new "areaColorFn" -has been added to Series, and corresponding data to the datum. We could not use the fillColorFn for -these elements, because that color is already applied to the internal section of points on line -charts (including highlighter behaviors). - -# 0.5.0 -* SelectionModelConfig's listener parameter has been renamed to "changeListener". This is a breaking -change. Please rename any existing uses of the "listener" parameter to "changeListener". This was -named in order to add an additional listener "updateListener" that listens to any update requests, -regardless if the selection model has changed. -* CartesianChart's method getMeasureAxis(String axisId) has been changed to -getMeasureAxis({String axisId) so that getting the primary measure axis will not need passing any id -that does not match the secondary measure axis id. This affects users implementing custom behaviors -using the existing method. - -# 0.4.0 -* Fixed export file to export ChartsBehavior in the Flutter library instead of the one that resides -in charts_common. The charts_common behavior should not be used except internally in the -charts_flutter library. This is a breaking change if you are using charts_common behavior. -* Declare compatibility with Dart 2. -* BasicNumericTickFormatterSpec now takes in a callback instead of NumberFormat as the default -constructor. Use named constructor withNumberFormat instead. This is a breaking change. -* BarRendererConfig is no longer default of type String, please change current usage to -BarRendererConfig. This is a breaking change. -* BarTargetLineRendererConfig is no longer default of type String, please change current usage to -BarTargetLineRendererConfig. This is a breaking change. - -# 0.3.0 -* Simplified API by removing the requirement for specifying the datum type when creating a chart. -For example, previously to construct a bar chart the syntax was 'new BarChart()'. -The syntax is now cleaned up to be 'new BarChart()'. Please refer to the -[online gallery](https://google.github.io/charts/flutter/gallery.html) for the correct syntax. -* Added scatter plot charts -* Added tap to hide for legends -* Added support for rendering area skirts to line charts -* Added support for configurable fill colors to bar charts - -# 0.2.0 - -* Update color palette. Please use MaterialPalette instead of QuantumPalette. -* Dart2 fixes - -# 0.1.0 - -Initial release. diff --git a/web/charts/flutter/LICENSE b/web/charts/flutter/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/web/charts/flutter/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/web/charts/flutter/README.md b/web/charts/flutter/README.md deleted file mode 100644 index 83ea081ff..000000000 --- a/web/charts/flutter/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Flutter Charting library - -[![pub package](https://img.shields.io/pub/v/charts_flutter.svg)](https://pub.dartlang.org/packages/charts_flutter) - -Material Design data visualization library written natively in Dart. - -## Supported charts - -See the [online gallery](https://google.github.io/charts/flutter/gallery.html). - -## Using the library - -The `/example/` folder inside `charts_flutter` in the [GitHub repo](https://github.com/google/charts) -contains a full Flutter app with many demo examples. diff --git a/web/charts/flutter/lib/flutter.dart b/web/charts/flutter/lib/flutter.dart deleted file mode 100644 index 84047edd8..000000000 --- a/web/charts/flutter/lib/flutter.dart +++ /dev/null @@ -1,191 +0,0 @@ -// 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. - -export 'package:charts_common/common.dart' - show - boundsLineRadiusPxFnKey, - boundsLineRadiusPxKey, - measureAxisIdKey, - pointSymbolRendererFnKey, - pointSymbolRendererIdKey, - rendererIdKey, - AnnotationLabelAnchor, - AnnotationLabelDirection, - AnnotationLabelPosition, - ArcLabelDecorator, - ArcLabelLeaderLineStyleSpec, - ArcLabelPosition, - ArcRenderer, - ArcRendererConfig, - AutoDateTimeTickFormatterSpec, - AutoDateTimeTickProviderSpec, - Axis, - AxisDirection, - AxisSpec, - BarGroupingType, - BarLabelAnchor, - BarLabelDecorator, - BarLabelPosition, - BarLaneRendererConfig, - BarRenderer, - BarRendererConfig, - BarTargetLineRenderer, - BarTargetLineRendererConfig, - BaseCartesianRenderer, - BasicNumericTickFormatterSpec, - BasicNumericTickProviderSpec, - BasicOrdinalTickProviderSpec, - BasicOrdinalTickFormatterSpec, - BehaviorPosition, - BucketingAxisSpec, - BucketingNumericTickProviderSpec, - CartesianChart, - ChartCanvas, - ChartContext, - ChartTitleDirection, - CircleSymbolRenderer, - Color, - ComparisonPointsDecorator, - ConstCornerStrategy, - CornerStrategy, - CylinderSymbolRenderer, - DateTimeAxisSpec, - DateTimeEndPointsTickProviderSpec, - DateTimeExtents, - DateTimeFactory, - DateTimeTickFormatter, - DateTimeTickFormatterSpec, - DateTimeTickProviderSpec, - DayTickProviderSpec, - DomainFormatter, - EndPointsTimeAxisSpec, - ExploreModeTrigger, - FillPatternType, - GestureListener, - GraphicsFactory, - GridlineRendererSpec, - ImmutableSeries, - InsideJustification, - LayoutPosition, - LayoutViewPaintOrder, - LayoutViewPositionOrder, - LegendDefaultMeasure, - LegendTapHandling, - LineAnnotationSegment, - LinePointHighlighterFollowLineType, - LineRenderer, - LineRendererConfig, - LineStyleSpec, - LocalDateTimeFactory, - LockSelection, - MarginSpec, - MaterialPalette, - MaterialStyle, - MaxWidthStrategy, - MeasureFormatter, - NoCornerStrategy, - NoneRenderSpec, - NumericAxis, - NumericAxisSpec, - NumericCartesianChart, - NumericEndPointsTickProviderSpec, - NumericExtents, - NumericTickFormatterSpec, - NumericTickProviderSpec, - OrdinalAxis, - OrdinalAxisSpec, - OrdinalCartesianChart, - OrdinalTickFormatterSpec, - OrdinalTickProviderSpec, - OrdinalViewport, - OutsideJustification, - PanningCompletedCallback, - PercentAxisSpec, - PercentInjectorTotalType, - Performance, - PointRenderer, - PointRendererConfig, - PointRendererDecorator, - PointSymbolRenderer, - RangeAnnotationAxisType, - RangeAnnotationSegment, - RectSymbolRenderer, - RenderSpec, - RTLSpec, - SelectionModel, - SelectionModelListener, - SelectionModelType, - SelectionTrigger, - Series, - SeriesDatum, - SeriesDatumConfig, - SeriesRenderer, - SeriesRendererConfig, - SimpleTickFormatterBase, - SliderListenerCallback, - SliderListenerDragState, - SliderStyle, - SmallTickRendererSpec, - StaticDateTimeTickProviderSpec, - StaticNumericTickProviderSpec, - StaticOrdinalTickProviderSpec, - StyleFactory, - SymbolAnnotationRenderer, - SymbolAnnotationRendererConfig, - TextStyleSpec, - TickFormatter, - TickFormatterSpec, - TickLabelAnchor, - TickLabelJustification, - TickSpec, - TimeFormatterSpec, - TypedAccessorFn, - UTCDateTimeFactory, - ViewMargin, - VocalizationCallback; - -export 'src/bar_chart.dart'; -export 'src/base_chart.dart' show BaseChart, LayoutConfig; -export 'src/behaviors/a11y/domain_a11y_explore_behavior.dart' - show DomainA11yExploreBehavior; -export 'src/behaviors/chart_behavior.dart' show ChartBehavior; -export 'src/behaviors/domain_highlighter.dart' show DomainHighlighter; -export 'src/behaviors/initial_selection.dart' show InitialSelection; -export 'src/behaviors/calculation/percent_injector.dart' show PercentInjector; -export 'src/behaviors/chart_title/chart_title.dart' show ChartTitle; -export 'src/behaviors/legend/datum_legend.dart' show DatumLegend; -export 'src/behaviors/legend/legend_content_builder.dart' - show LegendContentBuilder, TabularLegendContentBuilder; -export 'src/behaviors/legend/legend_layout.dart' - show LegendLayout, TabularLegendLayout; -export 'src/behaviors/legend/series_legend.dart' show SeriesLegend; -export 'src/behaviors/line_point_highlighter.dart' show LinePointHighlighter; -export 'src/behaviors/range_annotation.dart' show RangeAnnotation; -export 'src/behaviors/select_nearest.dart' show SelectNearest; -export 'src/behaviors/sliding_viewport.dart' show SlidingViewport; -export 'src/behaviors/slider/slider.dart' show Slider; -export 'src/behaviors/zoom/initial_hint_behavior.dart' show InitialHintBehavior; -export 'src/behaviors/zoom/pan_and_zoom_behavior.dart' show PanAndZoomBehavior; -export 'src/behaviors/zoom/pan_behavior.dart' show PanBehavior; -export 'src/combo_chart/combo_chart.dart'; -export 'src/line_chart.dart'; -export 'src/pie_chart.dart'; -export 'src/scatter_plot_chart.dart'; -export 'src/selection_model_config.dart' show SelectionModelConfig; -export 'src/symbol_renderer.dart' show CustomSymbolRenderer; -export 'src/time_series_chart.dart'; -export 'src/user_managed_state.dart' - show UserManagedState, UserManagedSelectionModel; -export 'src/util/color.dart' show ColorUtil; diff --git a/web/charts/flutter/lib/src/bar_chart.dart b/web/charts/flutter/lib/src/bar_chart.dart deleted file mode 100644 index f8777916c..000000000 --- a/web/charts/flutter/lib/src/bar_chart.dart +++ /dev/null @@ -1,104 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import 'package:charts_common/common.dart' as common - show - AxisSpec, - BarChart, - BarGroupingType, - BarRendererConfig, - BarRendererDecorator, - NumericAxisSpec, - RTLSpec, - Series, - SeriesRendererConfig; -import 'behaviors/domain_highlighter.dart' show DomainHighlighter; -import 'behaviors/chart_behavior.dart' show ChartBehavior; -import 'package:meta/meta.dart' show immutable; -import 'base_chart.dart' show LayoutConfig; -import 'base_chart_state.dart' show BaseChartState; -import 'cartesian_chart.dart' show CartesianChart; -import 'selection_model_config.dart' show SelectionModelConfig; -import 'user_managed_state.dart' show UserManagedState; - -@immutable -class BarChart extends CartesianChart { - final bool vertical; - final common.BarRendererDecorator barRendererDecorator; - - BarChart( - List> seriesList, { - bool animate, - Duration animationDuration, - common.AxisSpec domainAxis, - common.AxisSpec primaryMeasureAxis, - common.AxisSpec secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes, - common.BarGroupingType barGroupingType, - common.BarRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - this.vertical: true, - bool defaultInteractions: true, - LayoutConfig layoutConfig, - UserManagedState userManagedState, - this.barRendererDecorator, - bool flipVerticalAxis, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - domainAxis: domainAxis, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes, - defaultRenderer: defaultRenderer ?? - new common.BarRendererConfig( - groupingType: barGroupingType, - barRendererDecorator: barRendererDecorator), - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - defaultInteractions: defaultInteractions, - layoutConfig: layoutConfig, - userManagedState: userManagedState, - flipVerticalAxis: flipVerticalAxis, - ); - - @override - common.BarChart createCommonChart(BaseChartState chartState) { - // Optionally create primary and secondary measure axes if the chart was - // configured with them. If no axes were configured, then the chart will - // use its default types (usually a numeric axis). - return new common.BarChart( - vertical: vertical, - layoutConfig: layoutConfig?.commonLayoutConfig, - primaryMeasureAxis: primaryMeasureAxis?.createAxis(), - secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), - disjointMeasureAxes: createDisjointMeasureAxes()); - } - - @override - void addDefaultInteractions(List behaviors) { - super.addDefaultInteractions(behaviors); - - behaviors.add(new DomainHighlighter()); - } -} diff --git a/web/charts/flutter/lib/src/base_chart.dart b/web/charts/flutter/lib/src/base_chart.dart deleted file mode 100644 index d240e2d89..000000000 --- a/web/charts/flutter/lib/src/base_chart.dart +++ /dev/null @@ -1,279 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - BaseChart, - LayoutConfig, - MarginSpec, - Performance, - RTLSpec, - Series, - SeriesRendererConfig, - SelectionModelType, - SelectionTrigger; -import 'behaviors/select_nearest.dart' show SelectNearest; -import 'package:meta/meta.dart' show immutable, required; -import 'behaviors/chart_behavior.dart' - show ChartBehavior, ChartStateBehavior, GestureType; -import 'selection_model_config.dart' show SelectionModelConfig; -import 'package:flutter_web/material.dart' show StatefulWidget; -import 'base_chart_state.dart' show BaseChartState; -import 'user_managed_state.dart' show UserManagedState; - -@immutable -abstract class BaseChart extends StatefulWidget { - /// Series list to draw. - final List> seriesList; - - /// Animation transitions. - final bool animate; - final Duration animationDuration; - - /// Used to configure the margin sizes around the drawArea that the axis and - /// other things render into. - final LayoutConfig layoutConfig; - - // Default renderer used to draw series data on the chart. - final common.SeriesRendererConfig defaultRenderer; - - /// Include the default interactions or not. - final bool defaultInteractions; - - final List behaviors; - - final List> selectionModels; - - // List of custom series renderers used to draw series data on the chart. - // - // Series assigned a rendererIdKey will be drawn with the matching renderer in - // this list. Series without a rendererIdKey will be drawn by the default - // renderer. - final List> customSeriesRenderers; - - /// The spec to use if RTL is enabled. - final common.RTLSpec rtlSpec; - - /// Optional state that overrides internally kept state, such as selection. - final UserManagedState userManagedState; - - BaseChart(this.seriesList, - {bool animate, - Duration animationDuration, - this.defaultRenderer, - this.customSeriesRenderers, - this.behaviors, - this.selectionModels, - this.rtlSpec, - this.defaultInteractions = true, - this.layoutConfig, - this.userManagedState}) - : this.animate = animate ?? true, - this.animationDuration = - animationDuration ?? const Duration(milliseconds: 300); - - @override - BaseChartState createState() => new BaseChartState(); - - /// Creates and returns a [common.BaseChart]. - common.BaseChart createCommonChart(BaseChartState chartState); - - /// Updates the [common.BaseChart]. - void updateCommonChart(common.BaseChart chart, BaseChart oldWidget, - BaseChartState chartState) { - common.Performance.time('chartsUpdateRenderers'); - // Set default renderer if one was provided. - if (defaultRenderer != null && - defaultRenderer != oldWidget?.defaultRenderer) { - chart.defaultRenderer = defaultRenderer.build(); - chartState.markChartDirty(); - } - - // Add custom series renderers if any were provided. - if (customSeriesRenderers != null) { - // TODO: This logic does not remove old renderers and - // shouldn't require the series configs to remain in the same order. - for (var i = 0; i < customSeriesRenderers.length; i++) { - if (oldWidget == null || - (oldWidget.customSeriesRenderers != null && - i > oldWidget.customSeriesRenderers.length) || - customSeriesRenderers[i] != oldWidget.customSeriesRenderers[i]) { - chart.addSeriesRenderer(customSeriesRenderers[i].build()); - chartState.markChartDirty(); - } - } - } - common.Performance.timeEnd('chartsUpdateRenderers'); - - common.Performance.time('chartsUpdateBehaviors'); - _updateBehaviors(chart, chartState); - common.Performance.timeEnd('chartsUpdateBehaviors'); - - _updateSelectionModel(chart, chartState); - - chart.transition = animate ? animationDuration : Duration.zero; - } - - void _updateBehaviors(common.BaseChart chart, BaseChartState chartState) { - final behaviorList = behaviors != null - ? new List.from(behaviors) - : []; - - // Insert automatic behaviors to the front of the behavior list. - if (defaultInteractions) { - if (chartState.autoBehaviorWidgets.isEmpty) { - addDefaultInteractions(chartState.autoBehaviorWidgets); - } - - // Add default interaction behaviors to the front of the list if they - // don't conflict with user behaviors by role. - chartState.autoBehaviorWidgets.reversed - .where(_notACustomBehavior) - .forEach((ChartBehavior behavior) { - behaviorList.insert(0, behavior); - }); - } - - // Remove any behaviors from the chart that are not in the incoming list. - // Walk in reverse order they were added. - // Also, remove any persisting behaviors from incoming list. - for (int i = chartState.addedBehaviorWidgets.length - 1; i >= 0; i--) { - final addedBehavior = chartState.addedBehaviorWidgets[i]; - if (!behaviorList.remove(addedBehavior)) { - final role = addedBehavior.role; - chartState.addedBehaviorWidgets.remove(addedBehavior); - chartState.addedCommonBehaviorsByRole.remove(role); - chart.removeBehavior(chartState.addedCommonBehaviorsByRole[role]); - chartState.markChartDirty(); - } - } - - // Add any remaining/new behaviors. - behaviorList.forEach((ChartBehavior behaviorWidget) { - final commonBehavior = chart - .createBehavior(() => behaviorWidget.createCommonBehavior()); - - // Assign the chart state to any behavior that needs it. - if (commonBehavior is ChartStateBehavior) { - (commonBehavior as ChartStateBehavior).chartState = chartState; - } - - chart.addBehavior(commonBehavior); - chartState.addedBehaviorWidgets.add(behaviorWidget); - chartState.addedCommonBehaviorsByRole[behaviorWidget.role] = - commonBehavior; - chartState.markChartDirty(); - }); - } - - /// Create the list of default interaction behaviors. - void addDefaultInteractions(List behaviors) { - // Update selection model - behaviors.add(new SelectNearest( - eventTrigger: common.SelectionTrigger.tap, - selectionModelType: common.SelectionModelType.info, - expandToDomain: true, - selectClosestSeries: true)); - } - - bool _notACustomBehavior(ChartBehavior behavior) { - return this.behaviors == null || - !this.behaviors.any( - (ChartBehavior userBehavior) => userBehavior.role == behavior.role); - } - - void _updateSelectionModel( - common.BaseChart chart, BaseChartState chartState) { - final prevTypes = new List.from( - chartState.addedSelectionChangedListenersByType.keys); - - // Update any listeners for each type. - selectionModels?.forEach((SelectionModelConfig model) { - final selectionModel = chart.getSelectionModel(model.type); - - final prevChangedListener = - chartState.addedSelectionChangedListenersByType[model.type]; - if (!identical(model.changedListener, prevChangedListener)) { - selectionModel.removeSelectionChangedListener(prevChangedListener); - selectionModel.addSelectionChangedListener(model.changedListener); - chartState.addedSelectionChangedListenersByType[model.type] = - model.changedListener; - } - - final prevUpdatedListener = - chartState.addedSelectionUpdatedListenersByType[model.type]; - if (!identical(model.updatedListener, prevUpdatedListener)) { - selectionModel.removeSelectionUpdatedListener(prevUpdatedListener); - selectionModel.addSelectionUpdatedListener(model.updatedListener); - chartState.addedSelectionUpdatedListenersByType[model.type] = - model.updatedListener; - } - - prevTypes.remove(model.type); - }); - - // Remove any lingering listeners. - prevTypes.forEach((common.SelectionModelType type) { - chart.getSelectionModel(type) - ..removeSelectionChangedListener( - chartState.addedSelectionChangedListenersByType[type]) - ..removeSelectionUpdatedListener( - chartState.addedSelectionUpdatedListenersByType[type]); - }); - } - - /// Gets distinct set of gestures this chart will subscribe to. - /// - /// This is needed to allow setup of the [GestureDetector] widget with only - /// gestures we need to listen to and it must wrap [ChartContainer] widget. - /// Gestures are then setup to be proxied in [common.BaseChart] and that is - /// held by [ChartContainerRenderObject]. - Set getDesiredGestures(BaseChartState chartState) { - final types = new Set(); - behaviors?.forEach((ChartBehavior behavior) { - types.addAll(behavior.desiredGestures); - }); - - if (defaultInteractions && chartState.autoBehaviorWidgets.isEmpty) { - addDefaultInteractions(chartState.autoBehaviorWidgets); - } - - chartState.autoBehaviorWidgets.forEach((ChartBehavior behavior) { - types.addAll(behavior.desiredGestures); - }); - return types; - } -} - -@immutable -class LayoutConfig { - final common.MarginSpec leftMarginSpec; - final common.MarginSpec topMarginSpec; - final common.MarginSpec rightMarginSpec; - final common.MarginSpec bottomMarginSpec; - - LayoutConfig({ - @required this.leftMarginSpec, - @required this.topMarginSpec, - @required this.rightMarginSpec, - @required this.bottomMarginSpec, - }); - - common.LayoutConfig get commonLayoutConfig => new common.LayoutConfig( - leftSpec: leftMarginSpec, - topSpec: topMarginSpec, - rightSpec: rightMarginSpec, - bottomSpec: bottomMarginSpec); -} diff --git a/web/charts/flutter/lib/src/base_chart_state.dart b/web/charts/flutter/lib/src/base_chart_state.dart deleted file mode 100644 index 82b15957b..000000000 --- a/web/charts/flutter/lib/src/base_chart_state.dart +++ /dev/null @@ -1,179 +0,0 @@ -// 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 'package:flutter_web_ui/ui.dart' show TextDirection; -import 'package:flutter_web/material.dart' - show - AnimationController, - BuildContext, - State, - TickerProviderStateMixin, - Widget; -import 'package:charts_common/common.dart' as common; -import 'package:flutter_web/widgets.dart' - show Directionality, LayoutId, CustomMultiChildLayout; -import 'behaviors/chart_behavior.dart' - show BuildableBehavior, ChartBehavior, ChartStateBehavior; -import 'base_chart.dart' show BaseChart; -import 'chart_container.dart' show ChartContainer; -import 'chart_state.dart' show ChartState; -import 'chart_gesture_detector.dart' show ChartGestureDetector; -import 'widget_layout_delegate.dart'; - -class BaseChartState extends State> - with TickerProviderStateMixin - implements ChartState { - // Animation - AnimationController _animationController; - double _animationValue = 0.0; - - Widget _oldWidget; - - ChartGestureDetector _chartGestureDetector; - - bool _configurationChanged = false; - - final autoBehaviorWidgets = []; - final addedBehaviorWidgets = []; - final addedCommonBehaviorsByRole = {}; - - final addedSelectionChangedListenersByType = - >{}; - final addedSelectionUpdatedListenersByType = - >{}; - - final _behaviorAnimationControllers = - {}; - - static const chartContainerLayoutID = 'chartContainer'; - - @override - void initState() { - super.initState(); - _animationController = new AnimationController(vsync: this) - ..addListener(_animationTick); - } - - @override - void requestRebuild() { - setState(() {}); - } - - @override - void markChartDirty() { - _configurationChanged = true; - } - - @override - void resetChartDirtyFlag() { - _configurationChanged = false; - } - - @override - bool get chartIsDirty => _configurationChanged; - - /// Builds the common chart canvas widget. - Widget _buildChartContainer() { - final chartContainer = new ChartContainer( - oldChartWidget: _oldWidget, - chartWidget: widget, - chartState: this, - animationValue: _animationValue, - rtl: Directionality.of(context) == TextDirection.rtl, - rtlSpec: widget.rtlSpec, - userManagedState: widget.userManagedState, - ); - _oldWidget = widget; - - final desiredGestures = widget.getDesiredGestures(this); - if (desiredGestures.isNotEmpty) { - _chartGestureDetector ??= new ChartGestureDetector(); - return _chartGestureDetector.makeWidget( - context, chartContainer, desiredGestures); - } else { - return chartContainer; - } - } - - @override - Widget build(BuildContext context) { - final chartWidgets = []; - final idAndBehaviorMap = {}; - - // Add the common chart canvas widget. - chartWidgets.add(new LayoutId( - id: chartContainerLayoutID, child: _buildChartContainer())); - - // Add widget for each behavior that can build widgets - addedCommonBehaviorsByRole.forEach((id, behavior) { - if (behavior is BuildableBehavior) { - assert(id != chartContainerLayoutID); - - final buildableBehavior = behavior as BuildableBehavior; - idAndBehaviorMap[id] = buildableBehavior; - - final widget = buildableBehavior.build(context); - chartWidgets.add(new LayoutId(id: id, child: widget)); - } - }); - - final isRTL = Directionality.of(context) == TextDirection.rtl; - - return new CustomMultiChildLayout( - delegate: new WidgetLayoutDelegate( - chartContainerLayoutID, idAndBehaviorMap, isRTL), - children: chartWidgets); - } - - @override - void dispose() { - _animationController.dispose(); - _behaviorAnimationControllers - .forEach((_, controller) => controller?.dispose()); - _behaviorAnimationControllers.clear(); - super.dispose(); - } - - @override - void setAnimation(Duration transition) { - _playAnimation(transition); - } - - void _playAnimation(Duration duration) { - _animationController.duration = duration; - _animationController.forward(from: (duration == Duration.zero) ? 1.0 : 0.0); - _animationValue = _animationController.value; - } - - void _animationTick() { - setState(() { - _animationValue = _animationController.value; - }); - } - - /// Get animation controller to be used by [behavior]. - AnimationController getAnimationController(ChartStateBehavior behavior) { - _behaviorAnimationControllers[behavior] ??= - new AnimationController(vsync: this); - - return _behaviorAnimationControllers[behavior]; - } - - /// Dispose of animation controller used by [behavior]. - void disposeAnimationController(ChartStateBehavior behavior) { - final controller = _behaviorAnimationControllers.remove(behavior); - controller?.dispose(); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/a11y/domain_a11y_explore_behavior.dart b/web/charts/flutter/lib/src/behaviors/a11y/domain_a11y_explore_behavior.dart deleted file mode 100644 index bc5e22adc..000000000 --- a/web/charts/flutter/lib/src/behaviors/a11y/domain_a11y_explore_behavior.dart +++ /dev/null @@ -1,112 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show DomainA11yExploreBehavior, VocalizationCallback, ExploreModeTrigger; -import 'package:flutter_web/widgets.dart' show hashValues; -import '../chart_behavior.dart' show ChartBehavior, GestureType; - -/// Behavior that generates semantic nodes for each domain. -class DomainA11yExploreBehavior - extends ChartBehavior { - /// Returns a string for a11y vocalization from a list of series datum. - final common.VocalizationCallback vocalizationCallback; - - final Set desiredGestures; - - /// The gesture that activates explore mode. Defaults to long press. - /// - /// Turning on explore mode asks this [A11yBehavior] to generate nodes within - /// this chart. - final common.ExploreModeTrigger exploreModeTrigger; - - /// Minimum width of the bounding box for the a11y focus. - /// - /// Must be 1 or higher because invisible semantic nodes should not be added. - final double minimumWidth; - - /// Optionally notify the OS when explore mode is enabled. - final String exploreModeEnabledAnnouncement; - - /// Optionally notify the OS when explore mode is disabled. - final String exploreModeDisabledAnnouncement; - - DomainA11yExploreBehavior._internal( - {this.vocalizationCallback, - this.exploreModeTrigger, - this.desiredGestures, - this.minimumWidth, - this.exploreModeEnabledAnnouncement, - this.exploreModeDisabledAnnouncement}); - - factory DomainA11yExploreBehavior( - {common.VocalizationCallback vocalizationCallback, - common.ExploreModeTrigger exploreModeTrigger, - double minimumWidth, - String exploreModeEnabledAnnouncement, - String exploreModeDisabledAnnouncement}) { - final desiredGestures = new Set(); - exploreModeTrigger ??= common.ExploreModeTrigger.pressHold; - - switch (exploreModeTrigger) { - case common.ExploreModeTrigger.pressHold: - desiredGestures..add(GestureType.onLongPress); - break; - case common.ExploreModeTrigger.tap: - desiredGestures..add(GestureType.onTap); - break; - } - - return new DomainA11yExploreBehavior._internal( - vocalizationCallback: vocalizationCallback, - desiredGestures: desiredGestures, - exploreModeTrigger: exploreModeTrigger, - minimumWidth: minimumWidth, - exploreModeEnabledAnnouncement: exploreModeEnabledAnnouncement, - exploreModeDisabledAnnouncement: exploreModeDisabledAnnouncement, - ); - } - - @override - common.DomainA11yExploreBehavior createCommonBehavior() { - return new common.DomainA11yExploreBehavior( - vocalizationCallback: vocalizationCallback, - exploreModeTrigger: exploreModeTrigger, - minimumWidth: minimumWidth, - exploreModeEnabledAnnouncement: exploreModeEnabledAnnouncement, - exploreModeDisabledAnnouncement: exploreModeDisabledAnnouncement); - } - - @override - void updateCommonBehavior(common.DomainA11yExploreBehavior commonBehavior) {} - - @override - String get role => 'DomainA11yExplore-${exploreModeTrigger}'; - - @override - bool operator ==(Object o) => - o is DomainA11yExploreBehavior && - vocalizationCallback == o.vocalizationCallback && - exploreModeTrigger == o.exploreModeTrigger && - minimumWidth == o.minimumWidth && - exploreModeEnabledAnnouncement == o.exploreModeEnabledAnnouncement && - exploreModeDisabledAnnouncement == o.exploreModeDisabledAnnouncement; - - @override - int get hashCode { - return hashValues(minimumWidth, vocalizationCallback, exploreModeTrigger, - exploreModeEnabledAnnouncement, exploreModeDisabledAnnouncement); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/calculation/percent_injector.dart b/web/charts/flutter/lib/src/behaviors/calculation/percent_injector.dart deleted file mode 100644 index 35a982c10..000000000 --- a/web/charts/flutter/lib/src/behaviors/calculation/percent_injector.dart +++ /dev/null @@ -1,72 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show PercentInjector, PercentInjectorTotalType; -import 'package:meta/meta.dart' show immutable; - -import '../chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that can inject series or domain percentages into each datum. -/// -/// [totalType] configures the type of total to be calculated. -/// -/// The measure values of each datum will be replaced by the percent of the -/// total measure value that each represents. The "raw" measure accessor -/// function on [MutableSeries] can still be used to get the original values. -/// -/// Note that the results for measureLowerBound and measureUpperBound are not -/// currently well defined when converted into percentage values. This behavior -/// will replace them as percents to prevent bad axis results, but no effort is -/// made to bound them to within a "0 to 100%" data range. -/// -/// Note that if the chart has a [Legend] that is capable of hiding series data, -/// then this behavior must be added after the [Legend] to ensure that it -/// calculates values after series have been potentially removed from the list. -@immutable -class PercentInjector extends ChartBehavior { - final desiredGestures = new Set(); - - /// The type of data total to be calculated. - final common.PercentInjectorTotalType totalType; - - PercentInjector._internal({this.totalType}); - - /// Constructs a [PercentInjector]. - /// - /// [totalType] configures the type of data total to be calculated. - factory PercentInjector({common.PercentInjectorTotalType totalType}) { - totalType ??= common.PercentInjectorTotalType.domain; - return new PercentInjector._internal(totalType: totalType); - } - - @override - common.PercentInjector createCommonBehavior() => - new common.PercentInjector(totalType: totalType); - - @override - void updateCommonBehavior(common.PercentInjector commonBehavior) {} - - @override - String get role => 'PercentInjector'; - - @override - bool operator ==(Object o) { - return o is PercentInjector && totalType == o.totalType; - } - - @override - int get hashCode => totalType.hashCode; -} diff --git a/web/charts/flutter/lib/src/behaviors/chart_behavior.dart b/web/charts/flutter/lib/src/behaviors/chart_behavior.dart deleted file mode 100644 index 98f702111..000000000 --- a/web/charts/flutter/lib/src/behaviors/chart_behavior.dart +++ /dev/null @@ -1,72 +0,0 @@ -// 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:charts_common/common.dart' as common - show - BehaviorPosition, - InsideJustification, - OutsideJustification, - ChartBehavior; -import 'package:meta/meta.dart' show immutable; -import 'package:flutter_web/widgets.dart' show BuildContext, Widget; - -import '../base_chart_state.dart' show BaseChartState; - -/// Flutter wrapper for chart behaviors. -@immutable -abstract class ChartBehavior { - Set get desiredGestures; - - B createCommonBehavior(); - - void updateCommonBehavior(B commonBehavior); - - String get role; -} - -/// A chart behavior that depends on Flutter [State]. -abstract class ChartStateBehavior { - set chartState(BaseChartState chartState); -} - -/// A chart behavior that can build a Flutter [Widget]. -abstract class BuildableBehavior { - /// Builds a [Widget] based on the information passed in. - /// - /// [context] Flutter build context for extracting inherited properties such - /// as Directionality. - Widget build(BuildContext context); - - /// The position on the widget. - common.BehaviorPosition get position; - - /// Justification of the widget, if [position] is top, bottom, start, or end. - common.OutsideJustification get outsideJustification; - - /// Justification of the widget if [position] is [common.BehaviorPosition.inside]. - common.InsideJustification get insideJustification; - - /// Chart's draw area bounds are used for positioning. - Rectangle get drawAreaBounds; -} - -/// Types of gestures accepted by a chart. -enum GestureType { - onLongPress, - onTap, - onHover, - onDrag, -} diff --git a/web/charts/flutter/lib/src/behaviors/chart_title/chart_title.dart b/web/charts/flutter/lib/src/behaviors/chart_title/chart_title.dart deleted file mode 100644 index 47d606cda..000000000 --- a/web/charts/flutter/lib/src/behaviors/chart_title/chart_title.dart +++ /dev/null @@ -1,200 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - BehaviorPosition, - ChartTitle, - ChartTitleDirection, - MaxWidthStrategy, - OutsideJustification, - TextStyleSpec; -import 'package:flutter_web/widgets.dart' show hashValues; -import 'package:meta/meta.dart' show immutable; - -import '../chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that adds a ChartTitle widget to a chart. -@immutable -class ChartTitle extends ChartBehavior { - final desiredGestures = new Set(); - - final common.BehaviorPosition behaviorPosition; - - /// Minimum size of the legend component. Optional. - /// - /// If the legend is positioned in the top or bottom margin, then this - /// configures the legend's height. If positioned in the start or end - /// position, this configures the legend's width. - final int layoutMinSize; - - /// Preferred size of the legend component. Defaults to 0. - /// - /// If the legend is positioned in the top or bottom margin, then this - /// configures the legend's height. If positioned in the start or end - /// position, this configures the legend's width. - final int layoutPreferredSize; - - /// Strategy for handling title text that is too large to fit. Defaults to - /// truncating the text with ellipses. - final common.MaxWidthStrategy maxWidthStrategy; - - /// Primary text for the title. - final String title; - - /// Direction of the chart title text. - /// - /// This defaults to horizontal for a title in the top or bottom - /// [behaviorPosition], or vertical for start or end [behaviorPosition]. - final common.ChartTitleDirection titleDirection; - - /// Justification of the title text if it is positioned outside of the draw - /// area. - final common.OutsideJustification titleOutsideJustification; - - /// Space between the title and sub-title text, if defined. - /// - /// This padding is not used if no sub-title is provided. - final int titlePadding; - - /// Style of the [title] text. - final common.TextStyleSpec titleStyleSpec; - - /// Secondary text for the sub-title. - /// - /// [subTitle] is rendered on a second line below the [title], and may be - /// styled differently. - final String subTitle; - - /// Style of the [subTitle] text. - final common.TextStyleSpec subTitleStyleSpec; - - /// Space between the "inside" of the chart, and the title behavior itself. - /// - /// This padding is applied to all the edge of the title that is in the - /// direction of the draw area. For a top positioned title, this is applied - /// to the bottom edge. [outerPadding] is applied to the top, left, and right - /// edges. - /// - /// If a sub-title is defined, this is the space between the sub-title text - /// and the inside of the chart. Otherwise, it is the space between the title - /// text and the inside of chart. - final int innerPadding; - - /// Space between the "outside" of the chart, and the title behavior itself. - /// - /// This padding is applied to all 3 edges of the title that are not in the - /// direction of the draw area. For a top positioned title, this is applied - /// to the top, left, and right edges. [innerPadding] is applied to the - /// bottom edge. - final int outerPadding; - - /// Constructs a [ChartTitle]. - /// - /// [title] primary text for the title. - /// - /// [behaviorPosition] layout position for the title. Defaults to the top of - /// the chart. - /// - /// [innerPadding] space between the "inside" of the chart, and the title - /// behavior itself. - /// - /// [maxWidthStrategy] strategy for handling title text that is too large to - /// fit. Defaults to truncating the text with ellipses. - /// - /// [titleDirection] direction of the chart title text. - /// - /// [titleOutsideJustification] Justification of the title text if it is - /// positioned outside of the draw. Defaults to the middle of the margin area. - /// - /// [titlePadding] space between the title and sub-title text, if defined. - /// - /// [titleStyleSpec] style of the [title] text. - /// - /// [subTitle] secondary text for the sub-title. Optional. - /// - /// [subTitleStyleSpec] style of the [subTitle] text. - ChartTitle(this.title, - {this.behaviorPosition, - this.innerPadding, - this.layoutMinSize, - this.layoutPreferredSize, - this.outerPadding, - this.maxWidthStrategy, - this.titleDirection, - this.titleOutsideJustification, - this.titlePadding, - this.titleStyleSpec, - this.subTitle, - this.subTitleStyleSpec}); - - @override - common.ChartTitle createCommonBehavior() => - new common.ChartTitle(title, - behaviorPosition: behaviorPosition, - innerPadding: innerPadding, - layoutMinSize: layoutMinSize, - layoutPreferredSize: layoutPreferredSize, - outerPadding: outerPadding, - maxWidthStrategy: maxWidthStrategy, - titleDirection: titleDirection, - titleOutsideJustification: titleOutsideJustification, - titlePadding: titlePadding, - titleStyleSpec: titleStyleSpec, - subTitle: subTitle, - subTitleStyleSpec: subTitleStyleSpec); - - @override - void updateCommonBehavior(common.ChartTitle commonBehavior) {} - - @override - String get role => 'ChartTitle-${behaviorPosition.toString()}'; - - @override - bool operator ==(Object o) { - return o is ChartTitle && - behaviorPosition == o.behaviorPosition && - layoutMinSize == o.layoutMinSize && - layoutPreferredSize == o.layoutPreferredSize && - maxWidthStrategy == o.maxWidthStrategy && - title == o.title && - titleDirection == o.titleDirection && - titleOutsideJustification == o.titleOutsideJustification && - titleStyleSpec == o.titleStyleSpec && - subTitle == o.subTitle && - subTitleStyleSpec == o.subTitleStyleSpec && - innerPadding == o.innerPadding && - titlePadding == o.titlePadding && - outerPadding == o.outerPadding; - } - - @override - int get hashCode { - return hashValues( - behaviorPosition, - layoutMinSize, - layoutPreferredSize, - maxWidthStrategy, - title, - titleDirection, - titleOutsideJustification, - titleStyleSpec, - subTitle, - subTitleStyleSpec, - innerPadding, - titlePadding, - outerPadding); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/domain_highlighter.dart b/web/charts/flutter/lib/src/behaviors/domain_highlighter.dart deleted file mode 100644 index 896a6bf6e..000000000 --- a/web/charts/flutter/lib/src/behaviors/domain_highlighter.dart +++ /dev/null @@ -1,54 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show DomainHighlighter, SelectionModelType; - -import 'package:meta/meta.dart' show immutable; - -import 'chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that monitors the specified [SelectionModel] and darkens the -/// color for selected data. -/// -/// This is typically used for bars and pies to highlight segments. -/// -/// It is used in combination with SelectNearest to update the selection model -/// and expand selection out to the domain value. -@immutable -class DomainHighlighter extends ChartBehavior { - final desiredGestures = new Set(); - - final common.SelectionModelType selectionModelType; - - DomainHighlighter([this.selectionModelType = common.SelectionModelType.info]); - - @override - common.DomainHighlighter createCommonBehavior() => - new common.DomainHighlighter(selectionModelType); - - @override - void updateCommonBehavior(common.DomainHighlighter commonBehavior) {} - - @override - String get role => 'domainHighlight-${selectionModelType.toString()}'; - - @override - bool operator ==(Object o) => - o is DomainHighlighter && selectionModelType == o.selectionModelType; - - @override - int get hashCode => selectionModelType.hashCode; -} diff --git a/web/charts/flutter/lib/src/behaviors/initial_selection.dart b/web/charts/flutter/lib/src/behaviors/initial_selection.dart deleted file mode 100644 index f7086b564..000000000 --- a/web/charts/flutter/lib/src/behaviors/initial_selection.dart +++ /dev/null @@ -1,68 +0,0 @@ -// 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 'package:collection/collection.dart' show ListEquality; - -import 'package:charts_common/common.dart' as common - show InitialSelection, SeriesDatumConfig, SelectionModelType; - -import 'package:meta/meta.dart' show immutable; - -import 'chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that sets the initial selection for a [selectionModelType]. -@immutable -class InitialSelection extends ChartBehavior { - final desiredGestures = new Set(); - - final common.SelectionModelType selectionModelType; - final List selectedSeriesConfig; - final List selectedDataConfig; - - InitialSelection( - {this.selectionModelType = common.SelectionModelType.info, - this.selectedSeriesConfig, - this.selectedDataConfig}); - - @override - common.InitialSelection createCommonBehavior() => - new common.InitialSelection( - selectionModelType: selectionModelType, - selectedDataConfig: selectedDataConfig, - selectedSeriesConfig: selectedSeriesConfig); - - @override - void updateCommonBehavior(common.InitialSelection commonBehavior) {} - - @override - String get role => 'InitialSelection-${selectionModelType.toString()}'; - - @override - bool operator ==(Object o) { - return o is InitialSelection && - selectionModelType == o.selectionModelType && - new ListEquality() - .equals(selectedSeriesConfig, o.selectedSeriesConfig) && - new ListEquality().equals(selectedDataConfig, o.selectedDataConfig); - } - - @override - int get hashCode { - int hashcode = selectionModelType.hashCode; - hashcode = hashcode * 37 + (selectedSeriesConfig?.hashCode ?? 0); - hashcode = hashcode * 37 + (selectedDataConfig?.hashCode ?? 0); - return hashcode; - } -} diff --git a/web/charts/flutter/lib/src/behaviors/legend/datum_legend.dart b/web/charts/flutter/lib/src/behaviors/legend/datum_legend.dart deleted file mode 100644 index 38645e58a..000000000 --- a/web/charts/flutter/lib/src/behaviors/legend/datum_legend.dart +++ /dev/null @@ -1,340 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - BehaviorPosition, - DatumLegend, - InsideJustification, - LegendEntry, - MeasureFormatter, - LegendDefaultMeasure, - OutsideJustification, - SelectionModelType, - TextStyleSpec; -import 'package:flutter_web/widgets.dart' - show BuildContext, EdgeInsets, Widget, hashValues; -import 'package:meta/meta.dart' show immutable; -import '../../chart_container.dart' show ChartContainerRenderObject; -import '../chart_behavior.dart' - show BuildableBehavior, ChartBehavior, GestureType; -import 'legend.dart' show TappableLegend; -import 'legend_content_builder.dart' - show LegendContentBuilder, TabularLegendContentBuilder; -import 'legend_layout.dart' show TabularLegendLayout; - -/// Datum legend behavior for charts. -/// -/// By default this behavior creates one legend entry per datum in the first -/// series rendered on the chart. -@immutable -class DatumLegend extends ChartBehavior { - static const defaultBehaviorPosition = common.BehaviorPosition.top; - static const defaultOutsideJustification = - common.OutsideJustification.startDrawArea; - static const defaultInsideJustification = common.InsideJustification.topStart; - - final desiredGestures = new Set(); - - final common.SelectionModelType selectionModelType; - - /// Builder for creating custom legend content. - final LegendContentBuilder contentBuilder; - - /// Position of the legend relative to the chart. - final common.BehaviorPosition position; - - /// Justification of the legend relative to the chart - final common.OutsideJustification outsideJustification; - final common.InsideJustification insideJustification; - - /// Whether or not the legend should show measures. - /// - /// By default this is false, measures are not shown. When set to true, the - /// default behavior is to show measure only if there is selected data. - /// Please set [legendDefaultMeasure] to something other than none to enable - /// showing measures when there is no selection. - /// - /// This flag is used by the [contentBuilder], so a custom content builder - /// has to choose if it wants to use this flag. - final bool showMeasures; - - /// Option to show measures when selection is null. - /// - /// By default this is set to none, so no measures are shown when there is - /// no selection. - final common.LegendDefaultMeasure legendDefaultMeasure; - - /// Formatter for measure value(s) if the measures are shown on the legend. - final common.MeasureFormatter measureFormatter; - - /// Formatter for secondary measure value(s) if the measures are shown on the - /// legend and the series uses the secondary axis. - final common.MeasureFormatter secondaryMeasureFormatter; - - /// Styles for legend entry label text. - final common.TextStyleSpec entryTextStyle; - - static const defaultCellPadding = const EdgeInsets.all(8.0); - - /// Create a new tabular layout legend. - /// - /// By default, the legend is place above the chart and horizontally aligned - /// to the start of the draw area. - /// - /// [position] the legend will be positioned relative to the chart. Default - /// position is top. - /// - /// [outsideJustification] justification of the legend relative to the chart - /// if the position is top, bottom, left, right. Default to start of the draw - /// area. - /// - /// [insideJustification] justification of the legend relative to the chart if - /// the position is inside. Default to top of the chart, start of draw area. - /// Start of draw area means left for LTR directionality, and right for RTL. - /// - /// [horizontalFirst] if true, legend entries will grow horizontally first - /// instead of vertically first. If the position is top, bottom, or inside, - /// this defaults to true. Otherwise false. - /// - /// [desiredMaxRows] the max rows to use before layout out items in a new - /// column. By default there is no limit. The max columns created is the - /// smaller of desiredMaxRows and number of legend entries. - /// - /// [desiredMaxColumns] the max columns to use before laying out items in a - /// new row. By default there is no limit. The max columns created is the - /// smaller of desiredMaxColumns and number of legend entries. - /// - /// [showMeasures] show measure values for each series. - /// - /// [legendDefaultMeasure] if measure should show when there is no selection. - /// This is set to none by default (only shows measure for selected data). - /// - /// [measureFormatter] formats measure value if measures are shown. - /// - /// [secondaryMeasureFormatter] formats measures if measures are shown for the - /// series that uses secondary measure axis. - factory DatumLegend({ - common.BehaviorPosition position, - common.OutsideJustification outsideJustification, - common.InsideJustification insideJustification, - bool horizontalFirst, - int desiredMaxRows, - int desiredMaxColumns, - EdgeInsets cellPadding, - bool showMeasures, - common.LegendDefaultMeasure legendDefaultMeasure, - common.MeasureFormatter measureFormatter, - common.MeasureFormatter secondaryMeasureFormatter, - common.TextStyleSpec entryTextStyle, - }) { - // Set defaults if empty. - position ??= defaultBehaviorPosition; - outsideJustification ??= defaultOutsideJustification; - insideJustification ??= defaultInsideJustification; - cellPadding ??= defaultCellPadding; - - // Set the tabular layout settings to match the position if it is not - // specified. - horizontalFirst ??= (position == common.BehaviorPosition.top || - position == common.BehaviorPosition.bottom || - position == common.BehaviorPosition.inside); - final layoutBuilder = horizontalFirst - ? new TabularLegendLayout.horizontalFirst( - desiredMaxColumns: desiredMaxColumns, cellPadding: cellPadding) - : new TabularLegendLayout.verticalFirst( - desiredMaxRows: desiredMaxRows, cellPadding: cellPadding); - - return new DatumLegend._internal( - contentBuilder: - new TabularLegendContentBuilder(legendLayout: layoutBuilder), - selectionModelType: common.SelectionModelType.info, - position: position, - outsideJustification: outsideJustification, - insideJustification: insideJustification, - showMeasures: showMeasures ?? false, - legendDefaultMeasure: - legendDefaultMeasure ?? common.LegendDefaultMeasure.none, - measureFormatter: measureFormatter, - secondaryMeasureFormatter: secondaryMeasureFormatter, - entryTextStyle: entryTextStyle); - } - - /// Create a legend with custom layout. - /// - /// By default, the legend is place above the chart and horizontally aligned - /// to the start of the draw area. - /// - /// [contentBuilder] builder for the custom layout. - /// - /// [position] the legend will be positioned relative to the chart. Default - /// position is top. - /// - /// [outsideJustification] justification of the legend relative to the chart - /// if the position is top, bottom, left, right. Default to start of the draw - /// area. - /// - /// [insideJustification] justification of the legend relative to the chart if - /// the position is inside. Default to top of the chart, start of draw area. - /// Start of draw area means left for LTR directionality, and right for RTL. - /// - /// [showMeasures] show measure values for each series. - /// - /// [legendDefaultMeasure] if measure should show when there is no selection. - /// This is set to none by default (only shows measure for selected data). - /// - /// [measureFormatter] formats measure value if measures are shown. - /// - /// [secondaryMeasureFormatter] formats measures if measures are shown for the - /// series that uses secondary measure axis. - factory DatumLegend.customLayout( - LegendContentBuilder contentBuilder, { - common.BehaviorPosition position, - common.OutsideJustification outsideJustification, - common.InsideJustification insideJustification, - bool showMeasures, - common.LegendDefaultMeasure legendDefaultMeasure, - common.MeasureFormatter measureFormatter, - common.MeasureFormatter secondaryMeasureFormatter, - common.TextStyleSpec entryTextStyle, - }) { - // Set defaults if empty. - position ??= defaultBehaviorPosition; - outsideJustification ??= defaultOutsideJustification; - insideJustification ??= defaultInsideJustification; - - return new DatumLegend._internal( - contentBuilder: contentBuilder, - selectionModelType: common.SelectionModelType.info, - position: position, - outsideJustification: outsideJustification, - insideJustification: insideJustification, - showMeasures: showMeasures ?? false, - legendDefaultMeasure: - legendDefaultMeasure ?? common.LegendDefaultMeasure.none, - measureFormatter: measureFormatter, - secondaryMeasureFormatter: secondaryMeasureFormatter, - entryTextStyle: entryTextStyle, - ); - } - - DatumLegend._internal({ - this.contentBuilder, - this.selectionModelType, - this.position, - this.outsideJustification, - this.insideJustification, - this.showMeasures, - this.legendDefaultMeasure, - this.measureFormatter, - this.secondaryMeasureFormatter, - this.entryTextStyle, - }); - - @override - common.DatumLegend createCommonBehavior() => - new _FlutterDatumLegend(this); - - @override - void updateCommonBehavior(common.DatumLegend commonBehavior) { - (commonBehavior as _FlutterDatumLegend).config = this; - } - - /// All Legend behaviors get the same role ID, because you should only have - /// one legend on a chart. - @override - String get role => 'legend'; - - @override - bool operator ==(Object o) { - return o is DatumLegend && - selectionModelType == o.selectionModelType && - contentBuilder == o.contentBuilder && - position == o.position && - outsideJustification == o.outsideJustification && - insideJustification == o.insideJustification && - showMeasures == o.showMeasures && - legendDefaultMeasure == o.legendDefaultMeasure && - measureFormatter == o.measureFormatter && - secondaryMeasureFormatter == o.secondaryMeasureFormatter && - entryTextStyle == o.entryTextStyle; - } - - @override - int get hashCode { - return hashValues( - selectionModelType, - contentBuilder, - position, - outsideJustification, - insideJustification, - showMeasures, - legendDefaultMeasure, - measureFormatter, - secondaryMeasureFormatter, - entryTextStyle); - } -} - -/// Flutter specific wrapper on the common Legend for building content. -class _FlutterDatumLegend extends common.DatumLegend - implements BuildableBehavior, TappableLegend { - DatumLegend config; - - _FlutterDatumLegend(this.config) - : super( - selectionModelType: config.selectionModelType, - measureFormatter: config.measureFormatter, - secondaryMeasureFormatter: config.secondaryMeasureFormatter, - legendDefaultMeasure: config.legendDefaultMeasure, - ) { - super.entryTextStyle = config.entryTextStyle; - } - - @override - void updateLegend() { - (chartContext as ChartContainerRenderObject).requestRebuild(); - } - - @override - common.BehaviorPosition get position => config.position; - - @override - common.OutsideJustification get outsideJustification => - config.outsideJustification; - - @override - common.InsideJustification get insideJustification => - config.insideJustification; - - @override - Widget build(BuildContext context) { - final hasSelection = - legendState.legendEntries.any((entry) => entry.isSelected); - - // Show measures if [showMeasures] is true and there is a selection or if - // showing measures when there is no selection. - final showMeasures = config.showMeasures && - (hasSelection || - legendDefaultMeasure != common.LegendDefaultMeasure.none); - - return config.contentBuilder - .build(context, legendState, this, showMeasures: showMeasures); - } - - /// TODO: Maybe highlight the pie wedge. - @override - onLegendEntryTapUp(common.LegendEntry detail) {} -} diff --git a/web/charts/flutter/lib/src/behaviors/legend/legend.dart b/web/charts/flutter/lib/src/behaviors/legend/legend.dart deleted file mode 100644 index 5cd347b2e..000000000 --- a/web/charts/flutter/lib/src/behaviors/legend/legend.dart +++ /dev/null @@ -1,22 +0,0 @@ -// 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 'package:charts_common/common.dart' show LegendEntry, LegendTapHandling; - -abstract class TappableLegend { - /// Delegates handling of legend entry clicks according to the configured - /// [LegendTapHandling] strategy. - onLegendEntryTapUp(LegendEntry detail); -} diff --git a/web/charts/flutter/lib/src/behaviors/legend/legend_content_builder.dart b/web/charts/flutter/lib/src/behaviors/legend/legend_content_builder.dart deleted file mode 100644 index 22f35a11e..000000000 --- a/web/charts/flutter/lib/src/behaviors/legend/legend_content_builder.dart +++ /dev/null @@ -1,92 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show Legend, LegendState, SeriesLegend; -import 'package:flutter_web/widgets.dart' show BuildContext, hashValues, Widget; -import 'legend.dart'; -import 'legend_entry_layout.dart'; -import 'legend_layout.dart'; - -/// Strategy for building a legend content widget. -abstract class LegendContentBuilder { - const LegendContentBuilder(); - - Widget build(BuildContext context, common.LegendState legendState, - common.Legend legend, - {bool showMeasures}); -} - -/// Base strategy for building a legend content widget. -/// -/// Each legend entry is passed to a [LegendLayout] strategy to create a widget -/// for each legend entry. These widgets are then passed to a -/// [LegendEntryLayout] strategy to create the legend widget. -abstract class BaseLegendContentBuilder implements LegendContentBuilder { - /// Strategy for creating one widget or each legend entry. - LegendEntryLayout get legendEntryLayout; - - /// Strategy for creating the legend content widget from a list of widgets. - /// - /// This is typically the list of widgets from legend entries. - LegendLayout get legendLayout; - - @override - Widget build(BuildContext context, common.LegendState legendState, - common.Legend legend, - {bool showMeasures}) { - final entryWidgets = legendState.legendEntries.map((entry) { - var isHidden = false; - if (legend is common.SeriesLegend) { - isHidden = legend.isSeriesHidden(entry.series.id); - } - - return legendEntryLayout.build( - context, entry, legend as TappableLegend, isHidden, - showMeasures: showMeasures); - }).toList(); - - return legendLayout.build(context, entryWidgets); - } -} - -// TODO: Expose settings for tabular layout. -/// Strategy that builds a tabular legend. -/// -/// [legendEntryLayout] custom strategy for creating widgets for each legend -/// entry. -/// [legendLayout] custom strategy for creating legend widget from list of -/// widgets that represent a legend entry. -class TabularLegendContentBuilder extends BaseLegendContentBuilder { - final LegendEntryLayout legendEntryLayout; - final LegendLayout legendLayout; - - TabularLegendContentBuilder( - {LegendEntryLayout legendEntryLayout, LegendLayout legendLayout}) - : this.legendEntryLayout = - legendEntryLayout ?? const SimpleLegendEntryLayout(), - this.legendLayout = - legendLayout ?? new TabularLegendLayout.horizontalFirst(); - - @override - bool operator ==(Object o) { - return o is TabularLegendContentBuilder && - legendEntryLayout == o.legendEntryLayout && - legendLayout == o.legendLayout; - } - - @override - int get hashCode => hashValues(legendEntryLayout, legendLayout); -} diff --git a/web/charts/flutter/lib/src/behaviors/legend/legend_entry_layout.dart b/web/charts/flutter/lib/src/behaviors/legend/legend_entry_layout.dart deleted file mode 100644 index 16882f792..000000000 --- a/web/charts/flutter/lib/src/behaviors/legend/legend_entry_layout.dart +++ /dev/null @@ -1,144 +0,0 @@ -// 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 'package:charts_common/common.dart' as common; -import 'package:charts_flutter/src/util/color.dart'; -import 'package:flutter_web/widgets.dart'; -import 'package:flutter_web/material.dart' - show GestureDetector, GestureTapUpCallback, TapUpDetails, Theme; - -import '../../symbol_renderer.dart'; -import 'legend.dart' show TappableLegend; - -/// Strategy for building one widget from one [common.LegendEntry]. -abstract class LegendEntryLayout { - Widget build(BuildContext context, common.LegendEntry legendEntry, - TappableLegend legend, bool isHidden, - {bool showMeasures}); -} - -/// Builds one legend entry as a row with symbol and label from the series. -/// -/// If directionality from the chart context indicates RTL, the symbol is placed -/// to the right of the text instead of the left of the text. -class SimpleLegendEntryLayout implements LegendEntryLayout { - const SimpleLegendEntryLayout(); - - Widget createSymbol(BuildContext context, common.LegendEntry legendEntry, - TappableLegend legend, bool isHidden) { - // TODO: Consider allowing scaling the size for the symbol. - // A custom symbol renderer can ignore this size and use their own. - final materialSymbolSize = new Size(12.0, 12.0); - - final entryColor = legendEntry.color; - var color = ColorUtil.toDartColor(entryColor); - - // Get the SymbolRendererBuilder wrapping a common.SymbolRenderer if needed. - final SymbolRendererBuilder symbolRendererBuilder = - legendEntry.symbolRenderer is SymbolRendererBuilder - ? legendEntry.symbolRenderer - : new SymbolRendererCanvas(legendEntry.symbolRenderer); - - return new GestureDetector( - child: symbolRendererBuilder.build( - context, - size: materialSymbolSize, - color: color, - enabled: !isHidden, - ), - onTapUp: makeTapUpCallback(context, legendEntry, legend)); - } - - Widget createLabel(BuildContext context, common.LegendEntry legendEntry, - TappableLegend legend, bool isHidden) { - TextStyle style = - _convertTextStyle(isHidden, context, legendEntry.textStyle); - - return new GestureDetector( - child: new Text(legendEntry.label, style: style), - onTapUp: makeTapUpCallback(context, legendEntry, legend)); - } - - Widget createMeasureValue(BuildContext context, - common.LegendEntry legendEntry, TappableLegend legend, bool isHidden) { - return new GestureDetector( - child: new Text(legendEntry.formattedValue), - onTapUp: makeTapUpCallback(context, legendEntry, legend)); - } - - @override - Widget build(BuildContext context, common.LegendEntry legendEntry, - TappableLegend legend, bool isHidden, - {bool showMeasures}) { - final rowChildren = []; - - // TODO: Allow setting to configure the padding. - final padding = new EdgeInsets.only(right: 8.0); // Material default. - final symbol = createSymbol(context, legendEntry, legend, isHidden); - final label = createLabel(context, legendEntry, legend, isHidden); - - final measure = showMeasures - ? createMeasureValue(context, legendEntry, legend, isHidden) - : null; - - rowChildren.add(symbol); - rowChildren.add(new Container(padding: padding)); - rowChildren.add(label); - if (measure != null) { - rowChildren.add(new Container(padding: padding)); - rowChildren.add(measure); - } - - // Row automatically reverses the content if Directionality is rtl. - return new Row(children: rowChildren); - } - - GestureTapUpCallback makeTapUpCallback(BuildContext context, - common.LegendEntry legendEntry, TappableLegend legend) { - return (TapUpDetails d) { - legend.onLegendEntryTapUp(legendEntry); - }; - } - - bool operator ==(Object other) => other is SimpleLegendEntryLayout; - - int get hashCode { - return this.runtimeType.hashCode; - } - - /// Convert the charts common TextStlyeSpec into a standard TextStyle, while - /// reducing the color opacity to 26% if the entry is hidden. - /// - /// For non-specified values, override the hidden text color to use the body 1 - /// theme, but allow other properties of [Text] to be inherited. - TextStyle _convertTextStyle( - bool isHidden, BuildContext context, common.TextStyleSpec textStyle) { - Color color = textStyle?.color != null - ? ColorUtil.toDartColor(textStyle.color) - : null; - if (isHidden) { - // Use a default color for hidden legend entries if none is provided. - color ??= Theme.of(context).textTheme.body1.color; - color = color.withOpacity(0.26); - } - - return new TextStyle( - inherit: true, - fontFamily: textStyle?.fontFamily, - fontSize: - textStyle?.fontSize != null ? textStyle.fontSize.toDouble() : null, - color: color); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/legend/legend_layout.dart b/web/charts/flutter/lib/src/behaviors/legend/legend_layout.dart deleted file mode 100644 index 76bf7c875..000000000 --- a/web/charts/flutter/lib/src/behaviors/legend/legend_layout.dart +++ /dev/null @@ -1,158 +0,0 @@ -// 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 min; -import 'package:flutter_web/rendering.dart'; -import 'package:flutter_web/widgets.dart'; - -/// Strategy for building legend from legend entry widgets. -abstract class LegendLayout { - Widget build(BuildContext context, List legendEntryWidgets); -} - -/// Layout legend entries in tabular format. -class TabularLegendLayout implements LegendLayout { - /// No limit for max rows or max columns. - static const _noLimit = -1; - - final bool isHorizontalFirst; - final int desiredMaxRows; - final int desiredMaxColumns; - final EdgeInsets cellPadding; - - TabularLegendLayout._internal( - {this.isHorizontalFirst, - this.desiredMaxRows, - this.desiredMaxColumns, - this.cellPadding}); - - /// Layout horizontally until columns exceed [desiredMaxColumns]. - /// - /// [desiredMaxColumns] the max columns to use before laying out items in a - /// new row. By default there is no limit. The max columns created is the - /// smaller of desiredMaxColumns and number of legend entries. - /// - /// [cellPadding] the [EdgeInsets] for each widget. - factory TabularLegendLayout.horizontalFirst({ - int desiredMaxColumns, - EdgeInsets cellPadding, - }) { - return new TabularLegendLayout._internal( - isHorizontalFirst: true, - desiredMaxRows: _noLimit, - desiredMaxColumns: desiredMaxColumns ?? _noLimit, - cellPadding: cellPadding, - ); - } - - /// Layout vertically, until rows exceed [desiredMaxRows]. - /// - /// [desiredMaxRows] the max rows to use before layout out items in a new - /// column. By default there is no limit. The max columns created is the - /// smaller of desiredMaxRows and number of legend entries. - /// - /// [cellPadding] the [EdgeInsets] for each widget. - factory TabularLegendLayout.verticalFirst({ - int desiredMaxRows, - EdgeInsets cellPadding, - }) { - return new TabularLegendLayout._internal( - isHorizontalFirst: false, - desiredMaxRows: desiredMaxRows ?? _noLimit, - desiredMaxColumns: _noLimit, - cellPadding: cellPadding, - ); - } - - @override - Widget build(BuildContext context, List legendEntries) { - final paddedLegendEntries = ((cellPadding == null) - ? legendEntries - : legendEntries - .map((entry) => new Padding(padding: cellPadding, child: entry)) - .toList()); - - return isHorizontalFirst - ? _buildHorizontalFirst(paddedLegendEntries) - : _buildVerticalFirst(paddedLegendEntries); - } - - @override - bool operator ==(o) => - o is TabularLegendLayout && - desiredMaxRows == o.desiredMaxRows && - desiredMaxColumns == o.desiredMaxColumns && - isHorizontalFirst == o.isHorizontalFirst && - cellPadding == o.cellPadding; - - @override - int get hashCode => hashValues( - desiredMaxRows, desiredMaxColumns, isHorizontalFirst, cellPadding); - - Widget _buildHorizontalFirst(List legendEntries) { - final maxColumns = (desiredMaxColumns == _noLimit) - ? legendEntries.length - : min(legendEntries.length, desiredMaxColumns); - - final rows = []; - for (var i = 0; i < legendEntries.length; i += maxColumns) { - rows.add(new TableRow( - children: legendEntries - .sublist(i, min(i + maxColumns, legendEntries.length)) - .toList())); - } - - return _buildTableFromRows(rows); - } - - Widget _buildVerticalFirst(List legendEntries) { - final maxRows = (desiredMaxRows == _noLimit) - ? legendEntries.length - : min(legendEntries.length, desiredMaxRows); - - final rows = - new List.generate(maxRows, (_) => new TableRow(children: [])); - for (var i = 0; i < legendEntries.length; i++) { - rows[i % maxRows].children.add(legendEntries[i]); - } - - return _buildTableFromRows(rows); - } - - Table _buildTableFromRows(List rows) { - final padWidget = new Row(); - - // Pad rows to the max column count, because each TableRow in a table is - // required to have the same number of children. - final columnCount = rows - .map((r) => r.children.length) - .fold(0, (max, current) => (current > max) ? current : max); - - for (var i = 0; i < rows.length; i++) { - final rowChildren = rows[i].children; - final padCount = columnCount - rowChildren.length; - if (padCount > 0) { - rowChildren.addAll(new Iterable.generate(padCount, (_) => padWidget)); - } - } - - // TODO: Investigate other means of creating the tabular legend - // Sizing the column width using [IntrinsicColumnWidth] is expensive per - // Flutter's documentation, but has to be used if the table is desired to - // have a width that is tight on each column. - return new Table( - children: rows, defaultColumnWidth: new IntrinsicColumnWidth()); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/legend/series_legend.dart b/web/charts/flutter/lib/src/behaviors/legend/series_legend.dart deleted file mode 100644 index e49b3c9ea..000000000 --- a/web/charts/flutter/lib/src/behaviors/legend/series_legend.dart +++ /dev/null @@ -1,382 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - BehaviorPosition, - InsideJustification, - LegendEntry, - LegendTapHandling, - MeasureFormatter, - LegendDefaultMeasure, - OutsideJustification, - SeriesLegend, - SelectionModelType, - TextStyleSpec; -import 'package:collection/collection.dart' show ListEquality; -import 'package:flutter_web/widgets.dart' - show BuildContext, EdgeInsets, Widget, hashValues; -import 'package:meta/meta.dart' show immutable; -import '../../chart_container.dart' show ChartContainerRenderObject; -import '../chart_behavior.dart' - show BuildableBehavior, ChartBehavior, GestureType; -import 'legend.dart' show TappableLegend; -import 'legend_content_builder.dart' - show LegendContentBuilder, TabularLegendContentBuilder; -import 'legend_layout.dart' show TabularLegendLayout; - -/// Series legend behavior for charts. -@immutable -class SeriesLegend extends ChartBehavior { - static const defaultBehaviorPosition = common.BehaviorPosition.top; - static const defaultOutsideJustification = - common.OutsideJustification.startDrawArea; - static const defaultInsideJustification = common.InsideJustification.topStart; - - final desiredGestures = new Set(); - - final common.SelectionModelType selectionModelType; - - /// Builder for creating custom legend content. - final LegendContentBuilder contentBuilder; - - /// Position of the legend relative to the chart. - final common.BehaviorPosition position; - - /// Justification of the legend relative to the chart - final common.OutsideJustification outsideJustification; - final common.InsideJustification insideJustification; - - /// Whether or not the legend should show measures. - /// - /// By default this is false, measures are not shown. When set to true, the - /// default behavior is to show measure only if there is selected data. - /// Please set [legendDefaultMeasure] to something other than none to enable - /// showing measures when there is no selection. - /// - /// This flag is used by the [contentBuilder], so a custom content builder - /// has to choose if it wants to use this flag. - final bool showMeasures; - - /// Option to show measures when selection is null. - /// - /// By default this is set to none, so no measures are shown when there is - /// no selection. - final common.LegendDefaultMeasure legendDefaultMeasure; - - /// Formatter for measure value(s) if the measures are shown on the legend. - final common.MeasureFormatter measureFormatter; - - /// Formatter for secondary measure value(s) if the measures are shown on the - /// legend and the series uses the secondary axis. - final common.MeasureFormatter secondaryMeasureFormatter; - - /// Styles for legend entry label text. - final common.TextStyleSpec entryTextStyle; - - static const defaultCellPadding = const EdgeInsets.all(8.0); - - final List defaultHiddenSeries; - - /// Create a new tabular layout legend. - /// - /// By default, the legend is place above the chart and horizontally aligned - /// to the start of the draw area. - /// - /// [position] the legend will be positioned relative to the chart. Default - /// position is top. - /// - /// [outsideJustification] justification of the legend relative to the chart - /// if the position is top, bottom, left, right. Default to start of the draw - /// area. - /// - /// [insideJustification] justification of the legend relative to the chart if - /// the position is inside. Default to top of the chart, start of draw area. - /// Start of draw area means left for LTR directionality, and right for RTL. - /// - /// [horizontalFirst] if true, legend entries will grow horizontally first - /// instead of vertically first. If the position is top, bottom, or inside, - /// this defaults to true. Otherwise false. - /// - /// [desiredMaxRows] the max rows to use before layout out items in a new - /// column. By default there is no limit. The max columns created is the - /// smaller of desiredMaxRows and number of legend entries. - /// - /// [desiredMaxColumns] the max columns to use before laying out items in a - /// new row. By default there is no limit. The max columns created is the - /// smaller of desiredMaxColumns and number of legend entries. - /// - /// [defaultHiddenSeries] lists the IDs of series that should be hidden on - /// first chart draw. - /// - /// [showMeasures] show measure values for each series. - /// - /// [legendDefaultMeasure] if measure should show when there is no selection. - /// This is set to none by default (only shows measure for selected data). - /// - /// [measureFormatter] formats measure value if measures are shown. - /// - /// [secondaryMeasureFormatter] formats measures if measures are shown for the - /// series that uses secondary measure axis. - factory SeriesLegend({ - common.BehaviorPosition position, - common.OutsideJustification outsideJustification, - common.InsideJustification insideJustification, - bool horizontalFirst, - int desiredMaxRows, - int desiredMaxColumns, - EdgeInsets cellPadding, - List defaultHiddenSeries, - bool showMeasures, - common.LegendDefaultMeasure legendDefaultMeasure, - common.MeasureFormatter measureFormatter, - common.MeasureFormatter secondaryMeasureFormatter, - common.TextStyleSpec entryTextStyle, - }) { - // Set defaults if empty. - position ??= defaultBehaviorPosition; - outsideJustification ??= defaultOutsideJustification; - insideJustification ??= defaultInsideJustification; - cellPadding ??= defaultCellPadding; - - // Set the tabular layout settings to match the position if it is not - // specified. - horizontalFirst ??= (position == common.BehaviorPosition.top || - position == common.BehaviorPosition.bottom || - position == common.BehaviorPosition.inside); - final layoutBuilder = horizontalFirst - ? new TabularLegendLayout.horizontalFirst( - desiredMaxColumns: desiredMaxColumns, cellPadding: cellPadding) - : new TabularLegendLayout.verticalFirst( - desiredMaxRows: desiredMaxRows, cellPadding: cellPadding); - - return new SeriesLegend._internal( - contentBuilder: - new TabularLegendContentBuilder(legendLayout: layoutBuilder), - selectionModelType: common.SelectionModelType.info, - position: position, - outsideJustification: outsideJustification, - insideJustification: insideJustification, - defaultHiddenSeries: defaultHiddenSeries, - showMeasures: showMeasures ?? false, - legendDefaultMeasure: - legendDefaultMeasure ?? common.LegendDefaultMeasure.none, - measureFormatter: measureFormatter, - secondaryMeasureFormatter: secondaryMeasureFormatter, - entryTextStyle: entryTextStyle); - } - - /// Create a legend with custom layout. - /// - /// By default, the legend is place above the chart and horizontally aligned - /// to the start of the draw area. - /// - /// [contentBuilder] builder for the custom layout. - /// - /// [position] the legend will be positioned relative to the chart. Default - /// position is top. - /// - /// [outsideJustification] justification of the legend relative to the chart - /// if the position is top, bottom, left, right. Default to start of the draw - /// area. - /// - /// [insideJustification] justification of the legend relative to the chart if - /// the position is inside. Default to top of the chart, start of draw area. - /// Start of draw area means left for LTR directionality, and right for RTL. - /// - /// [defaultHiddenSeries] lists the IDs of series that should be hidden on - /// first chart draw. - /// - /// [showMeasures] show measure values for each series. - /// - /// [legendDefaultMeasure] if measure should show when there is no selection. - /// This is set to none by default (only shows measure for selected data). - /// - /// [measureFormatter] formats measure value if measures are shown. - /// - /// [secondaryMeasureFormatter] formats measures if measures are shown for the - /// series that uses secondary measure axis. - factory SeriesLegend.customLayout( - LegendContentBuilder contentBuilder, { - common.BehaviorPosition position, - common.OutsideJustification outsideJustification, - common.InsideJustification insideJustification, - List defaultHiddenSeries, - bool showMeasures, - common.LegendDefaultMeasure legendDefaultMeasure, - common.MeasureFormatter measureFormatter, - common.MeasureFormatter secondaryMeasureFormatter, - common.TextStyleSpec entryTextStyle, - }) { - // Set defaults if empty. - position ??= defaultBehaviorPosition; - outsideJustification ??= defaultOutsideJustification; - insideJustification ??= defaultInsideJustification; - - return new SeriesLegend._internal( - contentBuilder: contentBuilder, - selectionModelType: common.SelectionModelType.info, - position: position, - outsideJustification: outsideJustification, - insideJustification: insideJustification, - defaultHiddenSeries: defaultHiddenSeries, - showMeasures: showMeasures ?? false, - legendDefaultMeasure: - legendDefaultMeasure ?? common.LegendDefaultMeasure.none, - measureFormatter: measureFormatter, - secondaryMeasureFormatter: secondaryMeasureFormatter, - entryTextStyle: entryTextStyle, - ); - } - - SeriesLegend._internal({ - this.contentBuilder, - this.selectionModelType, - this.position, - this.outsideJustification, - this.insideJustification, - this.defaultHiddenSeries, - this.showMeasures, - this.legendDefaultMeasure, - this.measureFormatter, - this.secondaryMeasureFormatter, - this.entryTextStyle, - }); - - @override - common.SeriesLegend createCommonBehavior() => - new _FlutterSeriesLegend(this); - - @override - void updateCommonBehavior(common.SeriesLegend commonBehavior) { - (commonBehavior as _FlutterSeriesLegend).config = this; - } - - /// All Legend behaviors get the same role ID, because you should only have - /// one legend on a chart. - @override - String get role => 'legend'; - - @override - bool operator ==(Object o) { - return o is SeriesLegend && - selectionModelType == o.selectionModelType && - contentBuilder == o.contentBuilder && - position == o.position && - outsideJustification == o.outsideJustification && - insideJustification == o.insideJustification && - new ListEquality().equals(defaultHiddenSeries, o.defaultHiddenSeries) && - showMeasures == o.showMeasures && - legendDefaultMeasure == o.legendDefaultMeasure && - measureFormatter == o.measureFormatter && - secondaryMeasureFormatter == o.secondaryMeasureFormatter && - entryTextStyle == o.entryTextStyle; - } - - @override - int get hashCode { - return hashValues( - selectionModelType, - contentBuilder, - position, - outsideJustification, - insideJustification, - defaultHiddenSeries, - showMeasures, - legendDefaultMeasure, - measureFormatter, - secondaryMeasureFormatter, - entryTextStyle); - } -} - -/// Flutter specific wrapper on the common Legend for building content. -class _FlutterSeriesLegend extends common.SeriesLegend - implements BuildableBehavior, TappableLegend { - SeriesLegend config; - - _FlutterSeriesLegend(this.config) - : super( - selectionModelType: config.selectionModelType, - measureFormatter: config.measureFormatter, - secondaryMeasureFormatter: config.secondaryMeasureFormatter, - legendDefaultMeasure: config.legendDefaultMeasure, - ) { - super.defaultHiddenSeries = config.defaultHiddenSeries; - super.entryTextStyle = config.entryTextStyle; - } - - @override - void updateLegend() { - (chartContext as ChartContainerRenderObject).requestRebuild(); - } - - @override - common.BehaviorPosition get position => config.position; - - @override - common.OutsideJustification get outsideJustification => - config.outsideJustification; - - @override - common.InsideJustification get insideJustification => - config.insideJustification; - - @override - Widget build(BuildContext context) { - final hasSelection = - legendState.legendEntries.any((entry) => entry.isSelected); - - // Show measures if [showMeasures] is true and there is a selection or if - // showing measures when there is no selection. - final showMeasures = config.showMeasures && - (hasSelection || - legendDefaultMeasure != common.LegendDefaultMeasure.none); - - return config.contentBuilder - .build(context, legendState, this, showMeasures: showMeasures); - } - - @override - onLegendEntryTapUp(common.LegendEntry detail) { - switch (legendTapHandling) { - case common.LegendTapHandling.hide: - _hideSeries(detail); - break; - - case common.LegendTapHandling.none: - default: - break; - } - } - - /// Handles tap events by hiding or un-hiding entries tapped in the legend. - /// - /// Tapping on a visible series in the legend will hide it. Tapping on a - /// hidden series will make it visible again. - void _hideSeries(common.LegendEntry detail) { - final seriesId = detail.series.id; - - // Handle the event by toggling the hidden state of the target. - if (isSeriesHidden(seriesId)) { - showSeries(seriesId); - } else { - hideSeries(seriesId); - } - - // Redraw the chart to actually hide hidden series. - chart.redraw(skipLayout: true, skipAnimation: false); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/line_point_highlighter.dart b/web/charts/flutter/lib/src/behaviors/line_point_highlighter.dart deleted file mode 100644 index 424f445d5..000000000 --- a/web/charts/flutter/lib/src/behaviors/line_point_highlighter.dart +++ /dev/null @@ -1,127 +0,0 @@ -// 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 'package:collection/collection.dart' show ListEquality; -import 'package:charts_common/common.dart' as common - show - LinePointHighlighter, - LinePointHighlighterFollowLineType, - SelectionModelType, - SymbolRenderer; -import 'package:flutter_web/widgets.dart' show hashValues; -import 'package:meta/meta.dart' show immutable; - -import 'chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that monitors the specified [SelectionModel] and darkens the -/// color for selected data. -/// -/// This is typically used for bars and pies to highlight segments. -/// -/// It is used in combination with SelectNearest to update the selection model -/// and expand selection out to the domain value. -@immutable -class LinePointHighlighter extends ChartBehavior { - final desiredGestures = new Set(); - - final common.SelectionModelType selectionModelType; - - /// Default radius of the dots if the series has no radius mapping function. - /// - /// When no radius mapping function is provided, this value will be used as - /// is. [radiusPaddingPx] will not be added to [defaultRadiusPx]. - final double defaultRadiusPx; - - /// Additional radius value added to the radius of the selected data. - /// - /// This value is only used when the series has a radius mapping function - /// defined. - final double radiusPaddingPx; - - final common.LinePointHighlighterFollowLineType showHorizontalFollowLine; - - final common.LinePointHighlighterFollowLineType showVerticalFollowLine; - - /// The dash pattern to be used for drawing the line. - /// - /// To disable dash pattern (to draw a solid line), pass in an empty list. - /// This is because if dashPattern is null or not set, it defaults to [1,3]. - final List dashPattern; - - /// Whether or not follow lines should be drawn across the entire chart draw - /// area, or just from the axis to the point. - /// - /// When disabled, measure follow lines will be drawn from the primary measure - /// axis to the point. In RTL mode, this means from the right-hand axis. In - /// LTR mode, from the left-hand axis. - final bool drawFollowLinesAcrossChart; - - /// Renderer used to draw the highlighted points. - final common.SymbolRenderer symbolRenderer; - - LinePointHighlighter( - {this.selectionModelType, - this.defaultRadiusPx, - this.radiusPaddingPx, - this.showHorizontalFollowLine, - this.showVerticalFollowLine, - this.dashPattern, - this.drawFollowLinesAcrossChart, - this.symbolRenderer}); - - @override - common.LinePointHighlighter createCommonBehavior() => - new common.LinePointHighlighter( - selectionModelType: selectionModelType, - defaultRadiusPx: defaultRadiusPx, - radiusPaddingPx: radiusPaddingPx, - showHorizontalFollowLine: showHorizontalFollowLine, - showVerticalFollowLine: showVerticalFollowLine, - dashPattern: dashPattern, - drawFollowLinesAcrossChart: drawFollowLinesAcrossChart, - symbolRenderer: symbolRenderer, - ); - - @override - void updateCommonBehavior(common.LinePointHighlighter commonBehavior) {} - - @override - String get role => 'LinePointHighlighter-${selectionModelType.toString()}'; - - @override - bool operator ==(Object o) { - return o is LinePointHighlighter && - defaultRadiusPx == o.defaultRadiusPx && - radiusPaddingPx == o.radiusPaddingPx && - showHorizontalFollowLine == o.showHorizontalFollowLine && - showVerticalFollowLine == o.showVerticalFollowLine && - selectionModelType == o.selectionModelType && - new ListEquality().equals(dashPattern, o.dashPattern) && - drawFollowLinesAcrossChart == o.drawFollowLinesAcrossChart; - } - - @override - int get hashCode { - return hashValues( - selectionModelType, - defaultRadiusPx, - radiusPaddingPx, - showHorizontalFollowLine, - showVerticalFollowLine, - dashPattern, - drawFollowLinesAcrossChart, - ); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/range_annotation.dart b/web/charts/flutter/lib/src/behaviors/range_annotation.dart deleted file mode 100644 index c094e9547..000000000 --- a/web/charts/flutter/lib/src/behaviors/range_annotation.dart +++ /dev/null @@ -1,117 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - AnnotationLabelAnchor, - AnnotationLabelDirection, - AnnotationLabelPosition, - AnnotationSegment, - Color, - MaterialPalette, - RangeAnnotation, - TextStyleSpec; -import 'package:collection/collection.dart' show ListEquality; -import 'package:flutter_web/widgets.dart' show hashValues; -import 'package:meta/meta.dart' show immutable; - -import 'chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that annotations domain ranges with a solid fill color. -/// -/// The annotations will be drawn underneath series data and chart axes. -/// -/// This is typically used for line charts to call out sections of the data -/// range. -@immutable -class RangeAnnotation extends ChartBehavior { - final desiredGestures = new Set(); - - /// List of annotations to render on the chart. - final List annotations; - - /// Configures where to anchor annotation label text. - final common.AnnotationLabelAnchor defaultLabelAnchor; - - /// Direction of label text on the annotations. - final common.AnnotationLabelDirection defaultLabelDirection; - - /// Configures where to place labels relative to the annotation. - final common.AnnotationLabelPosition defaultLabelPosition; - - /// Configures the style of label text. - final common.TextStyleSpec defaultLabelStyleSpec; - - /// Default color for annotations. - final common.Color defaultColor; - - /// Whether or not the range of the axis should be extended to include the - /// annotation start and end values. - final bool extendAxis; - - /// Space before and after label text. - final int labelPadding; - - RangeAnnotation(this.annotations, - {common.Color defaultColor, - this.defaultLabelAnchor, - this.defaultLabelDirection, - this.defaultLabelPosition, - this.defaultLabelStyleSpec, - this.extendAxis, - this.labelPadding}) - : defaultColor = common.MaterialPalette.gray.shade100; - - @override - common.RangeAnnotation createCommonBehavior() => - new common.RangeAnnotation(annotations, - defaultColor: defaultColor, - defaultLabelAnchor: defaultLabelAnchor, - defaultLabelDirection: defaultLabelDirection, - defaultLabelPosition: defaultLabelPosition, - defaultLabelStyleSpec: defaultLabelStyleSpec, - extendAxis: extendAxis, - labelPadding: labelPadding); - - @override - void updateCommonBehavior(common.RangeAnnotation commonBehavior) {} - - @override - String get role => 'RangeAnnotation'; - - @override - bool operator ==(Object o) { - return o is RangeAnnotation && - new ListEquality().equals(annotations, o.annotations) && - defaultColor == o.defaultColor && - extendAxis == o.extendAxis && - defaultLabelAnchor == o.defaultLabelAnchor && - defaultLabelDirection == o.defaultLabelDirection && - defaultLabelPosition == o.defaultLabelPosition && - defaultLabelStyleSpec == o.defaultLabelStyleSpec && - labelPadding == o.labelPadding; - } - - @override - int get hashCode => hashValues( - annotations, - defaultColor, - extendAxis, - defaultLabelAnchor, - defaultLabelDirection, - defaultLabelPosition, - defaultLabelStyleSpec, - labelPadding); -} diff --git a/web/charts/flutter/lib/src/behaviors/select_nearest.dart b/web/charts/flutter/lib/src/behaviors/select_nearest.dart deleted file mode 100644 index ae1e7d5f5..000000000 --- a/web/charts/flutter/lib/src/behaviors/select_nearest.dart +++ /dev/null @@ -1,147 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show ChartBehavior, SelectNearest, SelectionModelType, SelectionTrigger; - -import 'package:meta/meta.dart' show immutable; - -import 'chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that listens to the given eventTrigger and updates the -/// specified [SelectionModel]. This is used to pair input events to behaviors -/// that listen to selection changes. -/// -/// Input event types: -/// hover (default) - Mouse over/near data. -/// tap - Mouse/Touch on/near data. -/// pressHold - Mouse/Touch and drag across the data instead of panning. -/// longPressHold - Mouse/Touch for a while in one place then drag across the data. -/// -/// SelectionModels that can be updated: -/// info - To view the details of the selected items (ie: hover for web). -/// action - To select an item as an input, drill, or other selection. -/// -/// Other options available -/// expandToDomain - all data points that match the domain value of the -/// closest data point will be included in the selection. (Default: true) -/// selectClosestSeries - mark the series for the closest data point as -/// selected. (Default: true) -/// -/// You can add one SelectNearest for each model type that you are updating. -/// Any previous SelectNearest behavior for that selection model will be -/// removed. -@immutable -class SelectNearest extends ChartBehavior { - final Set desiredGestures; - - final common.SelectionModelType selectionModelType; - final common.SelectionTrigger eventTrigger; - final bool expandToDomain; - final bool selectAcrossAllDrawAreaComponents; - final bool selectClosestSeries; - final int maximumDomainDistancePx; - - SelectNearest._internal( - {this.selectionModelType, - this.expandToDomain = true, - this.selectAcrossAllDrawAreaComponents = false, - this.selectClosestSeries = true, - this.eventTrigger, - this.desiredGestures, - this.maximumDomainDistancePx}); - - factory SelectNearest( - {common.SelectionModelType selectionModelType = - common.SelectionModelType.info, - bool expandToDomain = true, - bool selectAcrossAllDrawAreaComponents = false, - bool selectClosestSeries = true, - common.SelectionTrigger eventTrigger = common.SelectionTrigger.tap, - int maximumDomainDistancePx}) { - return new SelectNearest._internal( - selectionModelType: selectionModelType, - expandToDomain: expandToDomain, - selectAcrossAllDrawAreaComponents: selectAcrossAllDrawAreaComponents, - selectClosestSeries: selectClosestSeries, - eventTrigger: eventTrigger, - desiredGestures: SelectNearest._getDesiredGestures(eventTrigger), - maximumDomainDistancePx: maximumDomainDistancePx); - } - - static Set _getDesiredGestures( - common.SelectionTrigger eventTrigger) { - final desiredGestures = new Set(); - switch (eventTrigger) { - case common.SelectionTrigger.tap: - desiredGestures..add(GestureType.onTap); - break; - case common.SelectionTrigger.tapAndDrag: - desiredGestures..add(GestureType.onTap)..add(GestureType.onDrag); - break; - case common.SelectionTrigger.pressHold: - case common.SelectionTrigger.longPressHold: - desiredGestures - ..add(GestureType.onTap) - ..add(GestureType.onLongPress) - ..add(GestureType.onDrag); - break; - case common.SelectionTrigger.hover: - default: - desiredGestures..add(GestureType.onHover); - break; - } - return desiredGestures; - } - - @override - common.SelectNearest createCommonBehavior() { - return new common.SelectNearest( - selectionModelType: selectionModelType, - eventTrigger: eventTrigger, - expandToDomain: expandToDomain, - selectClosestSeries: selectClosestSeries, - maximumDomainDistancePx: maximumDomainDistancePx); - } - - @override - void updateCommonBehavior(common.ChartBehavior commonBehavior) {} - - // TODO: Explore the performance impact of calculating this once - // at the constructor for this and common ChartBehaviors. - @override - String get role => 'SelectNearest-${selectionModelType.toString()}}'; - - bool operator ==(Object other) { - if (other is SelectNearest) { - return (selectionModelType == other.selectionModelType) && - (eventTrigger == other.eventTrigger) && - (expandToDomain == other.expandToDomain) && - (selectClosestSeries == other.selectClosestSeries) && - (maximumDomainDistancePx == other.maximumDomainDistancePx); - } else { - return false; - } - } - - int get hashCode { - int hashcode = selectionModelType.hashCode; - hashcode = hashcode * 37 + eventTrigger.hashCode; - hashcode = hashcode * 37 + expandToDomain.hashCode; - hashcode = hashcode * 37 + selectClosestSeries.hashCode; - hashcode = hashcode * 37 + maximumDomainDistancePx.hashCode; - return hashcode; - } -} diff --git a/web/charts/flutter/lib/src/behaviors/slider/slider.dart b/web/charts/flutter/lib/src/behaviors/slider/slider.dart deleted file mode 100644 index 30c93dc8a..000000000 --- a/web/charts/flutter/lib/src/behaviors/slider/slider.dart +++ /dev/null @@ -1,196 +0,0 @@ -// 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:charts_common/common.dart' as common - show - LayoutViewPaintOrder, - RectSymbolRenderer, - SelectionTrigger, - Slider, - SliderListenerCallback, - SliderStyle, - SymbolRenderer; -import 'package:flutter_web/widgets.dart' show hashValues; -import 'package:meta/meta.dart' show immutable; - -import '../chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that adds a slider widget to a chart. When the slider is -/// dropped after drag, it will report its domain position and nearest datum -/// value. This behavior only supports charts that use continuous scales. -/// -/// Input event types: -/// tapAndDrag - Mouse/Touch on the handle and drag across the chart. -/// pressHold - Mouse/Touch on the handle and drag across the chart instead of -/// panning. -/// longPressHold - Mouse/Touch for a while on the handle, then drag across -/// the data. -@immutable -class Slider extends ChartBehavior { - final Set desiredGestures; - - /// Type of input event for the slider. - /// - /// Input event types: - /// tapAndDrag - Mouse/Touch on the handle and drag across the chart. - /// pressHold - Mouse/Touch on the handle and drag across the chart instead - /// of panning. - /// longPressHold - Mouse/Touch for a while on the handle, then drag across - /// the data. - final common.SelectionTrigger eventTrigger; - - /// The order to paint slider on the canvas. - /// - /// The smaller number is drawn first. This value should be relative to - /// LayoutPaintViewOrder.slider (e.g. LayoutViewPaintOrder.slider + 1). - final int layoutPaintOrder; - - /// Initial domain position of the slider, in domain units. - final dynamic initialDomainValue; - - /// Callback function that will be called when the position of the slider - /// changes during a drag event. - /// - /// The callback will be given the current domain position of the slider. - final common.SliderListenerCallback onChangeCallback; - - /// Custom role ID for this slider - final String roleId; - - /// Whether or not the slider will snap onto the nearest datum (by domain - /// distance) when dragged. - final bool snapToDatum; - - /// Color and size styles for the slider. - final common.SliderStyle style; - - /// Renderer for the handle. Defaults to a rectangle. - final common.SymbolRenderer handleRenderer; - - Slider._internal( - {this.eventTrigger, - this.onChangeCallback, - this.initialDomainValue, - this.roleId, - this.snapToDatum, - this.style, - this.handleRenderer, - this.desiredGestures, - this.layoutPaintOrder}); - - /// Constructs a [Slider]. - /// - /// [eventTrigger] sets the type of gesture handled by the slider. - /// - /// [handleRenderer] draws a handle for the slider. Defaults to a rectangle. - /// - /// [initialDomainValue] sets the initial position of the slider in domain - /// units. The default is the center of the chart. - /// - /// [onChangeCallback] will be called when the position of the slider - /// changes during a drag event. - /// - /// [snapToDatum] configures the slider to snap snap onto the nearest datum - /// (by domain distance) when dragged. By default, the slider can be - /// positioned anywhere along the domain axis. - /// - /// [style] configures the color and sizing of the slider line and handle. - /// - /// [layoutPaintOrder] configures the order in which the behavior should be - /// painted. This value should be relative to LayoutPaintViewOrder.slider. - /// (e.g. LayoutViewPaintOrder.slider + 1). - factory Slider( - {common.SelectionTrigger eventTrigger, - common.SymbolRenderer handleRenderer, - dynamic initialDomainValue, - String roleId, - common.SliderListenerCallback onChangeCallback, - bool snapToDatum = false, - common.SliderStyle style, - int layoutPaintOrder = common.LayoutViewPaintOrder.slider}) { - eventTrigger ??= common.SelectionTrigger.tapAndDrag; - handleRenderer ??= new common.RectSymbolRenderer(); - // Default the handle size large enough to tap on a mobile device. - style ??= new common.SliderStyle(handleSize: Rectangle(0, 0, 20, 30)); - return new Slider._internal( - eventTrigger: eventTrigger, - handleRenderer: handleRenderer, - initialDomainValue: initialDomainValue, - onChangeCallback: onChangeCallback, - roleId: roleId, - snapToDatum: snapToDatum, - style: style, - desiredGestures: Slider._getDesiredGestures(eventTrigger), - layoutPaintOrder: layoutPaintOrder); - } - - static Set _getDesiredGestures( - common.SelectionTrigger eventTrigger) { - final desiredGestures = new Set(); - switch (eventTrigger) { - case common.SelectionTrigger.tapAndDrag: - desiredGestures..add(GestureType.onTap)..add(GestureType.onDrag); - break; - case common.SelectionTrigger.pressHold: - case common.SelectionTrigger.longPressHold: - desiredGestures - ..add(GestureType.onTap) - ..add(GestureType.onLongPress) - ..add(GestureType.onDrag); - break; - default: - throw new ArgumentError( - 'Slider does not support the event trigger ' + '"${eventTrigger}"'); - break; - } - return desiredGestures; - } - - @override - common.Slider createCommonBehavior() => new common.Slider( - eventTrigger: eventTrigger, - handleRenderer: handleRenderer, - initialDomainValue: initialDomainValue as D, - onChangeCallback: onChangeCallback, - roleId: roleId, - snapToDatum: snapToDatum, - style: style); - - @override - void updateCommonBehavior(common.Slider commonBehavior) {} - - @override - String get role => 'Slider-${eventTrigger.toString()}'; - - @override - bool operator ==(Object o) { - return o is Slider && - eventTrigger == o.eventTrigger && - handleRenderer == o.handleRenderer && - initialDomainValue == o.initialDomainValue && - onChangeCallback == o.onChangeCallback && - roleId == o.roleId && - snapToDatum == o.snapToDatum && - style == o.style && - layoutPaintOrder == o.layoutPaintOrder; - } - - @override - int get hashCode { - return hashValues(eventTrigger, handleRenderer, initialDomainValue, roleId, - snapToDatum, style, layoutPaintOrder); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/sliding_viewport.dart b/web/charts/flutter/lib/src/behaviors/sliding_viewport.dart deleted file mode 100644 index d8241af49..000000000 --- a/web/charts/flutter/lib/src/behaviors/sliding_viewport.dart +++ /dev/null @@ -1,53 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show SelectionModelType, SlidingViewport; - -import 'package:meta/meta.dart' show immutable; - -import 'chart_behavior.dart' show ChartBehavior, GestureType; - -/// Chart behavior that centers the viewport on the selected domain. -/// -/// It is used in combination with SelectNearest to update the selection model -/// and notify this behavior to update the viewport on selection change. -/// -/// This behavior can only be used on [CartesianChart]. -@immutable -class SlidingViewport extends ChartBehavior { - final desiredGestures = new Set(); - - final common.SelectionModelType selectionModelType; - - SlidingViewport([this.selectionModelType = common.SelectionModelType.info]); - - @override - common.SlidingViewport createCommonBehavior() => - new common.SlidingViewport(selectionModelType); - - @override - void updateCommonBehavior(common.SlidingViewport commonBehavior) {} - - @override - String get role => 'slidingViewport-${selectionModelType.toString()}'; - - @override - bool operator ==(Object o) => - o is SlidingViewport && selectionModelType == o.selectionModelType; - - @override - int get hashCode => selectionModelType.hashCode; -} diff --git a/web/charts/flutter/lib/src/behaviors/zoom/initial_hint_behavior.dart b/web/charts/flutter/lib/src/behaviors/zoom/initial_hint_behavior.dart deleted file mode 100644 index 2477ef48f..000000000 --- a/web/charts/flutter/lib/src/behaviors/zoom/initial_hint_behavior.dart +++ /dev/null @@ -1,131 +0,0 @@ -// 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 'package:flutter_web/widgets.dart' show AnimationController; - -import 'package:charts_common/common.dart' as common - show BaseChart, ChartBehavior, InitialHintBehavior; -import 'package:meta/meta.dart' show immutable; - -import '../../base_chart_state.dart' show BaseChartState; -import '../chart_behavior.dart' - show ChartBehavior, ChartStateBehavior, GestureType; - -@immutable -class InitialHintBehavior extends ChartBehavior { - final desiredGestures = new Set(); - - final Duration hintDuration; - final double maxHintTranslate; - final double maxHintScaleFactor; - - InitialHintBehavior( - {this.hintDuration, this.maxHintTranslate, this.maxHintScaleFactor}); - - @override - common.InitialHintBehavior createCommonBehavior() { - final behavior = new FlutterInitialHintBehavior(); - - if (hintDuration != null) { - behavior.hintDuration = hintDuration; - } - - if (maxHintTranslate != null) { - behavior.maxHintTranslate = maxHintTranslate; - } - - if (maxHintScaleFactor != null) { - behavior.maxHintScaleFactor = maxHintScaleFactor; - } - - return behavior; - } - - @override - void updateCommonBehavior(common.ChartBehavior commonBehavior) {} - - @override - String get role => 'InitialHint'; - - bool operator ==(Object other) { - return other is InitialHintBehavior && other.hintDuration == hintDuration; - } - - int get hashCode { - return hintDuration.hashCode; - } -} - -/// Adds a native animation controller required for [common.InitialHintBehavior] -/// to function. -class FlutterInitialHintBehavior extends common.InitialHintBehavior - implements ChartStateBehavior { - AnimationController _hintAnimator; - - BaseChartState _chartState; - - set chartState(BaseChartState chartState) { - assert(chartState != null); - - _chartState = chartState; - - _hintAnimator = _chartState.getAnimationController(this); - _hintAnimator?.addListener(onHintTick); - } - - @override - void startHintAnimation() { - super.startHintAnimation(); - - _hintAnimator - ..duration = hintDuration - ..forward(from: 0.0); - } - - @override - void stopHintAnimation() { - super.stopHintAnimation(); - - _hintAnimator?.stop(); - // Hint animation occurs only on the first draw. The hint animator is no - // longer needed after the hint animation stops and is removed. - _chartState.disposeAnimationController(this); - _hintAnimator = null; - } - - @override - double get hintAnimationPercent => _hintAnimator.value; - - bool _skippedFirstTick = true; - - @override - void onHintTick() { - // Skip the first tick on Flutter because the widget rebuild scheduled - // during onAnimation fails on an assert on render object in the framework. - if (_skippedFirstTick) { - _skippedFirstTick = false; - return; - } - - super.onHintTick(); - } - - @override - removeFrom(common.BaseChart chart) { - _chartState.disposeAnimationController(this); - _hintAnimator = null; - super.removeFrom(chart); - } -} diff --git a/web/charts/flutter/lib/src/behaviors/zoom/pan_and_zoom_behavior.dart b/web/charts/flutter/lib/src/behaviors/zoom/pan_and_zoom_behavior.dart deleted file mode 100644 index d0826be9c..000000000 --- a/web/charts/flutter/lib/src/behaviors/zoom/pan_and_zoom_behavior.dart +++ /dev/null @@ -1,64 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show ChartBehavior, PanAndZoomBehavior, PanningCompletedCallback; -import 'package:meta/meta.dart' show immutable; - -import '../chart_behavior.dart' show ChartBehavior, GestureType; -import 'pan_behavior.dart' show FlutterPanBehaviorMixin; - -@immutable -class PanAndZoomBehavior extends ChartBehavior { - final _desiredGestures = new Set.from([ - GestureType.onDrag, - ]); - - Set get desiredGestures => _desiredGestures; - - /// Optional callback that is called when pan / zoom is completed. - /// - /// When flinging this callback is called after the fling is completed. - /// This is because panning is only completed when the flinging stops. - final common.PanningCompletedCallback panningCompletedCallback; - - PanAndZoomBehavior({this.panningCompletedCallback}); - - @override - common.PanAndZoomBehavior createCommonBehavior() { - return new FlutterPanAndZoomBehavior() - ..panningCompletedCallback = panningCompletedCallback; - } - - @override - void updateCommonBehavior(common.ChartBehavior commonBehavior) {} - - @override - String get role => 'PanAndZoom'; - - bool operator ==(Object other) { - return other is PanAndZoomBehavior && - other.panningCompletedCallback == panningCompletedCallback; - } - - int get hashCode { - return panningCompletedCallback.hashCode; - } -} - -/// Adds fling gesture support to [common.PanAndZoomBehavior], by way of -/// [FlutterPanBehaviorMixin]. -class FlutterPanAndZoomBehavior extends common.PanAndZoomBehavior - with FlutterPanBehaviorMixin {} diff --git a/web/charts/flutter/lib/src/behaviors/zoom/pan_behavior.dart b/web/charts/flutter/lib/src/behaviors/zoom/pan_behavior.dart deleted file mode 100644 index 38c2a671a..000000000 --- a/web/charts/flutter/lib/src/behaviors/zoom/pan_behavior.dart +++ /dev/null @@ -1,186 +0,0 @@ -// 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 max, pow, Point; -import 'package:flutter_web_ui/ui.dart' hide Point; - -import 'package:flutter_web/widgets.dart' show AnimationController; - -import 'package:charts_common/common.dart' as common - show BaseChart, ChartBehavior, PanBehavior, PanningCompletedCallback; -import 'package:meta/meta.dart' show immutable; - -import '../../base_chart_state.dart' show BaseChartState; -import '../chart_behavior.dart' - show ChartBehavior, ChartStateBehavior, GestureType; - -@immutable -class PanBehavior extends ChartBehavior { - final _desiredGestures = new Set.from([ - GestureType.onDrag, - ]); - - /// Optional callback that is called when panning is completed. - /// - /// When flinging this callback is called after the fling is completed. - /// This is because panning is only completed when the flinging stops. - final common.PanningCompletedCallback panningCompletedCallback; - - PanBehavior({this.panningCompletedCallback}); - - Set get desiredGestures => _desiredGestures; - - @override - common.PanBehavior createCommonBehavior() { - return new FlutterPanBehavior() - ..panningCompletedCallback = panningCompletedCallback; - } - - @override - void updateCommonBehavior(common.ChartBehavior commonBehavior) {} - - @override - String get role => 'Pan'; - - bool operator ==(Object other) { - return other is PanBehavior && - other.panningCompletedCallback == panningCompletedCallback; - } - - int get hashCode { - return panningCompletedCallback.hashCode; - } -} - -/// Class extending [common.PanBehavior] with fling gesture support. -class FlutterPanBehavior = common.PanBehavior - with FlutterPanBehaviorMixin; - -/// Mixin that adds fling gesture support to [common.PanBehavior] or subclasses -/// thereof. -mixin FlutterPanBehaviorMixin on common.PanBehavior - implements ChartStateBehavior { - BaseChartState _chartState; - - set chartState(BaseChartState chartState) { - assert(chartState != null); - - _chartState = chartState; - _flingAnimator = _chartState.getAnimationController(this); - _flingAnimator?.addListener(_onFlingTick); - } - - AnimationController _flingAnimator; - - double _flingAnimationInitialTranslatePx; - double _flingAnimationTargetTranslatePx; - - bool _isFlinging = false; - - static const flingDistanceMultiplier = 0.15; - static const flingDeceleratorFactor = 1.0; - static const flingDurationMultiplier = 0.15; - static const minimumFlingVelocity = 300.0; - - @override - removeFrom(common.BaseChart chart) { - stopFlingAnimation(); - _chartState.disposeAnimationController(this); - _flingAnimator = null; - super.removeFrom(chart); - } - - @override - bool onTapTest(Point chartPoint) { - super.onTapTest(chartPoint); - - stopFlingAnimation(); - - return true; - } - - @override - bool onDragEnd( - Point localPosition, double scale, double pixelsPerSec) { - if (isPanning) { - // Ignore slow drag gestures to avoid jitter. - if (pixelsPerSec.abs() < minimumFlingVelocity) { - onPanEnd(); - return true; - } - - _startFling(pixelsPerSec); - } - - return super.onDragEnd(localPosition, scale, pixelsPerSec); - } - - /// Starts a 'fling' in the direction and speed given by [pixelsPerSec]. - void _startFling(double pixelsPerSec) { - final domainAxis = chart.domainAxis; - - _flingAnimationInitialTranslatePx = domainAxis.viewportTranslatePx; - _flingAnimationTargetTranslatePx = _flingAnimationInitialTranslatePx + - pixelsPerSec * flingDistanceMultiplier; - - final flingDuration = new Duration( - milliseconds: - max(200, (pixelsPerSec * flingDurationMultiplier).abs().round())); - - _flingAnimator - ..duration = flingDuration - ..forward(from: 0.0); - _isFlinging = true; - } - - /// Decelerates a fling event. - double _decelerate(double value) => flingDeceleratorFactor == 1.0 - ? 1.0 - (1.0 - value) * (1.0 - value) - : 1.0 - pow(1.0 - value, 2 * flingDeceleratorFactor); - - /// Updates the chart axis state on each tick of the [AnimationController]. - void _onFlingTick() { - if (!_isFlinging) { - return; - } - - final percent = _flingAnimator.value; - final deceleratedPercent = _decelerate(percent); - final translation = lerpDouble(_flingAnimationInitialTranslatePx, - _flingAnimationTargetTranslatePx, deceleratedPercent); - - final domainAxis = chart.domainAxis; - - domainAxis.setViewportSettings( - domainAxis.viewportScalingFactor, translation, - drawAreaWidth: chart.drawAreaBounds.width); - - if (percent >= 1.0) { - stopFlingAnimation(); - onPanEnd(); - chart.redraw(); - } else { - chart.redraw(skipAnimation: true, skipLayout: true); - } - } - - /// Stops any current fling animations that may be executing. - void stopFlingAnimation() { - if (_isFlinging) { - _isFlinging = false; - _flingAnimator?.stop(); - } - } -} diff --git a/web/charts/flutter/lib/src/canvas/circle_sector_painter.dart b/web/charts/flutter/lib/src/canvas/circle_sector_painter.dart deleted file mode 100644 index d7f98a588..000000000 --- a/web/charts/flutter/lib/src/canvas/circle_sector_painter.dart +++ /dev/null @@ -1,104 +0,0 @@ -// 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 cos, pi, sin, Point; -import 'package:flutter_web/material.dart'; -import 'package:charts_common/common.dart' as common show Color; - -/// Draws a sector of a circle, with an optional hole in the center. -class CircleSectorPainter { - /// Draws a sector of a circle, with an optional hole in the center. - /// - /// [center] The x, y coordinates of the circle's center. - /// [radius] The radius of the circle. - /// [innerRadius] Optional radius of a hole in the center of the circle that - /// should not be filled in as part of the sector. - /// [startAngle] The angle at which the arc starts, measured clockwise from - /// the positive x axis and expressed in radians. - /// [endAngle] The angle at which the arc ends, measured clockwise from the - /// positive x axis and expressed in radians. - /// [fill] Fill color for the sector. - /// [stroke] Stroke color of the arc and radius lines. - /// [strokeWidthPx] Stroke width of the arc and radius lines. - void draw( - {Canvas canvas, - Paint paint, - Point center, - double radius, - double innerRadius, - double startAngle, - double endAngle, - common.Color fill, - common.Color stroke, - double strokeWidthPx}) { - paint.color = new Color.fromARGB(fill.a, fill.r, fill.g, fill.b); - paint.style = PaintingStyle.fill; - - final innerRadiusStartPoint = new Point( - innerRadius * cos(startAngle) + center.x, - innerRadius * sin(startAngle) + center.y); - - final innerRadiusEndPoint = new Point( - innerRadius * cos(endAngle) + center.x, - innerRadius * sin(endAngle) + center.y); - - final radiusStartPoint = new Point( - radius * cos(startAngle) + center.x, - radius * sin(startAngle) + center.y); - - final centerOffset = new Offset(center.x, center.y); - - final isFullCircle = startAngle != null && - endAngle != null && - endAngle - startAngle == 2 * pi; - - final midpointAngle = (endAngle + startAngle) / 2; - - final path = new Path() - ..moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); - - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - - // For full circles, draw the arc in two parts. - if (isFullCircle) { - path.arcTo(new Rect.fromCircle(center: centerOffset, radius: radius), - startAngle, midpointAngle - startAngle, true); - path.arcTo(new Rect.fromCircle(center: centerOffset, radius: radius), - midpointAngle, endAngle - midpointAngle, true); - } else { - path.arcTo(new Rect.fromCircle(center: centerOffset, radius: radius), - startAngle, endAngle - startAngle, true); - } - - path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); - - // For full circles, draw the arc in two parts. - if (isFullCircle) { - path.arcTo(new Rect.fromCircle(center: centerOffset, radius: innerRadius), - endAngle, midpointAngle - endAngle, true); - path.arcTo(new Rect.fromCircle(center: centerOffset, radius: innerRadius), - midpointAngle, startAngle - midpointAngle, true); - } else { - path.arcTo(new Rect.fromCircle(center: centerOffset, radius: innerRadius), - endAngle, startAngle - endAngle, true); - } - - // Drawing two copies of this line segment, before and after the arcs, - // ensures that the path actually gets closed correctly. - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - - canvas.drawPath(path, paint); - } -} diff --git a/web/charts/flutter/lib/src/canvas/line_painter.dart b/web/charts/flutter/lib/src/canvas/line_painter.dart deleted file mode 100644 index 44d2d3bcc..000000000 --- a/web/charts/flutter/lib/src/canvas/line_painter.dart +++ /dev/null @@ -1,242 +0,0 @@ -// 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 'package:flutter_web_ui/ui.dart' as ui show Shader; -import 'dart:math' show Point, Rectangle; -import 'package:flutter_web/material.dart'; -import 'package:charts_common/common.dart' as common show Color; - -/// Draws a simple line. -/// -/// Lines may be styled with dash patterns similar to stroke-dasharray in SVG -/// path elements. Dash patterns are currently only supported between vertical -/// or horizontal line segments at this time. -class LinePainter { - /// Draws a simple line. - /// - /// [dashPattern] controls the pattern of dashes and gaps in a line. It is a - /// list of lengths of alternating dashes and gaps. The rendering is similar - /// to stroke-dasharray in SVG path elements. An odd number of values in the - /// pattern will be repeated to derive an even number of values. "1,2,3" is - /// equivalent to "1,2,3,1,2,3." - void draw( - {Canvas canvas, - Paint paint, - List points, - Rectangle clipBounds, - common.Color fill, - common.Color stroke, - bool roundEndCaps, - double strokeWidthPx, - List dashPattern, - ui.Shader shader}) { - if (points.isEmpty) { - return; - } - - // Apply clip bounds as a clip region. - if (clipBounds != null) { - canvas - ..save() - ..clipRect(new Rect.fromLTWH( - clipBounds.left.toDouble(), - clipBounds.top.toDouble(), - clipBounds.width.toDouble(), - clipBounds.height.toDouble())); - } - - paint.color = new Color.fromARGB(stroke.a, stroke.r, stroke.g, stroke.b); - if (shader != null) { - paint.shader = shader; - } - - // If the line has a single point, draw a circle. - if (points.length == 1) { - final point = points.first; - paint.style = PaintingStyle.fill; - canvas.drawCircle(new Offset(point.x, point.y), strokeWidthPx, paint); - } else { - if (strokeWidthPx != null) { - paint.strokeWidth = strokeWidthPx; - } - paint.strokeJoin = StrokeJoin.round; - paint.style = PaintingStyle.stroke; - - if (dashPattern == null || dashPattern.isEmpty) { - if (roundEndCaps == true) { - paint.strokeCap = StrokeCap.round; - } - - _drawSolidLine(canvas, paint, points); - } else { - _drawDashedLine(canvas, paint, points, dashPattern); - } - } - - if (clipBounds != null) { - canvas.restore(); - } - } - - /// Draws solid lines between each point. - void _drawSolidLine(Canvas canvas, Paint paint, List points) { - // TODO: Extract a native line component which constructs the - // appropriate underlying data structures to avoid conversion. - final path = new Path() - ..moveTo(points.first.x.toDouble(), points.first.y.toDouble()); - - for (var point in points) { - path.lineTo(point.x.toDouble(), point.y.toDouble()); - } - - canvas.drawPath(path, paint); - } - - /// Draws dashed lines lines between each point. - void _drawDashedLine( - Canvas canvas, Paint paint, List points, List dashPattern) { - final localDashPattern = new List.from(dashPattern); - - // If an odd number of parts are defined, repeat the pattern to get an even - // number. - if (dashPattern.length % 2 == 1) { - localDashPattern.addAll(dashPattern); - } - - // Stores the previous point in the series. - var previousSeriesPoint = _getOffset(points.first); - - var remainder = 0; - var solid = true; - var dashPatternIndex = 0; - - // Gets the next segment in the dash pattern, looping back to the - // beginning once the end has been reached. - var getNextDashPatternSegment = () { - final dashSegment = localDashPattern[dashPatternIndex]; - dashPatternIndex = (dashPatternIndex + 1) % localDashPattern.length; - return dashSegment; - }; - - // Array of points that is used to draw a connecting path when only a - // partial dash pattern segment can be drawn in the remaining length of a - // line segment (between two defined points in the shape). - var remainderPoints; - - // Draw the path through all the rest of the points in the series. - for (var pointIndex = 1; pointIndex < points.length; pointIndex++) { - // Stores the current point in the series. - final seriesPoint = _getOffset(points[pointIndex]); - - if (previousSeriesPoint == seriesPoint) { - // Bypass dash pattern handling if the points are the same. - } else { - // Stores the previous point along the current series line segment where - // we rendered a dash (or left a gap). - var previousPoint = previousSeriesPoint; - - var d = _getOffsetDistance(previousSeriesPoint, seriesPoint); - - while (d > 0) { - var dashSegment = - remainder > 0 ? remainder : getNextDashPatternSegment(); - remainder = 0; - - // Create a unit vector in the direction from previous to next point. - final v = seriesPoint - previousPoint; - final u = new Offset(v.dx / v.distance, v.dy / v.distance); - - // If the remaining distance is less than the length of the dash - // pattern segment, then cut off the pattern segment for this portion - // of the overall line. - final distance = d < dashSegment ? d : dashSegment.toDouble(); - - // Compute a vector representing the length of dash pattern segment to - // be drawn. - final nextPoint = previousPoint + (u * distance); - - // If we are in a solid portion of the dash pattern, draw a line. - // Else, move on. - if (solid) { - if (remainderPoints != null) { - // If we had a partial un-drawn dash from the previous point along - // the line, draw a path that includes it and the end of the dash - // pattern segment in the current line segment. - remainderPoints.add(new Offset(nextPoint.dx, nextPoint.dy)); - - final path = new Path() - ..moveTo(remainderPoints.first.dx, remainderPoints.first.dy); - - for (var p in remainderPoints) { - path.lineTo(p.dx, p.dy); - } - - canvas.drawPath(path, paint); - - remainderPoints = null; - } else { - if (d < dashSegment && pointIndex < points.length - 1) { - // If the remaining distance d is too small to fit this dash, - // and we have more points in the line, save off a series of - // remainder points so that we can draw a path segment moving in - // the direction of the next point. - // - // Note that we don't need to save anything off for the "blank" - // portions of the pattern because we still take the remaining - // distance into account before starting the next dash in the - // next line segment. - remainderPoints = [ - new Offset(previousPoint.dx, previousPoint.dy), - new Offset(nextPoint.dx, nextPoint.dy) - ]; - } else { - // Otherwise, draw a simple line segment for this dash. - canvas.drawLine(previousPoint, nextPoint, paint); - } - } - } - - solid = !solid; - previousPoint = nextPoint; - d = d - dashSegment; - } - - // Save off the remaining distance so that we can continue the dash (or - // gap) into the next line segment. - remainder = -d.round(); - - // If we have a remaining un-drawn distance for the current dash (or - // gap), revert the last change to "solid" so that we will continue - // either drawing a dash or leaving a gap. - if (remainder > 0) { - solid = !solid; - } - } - - previousSeriesPoint = seriesPoint; - } - } - - /// Converts a [Point] into an [Offset]. - Offset _getOffset(Point point) => - new Offset(point.x.toDouble(), point.y.toDouble()); - - /// Computes the distance between two [Offset]s, as if they were [Point]s. - num _getOffsetDistance(Offset o1, Offset o2) { - final p1 = new Point(o1.dx, o1.dy); - final p2 = new Point(o2.dx, o2.dy); - return p1.distanceTo(p2); - } -} diff --git a/web/charts/flutter/lib/src/canvas/pie_painter.dart b/web/charts/flutter/lib/src/canvas/pie_painter.dart deleted file mode 100644 index 7d75f551d..000000000 --- a/web/charts/flutter/lib/src/canvas/pie_painter.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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 cos, sin, Point; -import 'package:flutter_web/material.dart'; -import 'package:charts_common/common.dart' as common show CanvasPie; -import 'circle_sector_painter.dart' show CircleSectorPainter; - -/// Draws a pie chart, with an optional hole in the center. -class PiePainter { - CircleSectorPainter _circleSectorPainter; - - /// Draws a pie chart, with an optional hole in the center. - void draw(Canvas canvas, Paint paint, common.CanvasPie canvasPie) { - _circleSectorPainter ??= new CircleSectorPainter(); - - final center = canvasPie.center; - final radius = canvasPie.radius; - final innerRadius = canvasPie.innerRadius; - - for (var slice in canvasPie.slices) { - _circleSectorPainter.draw( - canvas: canvas, - paint: paint, - center: center, - radius: radius, - innerRadius: innerRadius, - startAngle: slice.startAngle, - endAngle: slice.endAngle, - fill: slice.fill); - } - - // Draw stroke lines between pie slices. This is done after the slices are - // drawn to ensure that they appear on top. - if (canvasPie.stroke != null && - canvasPie.strokeWidthPx != null && - canvasPie.slices.length > 1) { - paint.color = new Color.fromARGB(canvasPie.stroke.a, canvasPie.stroke.r, - canvasPie.stroke.g, canvasPie.stroke.b); - - paint.strokeWidth = canvasPie.strokeWidthPx; - paint.strokeJoin = StrokeJoin.bevel; - paint.style = PaintingStyle.stroke; - - final path = new Path(); - - for (var slice in canvasPie.slices) { - final innerRadiusStartPoint = new Point( - innerRadius * cos(slice.startAngle) + center.x, - innerRadius * sin(slice.startAngle) + center.y); - - final innerRadiusEndPoint = new Point( - innerRadius * cos(slice.endAngle) + center.x, - innerRadius * sin(slice.endAngle) + center.y); - - final radiusStartPoint = new Point( - radius * cos(slice.startAngle) + center.x, - radius * sin(slice.startAngle) + center.y); - - final radiusEndPoint = new Point( - radius * cos(slice.endAngle) + center.x, - radius * sin(slice.endAngle) + center.y); - - path.moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); - - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - - path.moveTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); - - path.lineTo(radiusEndPoint.x, radiusEndPoint.y); - } - - canvas.drawPath(path, paint); - } - } -} diff --git a/web/charts/flutter/lib/src/canvas/point_painter.dart b/web/charts/flutter/lib/src/canvas/point_painter.dart deleted file mode 100644 index efaa95e9e..000000000 --- a/web/charts/flutter/lib/src/canvas/point_painter.dart +++ /dev/null @@ -1,56 +0,0 @@ -// 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; -import 'package:flutter_web/material.dart'; -import 'package:charts_common/common.dart' as common show Color; - -/// Draws a simple point. -/// -/// TODO: Support for more shapes than circles? -class PointPainter { - void draw( - {Canvas canvas, - Paint paint, - Point point, - double radius, - common.Color fill, - common.Color stroke, - double strokeWidthPx}) { - if (point == null) { - return; - } - - if (fill != null) { - paint.color = new Color.fromARGB(fill.a, fill.r, fill.g, fill.b); - paint.style = PaintingStyle.fill; - - canvas.drawCircle( - new Offset(point.x.toDouble(), point.y.toDouble()), radius, paint); - } - - // [Canvas.drawCircle] does not support drawing a circle with both a fill - // and a stroke at this time. Use a separate circle for the stroke. - if (stroke != null && strokeWidthPx != null && strokeWidthPx > 0.0) { - paint.color = new Color.fromARGB(stroke.a, stroke.r, stroke.g, stroke.b); - paint.strokeWidth = strokeWidthPx; - paint.strokeJoin = StrokeJoin.bevel; - paint.style = PaintingStyle.stroke; - - canvas.drawCircle( - new Offset(point.x.toDouble(), point.y.toDouble()), radius, paint); - } - } -} diff --git a/web/charts/flutter/lib/src/canvas/polygon_painter.dart b/web/charts/flutter/lib/src/canvas/polygon_painter.dart deleted file mode 100644 index 600b0f6cd..000000000 --- a/web/charts/flutter/lib/src/canvas/polygon_painter.dart +++ /dev/null @@ -1,96 +0,0 @@ -// 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 'package:flutter_web/material.dart'; -import 'package:charts_common/common.dart' as common show Color; - -/// Draws a simple line. -/// -/// Lines may be styled with dash patterns similar to stroke-dasharray in SVG -/// path elements. Dash patterns are currently only supported between vertical -/// or horizontal line segments at this time. -class PolygonPainter { - /// Draws a simple line. - /// - /// [dashPattern] controls the pattern of dashes and gaps in a line. It is a - /// list of lengths of alternating dashes and gaps. The rendering is similar - /// to stroke-dasharray in SVG path elements. An odd number of values in the - /// pattern will be repeated to derive an even number of values. "1,2,3" is - /// equivalent to "1,2,3,1,2,3." - void draw( - {Canvas canvas, - Paint paint, - List points, - Rectangle clipBounds, - common.Color fill, - common.Color stroke, - double strokeWidthPx}) { - if (points.isEmpty) { - return; - } - - // Apply clip bounds as a clip region. - if (clipBounds != null) { - canvas - ..save() - ..clipRect(new Rect.fromLTWH( - clipBounds.left.toDouble(), - clipBounds.top.toDouble(), - clipBounds.width.toDouble(), - clipBounds.height.toDouble())); - } - - final strokeColor = stroke != null - ? new Color.fromARGB(stroke.a, stroke.r, stroke.g, stroke.b) - : null; - - final fillColor = fill != null - ? new Color.fromARGB(fill.a, fill.r, fill.g, fill.b) - : null; - - // If the line has a single point, draw a circle. - if (points.length == 1) { - final point = points.first; - paint.color = fillColor; - paint.style = PaintingStyle.fill; - canvas.drawCircle(new Offset(point.x, point.y), strokeWidthPx, paint); - } else { - if (strokeColor != null && strokeWidthPx != null) { - paint.strokeWidth = strokeWidthPx; - paint.strokeJoin = StrokeJoin.bevel; - paint.style = PaintingStyle.stroke; - } - - if (fillColor != null) { - paint.color = fillColor; - paint.style = PaintingStyle.fill; - } - - final path = new Path() - ..moveTo(points.first.x.toDouble(), points.first.y.toDouble()); - - for (var point in points) { - path.lineTo(point.x.toDouble(), point.y.toDouble()); - } - - canvas.drawPath(path, paint); - } - - if (clipBounds != null) { - canvas.restore(); - } - } -} diff --git a/web/charts/flutter/lib/src/cartesian_chart.dart b/web/charts/flutter/lib/src/cartesian_chart.dart deleted file mode 100644 index 959871d6e..000000000 --- a/web/charts/flutter/lib/src/cartesian_chart.dart +++ /dev/null @@ -1,125 +0,0 @@ -// 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:collection' show LinkedHashMap; -import 'package:meta/meta.dart' show immutable, protected; - -import 'package:charts_common/common.dart' as common - show - AxisSpec, - BaseChart, - CartesianChart, - NumericAxis, - NumericAxisSpec, - RTLSpec, - Series, - SeriesRendererConfig; -import 'base_chart_state.dart' show BaseChartState; -import 'behaviors/chart_behavior.dart' show ChartBehavior; -import 'base_chart.dart' show BaseChart, LayoutConfig; -import 'selection_model_config.dart' show SelectionModelConfig; -import 'user_managed_state.dart' show UserManagedState; - -@immutable -abstract class CartesianChart extends BaseChart { - final common.AxisSpec domainAxis; - final common.AxisSpec primaryMeasureAxis; - final common.AxisSpec secondaryMeasureAxis; - final LinkedHashMap disjointMeasureAxes; - final bool flipVerticalAxis; - - CartesianChart( - List> seriesList, { - bool animate, - Duration animationDuration, - this.domainAxis, - this.primaryMeasureAxis, - this.secondaryMeasureAxis, - this.disjointMeasureAxes, - common.SeriesRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - bool defaultInteractions: true, - LayoutConfig layoutConfig, - UserManagedState userManagedState, - this.flipVerticalAxis, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - defaultRenderer: defaultRenderer, - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - defaultInteractions: defaultInteractions, - layoutConfig: layoutConfig, - userManagedState: userManagedState, - ); - - @override - void updateCommonChart(common.BaseChart baseChart, BaseChart oldWidget, - BaseChartState chartState) { - super.updateCommonChart(baseChart, oldWidget, chartState); - - final prev = oldWidget as CartesianChart; - final chart = baseChart as common.CartesianChart; - - if (flipVerticalAxis != null) { - chart.flipVerticalAxisOutput = flipVerticalAxis; - } - - if (domainAxis != null && domainAxis != prev?.domainAxis) { - chart.domainAxisSpec = domainAxis; - chartState.markChartDirty(); - } - - if (primaryMeasureAxis != null && - primaryMeasureAxis != prev?.primaryMeasureAxis) { - chart.primaryMeasureAxisSpec = primaryMeasureAxis; - chartState.markChartDirty(); - } - - if (secondaryMeasureAxis != null && - secondaryMeasureAxis != prev?.secondaryMeasureAxis) { - chart.secondaryMeasureAxisSpec = secondaryMeasureAxis; - chartState.markChartDirty(); - } - - if (disjointMeasureAxes != null && - disjointMeasureAxes != prev?.disjointMeasureAxes) { - chart.disjointMeasureAxisSpecs = disjointMeasureAxes; - chartState.markChartDirty(); - } - } - - @protected - LinkedHashMap createDisjointMeasureAxes() { - if (disjointMeasureAxes != null) { - final disjointAxes = new LinkedHashMap(); - - disjointMeasureAxes - .forEach((String axisId, common.NumericAxisSpec axisSpec) { - disjointAxes[axisId] = axisSpec.createAxis(); - }); - - return disjointAxes; - } else { - return null; - } - } -} diff --git a/web/charts/flutter/lib/src/chart_canvas.dart b/web/charts/flutter/lib/src/chart_canvas.dart deleted file mode 100644 index 19409ffcc..000000000 --- a/web/charts/flutter/lib/src/chart_canvas.dart +++ /dev/null @@ -1,442 +0,0 @@ -// 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 'package:flutter_web_ui/ui.dart' as ui show Gradient, Shader; -import 'dart:math' show Point, Rectangle, max; -import 'package:charts_common/common.dart' as common - show - ChartCanvas, - CanvasBarStack, - CanvasPie, - Color, - FillPatternType, - GraphicsFactory, - StyleFactory, - TextElement, - TextDirection; -import 'package:flutter_web/material.dart'; -import 'text_element.dart' show TextElement; -import 'canvas/circle_sector_painter.dart' show CircleSectorPainter; -import 'canvas/line_painter.dart' show LinePainter; -import 'canvas/pie_painter.dart' show PiePainter; -import 'canvas/point_painter.dart' show PointPainter; -import 'canvas/polygon_painter.dart' show PolygonPainter; - -class ChartCanvas implements common.ChartCanvas { - /// Pixels to allow to overdraw above the draw area that fades to transparent. - static const double rect_top_gradient_pixels = 5; - - final Canvas canvas; - final common.GraphicsFactory graphicsFactory; - final _paint = new Paint(); - - CircleSectorPainter _circleSectorPainter; - LinePainter _linePainter; - PiePainter _piePainter; - PointPainter _pointPainter; - PolygonPainter _polygonPainter; - - ChartCanvas(this.canvas, this.graphicsFactory); - - @override - void drawCircleSector(Point center, double radius, double innerRadius, - double startAngle, double endAngle, - {common.Color fill, common.Color stroke, double strokeWidthPx}) { - _circleSectorPainter ??= new CircleSectorPainter(); - _circleSectorPainter.draw( - canvas: canvas, - paint: _paint, - center: center, - radius: radius, - innerRadius: innerRadius, - startAngle: startAngle, - endAngle: endAngle, - fill: fill, - stroke: stroke, - strokeWidthPx: strokeWidthPx); - } - - @override - void drawLine( - {List points, - Rectangle clipBounds, - common.Color fill, - common.Color stroke, - bool roundEndCaps, - double strokeWidthPx, - List dashPattern}) { - _linePainter ??= new LinePainter(); - _linePainter.draw( - canvas: canvas, - paint: _paint, - points: points, - clipBounds: clipBounds, - fill: fill, - stroke: stroke, - roundEndCaps: roundEndCaps, - strokeWidthPx: strokeWidthPx, - dashPattern: dashPattern); - } - - @override - void drawPie(common.CanvasPie canvasPie) { - _piePainter ??= new PiePainter(); - _piePainter.draw(canvas, _paint, canvasPie); - } - - @override - void drawPoint( - {Point point, - double radius, - common.Color fill, - common.Color stroke, - double strokeWidthPx}) { - _pointPainter ??= new PointPainter(); - _pointPainter.draw( - canvas: canvas, - paint: _paint, - point: point, - radius: radius, - fill: fill, - stroke: stroke, - strokeWidthPx: strokeWidthPx); - } - - @override - void drawPolygon( - {List points, - Rectangle clipBounds, - common.Color fill, - common.Color stroke, - double strokeWidthPx}) { - _polygonPainter ??= new PolygonPainter(); - _polygonPainter.draw( - canvas: canvas, - paint: _paint, - points: points, - clipBounds: clipBounds, - fill: fill, - stroke: stroke, - strokeWidthPx: strokeWidthPx); - } - - /// Creates a bottom to top gradient that transitions [fill] to transparent. - ui.Gradient _createHintGradient(double left, double top, common.Color fill) { - return new ui.Gradient.linear( - new Offset(left, top), - new Offset(left, top - rect_top_gradient_pixels), - [ - new Color.fromARGB(fill.a, fill.r, fill.g, fill.b), - new Color.fromARGB(0, fill.r, fill.g, fill.b) - ], - ); - } - - @override - void drawRect(Rectangle bounds, - {common.Color fill, - common.FillPatternType pattern, - common.Color stroke, - double strokeWidthPx, - Rectangle drawAreaBounds}) { - final drawStroke = - (strokeWidthPx != null && strokeWidthPx > 0.0 && stroke != null); - - final strokeWidthOffset = (drawStroke ? strokeWidthPx : 0); - - // Factor out stroke width, if a stroke is enabled. - final fillRectBounds = new Rectangle( - bounds.left + strokeWidthOffset / 2, - bounds.top + strokeWidthOffset / 2, - bounds.width - strokeWidthOffset, - bounds.height - strokeWidthOffset); - - switch (pattern) { - case common.FillPatternType.forwardHatch: - _drawForwardHatchPattern(fillRectBounds, canvas, - fill: fill, drawAreaBounds: drawAreaBounds); - break; - - case common.FillPatternType.solid: - default: - // Use separate rect for drawing stroke - _paint.color = new Color.fromARGB(fill.a, fill.r, fill.g, fill.b); - _paint.style = PaintingStyle.fill; - - // Apply a gradient to the top [rect_top_gradient_pixels] to transparent - // if the rectangle is higher than the [drawAreaBounds] top. - if (drawAreaBounds != null && bounds.top < drawAreaBounds.top) { - _paint.shader = _createHintGradient(drawAreaBounds.left.toDouble(), - drawAreaBounds.top.toDouble(), fill); - } - - canvas.drawRect(_getRect(fillRectBounds), _paint); - break; - } - - // [Canvas.drawRect] does not support drawing a rectangle with both a fill - // and a stroke at this time. Use a separate rect for the stroke. - if (drawStroke) { - _paint.color = new Color.fromARGB(stroke.a, stroke.r, stroke.g, stroke.b); - // Set shader to null if no draw area bounds so it can use the color - // instead. - _paint.shader = drawAreaBounds != null - ? _createHintGradient(drawAreaBounds.left.toDouble(), - drawAreaBounds.top.toDouble(), stroke) - : null; - _paint.strokeJoin = StrokeJoin.round; - _paint.strokeWidth = strokeWidthPx; - _paint.style = PaintingStyle.stroke; - - canvas.drawRect(_getRect(bounds), _paint); - } - - // Reset the shader. - _paint.shader = null; - } - - @override - void drawRRect(Rectangle bounds, - {common.Color fill, - common.Color stroke, - num radius, - bool roundTopLeft, - bool roundTopRight, - bool roundBottomLeft, - bool roundBottomRight}) { - // Use separate rect for drawing stroke - _paint.color = new Color.fromARGB(fill.a, fill.r, fill.g, fill.b); - _paint.style = PaintingStyle.fill; - - canvas.drawRRect( - _getRRect(bounds, - radius: radius, - roundTopLeft: roundTopLeft, - roundTopRight: roundTopRight, - roundBottomLeft: roundBottomLeft, - roundBottomRight: roundBottomRight), - _paint); - } - - @override - void drawBarStack(common.CanvasBarStack barStack, - {Rectangle drawAreaBounds}) { - // only clip if rounded rect. - - // Clip a rounded rect for the whole region if rounded bars. - final roundedCorners = 0 < barStack.radius; - - if (roundedCorners) { - canvas - ..save() - ..clipRRect(_getRRect( - barStack.fullStackRect, - radius: barStack.radius.toDouble(), - roundTopLeft: barStack.roundTopLeft, - roundTopRight: barStack.roundTopRight, - roundBottomLeft: barStack.roundBottomLeft, - roundBottomRight: barStack.roundBottomRight, - )); - } - - // Draw each bar. - for (var barIndex = 0; barIndex < barStack.segments.length; barIndex++) { - // TODO: Add configuration for hiding stack line. - // TODO: Don't draw stroke on bottom of bars. - final segment = barStack.segments[barIndex]; - drawRect(segment.bounds, - fill: segment.fill, - pattern: segment.pattern, - stroke: segment.stroke, - strokeWidthPx: segment.strokeWidthPx, - drawAreaBounds: drawAreaBounds); - } - - if (roundedCorners) { - canvas.restore(); - } - } - - @override - void drawText(common.TextElement textElement, int offsetX, int offsetY, - {double rotation = 0.0}) { - // Must be Flutter TextElement. - assert(textElement is TextElement); - - final flutterTextElement = textElement as TextElement; - final textDirection = flutterTextElement.textDirection; - final measurement = flutterTextElement.measurement; - - if (rotation != 0) { - // TODO: Remove once textAnchor works. - if (textDirection == common.TextDirection.rtl) { - offsetY += measurement.horizontalSliceWidth.toInt(); - } - - offsetX -= flutterTextElement.verticalFontShift; - - canvas.save(); - canvas.translate(offsetX.toDouble(), offsetY.toDouble()); - canvas.rotate(rotation); - - (textElement as TextElement) - .textPainter - .paint(canvas, new Offset(0.0, 0.0)); - - canvas.restore(); - } else { - // TODO: Remove once textAnchor works. - if (textDirection == common.TextDirection.rtl) { - offsetX -= measurement.horizontalSliceWidth.toInt(); - } - - // Account for missing center alignment. - if (textDirection == common.TextDirection.center) { - offsetX -= (measurement.horizontalSliceWidth / 2).ceil(); - } - - offsetY -= flutterTextElement.verticalFontShift; - - (textElement as TextElement) - .textPainter - .paint(canvas, new Offset(offsetX.toDouble(), offsetY.toDouble())); - } - } - - @override - void setClipBounds(Rectangle clipBounds) { - canvas - ..save() - ..clipRect(_getRect(clipBounds)); - } - - @override - void resetClipBounds() { - canvas.restore(); - } - - /// Convert dart:math [Rectangle] to Flutter [Rect]. - Rect _getRect(Rectangle rectangle) { - return new Rect.fromLTWH( - rectangle.left.toDouble(), - rectangle.top.toDouble(), - rectangle.width.toDouble(), - rectangle.height.toDouble()); - } - - /// Convert dart:math [Rectangle] and to Flutter [RRect]. - RRect _getRRect( - Rectangle rectangle, { - double radius, - bool roundTopLeft = false, - bool roundTopRight = false, - bool roundBottomLeft = false, - bool roundBottomRight = false, - }) { - final cornerRadius = - radius == 0 ? Radius.zero : new Radius.circular(radius); - - return new RRect.fromLTRBAndCorners( - rectangle.left.toDouble(), - rectangle.top.toDouble(), - rectangle.right.toDouble(), - rectangle.bottom.toDouble(), - topLeft: roundTopLeft ? cornerRadius : Radius.zero, - topRight: roundTopRight ? cornerRadius : Radius.zero, - bottomLeft: roundBottomLeft ? cornerRadius : Radius.zero, - bottomRight: roundBottomRight ? cornerRadius : Radius.zero); - } - - /// Draws a forward hatch pattern in the given bounds. - _drawForwardHatchPattern( - Rectangle bounds, - Canvas canvas, { - common.Color background, - common.Color fill, - double fillWidthPx = 4.0, - Rectangle drawAreaBounds, - }) { - background ??= common.StyleFactory.style.white; - fill ??= common.StyleFactory.style.black; - - // Fill in the shape with a solid background color. - _paint.color = new Color.fromARGB( - background.a, background.r, background.g, background.b); - _paint.style = PaintingStyle.fill; - - // Apply a gradient the background if bounds exceed the draw area. - if (drawAreaBounds != null && bounds.top < drawAreaBounds.top) { - _paint.shader = _createHintGradient(drawAreaBounds.left.toDouble(), - drawAreaBounds.top.toDouble(), background); - } - - canvas.drawRect(_getRect(bounds), _paint); - - // As a simplification, we will treat the bounds as a large square and fill - // it up with lines from the bottom-left corner to the top-right corner. - // Get the longer side of the bounds here for the size of this square. - final size = max(bounds.width, bounds.height); - - final x0 = bounds.left + size + fillWidthPx; - final x1 = bounds.left - fillWidthPx; - final y0 = bounds.bottom - size - fillWidthPx; - final y1 = bounds.bottom + fillWidthPx; - final offset = 8; - - final isVertical = bounds.height >= bounds.width; - - _linePainter ??= new LinePainter(); - - // The "first" line segment will be drawn from the bottom left corner of the - // bounds, up and towards the right. Start the loop N iterations "back" to - // draw partial line segments beneath (or to the left) of this segment, - // where N is the number of offsets that fit inside the smaller dimension of - // the bounds. - final smallSide = isVertical ? bounds.width : bounds.height; - final start = -(smallSide / offset).round() * offset; - - // Keep going until we reach the top or right of the bounds, depending on - // whether the rectangle is oriented vertically or horizontally. - final end = size + offset; - - // Create gradient for line painter if top bounds exceeded. - ui.Shader lineShader; - if (drawAreaBounds != null && bounds.top < drawAreaBounds.top) { - lineShader = _createHintGradient( - drawAreaBounds.left.toDouble(), drawAreaBounds.top.toDouble(), fill); - } - - for (int i = start; i < end; i = i + offset) { - // For vertical bounds, we need to draw lines from top to bottom. For - // bounds, we need to draw lines from left to right. - final modifier = isVertical ? -1 * i : i; - - // Draw a line segment in the bottom right corner of the pattern. - _linePainter.draw( - canvas: canvas, - paint: _paint, - points: [ - new Point(x0 + modifier, y0), - new Point(x1 + modifier, y1), - ], - stroke: fill, - strokeWidthPx: fillWidthPx, - shader: lineShader); - } - } - - @override - set drawingView(String viewName) {} -} diff --git a/web/charts/flutter/lib/src/chart_container.dart b/web/charts/flutter/lib/src/chart_container.dart deleted file mode 100644 index 62f89e24c..000000000 --- a/web/charts/flutter/lib/src/chart_container.dart +++ /dev/null @@ -1,419 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - A11yNode, - AxisDirection, - BaseChart, - ChartContext, - DateTimeFactory, - LocalDateTimeFactory, - ProxyGestureListener, - RTLSpec, - SelectionModelType, - Series, - Performance; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; -import 'package:flutter_web/scheduler.dart'; -import 'package:logging/logging.dart'; -import 'package:meta/meta.dart' show required; -import 'chart_canvas.dart' show ChartCanvas; -import 'chart_state.dart' show ChartState; -import 'base_chart.dart' show BaseChart; -import 'graphics_factory.dart' show GraphicsFactory; -import 'time_series_chart.dart' show TimeSeriesChart; -import 'user_managed_state.dart' show UserManagedState; - -/// Widget that inflates to a [CustomPaint] that implements common [ChartContext]. -class ChartContainer extends CustomPaint { - final BaseChart chartWidget; - final BaseChart oldChartWidget; - final ChartState chartState; - final double animationValue; - final bool rtl; - final common.RTLSpec rtlSpec; - final UserManagedState userManagedState; - - ChartContainer( - {@required this.oldChartWidget, - @required this.chartWidget, - @required this.chartState, - @required this.animationValue, - @required this.rtl, - @required this.rtlSpec, - this.userManagedState}); - - @override - RenderCustomPaint createRenderObject(BuildContext context) { - return new ChartContainerRenderObject()..reconfigure(this, context); - } - - @override - void updateRenderObject( - BuildContext context, ChartContainerRenderObject renderObject) { - renderObject.reconfigure(this, context); - } -} - -/// [RenderCustomPaint] that implements common [ChartContext]. -class ChartContainerRenderObject extends RenderCustomPaint - implements common.ChartContext { - common.BaseChart _chart; - List> _seriesList; - ChartState _chartState; - bool _chartContainerIsRtl = false; - common.RTLSpec _rtlSpec; - common.DateTimeFactory _dateTimeFactory; - bool _exploreMode = false; - List _a11yNodes; - - final Logger _log = new Logger('charts_flutter.charts_container'); - - /// Keeps the last time the configuration was changed and chart draw on the - /// common chart is called. - /// - /// An assert uses this value to check if the configuration changes more - /// frequently than a threshold. This is to notify developers of something - /// wrong in the configuration of their charts if it keeps changes (usually - /// due to equality checks not being implemented and when a new object is - /// created inside a new chart widget, a change is detected even if nothing - /// has changed). - DateTime _lastConfigurationChangeTime; - - /// The minimum time required before the next configuration change. - static const configurationChangeThresholdMs = 500; - - void reconfigure(ChartContainer config, BuildContext context) { - _chartState = config.chartState; - - _dateTimeFactory = (config.chartWidget is TimeSeriesChart) - ? (config.chartWidget as TimeSeriesChart).dateTimeFactory - : null; - _dateTimeFactory ??= new common.LocalDateTimeFactory(); - - if (_chart == null) { - common.Performance.time('chartsCreate'); - _chart = config.chartWidget.createCommonChart(_chartState); - _chart.init(this, new GraphicsFactory(context)); - common.Performance.timeEnd('chartsCreate'); - } - common.Performance.time('chartsConfig'); - config.chartWidget - .updateCommonChart(_chart, config.oldChartWidget, _chartState); - - _rtlSpec = config.rtlSpec ?? const common.RTLSpec(); - _chartContainerIsRtl = config.rtl ?? false; - - common.Performance.timeEnd('chartsConfig'); - - // If the configuration is changed more frequently than the threshold, - // log the occurrence and reset the configurationChanged flag to false - // to skip calling chart draw and avoid getting into an infinite rebuild - // cycle. - // - // One common cause for the configuration changing on every chart build - // is because a behavior is detected to have changed when it has not. - // A common case is when a setting is passed to a behavior is an object - // and doesn't override the equality checks. - if (_chartState.chartIsDirty) { - final currentTime = DateTime.now(); - final lastConfigurationBelowThreshold = _lastConfigurationChangeTime != - null && - currentTime.difference(_lastConfigurationChangeTime).inMilliseconds < - configurationChangeThresholdMs; - - _lastConfigurationChangeTime = currentTime; - - if (lastConfigurationBelowThreshold) { - _chartState.resetChartDirtyFlag(); - _log.warning( - 'Chart configuration is changing more frequent than threshold' - ' of $configurationChangeThresholdMs. Check if your behavior, axis,' - ' or renderer config is missing equality checks that may be causing' - ' configuration to be detected as changed. '); - } - } - - if (_chartState.chartIsDirty) { - _chart.configurationChanged(); - } - - // If series list changes or other configuration changed that triggered the - // _chartState.configurationChanged flag to be set (such as axis, behavior, - // and renderer changes). Otherwise, the chart only requests repainting and - // does not reprocess the series. - // - // Series list is considered "changed" based on the instance. - if (_seriesList != config.chartWidget.seriesList || - _chartState.chartIsDirty) { - _chartState.resetChartDirtyFlag(); - _seriesList = config.chartWidget.seriesList; - - // Clear out the a11y nodes generated. - _a11yNodes = null; - - common.Performance.time('chartsDraw'); - _chart.draw(_seriesList); - common.Performance.timeEnd('chartsDraw'); - - // This is needed because when a series changes we need to reset flutter's - // animation value from 1.0 back to 0.0. - _chart.animationPercent = 0.0; - markNeedsLayout(); - } else { - _chart.animationPercent = config.animationValue; - markNeedsPaint(); - } - - _updateUserManagedState(config.userManagedState); - - // Set the painter used for calling common chart for paint. - // This painter is also used to generate semantic nodes for a11y. - _setNewPainter(); - } - - /// If user managed state is set, check each setting to see if it is different - /// than internal chart state and only update if different. - _updateUserManagedState(UserManagedState newState) { - if (newState == null) { - return; - } - - // Only override the selection model if it is different than the existing - // selection model so update listeners are not unnecessarily triggered. - for (common.SelectionModelType type in newState.selectionModels.keys) { - final model = _chart.getSelectionModel(type); - - final userModel = - newState.selectionModels[type].getModel(_chart.currentSeriesList); - - if (model != userModel) { - model.updateSelection( - userModel.selectedDatum, userModel.selectedSeries); - } - } - } - - @override - void performLayout() { - common.Performance.time('chartsLayout'); - _chart.measure(constraints.maxWidth.toInt(), constraints.maxHeight.toInt()); - _chart.layout(constraints.maxWidth.toInt(), constraints.maxHeight.toInt()); - common.Performance.timeEnd('chartsLayout'); - size = constraints.biggest; - - // Check if the gestures registered in gesture registry matches what the - // common chart is listening to. - // TODO: Still need a test for this for sanity sake. -// assert(_desiredGestures -// .difference(_chart.gestureProxy.listenedGestures) -// .isEmpty); - } - - @override - void markNeedsLayout() { - super.markNeedsLayout(); - if (parent != null) { - markParentNeedsLayout(); - } - } - - @override - bool hitTestSelf(Offset position) => true; - - @override - void requestRedraw() {} - - @override - void requestAnimation(Duration transition) { - void startAnimationController(_) { - _chartState.setAnimation(transition); - } - - // Sometimes chart behaviors try to draw the chart outside of a Flutter draw - // cycle. Schedule a frame manually to handle these cases. - if (!SchedulerBinding.instance.hasScheduledFrame) { - SchedulerBinding.instance.scheduleFrame(); - } - - SchedulerBinding.instance.addPostFrameCallback(startAnimationController); - } - - /// Request Flutter to rebuild the widget/container of chart. - /// - /// This is different than requesting redraw and paint because those only - /// affect the chart widget. This is for requesting rebuild of the Flutter - /// widget that contains the chart widget. This is necessary for supporting - /// Flutter widgets that are layout with the chart. - /// - /// Example is legends, a legend widget can be layout on top of the chart - /// widget or along the sides of the chart. Requesting a rebuild allows - /// the legend to layout and redraw itself. - void requestRebuild() { - void doRebuild(_) { - _chartState.requestRebuild(); - } - - // Flutter does not allow requesting rebuild during the build cycle, this - // schedules rebuild request to happen after the current build cycle. - // This is needed to request rebuild after the legend has been added in the - // post process phase of the chart, which happens during the chart widget's - // build cycle. - SchedulerBinding.instance.addPostFrameCallback(doRebuild); - } - - /// When Flutter's markNeedsLayout is called, layout and paint are both - /// called. If animations are off, Flutter's paint call after layout will - /// paint the chart. If animations are on, Flutter's paint is called with the - /// initial animation value and then the animation controller is started after - /// this first build cycle. - @override - void requestPaint() { - markNeedsPaint(); - } - - @override - double get pixelsPerDp => 1.0; - - @override - bool get chartContainerIsRtl => _chartContainerIsRtl; - - @override - common.RTLSpec get rtlSpec => _rtlSpec; - - @override - bool get isRtl => - _chartContainerIsRtl && - _rtlSpec?.axisDirection == common.AxisDirection.reversed; - - @override - bool get isTappable => _chart.isTappable; - - @override - common.DateTimeFactory get dateTimeFactory => _dateTimeFactory; - - /// Gets the chart's gesture listener. - common.ProxyGestureListener get gestureProxy => _chart.gestureProxy; - - TextDirection get textDirection => - _chartContainerIsRtl ? TextDirection.rtl : TextDirection.ltr; - - @override - void enableA11yExploreMode(List nodes, - {String announcement}) { - _a11yNodes = nodes; - _exploreMode = true; - _setNewPainter(); - requestRebuild(); - if (announcement != null) { - SemanticsService.announce(announcement, textDirection); - } - } - - @override - void disableA11yExploreMode({String announcement}) { - _a11yNodes = []; - _exploreMode = false; - _setNewPainter(); - requestRebuild(); - if (announcement != null) { - SemanticsService.announce(announcement, textDirection); - } - } - - void _setNewPainter() { - painter = new ChartContainerCustomPaint( - oldPainter: painter, - chart: _chart, - exploreMode: _exploreMode, - a11yNodes: _a11yNodes, - textDirection: textDirection); - } -} - -class ChartContainerCustomPaint extends CustomPainter { - final common.BaseChart chart; - final bool exploreMode; - final List a11yNodes; - final TextDirection textDirection; - - factory ChartContainerCustomPaint( - {ChartContainerCustomPaint oldPainter, - common.BaseChart chart, - bool exploreMode, - List a11yNodes, - TextDirection textDirection}) { - if (oldPainter != null && - oldPainter.exploreMode == exploreMode && - oldPainter.a11yNodes == a11yNodes && - oldPainter.textDirection == textDirection) { - return oldPainter; - } else { - return new ChartContainerCustomPaint._internal( - chart: chart, - exploreMode: exploreMode ?? false, - a11yNodes: a11yNodes ?? [], - textDirection: textDirection ?? TextDirection.ltr); - } - } - - ChartContainerCustomPaint._internal( - {this.chart, this.exploreMode, this.a11yNodes, this.textDirection}); - - @override - void paint(Canvas canvas, Size size) { - common.Performance.time('chartsPaint'); - final chartsCanvas = new ChartCanvas(canvas, chart.graphicsFactory); - chart.paint(chartsCanvas); - common.Performance.timeEnd('chartsPaint'); - } - - /// Common chart requests rebuild that handle repaint requests. - @override - bool shouldRepaint(ChartContainerCustomPaint oldPainter) => false; - - /// Rebuild semantics when explore mode is toggled semantic properties change. - @override - bool shouldRebuildSemantics(ChartContainerCustomPaint oldDelegate) { - return exploreMode != oldDelegate.exploreMode || - a11yNodes != oldDelegate.a11yNodes || - textDirection != textDirection; - } - - @override - SemanticsBuilderCallback get semanticsBuilder => _buildSemantics; - - List _buildSemantics(Size size) { - final nodes = []; - - for (common.A11yNode node in a11yNodes) { - final rect = new Rect.fromLTWH( - node.boundingBox.left.toDouble(), - node.boundingBox.top.toDouble(), - node.boundingBox.width.toDouble(), - node.boundingBox.height.toDouble()); - nodes.add(new CustomPainterSemantics( - rect: rect, - properties: new SemanticsProperties( - value: node.label, - textDirection: textDirection, - onDidGainAccessibilityFocus: node.onFocus))); - } - - return nodes; - } -} diff --git a/web/charts/flutter/lib/src/chart_gesture_detector.dart b/web/charts/flutter/lib/src/chart_gesture_detector.dart deleted file mode 100644 index f24e6117a..000000000 --- a/web/charts/flutter/lib/src/chart_gesture_detector.dart +++ /dev/null @@ -1,136 +0,0 @@ -// 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:async' show Timer; -import 'dart:math' show Point; -import 'package:flutter_web/material.dart' - show - BuildContext, - GestureDetector, - ScaleEndDetails, - ScaleStartDetails, - ScaleUpdateDetails, - TapDownDetails, - TapUpDetails; - -import 'behaviors/chart_behavior.dart' show GestureType; -import 'chart_container.dart' show ChartContainer, ChartContainerRenderObject; -import 'util.dart' show getChartContainerRenderObject; - -// From https://docs.flutter.io/flutter/gestures/kLongPressTimeout-constant.html -const Duration _kLongPressTimeout = const Duration(milliseconds: 500); - -class ChartGestureDetector { - bool _listeningForLongPress; - - bool _isDragging = false; - - Timer _longPressTimer; - Point _lastTapPoint; - double _lastScale; - - _ContainerResolver _containerResolver; - - makeWidget(BuildContext context, ChartContainer chartContainer, - Set desiredGestures) { - _containerResolver = - () => getChartContainerRenderObject(context.findRenderObject()); - - final wantTapDown = desiredGestures.isNotEmpty; - final wantTap = desiredGestures.contains(GestureType.onTap); - final wantDrag = desiredGestures.contains(GestureType.onDrag); - - // LongPress is special, we'd like to be able to trigger long press before - // Drag/Press to trigger tooltips then explore with them. This means we - // can't rely on gesture detection since it will block out the scale - // gestures. - _listeningForLongPress = desiredGestures.contains(GestureType.onLongPress); - - return new GestureDetector( - child: chartContainer, - onTapDown: wantTapDown ? onTapDown : null, - onTapUp: wantTap ? onTapUp : null, - onScaleStart: wantDrag ? onScaleStart : null, - onScaleUpdate: wantDrag ? onScaleUpdate : null, - onScaleEnd: wantDrag ? onScaleEnd : null, - ); - } - - void onTapDown(TapDownDetails d) { - final container = _containerResolver(); - final localPosition = container.globalToLocal(d.globalPosition); - _lastTapPoint = new Point(localPosition.dx, localPosition.dy); - container.gestureProxy.onTapTest(_lastTapPoint); - - // Kick off a timer to see if this is a LongPress. - if (_listeningForLongPress) { - _longPressTimer = new Timer(_kLongPressTimeout, () { - onLongPress(); - _longPressTimer = null; - }); - } - } - - void onTapUp(TapUpDetails d) { - _longPressTimer?.cancel(); - - final container = _containerResolver(); - final localPosition = container.globalToLocal(d.globalPosition); - _lastTapPoint = new Point(localPosition.dx, localPosition.dy); - container.gestureProxy.onTap(_lastTapPoint); - } - - void onLongPress() { - final container = _containerResolver(); - container.gestureProxy.onLongPress(_lastTapPoint); - } - - void onScaleStart(ScaleStartDetails d) { - _longPressTimer?.cancel(); - - final container = _containerResolver(); - final localPosition = container.globalToLocal(d.focalPoint); - _lastTapPoint = new Point(localPosition.dx, localPosition.dy); - - _isDragging = container.gestureProxy.onDragStart(_lastTapPoint); - } - - void onScaleUpdate(ScaleUpdateDetails d) { - if (!_isDragging) { - return; - } - - final container = _containerResolver(); - final localPosition = container.globalToLocal(d.focalPoint); - _lastTapPoint = new Point(localPosition.dx, localPosition.dy); - _lastScale = d.scale; - - container.gestureProxy.onDragUpdate(_lastTapPoint, d.scale); - } - - void onScaleEnd(ScaleEndDetails d) { - if (!_isDragging) { - return; - } - - final container = _containerResolver(); - - container.gestureProxy - .onDragEnd(_lastTapPoint, _lastScale, d.velocity.pixelsPerSecond.dx); - } -} - -// Exposed for testing. -typedef ChartContainerRenderObject _ContainerResolver(); diff --git a/web/charts/flutter/lib/src/chart_state.dart b/web/charts/flutter/lib/src/chart_state.dart deleted file mode 100644 index 6103968bf..000000000 --- a/web/charts/flutter/lib/src/chart_state.dart +++ /dev/null @@ -1,36 +0,0 @@ -// 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. - -abstract class ChartState { - void setAnimation(Duration transition); - - /// Request to the native platform to rebuild the chart. - void requestRebuild(); - - /// Informs the chart that the configuration has changed. - /// - /// This flag is set by checks that detect if a configuration has changed, - /// such as behaviors, axis, and renderers. - /// - /// This flag is read on chart rebuild, if chart is marked as dirty, then the - /// chart will call a base chart draw. - void markChartDirty(); - - /// Reset the chart dirty flag. - void resetChartDirtyFlag(); - - /// Gets if the chart is dirty. - bool get chartIsDirty; -} diff --git a/web/charts/flutter/lib/src/combo_chart/combo_chart.dart b/web/charts/flutter/lib/src/combo_chart/combo_chart.dart deleted file mode 100644 index a9ef033b8..000000000 --- a/web/charts/flutter/lib/src/combo_chart/combo_chart.dart +++ /dev/null @@ -1,122 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show - AxisSpec, - NumericCartesianChart, - OrdinalCartesianChart, - RTLSpec, - Series, - SeriesRendererConfig; -import '../behaviors/chart_behavior.dart' show ChartBehavior; -import '../base_chart.dart' show LayoutConfig; -import '../base_chart_state.dart' show BaseChartState; -import '../cartesian_chart.dart' show CartesianChart; -import '../selection_model_config.dart' show SelectionModelConfig; - -/// A numeric combo chart supports rendering each series of data with different -/// series renderers. -/// -/// Note that if you have DateTime data, you should use [TimeSeriesChart]. We do -/// not expose a separate DateTimeComboChart because it would just be a copy of -/// that chart. -class NumericComboChart extends CartesianChart { - NumericComboChart( - List seriesList, { - bool animate, - Duration animationDuration, - common.AxisSpec domainAxis, - common.AxisSpec primaryMeasureAxis, - common.AxisSpec secondaryMeasureAxis, - common.SeriesRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - LayoutConfig layoutConfig, - bool defaultInteractions: true, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - domainAxis: domainAxis, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - defaultRenderer: defaultRenderer, - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - layoutConfig: layoutConfig, - defaultInteractions: defaultInteractions, - ); - - @override - common.NumericCartesianChart createCommonChart(BaseChartState chartState) { - // Optionally create primary and secondary measure axes if the chart was - // configured with them. If no axes were configured, then the chart will - // use its default types (usually a numeric axis). - return new common.NumericCartesianChart( - layoutConfig: layoutConfig?.commonLayoutConfig, - primaryMeasureAxis: primaryMeasureAxis?.createAxis(), - secondaryMeasureAxis: secondaryMeasureAxis?.createAxis()); - } -} - -/// An ordinal combo chart supports rendering each series of data with different -/// series renderers. -class OrdinalComboChart extends CartesianChart { - OrdinalComboChart( - List seriesList, { - bool animate, - Duration animationDuration, - common.AxisSpec domainAxis, - common.AxisSpec primaryMeasureAxis, - common.AxisSpec secondaryMeasureAxis, - common.SeriesRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - LayoutConfig layoutConfig, - bool defaultInteractions: true, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - domainAxis: domainAxis, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - defaultRenderer: defaultRenderer, - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - layoutConfig: layoutConfig, - defaultInteractions: defaultInteractions, - ); - - @override - common.OrdinalCartesianChart createCommonChart(BaseChartState chartState) { - // Optionally create primary and secondary measure axes if the chart was - // configured with them. If no axes were configured, then the chart will - // use its default types (usually a numeric axis). - return new common.OrdinalCartesianChart( - layoutConfig: layoutConfig?.commonLayoutConfig, - primaryMeasureAxis: primaryMeasureAxis?.createAxis(), - secondaryMeasureAxis: secondaryMeasureAxis?.createAxis()); - } -} diff --git a/web/charts/flutter/lib/src/graphics_factory.dart b/web/charts/flutter/lib/src/graphics_factory.dart deleted file mode 100644 index b1027c9d4..000000000 --- a/web/charts/flutter/lib/src/graphics_factory.dart +++ /dev/null @@ -1,50 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show GraphicsFactory, LineStyle, TextElement, TextStyle; -import 'package:flutter_web/widgets.dart' show BuildContext, MediaQuery; -import 'line_style.dart' show LineStyle; -import 'text_element.dart' show TextElement; -import 'text_style.dart' show TextStyle; - -class GraphicsFactory implements common.GraphicsFactory { - final double textScaleFactor; - - GraphicsFactory(BuildContext context, - {GraphicsFactoryHelper helper = const GraphicsFactoryHelper()}) - : textScaleFactor = helper.getTextScaleFactorOf(context); - - /// Returns a [TextStyle] object. - @override - common.TextStyle createTextPaint() => new TextStyle(); - - /// Returns a text element from [text] and [style]. - @override - common.TextElement createTextElement(String text) { - return new TextElement(text, textScaleFactor: textScaleFactor); - } - - @override - common.LineStyle createLinePaint() => new LineStyle(); -} - -/// Wraps the MediaQuery function to allow for testing. -class GraphicsFactoryHelper { - const GraphicsFactoryHelper(); - - double getTextScaleFactorOf(BuildContext context) => - MediaQuery.textScaleFactorOf(context); -} diff --git a/web/charts/flutter/lib/src/line_chart.dart b/web/charts/flutter/lib/src/line_chart.dart deleted file mode 100644 index f0407cb3f..000000000 --- a/web/charts/flutter/lib/src/line_chart.dart +++ /dev/null @@ -1,90 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import 'package:charts_common/common.dart' as common - show - AxisSpec, - LineChart, - NumericAxisSpec, - RTLSpec, - Series, - LineRendererConfig, - SeriesRendererConfig; -import 'behaviors/line_point_highlighter.dart' show LinePointHighlighter; -import 'behaviors/chart_behavior.dart' show ChartBehavior; -import 'base_chart.dart' show LayoutConfig; -import 'base_chart_state.dart' show BaseChartState; -import 'cartesian_chart.dart' show CartesianChart; -import 'selection_model_config.dart' show SelectionModelConfig; -import 'user_managed_state.dart' show UserManagedState; - -class LineChart extends CartesianChart { - LineChart( - List seriesList, { - bool animate, - Duration animationDuration, - common.AxisSpec domainAxis, - common.AxisSpec primaryMeasureAxis, - common.AxisSpec secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes, - common.LineRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - LayoutConfig layoutConfig, - bool defaultInteractions: true, - bool flipVerticalAxis, - UserManagedState userManagedState, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - domainAxis: domainAxis, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes, - defaultRenderer: defaultRenderer, - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - layoutConfig: layoutConfig, - defaultInteractions: defaultInteractions, - flipVerticalAxis: flipVerticalAxis, - userManagedState: userManagedState, - ); - - @override - common.LineChart createCommonChart(BaseChartState chartState) { - // Optionally create primary and secondary measure axes if the chart was - // configured with them. If no axes were configured, then the chart will - // use its default types (usually a numeric axis). - return new common.LineChart( - layoutConfig: layoutConfig?.commonLayoutConfig, - primaryMeasureAxis: primaryMeasureAxis?.createAxis(), - secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), - disjointMeasureAxes: createDisjointMeasureAxes()); - } - - @override - void addDefaultInteractions(List behaviors) { - super.addDefaultInteractions(behaviors); - - behaviors.add(new LinePointHighlighter()); - } -} diff --git a/web/charts/flutter/lib/src/line_style.dart b/web/charts/flutter/lib/src/line_style.dart deleted file mode 100644 index 2353dafb8..000000000 --- a/web/charts/flutter/lib/src/line_style.dart +++ /dev/null @@ -1,25 +0,0 @@ -// 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 'package:charts_common/common.dart' as common show Color, LineStyle; - -class LineStyle implements common.LineStyle { - @override - common.Color color; - @override - List dashPattern; - @override - int strokeWidth; -} diff --git a/web/charts/flutter/lib/src/pie_chart.dart b/web/charts/flutter/lib/src/pie_chart.dart deleted file mode 100644 index 13ce35c7a..000000000 --- a/web/charts/flutter/lib/src/pie_chart.dart +++ /dev/null @@ -1,54 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show ArcRendererConfig, PieChart, RTLSpec, Series; -import 'behaviors/chart_behavior.dart' show ChartBehavior; -import 'base_chart.dart' show BaseChart, LayoutConfig; -import 'base_chart_state.dart' show BaseChartState; -import 'selection_model_config.dart' show SelectionModelConfig; - -class PieChart extends BaseChart { - PieChart( - List seriesList, { - bool animate, - Duration animationDuration, - common.ArcRendererConfig defaultRenderer, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - LayoutConfig layoutConfig, - bool defaultInteractions: true, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - defaultRenderer: defaultRenderer, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - layoutConfig: layoutConfig, - defaultInteractions: defaultInteractions, - ); - - @override - common.PieChart createCommonChart(BaseChartState chartState) => - new common.PieChart(layoutConfig: layoutConfig?.commonLayoutConfig); - - @override - void addDefaultInteractions(List behaviors) { - super.addDefaultInteractions(behaviors); - } -} diff --git a/web/charts/flutter/lib/src/scatter_plot_chart.dart b/web/charts/flutter/lib/src/scatter_plot_chart.dart deleted file mode 100644 index 0347aa66c..000000000 --- a/web/charts/flutter/lib/src/scatter_plot_chart.dart +++ /dev/null @@ -1,82 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import 'package:charts_common/common.dart' as common - show - AxisSpec, - NumericAxisSpec, - PointRendererConfig, - RTLSpec, - ScatterPlotChart, - SeriesRendererConfig, - Series; -import 'behaviors/chart_behavior.dart' show ChartBehavior; -import 'base_chart.dart' show LayoutConfig; -import 'base_chart_state.dart' show BaseChartState; -import 'cartesian_chart.dart' show CartesianChart; -import 'selection_model_config.dart' show SelectionModelConfig; -import 'user_managed_state.dart' show UserManagedState; - -class ScatterPlotChart extends CartesianChart { - ScatterPlotChart( - List seriesList, { - bool animate, - Duration animationDuration, - common.AxisSpec domainAxis, - common.AxisSpec primaryMeasureAxis, - common.AxisSpec secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes, - common.PointRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - common.RTLSpec rtlSpec, - LayoutConfig layoutConfig, - bool defaultInteractions: true, - bool flipVerticalAxis, - UserManagedState userManagedState, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - domainAxis: domainAxis, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes, - defaultRenderer: defaultRenderer, - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - rtlSpec: rtlSpec, - layoutConfig: layoutConfig, - defaultInteractions: defaultInteractions, - flipVerticalAxis: flipVerticalAxis, - userManagedState: userManagedState, - ); - - @override - common.ScatterPlotChart createCommonChart(BaseChartState chartState) { - // Optionally create primary and secondary measure axes if the chart was - // configured with them. If no axes were configured, then the chart will - // use its default types (usually a numeric axis). - return new common.ScatterPlotChart( - layoutConfig: layoutConfig?.commonLayoutConfig, - primaryMeasureAxis: primaryMeasureAxis?.createAxis(), - secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), - disjointMeasureAxes: createDisjointMeasureAxes()); - } -} diff --git a/web/charts/flutter/lib/src/selection_model_config.dart b/web/charts/flutter/lib/src/selection_model_config.dart deleted file mode 100644 index 5ecac3901..000000000 --- a/web/charts/flutter/lib/src/selection_model_config.dart +++ /dev/null @@ -1,34 +0,0 @@ -// 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 'package:meta/meta.dart' show immutable; - -import 'package:charts_common/common.dart' as common; - -@immutable -class SelectionModelConfig { - final common.SelectionModelType type; - - /// Listens for change in selection. - final common.SelectionModelListener changedListener; - - /// Listens anytime update selection is called. - final common.SelectionModelListener updatedListener; - - SelectionModelConfig( - {this.type = common.SelectionModelType.info, - this.changedListener, - this.updatedListener}); -} diff --git a/web/charts/flutter/lib/src/symbol_renderer.dart b/web/charts/flutter/lib/src/symbol_renderer.dart deleted file mode 100644 index 036749299..000000000 --- a/web/charts/flutter/lib/src/symbol_renderer.dart +++ /dev/null @@ -1,104 +0,0 @@ -// 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:charts_common/common.dart' as common - show ChartCanvas, Color, SymbolRenderer; -import 'package:flutter_web/widgets.dart'; -import 'chart_canvas.dart' show ChartCanvas; -import 'graphics_factory.dart' show GraphicsFactory; - -/// Flutter widget responsible for painting a common SymbolRenderer from the -/// chart. -/// -/// If you want to customize the symbol, then use [CustomSymbolRenderer]. -class SymbolRendererCanvas implements SymbolRendererBuilder { - final common.SymbolRenderer commonSymbolRenderer; - - SymbolRendererCanvas(this.commonSymbolRenderer); - - @override - Widget build(BuildContext context, - {Color color, Size size, bool enabled = true}) { - if (!enabled) { - color = color.withOpacity(0.26); - } - - return new SizedBox.fromSize( - size: size, - child: new CustomPaint( - painter: - new _SymbolCustomPaint(context, commonSymbolRenderer, color))); - } -} - -/// Convenience class allowing you to pass your Widget builder through the -/// common chart so that it is created for you by the Legend. -/// -/// This allows a custom SymbolRenderer in Flutter without having to create -/// a completely custom legend. -abstract class CustomSymbolRenderer extends common.SymbolRenderer - implements SymbolRendererBuilder { - /// Must override this method to build the custom Widget with the given color - /// as - @override - Widget build(BuildContext context, {Color color, Size size, bool enabled}); - - @override - void paint(common.ChartCanvas canvas, Rectangle bounds, - {List dashPattern, - common.Color fillColor, - common.Color strokeColor, - double strokeWidthPx}) { - // Intentionally ignored (never called). - } - - @override - bool shouldRepaint(common.SymbolRenderer oldRenderer) { - return false; // Repainting is handled directly in Flutter. - } -} - -/// Common interface for [CustomSymbolRenderer] & [SymbolRendererCanvas] for -/// convenience for [LegendEntryLayout]. -abstract class SymbolRendererBuilder { - Widget build(BuildContext context, {Color color, Size size, bool enabled}); -} - -/// The Widget which fulfills the guts of [SymbolRendererCanvas] actually -/// painting the symbol to a canvas using [CustomPainter]. -class _SymbolCustomPaint extends CustomPainter { - final BuildContext context; - final common.SymbolRenderer symbolRenderer; - final Color color; - - _SymbolCustomPaint(this.context, this.symbolRenderer, this.color); - - @override - void paint(Canvas canvas, Size size) { - final bounds = - new Rectangle(0, 0, size.width.toInt(), size.height.toInt()); - final commonColor = new common.Color( - r: color.red, g: color.green, b: color.blue, a: color.alpha); - symbolRenderer.paint( - new ChartCanvas(canvas, GraphicsFactory(context)), bounds, - fillColor: commonColor, strokeColor: commonColor); - } - - @override - bool shouldRepaint(_SymbolCustomPaint oldDelegate) { - return symbolRenderer.shouldRepaint(oldDelegate.symbolRenderer); - } -} diff --git a/web/charts/flutter/lib/src/text_element.dart b/web/charts/flutter/lib/src/text_element.dart deleted file mode 100644 index 65b49171a..000000000 --- a/web/charts/flutter/lib/src/text_element.dart +++ /dev/null @@ -1,183 +0,0 @@ -// 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 'package:flutter_web_ui/ui.dart' show TextAlign, TextDirection; -import 'package:charts_common/common.dart' as common - show - MaxWidthStrategy, - TextElement, - TextDirection, - TextMeasurement, - TextStyle; -import 'package:flutter_web/rendering.dart' - show Color, TextBaseline, TextPainter, TextSpan, TextStyle; - -/// Flutter implementation for text measurement and painter. -class TextElement implements common.TextElement { - static const ellipsis = '\u{2026}'; - - @override - final String text; - - final double textScaleFactor; - - var _painterReady = false; - common.TextStyle _textStyle; - common.TextDirection _textDirection = common.TextDirection.ltr; - - int _maxWidth; - common.MaxWidthStrategy _maxWidthStrategy; - - TextPainter _textPainter; - - common.TextMeasurement _measurement; - - double _opacity; - - TextElement(this.text, {common.TextStyle style, this.textScaleFactor}) - : _textStyle = style; - - @override - common.TextStyle get textStyle => _textStyle; - - @override - set textStyle(common.TextStyle value) { - if (_textStyle == value) { - return; - } - _textStyle = value; - _painterReady = false; - } - - @override - set textDirection(common.TextDirection direction) { - if (_textDirection == direction) { - return; - } - _textDirection = direction; - _painterReady = false; - } - - @override - common.TextDirection get textDirection => _textDirection; - - @override - int get maxWidth => _maxWidth; - - @override - set maxWidth(int value) { - if (_maxWidth == value) { - return; - } - _maxWidth = value; - _painterReady = false; - } - - @override - common.MaxWidthStrategy get maxWidthStrategy => _maxWidthStrategy; - - @override - set maxWidthStrategy(common.MaxWidthStrategy maxWidthStrategy) { - if (_maxWidthStrategy == maxWidthStrategy) { - return; - } - _maxWidthStrategy = maxWidthStrategy; - _painterReady = false; - } - - @override - set opacity(double opacity) { - if (opacity != _opacity) { - _painterReady = false; - _opacity = opacity; - } - } - - @override - common.TextMeasurement get measurement { - if (!_painterReady) { - _refreshPainter(); - } - - return _measurement; - } - - /// The estimated distance between where we asked to draw the text (top, left) - /// and where it visually started (top + verticalFontShift, left). - /// - /// 10% of reported font height seems to be about right. - int get verticalFontShift { - if (!_painterReady) { - _refreshPainter(); - } - - return (_textPainter.height * 0.1).ceil(); - } - - TextPainter get textPainter { - if (!_painterReady) { - _refreshPainter(); - } - return _textPainter; - } - - /// Create text painter and measure based on current settings - void _refreshPainter() { - _opacity ??= 1.0; - var color = new Color.fromARGB( - (textStyle.color.a * _opacity).round(), - textStyle.color.r, - textStyle.color.g, - textStyle.color.b, - ); - - _textPainter = new TextPainter( - text: new TextSpan( - text: text, - style: new TextStyle( - color: color, - fontSize: textStyle.fontSize.toDouble(), - fontFamily: textStyle.fontFamily))) - ..textDirection = TextDirection.ltr - // TODO Flip once textAlign works - ..textAlign = TextAlign.left - // ..textAlign = _textDirection == common.TextDirection.rtl ? - // TextAlign.right : TextAlign.left - ..ellipsis = maxWidthStrategy == common.MaxWidthStrategy.ellipsize - ? ellipsis - : null; - - if (textScaleFactor != null) { - _textPainter.textScaleFactor = textScaleFactor; - } - - _textPainter.layout(maxWidth: maxWidth?.toDouble() ?? double.infinity); - - final baseline = - _textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic); - - // Estimating the actual draw height to 70% of measures size. - // - // The font reports a size larger than the drawn size, which makes it - // difficult to shift the text around to get it to visually line up - // vertically with other components. - _measurement = new common.TextMeasurement( - horizontalSliceWidth: _textPainter.width, - verticalSliceWidth: _textPainter.height * 0.70, - baseline: baseline); - - _painterReady = true; - } -} diff --git a/web/charts/flutter/lib/src/text_style.dart b/web/charts/flutter/lib/src/text_style.dart deleted file mode 100644 index 8508a3bd9..000000000 --- a/web/charts/flutter/lib/src/text_style.dart +++ /dev/null @@ -1,33 +0,0 @@ -// 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 'package:flutter_web_ui/ui.dart' show hashValues; -import 'package:charts_common/common.dart' as common show Color, TextStyle; - -class TextStyle implements common.TextStyle { - int fontSize; - String fontFamily; - common.Color color; - - @override - bool operator ==(Object other) => - other is TextStyle && - fontSize == other.fontSize && - fontFamily == other.fontFamily && - color == other.color; - - @override - int get hashCode => hashValues(fontSize, fontFamily, color); -} diff --git a/web/charts/flutter/lib/src/time_series_chart.dart b/web/charts/flutter/lib/src/time_series_chart.dart deleted file mode 100644 index 9ea31db0e..000000000 --- a/web/charts/flutter/lib/src/time_series_chart.dart +++ /dev/null @@ -1,94 +0,0 @@ -// 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:collection' show LinkedHashMap; - -import 'package:charts_common/common.dart' as common - show - AxisSpec, - DateTimeFactory, - NumericAxisSpec, - Series, - SeriesRendererConfig, - TimeSeriesChart; -import 'behaviors/chart_behavior.dart' show ChartBehavior; -import 'behaviors/line_point_highlighter.dart' show LinePointHighlighter; -import 'cartesian_chart.dart' show CartesianChart; -import 'base_chart.dart' show LayoutConfig; -import 'base_chart_state.dart' show BaseChartState; -import 'selection_model_config.dart' show SelectionModelConfig; -import 'user_managed_state.dart' show UserManagedState; - -class TimeSeriesChart extends CartesianChart { - final common.DateTimeFactory dateTimeFactory; - - /// Create a [TimeSeriesChart]. - /// - /// [dateTimeFactory] allows specifying a factory that creates [DateTime] to - /// be used for the time axis. If none specified, local date time is used. - TimeSeriesChart( - List> seriesList, { - bool animate, - Duration animationDuration, - common.AxisSpec domainAxis, - common.AxisSpec primaryMeasureAxis, - common.AxisSpec secondaryMeasureAxis, - LinkedHashMap disjointMeasureAxes, - common.SeriesRendererConfig defaultRenderer, - List> customSeriesRenderers, - List behaviors, - List> selectionModels, - LayoutConfig layoutConfig, - this.dateTimeFactory, - bool defaultInteractions: true, - bool flipVerticalAxis, - UserManagedState userManagedState, - }) : super( - seriesList, - animate: animate, - animationDuration: animationDuration, - domainAxis: domainAxis, - primaryMeasureAxis: primaryMeasureAxis, - secondaryMeasureAxis: secondaryMeasureAxis, - disjointMeasureAxes: disjointMeasureAxes, - defaultRenderer: defaultRenderer, - customSeriesRenderers: customSeriesRenderers, - behaviors: behaviors, - selectionModels: selectionModels, - layoutConfig: layoutConfig, - defaultInteractions: defaultInteractions, - flipVerticalAxis: flipVerticalAxis, - userManagedState: userManagedState, - ); - - @override - common.TimeSeriesChart createCommonChart(BaseChartState chartState) { - // Optionally create primary and secondary measure axes if the chart was - // configured with them. If no axes were configured, then the chart will - // use its default types (usually a numeric axis). - return new common.TimeSeriesChart( - layoutConfig: layoutConfig?.commonLayoutConfig, - primaryMeasureAxis: primaryMeasureAxis?.createAxis(), - secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), - disjointMeasureAxes: createDisjointMeasureAxes()); - } - - @override - void addDefaultInteractions(List behaviors) { - super.addDefaultInteractions(behaviors); - - behaviors.add(new LinePointHighlighter()); - } -} diff --git a/web/charts/flutter/lib/src/user_managed_state.dart b/web/charts/flutter/lib/src/user_managed_state.dart deleted file mode 100644 index 0fc4caab3..000000000 --- a/web/charts/flutter/lib/src/user_managed_state.dart +++ /dev/null @@ -1,78 +0,0 @@ -// 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 'package:charts_common/common.dart' as common - show ImmutableSeries, SelectionModel, SelectionModelType, SeriesDatumConfig; - -/// Contains override settings for the internal chart state. -/// -/// The chart will check non null settings and apply them if they differ from -/// the internal chart state and trigger the appropriate level of redrawing. -class UserManagedState { - /// The expected selection(s) on the chart. - /// - /// If this is set and the model for the selection model type differs from - /// what is in the internal chart state, the selection will be applied and - /// repainting will occur such that behaviors that draw differently on - /// selection change can update, such as the line point highlighter. - /// - /// If more than one type of selection model is used, only the one(s) - /// specified in this list will override what is kept in the internally. - /// - /// To clear the selection, add an empty selection model. - final selectionModels = - >{}; -} - -/// Container for the user managed selection model. -/// -/// This container is needed because the selection model generated by selection -/// events is a [SelectionModel], while any user defined selection has to be -/// specified by passing in [selectedSeriesConfig] and [selectedDataConfig]. -/// The configuration is converted to a selection model after the series data -/// has been processed. -class UserManagedSelectionModel { - final List selectedSeriesConfig; - final List selectedDataConfig; - common.SelectionModel _model; - - /// Creates a [UserManagedSelectionModel] that holds [SelectionModel]. - /// - /// [selectedSeriesConfig] and [selectedDataConfig] is set to null because the - /// [_model] is returned when [getModel] is called. - UserManagedSelectionModel({common.SelectionModel model}) - : _model = model ?? new common.SelectionModel(), - selectedSeriesConfig = null, - selectedDataConfig = null; - - /// Creates a [UserManagedSelectionModel] with configuration that is converted - /// to a [SelectionModel] when [getModel] provides a processed series list. - UserManagedSelectionModel.fromConfig( - {List selectedSeriesConfig, - List selectedDataConfig}) - : this.selectedSeriesConfig = selectedSeriesConfig ?? [], - this.selectedDataConfig = - selectedDataConfig ?? []; - - /// Gets the selection model. If the model is null, create one from - /// configuration and the processed [seriesList] passed in. - common.SelectionModel getModel( - List> seriesList) { - _model ??= new common.SelectionModel.fromConfig( - selectedDataConfig, selectedSeriesConfig, seriesList); - - return _model; - } -} diff --git a/web/charts/flutter/lib/src/util.dart b/web/charts/flutter/lib/src/util.dart deleted file mode 100644 index 1742ee32b..000000000 --- a/web/charts/flutter/lib/src/util.dart +++ /dev/null @@ -1,45 +0,0 @@ -// 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 'package:flutter_web/rendering.dart' - show - RenderBox, - RenderSemanticsGestureHandler, - RenderPointerListener, - RenderCustomMultiChildLayoutBox; -import 'chart_container.dart' show ChartContainerRenderObject; - -/// Get the [ChartContainerRenderObject] from a [RenderBox]. -/// -/// [RenderBox] is expected to be a [RenderSemanticsGestureHandler] with child -/// of [RenderPointerListener] with child of [ChartContainerRenderObject]. -ChartContainerRenderObject getChartContainerRenderObject(RenderBox box) { - assert(box is RenderCustomMultiChildLayoutBox); - final semanticHandler = (box as RenderCustomMultiChildLayoutBox) - .getChildrenAsList() - .firstWhere((child) => child is RenderSemanticsGestureHandler); - - assert(semanticHandler is RenderSemanticsGestureHandler); - final renderPointerListener = - (semanticHandler as RenderSemanticsGestureHandler).child; - - assert(renderPointerListener is RenderPointerListener); - final chartContainerRenderObject = - (renderPointerListener as RenderPointerListener).child; - - assert(chartContainerRenderObject is ChartContainerRenderObject); - - return chartContainerRenderObject as ChartContainerRenderObject; -} diff --git a/web/charts/flutter/lib/src/util/color.dart b/web/charts/flutter/lib/src/util/color.dart deleted file mode 100644 index af5440832..000000000 --- a/web/charts/flutter/lib/src/util/color.dart +++ /dev/null @@ -1,28 +0,0 @@ -// 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 'package:charts_common/common.dart' as common show Color; -import 'package:flutter_web_ui/ui.dart' as ui; - -class ColorUtil { - static ui.Color toDartColor(common.Color color) { - return ui.Color.fromARGB(color.a, color.r, color.g, color.b); - } - - static common.Color fromDartColor(ui.Color color) { - return common.Color( - r: color.red, g: color.green, b: color.blue, a: color.alpha); - } -} diff --git a/web/charts/flutter/lib/src/widget_layout_delegate.dart b/web/charts/flutter/lib/src/widget_layout_delegate.dart deleted file mode 100644 index baecb07ce..000000000 --- a/web/charts/flutter/lib/src/widget_layout_delegate.dart +++ /dev/null @@ -1,219 +0,0 @@ -// 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 'package:flutter_web_ui/ui.dart' show Offset; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; -import 'package:flutter_web/widgets.dart'; -import 'package:charts_common/common.dart' as common - show BehaviorPosition, InsideJustification, OutsideJustification; - -import 'behaviors/chart_behavior.dart' show BuildableBehavior; - -/// Layout delegate that layout chart widget with [BuildableBehavior] widgets. -class WidgetLayoutDelegate extends MultiChildLayoutDelegate { - /// ID of the common chart widget. - final String chartID; - - /// Directionality of the widget. - final isRTL; - - /// ID and [BuildableBehavior] of the widgets for calculating offset. - final Map idAndBehavior; - - WidgetLayoutDelegate(this.chartID, this.idAndBehavior, this.isRTL); - - @override - void performLayout(Size size) { - // TODO: Change this to a layout manager that supports more - // than one buildable behavior that changes chart size. Remove assert when - // this is possible. - assert(idAndBehavior.keys.isEmpty || idAndBehavior.keys.length == 1); - - // Size available for the chart widget. - var availableWidth = size.width; - var availableHeight = size.height; - var chartOffset = Offset.zero; - - // Measure the first buildable behavior. - final behaviorID = - idAndBehavior.keys.isNotEmpty ? idAndBehavior.keys.first : null; - var behaviorSize = Size.zero; - if (behaviorID != null) { - if (hasChild(behaviorID)) { - final leftPosition = - isRTL ? common.BehaviorPosition.end : common.BehaviorPosition.start; - final rightPosition = - isRTL ? common.BehaviorPosition.start : common.BehaviorPosition.end; - final behaviorPosition = idAndBehavior[behaviorID].position; - - behaviorSize = layoutChild(behaviorID, new BoxConstraints.loose(size)); - if (behaviorPosition == common.BehaviorPosition.top) { - chartOffset = new Offset(0.0, behaviorSize.height); - availableHeight -= behaviorSize.height; - } else if (behaviorPosition == common.BehaviorPosition.bottom) { - availableHeight -= behaviorSize.height; - } else if (behaviorPosition == leftPosition) { - chartOffset = new Offset(behaviorSize.width, 0.0); - availableWidth -= behaviorSize.width; - } else if (behaviorPosition == rightPosition) { - availableWidth -= behaviorSize.width; - } - } - } - - // Layout chart. - final chartSize = new Size(availableWidth, availableHeight); - if (hasChild(chartID)) { - layoutChild(chartID, new BoxConstraints.tight(chartSize)); - positionChild(chartID, chartOffset); - } - - // Position buildable behavior. - if (behaviorID != null) { - // TODO: Unable to relayout with new smaller width. - // In the delegate, all children are required to have layout called - // exactly once. - final behaviorOffset = _getBehaviorOffset(idAndBehavior[behaviorID], - behaviorSize: behaviorSize, chartSize: chartSize, isRTL: isRTL); - - positionChild(behaviorID, behaviorOffset); - } - } - - @override - bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) { - // TODO: Deep equality check because the instance will not be - // the same on each build, even if the buildable behavior has not changed. - return idAndBehavior != (oldDelegate as WidgetLayoutDelegate).idAndBehavior; - } - - // Calculate buildable behavior's offset. - Offset _getBehaviorOffset(BuildableBehavior behavior, - {Size behaviorSize, Size chartSize, bool isRTL}) { - Offset behaviorOffset; - - final behaviorPosition = behavior.position; - final outsideJustification = behavior.outsideJustification; - final insideJustification = behavior.insideJustification; - - if (behaviorPosition == common.BehaviorPosition.top || - behaviorPosition == common.BehaviorPosition.bottom) { - final heightOffset = behaviorPosition == common.BehaviorPosition.bottom - ? chartSize.height - : 0.0; - - final horizontalJustification = - getOutsideJustification(outsideJustification, isRTL); - - switch (horizontalJustification) { - case _HorizontalJustification.leftDrawArea: - behaviorOffset = - new Offset(behavior.drawAreaBounds.left.toDouble(), heightOffset); - break; - case _HorizontalJustification.left: - behaviorOffset = new Offset(0.0, heightOffset); - break; - case _HorizontalJustification.rightDrawArea: - behaviorOffset = new Offset( - behavior.drawAreaBounds.right - behaviorSize.width, heightOffset); - break; - case _HorizontalJustification.right: - behaviorOffset = - new Offset(chartSize.width - behaviorSize.width, heightOffset); - break; - } - } else if (behaviorPosition == common.BehaviorPosition.start || - behaviorPosition == common.BehaviorPosition.end) { - final widthOffset = - (isRTL && behaviorPosition == common.BehaviorPosition.start) || - (!isRTL && behaviorPosition == common.BehaviorPosition.end) - ? chartSize.width - : 0.0; - - switch (outsideJustification) { - case common.OutsideJustification.startDrawArea: - case common.OutsideJustification.middleDrawArea: - behaviorOffset = - new Offset(widthOffset, behavior.drawAreaBounds.top.toDouble()); - break; - case common.OutsideJustification.start: - case common.OutsideJustification.middle: - behaviorOffset = new Offset(widthOffset, 0.0); - break; - case common.OutsideJustification.endDrawArea: - behaviorOffset = new Offset(widthOffset, - behavior.drawAreaBounds.bottom - behaviorSize.height); - break; - case common.OutsideJustification.end: - behaviorOffset = - new Offset(widthOffset, chartSize.height - behaviorSize.height); - break; - } - } else if (behaviorPosition == common.BehaviorPosition.inside) { - var rightOffset = new Offset(chartSize.width - behaviorSize.width, 0.0); - - switch (insideJustification) { - case common.InsideJustification.topStart: - behaviorOffset = isRTL ? rightOffset : Offset.zero; - break; - case common.InsideJustification.topEnd: - behaviorOffset = isRTL ? Offset.zero : rightOffset; - break; - } - } - - return behaviorOffset; - } - - _HorizontalJustification getOutsideJustification( - common.OutsideJustification justification, bool isRTL) { - _HorizontalJustification mappedJustification; - - switch (justification) { - case common.OutsideJustification.startDrawArea: - case common.OutsideJustification.middleDrawArea: - mappedJustification = isRTL - ? _HorizontalJustification.rightDrawArea - : _HorizontalJustification.leftDrawArea; - break; - case common.OutsideJustification.start: - case common.OutsideJustification.middle: - mappedJustification = isRTL - ? _HorizontalJustification.right - : _HorizontalJustification.left; - break; - case common.OutsideJustification.endDrawArea: - mappedJustification = isRTL - ? _HorizontalJustification.leftDrawArea - : _HorizontalJustification.rightDrawArea; - break; - case common.OutsideJustification.end: - mappedJustification = isRTL - ? _HorizontalJustification.left - : _HorizontalJustification.right; - break; - } - - return mappedJustification; - } -} - -enum _HorizontalJustification { - leftDrawArea, - left, - rightDrawArea, - right, -} diff --git a/web/charts/flutter/pubspec.lock b/web/charts/flutter/pubspec.lock deleted file mode 100644 index bd37c86b0..000000000 --- a/web/charts/flutter/pubspec.lock +++ /dev/null @@ -1,417 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - charts_common: - dependency: "direct main" - description: - path: "../common" - relative: true - source: path - version: "0.6.0" - collection: - dependency: "direct main" - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - flutter_web: - dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - flutter_web_test: - dependency: "direct dev" - description: - path: "packages/flutter_web_test" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - flutter_web_ui: - dependency: "direct overridden" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: "direct main" - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: "direct main" - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" - meta: - dependency: "direct main" - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - mockito: - dependency: "direct dev" - description: - name: mockito - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.0" - multi_server_socket: - dependency: transitive - description: - name: multi_server_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.6" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.8" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - test: - dependency: "direct dev" - description: - name: test - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.6" - test_core: - dependency: transitive - description: - name: test_core - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.7" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - vm_service_lib: - dependency: transitive - description: - name: vm_service_lib - url: "https://pub.dartlang.org" - source: hosted - version: "3.22.2+1" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" -sdks: - dart: ">=2.3.0-dev <3.0.0" diff --git a/web/charts/flutter/pubspec.yaml b/web/charts/flutter/pubspec.yaml deleted file mode 100644 index 0c10b957c..000000000 --- a/web/charts/flutter/pubspec.yaml +++ /dev/null @@ -1,45 +0,0 @@ -name: charts_flutter -version: 0.6.0 -description: Material Design charting library for flutter. -author: Charts Team -homepage: https://github.com/google/charts - -environment: - sdk: '>=2.0.0 <3.0.0' - -dependencies: - # Pointing this to a local path allows for pointing to the latest code - # in Github for open source development. - # - # The pub version of charts_flutter will point to the pub version of charts_common. - # The latest pub version is commented out and shown below as an example. - # charts_common: 0.6.0 - charts_common: - path: ../common/ - collection: ^1.14.5 - flutter_web: any - intl: ^0.15.2 - logging: any - meta: ^1.1.1 - - -dev_dependencies: - mockito: - flutter_web_test: any - test: ^1.3.0 - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_test: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_test - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui diff --git a/web/charts/flutter/test/behaviors/legend/legend_layout_test.dart b/web/charts/flutter/test/behaviors/legend/legend_layout_test.dart deleted file mode 100644 index 8fbc80522..000000000 --- a/web/charts/flutter/test/behaviors/legend/legend_layout_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -// 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 'package:flutter_web/material.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'package:charts_flutter/src/behaviors/legend/legend_layout.dart'; - -class MockContext extends Mock implements BuildContext {} - -void main() { - BuildContext context; - - setUp(() { - context = new MockContext(); - }); - - group('TabularLegendLayoutBuilder', () { - test('builds horizontally', () { - final builder = new TabularLegendLayout.horizontalFirst(); - final widgets = [new Text('1'), new Text('2'), new Text('3')]; - - final Table layout = builder.build(context, widgets); - expect(layout.children.length, 1); - expect(layout.children.first.children.length, 3); - }); - - test('does not build extra columns if max columns exceed widget count', () { - final builder = - new TabularLegendLayout.horizontalFirst(desiredMaxColumns: 10); - final widgets = [new Text('1'), new Text('2'), new Text('3')]; - - final Table layout = builder.build(context, widgets); - expect(layout.children.length, 1); - expect(layout.children.first.children.length, 3); - }); - - test('builds horizontally until max column exceeded', () { - final builder = - new TabularLegendLayout.horizontalFirst(desiredMaxColumns: 2); - - final widgets = new List.generate( - 7, (int index) => new Text(index.toString())); - - final Table layout = builder.build(context, widgets); - expect(layout.children.length, 4); - - expect(layout.children[0].children[0], equals(widgets[0])); - expect(layout.children[0].children[1], equals(widgets[1])); - - expect(layout.children[1].children[0], equals(widgets[2])); - expect(layout.children[1].children[1], equals(widgets[3])); - - expect(layout.children[2].children[0], equals(widgets[4])); - expect(layout.children[2].children[1], equals(widgets[5])); - - expect(layout.children[3].children[0], equals(widgets[6])); - }); - - test('builds vertically', () { - final builder = new TabularLegendLayout.verticalFirst(); - final widgets = [new Text('1'), new Text('2'), new Text('3')]; - - final Table layout = builder.build(context, widgets); - expect(layout.children.length, 3); - expect(layout.children[0].children.length, 1); - expect(layout.children[1].children.length, 1); - expect(layout.children[2].children.length, 1); - }); - - test('does not build extra rows if max rows exceed widget count', () { - final builder = new TabularLegendLayout.verticalFirst(desiredMaxRows: 10); - final widgets = [new Text('1'), new Text('2'), new Text('3')]; - - final Table layout = builder.build(context, widgets); - expect(layout.children.length, 3); - expect(layout.children[0].children.length, 1); - expect(layout.children[1].children.length, 1); - expect(layout.children[2].children.length, 1); - }); - - test('builds vertically until max column exceeded', () { - final builder = new TabularLegendLayout.verticalFirst(desiredMaxRows: 2); - - final widgets = new List.generate( - 7, (int index) => new Text(index.toString())); - - final Table layout = builder.build(context, widgets); - expect(layout.children.length, 2); - - expect(layout.children[0].children[0], equals(widgets[0])); - expect(layout.children[1].children[0], equals(widgets[1])); - - expect(layout.children[0].children[1], equals(widgets[2])); - expect(layout.children[1].children[1], equals(widgets[3])); - - expect(layout.children[0].children[2], equals(widgets[4])); - expect(layout.children[1].children[2], equals(widgets[5])); - - expect(layout.children[0].children[3], equals(widgets[6])); - }); - }); -} diff --git a/web/charts/flutter/test/text_element_test.dart b/web/charts/flutter/test/text_element_test.dart deleted file mode 100644 index 4cdff2c75..000000000 --- a/web/charts/flutter/test/text_element_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -// 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 'package:flutter_web/material.dart' show BuildContext; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; -import 'package:charts_flutter/src/graphics_factory.dart'; -import 'package:charts_flutter/src/text_element.dart'; - -class MockContext extends Mock implements BuildContext {} - -class MockGraphicsFactoryHelper extends Mock implements GraphicsFactoryHelper {} - -void main() { - test('Text element gets assigned scale factor', () { - final helper = new MockGraphicsFactoryHelper(); - when(helper.getTextScaleFactorOf(any)).thenReturn(3.0); - final graphicsFactory = - new GraphicsFactory(new MockContext(), helper: helper); - - final textElement = - graphicsFactory.createTextElement('test') as TextElement; - - expect(textElement.text, equals('test')); - expect(textElement.textScaleFactor, equals(3.0)); - }); -} diff --git a/web/charts/flutter/test/user_managed_state_test.dart b/web/charts/flutter/test/user_managed_state_test.dart deleted file mode 100644 index a2eb2c737..000000000 --- a/web/charts/flutter/test/user_managed_state_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -// 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 'package:flutter_web/widgets.dart'; -import 'package:flutter_web_test/flutter_web_test.dart'; - -import 'package:charts_flutter/flutter.dart' as charts; - -void main() { - testWidgets('selection can be set programmatically', - (WidgetTester tester) async { - final onTapSelection = - new charts.UserManagedSelectionModel.fromConfig( - selectedDataConfig: [ - new charts.SeriesDatumConfig('Sales', '2016') - ]); - - charts.SelectionModel currentSelectionModel; - - void selectionChangedListener(charts.SelectionModel model) { - currentSelectionModel = model; - } - - final testChart = new TestChart(selectionChangedListener, onTapSelection); - - await tester.pumpWidget(testChart); - - expect(currentSelectionModel, isNull); - - await tester.tap(find.byType(charts.BarChart)); - - await tester.pump(); - - expect(currentSelectionModel.selectedDatum, hasLength(1)); - final selectedDatum = - currentSelectionModel.selectedDatum.first.datum as OrdinalSales; - expect(selectedDatum.year, equals('2016')); - expect(selectedDatum.sales, equals(100)); - expect(currentSelectionModel.selectedSeries, hasLength(1)); - expect(currentSelectionModel.selectedSeries.first.id, equals('Sales')); - }); -} - -class TestChart extends StatefulWidget { - final charts.SelectionModelListener selectionChangedListener; - final charts.UserManagedSelectionModel onTapSelection; - - TestChart(this.selectionChangedListener, this.onTapSelection); - - @override - TestChartState createState() { - return new TestChartState(selectionChangedListener, onTapSelection); - } -} - -class TestChartState extends State { - final charts.SelectionModelListener selectionChangedListener; - final charts.UserManagedSelectionModel onTapSelection; - - final seriesList = _createSampleData(); - final myState = new charts.UserManagedState(); - - TestChartState(this.selectionChangedListener, this.onTapSelection); - - @override - Widget build(BuildContext context) { - final chart = new charts.BarChart( - seriesList, - userManagedState: myState, - selectionModels: [ - new charts.SelectionModelConfig( - type: charts.SelectionModelType.info, - changedListener: widget.selectionChangedListener) - ], - // Disable animation and gesture for testing. - animate: false, //widget.animate, - defaultInteractions: false, - ); - - return new GestureDetector(child: chart, onTap: handleOnTap); - } - - void handleOnTap() { - setState(() { - myState.selectionModels[charts.SelectionModelType.info] = onTapSelection; - }); - } -} - -/// Create one series with sample hard coded data. -List> _createSampleData() { - final data = [ - new OrdinalSales('2014', 5), - new OrdinalSales('2015', 25), - new OrdinalSales('2016', 100), - new OrdinalSales('2017', 75), - ]; - - return [ - new charts.Series( - id: 'Sales', - colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, - domainFn: (OrdinalSales sales, _) => sales.year, - measureFn: (OrdinalSales sales, _) => sales.sales, - data: data, - ) - ]; -} - -/// Sample ordinal data type. -class OrdinalSales { - final String year; - final int sales; - - OrdinalSales(this.year, this.sales); -} diff --git a/web/charts/flutter/test/widget_layout_delegate_test.dart b/web/charts/flutter/test/widget_layout_delegate_test.dart deleted file mode 100644 index 149da451b..000000000 --- a/web/charts/flutter/test/widget_layout_delegate_test.dart +++ /dev/null @@ -1,548 +0,0 @@ -// 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:flutter_web/material.dart'; -import 'package:mockito/mockito.dart'; -import 'package:flutter_web_test/flutter_web_test.dart'; - -import 'package:charts_common/common.dart' as common - show BehaviorPosition, InsideJustification, OutsideJustification; -import 'package:charts_flutter/src/behaviors/chart_behavior.dart'; -import 'package:charts_flutter/src/widget_layout_delegate.dart'; - -const chartContainerLayoutID = 'chartContainer'; - -class MockBuildableBehavior extends Mock implements BuildableBehavior {} - -void main() { - group('widget layout test', () { - final chartKey = new UniqueKey(); - final behaviorKey = new UniqueKey(); - final behaviorID = 'behavior'; - final totalSize = const Size(200.0, 100.0); - final behaviorSize = const Size(50.0, 50.0); - - /// Creates widget for testing. - Widget createWidget( - Size chartSize, Size behaviorSize, common.BehaviorPosition position, - {common.OutsideJustification outsideJustification, - common.InsideJustification insideJustification, - Rectangle drawAreaBounds, - bool isRTL: false}) { - // Create a mock buildable behavior that returns information about the - // position and justification desired. - final behavior = new MockBuildableBehavior(); - when(behavior.position).thenReturn(position); - when(behavior.outsideJustification).thenReturn(outsideJustification); - when(behavior.insideJustification).thenReturn(insideJustification); - when(behavior.drawAreaBounds).thenReturn(drawAreaBounds); - - // The 'chart' widget that expands to the full size allowed to test that - // the behavior widget's size affects the size given to the chart. - final chart = new LayoutId( - key: chartKey, id: chartContainerLayoutID, child: new Container()); - - // A behavior widget - final behaviorWidget = new LayoutId( - key: behaviorKey, - id: behaviorID, - child: new SizedBox.fromSize(size: behaviorSize)); - - // Create a the widget that uses the layout delegate that is being tested. - final layout = new CustomMultiChildLayout( - delegate: new WidgetLayoutDelegate( - chartContainerLayoutID, {behaviorID: behavior}, isRTL), - children: [chart, behaviorWidget]); - - final container = new Align( - alignment: Alignment.topLeft, - child: new Container( - width: chartSize.width, height: chartSize.height, child: layout)); - - return container; - } - - // Verifies the expected results. - void verifyResults(WidgetTester tester, Size expectedChartSize, - Offset expectedChartOffset, Offset expectedBehaviorOffset) { - final RenderBox chartBox = tester.firstRenderObject(find.byKey(chartKey)); - expect(chartBox.size, equals(expectedChartSize)); - - final chartOffset = chartBox.localToGlobal(Offset.zero); - expect(chartOffset, equals(expectedChartOffset)); - - final RenderBox behaviorBox = - tester.firstRenderObject(find.byKey(behaviorKey)); - final behaviorOffset = behaviorBox.localToGlobal(Offset.zero); - expect(behaviorOffset, equals(expectedBehaviorOffset)); - } - - testWidgets('Position top - start draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.top; - final outsideJustification = common.OutsideJustification.startDrawArea; - final drawAreaBounds = const Rectangle(25, 50, 150, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the top, so the chart is offset by 50. - final expectedChartOffset = const Offset(0.0, 50.0); - // Behavior is aligned to draw area - final expectedBehaviorOffset = const Offset(25.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position bottom - end draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.bottom; - final outsideJustification = common.OutsideJustification.endDrawArea; - final drawAreaBounds = const Rectangle(25, 0, 125, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the bottom, so the chart is offset by 0. - final expectedChartOffset = const Offset(0.0, 0.0); - // Behavior is aligned to draw area and offset to the bottom. - final expectedBehaviorOffset = const Offset(100.0, 50.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position start - start draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.start; - final outsideJustification = common.OutsideJustification.startDrawArea; - final drawAreaBounds = const Rectangle(75, 25, 150, 50); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Behavior is positioned at the start (left) since this is NOT a RTL - // so the chart is offset to the right by the behavior width of 50. - final expectedChartOffset = const Offset(50.0, 0.0); - // Behavior is aligned to draw area. - final expectedBehaviorOffset = const Offset(0.0, 25.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position end - end draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.end; - final outsideJustification = common.OutsideJustification.endDrawArea; - final drawAreaBounds = const Rectangle(25, 25, 150, 50); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Behavior is positioned at the right (left) since this is NOT a RTL - // so no offset for the chart. - final expectedChartOffset = const Offset(0.0, 0.0); - // Behavior is aligned to draw area and offset to the right of the - // chart. - final expectedBehaviorOffset = const Offset(150.0, 25.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position top - start justified', (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.top; - final outsideJustification = common.OutsideJustification.start; - final drawAreaBounds = const Rectangle(25, 50, 150, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the top, so the chart is offset by 50. - final expectedChartOffset = const Offset(0.0, 50.0); - // Behavior is aligned to the start, so no offset - final expectedBehaviorOffset = const Offset(0.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position top - end justified', (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.top; - final outsideJustification = common.OutsideJustification.end; - final drawAreaBounds = const Rectangle(25, 50, 150, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the top, so the chart is offset by 50. - final expectedChartOffset = const Offset(0.0, 50.0); - // Behavior is aligned to the end, so it is offset by total size minus - // the behavior size. - final expectedBehaviorOffset = const Offset(150.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position start - start justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.start; - final outsideJustification = common.OutsideJustification.start; - final drawAreaBounds = const Rectangle(75, 25, 150, 50); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Behavior is positioned at the start (left) since this is NOT a RTL - // so the chart is offset to the right by the behavior width of 50. - final expectedChartOffset = const Offset(50.0, 0.0); - // No offset because it is start justified. - final expectedBehaviorOffset = const Offset(0.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position start - end justified', (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.start; - final outsideJustification = common.OutsideJustification.end; - final drawAreaBounds = const Rectangle(75, 25, 150, 50); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Behavior is positioned at the start (left) since this is NOT a RTL - // so the chart is offset to the right by the behavior width of 50. - final expectedChartOffset = const Offset(50.0, 0.0); - // End justified, total height minus behavior height - final expectedBehaviorOffset = const Offset(0.0, 50.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position inside - top start justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.inside; - final insideJustification = common.InsideJustification.topStart; - final drawAreaBounds = const Rectangle(25, 25, 175, 75); - - // Behavior is layered on top, chart uses the full size. - final expectedChartSize = const Size(200.0, 100.0); - // No offset since chart takes up full size. - final expectedChartOffset = const Offset(0.0, 0.0); - // Top start justified, no offset - final expectedBehaviorOffset = const Offset(0.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - insideJustification: insideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('Position inside - top end justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.inside; - final insideJustification = common.InsideJustification.topEnd; - final drawAreaBounds = const Rectangle(25, 25, 175, 75); - - // Behavior is layered on top, chart uses the full size. - final expectedChartSize = const Size(200.0, 100.0); - // No offset since chart takes up full size. - final expectedChartOffset = const Offset(0.0, 0.0); - // Offset to the top end - final expectedBehaviorOffset = const Offset(150.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - insideJustification: insideJustification, - drawAreaBounds: drawAreaBounds)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position top - start draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.top; - final outsideJustification = common.OutsideJustification.startDrawArea; - final drawAreaBounds = const Rectangle(0, 50, 175, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the top, so the chart is offset by 50. - final expectedChartOffset = const Offset(0.0, 50.0); - // Behavior is aligned to start draw area, which is to the left in RTL - final expectedBehaviorOffset = const Offset(125.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position bottom - end draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.bottom; - final outsideJustification = common.OutsideJustification.endDrawArea; - final drawAreaBounds = const Rectangle(0, 0, 175, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the bottom, so the chart is offset by 0. - final expectedChartOffset = const Offset(0.0, 0.0); - // Behavior is aligned to end draw area (left) and offset to the bottom. - final expectedBehaviorOffset = const Offset(0.0, 50.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position start - start draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.start; - final outsideJustification = common.OutsideJustification.startDrawArea; - final drawAreaBounds = const Rectangle(0, 25, 125, 75); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Chart is on the left, so no offset. - final expectedChartOffset = const Offset(0.0, 0.0); - // Behavior is positioned at the start (right) and start draw area. - final expectedBehaviorOffset = const Offset(150.0, 25.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position end - end draw area justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.end; - final outsideJustification = common.OutsideJustification.endDrawArea; - final drawAreaBounds = const Rectangle(75, 25, 125, 75); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Chart is to the left of the behavior because of RTL. - final expectedChartOffset = const Offset(50.0, 0.0); - // Behavior is aligned to end draw area. - final expectedBehaviorOffset = const Offset(0.0, 50.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position top - start justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.top; - final outsideJustification = common.OutsideJustification.start; - final drawAreaBounds = const Rectangle(25, 50, 150, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the top, so the chart is offset by 50. - final expectedChartOffset = const Offset(0.0, 50.0); - // Behavior is aligned to the end, offset by behavior size. - final expectedBehaviorOffset = const Offset(150.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position top - end justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.top; - final outsideJustification = common.OutsideJustification.end; - final drawAreaBounds = const Rectangle(25, 50, 150, 50); - - // Behavior takes up 50 height, so 50 height remains for the chart. - final expectedChartSize = const Size(200.0, 50.0); - // Behavior is positioned on the top, so the chart is offset by 50. - final expectedChartOffset = const Offset(0.0, 50.0); - // Behavior is aligned to the end, no offset. - final expectedBehaviorOffset = const Offset(0.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position start - start justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.start; - final outsideJustification = common.OutsideJustification.start; - final drawAreaBounds = const Rectangle(75, 25, 150, 50); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Behavior is positioned at the right since this is RTL so the chart is - // has no offset. - final expectedChartOffset = const Offset(0.0, 0.0); - // No offset because it is start justified. - final expectedBehaviorOffset = const Offset(150.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position start - end justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.start; - final outsideJustification = common.OutsideJustification.end; - final drawAreaBounds = const Rectangle(75, 25, 150, 50); - - // Behavior takes up 50 width, so 150 width remains for the chart. - final expectedChartSize = const Size(150.0, 100.0); - // Behavior is positioned at the right since this is RTL so the chart is - // has no offset. - final expectedChartOffset = const Offset(0.0, 0.0); - // End justified, total height minus behavior height - final expectedBehaviorOffset = const Offset(150.0, 50.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - outsideJustification: outsideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position inside - top start justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.inside; - final insideJustification = common.InsideJustification.topStart; - final drawAreaBounds = const Rectangle(25, 25, 175, 75); - - // Behavior is layered on top, chart uses the full size. - final expectedChartSize = const Size(200.0, 100.0); - // No offset since chart takes up full size. - final expectedChartOffset = const Offset(0.0, 0.0); - // Offset to the right - final expectedBehaviorOffset = const Offset(150.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - insideJustification: insideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - - testWidgets('RTL - Position inside - top end justified', - (WidgetTester tester) async { - final behaviorPosition = common.BehaviorPosition.inside; - final insideJustification = common.InsideJustification.topEnd; - final drawAreaBounds = const Rectangle(25, 25, 175, 75); - - // Behavior is layered on top, chart uses the full size. - final expectedChartSize = const Size(200.0, 100.0); - // No offset since chart takes up full size. - final expectedChartOffset = const Offset(0.0, 0.0); - // No offset, since end is to the left. - final expectedBehaviorOffset = const Offset(0.0, 0.0); - - await tester.pumpWidget(createWidget( - totalSize, behaviorSize, behaviorPosition, - insideJustification: insideJustification, - drawAreaBounds: drawAreaBounds, - isRTL: true)); - - verifyResults(tester, expectedChartSize, expectedChartOffset, - expectedBehaviorOffset); - }); - }); -} diff --git a/web/charts/example/lib/a11y/a11y_gallery.dart b/web/charts/lib/a11y/a11y_gallery.dart similarity index 96% rename from web/charts/example/lib/a11y/a11y_gallery.dart rename to web/charts/lib/a11y/a11y_gallery.dart index d5e785528..c5d4b5f71 100644 --- a/web/charts/example/lib/a11y/a11y_gallery.dart +++ b/web/charts/lib/a11y/a11y_gallery.dart @@ -12,7 +12,7 @@ // 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 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'domain_a11y_explore_bar_chart.dart'; diff --git a/web/charts/example/lib/a11y/domain_a11y_explore_bar_chart.dart b/web/charts/lib/a11y/domain_a11y_explore_bar_chart.dart similarity index 99% rename from web/charts/example/lib/a11y/domain_a11y_explore_bar_chart.dart rename to web/charts/lib/a11y/domain_a11y_explore_bar_chart.dart index 1bcac0ede..e4c03ca79 100644 --- a/web/charts/example/lib/a11y/domain_a11y_explore_bar_chart.dart +++ b/web/charts/lib/a11y/domain_a11y_explore_bar_chart.dart @@ -35,7 +35,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class DomainA11yExploreBarChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/app_config.dart b/web/charts/lib/app_config.dart similarity index 96% rename from web/charts/example/lib/app_config.dart rename to web/charts/lib/app_config.dart index 3e937eef2..563c5c7c3 100644 --- a/web/charts/example/lib/app_config.dart +++ b/web/charts/lib/app_config.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; /// A particular configuration of the app. class AppConfig { diff --git a/web/charts/example/lib/axes/axes_gallery.dart b/web/charts/lib/axes/axes_gallery.dart similarity index 99% rename from web/charts/example/lib/axes/axes_gallery.dart rename to web/charts/lib/axes/axes_gallery.dart index 69fafd962..f192e2c7e 100644 --- a/web/charts/example/lib/axes/axes_gallery.dart +++ b/web/charts/lib/axes/axes_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'bar_secondary_axis.dart'; import 'bar_secondary_axis_only.dart'; diff --git a/web/charts/example/lib/axes/bar_secondary_axis.dart b/web/charts/lib/axes/bar_secondary_axis.dart similarity index 99% rename from web/charts/example/lib/axes/bar_secondary_axis.dart rename to web/charts/lib/axes/bar_secondary_axis.dart index 517221951..6fe676317 100644 --- a/web/charts/example/lib/axes/bar_secondary_axis.dart +++ b/web/charts/lib/axes/bar_secondary_axis.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of using a primary and secondary axis (left & right respectively) diff --git a/web/charts/example/lib/axes/bar_secondary_axis_only.dart b/web/charts/lib/axes/bar_secondary_axis_only.dart similarity index 98% rename from web/charts/example/lib/axes/bar_secondary_axis_only.dart rename to web/charts/lib/axes/bar_secondary_axis_only.dart index 622408b32..3864028b8 100644 --- a/web/charts/example/lib/axes/bar_secondary_axis_only.dart +++ b/web/charts/lib/axes/bar_secondary_axis_only.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of using only a secondary axis (on the right) for a set of grouped diff --git a/web/charts/example/lib/axes/custom_axis_tick_formatters.dart b/web/charts/lib/axes/custom_axis_tick_formatters.dart similarity index 99% rename from web/charts/example/lib/axes/custom_axis_tick_formatters.dart rename to web/charts/lib/axes/custom_axis_tick_formatters.dart index 476ed2a38..527d8b4a5 100644 --- a/web/charts/example/lib/axes/custom_axis_tick_formatters.dart +++ b/web/charts/lib/axes/custom_axis_tick_formatters.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class CustomAxisTickFormatters extends StatelessWidget { diff --git a/web/charts/example/lib/axes/custom_font_size_and_color.dart b/web/charts/lib/axes/custom_font_size_and_color.dart similarity index 99% rename from web/charts/example/lib/axes/custom_font_size_and_color.dart rename to web/charts/lib/axes/custom_font_size_and_color.dart index d139963d6..3dc8e63ea 100644 --- a/web/charts/example/lib/axes/custom_font_size_and_color.dart +++ b/web/charts/lib/axes/custom_font_size_and_color.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of using a custom primary measure and domain axis replacing the diff --git a/web/charts/example/lib/axes/custom_measure_tick_count.dart b/web/charts/lib/axes/custom_measure_tick_count.dart similarity index 98% rename from web/charts/example/lib/axes/custom_measure_tick_count.dart rename to web/charts/lib/axes/custom_measure_tick_count.dart index 33de075cf..7a0efd36c 100644 --- a/web/charts/example/lib/axes/custom_measure_tick_count.dart +++ b/web/charts/lib/axes/custom_measure_tick_count.dart @@ -22,7 +22,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class CustomMeasureTickCount extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/flipped_vertical_axis.dart b/web/charts/lib/axes/flipped_vertical_axis.dart similarity index 98% rename from web/charts/example/lib/axes/flipped_vertical_axis.dart rename to web/charts/lib/axes/flipped_vertical_axis.dart index f59f1c43a..d3fee92af 100644 --- a/web/charts/example/lib/axes/flipped_vertical_axis.dart +++ b/web/charts/lib/axes/flipped_vertical_axis.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of flipping the vertical measure axis direction so that larger diff --git a/web/charts/example/lib/axes/gridline_dash_pattern.dart b/web/charts/lib/axes/gridline_dash_pattern.dart similarity index 98% rename from web/charts/example/lib/axes/gridline_dash_pattern.dart rename to web/charts/lib/axes/gridline_dash_pattern.dart index 7980c2ac7..087dcc0aa 100644 --- a/web/charts/example/lib/axes/gridline_dash_pattern.dart +++ b/web/charts/lib/axes/gridline_dash_pattern.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class GridlineDashPattern extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/hidden_ticks_and_labels_axis.dart b/web/charts/lib/axes/hidden_ticks_and_labels_axis.dart similarity index 98% rename from web/charts/example/lib/axes/hidden_ticks_and_labels_axis.dart rename to web/charts/lib/axes/hidden_ticks_and_labels_axis.dart index 94f28ebbd..cc1204e49 100644 --- a/web/charts/example/lib/axes/hidden_ticks_and_labels_axis.dart +++ b/web/charts/lib/axes/hidden_ticks_and_labels_axis.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of hiding both axis. diff --git a/web/charts/example/lib/axes/horizontal_bar_secondary_axis.dart b/web/charts/lib/axes/horizontal_bar_secondary_axis.dart similarity index 99% rename from web/charts/example/lib/axes/horizontal_bar_secondary_axis.dart rename to web/charts/lib/axes/horizontal_bar_secondary_axis.dart index 82269865a..51f207ba0 100644 --- a/web/charts/example/lib/axes/horizontal_bar_secondary_axis.dart +++ b/web/charts/lib/axes/horizontal_bar_secondary_axis.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of using a primary and secondary axis (left & right respectively) diff --git a/web/charts/example/lib/axes/integer_only_measure_axis.dart b/web/charts/lib/axes/integer_only_measure_axis.dart similarity index 99% rename from web/charts/example/lib/axes/integer_only_measure_axis.dart rename to web/charts/lib/axes/integer_only_measure_axis.dart index dc5e33a06..fbe445de4 100644 --- a/web/charts/example/lib/axes/integer_only_measure_axis.dart +++ b/web/charts/lib/axes/integer_only_measure_axis.dart @@ -23,7 +23,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class IntegerOnlyMeasureAxis extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/line_disjoint_axis.dart b/web/charts/lib/axes/line_disjoint_axis.dart similarity index 99% rename from web/charts/example/lib/axes/line_disjoint_axis.dart rename to web/charts/lib/axes/line_disjoint_axis.dart index 91059601d..9b77ae73d 100644 --- a/web/charts/example/lib/axes/line_disjoint_axis.dart +++ b/web/charts/lib/axes/line_disjoint_axis.dart @@ -25,7 +25,7 @@ import 'dart:collection' show LinkedHashMap; import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class DisjointMeasureAxisLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/measure_axis_label_alignment.dart b/web/charts/lib/axes/measure_axis_label_alignment.dart similarity index 98% rename from web/charts/example/lib/axes/measure_axis_label_alignment.dart rename to web/charts/lib/axes/measure_axis_label_alignment.dart index b9cfeda18..ffcf590cd 100644 --- a/web/charts/example/lib/axes/measure_axis_label_alignment.dart +++ b/web/charts/lib/axes/measure_axis_label_alignment.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of using a custom primary measure replacing the renderSpec with one diff --git a/web/charts/example/lib/axes/nonzero_bound_measure_axis.dart b/web/charts/lib/axes/nonzero_bound_measure_axis.dart similarity index 98% rename from web/charts/example/lib/axes/nonzero_bound_measure_axis.dart rename to web/charts/lib/axes/nonzero_bound_measure_axis.dart index fa216f061..f1fe5c8aa 100644 --- a/web/charts/example/lib/axes/nonzero_bound_measure_axis.dart +++ b/web/charts/lib/axes/nonzero_bound_measure_axis.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class NonzeroBoundMeasureAxis extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/numeric_initial_viewport.dart b/web/charts/lib/axes/numeric_initial_viewport.dart similarity index 99% rename from web/charts/example/lib/axes/numeric_initial_viewport.dart rename to web/charts/lib/axes/numeric_initial_viewport.dart index 958319a5e..5f9144e81 100644 --- a/web/charts/example/lib/axes/numeric_initial_viewport.dart +++ b/web/charts/lib/axes/numeric_initial_viewport.dart @@ -26,7 +26,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class NumericInitialViewport extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/ordinal_initial_viewport.dart b/web/charts/lib/axes/ordinal_initial_viewport.dart similarity index 99% rename from web/charts/example/lib/axes/ordinal_initial_viewport.dart rename to web/charts/lib/axes/ordinal_initial_viewport.dart index 5cb6f9894..8dde13872 100644 --- a/web/charts/example/lib/axes/ordinal_initial_viewport.dart +++ b/web/charts/lib/axes/ordinal_initial_viewport.dart @@ -26,7 +26,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class OrdinalInitialViewport extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/axes/short_tick_length_axis.dart b/web/charts/lib/axes/short_tick_length_axis.dart similarity index 98% rename from web/charts/example/lib/axes/short_tick_length_axis.dart rename to web/charts/lib/axes/short_tick_length_axis.dart index 7356e95f7..365afa527 100644 --- a/web/charts/example/lib/axes/short_tick_length_axis.dart +++ b/web/charts/lib/axes/short_tick_length_axis.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of using a custom primary measure axis replacing the default diff --git a/web/charts/example/lib/axes/statically_provided_ticks.dart b/web/charts/lib/axes/statically_provided_ticks.dart similarity index 99% rename from web/charts/example/lib/axes/statically_provided_ticks.dart rename to web/charts/lib/axes/statically_provided_ticks.dart index 0b32c3ad6..6f09e98ce 100644 --- a/web/charts/example/lib/axes/statically_provided_ticks.dart +++ b/web/charts/lib/axes/statically_provided_ticks.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of specifying a custom set of ticks to be used on the domain axis. diff --git a/web/charts/example/lib/bar_chart/bar_gallery.dart b/web/charts/lib/bar_chart/bar_gallery.dart similarity index 99% rename from web/charts/example/lib/bar_chart/bar_gallery.dart rename to web/charts/lib/bar_chart/bar_gallery.dart index 0a737d9a6..7b5a59b69 100644 --- a/web/charts/example/lib/bar_chart/bar_gallery.dart +++ b/web/charts/lib/bar_chart/bar_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'custom_rounded_bars.dart'; import 'grouped.dart'; diff --git a/web/charts/example/lib/bar_chart/custom_rounded_bars.dart b/web/charts/lib/bar_chart/custom_rounded_bars.dart similarity index 98% rename from web/charts/example/lib/bar_chart/custom_rounded_bars.dart rename to web/charts/lib/bar_chart/custom_rounded_bars.dart index a8952285e..7de5587f0 100644 --- a/web/charts/example/lib/bar_chart/custom_rounded_bars.dart +++ b/web/charts/lib/bar_chart/custom_rounded_bars.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class CustomRoundedBars extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/bar_chart/grouped.dart b/web/charts/lib/bar_chart/grouped.dart similarity index 99% rename from web/charts/example/lib/bar_chart/grouped.dart rename to web/charts/lib/bar_chart/grouped.dart index c0e687897..f6e874aaf 100644 --- a/web/charts/example/lib/bar_chart/grouped.dart +++ b/web/charts/lib/bar_chart/grouped.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class GroupedBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/grouped_fill_color.dart b/web/charts/lib/bar_chart/grouped_fill_color.dart similarity index 99% rename from web/charts/example/lib/bar_chart/grouped_fill_color.dart rename to web/charts/lib/bar_chart/grouped_fill_color.dart index a84178342..fb34a3f73 100644 --- a/web/charts/example/lib/bar_chart/grouped_fill_color.dart +++ b/web/charts/lib/bar_chart/grouped_fill_color.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of a grouped bar chart with three series, each rendered with diff --git a/web/charts/example/lib/bar_chart/grouped_single_target_line.dart b/web/charts/lib/bar_chart/grouped_single_target_line.dart similarity index 99% rename from web/charts/example/lib/bar_chart/grouped_single_target_line.dart rename to web/charts/lib/bar_chart/grouped_single_target_line.dart index fdb6a63a5..3a18c0e23 100644 --- a/web/charts/example/lib/bar_chart/grouped_single_target_line.dart +++ b/web/charts/lib/bar_chart/grouped_single_target_line.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class GroupedBarSingleTargetLineChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/grouped_stacked.dart b/web/charts/lib/bar_chart/grouped_stacked.dart similarity index 99% rename from web/charts/example/lib/bar_chart/grouped_stacked.dart rename to web/charts/lib/bar_chart/grouped_stacked.dart index ecf178744..be224f2f1 100644 --- a/web/charts/example/lib/bar_chart/grouped_stacked.dart +++ b/web/charts/lib/bar_chart/grouped_stacked.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class GroupedStackedBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/grouped_stacked_weight_pattern.dart b/web/charts/lib/bar_chart/grouped_stacked_weight_pattern.dart similarity index 99% rename from web/charts/example/lib/bar_chart/grouped_stacked_weight_pattern.dart rename to web/charts/lib/bar_chart/grouped_stacked_weight_pattern.dart index 9cf0a8705..5f3ebf51f 100644 --- a/web/charts/example/lib/bar_chart/grouped_stacked_weight_pattern.dart +++ b/web/charts/lib/bar_chart/grouped_stacked_weight_pattern.dart @@ -21,7 +21,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class GroupedStackedWeightPatternBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/grouped_target_line.dart b/web/charts/lib/bar_chart/grouped_target_line.dart similarity index 99% rename from web/charts/example/lib/bar_chart/grouped_target_line.dart rename to web/charts/lib/bar_chart/grouped_target_line.dart index 21c0f8e41..bdbec34ff 100644 --- a/web/charts/example/lib/bar_chart/grouped_target_line.dart +++ b/web/charts/lib/bar_chart/grouped_target_line.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class GroupedBarTargetLineChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/horizontal.dart b/web/charts/lib/bar_chart/horizontal.dart similarity index 98% rename from web/charts/example/lib/bar_chart/horizontal.dart rename to web/charts/lib/bar_chart/horizontal.dart index 5e27583bd..b12bd8159 100644 --- a/web/charts/example/lib/bar_chart/horizontal.dart +++ b/web/charts/lib/bar_chart/horizontal.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class HorizontalBarChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/bar_chart/horizontal_bar_label.dart b/web/charts/lib/bar_chart/horizontal_bar_label.dart similarity index 99% rename from web/charts/example/lib/bar_chart/horizontal_bar_label.dart rename to web/charts/lib/bar_chart/horizontal_bar_label.dart index c59d81bef..98b14ecc0 100644 --- a/web/charts/example/lib/bar_chart/horizontal_bar_label.dart +++ b/web/charts/lib/bar_chart/horizontal_bar_label.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class HorizontalBarLabelChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/bar_chart/horizontal_bar_label_custom.dart b/web/charts/lib/bar_chart/horizontal_bar_label_custom.dart similarity index 99% rename from web/charts/example/lib/bar_chart/horizontal_bar_label_custom.dart rename to web/charts/lib/bar_chart/horizontal_bar_label_custom.dart index c247b51b9..7dd34d524 100644 --- a/web/charts/example/lib/bar_chart/horizontal_bar_label_custom.dart +++ b/web/charts/lib/bar_chart/horizontal_bar_label_custom.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class HorizontalBarLabelCustomChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/bar_chart/horizontal_pattern_forward_hatch.dart b/web/charts/lib/bar_chart/horizontal_pattern_forward_hatch.dart similarity index 99% rename from web/charts/example/lib/bar_chart/horizontal_pattern_forward_hatch.dart rename to web/charts/lib/bar_chart/horizontal_pattern_forward_hatch.dart index edff5618d..e89c510d9 100644 --- a/web/charts/example/lib/bar_chart/horizontal_pattern_forward_hatch.dart +++ b/web/charts/lib/bar_chart/horizontal_pattern_forward_hatch.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Forward hatch pattern horizontal bar chart example. diff --git a/web/charts/example/lib/bar_chart/pattern_forward_hatch.dart b/web/charts/lib/bar_chart/pattern_forward_hatch.dart similarity index 99% rename from web/charts/example/lib/bar_chart/pattern_forward_hatch.dart rename to web/charts/lib/bar_chart/pattern_forward_hatch.dart index d951a712c..14e3bd4e6 100644 --- a/web/charts/example/lib/bar_chart/pattern_forward_hatch.dart +++ b/web/charts/lib/bar_chart/pattern_forward_hatch.dart @@ -20,7 +20,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class PatternForwardHatchBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/simple.dart b/web/charts/lib/bar_chart/simple.dart similarity index 98% rename from web/charts/example/lib/bar_chart/simple.dart rename to web/charts/lib/bar_chart/simple.dart index 51ed6e43b..b1d13bf14 100644 --- a/web/charts/example/lib/bar_chart/simple.dart +++ b/web/charts/lib/bar_chart/simple.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SimpleBarChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/bar_chart/spark_bar.dart b/web/charts/lib/bar_chart/spark_bar.dart similarity index 99% rename from web/charts/example/lib/bar_chart/spark_bar.dart rename to web/charts/lib/bar_chart/spark_bar.dart index fe4749f84..aadb35f1f 100644 --- a/web/charts/example/lib/bar_chart/spark_bar.dart +++ b/web/charts/lib/bar_chart/spark_bar.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of a Spark Bar by hiding both axis, reducing the chart margins. diff --git a/web/charts/example/lib/bar_chart/stacked.dart b/web/charts/lib/bar_chart/stacked.dart similarity index 99% rename from web/charts/example/lib/bar_chart/stacked.dart rename to web/charts/lib/bar_chart/stacked.dart index 37fc75002..b3303fa22 100644 --- a/web/charts/example/lib/bar_chart/stacked.dart +++ b/web/charts/lib/bar_chart/stacked.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class StackedBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/stacked_fill_color.dart b/web/charts/lib/bar_chart/stacked_fill_color.dart similarity index 99% rename from web/charts/example/lib/bar_chart/stacked_fill_color.dart rename to web/charts/lib/bar_chart/stacked_fill_color.dart index b401a8d37..029b09640 100644 --- a/web/charts/example/lib/bar_chart/stacked_fill_color.dart +++ b/web/charts/lib/bar_chart/stacked_fill_color.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example of a stacked bar chart with three series, each rendered with diff --git a/web/charts/example/lib/bar_chart/stacked_horizontal.dart b/web/charts/lib/bar_chart/stacked_horizontal.dart similarity index 99% rename from web/charts/example/lib/bar_chart/stacked_horizontal.dart rename to web/charts/lib/bar_chart/stacked_horizontal.dart index 691313aee..8b7c66935 100644 --- a/web/charts/example/lib/bar_chart/stacked_horizontal.dart +++ b/web/charts/lib/bar_chart/stacked_horizontal.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class StackedHorizontalBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/bar_chart/stacked_target_line.dart b/web/charts/lib/bar_chart/stacked_target_line.dart similarity index 99% rename from web/charts/example/lib/bar_chart/stacked_target_line.dart rename to web/charts/lib/bar_chart/stacked_target_line.dart index be795bff4..4a1252072 100644 --- a/web/charts/example/lib/bar_chart/stacked_target_line.dart +++ b/web/charts/lib/bar_chart/stacked_target_line.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class StackedBarTargetLineChart extends StatelessWidget { diff --git a/web/charts/example/lib/behaviors/behaviors_gallery.dart b/web/charts/lib/behaviors/behaviors_gallery.dart similarity index 99% rename from web/charts/example/lib/behaviors/behaviors_gallery.dart rename to web/charts/lib/behaviors/behaviors_gallery.dart index aa8ea004d..36d25e995 100644 --- a/web/charts/example/lib/behaviors/behaviors_gallery.dart +++ b/web/charts/lib/behaviors/behaviors_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'chart_title.dart'; import 'initial_hint_animation.dart'; diff --git a/web/charts/example/lib/behaviors/chart_title.dart b/web/charts/lib/behaviors/chart_title.dart similarity index 99% rename from web/charts/example/lib/behaviors/chart_title.dart rename to web/charts/lib/behaviors/chart_title.dart index ce0a7c730..14ab8ae43 100644 --- a/web/charts/example/lib/behaviors/chart_title.dart +++ b/web/charts/lib/behaviors/chart_title.dart @@ -16,7 +16,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; /// This is a line chart with a title text in every margin. /// diff --git a/web/charts/example/lib/behaviors/initial_hint_animation.dart b/web/charts/lib/behaviors/initial_hint_animation.dart similarity index 99% rename from web/charts/example/lib/behaviors/initial_hint_animation.dart rename to web/charts/lib/behaviors/initial_hint_animation.dart index cc1e47b99..f6ea5b14a 100644 --- a/web/charts/example/lib/behaviors/initial_hint_animation.dart +++ b/web/charts/lib/behaviors/initial_hint_animation.dart @@ -45,7 +45,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class InitialHintAnimation extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/initial_selection.dart b/web/charts/lib/behaviors/initial_selection.dart similarity index 98% rename from web/charts/example/lib/behaviors/initial_selection.dart rename to web/charts/lib/behaviors/initial_selection.dart index 95573ef92..44d5f5828 100644 --- a/web/charts/example/lib/behaviors/initial_selection.dart +++ b/web/charts/lib/behaviors/initial_selection.dart @@ -28,7 +28,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class InitialSelection extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/percent_of_domain.dart b/web/charts/lib/behaviors/percent_of_domain.dart similarity index 99% rename from web/charts/example/lib/behaviors/percent_of_domain.dart rename to web/charts/lib/behaviors/percent_of_domain.dart index b9ef4dfcb..3f74d7695 100644 --- a/web/charts/example/lib/behaviors/percent_of_domain.dart +++ b/web/charts/lib/behaviors/percent_of_domain.dart @@ -20,7 +20,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class PercentOfDomainBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/behaviors/percent_of_domain_by_category.dart b/web/charts/lib/behaviors/percent_of_domain_by_category.dart similarity index 99% rename from web/charts/example/lib/behaviors/percent_of_domain_by_category.dart rename to web/charts/lib/behaviors/percent_of_domain_by_category.dart index 648304224..fe252a18c 100644 --- a/web/charts/example/lib/behaviors/percent_of_domain_by_category.dart +++ b/web/charts/lib/behaviors/percent_of_domain_by_category.dart @@ -21,7 +21,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class PercentOfDomainByCategoryBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/behaviors/percent_of_series.dart b/web/charts/lib/behaviors/percent_of_series.dart similarity index 98% rename from web/charts/example/lib/behaviors/percent_of_series.dart rename to web/charts/lib/behaviors/percent_of_series.dart index 60e30c4b1..279e8cd91 100644 --- a/web/charts/example/lib/behaviors/percent_of_series.dart +++ b/web/charts/lib/behaviors/percent_of_series.dart @@ -18,7 +18,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class PercentOfSeriesBarChart extends StatelessWidget { diff --git a/web/charts/example/lib/behaviors/selection_bar_highlight.dart b/web/charts/lib/behaviors/selection_bar_highlight.dart similarity index 98% rename from web/charts/example/lib/behaviors/selection_bar_highlight.dart rename to web/charts/lib/behaviors/selection_bar_highlight.dart index 34bdbc500..6ebb12c18 100644 --- a/web/charts/example/lib/behaviors/selection_bar_highlight.dart +++ b/web/charts/lib/behaviors/selection_bar_highlight.dart @@ -16,7 +16,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SelectionBarHighlight extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/selection_callback_example.dart b/web/charts/lib/behaviors/selection_callback_example.dart similarity index 99% rename from web/charts/example/lib/behaviors/selection_callback_example.dart rename to web/charts/lib/behaviors/selection_callback_example.dart index 66c17d896..79e33e7fc 100644 --- a/web/charts/example/lib/behaviors/selection_callback_example.dart +++ b/web/charts/lib/behaviors/selection_callback_example.dart @@ -30,7 +30,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SelectionCallbackExample extends StatefulWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/selection_line_highlight.dart b/web/charts/lib/behaviors/selection_line_highlight.dart similarity index 99% rename from web/charts/example/lib/behaviors/selection_line_highlight.dart rename to web/charts/lib/behaviors/selection_line_highlight.dart index a1964049f..e5307d562 100644 --- a/web/charts/example/lib/behaviors/selection_line_highlight.dart +++ b/web/charts/lib/behaviors/selection_line_highlight.dart @@ -16,7 +16,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SelectionLineHighlight extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/selection_line_highlight_custom_shape.dart b/web/charts/lib/behaviors/selection_line_highlight_custom_shape.dart similarity index 99% rename from web/charts/example/lib/behaviors/selection_line_highlight_custom_shape.dart rename to web/charts/lib/behaviors/selection_line_highlight_custom_shape.dart index 843d22488..fada01940 100644 --- a/web/charts/example/lib/behaviors/selection_line_highlight_custom_shape.dart +++ b/web/charts/lib/behaviors/selection_line_highlight_custom_shape.dart @@ -16,7 +16,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SelectionLineHighlightCustomShape extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/selection_scatter_plot_highlight.dart b/web/charts/lib/behaviors/selection_scatter_plot_highlight.dart similarity index 99% rename from web/charts/example/lib/behaviors/selection_scatter_plot_highlight.dart rename to web/charts/lib/behaviors/selection_scatter_plot_highlight.dart index 10657c648..087b105a6 100644 --- a/web/charts/example/lib/behaviors/selection_scatter_plot_highlight.dart +++ b/web/charts/lib/behaviors/selection_scatter_plot_highlight.dart @@ -35,7 +35,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SelectionScatterPlotHighlight extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/selection_user_managed.dart b/web/charts/lib/behaviors/selection_user_managed.dart similarity index 99% rename from web/charts/example/lib/behaviors/selection_user_managed.dart rename to web/charts/lib/behaviors/selection_user_managed.dart index 5293b895e..2378e1d2f 100644 --- a/web/charts/example/lib/behaviors/selection_user_managed.dart +++ b/web/charts/lib/behaviors/selection_user_managed.dart @@ -26,7 +26,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SelectionUserManaged extends StatefulWidget { final List seriesList; diff --git a/web/charts/example/lib/behaviors/slider.dart b/web/charts/lib/behaviors/slider.dart similarity index 98% rename from web/charts/example/lib/behaviors/slider.dart rename to web/charts/lib/behaviors/slider.dart index 4099d64e2..5c57180e3 100644 --- a/web/charts/example/lib/behaviors/slider.dart +++ b/web/charts/lib/behaviors/slider.dart @@ -16,9 +16,9 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; -import 'package:flutter_web/scheduler.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; /// This is just a simple line chart with a behavior that adds slider controls. /// diff --git a/web/charts/example/lib/behaviors/sliding_viewport_on_selection.dart b/web/charts/lib/behaviors/sliding_viewport_on_selection.dart similarity index 99% rename from web/charts/example/lib/behaviors/sliding_viewport_on_selection.dart rename to web/charts/lib/behaviors/sliding_viewport_on_selection.dart index 307f61535..52208ce67 100644 --- a/web/charts/example/lib/behaviors/sliding_viewport_on_selection.dart +++ b/web/charts/lib/behaviors/sliding_viewport_on_selection.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SlidingViewportOnSelection extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/combo_chart/combo_gallery.dart b/web/charts/lib/combo_chart/combo_gallery.dart similarity index 98% rename from web/charts/example/lib/combo_chart/combo_gallery.dart rename to web/charts/lib/combo_chart/combo_gallery.dart index 487ccf0ef..e56655102 100644 --- a/web/charts/example/lib/combo_chart/combo_gallery.dart +++ b/web/charts/lib/combo_chart/combo_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'date_time_line_point.dart'; import 'numeric_line_bar.dart'; diff --git a/web/charts/example/lib/combo_chart/date_time_line_point.dart b/web/charts/lib/combo_chart/date_time_line_point.dart similarity index 99% rename from web/charts/example/lib/combo_chart/date_time_line_point.dart rename to web/charts/lib/combo_chart/date_time_line_point.dart index 86c523f50..cf2ffd11b 100644 --- a/web/charts/example/lib/combo_chart/date_time_line_point.dart +++ b/web/charts/lib/combo_chart/date_time_line_point.dart @@ -24,7 +24,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class DateTimeComboLinePointChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/combo_chart/numeric_line_bar.dart b/web/charts/lib/combo_chart/numeric_line_bar.dart similarity index 99% rename from web/charts/example/lib/combo_chart/numeric_line_bar.dart rename to web/charts/lib/combo_chart/numeric_line_bar.dart index 37b9b7c3e..1f9ef516f 100644 --- a/web/charts/example/lib/combo_chart/numeric_line_bar.dart +++ b/web/charts/lib/combo_chart/numeric_line_bar.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class NumericComboLineBarChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/combo_chart/numeric_line_point.dart b/web/charts/lib/combo_chart/numeric_line_point.dart similarity index 99% rename from web/charts/example/lib/combo_chart/numeric_line_point.dart rename to web/charts/lib/combo_chart/numeric_line_point.dart index 60396dc76..0b371345d 100644 --- a/web/charts/example/lib/combo_chart/numeric_line_point.dart +++ b/web/charts/lib/combo_chart/numeric_line_point.dart @@ -24,7 +24,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class NumericComboLinePointChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/combo_chart/ordinal_bar_line.dart b/web/charts/lib/combo_chart/ordinal_bar_line.dart similarity index 99% rename from web/charts/example/lib/combo_chart/ordinal_bar_line.dart rename to web/charts/lib/combo_chart/ordinal_bar_line.dart index 91fda1223..ae2086001 100644 --- a/web/charts/example/lib/combo_chart/ordinal_bar_line.dart +++ b/web/charts/lib/combo_chart/ordinal_bar_line.dart @@ -18,7 +18,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class OrdinalComboBarLineChart extends StatelessWidget { diff --git a/web/charts/example/lib/combo_chart/scatter_plot_line.dart b/web/charts/lib/combo_chart/scatter_plot_line.dart similarity index 99% rename from web/charts/example/lib/combo_chart/scatter_plot_line.dart rename to web/charts/lib/combo_chart/scatter_plot_line.dart index ce542f614..e2d6df9f3 100644 --- a/web/charts/example/lib/combo_chart/scatter_plot_line.dart +++ b/web/charts/lib/combo_chart/scatter_plot_line.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class ScatterPlotComboLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/drawer.dart b/web/charts/lib/drawer.dart similarity index 97% rename from web/charts/example/lib/drawer.dart rename to web/charts/lib/drawer.dart index 1e76c7dcb..ad2f68ac1 100644 --- a/web/charts/example/lib/drawer.dart +++ b/web/charts/lib/drawer.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; /// A menu drawer supporting toggling theme and performance overlay. class GalleryDrawer extends StatelessWidget { diff --git a/web/charts/example/lib/gallery_scaffold.dart b/web/charts/lib/gallery_scaffold.dart similarity index 97% rename from web/charts/example/lib/gallery_scaffold.dart rename to web/charts/lib/gallery_scaffold.dart index 37c428cb5..9278ce6fd 100644 --- a/web/charts/example/lib/gallery_scaffold.dart +++ b/web/charts/lib/gallery_scaffold.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; typedef Widget GalleryWidgetBuilder(); diff --git a/web/charts/example/lib/home.dart b/web/charts/lib/home.dart similarity index 99% rename from web/charts/example/lib/home.dart rename to web/charts/lib/home.dart index ce3e2ca0f..c51904f89 100644 --- a/web/charts/example/lib/home.dart +++ b/web/charts/lib/home.dart @@ -14,7 +14,7 @@ // limitations under the License. import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'dart:developer'; import 'app_config.dart'; import 'drawer.dart'; diff --git a/web/charts/example/lib/i18n/i18n_gallery.dart b/web/charts/lib/i18n/i18n_gallery.dart similarity index 97% rename from web/charts/example/lib/i18n/i18n_gallery.dart rename to web/charts/lib/i18n/i18n_gallery.dart index 09ab41d08..cab90664d 100644 --- a/web/charts/example/lib/i18n/i18n_gallery.dart +++ b/web/charts/lib/i18n/i18n_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'rtl_bar_chart.dart'; import 'rtl_line_chart.dart'; diff --git a/web/charts/example/lib/i18n/rtl_bar_chart.dart b/web/charts/lib/i18n/rtl_bar_chart.dart similarity index 98% rename from web/charts/example/lib/i18n/rtl_bar_chart.dart rename to web/charts/lib/i18n/rtl_bar_chart.dart index bb090bb12..d4916561b 100644 --- a/web/charts/example/lib/i18n/rtl_bar_chart.dart +++ b/web/charts/lib/i18n/rtl_bar_chart.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class RTLBarChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/i18n/rtl_line_chart.dart b/web/charts/lib/i18n/rtl_line_chart.dart similarity index 98% rename from web/charts/example/lib/i18n/rtl_line_chart.dart rename to web/charts/lib/i18n/rtl_line_chart.dart index aca6e2b52..01b5d6130 100644 --- a/web/charts/example/lib/i18n/rtl_line_chart.dart +++ b/web/charts/lib/i18n/rtl_line_chart.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class RTLLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/i18n/rtl_line_segments.dart b/web/charts/lib/i18n/rtl_line_segments.dart similarity index 99% rename from web/charts/example/lib/i18n/rtl_line_segments.dart rename to web/charts/lib/i18n/rtl_line_segments.dart index 1b22c70bb..9560d35d5 100644 --- a/web/charts/example/lib/i18n/rtl_line_segments.dart +++ b/web/charts/lib/i18n/rtl_line_segments.dart @@ -29,7 +29,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class RTLLineSegments extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/i18n/rtl_series_legend.dart b/web/charts/lib/i18n/rtl_series_legend.dart similarity index 99% rename from web/charts/example/lib/i18n/rtl_series_legend.dart rename to web/charts/lib/i18n/rtl_series_legend.dart index 03a10c5f1..06763382c 100644 --- a/web/charts/example/lib/i18n/rtl_series_legend.dart +++ b/web/charts/lib/i18n/rtl_series_legend.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class RTLSeriesLegend extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/legends/datum_legend_options.dart b/web/charts/lib/legends/datum_legend_options.dart similarity index 99% rename from web/charts/example/lib/legends/datum_legend_options.dart rename to web/charts/lib/legends/datum_legend_options.dart index 3cdc327d9..6ab6da204 100644 --- a/web/charts/example/lib/legends/datum_legend_options.dart +++ b/web/charts/lib/legends/datum_legend_options.dart @@ -21,7 +21,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class DatumLegendOptions extends StatelessWidget { diff --git a/web/charts/example/lib/legends/datum_legend_with_measures.dart b/web/charts/lib/legends/datum_legend_with_measures.dart similarity index 99% rename from web/charts/example/lib/legends/datum_legend_with_measures.dart rename to web/charts/lib/legends/datum_legend_with_measures.dart index bbbb924c9..0d9dbd962 100644 --- a/web/charts/example/lib/legends/datum_legend_with_measures.dart +++ b/web/charts/lib/legends/datum_legend_with_measures.dart @@ -21,7 +21,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example that shows how to build a datum legend that shows measure values. diff --git a/web/charts/example/lib/legends/default_hidden_series_legend.dart b/web/charts/lib/legends/default_hidden_series_legend.dart similarity index 99% rename from web/charts/example/lib/legends/default_hidden_series_legend.dart rename to web/charts/lib/legends/default_hidden_series_legend.dart index df7fe1e07..d1cc48275 100644 --- a/web/charts/example/lib/legends/default_hidden_series_legend.dart +++ b/web/charts/lib/legends/default_hidden_series_legend.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class DefaultHiddenSeriesLegend extends StatelessWidget { diff --git a/web/charts/example/lib/legends/legend_custom_symbol.dart b/web/charts/lib/legends/legend_custom_symbol.dart similarity index 99% rename from web/charts/example/lib/legends/legend_custom_symbol.dart rename to web/charts/lib/legends/legend_custom_symbol.dart index f18552b2d..4bf4b1450 100644 --- a/web/charts/example/lib/legends/legend_custom_symbol.dart +++ b/web/charts/lib/legends/legend_custom_symbol.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example custom renderer that renders [IconData]. diff --git a/web/charts/example/lib/legends/legends_gallery.dart b/web/charts/lib/legends/legends_gallery.dart similarity index 98% rename from web/charts/example/lib/legends/legends_gallery.dart rename to web/charts/lib/legends/legends_gallery.dart index e94c3c8d3..82be62865 100644 --- a/web/charts/example/lib/legends/legends_gallery.dart +++ b/web/charts/lib/legends/legends_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'datum_legend_options.dart'; import 'datum_legend_with_measures.dart'; diff --git a/web/charts/example/lib/legends/series_legend_options.dart b/web/charts/lib/legends/series_legend_options.dart similarity index 99% rename from web/charts/example/lib/legends/series_legend_options.dart rename to web/charts/lib/legends/series_legend_options.dart index 3f4541603..1ac3a7a56 100644 --- a/web/charts/example/lib/legends/series_legend_options.dart +++ b/web/charts/lib/legends/series_legend_options.dart @@ -21,7 +21,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class LegendOptions extends StatelessWidget { diff --git a/web/charts/example/lib/legends/series_legend_with_measures.dart b/web/charts/lib/legends/series_legend_with_measures.dart similarity index 99% rename from web/charts/example/lib/legends/series_legend_with_measures.dart rename to web/charts/lib/legends/series_legend_with_measures.dart index a5c229244..35ed13241 100644 --- a/web/charts/example/lib/legends/series_legend_with_measures.dart +++ b/web/charts/lib/legends/series_legend_with_measures.dart @@ -21,7 +21,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; /// Example that shows how to build a series legend that shows measure values diff --git a/web/charts/example/lib/legends/simple_datum_legend.dart b/web/charts/lib/legends/simple_datum_legend.dart similarity index 98% rename from web/charts/example/lib/legends/simple_datum_legend.dart rename to web/charts/lib/legends/simple_datum_legend.dart index da4e40972..b37378223 100644 --- a/web/charts/example/lib/legends/simple_datum_legend.dart +++ b/web/charts/lib/legends/simple_datum_legend.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class SimpleDatumLegend extends StatelessWidget { diff --git a/web/charts/example/lib/legends/simple_series_legend.dart b/web/charts/lib/legends/simple_series_legend.dart similarity index 99% rename from web/charts/example/lib/legends/simple_series_legend.dart rename to web/charts/lib/legends/simple_series_legend.dart index 1284bd4f9..a665b410c 100644 --- a/web/charts/example/lib/legends/simple_series_legend.dart +++ b/web/charts/lib/legends/simple_series_legend.dart @@ -17,7 +17,7 @@ // EXCLUDE_FROM_GALLERY_DOCS_START import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:charts_flutter/flutter.dart' as charts; class SimpleSeriesLegend extends StatelessWidget { diff --git a/web/charts/example/lib/line_chart/animation_zoom.dart b/web/charts/lib/line_chart/animation_zoom.dart similarity index 98% rename from web/charts/example/lib/line_chart/animation_zoom.dart rename to web/charts/lib/line_chart/animation_zoom.dart index 1f62e3ed9..7f52ed128 100644 --- a/web/charts/example/lib/line_chart/animation_zoom.dart +++ b/web/charts/lib/line_chart/animation_zoom.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class LineAnimationZoomChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/area_and_line.dart b/web/charts/lib/line_chart/area_and_line.dart similarity index 99% rename from web/charts/example/lib/line_chart/area_and_line.dart rename to web/charts/lib/line_chart/area_and_line.dart index c660c129f..d2005168b 100644 --- a/web/charts/example/lib/line_chart/area_and_line.dart +++ b/web/charts/lib/line_chart/area_and_line.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class AreaAndLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/dash_pattern.dart b/web/charts/lib/line_chart/dash_pattern.dart similarity index 99% rename from web/charts/example/lib/line_chart/dash_pattern.dart rename to web/charts/lib/line_chart/dash_pattern.dart index 385aa766c..af4ff4767 100644 --- a/web/charts/example/lib/line_chart/dash_pattern.dart +++ b/web/charts/lib/line_chart/dash_pattern.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; /// Example of a line chart rendered with dash patterns. class DashPatternLineChart extends StatelessWidget { diff --git a/web/charts/example/lib/line_chart/line_annotation.dart b/web/charts/lib/line_chart/line_annotation.dart similarity index 98% rename from web/charts/example/lib/line_chart/line_annotation.dart rename to web/charts/lib/line_chart/line_annotation.dart index 5d21dc43b..657ac380b 100644 --- a/web/charts/example/lib/line_chart/line_annotation.dart +++ b/web/charts/lib/line_chart/line_annotation.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class LineLineAnnotationChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/line_gallery.dart b/web/charts/lib/line_chart/line_gallery.dart similarity index 99% rename from web/charts/example/lib/line_chart/line_gallery.dart rename to web/charts/lib/line_chart/line_gallery.dart index b19fc9aee..edd29a411 100644 --- a/web/charts/example/lib/line_chart/line_gallery.dart +++ b/web/charts/lib/line_chart/line_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'animation_zoom.dart'; import 'area_and_line.dart'; diff --git a/web/charts/example/lib/line_chart/points.dart b/web/charts/lib/line_chart/points.dart similarity index 98% rename from web/charts/example/lib/line_chart/points.dart rename to web/charts/lib/line_chart/points.dart index 90659c630..76f08d55a 100644 --- a/web/charts/example/lib/line_chart/points.dart +++ b/web/charts/lib/line_chart/points.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class PointsLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/range_annotation.dart b/web/charts/lib/line_chart/range_annotation.dart similarity index 98% rename from web/charts/example/lib/line_chart/range_annotation.dart rename to web/charts/lib/line_chart/range_annotation.dart index 92ceea3a6..59a56e816 100644 --- a/web/charts/example/lib/line_chart/range_annotation.dart +++ b/web/charts/lib/line_chart/range_annotation.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class LineRangeAnnotationChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/range_annotation_margin.dart b/web/charts/lib/line_chart/range_annotation_margin.dart similarity index 99% rename from web/charts/example/lib/line_chart/range_annotation_margin.dart rename to web/charts/lib/line_chart/range_annotation_margin.dart index 8ff64786f..3edcb0aa4 100644 --- a/web/charts/example/lib/line_chart/range_annotation_margin.dart +++ b/web/charts/lib/line_chart/range_annotation_margin.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class LineRangeAnnotationMarginChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/segments.dart b/web/charts/lib/line_chart/segments.dart similarity index 99% rename from web/charts/example/lib/line_chart/segments.dart rename to web/charts/lib/line_chart/segments.dart index 603129b2b..1ef629e90 100644 --- a/web/charts/example/lib/line_chart/segments.dart +++ b/web/charts/lib/line_chart/segments.dart @@ -29,7 +29,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SegmentsLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/simple.dart b/web/charts/lib/line_chart/simple.dart similarity index 98% rename from web/charts/example/lib/line_chart/simple.dart rename to web/charts/lib/line_chart/simple.dart index 231bc4d55..1975162ef 100644 --- a/web/charts/example/lib/line_chart/simple.dart +++ b/web/charts/lib/line_chart/simple.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SimpleLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/simple_nulls.dart b/web/charts/lib/line_chart/simple_nulls.dart similarity index 99% rename from web/charts/example/lib/line_chart/simple_nulls.dart rename to web/charts/lib/line_chart/simple_nulls.dart index a99c9dba4..91ad7f575 100644 --- a/web/charts/example/lib/line_chart/simple_nulls.dart +++ b/web/charts/lib/line_chart/simple_nulls.dart @@ -22,7 +22,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SimpleNullsLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/stacked_area.dart b/web/charts/lib/line_chart/stacked_area.dart similarity index 99% rename from web/charts/example/lib/line_chart/stacked_area.dart rename to web/charts/lib/line_chart/stacked_area.dart index 7ef94444b..989dfecee 100644 --- a/web/charts/example/lib/line_chart/stacked_area.dart +++ b/web/charts/lib/line_chart/stacked_area.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class StackedAreaLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/stacked_area_custom_color.dart b/web/charts/lib/line_chart/stacked_area_custom_color.dart similarity index 99% rename from web/charts/example/lib/line_chart/stacked_area_custom_color.dart rename to web/charts/lib/line_chart/stacked_area_custom_color.dart index 42baa853c..1f218e8c1 100644 --- a/web/charts/example/lib/line_chart/stacked_area_custom_color.dart +++ b/web/charts/lib/line_chart/stacked_area_custom_color.dart @@ -22,7 +22,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class StackedAreaCustomColorLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/line_chart/stacked_area_nulls.dart b/web/charts/lib/line_chart/stacked_area_nulls.dart similarity index 99% rename from web/charts/example/lib/line_chart/stacked_area_nulls.dart rename to web/charts/lib/line_chart/stacked_area_nulls.dart index b372ee8be..90d0b4177 100644 --- a/web/charts/example/lib/line_chart/stacked_area_nulls.dart +++ b/web/charts/lib/line_chart/stacked_area_nulls.dart @@ -31,7 +31,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class StackedAreaNullsLineChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/main.dart b/web/charts/lib/main.dart similarity index 97% rename from web/charts/example/lib/main.dart rename to web/charts/lib/main.dart index ac850e0f8..add19300d 100644 --- a/web/charts/example/lib/main.dart +++ b/web/charts/lib/main.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'app_config.dart'; import 'home.dart'; diff --git a/web/charts/example/lib/pie_chart/auto_label.dart b/web/charts/lib/pie_chart/auto_label.dart similarity index 98% rename from web/charts/example/lib/pie_chart/auto_label.dart rename to web/charts/lib/pie_chart/auto_label.dart index a49daecc3..af719d776 100644 --- a/web/charts/example/lib/pie_chart/auto_label.dart +++ b/web/charts/lib/pie_chart/auto_label.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class DonutAutoLabelChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/pie_chart/donut.dart b/web/charts/lib/pie_chart/donut.dart similarity index 98% rename from web/charts/example/lib/pie_chart/donut.dart rename to web/charts/lib/pie_chart/donut.dart index 25fbb71a4..59c02aa2b 100644 --- a/web/charts/example/lib/pie_chart/donut.dart +++ b/web/charts/lib/pie_chart/donut.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class DonutPieChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/pie_chart/gauge.dart b/web/charts/lib/pie_chart/gauge.dart similarity index 98% rename from web/charts/example/lib/pie_chart/gauge.dart rename to web/charts/lib/pie_chart/gauge.dart index 59d28f2de..ae1fae77f 100644 --- a/web/charts/example/lib/pie_chart/gauge.dart +++ b/web/charts/lib/pie_chart/gauge.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class GaugeChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/pie_chart/outside_label.dart b/web/charts/lib/pie_chart/outside_label.dart similarity index 98% rename from web/charts/example/lib/pie_chart/outside_label.dart rename to web/charts/lib/pie_chart/outside_label.dart index 93328c873..bf87677d6 100644 --- a/web/charts/example/lib/pie_chart/outside_label.dart +++ b/web/charts/lib/pie_chart/outside_label.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class PieOutsideLabelChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/pie_chart/partial_pie.dart b/web/charts/lib/pie_chart/partial_pie.dart similarity index 98% rename from web/charts/example/lib/pie_chart/partial_pie.dart rename to web/charts/lib/pie_chart/partial_pie.dart index 6eb9e6aca..1d6bf1f05 100644 --- a/web/charts/example/lib/pie_chart/partial_pie.dart +++ b/web/charts/lib/pie_chart/partial_pie.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class PartialPieChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/pie_chart/pie_gallery.dart b/web/charts/lib/pie_chart/pie_gallery.dart similarity index 98% rename from web/charts/example/lib/pie_chart/pie_gallery.dart rename to web/charts/lib/pie_chart/pie_gallery.dart index 224e96972..56c917803 100644 --- a/web/charts/example/lib/pie_chart/pie_gallery.dart +++ b/web/charts/lib/pie_chart/pie_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'auto_label.dart'; import 'donut.dart'; diff --git a/web/charts/example/lib/pie_chart/simple.dart b/web/charts/lib/pie_chart/simple.dart similarity index 98% rename from web/charts/example/lib/pie_chart/simple.dart rename to web/charts/lib/pie_chart/simple.dart index 6dd6908a4..667ddc121 100644 --- a/web/charts/example/lib/pie_chart/simple.dart +++ b/web/charts/lib/pie_chart/simple.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SimplePieChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/scatter_plot_chart/animation_zoom.dart b/web/charts/lib/scatter_plot_chart/animation_zoom.dart similarity index 99% rename from web/charts/example/lib/scatter_plot_chart/animation_zoom.dart rename to web/charts/lib/scatter_plot_chart/animation_zoom.dart index a5bf95d5b..bd4c683f4 100644 --- a/web/charts/example/lib/scatter_plot_chart/animation_zoom.dart +++ b/web/charts/lib/scatter_plot_chart/animation_zoom.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class ScatterPlotAnimationZoomChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/scatter_plot_chart/bucketing_axis.dart b/web/charts/lib/scatter_plot_chart/bucketing_axis.dart similarity index 99% rename from web/charts/example/lib/scatter_plot_chart/bucketing_axis.dart rename to web/charts/lib/scatter_plot_chart/bucketing_axis.dart index acfe587e0..1488841c0 100644 --- a/web/charts/example/lib/scatter_plot_chart/bucketing_axis.dart +++ b/web/charts/lib/scatter_plot_chart/bucketing_axis.dart @@ -23,7 +23,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class BucketingAxisScatterPlotChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/scatter_plot_chart/comparison_points.dart b/web/charts/lib/scatter_plot_chart/comparison_points.dart similarity index 99% rename from web/charts/example/lib/scatter_plot_chart/comparison_points.dart rename to web/charts/lib/scatter_plot_chart/comparison_points.dart index 1e0e5d07b..000c4ebbf 100644 --- a/web/charts/example/lib/scatter_plot_chart/comparison_points.dart +++ b/web/charts/lib/scatter_plot_chart/comparison_points.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class ComparisonPointsScatterPlotChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/scatter_plot_chart/scatter_plot_gallery.dart b/web/charts/lib/scatter_plot_chart/scatter_plot_gallery.dart similarity index 98% rename from web/charts/example/lib/scatter_plot_chart/scatter_plot_gallery.dart rename to web/charts/lib/scatter_plot_chart/scatter_plot_gallery.dart index be022a565..3c1b3f7a2 100644 --- a/web/charts/example/lib/scatter_plot_chart/scatter_plot_gallery.dart +++ b/web/charts/lib/scatter_plot_chart/scatter_plot_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'animation_zoom.dart'; import 'bucketing_axis.dart'; diff --git a/web/charts/example/lib/scatter_plot_chart/shapes.dart b/web/charts/lib/scatter_plot_chart/shapes.dart similarity index 99% rename from web/charts/example/lib/scatter_plot_chart/shapes.dart rename to web/charts/lib/scatter_plot_chart/shapes.dart index f6824f4db..f06095072 100644 --- a/web/charts/example/lib/scatter_plot_chart/shapes.dart +++ b/web/charts/lib/scatter_plot_chart/shapes.dart @@ -29,7 +29,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class ShapesScatterPlotChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/scatter_plot_chart/simple.dart b/web/charts/lib/scatter_plot_chart/simple.dart similarity index 99% rename from web/charts/example/lib/scatter_plot_chart/simple.dart rename to web/charts/lib/scatter_plot_chart/simple.dart index e7b4a0df0..fc2fee1f1 100644 --- a/web/charts/example/lib/scatter_plot_chart/simple.dart +++ b/web/charts/lib/scatter_plot_chart/simple.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SimpleScatterPlotChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/confidence_interval.dart b/web/charts/lib/time_series_chart/confidence_interval.dart similarity index 99% rename from web/charts/example/lib/time_series_chart/confidence_interval.dart rename to web/charts/lib/time_series_chart/confidence_interval.dart index d421b3e05..b1e4f52c8 100644 --- a/web/charts/example/lib/time_series_chart/confidence_interval.dart +++ b/web/charts/lib/time_series_chart/confidence_interval.dart @@ -21,7 +21,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TimeSeriesConfidenceInterval extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/end_points_axis.dart b/web/charts/lib/time_series_chart/end_points_axis.dart similarity index 98% rename from web/charts/example/lib/time_series_chart/end_points_axis.dart rename to web/charts/lib/time_series_chart/end_points_axis.dart index 9793c62d3..69433615f 100644 --- a/web/charts/example/lib/time_series_chart/end_points_axis.dart +++ b/web/charts/lib/time_series_chart/end_points_axis.dart @@ -20,7 +20,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class EndPointsAxisTimeSeriesChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/line_annotation.dart b/web/charts/lib/time_series_chart/line_annotation.dart similarity index 98% rename from web/charts/example/lib/time_series_chart/line_annotation.dart rename to web/charts/lib/time_series_chart/line_annotation.dart index 22e8c02a0..78dd36a99 100644 --- a/web/charts/example/lib/time_series_chart/line_annotation.dart +++ b/web/charts/lib/time_series_chart/line_annotation.dart @@ -25,7 +25,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TimeSeriesLineAnnotationChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/range_annotation.dart b/web/charts/lib/time_series_chart/range_annotation.dart similarity index 98% rename from web/charts/example/lib/time_series_chart/range_annotation.dart rename to web/charts/lib/time_series_chart/range_annotation.dart index b98e22ec5..8620078ec 100644 --- a/web/charts/example/lib/time_series_chart/range_annotation.dart +++ b/web/charts/lib/time_series_chart/range_annotation.dart @@ -25,7 +25,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TimeSeriesRangeAnnotationChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/range_annotation_margin.dart b/web/charts/lib/time_series_chart/range_annotation_margin.dart similarity index 99% rename from web/charts/example/lib/time_series_chart/range_annotation_margin.dart rename to web/charts/lib/time_series_chart/range_annotation_margin.dart index 3c8c0b763..80305f15c 100644 --- a/web/charts/example/lib/time_series_chart/range_annotation_margin.dart +++ b/web/charts/lib/time_series_chart/range_annotation_margin.dart @@ -19,7 +19,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TimeSeriesRangeAnnotationMarginChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/simple.dart b/web/charts/lib/time_series_chart/simple.dart similarity index 98% rename from web/charts/example/lib/time_series_chart/simple.dart rename to web/charts/lib/time_series_chart/simple.dart index b286f7c96..81e594b0a 100644 --- a/web/charts/example/lib/time_series_chart/simple.dart +++ b/web/charts/lib/time_series_chart/simple.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SimpleTimeSeriesChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/symbol_annotation.dart b/web/charts/lib/time_series_chart/symbol_annotation.dart similarity index 99% rename from web/charts/example/lib/time_series_chart/symbol_annotation.dart rename to web/charts/lib/time_series_chart/symbol_annotation.dart index ca28bd504..5d7b6e6d3 100644 --- a/web/charts/example/lib/time_series_chart/symbol_annotation.dart +++ b/web/charts/lib/time_series_chart/symbol_annotation.dart @@ -29,7 +29,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TimeSeriesSymbolAnnotationChart extends StatelessWidget { final List seriesList; diff --git a/web/charts/example/lib/time_series_chart/time_series_gallery.dart b/web/charts/lib/time_series_chart/time_series_gallery.dart similarity index 98% rename from web/charts/example/lib/time_series_chart/time_series_gallery.dart rename to web/charts/lib/time_series_chart/time_series_gallery.dart index 96ce0f1fa..f60d6dda1 100644 --- a/web/charts/example/lib/time_series_chart/time_series_gallery.dart +++ b/web/charts/lib/time_series_chart/time_series_gallery.dart @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../gallery_scaffold.dart'; import 'confidence_interval.dart'; import 'end_points_axis.dart'; diff --git a/web/charts/example/lib/time_series_chart/with_bar_renderer.dart b/web/charts/lib/time_series_chart/with_bar_renderer.dart similarity index 99% rename from web/charts/example/lib/time_series_chart/with_bar_renderer.dart rename to web/charts/lib/time_series_chart/with_bar_renderer.dart index fcd67f83a..603c5952c 100644 --- a/web/charts/example/lib/time_series_chart/with_bar_renderer.dart +++ b/web/charts/lib/time_series_chart/with_bar_renderer.dart @@ -18,7 +18,7 @@ import 'dart:math'; // EXCLUDE_FROM_GALLERY_DOCS_END import 'package:charts_flutter/flutter.dart' as charts; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TimeSeriesBar extends StatelessWidget { final List> seriesList; diff --git a/web/charts/pubspec.lock b/web/charts/pubspec.lock new file mode 100644 index 000000000..0fbd548dd --- /dev/null +++ b/web/charts/pubspec.lock @@ -0,0 +1,78 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + charts_common: + dependency: transitive + description: + name: charts_common + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.0" + charts_flutter: + dependency: "direct main" + description: + name: charts_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.8" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" + meta: + dependency: "direct main" + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.2.2 <3.0.0" diff --git a/web/charts/pubspec.yaml b/web/charts/pubspec.yaml new file mode 100644 index 000000000..60c45bd12 --- /dev/null +++ b/web/charts/pubspec.yaml @@ -0,0 +1,13 @@ +name: example +description: Charts-Flutter Demo +dependencies: + flutter: + sdk: flutter + charts_flutter: any + meta: ^1.1.1 + intl: ^0.15.2 + +flutter: + uses-material-design: true + assets: + - preview.png diff --git a/web/charts/example/web/index.html b/web/charts/web/index.html similarity index 100% rename from web/charts/example/web/index.html rename to web/charts/web/index.html diff --git a/web/dad_jokes/web/assets/FontManifest.json b/web/dad_jokes/assets/FontManifest.json similarity index 100% rename from web/dad_jokes/web/assets/FontManifest.json rename to web/dad_jokes/assets/FontManifest.json diff --git a/web/dad_jokes/web/assets/fonts/PatrickHand-Regular.ttf b/web/dad_jokes/assets/fonts/PatrickHand-Regular.ttf similarity index 100% rename from web/dad_jokes/web/assets/fonts/PatrickHand-Regular.ttf rename to web/dad_jokes/assets/fonts/PatrickHand-Regular.ttf diff --git a/web/dad_jokes/web/assets/icon.png b/web/dad_jokes/assets/icon.png similarity index 100% rename from web/dad_jokes/web/assets/icon.png rename to web/dad_jokes/assets/icon.png diff --git a/web/dad_jokes/web/preview.png b/web/dad_jokes/assets/preview.png similarity index 100% rename from web/dad_jokes/web/preview.png rename to web/dad_jokes/assets/preview.png diff --git a/web/dad_jokes/lib/auto_size_text.dart b/web/dad_jokes/lib/auto_size_text.dart index 53675216e..8956501bc 100644 --- a/web/dad_jokes/lib/auto_size_text.dart +++ b/web/dad_jokes/lib/auto_size_text.dart @@ -1,7 +1,7 @@ // Package auto_size_text: // https://pub.dartlang.org/packages/auto_size_text -import 'package:flutter_web/widgets.dart'; +import 'package:flutter/widgets.dart'; bool checkTextFits(TextSpan text, Locale locale, double scale, int maxLines, double maxWidth, double maxHeight) { diff --git a/web/dad_jokes/lib/main.dart b/web/dad_jokes/lib/main.dart index 32a82a4f9..71490ec95 100644 --- a/web/dad_jokes/lib/main.dart +++ b/web/dad_jokes/lib/main.dart @@ -1,4 +1,4 @@ -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'main_page.dart'; void main() => runApp(MyApp()); diff --git a/web/dad_jokes/lib/main_page.dart b/web/dad_jokes/lib/main_page.dart index b440a6427..8e35e9db4 100644 --- a/web/dad_jokes/lib/main_page.dart +++ b/web/dad_jokes/lib/main_page.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'auto_size_text.dart'; diff --git a/web/dad_jokes/pubspec.lock b/web/dad_jokes/pubspec.lock index 07974c621..701136a36 100644 --- a/web/dad_jokes/pubspec.lock +++ b/web/dad_jokes/pubspec.lock @@ -1,27 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" async: dependency: transitive description: @@ -29,83 +8,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" charcode: dependency: transitive description: @@ -113,20 +15,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,101 +22,18 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - flutter_web_ui: - dependency: "direct main" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" http: - dependency: transitive + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" http_parser: dependency: transitive description: @@ -236,55 +41,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.3" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" meta: dependency: transitive description: @@ -292,27 +48,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" path: dependency: transitive description: @@ -327,69 +62,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: + sky_engine: dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" + description: flutter + source: sdk + version: "0.0.99" source_span: dependency: transitive description: @@ -397,27 +74,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" string_scanner: dependency: transitive description: @@ -432,13 +88,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" typed_data: dependency: transitive description: @@ -453,26 +102,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/web/dad_jokes/pubspec.yaml b/web/dad_jokes/pubspec.yaml index e75405e65..4da7f7a35 100644 --- a/web/dad_jokes/pubspec.yaml +++ b/web/dad_jokes/pubspec.yaml @@ -4,23 +4,15 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any - flutter_web_ui: any + flutter: + sdk: flutter + http: ^0.12.0 dev_dependencies: pedantic: ^1.3.0 - build_runner: any - build_web_compilers: any - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui +flutter: + uses-material-design: true + assets: + - icon.png + - preview.png \ No newline at end of file diff --git a/web/dad_jokes/web/main.dart b/web/dad_jokes/web/main.dart deleted file mode 100644 index 848590a19..000000000 --- a/web/dad_jokes/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:dad_jokes/main.dart' as app; -import 'package:flutter_web_ui/ui.dart' as ui; - -Future main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/filipino_cuisine/assets/banana.png b/web/filipino_cuisine/assets/banana.png new file mode 100644 index 000000000..2ffe61bd7 Binary files /dev/null and b/web/filipino_cuisine/assets/banana.png differ diff --git a/web/filipino_cuisine/assets/beef.png b/web/filipino_cuisine/assets/beef.png new file mode 100644 index 000000000..6727b415a Binary files /dev/null and b/web/filipino_cuisine/assets/beef.png differ diff --git a/web/filipino_cuisine/web/assets/beef_caldereta.jpg b/web/filipino_cuisine/assets/beef_caldereta.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/beef_caldereta.jpg rename to web/filipino_cuisine/assets/beef_caldereta.jpg diff --git a/web/filipino_cuisine/web/assets/black_pepper.png b/web/filipino_cuisine/assets/black_pepper.png similarity index 100% rename from web/filipino_cuisine/web/assets/black_pepper.png rename to web/filipino_cuisine/assets/black_pepper.png diff --git a/web/filipino_cuisine/assets/bokchoy.png b/web/filipino_cuisine/assets/bokchoy.png new file mode 100644 index 000000000..d1fc50181 Binary files /dev/null and b/web/filipino_cuisine/assets/bokchoy.png differ diff --git a/web/filipino_cuisine/assets/butter.png b/web/filipino_cuisine/assets/butter.png new file mode 100644 index 000000000..83799878a Binary files /dev/null and b/web/filipino_cuisine/assets/butter.png differ diff --git a/web/filipino_cuisine/assets/cabbage.png b/web/filipino_cuisine/assets/cabbage.png new file mode 100644 index 000000000..bf754cc7c Binary files /dev/null and b/web/filipino_cuisine/assets/cabbage.png differ diff --git a/web/filipino_cuisine/web/assets/calamares.jpg b/web/filipino_cuisine/assets/calamares.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/calamares.jpg rename to web/filipino_cuisine/assets/calamares.jpg diff --git a/web/filipino_cuisine/assets/carrot.png b/web/filipino_cuisine/assets/carrot.png new file mode 100644 index 000000000..fb988e53b Binary files /dev/null and b/web/filipino_cuisine/assets/carrot.png differ diff --git a/web/filipino_cuisine/assets/cheese.png b/web/filipino_cuisine/assets/cheese.png new file mode 100644 index 000000000..0a8b2f76e Binary files /dev/null and b/web/filipino_cuisine/assets/cheese.png differ diff --git a/web/filipino_cuisine/web/assets/chicken_adobo.jpg b/web/filipino_cuisine/assets/chicken_adobo.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/chicken_adobo.jpg rename to web/filipino_cuisine/assets/chicken_adobo.jpg diff --git a/web/filipino_cuisine/assets/chili.png b/web/filipino_cuisine/assets/chili.png new file mode 100644 index 000000000..b2c1f1f46 Binary files /dev/null and b/web/filipino_cuisine/assets/chili.png differ diff --git a/web/filipino_cuisine/web/assets/crispy_pata.jpg b/web/filipino_cuisine/assets/crispy_pata.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/crispy_pata.jpg rename to web/filipino_cuisine/assets/crispy_pata.jpg diff --git a/web/filipino_cuisine/assets/egg.png b/web/filipino_cuisine/assets/egg.png new file mode 100644 index 000000000..83471a731 Binary files /dev/null and b/web/filipino_cuisine/assets/egg.png differ diff --git a/web/filipino_cuisine/web/assets/embutido.jpg b/web/filipino_cuisine/assets/embutido.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/embutido.jpg rename to web/filipino_cuisine/assets/embutido.jpg diff --git a/web/filipino_cuisine/assets/flour.png b/web/filipino_cuisine/assets/flour.png new file mode 100644 index 000000000..b3bf62641 Binary files /dev/null and b/web/filipino_cuisine/assets/flour.png differ diff --git a/web/filipino_cuisine/assets/garlic.png b/web/filipino_cuisine/assets/garlic.png new file mode 100644 index 000000000..f439c36a1 Binary files /dev/null and b/web/filipino_cuisine/assets/garlic.png differ diff --git a/web/filipino_cuisine/assets/green_beans.png b/web/filipino_cuisine/assets/green_beans.png new file mode 100644 index 000000000..038e98fdb Binary files /dev/null and b/web/filipino_cuisine/assets/green_beans.png differ diff --git a/web/filipino_cuisine/assets/green_bell.png b/web/filipino_cuisine/assets/green_bell.png new file mode 100644 index 000000000..9cc90d548 Binary files /dev/null and b/web/filipino_cuisine/assets/green_bell.png differ diff --git a/web/filipino_cuisine/web/assets/grilled_pork_ribs.jpg b/web/filipino_cuisine/assets/grilled_pork_ribs.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/grilled_pork_ribs.jpg rename to web/filipino_cuisine/assets/grilled_pork_ribs.jpg diff --git a/web/filipino_cuisine/web/assets/grilled_seafood.jpg b/web/filipino_cuisine/assets/grilled_seafood.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/grilled_seafood.jpg rename to web/filipino_cuisine/assets/grilled_seafood.jpg diff --git a/web/filipino_cuisine/assets/ground_pork.png b/web/filipino_cuisine/assets/ground_pork.png new file mode 100644 index 000000000..36ad1c41f Binary files /dev/null and b/web/filipino_cuisine/assets/ground_pork.png differ diff --git a/web/filipino_cuisine/assets/icon/fc_icon.png b/web/filipino_cuisine/assets/icon/fc_icon.png new file mode 100644 index 000000000..b40f02533 Binary files /dev/null and b/web/filipino_cuisine/assets/icon/fc_icon.png differ diff --git a/web/filipino_cuisine/assets/lemon.png b/web/filipino_cuisine/assets/lemon.png new file mode 100644 index 000000000..00a6d6041 Binary files /dev/null and b/web/filipino_cuisine/assets/lemon.png differ diff --git a/web/filipino_cuisine/assets/oil.png b/web/filipino_cuisine/assets/oil.png new file mode 100644 index 000000000..6428fea98 Binary files /dev/null and b/web/filipino_cuisine/assets/oil.png differ diff --git a/web/filipino_cuisine/assets/onion.png b/web/filipino_cuisine/assets/onion.png new file mode 100644 index 000000000..23e8ad722 Binary files /dev/null and b/web/filipino_cuisine/assets/onion.png differ diff --git a/web/filipino_cuisine/web/assets/pancit_canton.jpg b/web/filipino_cuisine/assets/pancit_canton.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/pancit_canton.jpg rename to web/filipino_cuisine/assets/pancit_canton.jpg diff --git a/web/filipino_cuisine/web/assets/pochero.jpg b/web/filipino_cuisine/assets/pochero.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/pochero.jpg rename to web/filipino_cuisine/assets/pochero.jpg diff --git a/web/filipino_cuisine/assets/pork.png b/web/filipino_cuisine/assets/pork.png new file mode 100644 index 000000000..56cd5ccf3 Binary files /dev/null and b/web/filipino_cuisine/assets/pork.png differ diff --git a/web/filipino_cuisine/web/assets/pork_sisig.jpg b/web/filipino_cuisine/assets/pork_sisig.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/pork_sisig.jpg rename to web/filipino_cuisine/assets/pork_sisig.jpg diff --git a/web/filipino_cuisine/assets/potato.png b/web/filipino_cuisine/assets/potato.png new file mode 100644 index 000000000..894f69d96 Binary files /dev/null and b/web/filipino_cuisine/assets/potato.png differ diff --git a/web/filipino_cuisine/web/preview.png b/web/filipino_cuisine/assets/preview.png similarity index 100% rename from web/filipino_cuisine/web/preview.png rename to web/filipino_cuisine/assets/preview.png diff --git a/web/filipino_cuisine/assets/raisins.png b/web/filipino_cuisine/assets/raisins.png new file mode 100644 index 000000000..e21ba5748 Binary files /dev/null and b/web/filipino_cuisine/assets/raisins.png differ diff --git a/web/filipino_cuisine/assets/red_bell.png b/web/filipino_cuisine/assets/red_bell.png new file mode 100644 index 000000000..15e87f38c Binary files /dev/null and b/web/filipino_cuisine/assets/red_bell.png differ diff --git a/web/filipino_cuisine/assets/red_pepper.png b/web/filipino_cuisine/assets/red_pepper.png new file mode 100644 index 000000000..fdffc287c Binary files /dev/null and b/web/filipino_cuisine/assets/red_pepper.png differ diff --git a/web/filipino_cuisine/assets/salt.png b/web/filipino_cuisine/assets/salt.png new file mode 100644 index 000000000..61291b6a7 Binary files /dev/null and b/web/filipino_cuisine/assets/salt.png differ diff --git a/web/filipino_cuisine/assets/sausage.png b/web/filipino_cuisine/assets/sausage.png new file mode 100644 index 000000000..472d8d3aa Binary files /dev/null and b/web/filipino_cuisine/assets/sausage.png differ diff --git a/web/filipino_cuisine/web/assets/smoked_salmon.jpg b/web/filipino_cuisine/assets/smoked_salmon.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/smoked_salmon.jpg rename to web/filipino_cuisine/assets/smoked_salmon.jpg diff --git a/web/filipino_cuisine/assets/squid.png b/web/filipino_cuisine/assets/squid.png new file mode 100644 index 000000000..3df4e8d42 Binary files /dev/null and b/web/filipino_cuisine/assets/squid.png differ diff --git a/web/filipino_cuisine/web/assets/sweet_and_sour_chicken_poultry.jpg b/web/filipino_cuisine/assets/sweet_and_sour_chicken_poultry.jpg similarity index 100% rename from web/filipino_cuisine/web/assets/sweet_and_sour_chicken_poultry.jpg rename to web/filipino_cuisine/assets/sweet_and_sour_chicken_poultry.jpg diff --git a/web/filipino_cuisine/assets/tomato.png b/web/filipino_cuisine/assets/tomato.png new file mode 100644 index 000000000..a0a0fc115 Binary files /dev/null and b/web/filipino_cuisine/assets/tomato.png differ diff --git a/web/filipino_cuisine/assets/yellow_onion.png b/web/filipino_cuisine/assets/yellow_onion.png new file mode 100644 index 000000000..94809cef8 Binary files /dev/null and b/web/filipino_cuisine/assets/yellow_onion.png differ diff --git a/web/filipino_cuisine/web/assets/fonts/Arkipelago.otf b/web/filipino_cuisine/fonts/Arkipelago.otf similarity index 100% rename from web/filipino_cuisine/web/assets/fonts/Arkipelago.otf rename to web/filipino_cuisine/fonts/Arkipelago.otf diff --git a/web/filipino_cuisine/web/assets/fonts/OpenSans-Bold.ttf b/web/filipino_cuisine/fonts/OpenSans-Bold.ttf similarity index 100% rename from web/filipino_cuisine/web/assets/fonts/OpenSans-Bold.ttf rename to web/filipino_cuisine/fonts/OpenSans-Bold.ttf diff --git a/web/filipino_cuisine/web/assets/fonts/OpenSans-Regular.ttf b/web/filipino_cuisine/fonts/OpenSans-Regular.ttf similarity index 100% rename from web/filipino_cuisine/web/assets/fonts/OpenSans-Regular.ttf rename to web/filipino_cuisine/fonts/OpenSans-Regular.ttf diff --git a/web/filipino_cuisine/lib/cook.dart b/web/filipino_cuisine/lib/cook.dart index c2c53aec9..0845643a2 100644 --- a/web/filipino_cuisine/lib/cook.dart +++ b/web/filipino_cuisine/lib/cook.dart @@ -1,4 +1,4 @@ -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class Cook extends StatefulWidget { final List dr; diff --git a/web/filipino_cuisine/lib/flutter_page_indicator.dart b/web/filipino_cuisine/lib/flutter_page_indicator.dart index 35fe702d2..4e7b63ba0 100644 --- a/web/filipino_cuisine/lib/flutter_page_indicator.dart +++ b/web/filipino_cuisine/lib/flutter_page_indicator.dart @@ -1,8 +1,8 @@ // Package flutter_page_indicator: // https://pub.dartlang.org/packages/flutter_page_indicator -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; class WarmPainter extends BasePainter { WarmPainter(PageIndicator widget, double page, int index, Paint paint) diff --git a/web/filipino_cuisine/lib/flutter_swiper.dart b/web/filipino_cuisine/lib/flutter_swiper.dart index f31458f8d..fa921982f 100644 --- a/web/filipino_cuisine/lib/flutter_swiper.dart +++ b/web/filipino_cuisine/lib/flutter_swiper.dart @@ -1,8 +1,8 @@ // Package flutter_swiper: // https://pub.dartlang.org/packages/flutter_swiper -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'dart:async'; diff --git a/web/filipino_cuisine/lib/main.dart b/web/filipino_cuisine/lib/main.dart index 9b386ec5d..8421fa4be 100644 --- a/web/filipino_cuisine/lib/main.dart +++ b/web/filipino_cuisine/lib/main.dart @@ -1,4 +1,4 @@ -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; diff --git a/web/filipino_cuisine/lib/transformer_page_view.dart b/web/filipino_cuisine/lib/transformer_page_view.dart index ff63234f8..3b586a849 100644 --- a/web/filipino_cuisine/lib/transformer_page_view.dart +++ b/web/filipino_cuisine/lib/transformer_page_view.dart @@ -3,8 +3,8 @@ import 'dart:async'; -import 'package:flutter_web/foundation.dart'; -import 'package:flutter_web/widgets.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; class IndexController extends ChangeNotifier { static const int NEXT = 1; diff --git a/web/filipino_cuisine/pubspec.lock b/web/filipino_cuisine/pubspec.lock index 07974c621..9fef22528 100644 --- a/web/filipino_cuisine/pubspec.lock +++ b/web/filipino_cuisine/pubspec.lock @@ -1,27 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" async: dependency: transitive description: @@ -29,83 +8,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" charcode: dependency: transitive description: @@ -113,20 +15,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,101 +22,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: - dependency: "direct main" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: + flutter_page_indicator: dependency: transitive description: - name: front_end + name: flutter_page_indicator url: "https://pub.dartlang.org" source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive + version: "0.0.3" + flutter_swiper: + dependency: "direct main" description: - name: html + name: flutter_swiper url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+2" + version: "1.1.6" http: - dependency: transitive + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" http_parser: dependency: transitive description: @@ -236,55 +55,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.3" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" meta: dependency: transitive description: @@ -292,27 +62,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" path: dependency: transitive description: @@ -327,69 +76,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: + sky_engine: dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" + description: flutter + source: sdk + version: "0.0.99" source_span: dependency: transitive description: @@ -397,27 +88,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" string_scanner: dependency: transitive description: @@ -432,13 +102,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - timing: + transformer_page_view: dependency: transitive description: - name: timing + name: transformer_page_view url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+1" + version: "0.1.6" typed_data: dependency: transitive description: @@ -453,26 +123,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" + flutter: ">=0.1.4 <3.0.0" diff --git a/web/filipino_cuisine/pubspec.yaml b/web/filipino_cuisine/pubspec.yaml index 3edea254e..e667ae06a 100644 --- a/web/filipino_cuisine/pubspec.yaml +++ b/web/filipino_cuisine/pubspec.yaml @@ -4,23 +4,64 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any - flutter_web_ui: any + flutter: + sdk: flutter + flutter_swiper: ^1.1.6 + http: ^0.12.0 dev_dependencies: pedantic: ^1.3.0 - build_runner: any - build_web_compilers: any -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui +flutter_icons: + android: "launcher_icon" + ios: true + image_path: "assets/icon/fc_icon.png" + +flutter: + uses-material-design: true + assets: + - calamares.jpg + - beef_caldereta.jpg + - pochero.jpg + - pork_sisig.jpg + - embutido.jpg + - black_pepper.png + - butter.png + - chili.png + - egg.png + - flour.png + - garlic.png + - lemon.png + - oil.png + - onion.png + - pork.png + - salt.png + - squid.png + - tomato.png + - banana.png + - cabbage.png + - bokchoy.png + - green_beans.png + - potato.png + - red_pepper.png + - yellow_onion.png + - red_bell.png + - green_bell.png + - carrot.png + - beef.png + - cheese.png + - raisins.png + - ground_pork.png + - sausage.png + - preview.png + fonts: + - family: opr + fonts: + - asset: fonts/OpenSans-Regular.ttf + - family: opb + fonts: + - asset: fonts/OpenSans-Bold.ttf + - family: ark + fonts: + - asset: fonts/Arkipelago.otf \ No newline at end of file diff --git a/web/filipino_cuisine/web/assets/FontManifest.json b/web/filipino_cuisine/web/assets/FontManifest.json deleted file mode 100644 index 03b3631de..000000000 --- a/web/filipino_cuisine/web/assets/FontManifest.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "family": "MaterialIcons", - "fonts": [ - { - "asset": "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" - } - ] - }, - { - "family": "opr", - "fonts": [ - { - "asset": "fonts/OpenSans-Regular.ttf" - } - ] - }, - { - "family": "opb", - "fonts": [ - { - "asset": "fonts/OpenSans-Bold.ttf" - } - ] - }, - { - "family": "ark", - "fonts": [ - { - "asset": "fonts/Arkipelago.otf" - } - ] - } -] \ No newline at end of file diff --git a/web/filipino_cuisine/web/assets/banana.png b/web/filipino_cuisine/web/assets/banana.png deleted file mode 100644 index 4b382a4bd..000000000 Binary files a/web/filipino_cuisine/web/assets/banana.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/beef.png b/web/filipino_cuisine/web/assets/beef.png deleted file mode 100644 index f7d66da5b..000000000 Binary files a/web/filipino_cuisine/web/assets/beef.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/bokchoy.png b/web/filipino_cuisine/web/assets/bokchoy.png deleted file mode 100644 index ff4a6d259..000000000 Binary files a/web/filipino_cuisine/web/assets/bokchoy.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/butter.png b/web/filipino_cuisine/web/assets/butter.png deleted file mode 100644 index 5aed09edf..000000000 Binary files a/web/filipino_cuisine/web/assets/butter.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/cabbage.png b/web/filipino_cuisine/web/assets/cabbage.png deleted file mode 100644 index ffd40c976..000000000 Binary files a/web/filipino_cuisine/web/assets/cabbage.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/carrot.png b/web/filipino_cuisine/web/assets/carrot.png deleted file mode 100644 index 121fc38eb..000000000 Binary files a/web/filipino_cuisine/web/assets/carrot.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/cheese.png b/web/filipino_cuisine/web/assets/cheese.png deleted file mode 100644 index 904d7ab4d..000000000 Binary files a/web/filipino_cuisine/web/assets/cheese.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/chili.png b/web/filipino_cuisine/web/assets/chili.png deleted file mode 100644 index 7297b5287..000000000 Binary files a/web/filipino_cuisine/web/assets/chili.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/egg.png b/web/filipino_cuisine/web/assets/egg.png deleted file mode 100644 index ca26cad2a..000000000 Binary files a/web/filipino_cuisine/web/assets/egg.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/flour.png b/web/filipino_cuisine/web/assets/flour.png deleted file mode 100644 index c64e781b4..000000000 Binary files a/web/filipino_cuisine/web/assets/flour.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/garlic.png b/web/filipino_cuisine/web/assets/garlic.png deleted file mode 100644 index c4e1c2f69..000000000 Binary files a/web/filipino_cuisine/web/assets/garlic.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/green_beans.png b/web/filipino_cuisine/web/assets/green_beans.png deleted file mode 100644 index 9bf1ab5c7..000000000 Binary files a/web/filipino_cuisine/web/assets/green_beans.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/green_bell.png b/web/filipino_cuisine/web/assets/green_bell.png deleted file mode 100644 index 5c1dd5912..000000000 Binary files a/web/filipino_cuisine/web/assets/green_bell.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/ground_pork.png b/web/filipino_cuisine/web/assets/ground_pork.png deleted file mode 100644 index 0d3167116..000000000 Binary files a/web/filipino_cuisine/web/assets/ground_pork.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/lemon.png b/web/filipino_cuisine/web/assets/lemon.png deleted file mode 100644 index 4f9e3ea76..000000000 Binary files a/web/filipino_cuisine/web/assets/lemon.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/oil.png b/web/filipino_cuisine/web/assets/oil.png deleted file mode 100644 index e4c733566..000000000 Binary files a/web/filipino_cuisine/web/assets/oil.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/onion.png b/web/filipino_cuisine/web/assets/onion.png deleted file mode 100644 index 3c0a9db8f..000000000 Binary files a/web/filipino_cuisine/web/assets/onion.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/pork.png b/web/filipino_cuisine/web/assets/pork.png deleted file mode 100644 index 38f94bc12..000000000 Binary files a/web/filipino_cuisine/web/assets/pork.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/potato.png b/web/filipino_cuisine/web/assets/potato.png deleted file mode 100644 index 2efa6e483..000000000 Binary files a/web/filipino_cuisine/web/assets/potato.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/raisins.png b/web/filipino_cuisine/web/assets/raisins.png deleted file mode 100644 index ae3514922..000000000 Binary files a/web/filipino_cuisine/web/assets/raisins.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/recipes.json b/web/filipino_cuisine/web/assets/recipes.json deleted file mode 100644 index 80131bb10..000000000 --- a/web/filipino_cuisine/web/assets/recipes.json +++ /dev/null @@ -1,366 +0,0 @@ -[ - { - "cn": "120 Gr 85 Kcal 13 Min", - "ct": "3 minutes", - "dc": "Calamares is the Filipino version of the Mediterranean breaded fried squid dish, Calamari. ", - "fn": "Calamares", - "fv": false, - "ig": [ - { - "c": "1/2 lb", - "n": "Squid", - "p": "squid.png" - }, - { - "c": "3/4 cup", - "n": "Flour", - "p": "flour.png" - }, - { - "c": "1 pc", - "n": "Egg", - "p": "egg.png" - }, - { - "c": "1 tsp", - "n": "Salt", - "p": "salt.png" - }, - { - "c": "1/2 tsp", - "n": "Black Pepper", - "p": "black_pepper.png" - }, - { - "c": "2 cups", - "n": "Cooking Oil", - "p": "oil.png" - } - ], - "in": [ - "Combine squid, salt, and ground black pepper then mix well. Let stand for 10 minutes", - "Heat a cooking pot the pour-in cooking oil", - "Dredge the squid in flour then dip in beaten egg and roll over", - "When the oil is hot enough, deep-fry the squid until the color of the coating turns brown", - "Remove the fried squid from the cooking pot and transfer in a plate lined with paper towels", - "Serve with sinamak or Asian dipping sauce" - ], - "pf": "calamares.jpg", - "pt": "10 minutes", - "sv": "3", - "tt": "13 MIN" - }, - { - "cn": "260 Gr 293 Kcal 1 Hour 42 Min", - "ct": "1 hour 30 minutes", - "dc": "Sisig is a popular Filipino dish. It is composed of minced pork, chopped onion, and mayonnaise.", - "fn": "Pork Sisig", - "fv": false, - "ig": [ - { - "c": "1 1/2 lb", - "n": "Pork Meat", - "p": "pork.png" - }, - { - "c": "1 pc", - "n": "Onion", - "p": "onion.png" - }, - { - "c": "3 tsp", - "n": "Chili", - "p": "chili.png" - }, - { - "c": "1/2 tsp", - "n": "Garlic", - "p": "garlic.png" - }, - { - "c": "1/4 tsp", - "n": "Black Pepper", - "p": "black_pepper.png" - }, - { - "c": "1 pc", - "n": "Lemon", - "p": "lemon.png" - }, - { - "c": "1/2 cup", - "n": "Butter", - "p": "butter.png" - }, - { - "c": "1/2 tsp", - "n": "Salt", - "p": "salt.png" - }, - { - "c": "1 pc", - "n": "Egg", - "p": "egg.png" - } - ], - "in": [ - "Pour the water in a pan and bring to a boil Add salt and pepper", - "Put-in the pork meat then simmer for 40 minutes to 1 hour (or until tender)", - "Grill the boiled pork meat until done", - "Chop the pork meat into fine pieces", - "In a wide pan, melt the butter. Add the onions. Cook until onions are soft.", - "Put-in the ginger and cook for 2 minutes", - "Add the pork meat. Cook for 10 to 12 minutes", - "Put-in the soy sauce, garlic powder, and chili. Mix well", - "Add salt and pepper to taste", - "Put-in the mayonnaise and mix with the other ingredients", - "Transfer to a serving plate. Top with chopped green onions and raw egg", - "Add the lemon before eating" - ], - "pf": "pork_sisig.jpg", - "pt": "12 minutes", - "sv": "6", - "tt": "1 hour 42 minutes" - }, - { - "cn": "120 Gr 168 Kcal 1 Hour 10 Min", - "ct": "3 minutes", - "dc": "Pochero or Puchero is a well-loved Filipino stew. Made with meat, tomatoes, and saba bananas. ", - "fn": "Pochero", - "fv": false, - "ig": [ - { - "c": "1 large", - "n": "Banana", - "p": "banana.png" - }, - { - "c": "2 pcs", - "n": "Tomato", - "p": "tomato.png" - }, - { - "c": "1 lb", - "n": "Pork Meat", - "p": "pork.png" - }, - { - "c": "1 pc", - "n": "Onion", - "p": "onion.png" - }, - { - "c": "1 tsp", - "n": "Garlic", - "p": "garlic.png" - }, - { - "c": "1 tbsp", - "n": "Peppercorn", - "p": "black_pepper.png" - }, - { - "c": "1 medium", - "n": "Potato", - "p": "potato.png" - }, - { - "c": "1 small", - "n": "Cabbage", - "p": "cabbage.png" - }, - { - "c": "1 bunch", - "n": "Bok Choy", - "p": "bokchoy.png" - }, - { - "c": "1/4 lb", - "n": "Green Beans", - "p": "green_beans.png" - }, - { - "c": "2 tbsp", - "n": "Cooking Oil", - "p": "oil.png" - } - ], - "in": [ - "Heat cooking oil in a cooking pot", - "Sauté garlic, onions, and tomatoes", - "Add pork and cook until the color turns light brown", - "Put-in fish sauce, whole pepper corn, and tomato sauce. Stir.", - "Add water and let boil. Simmer until pork is tender (about 30 to 40 minutes)", - "Put-in potato, plantain, and chick peas. Cook for 5 to 7 minutes.", - "Add cabbage and long green beans. Cook for 5 minutes", - "Stir-in the bok choy. Cover the pot and turn off the heat", - "Let the residual heat cook the bok choy (about 5 minutes)", - "Transfer to a serving plate and serve" - ], - "pf": "pochero.jpg", - "pt": "10 minutes", - "sv": "3", - "tt": "13 MIN" - }, - { - "cn": "140 Gr 250 Kcal 1 Hour 30 Min", - "ct": "3 minutes", - "dc": "A type of Filipino Beef Stew. This dish is cooked in a tomato-based sauce with vegetables such as potato, carrot, and bell pepper.", - "fn": "Beef Caldereta", - "fv": false, - "ig": [ - { - "c": "1/2 lb", - "n": "Beef", - "p": "beef.png" - }, - { - "c": "2 medium", - "n": "Carrot", - "p": "carrot.png" - }, - { - "c": "1 large", - "n": "Potato", - "p": "potato.png" - }, - { - "c": "1 small", - "n": "Green Bell Pepper", - "p": "green_bell.png" - }, - { - "c": "1 small", - "n": "Red Bell Pepper", - "p": "red_bell.png" - }, - { - "c": "2 Cloves", - "n": "Garlic", - "p": "garlic.png" - }, - { - "c": "1 medium", - "n": "Yellow Onion", - "p": "yellow_onion.png" - }, - { - "c": "1 tsp", - "n": "Salt", - "p": "salt.png" - }, - { - "c": "6 tbsp", - "n": "Cooking oil", - "p": "oil.png" - }, - { - "c": "1 tsp", - "n": "Peppercorn", - "p": "black_pepper.png" - }, - { - "c": "1 tsp", - "n": "Red Pepper", - "p": "chili.png" - } - ], - "in": [ - "Heat a pan or wok and then pour 3 tablespoons cooking oil. Stir-fry the bell peppers for 3 minutes. Remove the bell peppers and put in a plate. Set aside", - "Using the oil in the pan (add more if necessary), pan fry the carrots and potato for 3 to 5 minutes. Put these in a plate and then set aside", - "Heat the remaining 3 tablespoons of oil in a clean pot", - "Sauté the garlic and onion", - "Add the beef. Cook until it turns light brown", - "Pour –in tomato sauce and water. Let boil", - "Continue to cook in low heat for 60 minutes or until the beef gets tender. Add more water if needed", - "Stir-in the liver spread and then add some salt and pepper", - "Put the pan-fried potato and carrots in the pot. Stir. Add the bell peppers", - "Cover the pot. Continue to cook for 5 minutes", - "Add the red pepper flakes. Stir and cook for 3 minutes more", - "Transfer to a serving plate. Serve" - ], - "pf": "beef_caldereta.jpg", - "pt": "10 minutes", - "sv": "3", - "tt": "13 MIN" - }, - { - "cn": "90 Gr 130 Kcal 1 Hour 15 Min", - "ct": "3 minutes", - "dc": "Pork Embutido is a Filipino-style meatloaf made with a festive mixture of ground pork, carrots, and raisins wrapped around slices of eggs and sausage.", - "fn": "Embutido", - "fv": false, - "ig": [ - { - "c": "2 lbs", - "n": "Ground pork", - "p": "ground_pork.png" - }, - { - "c": "12 pcs", - "n": "Sausage", - "p": "sausage.png" - }, - { - "c": "3 pcs", - "n": "Egg", - "p": "egg.png" - }, - { - "c": "2 cups", - "n": "Cheese", - "p": "cheese.png" - }, - { - "c": "1 cup", - "n": "Red Bell Pepper", - "p": "red_bell.png" - }, - { - "c": "1 cup", - "n": "Green Bell Pepper", - "p": "green_bell.png" - }, - { - "c": "1/2 cup", - "n": "Raisins", - "p": "raisins.png" - }, - { - "c": "1/2 cup", - "n": "Carrot", - "p": "carrot.png" - }, - { - "c": "1/2 cup", - "n": "Onion", - "p": "onion.png" - }, - { - "c": "2 tbsp", - "n": "Salt", - "p": "salt.png" - }, - { - "c": "1 tbsp", - "n": "Peppercorn", - "p": "black_pepper.png" - } - ], - "in": [ - "Place the ground pork in a large container", - "Add the bread crumbs then break the raw eggs and add it in. Mix well", - "Put-in the carrots, bell pepper (red and green), onion, pickle relish, and cheddar cheese. Mix thoroughly", - "Add the raisins, tomato sauce, salt, and pepper then mix well.", - "Put in the sliced vienna sausage and sliced boiled eggs alternately on the middle of the flat meat mixture.", - "Roll the foil to form a cylinder — locking the sausage and egg in the middle if the meat mixture. Once done, lock the edges of the foil.", - "Place in a steamer and let cook for 1 hour.", - "Place inside the refrigerator until temperature turns cold", - "Slice and serve" - ], - "pf": "embutido.jpg", - "pt": "10 minutes", - "sv": "3", - "tt": "13 MIN" - } -] \ No newline at end of file diff --git a/web/filipino_cuisine/web/assets/red_bell.png b/web/filipino_cuisine/web/assets/red_bell.png deleted file mode 100644 index c8147a1de..000000000 Binary files a/web/filipino_cuisine/web/assets/red_bell.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/red_pepper.png b/web/filipino_cuisine/web/assets/red_pepper.png deleted file mode 100644 index 2e6393cd5..000000000 Binary files a/web/filipino_cuisine/web/assets/red_pepper.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/salt.png b/web/filipino_cuisine/web/assets/salt.png deleted file mode 100644 index a151a5792..000000000 Binary files a/web/filipino_cuisine/web/assets/salt.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/sausage.png b/web/filipino_cuisine/web/assets/sausage.png deleted file mode 100644 index 34b8e8d7b..000000000 Binary files a/web/filipino_cuisine/web/assets/sausage.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/squid.png b/web/filipino_cuisine/web/assets/squid.png deleted file mode 100644 index beafe4a73..000000000 Binary files a/web/filipino_cuisine/web/assets/squid.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/tomato.png b/web/filipino_cuisine/web/assets/tomato.png deleted file mode 100644 index cb7316f41..000000000 Binary files a/web/filipino_cuisine/web/assets/tomato.png and /dev/null differ diff --git a/web/filipino_cuisine/web/assets/yellow_onion.png b/web/filipino_cuisine/web/assets/yellow_onion.png deleted file mode 100644 index c03e83194..000000000 Binary files a/web/filipino_cuisine/web/assets/yellow_onion.png and /dev/null differ diff --git a/web/filipino_cuisine/web/main.dart b/web/filipino_cuisine/web/main.dart deleted file mode 100644 index 95b5427ac..000000000 --- a/web/filipino_cuisine/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:filipino_cuisine/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/gallery/.gitignore b/web/gallery/.gitignore new file mode 100644 index 000000000..2ddde2a5e --- /dev/null +++ b/web/gallery/.gitignore @@ -0,0 +1,73 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/web/gallery/.metadata b/web/gallery/.metadata new file mode 100644 index 000000000..412f5adaf --- /dev/null +++ b/web/gallery/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 393106fbf58124aef777fc12e97a20e237842f99 + channel: master + +project_type: app diff --git a/web/gallery/README.md b/web/gallery/README.md index 04322ad7c..7d47cdc89 100644 --- a/web/gallery/README.md +++ b/web/gallery/README.md @@ -1,3 +1,5 @@ A gallery of Flutter widgets and UX studies. -Original source on [GitHub](https://github.com/flutter/flutter/tree/master/examples/flutter_gallery). +Original source on [GitHub][gallery]. + +[gallery]: https://github.com/flutter/flutter/tree/master/examples/flutter_gallery diff --git a/web/gallery/web/preview.png b/web/gallery/assets/preview.png similarity index 100% rename from web/gallery/web/preview.png rename to web/gallery/assets/preview.png diff --git a/web/gallery/build.yaml b/web/gallery/build.yaml deleted file mode 100644 index c897b2e6d..000000000 --- a/web/gallery/build.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# See https://github.com/dart-lang/build/tree/master/build_web_compilers#configuration -targets: - $default: - builders: - build_web_compilers|entrypoint: - # Avoid building the test directory. - generate_for: ['web/**.dart'] diff --git a/web/gallery/lib/demo/all.dart b/web/gallery/lib/demo/all.dart index 9ebcbc82c..0dfe26be8 100644 --- a/web/gallery/lib/demo/all.dart +++ b/web/gallery/lib/demo/all.dart @@ -1,15 +1,17 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'animation_demo.dart'; -//export 'calculator_demo.dart'; +export 'calculator_demo.dart'; export 'colors_demo.dart'; export 'contacts_demo.dart'; -//export 'cupertino/cupertino.dart'; -//export 'images_demo.dart'; +export 'cupertino/cupertino.dart'; +export 'fortnightly/fortnightly.dart'; +export 'images_demo.dart'; export 'material/material.dart'; export 'pesto_demo.dart'; export 'shrine_demo.dart'; +export 'transformations/transformations_demo.dart'; export 'typography_demo.dart'; -//export 'video_demo.dart'; +export 'video_demo.dart'; diff --git a/web/gallery/lib/demo/animation/home.dart b/web/gallery/lib/demo/animation/home.dart index 5fa2bd416..857cf23ea 100644 --- a/web/gallery/lib/demo/animation/home.dart +++ b/web/gallery/lib/demo/animation/home.dart @@ -1,11 +1,14 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Based on https://material.uplabs.com/posts/google-newsstand-navigation-pattern +// See also: https://material-motion.github.io/material-motion/documentation/ + import 'dart:math' as math; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'sections.dart'; import 'widgets.dart'; @@ -31,17 +34,18 @@ class _RenderStatusBarPaddingSliver extends RenderSliver { _RenderStatusBarPaddingSliver({ @required double maxHeight, @required double scrollFactor, - }) : assert(maxHeight != null && maxHeight >= 0.0), - assert(scrollFactor != null && scrollFactor >= 1.0), - _maxHeight = maxHeight, - _scrollFactor = scrollFactor; + }) : assert(maxHeight != null && maxHeight >= 0.0), + assert(scrollFactor != null && scrollFactor >= 1.0), + _maxHeight = maxHeight, + _scrollFactor = scrollFactor; // The height of the status bar double get maxHeight => _maxHeight; double _maxHeight; set maxHeight(double value) { assert(maxHeight != null && maxHeight >= 0.0); - if (_maxHeight == value) return; + if (_maxHeight == value) + return; _maxHeight = value; markNeedsLayout(); } @@ -52,15 +56,15 @@ class _RenderStatusBarPaddingSliver extends RenderSliver { double _scrollFactor; set scrollFactor(double value) { assert(scrollFactor != null && scrollFactor >= 1.0); - if (_scrollFactor == value) return; + if (_scrollFactor == value) + return; _scrollFactor = value; markNeedsLayout(); } @override void performLayout() { - final double height = (maxHeight - constraints.scrollOffset / scrollFactor) - .clamp(0.0, maxHeight); + final double height = (maxHeight - constraints.scrollOffset / scrollFactor).clamp(0.0, maxHeight); geometry = SliverGeometry( paintExtent: math.min(height, constraints.remainingPaintExtent), scrollExtent: maxHeight, @@ -74,9 +78,9 @@ class _StatusBarPaddingSliver extends SingleChildRenderObjectWidget { Key key, @required this.maxHeight, this.scrollFactor = 5.0, - }) : assert(maxHeight != null && maxHeight >= 0.0), - assert(scrollFactor != null && scrollFactor >= 1.0), - super(key: key); + }) : assert(maxHeight != null && maxHeight >= 0.0), + assert(scrollFactor != null && scrollFactor >= 1.0), + super(key: key); final double maxHeight; final double scrollFactor; @@ -90,8 +94,7 @@ class _StatusBarPaddingSliver extends SingleChildRenderObjectWidget { } @override - void updateRenderObject( - BuildContext context, _RenderStatusBarPaddingSliver renderObject) { + void updateRenderObject(BuildContext context, _RenderStatusBarPaddingSliver renderObject) { renderObject ..maxHeight = maxHeight ..scrollFactor = scrollFactor; @@ -122,16 +125,15 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { double get maxExtent => math.max(maxHeight, minHeight); @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return SizedBox.expand(child: child); } @override bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { - return maxHeight != oldDelegate.maxHeight || - minHeight != oldDelegate.minHeight || - child != oldDelegate.child; + return maxHeight != oldDelegate.maxHeight + || minHeight != oldDelegate.minHeight + || child != oldDelegate.child; } @override @@ -195,28 +197,24 @@ class _AllSectionsLayout extends MultiChildLayoutDelegate { // When tCollapsed > 0 the titles spread apart final double columnTitleX = size.width / 10.0; final double rowTitleWidth = size.width * ((1 + tCollapsed) / 2.25); - double rowTitleX = - (size.width - rowTitleWidth) / 2.0 - selectedIndex * rowTitleWidth; + double rowTitleX = (size.width - rowTitleWidth) / 2.0 - selectedIndex * rowTitleWidth; // When tCollapsed > 0, the indicators move closer together //final double rowIndicatorWidth = 48.0 + (1.0 - tCollapsed) * (rowTitleWidth - 48.0); const double paddedSectionIndicatorWidth = kSectionIndicatorWidth + 8.0; final double rowIndicatorWidth = paddedSectionIndicatorWidth + - (1.0 - tCollapsed) * (rowTitleWidth - paddedSectionIndicatorWidth); - double rowIndicatorX = (size.width - rowIndicatorWidth) / 2.0 - - selectedIndex * rowIndicatorWidth; + (1.0 - tCollapsed) * (rowTitleWidth - paddedSectionIndicatorWidth); + double rowIndicatorX = (size.width - rowIndicatorWidth) / 2.0 - selectedIndex * rowIndicatorWidth; // Compute the size and origin of each card, title, and indicator for the maxHeight // "column" layout, and the midHeight "row" layout. The actual layout is just the // interpolated value between the column and row layouts for t. for (int index = 0; index < cardCount; index++) { + // Layout the card for index. - final Rect columnCardRect = Rect.fromLTWH( - columnCardX, columnCardY, columnCardWidth, columnCardHeight); - final Rect rowCardRect = - Rect.fromLTWH(rowCardX, 0.0, rowCardWidth, size.height); - final Rect cardRect = - _interpolateRect(columnCardRect, rowCardRect).shift(offset); + final Rect columnCardRect = Rect.fromLTWH(columnCardX, columnCardY, columnCardWidth, columnCardHeight); + final Rect rowCardRect = Rect.fromLTWH(rowCardX, 0.0, rowCardWidth, size.height); + final Rect cardRect = _interpolateRect(columnCardRect, rowCardRect).shift(offset); final String cardId = 'card$index'; if (hasChild(cardId)) { layoutChild(cardId, BoxConstraints.tight(cardRect.size)); @@ -224,38 +222,25 @@ class _AllSectionsLayout extends MultiChildLayoutDelegate { } // Layout the title for index. - final Size titleSize = - layoutChild('title$index', BoxConstraints.loose(cardRect.size)); - final double columnTitleY = - columnCardRect.centerLeft.dy - titleSize.height / 2.0; - final double rowTitleY = - rowCardRect.centerLeft.dy - titleSize.height / 2.0; - final double centeredRowTitleX = - rowTitleX + (rowTitleWidth - titleSize.width) / 2.0; + final Size titleSize = layoutChild('title$index', BoxConstraints.loose(cardRect.size)); + final double columnTitleY = columnCardRect.centerLeft.dy - titleSize.height / 2.0; + final double rowTitleY = rowCardRect.centerLeft.dy - titleSize.height / 2.0; + final double centeredRowTitleX = rowTitleX + (rowTitleWidth - titleSize.width) / 2.0; final Offset columnTitleOrigin = Offset(columnTitleX, columnTitleY); final Offset rowTitleOrigin = Offset(centeredRowTitleX, rowTitleY); - final Offset titleOrigin = - _interpolatePoint(columnTitleOrigin, rowTitleOrigin); + final Offset titleOrigin = _interpolatePoint(columnTitleOrigin, rowTitleOrigin); positionChild('title$index', titleOrigin + offset); // Layout the selection indicator for index. - final Size indicatorSize = - layoutChild('indicator$index', BoxConstraints.loose(cardRect.size)); - final double columnIndicatorX = - cardRect.centerRight.dx - indicatorSize.width - 16.0; - final double columnIndicatorY = - cardRect.bottomRight.dy - indicatorSize.height - 16.0; - final Offset columnIndicatorOrigin = - Offset(columnIndicatorX, columnIndicatorY); - final Rect titleRect = - Rect.fromPoints(titleOrigin, titleSize.bottomRight(titleOrigin)); - final double centeredRowIndicatorX = - rowIndicatorX + (rowIndicatorWidth - indicatorSize.width) / 2.0; + final Size indicatorSize = layoutChild('indicator$index', BoxConstraints.loose(cardRect.size)); + final double columnIndicatorX = cardRect.centerRight.dx - indicatorSize.width - 16.0; + final double columnIndicatorY = cardRect.bottomRight.dy - indicatorSize.height - 16.0; + final Offset columnIndicatorOrigin = Offset(columnIndicatorX, columnIndicatorY); + final Rect titleRect = Rect.fromPoints(titleOrigin, titleSize.bottomRight(titleOrigin)); + final double centeredRowIndicatorX = rowIndicatorX + (rowIndicatorWidth - indicatorSize.width) / 2.0; final double rowIndicatorY = titleRect.bottomCenter.dy + 16.0; - final Offset rowIndicatorOrigin = - Offset(centeredRowIndicatorX, rowIndicatorY); - final Offset indicatorOrigin = - _interpolatePoint(columnIndicatorOrigin, rowIndicatorOrigin); + final Offset rowIndicatorOrigin = Offset(centeredRowIndicatorX, rowIndicatorY); + final Offset indicatorOrigin = _interpolatePoint(columnIndicatorOrigin, rowIndicatorOrigin); positionChild('indicator$index', indicatorOrigin + offset); columnCardY += columnCardHeight; @@ -267,9 +252,9 @@ class _AllSectionsLayout extends MultiChildLayoutDelegate { @override bool shouldRelayout(_AllSectionsLayout oldDelegate) { - return tColumnToRow != oldDelegate.tColumnToRow || - cardCount != oldDelegate.cardCount || - selectedIndex != oldDelegate.selectedIndex; + return tColumnToRow != oldDelegate.tColumnToRow + || cardCount != oldDelegate.cardCount + || selectedIndex != oldDelegate.selectedIndex; } } @@ -283,14 +268,13 @@ class _AllSectionsView extends AnimatedWidget { this.midHeight, this.maxHeight, this.sectionCards = const [], - }) : assert(sections != null), - assert(sectionCards != null), - assert(sectionCards.length == sections.length), - assert(sectionIndex >= 0 && sectionIndex < sections.length), - assert(selectedIndex != null), - assert(selectedIndex.value >= 0.0 && - selectedIndex.value < sections.length.toDouble()), - super(key: key, listenable: selectedIndex); + }) : assert(sections != null), + assert(sectionCards != null), + assert(sectionCards.length == sections.length), + assert(sectionIndex >= 0 && sectionIndex < sections.length), + assert(selectedIndex != null), + assert(selectedIndex.value >= 0.0 && selectedIndex.value < sections.length.toDouble()), + super(key: key, listenable: selectedIndex); final int sectionIndex; final List

sections; @@ -310,14 +294,17 @@ class _AllSectionsView extends AnimatedWidget { // The layout's progress from from a column to a row. Its value is // 0.0 when size.height equals the maxHeight, 1.0 when the size.height // equals the midHeight. - final double tColumnToRow = 1.0 - - ((size.height - midHeight) / (maxHeight - midHeight)).clamp(0.0, 1.0); + final double tColumnToRow = + 1.0 - ((size.height - midHeight) / + (maxHeight - midHeight)).clamp(0.0, 1.0); + // The layout's progress from from the midHeight row layout to // a minHeight row layout. Its value is 0.0 when size.height equals // midHeight and 1.0 when size.height equals minHeight. - final double tCollapsed = 1.0 - - ((size.height - minHeight) / (midHeight - minHeight)).clamp(0.0, 1.0); + final double tCollapsed = + 1.0 - ((size.height - minHeight) / + (midHeight - minHeight)).clamp(0.0, 1.0); double _indicatorOpacity(int index) { return 1.0 - _selectedIndexDelta(index) * 0.5; @@ -356,8 +343,7 @@ class _AllSectionsView extends AnimatedWidget { return CustomMultiChildLayout( delegate: _AllSectionsLayout( - translation: - Alignment((selectedIndex.value - sectionIndex) * 2.0 - 1.0, -1.0), + translation: Alignment((selectedIndex.value - sectionIndex) * 2.0 - 1.0, -1.0), tColumnToRow: tColumnToRow, tCollapsed: tCollapsed, cardCount: sections.length, @@ -380,34 +366,29 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics { const _SnappingScrollPhysics({ ScrollPhysics parent, @required this.midScrollOffset, - }) : assert(midScrollOffset != null), - super(parent: parent); + }) : assert(midScrollOffset != null), + super(parent: parent); final double midScrollOffset; @override _SnappingScrollPhysics applyTo(ScrollPhysics ancestor) { - return _SnappingScrollPhysics( - parent: buildParent(ancestor), midScrollOffset: midScrollOffset); + return _SnappingScrollPhysics(parent: buildParent(ancestor), midScrollOffset: midScrollOffset); } Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity) { final double velocity = math.max(dragVelocity, minFlingVelocity); - return ScrollSpringSimulation(spring, offset, midScrollOffset, velocity, - tolerance: tolerance); + return ScrollSpringSimulation(spring, offset, midScrollOffset, velocity, tolerance: tolerance); } Simulation _toZeroScrollOffsetSimulation(double offset, double dragVelocity) { final double velocity = math.max(dragVelocity, minFlingVelocity); - return ScrollSpringSimulation(spring, offset, 0.0, velocity, - tolerance: tolerance); + return ScrollSpringSimulation(spring, offset, 0.0, velocity, tolerance: tolerance); } @override - Simulation createBallisticSimulation( - ScrollMetrics position, double dragVelocity) { - final Simulation simulation = - super.createBallisticSimulation(position, dragVelocity); + Simulation createBallisticSimulation(ScrollMetrics position, double dragVelocity) { + final Simulation simulation = super.createBallisticSimulation(position, dragVelocity); final double offset = position.pixels; if (simulation != null) { @@ -416,7 +397,8 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics { // then snap it there. Similarly if the simulation is headed down past // midScrollOffset but will not reach zero, then snap it to zero. final double simulationEnd = simulation.x(double.infinity); - if (simulationEnd >= midScrollOffset) return simulation; + if (simulationEnd >= midScrollOffset) + return simulation; if (dragVelocity > 0.0) return _toMidScrollOffsetSimulation(offset, dragVelocity); if (dragVelocity < 0.0) @@ -437,7 +419,7 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics { } class AnimationDemoHome extends StatefulWidget { - const AnimationDemoHome({Key key}) : super(key: key); + const AnimationDemoHome({ Key key }) : super(key: key); static const String routeName = '/animation'; @@ -465,21 +447,18 @@ class _AnimationDemoHomeState extends State { void _handleBackButton(double midScrollOffset) { if (_scrollController.offset >= midScrollOffset) - _scrollController.animateTo(0.0, - curve: _kScrollCurve, duration: _kScrollDuration); + _scrollController.animateTo(0.0, curve: _kScrollCurve, duration: _kScrollDuration); else Navigator.maybePop(context); } // Only enable paging for the heading when the user has scrolled to midScrollOffset. // Paging is enabled/disabled by setting the heading's PageView scroll physics. - bool _handleScrollNotification( - ScrollNotification notification, double midScrollOffset) { + bool _handleScrollNotification(ScrollNotification notification, double midScrollOffset) { if (notification.depth == 0 && notification is ScrollUpdateNotification) { - final ScrollPhysics physics = - _scrollController.position.pixels >= midScrollOffset - ? const PageScrollPhysics() - : const NeverScrollableScrollPhysics(); + final ScrollPhysics physics = _scrollController.position.pixels >= midScrollOffset + ? const PageScrollPhysics() + : const NeverScrollableScrollPhysics(); if (physics != _headingScrollPhysics) { setState(() { _headingScrollPhysics = physics; @@ -493,35 +472,27 @@ class _AnimationDemoHomeState extends State { if (_scrollController.offset < midScrollOffset) { // Scroll the overall list to the point where only one section card shows. // At the same time scroll the PageViews to the page at pageIndex. - _headingPageController.animateToPage(pageIndex, - curve: _kScrollCurve, duration: _kScrollDuration); - _scrollController.animateTo(midScrollOffset, - curve: _kScrollCurve, duration: _kScrollDuration); + _headingPageController.animateToPage(pageIndex, curve: _kScrollCurve, duration: _kScrollDuration); + _scrollController.animateTo(midScrollOffset, curve: _kScrollCurve, duration: _kScrollDuration); } else { // One one section card is showing: scroll one page forward or back. - final double centerX = - _headingPageController.position.viewportDimension / 2.0; - final int newPageIndex = - xOffset > centerX ? pageIndex + 1 : pageIndex - 1; - _headingPageController.animateToPage(newPageIndex, - curve: _kScrollCurve, duration: _kScrollDuration); + final double centerX = _headingPageController.position.viewportDimension / 2.0; + final int newPageIndex = xOffset > centerX ? pageIndex + 1 : pageIndex - 1; + _headingPageController.animateToPage(newPageIndex, curve: _kScrollCurve, duration: _kScrollDuration); } } - bool _handlePageNotification(ScrollNotification notification, - PageController leader, PageController follower) { + bool _handlePageNotification(ScrollNotification notification, PageController leader, PageController follower) { if (notification.depth == 0 && notification is ScrollUpdateNotification) { selectedIndex.value = leader.page; if (follower.page != leader.page) - // ignore: deprecated_member_use - follower.position.jumpToWithoutSettling(leader.position.pixels); + follower.position.jumpToWithoutSettling(leader.position.pixels); // ignore: deprecated_member_use } return false; } Iterable _detailItemsFor(Section section) { - final Iterable detailItems = - section.details.map((SectionDetail detail) { + final Iterable detailItems = section.details.map((SectionDetail detail) { return SectionDetailView(detail: detail); }); return ListTile.divideTiles(context: context, tiles: detailItems); @@ -533,33 +504,35 @@ class _AnimationDemoHomeState extends State { sectionCards.add(LayoutId( id: 'card$index', child: GestureDetector( - behavior: HitTestBehavior.opaque, - child: SectionCard(section: allSections[index]), - onTapUp: (TapUpDetails details) { - final double xOffset = details.globalPosition.dx; - setState(() { - _maybeScroll(midScrollOffset, index, xOffset); - }); - }), + behavior: HitTestBehavior.opaque, + child: SectionCard(section: allSections[index]), + onTapUp: (TapUpDetails details) { + final double xOffset = details.globalPosition.dx; + setState(() { + _maybeScroll(midScrollOffset, index, xOffset); + }); + }, + ), )); } final List headings = []; for (int index = 0; index < allSections.length; index++) { headings.add(Container( - color: _kAppBackgroundColor, - child: ClipRect( - child: _AllSectionsView( - sectionIndex: index, - sections: allSections, - selectedIndex: selectedIndex, - minHeight: _kAppBarMinHeight, - midHeight: _kAppBarMidHeight, - maxHeight: maxHeight, - sectionCards: sectionCards, + color: _kAppBackgroundColor, + child: ClipRect( + child: _AllSectionsView( + sectionIndex: index, + sections: allSections, + selectedIndex: selectedIndex, + minHeight: _kAppBarMinHeight, + midHeight: _kAppBarMidHeight, + maxHeight: maxHeight, + sectionCards: sectionCards, + ), ), - ), - )); + ) + ); } return headings; } @@ -571,21 +544,18 @@ class _AnimationDemoHomeState extends State { final double appBarMaxHeight = screenHeight - statusBarHeight; // The scroll offset that reveals the appBarMidHeight appbar. - final double appBarMidScrollOffset = - statusBarHeight + appBarMaxHeight - _kAppBarMidHeight; + final double appBarMidScrollOffset = statusBarHeight + appBarMaxHeight - _kAppBarMidHeight; return SizedBox.expand( child: Stack( children: [ NotificationListener( onNotification: (ScrollNotification notification) { - return _handleScrollNotification( - notification, appBarMidScrollOffset); + return _handleScrollNotification(notification, appBarMidScrollOffset); }, child: CustomScrollView( controller: _scrollController, - physics: _SnappingScrollPhysics( - midScrollOffset: appBarMidScrollOffset), + physics: _SnappingScrollPhysics(midScrollOffset: appBarMidScrollOffset), slivers: [ // Start out below the status bar, gradually move to the top of the screen. _StatusBarPaddingSliver( @@ -600,14 +570,12 @@ class _AnimationDemoHomeState extends State { maxHeight: appBarMaxHeight, child: NotificationListener( onNotification: (ScrollNotification notification) { - return _handlePageNotification(notification, - _headingPageController, _detailsPageController); + return _handlePageNotification(notification, _headingPageController, _detailsPageController); }, child: PageView( physics: _headingScrollPhysics, controller: _headingPageController, - children: _allHeadingItems( - appBarMaxHeight, appBarMidScrollOffset), + children: _allHeadingItems(appBarMaxHeight, appBarMidScrollOffset), ), ), ), @@ -618,8 +586,7 @@ class _AnimationDemoHomeState extends State { height: 610.0, child: NotificationListener( onNotification: (ScrollNotification notification) { - return _handlePageNotification(notification, - _detailsPageController, _headingPageController); + return _handlePageNotification(notification, _detailsPageController, _headingPageController); }, child: PageView( controller: _detailsPageController, @@ -645,11 +612,12 @@ class _AnimationDemoHomeState extends State { top: false, bottom: false, child: IconButton( - icon: const BackButtonIcon(), - tooltip: 'Back', - onPressed: () { - _handleBackButton(appBarMidScrollOffset); - }), + icon: const BackButtonIcon(), + tooltip: 'Back', + onPressed: () { + _handleBackButton(appBarMidScrollOffset); + }, + ), ), ), ), diff --git a/web/gallery/lib/demo/animation/sections.dart b/web/gallery/lib/demo/animation/sections.dart index 19cf4e40b..07e7d0919 100644 --- a/web/gallery/lib/demo/animation/sections.dart +++ b/web/gallery/lib/demo/animation/sections.dart @@ -1,15 +1,17 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +// Raw data for the animation demo. + +import 'package:flutter/material.dart'; const Color _mariner = Color(0xFF3B5F8F); const Color _mediumPurple = Color(0xFF8266D4); const Color _tomato = Color(0xFFF95B57); const Color _mySin = Color(0xFFF3A646); -const String _kGalleryAssetsPackage = null; +const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; class SectionDetail { const SectionDetail({ @@ -41,8 +43,9 @@ class Section { final List details; @override - bool operator ==(Object other) { - if (other is! Section) return false; + bool operator==(Object other) { + if (other is! Section) + return false; final Section otherSection = other; return title == otherSection.title; } diff --git a/web/gallery/lib/demo/animation/widgets.dart b/web/gallery/lib/demo/animation/widgets.dart index b7a92d8d1..60583d3e6 100644 --- a/web/gallery/lib/demo/animation/widgets.dart +++ b/web/gallery/lib/demo/animation/widgets.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'sections.dart'; @@ -10,9 +10,9 @@ const double kSectionIndicatorWidth = 32.0; // The card for a single section. Displays the section's gradient and background image. class SectionCard extends StatelessWidget { - const SectionCard({Key key, @required this.section}) - : assert(section != null), - super(key: key); + const SectionCard({ Key key, @required this.section }) + : assert(section != null), + super(key: key); final Section section; @@ -32,17 +32,12 @@ class SectionCard extends StatelessWidget { ], ), ), - // TODO(b:119312219): Remove Opacity layer when Image Color Filter - // is implemented in paintImage. - child: Opacity( - opacity: 0.075, - child: Image.asset( - section.backgroundAsset, - package: section.backgroundAssetPackage, - color: const Color.fromRGBO(255, 255, 255, 0.075), - colorBlendMode: BlendMode.modulate, - fit: BoxFit.cover, - ), + child: Image.asset( + section.backgroundAsset, + package: section.backgroundAssetPackage, + color: const Color.fromRGBO(255, 255, 255, 0.075), + colorBlendMode: BlendMode.modulate, + fit: BoxFit.cover, ), ), ); @@ -57,10 +52,10 @@ class SectionTitle extends StatelessWidget { @required this.section, @required this.scale, @required this.opacity, - }) : assert(section != null), - assert(scale != null), - assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), - super(key: key); + }) : assert(section != null), + assert(scale != null), + assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), + super(key: key); final Section section; final double scale; @@ -104,7 +99,7 @@ class SectionTitle extends StatelessWidget { // Small horizontal bar that indicates the selected section. class SectionIndicator extends StatelessWidget { - const SectionIndicator({Key key, this.opacity = 1.0}) : super(key: key); + const SectionIndicator({ Key key, this.opacity = 1.0 }) : super(key: key); final double opacity; @@ -122,10 +117,10 @@ class SectionIndicator extends StatelessWidget { // Display a single SectionDetail. class SectionDetailView extends StatelessWidget { - SectionDetailView({Key key, @required this.detail}) - : assert(detail != null && detail.imageAsset != null), - assert((detail.imageAsset ?? detail.title) != null), - super(key: key); + SectionDetailView({ Key key, @required this.detail }) + : assert(detail != null && detail.imageAsset != null), + assert((detail.imageAsset ?? detail.title) != null), + super(key: key); final SectionDetail detail; diff --git a/web/gallery/lib/demo/animation_demo.dart b/web/gallery/lib/demo/animation_demo.dart index 74a705428..62e5ef0d6 100644 --- a/web/gallery/lib/demo/animation_demo.dart +++ b/web/gallery/lib/demo/animation_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'animation/home.dart'; diff --git a/web/gallery/lib/demo/calculator/home.dart b/web/gallery/lib/demo/calculator/home.dart new file mode 100644 index 000000000..84250f616 --- /dev/null +++ b/web/gallery/lib/demo/calculator/home.dart @@ -0,0 +1,270 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'logic.dart'; + +class Calculator extends StatefulWidget { + const Calculator({Key key}) : super(key: key); + + @override + _CalculatorState createState() => _CalculatorState(); +} + +class _CalculatorState extends State { + /// As the user taps keys we update the current `_expression` and we also + /// keep a stack of previous expressions so we can return to earlier states + /// when the user hits the DEL key. + final List _expressionStack = []; + CalcExpression _expression = CalcExpression.empty(); + + // Make `expression` the current expression and push the previous current + // expression onto the stack. + void pushExpression(CalcExpression expression) { + _expressionStack.add(_expression); + _expression = expression; + } + + /// Pop the top expression off of the stack and make it the current expression. + void popCalcExpression() { + if (_expressionStack.isNotEmpty) { + _expression = _expressionStack.removeLast(); + } else { + _expression = CalcExpression.empty(); + } + } + + /// Set `resultExpression` to the current expression and clear the stack. + void setResult(CalcExpression resultExpression) { + _expressionStack.clear(); + _expression = resultExpression; + } + + void handleNumberTap(int n) { + final CalcExpression expression = _expression.appendDigit(n); + if (expression != null) { + setState(() { + pushExpression(expression); + }); + } + } + + void handlePointTap() { + final CalcExpression expression = _expression.appendPoint(); + if (expression != null) { + setState(() { + pushExpression(expression); + }); + } + } + + void handlePlusTap() { + final CalcExpression expression = _expression.appendOperation(Operation.Addition); + if (expression != null) { + setState(() { + pushExpression(expression); + }); + } + } + + void handleMinusTap() { + final CalcExpression expression = _expression.appendMinus(); + if (expression != null) { + setState(() { + pushExpression(expression); + }); + } + } + + void handleMultTap() { + final CalcExpression expression = _expression.appendOperation(Operation.Multiplication); + if (expression != null) { + setState(() { + pushExpression(expression); + }); + } + } + + void handleDivTap() { + final CalcExpression expression = _expression.appendOperation(Operation.Division); + if (expression != null) { + setState(() { + pushExpression(expression); + }); + } + } + + void handleEqualsTap() { + final CalcExpression resultExpression = _expression.computeResult(); + if (resultExpression != null) { + setState(() { + setResult(resultExpression); + }); + } + } + + void handleDelTap() { + setState(() { + popCalcExpression(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).canvasColor, + elevation: 0.0, + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Give the key-pad 3/5 of the vertical space and the display 2/5. + Expanded( + flex: 2, + child: CalcDisplay(content: _expression.toString()), + ), + const Divider(height: 1.0), + Expanded( + flex: 3, + child: KeyPad(calcState: this), + ), + ], + ), + ); + } +} + +class CalcDisplay extends StatelessWidget { + const CalcDisplay({ this.content }); + + final String content; + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + content, + style: const TextStyle(fontSize: 24.0), + ), + ); + } +} + +class KeyPad extends StatelessWidget { + const KeyPad({ this.calcState }); + + final _CalculatorState calcState; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = ThemeData( + primarySwatch: Colors.purple, + brightness: Brightness.dark, + platform: Theme.of(context).platform, + ); + return Theme( + data: themeData, + child: Material( + child: Row( + children: [ + Expanded( + // We set flex equal to the number of columns so that the main keypad + // and the op keypad have sizes proportional to their number of + // columns. + flex: 3, + child: Column( + children: [ + KeyRow([ + NumberKey(7, calcState), + NumberKey(8, calcState), + NumberKey(9, calcState), + ]), + KeyRow([ + NumberKey(4, calcState), + NumberKey(5, calcState), + NumberKey(6, calcState), + ]), + KeyRow([ + NumberKey(1, calcState), + NumberKey(2, calcState), + NumberKey(3, calcState), + ]), + KeyRow([ + CalcKey('.', calcState.handlePointTap), + NumberKey(0, calcState), + CalcKey('=', calcState.handleEqualsTap), + ]), + ], + ), + ), + Expanded( + child: Material( + color: themeData.backgroundColor, + child: Column( + children: [ + CalcKey('\u232B', calcState.handleDelTap), + CalcKey('\u00F7', calcState.handleDivTap), + CalcKey('\u00D7', calcState.handleMultTap), + CalcKey('-', calcState.handleMinusTap), + CalcKey('+', calcState.handlePlusTap), + ], + ), + ), + ), + ], + ), + ), + ); + } +} + +class KeyRow extends StatelessWidget { + const KeyRow(this.keys); + + final List keys; + + @override + Widget build(BuildContext context) { + return Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: keys, + ), + ); + } +} + +class CalcKey extends StatelessWidget { + const CalcKey(this.text, this.onTap); + + final String text; + final GestureTapCallback onTap; + + @override + Widget build(BuildContext context) { + final Orientation orientation = MediaQuery.of(context).orientation; + return Expanded( + child: InkResponse( + onTap: onTap, + child: Center( + child: Text( + text, + style: TextStyle( + fontSize: (orientation == Orientation.portrait) ? 32.0 : 24.0 + ), + ), + ), + ), + ); + } +} + +class NumberKey extends CalcKey { + NumberKey(int value, _CalculatorState calcState) + : super('$value', () { + calcState.handleNumberTap(value); + }); +} diff --git a/web/gallery/lib/demo/calculator/logic.dart b/web/gallery/lib/demo/calculator/logic.dart new file mode 100644 index 000000000..121b1ae99 --- /dev/null +++ b/web/gallery/lib/demo/calculator/logic.dart @@ -0,0 +1,342 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A token that composes an expression. There are several kinds of tokens +/// that represent arithmetic operation symbols, numbers and pieces of numbers. +/// We need to represent pieces of numbers because the user may have only +/// entered a partial expression so far. +class ExpressionToken { + ExpressionToken(this.stringRep); + + final String stringRep; + + @override + String toString() => stringRep; +} + +/// A token that represents a number. +class NumberToken extends ExpressionToken { + NumberToken(String stringRep, this.number) : super(stringRep); + + NumberToken.fromNumber(num number) : this('$number', number); + + final num number; +} + +/// A token that represents an integer. +class IntToken extends NumberToken { + IntToken(String stringRep) : super(stringRep, int.parse(stringRep)); +} + +/// A token that represents a floating point number. +class FloatToken extends NumberToken { + FloatToken(String stringRep) : super(stringRep, _parse(stringRep)); + + static double _parse(String stringRep) { + String toParse = stringRep; + if (toParse.startsWith('.')) + toParse = '0' + toParse; + if (toParse.endsWith('.')) + toParse = toParse + '0'; + return double.parse(toParse); + } +} + +/// A token that represents a number that is the result of a computation. +class ResultToken extends NumberToken { + ResultToken(num number) : super.fromNumber(round(number)); + + /// rounds `number` to 14 digits of precision. A double precision + /// floating point number is guaranteed to have at least this many + /// decimal digits of precision. + static num round(num number) { + if (number is int) + return number; + return double.parse(number.toStringAsPrecision(14)); + } +} + +/// A token that represents the unary minus prefix. +class LeadingNegToken extends ExpressionToken { + LeadingNegToken() : super('-'); +} + +enum Operation { Addition, Subtraction, Multiplication, Division } + +/// A token that represents an arithmetic operation symbol. +class OperationToken extends ExpressionToken { + OperationToken(this.operation) + : super(opString(operation)); + + Operation operation; + + static String opString(Operation operation) { + switch (operation) { + case Operation.Addition: + return ' + '; + case Operation.Subtraction: + return ' - '; + case Operation.Multiplication: + return ' \u00D7 '; + case Operation.Division: + return ' \u00F7 '; + } + assert(operation != null); + return null; + } +} + +/// As the user taps different keys the current expression can be in one +/// of several states. +enum ExpressionState { + /// The expression is empty or an operation symbol was just entered. + /// A new number must be started now. + Start, + + /// A minus sign was entered as a leading negative prefix. + LeadingNeg, + + /// We are in the midst of a number without a point. + Number, + + /// A point was just entered. + Point, + + /// We are in the midst of a number with a point. + NumberWithPoint, + + /// A result is being displayed + Result, +} + +/// An expression that can be displayed in a calculator. It is the result +/// of a sequence of user entries. It is represented by a sequence of tokens. +/// +/// The tokens are not in one to one correspondence with the key taps because we +/// use one token per number, not one token per digit. A [CalcExpression] is +/// immutable. The `append*` methods return a new [CalcExpression] that +/// represents the appropriate expression when one additional key tap occurs. +class CalcExpression { + CalcExpression(this._list, this.state); + + CalcExpression.empty() + : this([], ExpressionState.Start); + + CalcExpression.result(FloatToken result) + : _list = [], + state = ExpressionState.Result { + _list.add(result); + } + + /// The tokens comprising the expression. + final List _list; + /// The state of the expression. + final ExpressionState state; + + /// The string representation of the expression. This will be displayed + /// in the calculator's display panel. + @override + String toString() { + final StringBuffer buffer = StringBuffer(''); + buffer.writeAll(_list); + return buffer.toString(); + } + + /// Append a digit to the current expression and return a new expression + /// representing the result. Returns null to indicate that it is not legal + /// to append a digit in the current state. + CalcExpression appendDigit(int digit) { + ExpressionState newState = ExpressionState.Number; + ExpressionToken newToken; + final List outList = _list.toList(); + switch (state) { + case ExpressionState.Start: + // Start a new number with digit. + newToken = IntToken('$digit'); + break; + case ExpressionState.LeadingNeg: + // Replace the leading neg with a negative number starting with digit. + outList.removeLast(); + newToken = IntToken('-$digit'); + break; + case ExpressionState.Number: + final ExpressionToken last = outList.removeLast(); + newToken = IntToken('${last.stringRep}$digit'); + break; + case ExpressionState.Point: + case ExpressionState.NumberWithPoint: + final ExpressionToken last = outList.removeLast(); + newState = ExpressionState.NumberWithPoint; + newToken = FloatToken('${last.stringRep}$digit'); + break; + case ExpressionState.Result: + // Cannot enter a number now + return null; + } + outList.add(newToken); + return CalcExpression(outList, newState); + } + + /// Append a point to the current expression and return a new expression + /// representing the result. Returns null to indicate that it is not legal + /// to append a point in the current state. + CalcExpression appendPoint() { + ExpressionToken newToken; + final List outList = _list.toList(); + switch (state) { + case ExpressionState.Start: + newToken = FloatToken('.'); + break; + case ExpressionState.LeadingNeg: + case ExpressionState.Number: + final ExpressionToken last = outList.removeLast(); + newToken = FloatToken(last.stringRep + '.'); + break; + case ExpressionState.Point: + case ExpressionState.NumberWithPoint: + case ExpressionState.Result: + // Cannot enter a point now + return null; + } + outList.add(newToken); + return CalcExpression(outList, ExpressionState.Point); + } + + /// Append an operation symbol to the current expression and return a new + /// expression representing the result. Returns null to indicate that it is not + /// legal to append an operation symbol in the current state. + CalcExpression appendOperation(Operation op) { + switch (state) { + case ExpressionState.Start: + case ExpressionState.LeadingNeg: + case ExpressionState.Point: + // Cannot enter operation now. + return null; + case ExpressionState.Number: + case ExpressionState.NumberWithPoint: + case ExpressionState.Result: + break; + } + final List outList = _list.toList(); + outList.add(OperationToken(op)); + return CalcExpression(outList, ExpressionState.Start); + } + + /// Append a leading minus sign to the current expression and return a new + /// expression representing the result. Returns null to indicate that it is not + /// legal to append a leading minus sign in the current state. + CalcExpression appendLeadingNeg() { + switch (state) { + case ExpressionState.Start: + break; + case ExpressionState.LeadingNeg: + case ExpressionState.Point: + case ExpressionState.Number: + case ExpressionState.NumberWithPoint: + case ExpressionState.Result: + // Cannot enter leading neg now. + return null; + } + final List outList = _list.toList(); + outList.add(LeadingNegToken()); + return CalcExpression(outList, ExpressionState.LeadingNeg); + } + + /// Append a minus sign to the current expression and return a new expression + /// representing the result. Returns null to indicate that it is not legal + /// to append a minus sign in the current state. Depending on the current + /// state the minus sign will be interpreted as either a leading negative + /// sign or a subtraction operation. + CalcExpression appendMinus() { + switch (state) { + case ExpressionState.Start: + return appendLeadingNeg(); + case ExpressionState.LeadingNeg: + case ExpressionState.Point: + case ExpressionState.Number: + case ExpressionState.NumberWithPoint: + case ExpressionState.Result: + return appendOperation(Operation.Subtraction); + default: + return null; + } + } + + /// Computes the result of the current expression and returns a new + /// ResultExpression containing the result. Returns null to indicate that + /// it is not legal to compute a result in the current state. + CalcExpression computeResult() { + switch (state) { + case ExpressionState.Start: + case ExpressionState.LeadingNeg: + case ExpressionState.Point: + case ExpressionState.Result: + // Cannot compute result now. + return null; + case ExpressionState.Number: + case ExpressionState.NumberWithPoint: + break; + } + + // We make a copy of _list because CalcExpressions are supposed to + // be immutable. + final List list = _list.toList(); + // We obey order-of-operations by computing the sum of the 'terms', + // where a "term" is defined to be a sequence of numbers separated by + // multiplication or division symbols. + num currentTermValue = removeNextTerm(list); + while (list.isNotEmpty) { + final OperationToken opToken = list.removeAt(0); + final num nextTermValue = removeNextTerm(list); + switch (opToken.operation) { + case Operation.Addition: + currentTermValue += nextTermValue; + break; + case Operation.Subtraction: + currentTermValue -= nextTermValue; + break; + case Operation.Multiplication: + case Operation.Division: + // Logic error. + assert(false); + } + } + final List outList = []; + outList.add(ResultToken(currentTermValue)); + return CalcExpression(outList, ExpressionState.Result); + } + + /// Removes the next "term" from `list` and returns its numeric value. + /// A "term" is a sequence of number tokens separated by multiplication + /// and division symbols. + static num removeNextTerm(List list) { + assert(list != null && list.isNotEmpty); + final NumberToken firstNumToken = list.removeAt(0); + num currentValue = firstNumToken.number; + while (list.isNotEmpty) { + bool isDivision = false; + final OperationToken nextOpToken = list.first; + switch (nextOpToken.operation) { + case Operation.Addition: + case Operation.Subtraction: + // We have reached the end of the current term + return currentValue; + case Operation.Multiplication: + break; + case Operation.Division: + isDivision = true; + } + // Remove the operation token. + list.removeAt(0); + // Remove the next number token. + final NumberToken nextNumToken = list.removeAt(0); + final num nextNumber = nextNumToken.number; + if (isDivision) + currentValue /= nextNumber; + else + currentValue *= nextNumber; + } + return currentValue; + } +} diff --git a/web/gallery/lib/demo/calculator_demo.dart b/web/gallery/lib/demo/calculator_demo.dart new file mode 100644 index 000000000..cc75bc469 --- /dev/null +++ b/web/gallery/lib/demo/calculator_demo.dart @@ -0,0 +1,16 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'calculator/home.dart'; + +class CalculatorDemo extends StatelessWidget { + const CalculatorDemo({Key key}) : super(key: key); + + static const String routeName = '/calculator'; + + @override + Widget build(BuildContext context) => const Calculator(); +} diff --git a/web/gallery/lib/demo/colors_demo.dart b/web/gallery/lib/demo/colors_demo.dart index ddc225f19..d27e70490 100644 --- a/web/gallery/lib/demo/colors_demo.dart +++ b/web/gallery/lib/demo/colors_demo.dart @@ -1,118 +1,61 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; const double kColorItemHeight = 48.0; class Palette { - Palette({this.name, this.primary, this.accent, this.threshold = 900}); + Palette({ this.name, this.primary, this.accent, this.threshold = 900}); final String name; final MaterialColor primary; final MaterialAccentColor accent; - final int - threshold; // titles for indices > threshold are white, otherwise black + final int threshold; // titles for indices > threshold are white, otherwise black bool get isValid => name != null && primary != null && threshold != null; } final List allPalettes = [ - Palette( - name: 'RED', - primary: Colors.red, - accent: Colors.redAccent, - threshold: 300), - Palette( - name: 'PINK', - primary: Colors.pink, - accent: Colors.pinkAccent, - threshold: 200), - Palette( - name: 'PURPLE', - primary: Colors.purple, - accent: Colors.purpleAccent, - threshold: 200), - Palette( - name: 'DEEP PURPLE', - primary: Colors.deepPurple, - accent: Colors.deepPurpleAccent, - threshold: 200), - Palette( - name: 'INDIGO', - primary: Colors.indigo, - accent: Colors.indigoAccent, - threshold: 200), - Palette( - name: 'BLUE', - primary: Colors.blue, - accent: Colors.blueAccent, - threshold: 400), - Palette( - name: 'LIGHT BLUE', - primary: Colors.lightBlue, - accent: Colors.lightBlueAccent, - threshold: 500), - Palette( - name: 'CYAN', - primary: Colors.cyan, - accent: Colors.cyanAccent, - threshold: 600), - Palette( - name: 'TEAL', - primary: Colors.teal, - accent: Colors.tealAccent, - threshold: 400), - Palette( - name: 'GREEN', - primary: Colors.green, - accent: Colors.greenAccent, - threshold: 500), - Palette( - name: 'LIGHT GREEN', - primary: Colors.lightGreen, - accent: Colors.lightGreenAccent, - threshold: 600), - Palette( - name: 'LIME', - primary: Colors.lime, - accent: Colors.limeAccent, - threshold: 800), + Palette(name: 'RED', primary: Colors.red, accent: Colors.redAccent, threshold: 300), + Palette(name: 'PINK', primary: Colors.pink, accent: Colors.pinkAccent, threshold: 200), + Palette(name: 'PURPLE', primary: Colors.purple, accent: Colors.purpleAccent, threshold: 200), + Palette(name: 'DEEP PURPLE', primary: Colors.deepPurple, accent: Colors.deepPurpleAccent, threshold: 200), + Palette(name: 'INDIGO', primary: Colors.indigo, accent: Colors.indigoAccent, threshold: 200), + Palette(name: 'BLUE', primary: Colors.blue, accent: Colors.blueAccent, threshold: 400), + Palette(name: 'LIGHT BLUE', primary: Colors.lightBlue, accent: Colors.lightBlueAccent, threshold: 500), + Palette(name: 'CYAN', primary: Colors.cyan, accent: Colors.cyanAccent, threshold: 600), + Palette(name: 'TEAL', primary: Colors.teal, accent: Colors.tealAccent, threshold: 400), + Palette(name: 'GREEN', primary: Colors.green, accent: Colors.greenAccent, threshold: 500), + Palette(name: 'LIGHT GREEN', primary: Colors.lightGreen, accent: Colors.lightGreenAccent, threshold: 600), + Palette(name: 'LIME', primary: Colors.lime, accent: Colors.limeAccent, threshold: 800), Palette(name: 'YELLOW', primary: Colors.yellow, accent: Colors.yellowAccent), Palette(name: 'AMBER', primary: Colors.amber, accent: Colors.amberAccent), - Palette( - name: 'ORANGE', - primary: Colors.orange, - accent: Colors.orangeAccent, - threshold: 700), - Palette( - name: 'DEEP ORANGE', - primary: Colors.deepOrange, - accent: Colors.deepOrangeAccent, - threshold: 400), + Palette(name: 'ORANGE', primary: Colors.orange, accent: Colors.orangeAccent, threshold: 700), + Palette(name: 'DEEP ORANGE', primary: Colors.deepOrange, accent: Colors.deepOrangeAccent, threshold: 400), Palette(name: 'BROWN', primary: Colors.brown, threshold: 200), Palette(name: 'GREY', primary: Colors.grey, threshold: 500), Palette(name: 'BLUE GREY', primary: Colors.blueGrey, threshold: 500), ]; + class ColorItem extends StatelessWidget { const ColorItem({ Key key, @required this.index, @required this.color, this.prefix = '', - }) : assert(index != null), - assert(color != null), - assert(prefix != null), - super(key: key); + }) : assert(index != null), + assert(color != null), + assert(prefix != null), + super(key: key); final int index; final Color color; final String prefix; - String colorString() => - "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + String colorString() => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; @override Widget build(BuildContext context) { @@ -143,52 +86,38 @@ class PaletteTabView extends StatelessWidget { PaletteTabView({ Key key, @required this.colors, - }) : assert(colors != null && colors.isValid), - super(key: key); + }) : assert(colors != null && colors.isValid), + super(key: key); final Palette colors; - static const List primaryKeys = [ - 50, - 100, - 200, - 300, - 400, - 500, - 600, - 700, - 800, - 900 - ]; + static const List primaryKeys = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]; static const List accentKeys = [100, 200, 400, 700]; @override Widget build(BuildContext context) { final TextTheme textTheme = Theme.of(context).textTheme; - final TextStyle whiteTextStyle = - textTheme.body1.copyWith(color: Colors.white); - final TextStyle blackTextStyle = - textTheme.body1.copyWith(color: Colors.black); - final List colorItems = primaryKeys.map((int index) { - return DefaultTextStyle( - style: index > colors.threshold ? whiteTextStyle : blackTextStyle, - child: ColorItem(index: index, color: colors.primary[index]), - ); - }).toList(); - - if (colors.accent != null) { - colorItems.addAll(accentKeys.map((int index) { - return DefaultTextStyle( - style: index > colors.threshold ? whiteTextStyle : blackTextStyle, - child: - ColorItem(index: index, color: colors.accent[index], prefix: 'A'), - ); - }).toList()); - } - - return ListView( - itemExtent: kColorItemHeight, - children: colorItems, + final TextStyle whiteTextStyle = textTheme.body1.copyWith(color: Colors.white); + final TextStyle blackTextStyle = textTheme.body1.copyWith(color: Colors.black); + return Scrollbar( + child: ListView( + itemExtent: kColorItemHeight, + children: [ + ...primaryKeys.map((int index) { + return DefaultTextStyle( + style: index > colors.threshold ? whiteTextStyle : blackTextStyle, + child: ColorItem(index: index, color: colors.primary[index]), + ); + }), + if (colors.accent != null) + ...accentKeys.map((int index) { + return DefaultTextStyle( + style: index > colors.threshold ? whiteTextStyle : blackTextStyle, + child: ColorItem(index: index, color: colors.accent[index], prefix: 'A'), + ); + }), + ], + ) ); } } @@ -206,9 +135,7 @@ class ColorsDemo extends StatelessWidget { title: const Text('Colors'), bottom: TabBar( isScrollable: true, - tabs: allPalettes - .map((Palette swatch) => Tab(text: swatch.name)) - .toList(), + tabs: allPalettes.map((Palette swatch) => Tab(text: swatch.name)).toList(), ), ), body: TabBarView( diff --git a/web/gallery/lib/demo/contacts_demo.dart b/web/gallery/lib/demo/contacts_demo.dart index 9c909bf5f..3dc5e5cd6 100644 --- a/web/gallery/lib/demo/contacts_demo.dart +++ b/web/gallery/lib/demo/contacts_demo.dart @@ -1,12 +1,12 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/services.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class _ContactCategory extends StatelessWidget { - const _ContactCategory({Key key, this.icon, this.children}) : super(key: key); + const _ContactCategory({ Key key, this.icon, this.children }) : super(key: key); final IconData icon; final List children; @@ -17,7 +17,8 @@ class _ContactCategory extends StatelessWidget { return Container( padding: const EdgeInsets.symmetric(vertical: 16.0), decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: themeData.dividerColor))), + border: Border(bottom: BorderSide(color: themeData.dividerColor)) + ), child: DefaultTextStyle( style: Theme.of(context).textTheme.subhead, child: SafeArea( @@ -27,10 +28,11 @@ class _ContactCategory extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.symmetric(vertical: 24.0), - width: 72.0, - child: Icon(icon, color: themeData.primaryColor)), - Expanded(child: Column(children: children)) + padding: const EdgeInsets.symmetric(vertical: 24.0), + width: 72.0, + child: Icon(icon, color: themeData.primaryColor), + ), + Expanded(child: Column(children: children)), ], ), ), @@ -40,9 +42,9 @@ class _ContactCategory extends StatelessWidget { } class _ContactItem extends StatelessWidget { - _ContactItem({Key key, this.icon, this.lines, this.tooltip, this.onPressed}) - : assert(lines.length > 1), - super(key: key); + const _ContactItem({ Key key, this.icon, this.lines, this.tooltip, this.onPressed }) + : assert(lines.length > 1), + super(key: key); final IconData icon; final List lines; @@ -52,32 +54,35 @@ class _ContactItem extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - final List columnChildren = lines - .sublist(0, lines.length - 1) - .map((String line) => Text(line)) - .toList(); + final List columnChildren = lines.sublist(0, lines.length - 1).map((String line) => Text(line)).toList(); columnChildren.add(Text(lines.last, style: themeData.textTheme.caption)); final List rowChildren = [ Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: columnChildren)) + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: columnChildren, + ), + ), ]; if (icon != null) { rowChildren.add(SizedBox( - width: 72.0, - child: IconButton( - icon: Icon(icon), - color: themeData.primaryColor, - onPressed: onPressed))); + width: 72.0, + child: IconButton( + icon: Icon(icon), + color: themeData.primaryColor, + onPressed: onPressed, + ), + )); } return MergeSemantics( child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: rowChildren)), + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: rowChildren, + ), + ), ); } } @@ -92,8 +97,7 @@ class ContactsDemo extends StatefulWidget { enum AppBarBehavior { normal, pinned, floating, snapping } class ContactsDemoState extends State { - static final GlobalKey _scaffoldKey = - GlobalKey(); + static final GlobalKey _scaffoldKey = GlobalKey(); final double _appBarHeight = 256.0; AppBarBehavior _appBarBehavior = AppBarBehavior.pinned; @@ -113,8 +117,7 @@ class ContactsDemoState extends State { SliverAppBar( expandedHeight: _appBarHeight, pinned: _appBarBehavior == AppBarBehavior.pinned, - floating: _appBarBehavior == AppBarBehavior.floating || - _appBarBehavior == AppBarBehavior.snapping, + floating: _appBarBehavior == AppBarBehavior.floating || _appBarBehavior == AppBarBehavior.snapping, snap: _appBarBehavior == AppBarBehavior.snapping, actions: [ IconButton( @@ -122,8 +125,8 @@ class ContactsDemoState extends State { tooltip: 'Edit', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: - Text("Editing isn't supported in this screen."))); + content: Text("Editing isn't supported in this screen."), + )); }, ), PopupMenuButton( @@ -132,20 +135,23 @@ class ContactsDemoState extends State { _appBarBehavior = value; }); }, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (BuildContext context) => >[ const PopupMenuItem( - value: AppBarBehavior.normal, - child: Text('App bar scrolls away')), + value: AppBarBehavior.normal, + child: Text('App bar scrolls away'), + ), const PopupMenuItem( - value: AppBarBehavior.pinned, - child: Text('App bar stays put')), + value: AppBarBehavior.pinned, + child: Text('App bar stays put'), + ), const PopupMenuItem( - value: AppBarBehavior.floating, - child: Text('App bar floats')), + value: AppBarBehavior.floating, + child: Text('App bar floats'), + ), const PopupMenuItem( - value: AppBarBehavior.snapping, - child: Text('App bar snaps')), + value: AppBarBehavior.snapping, + child: Text('App bar snaps'), + ), ], ), ], @@ -156,7 +162,7 @@ class ContactsDemoState extends State { children: [ Image.asset( 'people/ali_landscape.png', - // package: 'flutter_gallery_assets', + package: 'flutter_gallery_assets', fit: BoxFit.cover, height: _appBarHeight, ), @@ -187,8 +193,8 @@ class ContactsDemoState extends State { tooltip: 'Send message', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'Pretend that this opened your SMS application.'))); + content: Text('Pretend that this opened your SMS application.'), + )); }, lines: const [ '(650) 555-1234', @@ -200,7 +206,8 @@ class ContactsDemoState extends State { tooltip: 'Send message', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text('A messaging app appears.'))); + content: Text('A messaging app appears.'), + )); }, lines: const [ '(323) 555-6789', @@ -212,8 +219,8 @@ class ContactsDemoState extends State { tooltip: 'Send message', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'Imagine if you will, a messaging application.'))); + content: Text('Imagine if you will, a messaging application.'), + )); }, lines: const [ '(650) 555-6789', @@ -231,8 +238,8 @@ class ContactsDemoState extends State { tooltip: 'Send personal e-mail', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'Here, your e-mail application would open.'))); + content: Text('Here, your e-mail application would open.'), + )); }, lines: const [ 'ali_connors@example.com', @@ -244,8 +251,8 @@ class ContactsDemoState extends State { tooltip: 'Send work e-mail', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'Summon your favorite e-mail application here.'))); + content: Text('Summon your favorite e-mail application here.'), + )); }, lines: const [ 'aliconnors@example.com', @@ -262,8 +269,8 @@ class ContactsDemoState extends State { tooltip: 'Open map', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'This would show a map of San Francisco.'))); + content: Text('This would show a map of San Francisco.'), + )); }, lines: const [ '2000 Main Street', @@ -276,8 +283,8 @@ class ContactsDemoState extends State { tooltip: 'Open map', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'This would show a map of Mountain View.'))); + content: Text('This would show a map of Mountain View.'), + )); }, lines: const [ '1600 Amphitheater Parkway', @@ -290,8 +297,8 @@ class ContactsDemoState extends State { tooltip: 'Open map', onPressed: () { _scaffoldKey.currentState.showSnackBar(const SnackBar( - content: Text( - 'This would also show a map, if this was not a demo.'))); + content: Text('This would also show a map, if this was not a demo.'), + )); }, lines: const [ '126 Severyns Ave', diff --git a/web/gallery/lib/demo/cupertino/cupertino.dart b/web/gallery/lib/demo/cupertino/cupertino.dart new file mode 100644 index 000000000..d76a1e315 --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino.dart @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'cupertino_activity_indicator_demo.dart'; +export 'cupertino_alert_demo.dart'; +export 'cupertino_buttons_demo.dart'; +export 'cupertino_navigation_demo.dart'; +export 'cupertino_picker_demo.dart'; +export 'cupertino_refresh_demo.dart'; +export 'cupertino_segmented_control_demo.dart'; +export 'cupertino_slider_demo.dart'; +export 'cupertino_switch_demo.dart'; +export 'cupertino_text_field_demo.dart'; diff --git a/web/gallery/lib/demo/cupertino/cupertino_activity_indicator_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_activity_indicator_demo.dart new file mode 100644 index 000000000..549485714 --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_activity_indicator_demo.dart @@ -0,0 +1,28 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import '../../gallery/demo.dart'; + +class CupertinoProgressIndicatorDemo extends StatelessWidget { + static const String routeName = '/cupertino/progress_indicator'; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + middle: const Text('Activity Indicator'), + trailing: CupertinoDemoDocumentationButton(routeName), + ), + child: const Center( + child: CupertinoActivityIndicator(), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_alert_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_alert_demo.dart new file mode 100644 index 000000000..ce0d749fd --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_alert_demo.dart @@ -0,0 +1,275 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import '../../gallery/demo.dart'; + +class CupertinoAlertDemo extends StatefulWidget { + static const String routeName = '/cupertino/alert'; + + @override + _CupertinoAlertDemoState createState() => _CupertinoAlertDemoState(); +} + +class _CupertinoAlertDemoState extends State { + String lastSelectedValue; + + void showDemoDialog({BuildContext context, Widget child}) { + showCupertinoDialog( + context: context, + builder: (BuildContext context) => child, + ).then((String value) { + if (value != null) { + setState(() { lastSelectedValue = value; }); + } + }); + } + + void showDemoActionSheet({BuildContext context, Widget child}) { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) => child, + ).then((String value) { + if (value != null) { + setState(() { lastSelectedValue = value; }); + } + }); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Alerts'), + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoAlertDemo.routeName), + ), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: Builder( + builder: (BuildContext context) { + final List stackChildren = [ + CupertinoScrollbar( + child: ListView( + // Add more padding to the normal safe area. + padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0) + + MediaQuery.of(context).padding, + children: [ + CupertinoButton.filled( + child: const Text('Alert'), + onPressed: () { + showDemoDialog( + context: context, + child: CupertinoAlertDialog( + title: const Text('Discard draft?'), + actions: [ + CupertinoDialogAction( + child: const Text('Discard'), + isDestructiveAction: true, + onPressed: () { + Navigator.pop(context, 'Discard'); + }, + ), + CupertinoDialogAction( + child: const Text('Cancel'), + isDefaultAction: true, + onPressed: () { + Navigator.pop(context, 'Cancel'); + }, + ), + ], + ), + ); + }, + ), + const Padding(padding: EdgeInsets.all(8.0)), + CupertinoButton.filled( + child: const Text('Alert with Title'), + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0), + onPressed: () { + showDemoDialog( + context: context, + child: CupertinoAlertDialog( + title: const Text('Allow "Maps" to access your location while you are using the app?'), + content: const Text('Your current location will be displayed on the map and used ' + 'for directions, nearby search results, and estimated travel times.'), + actions: [ + CupertinoDialogAction( + child: const Text('Don\'t Allow'), + onPressed: () { + Navigator.pop(context, 'Disallow'); + }, + ), + CupertinoDialogAction( + child: const Text('Allow'), + onPressed: () { + Navigator.pop(context, 'Allow'); + }, + ), + ], + ), + ); + }, + ), + const Padding(padding: EdgeInsets.all(8.0)), + CupertinoButton.filled( + child: const Text('Alert with Buttons'), + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0), + onPressed: () { + showDemoDialog( + context: context, + child: const CupertinoDessertDialog( + title: Text('Select Favorite Dessert'), + content: Text('Please select your favorite type of dessert from the ' + 'list below. Your selection will be used to customize the suggested ' + 'list of eateries in your area.'), + ), + ); + }, + ), + const Padding(padding: EdgeInsets.all(8.0)), + CupertinoButton.filled( + child: const Text('Alert Buttons Only'), + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0), + onPressed: () { + showDemoDialog( + context: context, + child: const CupertinoDessertDialog(), + ); + }, + ), + const Padding(padding: EdgeInsets.all(8.0)), + CupertinoButton.filled( + child: const Text('Action Sheet'), + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 36.0), + onPressed: () { + showDemoActionSheet( + context: context, + child: CupertinoActionSheet( + title: const Text('Favorite Dessert'), + message: const Text('Please select the best dessert from the options below.'), + actions: [ + CupertinoActionSheetAction( + child: const Text('Profiteroles'), + onPressed: () { + Navigator.pop(context, 'Profiteroles'); + }, + ), + CupertinoActionSheetAction( + child: const Text('Cannolis'), + onPressed: () { + Navigator.pop(context, 'Cannolis'); + }, + ), + CupertinoActionSheetAction( + child: const Text('Trifle'), + onPressed: () { + Navigator.pop(context, 'Trifle'); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + child: const Text('Cancel'), + isDefaultAction: true, + onPressed: () { + Navigator.pop(context, 'Cancel'); + }, + ), + ), + ); + }, + ), + ], + ), + ), + ]; + + if (lastSelectedValue != null) { + stackChildren.add( + Positioned( + bottom: 32.0, + child: Text('You selected: $lastSelectedValue'), + ), + ); + } + return Stack( + alignment: Alignment.center, + children: stackChildren, + ); + }, + ), + ), + ); + } +} + +class CupertinoDessertDialog extends StatelessWidget { + const CupertinoDessertDialog({Key key, this.title, this.content}) : super(key: key); + + final Widget title; + final Widget content; + + @override + Widget build(BuildContext context) { + return CupertinoAlertDialog( + title: title, + content: content, + actions: [ + CupertinoDialogAction( + child: const Text('Cheesecake'), + onPressed: () { + Navigator.pop(context, 'Cheesecake'); + }, + ), + CupertinoDialogAction( + child: const Text('Tiramisu'), + onPressed: () { + Navigator.pop(context, 'Tiramisu'); + }, + ), + CupertinoDialogAction( + child: const Text('Apple Pie'), + onPressed: () { + Navigator.pop(context, 'Apple Pie'); + }, + ), + CupertinoDialogAction( + child: const Text("Devil's food cake"), + onPressed: () { + Navigator.pop(context, "Devil's food cake"); + }, + ), + CupertinoDialogAction( + child: const Text('Banana Split'), + onPressed: () { + Navigator.pop(context, 'Banana Split'); + }, + ), + CupertinoDialogAction( + child: const Text('Oatmeal Cookie'), + onPressed: () { + Navigator.pop(context, 'Oatmeal Cookies'); + }, + ), + CupertinoDialogAction( + child: const Text('Chocolate Brownie'), + onPressed: () { + Navigator.pop(context, 'Chocolate Brownies'); + }, + ), + CupertinoDialogAction( + child: const Text('Cancel'), + isDestructiveAction: true, + onPressed: () { + Navigator.pop(context, 'Cancel'); + }, + ), + ], + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_buttons_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_buttons_demo.dart new file mode 100644 index 000000000..8bddc8c6a --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_buttons_demo.dart @@ -0,0 +1,89 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import '../../gallery/demo.dart'; + +class CupertinoButtonsDemo extends StatefulWidget { + static const String routeName = '/cupertino/buttons'; + + @override + _CupertinoButtonDemoState createState() => _CupertinoButtonDemoState(); +} + +class _CupertinoButtonDemoState extends State { + int _pressedCount = 0; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Buttons'), + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoButtonsDemo.routeName), + ), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: SafeArea( + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'iOS themed buttons are flat. They can have borders or backgrounds but ' + 'only when necessary.' + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(_pressedCount > 0 + ? 'Button pressed $_pressedCount time${_pressedCount == 1 ? "" : "s"}' + : ' '), + const Padding(padding: EdgeInsets.all(12.0)), + Align( + alignment: const Alignment(0.0, -0.2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoButton( + child: const Text('Cupertino Button'), + onPressed: () { + setState(() { _pressedCount += 1; }); + }, + ), + const CupertinoButton( + child: Text('Disabled'), + onPressed: null, + ), + ], + ), + ), + const Padding(padding: EdgeInsets.all(12.0)), + CupertinoButton.filled( + child: const Text('With Background'), + onPressed: () { + setState(() { _pressedCount += 1; }); + }, + ), + const Padding(padding: EdgeInsets.all(12.0)), + const CupertinoButton.filled( + child: Text('Disabled'), + onPressed: null, + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_navigation_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_navigation_demo.dart new file mode 100644 index 000000000..5f842a0f7 --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_navigation_demo.dart @@ -0,0 +1,804 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../gallery/demo.dart'; + +const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; + +const List coolColors = [ + Color.fromARGB(255, 255, 59, 48), + Color.fromARGB(255, 255, 149, 0), + Color.fromARGB(255, 255, 204, 0), + Color.fromARGB(255, 76, 217, 100), + Color.fromARGB(255, 90, 200, 250), + Color.fromARGB(255, 0, 122, 255), + Color.fromARGB(255, 88, 86, 214), + Color.fromARGB(255, 255, 45, 85), +]; + +const List coolColorNames = [ + 'Sarcoline', 'Coquelicot', 'Smaragdine', 'Mikado', 'Glaucous', 'Wenge', + 'Fulvous', 'Xanadu', 'Falu', 'Eburnean', 'Amaranth', 'Australien', + 'Banan', 'Falu', 'Gingerline', 'Incarnadine', 'Labrador', 'Nattier', + 'Pervenche', 'Sinoper', 'Verditer', 'Watchet', 'Zaffre', +]; + +const int _kChildCount = 50; + +class CupertinoNavigationDemo extends StatelessWidget { + CupertinoNavigationDemo() + : colorItems = List.generate(_kChildCount, (int index) { + return coolColors[math.Random().nextInt(coolColors.length)]; + }) , + colorNameItems = List.generate(_kChildCount, (int index) { + return coolColorNames[math.Random().nextInt(coolColorNames.length)]; + }); + + static const String routeName = '/cupertino/navigation'; + + final List colorItems; + final List colorNameItems; + + @override + Widget build(BuildContext context) { + return WillPopScope( + // Prevent swipe popping of this page. Use explicit exit buttons only. + onWillPop: () => Future.value(true), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: CupertinoTabScaffold( + tabBar: CupertinoTabBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(CupertinoIcons.home), + title: Text('Home'), + ), + BottomNavigationBarItem( + icon: Icon(CupertinoIcons.conversation_bubble), + title: Text('Support'), + ), + BottomNavigationBarItem( + icon: Icon(CupertinoIcons.profile_circled), + title: Text('Profile'), + ), + ], + ), + tabBuilder: (BuildContext context, int index) { + assert(index >= 0 && index <= 2); + switch (index) { + case 0: + return CupertinoTabView( + builder: (BuildContext context) { + return CupertinoDemoTab1( + colorItems: colorItems, + colorNameItems: colorNameItems, + ); + }, + defaultTitle: 'Colors', + ); + break; + case 1: + return CupertinoTabView( + builder: (BuildContext context) => CupertinoDemoTab2(), + defaultTitle: 'Support Chat', + ); + break; + case 2: + return CupertinoTabView( + builder: (BuildContext context) => CupertinoDemoTab3(), + defaultTitle: 'Account', + ); + break; + } + return null; + }, + ), + ), + ); + } +} + +class ExitButton extends StatelessWidget { + const ExitButton(); + + @override + Widget build(BuildContext context) { + return CupertinoButton( + padding: EdgeInsets.zero, + child: const Tooltip( + message: 'Back', + child: Text('Exit'), + excludeFromSemantics: true, + ), + onPressed: () { + // The demo is on the root navigator. + Navigator.of(context, rootNavigator: true).pop(); + }, + ); + } +} + +final Widget trailingButtons = Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoDemoDocumentationButton(CupertinoNavigationDemo.routeName), + const Padding(padding: EdgeInsets.only(left: 8.0)), + const ExitButton(), + ], +); + +class CupertinoDemoTab1 extends StatelessWidget { + const CupertinoDemoTab1({this.colorItems, this.colorNameItems}); + + final List colorItems; + final List colorNameItems; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: CustomScrollView( + semanticChildCount: _kChildCount, + slivers: [ + CupertinoSliverNavigationBar( + trailing: trailingButtons, + ), + SliverPadding( + // Top media padding consumed by CupertinoSliverNavigationBar. + // Left/Right media padding consumed by Tab1RowItem. + padding: MediaQuery.of(context).removePadding( + removeTop: true, + removeLeft: true, + removeRight: true, + ).padding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return Tab1RowItem( + index: index, + lastItem: index == _kChildCount - 1, + color: colorItems[index], + colorName: colorNameItems[index], + ); + }, + childCount: _kChildCount, + ), + ), + ), + ], + ), + ); + } +} + +class Tab1RowItem extends StatelessWidget { + const Tab1RowItem({this.index, this.lastItem, this.color, this.colorName}); + + final int index; + final bool lastItem; + final Color color; + final String colorName; + + @override + Widget build(BuildContext context) { + final Widget row = GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.of(context).push(CupertinoPageRoute( + title: colorName, + builder: (BuildContext context) => Tab1ItemPage( + color: color, + colorName: colorName, + index: index, + ), + )); + }, + child: SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.only(left: 16.0, top: 8.0, bottom: 8.0, right: 8.0), + child: Row( + children: [ + Container( + height: 60.0, + width: 60.0, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(8.0), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(colorName), + const Padding(padding: EdgeInsets.only(top: 8.0)), + const Text( + 'Buy this cool color', + style: TextStyle( + color: Color(0xFF8E8E93), + fontSize: 13.0, + fontWeight: FontWeight.w300, + ), + ), + ], + ), + ), + ), + CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.plus_circled, + semanticLabel: 'Add', + ), + onPressed: () { }, + ), + CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.share, + semanticLabel: 'Share', + ), + onPressed: () { }, + ), + ], + ), + ), + ), + ); + + if (lastItem) { + return row; + } + + return Column( + children: [ + row, + Container( + height: 1.0, + color: const Color(0xFFD9D9D9), + ), + ], + ); + } +} + +class Tab1ItemPage extends StatefulWidget { + const Tab1ItemPage({this.color, this.colorName, this.index}); + + final Color color; + final String colorName; + final int index; + + @override + State createState() => Tab1ItemPageState(); +} + +class Tab1ItemPageState extends State { + @override + void initState() { + super.initState(); + relatedColors = List.generate(10, (int index) { + final math.Random random = math.Random(); + return Color.fromARGB( + 255, + (widget.color.red + random.nextInt(100) - 50).clamp(0, 255), + (widget.color.green + random.nextInt(100) - 50).clamp(0, 255), + (widget.color.blue + random.nextInt(100) - 50).clamp(0, 255), + ); + }); + } + + List relatedColors; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + trailing: ExitButton(), + ), + child: SafeArea( + top: false, + bottom: false, + child: ListView( + children: [ + const Padding(padding: EdgeInsets.only(top: 16.0)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + height: 128.0, + width: 128.0, + decoration: BoxDecoration( + color: widget.color, + borderRadius: BorderRadius.circular(24.0), + ), + ), + const Padding(padding: EdgeInsets.only(left: 18.0)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.colorName, + style: const TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold), + ), + const Padding(padding: EdgeInsets.only(top: 6.0)), + Text( + 'Item number ${widget.index}', + style: const TextStyle( + color: Color(0xFF8E8E93), + fontSize: 16.0, + fontWeight: FontWeight.w100, + ), + ), + const Padding(padding: EdgeInsets.only(top: 20.0)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CupertinoButton.filled( + minSize: 30.0, + padding: const EdgeInsets.symmetric(horizontal: 24.0), + borderRadius: BorderRadius.circular(32.0), + child: const Text( + 'GET', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w700, + letterSpacing: -0.28, + ), + ), + onPressed: () { }, + ), + CupertinoButton.filled( + minSize: 30.0, + padding: EdgeInsets.zero, + borderRadius: BorderRadius.circular(32.0), + child: const Icon(CupertinoIcons.ellipsis), + onPressed: () { }, + ), + ], + ), + ], + ), + ), + ], + ), + ), + const Padding( + padding: EdgeInsets.only(left: 16.0, top: 28.0, bottom: 8.0), + child: Text( + 'USERS ALSO LIKED', + style: TextStyle( + color: Color(0xFF646464), + letterSpacing: -0.60, + fontSize: 15.0, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox( + height: 200.0, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: 10, + itemExtent: 160.0, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + color: relatedColors[index], + ), + child: Center( + child: CupertinoButton( + child: const Icon( + CupertinoIcons.plus_circled, + color: CupertinoColors.white, + size: 36.0, + ), + onPressed: () { }, + ), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} + +class CupertinoDemoTab2 extends StatelessWidget { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + trailing: trailingButtons, + ), + child: CupertinoScrollbar( + child: ListView( + children: [ + Tab2Header(), + ...buildTab2Conversation(), + ], + ), + ), + ); + } +} + +class Tab2Header extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: SafeArea( + top: false, + bottom: false, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: const BoxDecoration( + color: Color(0xFFE5E5E5), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [ + Text( + 'SUPPORT TICKET', + style: TextStyle( + color: Color(0xFF646464), + letterSpacing: -0.9, + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + Text( + 'Show More', + style: TextStyle( + color: Color(0xFF646464), + letterSpacing: -0.6, + fontSize: 12.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + Container( + decoration: const BoxDecoration( + color: Color(0xFFF3F3F3), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Product or product packaging damaged during transit', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + letterSpacing: -0.46, + ), + ), + const Padding(padding: EdgeInsets.only(top: 16.0)), + const Text( + 'REVIEWERS', + style: TextStyle( + color: Color(0xFF646464), + fontSize: 12.0, + letterSpacing: -0.6, + fontWeight: FontWeight.w500, + ), + ), + const Padding(padding: EdgeInsets.only(top: 8.0)), + Row( + children: [ + Container( + width: 44.0, + height: 44.0, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'people/square/trevor.png', + package: _kGalleryAssetsPackage, + ), + ), + shape: BoxShape.circle, + ), + ), + const Padding(padding: EdgeInsets.only(left: 8.0)), + Container( + width: 44.0, + height: 44.0, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'people/square/sandra.png', + package: _kGalleryAssetsPackage, + ), + ), + shape: BoxShape.circle, + ), + ), + const Padding(padding: EdgeInsets.only(left: 2.0)), + const Icon( + CupertinoIcons.check_mark_circled, + color: Color(0xFF646464), + size: 20.0, + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +enum Tab2ConversationBubbleColor { + blue, + gray, +} + +class Tab2ConversationBubble extends StatelessWidget { + const Tab2ConversationBubble({this.text, this.color}); + + final String text; + final Tab2ConversationBubbleColor color; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(18.0)), + color: color == Tab2ConversationBubbleColor.blue + ? CupertinoColors.activeBlue + : CupertinoColors.lightBackgroundGray, + ), + margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 10.0), + child: Text( + text, + style: TextStyle( + color: color == Tab2ConversationBubbleColor.blue + ? CupertinoColors.white + : CupertinoColors.black, + letterSpacing: -0.4, + fontSize: 15.0, + fontWeight: FontWeight.w400, + ), + ), + ); + } +} + +class Tab2ConversationAvatar extends StatelessWidget { + const Tab2ConversationAvatar({this.text, this.color}); + + final String text; + final Color color; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: FractionalOffset.topCenter, + end: FractionalOffset.bottomCenter, + colors: [ + color, + Color.fromARGB( + color.alpha, + (color.red - 60).clamp(0, 255), + (color.green - 60).clamp(0, 255), + (color.blue - 60).clamp(0, 255), + ), + ], + ), + ), + margin: const EdgeInsets.only(left: 8.0, bottom: 8.0), + padding: const EdgeInsets.all(12.0), + child: Text( + text, + style: const TextStyle( + color: CupertinoColors.white, + fontSize: 13.0, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} + +class Tab2ConversationRow extends StatelessWidget { + const Tab2ConversationRow({this.avatar, this.text}); + + final Tab2ConversationAvatar avatar; + final String text; + + @override + Widget build(BuildContext context) { + final List children = []; + if (avatar != null) + children.add(avatar); + + final bool isSelf = avatar == null; + children.add( + Tab2ConversationBubble( + text: text, + color: isSelf + ? Tab2ConversationBubbleColor.blue + : Tab2ConversationBubbleColor.gray, + ), + ); + return SafeArea( + child: Row( + mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: isSelf ? CrossAxisAlignment.center : CrossAxisAlignment.end, + children: children, + ), + ); + } +} + +List buildTab2Conversation() { + return [ + const Tab2ConversationRow( + text: "My Xanadu doesn't look right", + ), + const Tab2ConversationRow( + avatar: Tab2ConversationAvatar( + text: 'KL', + color: Color(0xFFFD5015), + ), + text: "We'll rush you a new one.\nIt's gonna be incredible", + ), + const Tab2ConversationRow( + text: 'Awesome thanks!', + ), + const Tab2ConversationRow( + avatar: Tab2ConversationAvatar( + text: 'SJ', + color: Color(0xFF34CAD6), + ), + text: "We'll send you our\nnewest Labrador too!", + ), + const Tab2ConversationRow( + text: 'Yay', + ), + const Tab2ConversationRow( + avatar: Tab2ConversationAvatar( + text: 'KL', + color: Color(0xFFFD5015), + ), + text: "Actually there's one more thing...", + ), + const Tab2ConversationRow( + text: "What's that?", + ), + ]; +} + +class CupertinoDemoTab3 extends StatelessWidget { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + trailing: trailingButtons, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: CupertinoTheme.of(context).brightness == Brightness.light + ? CupertinoColors.extraLightBackgroundGray + : CupertinoColors.darkBackgroundGray, + ), + child: ListView( + children: [ + const Padding(padding: EdgeInsets.only(top: 32.0)), + GestureDetector( + onTap: () { + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) => Tab3Dialog(), + ), + ); + }, + child: Container( + decoration: BoxDecoration( + color: CupertinoTheme.of(context).scaffoldBackgroundColor, + border: const Border( + top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), + bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), + ), + ), + height: 44.0, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: SafeArea( + top: false, + bottom: false, + child: Row( + children: [ + Text( + 'Sign in', + style: TextStyle(color: CupertinoTheme.of(context).primaryColor), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class Tab3Dialog extends StatelessWidget { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + leading: CupertinoButton( + child: const Text('Cancel'), + padding: EdgeInsets.zero, + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + CupertinoIcons.profile_circled, + size: 160.0, + color: Color(0xFF646464), + ), + const Padding(padding: EdgeInsets.only(top: 18.0)), + CupertinoButton.filled( + child: const Text('Sign in'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_picker_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_picker_demo.dart new file mode 100644 index 000000000..3bcc00f1d --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_picker_demo.dart @@ -0,0 +1,275 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:flutter/cupertino.dart'; +import 'package:intl/intl.dart'; + +import '../../gallery/demo.dart'; +import 'cupertino_navigation_demo.dart' show coolColorNames; + +const double _kPickerSheetHeight = 216.0; +const double _kPickerItemHeight = 32.0; + +class CupertinoPickerDemo extends StatefulWidget { + static const String routeName = '/cupertino/picker'; + + @override + _CupertinoPickerDemoState createState() => _CupertinoPickerDemoState(); +} + +class _CupertinoPickerDemoState extends State { + int _selectedColorIndex = 0; + + Duration timer = const Duration(); + + // Value that is shown in the date picker in date mode. + DateTime date = DateTime.now(); + + // Value that is shown in the date picker in time mode. + DateTime time = DateTime.now(); + + // Value that is shown in the date picker in dateAndTime mode. + DateTime dateTime = DateTime.now(); + + Widget _buildMenu(List children) { + return Container( + decoration: BoxDecoration( + color: CupertinoTheme.of(context).scaffoldBackgroundColor, + border: const Border( + top: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), + bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), + ), + ), + height: 44.0, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SafeArea( + top: false, + bottom: false, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: children, + ), + ), + ), + ); + } + + Widget _buildBottomPicker(Widget picker) { + return Container( + height: _kPickerSheetHeight, + padding: const EdgeInsets.only(top: 6.0), + color: CupertinoColors.white, + child: DefaultTextStyle( + style: const TextStyle( + color: CupertinoColors.black, + fontSize: 22.0, + ), + child: GestureDetector( + // Blocks taps from propagating to the modal sheet and popping. + onTap: () { }, + child: SafeArea( + top: false, + child: picker, + ), + ), + ), + ); + } + + Widget _buildColorPicker(BuildContext context) { + final FixedExtentScrollController scrollController = + FixedExtentScrollController(initialItem: _selectedColorIndex); + + return GestureDetector( + onTap: () async { + await showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return _buildBottomPicker( + CupertinoPicker( + scrollController: scrollController, + itemExtent: _kPickerItemHeight, + backgroundColor: CupertinoColors.white, + onSelectedItemChanged: (int index) { + setState(() => _selectedColorIndex = index); + }, + children: List.generate(coolColorNames.length, (int index) { + return Center( + child: Text(coolColorNames[index]), + ); + }), + ), + ); + }, + ); + }, + child: _buildMenu( + [ + const Text('Favorite Color'), + Text( + coolColorNames[_selectedColorIndex], + style: const TextStyle( + color: CupertinoColors.inactiveGray + ), + ), + ], + ), + ); + } + + Widget _buildCountdownTimerPicker(BuildContext context) { + return GestureDetector( + onTap: () { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return _buildBottomPicker( + CupertinoTimerPicker( + initialTimerDuration: timer, + onTimerDurationChanged: (Duration newTimer) { + setState(() => timer = newTimer); + }, + ), + ); + }, + ); + }, + child: _buildMenu( + [ + const Text('Countdown Timer'), + Text( + '${timer.inHours}:' + '${(timer.inMinutes % 60).toString().padLeft(2,'0')}:' + '${(timer.inSeconds % 60).toString().padLeft(2,'0')}', + style: const TextStyle(color: CupertinoColors.inactiveGray), + ), + ], + ), + ); + } + + Widget _buildDatePicker(BuildContext context) { + return GestureDetector( + onTap: () { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return _buildBottomPicker( + CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: date, + onDateTimeChanged: (DateTime newDateTime) { + setState(() => date = newDateTime); + }, + ), + ); + }, + ); + }, + child: _buildMenu( + [ + const Text('Date'), + Text( + DateFormat.yMMMMd().format(date), + style: const TextStyle(color: CupertinoColors.inactiveGray), + ), + ] + ), + ); + } + + Widget _buildTimePicker(BuildContext context) { + return GestureDetector( + onTap: () { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return _buildBottomPicker( + CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime: time, + onDateTimeChanged: (DateTime newDateTime) { + setState(() => time = newDateTime); + }, + ), + ); + }, + ); + }, + child: _buildMenu( + [ + const Text('Time'), + Text( + DateFormat.jm().format(time), + style: const TextStyle(color: CupertinoColors.inactiveGray), + ), + ], + ), + ); + } + + Widget _buildDateAndTimePicker(BuildContext context) { + return GestureDetector( + onTap: () { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return _buildBottomPicker( + CupertinoDatePicker( + mode: CupertinoDatePickerMode.dateAndTime, + initialDateTime: dateTime, + onDateTimeChanged: (DateTime newDateTime) { + setState(() => dateTime = newDateTime); + }, + ), + ); + }, + ); + }, + child: _buildMenu( + [ + const Text('Date and Time'), + Text( + DateFormat.yMMMd().add_jm().format(dateTime), + style: const TextStyle(color: CupertinoColors.inactiveGray), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Picker'), + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoPickerDemo.routeName), + ), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: DecoratedBox( + decoration: BoxDecoration( + color: CupertinoTheme.of(context).brightness == Brightness.light + ? CupertinoColors.extraLightBackgroundGray + : CupertinoColors.darkBackgroundGray, + ), + child: ListView( + children: [ + const Padding(padding: EdgeInsets.only(top: 32.0)), + _buildColorPicker(context), + _buildCountdownTimerPicker(context), + _buildDatePicker(context), + _buildTimePicker(context), + _buildDateAndTimePicker(context), + ], + ), + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_refresh_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_refresh_demo.dart new file mode 100644 index 000000000..6fa26d03d --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_refresh_demo.dart @@ -0,0 +1,244 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'dart:async'; +import 'dart:math' show Random; + +import 'package:flutter/cupertino.dart'; + +import '../../gallery/demo.dart'; + +class CupertinoRefreshControlDemo extends StatefulWidget { + static const String routeName = '/cupertino/refresh'; + + @override + _CupertinoRefreshControlDemoState createState() => _CupertinoRefreshControlDemoState(); +} + +class _CupertinoRefreshControlDemoState extends State { + List> randomizedContacts; + + @override + void initState() { + super.initState(); + repopulateList(); + } + + void repopulateList() { + final Random random = Random(); + randomizedContacts = List>.generate( + 100, + (int index) { + return contacts[random.nextInt(contacts.length)] + // Randomly adds a telephone icon next to the contact or not. + ..add(random.nextBool().toString()); + }, + ); + } + + @override + Widget build(BuildContext context) { + return DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: CupertinoPageScaffold( + child: DecoratedBox( + decoration: BoxDecoration( + color: CupertinoTheme.of(context).brightness == Brightness.light + ? CupertinoColors.extraLightBackgroundGray + : CupertinoColors.darkBackgroundGray, + ), + child: CustomScrollView( + // If left unspecified, the [CustomScrollView] appends an + // [AlwaysScrollableScrollPhysics]. Behind the scene, the ScrollableState + // will attach that [AlwaysScrollableScrollPhysics] to the output of + // [ScrollConfiguration.of] which will be a [ClampingScrollPhysics] + // on Android. + // To demonstrate the iOS behavior in this demo and to ensure that the list + // always scrolls, we specifically use a [BouncingScrollPhysics] combined + // with a [AlwaysScrollableScrollPhysics] + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + CupertinoSliverNavigationBar( + largeTitle: const Text('Refresh'), + // We're specifying a back label here because the previous page + // is a Material page. CupertinoPageRoutes could auto-populate + // these back labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoRefreshControlDemo.routeName), + ), + CupertinoSliverRefreshControl( + onRefresh: () { + return Future.delayed(const Duration(seconds: 2)) + ..then((_) { + if (mounted) { + setState(() => repopulateList()); + } + }); + }, + ), + SliverSafeArea( + top: false, // Top safe area is consumed by the navigation bar. + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return _ListItem( + name: randomizedContacts[index][0], + place: randomizedContacts[index][1], + date: randomizedContacts[index][2], + called: randomizedContacts[index][3] == 'true', + ); + }, + childCount: 20, + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +List> contacts = >[ + ['George Washington', 'Westmoreland County', ' 4/30/1789'], + ['John Adams', 'Braintree', ' 3/4/1797'], + ['Thomas Jefferson', 'Shadwell', ' 3/4/1801'], + ['James Madison', 'Port Conway', ' 3/4/1809'], + ['James Monroe', 'Monroe Hall', ' 3/4/1817'], + ['Andrew Jackson', 'Waxhaws Region South/North', ' 3/4/1829'], + ['John Quincy Adams', 'Braintree', ' 3/4/1825'], + ['William Henry Harrison', 'Charles City County', ' 3/4/1841'], + ['Martin Van Buren', 'Kinderhook New', ' 3/4/1837'], + ['Zachary Taylor', 'Barboursville', ' 3/4/1849'], + ['John Tyler', 'Charles City County', ' 4/4/1841'], + ['James Buchanan', 'Cove Gap', ' 3/4/1857'], + ['James K. Polk', 'Pineville North', ' 3/4/1845'], + ['Millard Fillmore', 'Summerhill New', '7/9/1850'], + ['Franklin Pierce', 'Hillsborough New', ' 3/4/1853'], + ['Andrew Johnson', 'Raleigh North', ' 4/15/1865'], + ['Abraham Lincoln', 'Sinking Spring', ' 3/4/1861'], + ['Ulysses S. Grant', 'Point Pleasant', ' 3/4/1869'], + ['Rutherford B. Hayes', 'Delaware', ' 3/4/1877'], + ['Chester A. Arthur', 'Fairfield', ' 9/19/1881'], + ['James A. Garfield', 'Moreland Hills', ' 3/4/1881'], + ['Benjamin Harrison', 'North Bend', ' 3/4/1889'], + ['Grover Cleveland', 'Caldwell New', ' 3/4/1885'], + ['William McKinley', 'Niles', ' 3/4/1897'], + ['Woodrow Wilson', 'Staunton', ' 3/4/1913'], + ['William H. Taft', 'Cincinnati', ' 3/4/1909'], + ['Theodore Roosevelt', 'New York City New', ' 9/14/1901'], + ['Warren G. Harding', 'Blooming Grove', ' 3/4/1921'], + ['Calvin Coolidge', 'Plymouth', '8/2/1923'], + ['Herbert Hoover', 'West Branch', ' 3/4/1929'], + ['Franklin D. Roosevelt', 'Hyde Park New', ' 3/4/1933'], + ['Harry S. Truman', 'Lamar', ' 4/12/1945'], + ['Dwight D. Eisenhower', 'Denison', ' 1/20/1953'], + ['Lyndon B. Johnson', 'Stonewall', '11/22/1963'], + ['Ronald Reagan', 'Tampico', ' 1/20/1981'], + ['Richard Nixon', 'Yorba Linda', ' 1/20/1969'], + ['Gerald Ford', 'Omaha', 'August 9/1974'], + ['John F. Kennedy', 'Brookline', ' 1/20/1961'], + ['George H. W. Bush', 'Milton', ' 1/20/1989'], + ['Jimmy Carter', 'Plains', ' 1/20/1977'], + ['George W. Bush', 'New Haven', ' 1/20, 2001'], + ['Bill Clinton', 'Hope', ' 1/20/1993'], + ['Barack Obama', 'Honolulu', ' 1/20/2009'], + ['Donald J. Trump', 'New York City', ' 1/20/2017'], +]; + +class _ListItem extends StatelessWidget { + const _ListItem({ + this.name, + this.place, + this.date, + this.called, + }); + + final String name; + final String place; + final String date; + final bool called; + + @override + Widget build(BuildContext context) { + return Container( + color: CupertinoTheme.of(context).scaffoldBackgroundColor, + height: 60.0, + padding: const EdgeInsets.only(top: 9.0), + child: Row( + children: [ + Container( + width: 38.0, + child: called + ? const Align( + alignment: Alignment.topCenter, + child: Icon( + CupertinoIcons.phone_solid, + color: CupertinoColors.inactiveGray, + size: 18.0, + ), + ) + : null, + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFBCBBC1), width: 0.0), + ), + ), + padding: const EdgeInsets.only(left: 1.0, bottom: 9.0, right: 10.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w600, + letterSpacing: -0.18, + ), + ), + Text( + place, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 15.0, + letterSpacing: -0.24, + color: CupertinoColors.inactiveGray, + ), + ), + ], + ), + ), + Text( + date, + style: const TextStyle( + color: CupertinoColors.inactiveGray, + fontSize: 15.0, + letterSpacing: -0.41, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 9.0), + child: Icon( + CupertinoIcons.info, + color: CupertinoTheme.of(context).primaryColor, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_segmented_control_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_segmented_control_demo.dart new file mode 100644 index 000000000..d63cae4dc --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_segmented_control_demo.dart @@ -0,0 +1,127 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../gallery/demo.dart'; + +const Color _kKeyUmbraOpacity = Color(0x33000000); // alpha = 0.2 +const Color _kKeyPenumbraOpacity = Color(0x24000000); // alpha = 0.14 +const Color _kAmbientShadowOpacity = Color(0x1F000000); // alpha = 0.12 + +class CupertinoSegmentedControlDemo extends StatefulWidget { + static const String routeName = 'cupertino/segmented_control'; + + @override + _CupertinoSegmentedControlDemoState createState() => _CupertinoSegmentedControlDemoState(); +} + +class _CupertinoSegmentedControlDemoState extends State { + final Map children = const { + 0: Text('Midnight'), + 1: Text('Viridian'), + 2: Text('Cerulean'), + }; + + final Map icons = const { + 0: Center( + child: FlutterLogo( + colors: Colors.indigo, + size: 200.0, + ), + ), + 1: Center( + child: FlutterLogo( + colors: Colors.teal, + size: 200.0, + ), + ), + 2: Center( + child: FlutterLogo( + colors: Colors.cyan, + size: 200.0, + ), + ), + }; + + int sharedValue = 0; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Segmented Control'), + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoSegmentedControlDemo.routeName), + ), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: SafeArea( + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + ), + SizedBox( + width: 500.0, + child: CupertinoSegmentedControl( + children: children, + onValueChanged: (int newValue) { + setState(() { + sharedValue = newValue; + }); + }, + groupValue: sharedValue, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 32.0, + horizontal: 16.0, + ), + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 64.0, + horizontal: 16.0, + ), + decoration: BoxDecoration( + color: CupertinoTheme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(3.0), + boxShadow: const [ + BoxShadow( + offset: Offset(0.0, 3.0), + blurRadius: 5.0, + spreadRadius: -1.0, + color: _kKeyUmbraOpacity, + ), + BoxShadow( + offset: Offset(0.0, 6.0), + blurRadius: 10.0, + spreadRadius: 0.0, + color: _kKeyPenumbraOpacity, + ), + BoxShadow( + offset: Offset(0.0, 1.0), + blurRadius: 18.0, + spreadRadius: 0.0, + color: _kAmbientShadowOpacity, + ), + ], + ), + child: icons[sharedValue], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_slider_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_slider_demo.dart new file mode 100644 index 000000000..bce7687f3 --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_slider_demo.dart @@ -0,0 +1,78 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import '../../gallery/demo.dart'; + +class CupertinoSliderDemo extends StatefulWidget { + static const String routeName = '/cupertino/slider'; + + @override + _CupertinoSliderDemoState createState() => _CupertinoSliderDemoState(); +} + +class _CupertinoSliderDemoState extends State { + double _value = 25.0; + double _discreteValue = 20.0; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Sliders'), + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoSliderDemo.routeName), + ), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: SafeArea( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoSlider( + value: _value, + min: 0.0, + max: 100.0, + onChanged: (double value) { + setState(() { + _value = value; + }); + }, + ), + Text('Cupertino Continuous: ${_value.toStringAsFixed(1)}'), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoSlider( + value: _discreteValue, + min: 0.0, + max: 100.0, + divisions: 5, + onChanged: (double value) { + setState(() { + _discreteValue = value; + }); + }, + ), + Text('Cupertino Discrete: $_discreteValue'), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_switch_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_switch_demo.dart new file mode 100644 index 000000000..2e5b6d9a7 --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_switch_demo.dart @@ -0,0 +1,91 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import '../../gallery/demo.dart'; + +class CupertinoSwitchDemo extends StatefulWidget { + static const String routeName = '/cupertino/switch'; + + @override + _CupertinoSwitchDemoState createState() => _CupertinoSwitchDemoState(); +} + +class _CupertinoSwitchDemoState extends State { + + bool _switchValue = false; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Switch'), + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + trailing: CupertinoDemoDocumentationButton(CupertinoSwitchDemo.routeName), + ), + child: DefaultTextStyle( + style: CupertinoTheme.of(context).textTheme.textStyle, + child: SafeArea( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Semantics( + container: true, + child: Column( + children: [ + CupertinoSwitch( + value: _switchValue, + onChanged: (bool value) { + setState(() { + _switchValue = value; + }); + }, + ), + Text( + "Enabled - ${_switchValue ? "On" : "Off"}" + ), + ], + ), + ), + Semantics( + container: true, + child: Column( + children: const [ + CupertinoSwitch( + value: true, + onChanged: null, + ), + Text( + 'Disabled - On' + ), + ], + ), + ), + Semantics( + container: true, + child: Column( + children: const [ + CupertinoSwitch( + value: false, + onChanged: null, + ), + Text( + 'Disabled - Off' + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/cupertino/cupertino_text_field_demo.dart b/web/gallery/lib/demo/cupertino/cupertino_text_field_demo.dart new file mode 100644 index 000000000..fc36ccecc --- /dev/null +++ b/web/gallery/lib/demo/cupertino/cupertino_text_field_demo.dart @@ -0,0 +1,194 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +class CupertinoTextFieldDemo extends StatefulWidget { + static const String routeName = '/cupertino/text_fields'; + + @override + _CupertinoTextFieldDemoState createState() { + return _CupertinoTextFieldDemoState(); + } +} + +class _CupertinoTextFieldDemoState extends State { + TextEditingController _chatTextController; + TextEditingController _locationTextController; + + @override + void initState() { + super.initState(); + _chatTextController = TextEditingController(); + _locationTextController = TextEditingController(text: 'Montreal, Canada'); + } + + Widget _buildChatTextField() { + return CupertinoTextField( + controller: _chatTextController, + textCapitalization: TextCapitalization.sentences, + placeholder: 'Text Message', + decoration: BoxDecoration( + border: Border.all( + width: 0.0, + color: CupertinoColors.inactiveGray, + ), + borderRadius: BorderRadius.circular(15.0), + ), + maxLines: null, + keyboardType: TextInputType.multiline, + prefix: const Padding(padding: EdgeInsets.symmetric(horizontal: 4.0)), + suffix: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: CupertinoButton( + color: CupertinoColors.activeGreen, + minSize: 0.0, + child: const Icon( + CupertinoIcons.up_arrow, + size: 21.0, + color: CupertinoColors.white, + ), + padding: const EdgeInsets.all(2.0), + borderRadius: BorderRadius.circular(15.0), + onPressed: ()=> setState(()=> _chatTextController.clear()), + ), + ), + autofocus: true, + suffixMode: OverlayVisibilityMode.editing, + onSubmitted: (String text)=> setState(()=> _chatTextController.clear()), + ); + } + + Widget _buildNameField() { + return const CupertinoTextField( + prefix: Icon( + CupertinoIcons.person_solid, + color: CupertinoColors.lightBackgroundGray, + size: 28.0, + ), + padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), + clearButtonMode: OverlayVisibilityMode.editing, + textCapitalization: TextCapitalization.words, + autocorrect: false, + decoration: BoxDecoration( + border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)), + ), + placeholder: 'Name', + ); + } + + Widget _buildEmailField() { + return const CupertinoTextField( + prefix: Icon( + CupertinoIcons.mail_solid, + color: CupertinoColors.lightBackgroundGray, + size: 28.0, + ), + padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), + clearButtonMode: OverlayVisibilityMode.editing, + keyboardType: TextInputType.emailAddress, + autocorrect: false, + decoration: BoxDecoration( + border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)), + ), + placeholder: 'Email', + ); + } + + Widget _buildLocationField() { + return CupertinoTextField( + controller: _locationTextController, + prefix: const Icon( + CupertinoIcons.location_solid, + color: CupertinoColors.lightBackgroundGray, + size: 28.0, + ), + padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), + clearButtonMode: OverlayVisibilityMode.editing, + textCapitalization: TextCapitalization.words, + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)), + ), + placeholder: 'Location', + ); + } + + Widget _buildPinField() { + return const CupertinoTextField( + prefix: Icon( + CupertinoIcons.padlock_solid, + color: CupertinoColors.lightBackgroundGray, + size: 28.0, + ), + padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), + clearButtonMode: OverlayVisibilityMode.editing, + keyboardType: TextInputType.number, + autocorrect: false, + obscureText: true, + decoration: BoxDecoration( + border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)), + ), + placeholder: 'Create a PIN', + ); + } + + Widget _buildTagsField() { + return CupertinoTextField( + controller: TextEditingController(text: 'colleague, reading club'), + prefix: const Icon( + CupertinoIcons.tags_solid, + color: CupertinoColors.lightBackgroundGray, + size: 28.0, + ), + enabled: false, + padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)), + ), + ); + } + + @override + Widget build(BuildContext context) { + return DefaultTextStyle( + style: const TextStyle( + fontFamily: '.SF UI Text', + inherit: false, + fontSize: 17.0, + color: CupertinoColors.black, + ), + child: CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + // We're specifying a back label here because the previous page is a + // Material page. CupertinoPageRoutes could auto-populate these back + // labels. + previousPageTitle: 'Cupertino', + middle: Text('Text Fields'), + ), + child: CupertinoScrollbar( + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 32.0, horizontal: 16.0), + child: Column( + children: [ + _buildNameField(), + _buildEmailField(), + _buildLocationField(), + _buildPinField(), + _buildTagsField(), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 32.0, horizontal: 16.0), + child: _buildChatTextField(), + ), + ], + ), + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/fortnightly/README.md b/web/gallery/lib/demo/fortnightly/README.md new file mode 100644 index 000000000..45c220334 --- /dev/null +++ b/web/gallery/lib/demo/fortnightly/README.md @@ -0,0 +1,52 @@ +# Fortnightly + +A Flutter sample app based on the Material study Fortnightly (a hypothetical, online newspaper.) It +showcases print-quality, custom typography, Material Theming, and text-heavy UI design and layout. + +For info on the Fortnightly Material Study, see: https://material.io/design/material-studies/fortnightly.html + +## Goals for this sample + +* Help you understand how to customize and layout text. +* Provide you with example code for + * Text + * A short app bar (the menu button top left.) + * Avatar images + +## Widgets / APIs + +* BeveledRectangleBorder +* BoxConstraints on Container +* CircleAvatar +* ExactAssetImage +* Fonts +* SafeArea +* Stack +* SingleChildScrollView +* Text +* TextStyle +* TextTheme + +## Notice + +* Theming is passed as a parameter in the constructor of `MaterialApp` (`theme:`). +* `SafeArea` adds padding around notches and virtual home buttons on screens that have them (like + iPhone X+). Here, it protects the `ShortAppBar` from overlapping with the status bar (time) + and makes sure the bottom of the newspaper article has padding beneath it if necessary. +* The entire newspaper article is wrapped in a `SingleChildScrollView` widget which ensures that the + entire article can be viewed no matter what the screen's size or orientation is. +* The `Text` widget with text ' ¬ ' has a `TextStyle` that changes one parameter of an inherited + `TextStyle` using `.apply()``. +* The `Text` widget with text 'Connor Eghan' has a `TextStyle` created explicitly instead of + inheriting from theming. +* You can break up long strings in your source files by putting them on multiple lines. +* Fonts are imported with multiple files expressing their weights (Bold, Light, Medium, Regular) + but are accessed with a `FontWeight` value like `FontWeight.w800` for Merriweather-Bold.ttf. + +## Questions/issues + +If you have a general question about developing in Flutter, the best places to go are: + +* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev) +* [The Flutter Gitter channel](https://gitter.im/flutter/flutter) +* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter) diff --git a/web/gallery/lib/demo/fortnightly/fortnightly.dart b/web/gallery/lib/demo/fortnightly/fortnightly.dart new file mode 100644 index 000000000..f8dcde8a9 --- /dev/null +++ b/web/gallery/lib/demo/fortnightly/fortnightly.dart @@ -0,0 +1,236 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class FortnightlyDemo extends StatelessWidget { + static const String routeName = '/fortnightly'; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Fortnightly Demo', + theme: _fortnightlyTheme, + home: Scaffold( + body: Stack( + children: [ + FruitPage(), + SafeArea( + child: ShortAppBar( + onBackPressed: () { + Navigator.pop(context); + }, + ), + ), + ], + ), + ), + ); + } +} + +class ShortAppBar extends StatelessWidget { + const ShortAppBar({ this.onBackPressed }); + + final VoidCallback onBackPressed; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 96, + height: 50, + child: Material( + color: Theme.of(context).colorScheme.surface, + elevation: 4, + shape: const BeveledRectangleBorder( + borderRadius: BorderRadius.only(bottomRight: Radius.circular(22)), + ), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back), + tooltip: 'Back', + onPressed: onBackPressed, + ), + const SizedBox(width: 12), + Image.asset( + 'logos/fortnightly/fortnightly_logo.png', + package: 'flutter_gallery_assets', + ), + ], + ), + ), + ); + } +} + +class FruitPage extends StatelessWidget { + static final String paragraph1 = '''Have you ever held a quince? It\'s strange; + covered in a fuzz somewhere between peach skin and a spider web. And it\'s + hard as soft lumber. You\'d be forgiven for thinking it\'s veneered Larch-wood. + But inhale the aroma and you\'ll instantly know you have something wonderful. + Its scent can fill a room for days. And all this before you\'ve even cooked it. +'''.replaceAll('\n', ''); + + static final String paragraph2 = '''Pomegranates on the other hand have become + almost ubiquitous. You can find its juice in any bodega, Walmart, and even some + gas stations. But at what cost? The pomegranate juice craze of the aughts made + \"megafarmers\" Lynda and Stewart Resnick billions. Unfortunately, it takes a lot + of water to make that much pomegranate juice. Water the Resnicks get from their + majority stake in the Kern Water Bank. How did one family come to hold control + over water meant for the whole central valley of California? The story will shock you. +'''.replaceAll('\n', ''); + + @override + Widget build(BuildContext context) { + final TextTheme textTheme = Theme.of(context).primaryTextTheme; + + return SingleChildScrollView( + child: SafeArea( + top: false, + child: Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Container( + constraints: const BoxConstraints.expand(height: 248), + child: Image.asset( + 'food/fruits.png', + package: 'flutter_gallery_assets', + fit: BoxFit.fitWidth, + ), + ), + const SizedBox(height: 17), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + 'US', + style: textTheme.overline, + ), + Text( + ' ¬ ', + // TODO(larche): Replace textTheme.display3.color with a ColorScheme value when known. + style: textTheme.overline.apply(color: textTheme.display3.color), + ), + Text( + 'CULTURE', + style: textTheme.overline, + ), + ], + ), + const SizedBox(height: 10), + Text( + 'Quince for Wisdom, Persimmon for Luck, Pomegranate for Love', + style: textTheme.display1, + ), + const SizedBox(height: 10), + Text( + 'How these crazy fruits sweetened our hearts, relationships,' + 'and puffed pastries', + style: textTheme.body1, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + children: [ + CircleAvatar( + backgroundImage: ExactAssetImage( + 'people/square/trevor.png', + package: 'flutter_gallery_assets', + ), + radius: 20, + ), + const SizedBox(width: 12), + Text( + 'by', + style: textTheme.display3, + ), + const SizedBox(width: 4), + const Text( + 'Connor Eghan', + style: TextStyle( + fontFamily: 'Merriweather', + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + ), + Text( + '$paragraph1\n\n$paragraph2', + style: textTheme.body2, + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} + +final ThemeData _fortnightlyTheme = _buildFortnightlyTheme(); + +ThemeData _buildFortnightlyTheme() { + final ThemeData base = ThemeData.light(); + return base.copyWith( + primaryTextTheme: _buildTextTheme(base.primaryTextTheme), + scaffoldBackgroundColor: Colors.white, + ); +} + +TextTheme _buildTextTheme(TextTheme base) { + TextTheme theme = base.apply(bodyColor: Colors.black); + theme = theme.apply(displayColor: Colors.black); + + theme = theme.copyWith( + display1: base.display1.copyWith( + fontFamily: 'Merriweather', + fontStyle: FontStyle.italic, + fontSize: 28, + fontWeight: FontWeight.w800, + color: Colors.black, + height: .88, + ), + display3: base.display3.copyWith( + fontFamily: 'LibreFranklin', + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.black.withAlpha(153), + ), + headline: base.headline.copyWith(fontWeight: FontWeight.w500), + body1: base.body1.copyWith( + fontFamily: 'Merriweather', + fontSize: 14, + fontWeight: FontWeight.w300, + color: const Color(0xFF666666), + height: 1.11, + ), + body2: base.body2.copyWith( + fontFamily: 'Merriweather', + fontSize: 16, + fontWeight: FontWeight.w300, + color: const Color(0xFF666666), + height: 1.4, + letterSpacing: .25, + ), + overline: const TextStyle( + fontFamily: 'LibreFranklin', + fontSize: 10, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ); + + return theme; +} diff --git a/web/gallery/lib/demo/images_demo.dart b/web/gallery/lib/demo/images_demo.dart new file mode 100644 index 000000000..9863a2bc8 --- /dev/null +++ b/web/gallery/lib/demo/images_demo.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../gallery/demo.dart'; + +class ImagesDemo extends StatelessWidget { + static const String routeName = '/images'; + + @override + Widget build(BuildContext context) { + return TabbedComponentDemoScaffold( + title: 'Animated images', + demos: [ + ComponentDemoTabData( + tabName: 'WEBP', + description: '', + exampleCodeTag: 'animated_image', + demoWidget: Semantics( + label: 'Example of animated WEBP', + child: Image.asset( + 'animated_images/animated_flutter_stickers.webp', + package: 'flutter_gallery_assets', + ), + ), + ), + ComponentDemoTabData( + tabName: 'GIF', + description: '', + exampleCodeTag: 'animated_image', + demoWidget: Semantics( + label: 'Example of animated GIF', + child:Image.asset( + 'animated_images/animated_flutter_lgtm.gif', + package: 'flutter_gallery_assets', + ), + ), + ), + ], + ); + } +} diff --git a/web/gallery/lib/demo/material/backdrop_demo.dart b/web/gallery/lib/demo/material/backdrop_demo.dart index d81b21670..0d2c785c4 100644 --- a/web/gallery/lib/demo/material/backdrop_demo.dart +++ b/web/gallery/lib/demo/material/backdrop_demo.dart @@ -4,14 +4,14 @@ import 'dart:math' as math; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; // This demo displays one Category at a time. The backdrop show a list // of all of the categories and the selected category is displayed // (CategoryView) on top of the backdrop. class Category { - const Category({this.title, this.assets}); + const Category({ this.title, this.assets }); final String title; final List assets; @override @@ -95,49 +95,52 @@ const List allCategories = [ ]; class CategoryView extends StatelessWidget { - const CategoryView({Key key, this.category}) : super(key: key); + const CategoryView({ Key key, this.category }) : super(key: key); final Category category; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - return ListView( - key: PageStorageKey(category), - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 64.0, - ), - children: category.assets.map((String asset) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Card( - child: Container( - width: 144.0, - alignment: Alignment.center, - child: Column( - children: [ - Image.asset( - '$asset', - fit: BoxFit.contain, - ), - Container( - padding: const EdgeInsets.only(bottom: 16.0), - alignment: AlignmentDirectional.center, - child: Text( + return Scrollbar( + child: ListView( + key: PageStorageKey(category), + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 64.0, + ), + children: category.assets.map((String asset) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Container( + width: 144.0, + alignment: Alignment.center, + child: Column( + children: [ + Image.asset( asset, - style: theme.textTheme.caption, + package: 'flutter_gallery_assets', + fit: BoxFit.contain, ), - ), - ], + Container( + padding: const EdgeInsets.only(bottom: 16.0), + alignment: AlignmentDirectional.center, + child: Text( + asset, + style: theme.textTheme.caption, + ), + ), + ], + ), ), ), - ), - const SizedBox(height: 24.0), - ], - ); - }).toList(), + const SizedBox(height: 24.0), + ], + ); + }).toList(), + ) ); } } @@ -242,8 +245,7 @@ class BackdropDemo extends StatefulWidget { _BackdropDemoState createState() => _BackdropDemoState(); } -class _BackdropDemoState extends State - with SingleTickerProviderStateMixin { +class _BackdropDemoState extends State with SingleTickerProviderStateMixin { final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop'); AnimationController _controller; Category _category = allCategories[0]; @@ -273,8 +275,7 @@ class _BackdropDemoState extends State bool get _backdropPanelVisible { final AnimationStatus status = _controller.status; - return status == AnimationStatus.completed || - status == AnimationStatus.forward; + return status == AnimationStatus.completed || status == AnimationStatus.forward; } void _toggleBackdropPanelVisibility() { @@ -290,19 +291,17 @@ class _BackdropDemoState extends State // the user must either tap its heading or the backdrop's menu icon. void _handleDragUpdate(DragUpdateDetails details) { - if (_controller.isAnimating || - _controller.status == AnimationStatus.completed) return; + if (_controller.isAnimating || _controller.status == AnimationStatus.completed) + return; - _controller.value -= - details.primaryDelta / (_backdropHeight ?? details.primaryDelta); + _controller.value -= details.primaryDelta / (_backdropHeight ?? details.primaryDelta); } void _handleDragEnd(DragEndDetails details) { - if (_controller.isAnimating || - _controller.status == AnimationStatus.completed) return; + if (_controller.isAnimating || _controller.status == AnimationStatus.completed) + return; - final double flingVelocity = - details.velocity.pixelsPerSecond.dy / _backdropHeight; + final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight; if (flingVelocity < 0.0) _controller.fling(velocity: math.max(2.0, -flingVelocity)); else if (flingVelocity > 0.0) @@ -334,14 +333,15 @@ class _BackdropDemoState extends State ); final ThemeData theme = Theme.of(context); - final List backdropItems = - allCategories.map((Category category) { + final List backdropItems = allCategories.map((Category category) { final bool selected = category == _category; return Material( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4.0)), ), - color: selected ? Colors.white.withOpacity(0.25) : Colors.transparent, + color: selected + ? Colors.white.withOpacity(0.25) + : Colors.transparent, child: ListTile( title: Text(category.title), selected: selected, diff --git a/web/gallery/lib/demo/material/banner_demo.dart b/web/gallery/lib/demo/material/banner_demo.dart new file mode 100644 index 000000000..68af50df3 --- /dev/null +++ b/web/gallery/lib/demo/material/banner_demo.dart @@ -0,0 +1,109 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../../gallery/demo.dart'; + +enum BannerDemoAction { + reset, + showMultipleActions, + showLeading, +} + +class BannerDemo extends StatefulWidget { + const BannerDemo({ Key key }) : super(key: key); + + static const String routeName = '/material/banner'; + + @override + _BannerDemoState createState() => _BannerDemoState(); +} + +class _BannerDemoState extends State { + static const int _numItems = 20; + bool _displayBanner = true; + bool _showMultipleActions = true; + bool _showLeading = true; + + void handleDemoAction(BannerDemoAction action) { + setState(() { + switch (action) { + case BannerDemoAction.reset: + _displayBanner = true; + _showMultipleActions = true; + _showLeading = true; + break; + case BannerDemoAction.showMultipleActions: + _showMultipleActions = !_showMultipleActions; + break; + case BannerDemoAction.showLeading: + _showLeading = !_showLeading; + break; + } + }); + } + + @override + Widget build(BuildContext context) { + final Widget banner = MaterialBanner( + content: const Text('Your password was updated on your other device. Please sign in again.'), + leading: _showLeading ? const CircleAvatar(child: Icon(Icons.access_alarm)) : null, + actions: [ + FlatButton( + child: const Text('SIGN IN'), + onPressed: () { + setState(() { + _displayBanner = false; + }); + } + ), + if (_showMultipleActions) + FlatButton( + child: const Text('DISMISS'), + onPressed: () { + setState(() { + _displayBanner = false; + }); + } + ), + ], + ); + + return Scaffold( + appBar: AppBar( + title: const Text('Banner'), + actions: [ + MaterialDemoDocumentationButton(BannerDemo.routeName), + PopupMenuButton( + onSelected: handleDemoAction, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: BannerDemoAction.reset, + child: Text('Reset the banner'), + ), + const PopupMenuDivider(), + CheckedPopupMenuItem( + value: BannerDemoAction.showMultipleActions, + checked: _showMultipleActions, + child: const Text('Multiple actions'), + ), + CheckedPopupMenuItem( + value: BannerDemoAction.showLeading, + checked: _showLeading, + child: const Text('Leading icon'), + ), + ], + ), + ], + ), + body: ListView.builder(itemCount: _displayBanner ? _numItems + 1 : _numItems, itemBuilder: (BuildContext context, int index) { + if (index == 0 && _displayBanner) { + return banner; + } + return ListTile(title: Text('Item ${_displayBanner ? index : index + 1}'),); + }), + ); + } +} diff --git a/web/gallery/lib/demo/material/bottom_app_bar_demo.dart b/web/gallery/lib/demo/material/bottom_app_bar_demo.dart index 130436ab7..d3da0fb87 100644 --- a/web/gallery/lib/demo/material/bottom_app_bar_demo.dart +++ b/web/gallery/lib/demo/material/bottom_app_bar_demo.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -18,8 +18,7 @@ class BottomAppBarDemo extends StatefulWidget { // for bottom application bar. class _BottomAppBarDemoState extends State { - static final GlobalKey _scaffoldKey = - GlobalKey(); + static final GlobalKey _scaffoldKey = GlobalKey(); // FAB shape @@ -64,41 +63,35 @@ class _BottomAppBarDemoState extends State { // FAB Position - static const _ChoiceValue kFabEndDocked = - _ChoiceValue( + static const _ChoiceValue kFabEndDocked = _ChoiceValue( title: 'Attached - End', label: 'floating action button is docked at the end of the bottom app bar', value: FloatingActionButtonLocation.endDocked, ); - static const _ChoiceValue kFabCenterDocked = - _ChoiceValue( + static const _ChoiceValue kFabCenterDocked = _ChoiceValue( title: 'Attached - Center', - label: - 'floating action button is docked at the center of the bottom app bar', + label: 'floating action button is docked at the center of the bottom app bar', value: FloatingActionButtonLocation.centerDocked, ); - static const _ChoiceValue kFabEndFloat = - _ChoiceValue( + static const _ChoiceValue kFabEndFloat= _ChoiceValue( title: 'Free - End', label: 'floating action button floats above the end of the bottom app bar', value: FloatingActionButtonLocation.endFloat, ); - static const _ChoiceValue kFabCenterFloat = - _ChoiceValue( + static const _ChoiceValue kFabCenterFloat = _ChoiceValue( title: 'Free - Center', - label: - 'floating action button is floats above the center of the bottom app bar', + label: 'floating action button is floats above the center of the bottom app bar', value: FloatingActionButtonLocation.centerFloat, ); static void _showSnackbar() { const String text = - "When the Scaffold's floating action button location changes, " - 'the floating action button animates to its new position.' - 'The BottomAppBar adapts its shape appropriately.'; + "When the Scaffold's floating action button location changes, " + 'the floating action button animates to its new position.' + 'The BottomAppBar adapts its shape appropriately.'; _scaffoldKey.currentState.showSnackBar( const SnackBar(content: Text(text)), ); @@ -154,42 +147,45 @@ class _BottomAppBarDemoState extends State { actions: [ MaterialDemoDocumentationButton(BottomAppBarDemo.routeName), IconButton( - icon: const Icon(Icons.sentiment_very_satisfied, - semanticLabel: 'Update shape'), + icon: const Icon(Icons.sentiment_very_satisfied, semanticLabel: 'Update shape'), onPressed: () { setState(() { - _fabShape = - _fabShape == kCircularFab ? kDiamondFab : kCircularFab; + _fabShape = _fabShape == kCircularFab ? kDiamondFab : kCircularFab; }); }, ), ], ), - body: ListView( - padding: const EdgeInsets.only(bottom: 88.0), - children: [ - const _Heading('FAB Shape'), - _RadioItem(kCircularFab, _fabShape, _onFabShapeChanged), - _RadioItem(kDiamondFab, _fabShape, _onFabShapeChanged), - _RadioItem(kNoFab, _fabShape, _onFabShapeChanged), - const Divider(), - const _Heading('Notch'), - _RadioItem(kShowNotchTrue, _showNotch, _onShowNotchChanged), - _RadioItem(kShowNotchFalse, _showNotch, _onShowNotchChanged), - const Divider(), - const _Heading('FAB Position'), - _RadioItem( - kFabEndDocked, _fabLocation, _onFabLocationChanged), - _RadioItem( - kFabCenterDocked, _fabLocation, _onFabLocationChanged), - _RadioItem( - kFabEndFloat, _fabLocation, _onFabLocationChanged), - _RadioItem( - kFabCenterFloat, _fabLocation, _onFabLocationChanged), - const Divider(), - const _Heading('App bar color'), - _ColorsItem(kBabColors, _babColor, _onBabColorChanged), - ], + body: Scrollbar( + child: ListView( + padding: const EdgeInsets.only(bottom: 88.0), + children: [ + const _Heading('FAB Shape'), + + _RadioItem(kCircularFab, _fabShape, _onFabShapeChanged), + _RadioItem(kDiamondFab, _fabShape, _onFabShapeChanged), + _RadioItem(kNoFab, _fabShape, _onFabShapeChanged), + + const Divider(), + const _Heading('Notch'), + + _RadioItem(kShowNotchTrue, _showNotch, _onShowNotchChanged), + _RadioItem(kShowNotchFalse, _showNotch, _onShowNotchChanged), + + const Divider(), + const _Heading('FAB Position'), + + _RadioItem(kFabEndDocked, _fabLocation, _onFabLocationChanged), + _RadioItem(kFabCenterDocked, _fabLocation, _onFabLocationChanged), + _RadioItem(kFabEndFloat, _fabLocation, _onFabLocationChanged), + _RadioItem(kFabCenterFloat, _fabLocation, _onFabLocationChanged), + + const Divider(), + const _Heading('App bar color'), + + _ColorsItem(kBabColors, _babColor, _onBabColorChanged), + ], + ), ), floatingActionButton: _fabShape.value, floatingActionButtonLocation: _fabLocation.value, @@ -202,15 +198,18 @@ class _BottomAppBarDemoState extends State { } NotchedShape _selectNotch() { - if (!_showNotch.value) return null; - if (_fabShape == kCircularFab) return const CircularNotchedRectangle(); - if (_fabShape == kDiamondFab) return const _DiamondNotchedRectangle(); + if (!_showNotch.value) + return null; + if (_fabShape == kCircularFab) + return const CircularNotchedRectangle(); + if (_fabShape == kDiamondFab) + return const _DiamondNotchedRectangle(); return null; } } class _ChoiceValue { - const _ChoiceValue({this.value, this.title, this.label}); + const _ChoiceValue({ this.value, this.title, this.label }); final T value; final String title; @@ -235,30 +234,32 @@ class _RadioItem extends StatelessWidget { padding: const EdgeInsetsDirectional.only(start: 16.0), alignment: AlignmentDirectional.centerStart, child: MergeSemantics( - child: Row(children: [ - Radio<_ChoiceValue>( - value: value, - groupValue: groupValue, - onChanged: onChanged, - ), - Expanded( - child: Semantics( - container: true, - button: true, - label: value.label, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - onChanged(value); - }, - child: Text( - value.title, - style: theme.textTheme.subhead, + child: Row( + children: [ + Radio<_ChoiceValue>( + value: value, + groupValue: groupValue, + onChanged: onChanged, + ), + Expanded( + child: Semantics( + container: true, + button: true, + label: value.label, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + onChanged(value); + }, + child: Text( + value.title, + style: theme.textTheme.subhead, + ), ), ), ), - ), - ]), + ], + ), ), ); } @@ -294,9 +295,7 @@ class _ColorsItem extends StatelessWidget { fillColor: namedColor.color, shape: CircleBorder( side: BorderSide( - color: namedColor.color == selectedColor - ? Colors.black - : const Color(0xFFD5D7DA), + color: namedColor.color == selectedColor ? Colors.black : const Color(0xFFD5D7DA), width: 2.0, ), ), @@ -333,69 +332,59 @@ class _Heading extends StatelessWidget { } class _DemoBottomAppBar extends StatelessWidget { - const _DemoBottomAppBar({this.color, this.fabLocation, this.shape}); + const _DemoBottomAppBar({ + this.color, + this.fabLocation, + this.shape, + }); final Color color; final FloatingActionButtonLocation fabLocation; final NotchedShape shape; - static final List kCenterLocations = - [ + static final List kCenterLocations = [ FloatingActionButtonLocation.centerDocked, FloatingActionButtonLocation.centerFloat, ]; @override Widget build(BuildContext context) { - final List rowContents = [ - IconButton( - icon: const Icon(Icons.menu, semanticLabel: 'Show bottom sheet'), - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) => const _DemoDrawer(), - ); - }, - ), - ]; - - if (kCenterLocations.contains(fabLocation)) { - rowContents.add( - const Expanded(child: SizedBox()), - ); - } - - rowContents.addAll([ - IconButton( - icon: const Icon( - Icons.search, - semanticLabel: 'show search action', - ), - onPressed: () { - Scaffold.of(context).showSnackBar( - const SnackBar(content: Text('This is a dummy search action.')), - ); - }, - ), - IconButton( - icon: Icon( - Theme.of(context).platform == TargetPlatform.iOS - ? Icons.more_horiz - : Icons.more_vert, - semanticLabel: 'Show menu actions', - ), - onPressed: () { - Scaffold.of(context).showSnackBar( - const SnackBar(content: Text('This is a dummy menu action.')), - ); - }, - ), - ]); - return BottomAppBar( color: color, - child: Row(children: rowContents), shape: shape, + child: Row(children: [ + IconButton( + icon: const Icon(Icons.menu, semanticLabel: 'Show bottom sheet'), + onPressed: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) => const _DemoDrawer(), + ); + }, + ), + if (kCenterLocations.contains(fabLocation)) const Expanded(child: SizedBox()), + IconButton( + icon: const Icon(Icons.search, semanticLabel: 'show search action',), + onPressed: () { + Scaffold.of(context).showSnackBar( + const SnackBar(content: Text('This is a dummy search action.')), + ); + }, + ), + IconButton( + icon: Icon( + Theme.of(context).platform == TargetPlatform.iOS + ? Icons.more_horiz + : Icons.more_vert, + semanticLabel: 'Show menu actions', + ), + onPressed: () { + Scaffold.of(context).showSnackBar( + const SnackBar(content: Text('This is a dummy menu action.')), + ); + }, + ), + ]), ); } } @@ -459,7 +448,8 @@ class _DiamondNotchedRectangle implements NotchedShape { @override Path getOuterPath(Rect host, Rect guest) { - if (!host.overlaps(guest)) return Path()..addRect(host); + if (!host.overlaps(guest)) + return Path()..addRect(host); assert(guest.width > 0.0); final Rect intersection = guest.intersect(host); @@ -476,7 +466,8 @@ class _DiamondNotchedRectangle implements NotchedShape { // the host's top edge where the notch starts (marked with "*"). // We compute notchToCenter by similar triangles: final double notchToCenter = - intersection.height * (guest.height / 2.0) / (guest.width / 2.0); + intersection.height * (guest.height / 2.0) + / (guest.width / 2.0); return Path() ..moveTo(host.left, host.top) @@ -499,22 +490,22 @@ class _DiamondBorder extends ShapeBorder { } @override - Path getInnerPath(Rect rect, {TextDirection textDirection}) { + Path getInnerPath(Rect rect, { TextDirection textDirection }) { return getOuterPath(rect, textDirection: textDirection); } @override - Path getOuterPath(Rect rect, {TextDirection textDirection}) { + Path getOuterPath(Rect rect, { TextDirection textDirection }) { return Path() ..moveTo(rect.left + rect.width / 2.0, rect.top) ..lineTo(rect.right, rect.top + rect.height / 2.0) - ..lineTo(rect.left + rect.width / 2.0, rect.bottom) + ..lineTo(rect.left + rect.width / 2.0, rect.bottom) ..lineTo(rect.left, rect.top + rect.height / 2.0) ..close(); } @override - void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {} + void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) { } // This border doesn't support scaling. @override diff --git a/web/gallery/lib/demo/material/bottom_navigation_demo.dart b/web/gallery/lib/demo/material/bottom_navigation_demo.dart index 3be89df1f..6efefa3cc 100644 --- a/web/gallery/lib/demo/material/bottom_navigation_demo.dart +++ b/web/gallery/lib/demo/material/bottom_navigation_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -13,19 +13,19 @@ class NavigationIconView { String title, Color color, TickerProvider vsync, - }) : _icon = icon, - _color = color, - _title = title, - item = BottomNavigationBarItem( - icon: icon, - activeIcon: activeIcon, - title: Text(title), - backgroundColor: color, - ), - controller = AnimationController( - duration: kThemeAnimationDuration, - vsync: vsync, - ) { + }) : _icon = icon, + _color = color, + _title = title, + item = BottomNavigationBarItem( + icon: icon, + activeIcon: activeIcon, + title: Text(title), + backgroundColor: color, + ), + controller = AnimationController( + duration: kThemeAnimationDuration, + vsync: vsync, + ) { _animation = controller.drive(CurveTween( curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn), )); @@ -38,8 +38,7 @@ class NavigationIconView { final AnimationController controller; Animation _animation; - FadeTransition transition( - BottomNavigationBarType type, BuildContext context) { + FadeTransition transition(BottomNavigationBarType type, BuildContext context) { Color iconColor; if (type == BottomNavigationBarType.shifting) { iconColor = _color; @@ -92,12 +91,13 @@ class CustomInactiveIcon extends StatelessWidget { Widget build(BuildContext context) { final IconThemeData iconTheme = IconTheme.of(context); return Container( - margin: const EdgeInsets.all(4.0), - width: iconTheme.size - 8.0, - height: iconTheme.size - 8.0, - decoration: BoxDecoration( - border: Border.all(color: iconTheme.color, width: 2.0), - )); + margin: const EdgeInsets.all(4.0), + width: iconTheme.size - 8.0, + height: iconTheme.size - 8.0, + decoration: BoxDecoration( + border: Border.all(color: iconTheme.color, width: 2.0), + ), + ); } } @@ -150,27 +150,19 @@ class _BottomNavigationDemoState extends State title: 'Event', color: Colors.pink, vsync: this, - ) + ), ]; - for (NavigationIconView view in _navigationViews) - view.controller.addListener(_rebuild); - _navigationViews[_currentIndex].controller.value = 1.0; } @override void dispose() { - for (NavigationIconView view in _navigationViews) view.controller.dispose(); + for (NavigationIconView view in _navigationViews) + view.controller.dispose(); super.dispose(); } - void _rebuild() { - setState(() { - // Rebuild in order to animate views. - }); - } - Widget _buildTransitionsStack() { final List transitions = []; @@ -193,8 +185,7 @@ class _BottomNavigationDemoState extends State Widget build(BuildContext context) { final BottomNavigationBar botNavBar = BottomNavigationBar( items: _navigationViews - .map( - (NavigationIconView navigationView) => navigationView.item) + .map((NavigationIconView navigationView) => navigationView.item) .toList(), currentIndex: _currentIndex, type: _type, @@ -218,8 +209,7 @@ class _BottomNavigationDemoState extends State _type = value; }); }, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: BottomNavigationBarType.fixed, child: Text('Fixed'), @@ -227,12 +217,14 @@ class _BottomNavigationDemoState extends State const PopupMenuItem( value: BottomNavigationBarType.shifting, child: Text('Shifting'), - ) + ), ], - ) + ), ], ), - body: Center(child: _buildTransitionsStack()), + body: Center( + child: _buildTransitionsStack(), + ), bottomNavigationBar: botNavBar, ); } diff --git a/web/gallery/lib/demo/material/buttons_demo.dart b/web/gallery/lib/demo/material/buttons_demo.dart new file mode 100644 index 000000000..7ae4ff405 --- /dev/null +++ b/web/gallery/lib/demo/material/buttons_demo.dart @@ -0,0 +1,386 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../../gallery/demo.dart'; + +const String _raisedText = + 'Raised buttons add dimension to mostly flat layouts. They emphasize ' + 'functions on busy or wide spaces.'; + +const String _raisedCode = 'buttons_raised'; + +const String _flatText = 'A flat button displays an ink splash on press ' + 'but does not lift. Use flat buttons on toolbars, in dialogs and ' + 'inline with padding'; + +const String _flatCode = 'buttons_flat'; + +const String _outlineText = + 'Outline buttons become opaque and elevate when pressed. They are often ' + 'paired with raised buttons to indicate an alternative, secondary action.'; + +const String _outlineCode = 'buttons_outline'; + +const String _dropdownText = + 'A dropdown button displays a menu that\'s used to select a value from a ' + 'small set of values. The button displays the current value and a down ' + 'arrow.'; + +const String _dropdownCode = 'buttons_dropdown'; + +const String _iconText = + 'IconButtons are appropriate for toggle buttons that allow a single choice ' + 'to be selected or deselected, such as adding or removing an item\'s star.'; + +const String _iconCode = 'buttons_icon'; + +const String _actionText = + 'Floating action buttons are used for a promoted action. They are ' + 'distinguished by a circled icon floating above the UI and can have motion ' + 'behaviors that include morphing, launching, and a transferring anchor ' + 'point.'; + +const String _actionCode = 'buttons_action'; + +class ButtonsDemo extends StatefulWidget { + static const String routeName = '/material/buttons'; + + @override + _ButtonsDemoState createState() => _ButtonsDemoState(); +} + +class _ButtonsDemoState extends State { + ShapeBorder _buttonShape; + + @override + Widget build(BuildContext context) { + final ButtonThemeData buttonTheme = ButtonTheme.of(context).copyWith( + shape: _buttonShape + ); + + final List demos = [ + ComponentDemoTabData( + tabName: 'RAISED', + description: _raisedText, + demoWidget: ButtonTheme.fromButtonThemeData( + data: buttonTheme, + child: buildRaisedButton(), + ), + exampleCodeTag: _raisedCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/RaisedButton-class.html', + ), + ComponentDemoTabData( + tabName: 'FLAT', + description: _flatText, + demoWidget: ButtonTheme.fromButtonThemeData( + data: buttonTheme, + child: buildFlatButton(), + ), + exampleCodeTag: _flatCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/FlatButton-class.html', + ), + ComponentDemoTabData( + tabName: 'OUTLINE', + description: _outlineText, + demoWidget: ButtonTheme.fromButtonThemeData( + data: buttonTheme, + child: buildOutlineButton(), + ), + exampleCodeTag: _outlineCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/OutlineButton-class.html', + ), + ComponentDemoTabData( + tabName: 'DROPDOWN', + description: _dropdownText, + demoWidget: buildDropdownButton(), + exampleCodeTag: _dropdownCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/DropdownButton-class.html', + ), + ComponentDemoTabData( + tabName: 'ICON', + description: _iconText, + demoWidget: buildIconButton(), + exampleCodeTag: _iconCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/IconButton-class.html', + ), + ComponentDemoTabData( + tabName: 'ACTION', + description: _actionText, + demoWidget: buildActionButton(), + exampleCodeTag: _actionCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/FloatingActionButton-class.html', + ), + ]; + + return TabbedComponentDemoScaffold( + title: 'Buttons', + demos: demos, + actions: [ + IconButton( + icon: const Icon(Icons.sentiment_very_satisfied, semanticLabel: 'Update shape'), + onPressed: () { + setState(() { + _buttonShape = _buttonShape == null ? const StadiumBorder() : null; + }); + }, + ), + ], + ); + } + + Widget buildRaisedButton() { + return Align( + alignment: const Alignment(0.0, -0.2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + RaisedButton( + child: const Text('RAISED BUTTON', semanticsLabel: 'RAISED BUTTON 1'), + onPressed: () { + // Perform some action + }, + ), + const RaisedButton( + child: Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 1'), + onPressed: null, + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + RaisedButton.icon( + icon: const Icon(Icons.add, size: 18.0), + label: const Text('RAISED BUTTON', semanticsLabel: 'RAISED BUTTON 2'), + onPressed: () { + // Perform some action + }, + ), + RaisedButton.icon( + icon: const Icon(Icons.add, size: 18.0), + label: const Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 2'), + onPressed: null, + ), + ], + ), + ], + ), + ); + } + + Widget buildFlatButton() { + return Align( + alignment: const Alignment(0.0, -0.2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + FlatButton( + child: const Text('FLAT BUTTON', semanticsLabel: 'FLAT BUTTON 1'), + onPressed: () { + // Perform some action + }, + ), + const FlatButton( + child: Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 3',), + onPressed: null, + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + FlatButton.icon( + icon: const Icon(Icons.add_circle_outline, size: 18.0), + label: const Text('FLAT BUTTON', semanticsLabel: 'FLAT BUTTON 2'), + onPressed: () { + // Perform some action + }, + ), + FlatButton.icon( + icon: const Icon(Icons.add_circle_outline, size: 18.0), + label: const Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 4'), + onPressed: null, + ), + ], + ), + ], + ), + ); + } + + Widget buildOutlineButton() { + return Align( + alignment: const Alignment(0.0, -0.2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + OutlineButton( + child: const Text('OUTLINE BUTTON', semanticsLabel: 'OUTLINE BUTTON 1'), + onPressed: () { + // Perform some action + }, + ), + const OutlineButton( + child: Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 5'), + onPressed: null, + ), + ], + ), + ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + OutlineButton.icon( + icon: const Icon(Icons.add, size: 18.0), + label: const Text('OUTLINE BUTTON', semanticsLabel: 'OUTLINE BUTTON 2'), + onPressed: () { + // Perform some action + }, + ), + OutlineButton.icon( + icon: const Icon(Icons.add, size: 18.0), + label: const Text('DISABLED', semanticsLabel: 'DISABLED BUTTON 6'), + onPressed: null, + ), + ], + ), + ], + ), + ); + } + + // https://en.wikipedia.org/wiki/Free_Four + String dropdown1Value = 'Free'; + String dropdown2Value; + String dropdown3Value = 'Four'; + + Widget buildDropdownButton() { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ListTile( + title: const Text('Simple dropdown:'), + trailing: DropdownButton( + value: dropdown1Value, + onChanged: (String newValue) { + setState(() { + dropdown1Value = newValue; + }); + }, + items: ['One', 'Two', 'Free', 'Four'].map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ), + const SizedBox( + height: 24.0, + ), + ListTile( + title: const Text('Dropdown with a hint:'), + trailing: DropdownButton( + value: dropdown2Value, + hint: const Text('Choose'), + onChanged: (String newValue) { + setState(() { + dropdown2Value = newValue; + }); + }, + items: ['One', 'Two', 'Free', 'Four'].map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ), + const SizedBox( + height: 24.0, + ), + ListTile( + title: const Text('Scrollable dropdown:'), + trailing: DropdownButton( + value: dropdown3Value, + onChanged: (String newValue) { + setState(() { + dropdown3Value = newValue; + }); + }, + items: [ + 'One', 'Two', 'Free', 'Four', 'Can', 'I', 'Have', 'A', 'Little', + 'Bit', 'More', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', + ] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }) + .toList(), + ), + ), + ], + ), + ); + } + + bool iconButtonToggle = false; + + Widget buildIconButton() { + return Align( + alignment: const Alignment(0.0, -0.2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon( + Icons.thumb_up, + semanticLabel: 'Thumbs up', + ), + onPressed: () { + setState(() => iconButtonToggle = !iconButtonToggle); + }, + color: iconButtonToggle ? Theme.of(context).primaryColor : null, + ), + const IconButton( + icon: Icon( + Icons.thumb_up, + semanticLabel: 'Thumbs not up', + ), + onPressed: null, + ), + ] + .map((Widget button) => SizedBox(width: 64.0, height: 64.0, child: button)) + .toList(), + ), + ); + } + + Widget buildActionButton() { + return Align( + alignment: const Alignment(0.0, -0.2), + child: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + // Perform some action + }, + tooltip: 'floating action button', + ), + ); + } +} diff --git a/web/gallery/lib/demo/material/cards_demo.dart b/web/gallery/lib/demo/material/cards_demo.dart index 4323c88ab..39c110695 100644 --- a/web/gallery/lib/demo/material/cards_demo.dart +++ b/web/gallery/lib/demo/material/cards_demo.dart @@ -1,147 +1,350 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/foundation.dart'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; +enum CardDemoType { + standard, + tappable, + selectable, +} + class TravelDestination { const TravelDestination({ - this.assetName, - this.assetPackage, - this.title, - this.description, - }); + @required this.assetName, + @required this.assetPackage, + @required this.title, + @required this.description, + @required this.city, + @required this.location, + this.type = CardDemoType.standard, + }) : assert(assetName != null), + assert(assetPackage != null), + assert(title != null), + assert(description != null), + assert(city != null), + assert(location != null); final String assetName; final String assetPackage; final String title; - final List description; - - bool get isValid => - assetName != null && title != null && description?.length == 3; + final String description; + final String city; + final String location; + final CardDemoType type; } -final List destinations = [ - const TravelDestination( +const List destinations = [ + TravelDestination( assetName: 'places/india_thanjavur_market.png', assetPackage: _kGalleryAssetsPackage, title: 'Top 10 Cities to Visit in Tamil Nadu', - description: [ - 'Number 10', - 'Thanjavur', - 'Thanjavur, Tamil Nadu', - ], + description: 'Number 10', + city: 'Thanjavur', + location: 'Thanjavur, Tamil Nadu', ), - const TravelDestination( + TravelDestination( assetName: 'places/india_chettinad_silk_maker.png', assetPackage: _kGalleryAssetsPackage, title: 'Artisans of Southern India', - description: [ - 'Silk Spinners', - 'Chettinad', - 'Sivaganga, Tamil Nadu', - ], - ) + description: 'Silk Spinners', + city: 'Chettinad', + location: 'Sivaganga, Tamil Nadu', + type: CardDemoType.tappable, + ), + TravelDestination( + assetName: 'places/india_tanjore_thanjavur_temple.png', + assetPackage: _kGalleryAssetsPackage, + title: 'Brihadisvara Temple', + description: 'Temples', + city: 'Thanjavur', + location: 'Thanjavur, Tamil Nadu', + type: CardDemoType.selectable, + ), ]; class TravelDestinationItem extends StatelessWidget { - TravelDestinationItem({Key key, @required this.destination, this.shape}) - : assert(destination != null && destination.isValid), - super(key: key); + const TravelDestinationItem({ Key key, @required this.destination, this.shape }) + : assert(destination != null), + super(key: key); - static const double height = 366.0; + // This height will allow for all the Card's content to fit comfortably within the card. + static const double height = 338.0; final TravelDestination destination; final ShapeBorder shape; @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final TextStyle titleStyle = - theme.textTheme.headline.copyWith(color: Colors.white); - final TextStyle descriptionStyle = theme.textTheme.subhead; + return SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + const SectionTitle(title: 'Normal'), + SizedBox( + height: height, + child: Card( + // This ensures that the Card's children are clipped correctly. + clipBehavior: Clip.antiAlias, + shape: shape, + child: TravelDestinationContent(destination: destination), + ), + ), + ], + ), + ), + ); + } +} - return Container( - padding: const EdgeInsets.all(8.0), - height: height, - child: Card( - shape: shape, +class TappableTravelDestinationItem extends StatelessWidget { + const TappableTravelDestinationItem({ Key key, @required this.destination, this.shape }) + : assert(destination != null), + super(key: key); + + // This height will allow for all the Card's content to fit comfortably within the card. + static const double height = 298.0; + final TravelDestination destination; + final ShapeBorder shape; + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.all(8.0), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - // photo and title + const SectionTitle(title: 'Tappable'), SizedBox( - height: 184.0, - child: Stack( - children: [ - Positioned( - bottom: 16.0, - left: 16.0, - right: 16.0, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text( - destination.title, - style: titleStyle, - ), - ), - ), - ], + height: height, + child: Card( + // This ensures that the Card's children (including the ink splash) are clipped correctly. + clipBehavior: Clip.antiAlias, + shape: shape, + child: InkWell( + onTap: () { + print('Card was tapped'); + }, + // Generally, material cards use onSurface with 12% opacity for the pressed state. + splashColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.12), + // Generally, material cards do not have a highlight overlay. + highlightColor: Colors.transparent, + child: TravelDestinationContent(destination: destination), + ), ), ), - // description and share/explore buttons - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0), - child: DefaultTextStyle( - softWrap: false, - overflow: TextOverflow.ellipsis, - style: descriptionStyle, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ], + ), + ), + ); + } +} + +class SelectableTravelDestinationItem extends StatefulWidget { + const SelectableTravelDestinationItem({ Key key, @required this.destination, this.shape }) + : assert(destination != null), + super(key: key); + + final TravelDestination destination; + final ShapeBorder shape; + + @override + _SelectableTravelDestinationItemState createState() => _SelectableTravelDestinationItemState(); +} + +class _SelectableTravelDestinationItemState extends State { + + // This height will allow for all the Card's content to fit comfortably within the card. + static const double height = 298.0; + bool _isSelected = false; + + @override + Widget build(BuildContext context) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + return SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + const SectionTitle(title: 'Selectable (long press)'), + SizedBox( + height: height, + child: Card( + // This ensures that the Card's children (including the ink splash) are clipped correctly. + clipBehavior: Clip.antiAlias, + shape: widget.shape, + child: InkWell( + onLongPress: () { + print('Selectable card state changed'); + setState(() { + _isSelected = !_isSelected; + }); + }, + // Generally, material cards use onSurface with 12% opacity for the pressed state. + splashColor: colorScheme.onSurface.withOpacity(0.12), + // Generally, material cards do not have a highlight overlay. + highlightColor: Colors.transparent, + child: Stack( children: [ - // three line description - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - destination.description[0], - style: - descriptionStyle.copyWith(color: Colors.black54), + Container( + color: _isSelected + // Generally, material cards use primary with 8% opacity for the selected state. + // See: https://material.io/design/interaction/states.html#anatomy + ? colorScheme.primary.withOpacity(0.08) + : Colors.transparent, + ), + TravelDestinationContent(destination: widget.destination), + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.check_circle, + color: _isSelected ? colorScheme.primary : Colors.transparent, + ), ), ), - Text(destination.description[1]), - Text(destination.description[2]), ], ), ), ), ), - // share, explore buttons - ButtonTheme.bar( - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FlatButton( - child: const Text('SHARE'), - textColor: Colors.amber.shade500, - onPressed: () {/* do nothing */}, - ), - FlatButton( - child: const Text('EXPLORE'), - textColor: Colors.amber.shade500, - onPressed: () {/* do nothing */}, - ), - ], + ], + ), + ), + ); + } +} + +class SectionTitle extends StatelessWidget { + const SectionTitle({ + Key key, + this.title, + }) : super(key: key); + + final String title; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(4.0, 4.0, 4.0, 12.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text(title, style: Theme.of(context).textTheme.subhead), + ), + ); + } +} + +class TravelDestinationContent extends StatelessWidget { + const TravelDestinationContent({ Key key, @required this.destination }) + : assert(destination != null), + super(key: key); + + final TravelDestination destination; + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TextStyle titleStyle = theme.textTheme.headline.copyWith(color: Colors.white); + final TextStyle descriptionStyle = theme.textTheme.subhead; + + final List children = [ + // Photo and title. + SizedBox( + height: 184.0, + child: Stack( + children: [ + Positioned.fill( + // In order to have the ink splash appear above the image, you + // must use Ink.image. This allows the image to be painted as part + // of the Material and display ink effects above it. Using a + // standard Image will obscure the ink splash. + child: Ink.image( + image: AssetImage(destination.assetName, package: destination.assetPackage), + fit: BoxFit.cover, + child: Container(), + ), + ), + Positioned( + bottom: 16.0, + left: 16.0, + right: 16.0, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + destination.title, + style: titleStyle, + ), ), ), ], ), ), + // Description and share/explore buttons. + Padding( + padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0), + child: DefaultTextStyle( + softWrap: false, + overflow: TextOverflow.ellipsis, + style: descriptionStyle, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // three line description + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + destination.description, + style: descriptionStyle.copyWith(color: Colors.black54), + ), + ), + Text(destination.city), + Text(destination.location), + ], + ), + ), + ), + ]; + + if (destination.type == CardDemoType.standard) { + children.add( + // share, explore buttons + ButtonBar( + alignment: MainAxisAlignment.start, + children: [ + FlatButton( + child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'), + textColor: Colors.amber.shade500, + onPressed: () { print('pressed'); }, + ), + FlatButton( + child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'), + textColor: Colors.amber.shade500, + onPressed: () { print('pressed'); }, + ), + ], + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, ); } } @@ -156,26 +359,57 @@ class CardsDemo extends StatefulWidget { class _CardsDemoState extends State { ShapeBorder _shape; - final GlobalKey _scaffoldKey = GlobalKey(); - @override Widget build(BuildContext context) { - return wrapScaffold('Cards Demo', context, _scaffoldKey, - _buildContents(context), CardsDemo.routeName); - } - - Widget _buildContents(BuildContext context) { - return ListView( - itemExtent: TravelDestinationItem.height, - padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), - children: destinations.map((TravelDestination destination) { - return Container( - margin: const EdgeInsets.only(bottom: 8.0), - child: TravelDestinationItem( - destination: destination, - shape: _shape, + return Scaffold( + appBar: AppBar( + title: const Text('Cards'), + actions: [ + MaterialDemoDocumentationButton(CardsDemo.routeName), + IconButton( + icon: const Icon( + Icons.sentiment_very_satisfied, + semanticLabel: 'update shape', ), - ); - }).toList()); + onPressed: () { + setState(() { + _shape = _shape != null ? null : const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + bottomLeft: Radius.circular(2.0), + bottomRight: Radius.circular(2.0), + ), + ); + }); + }, + ), + ], + ), + body: Scrollbar( + child: ListView( + padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), + children: destinations.map((TravelDestination destination) { + Widget child; + switch (destination.type) { + case CardDemoType.standard: + child = TravelDestinationItem(destination: destination, shape: _shape); + break; + case CardDemoType.tappable: + child = TappableTravelDestinationItem(destination: destination, shape: _shape); + break; + case CardDemoType.selectable: + child = SelectableTravelDestinationItem(destination: destination, shape: _shape); + break; + } + + return Container( + margin: const EdgeInsets.only(bottom: 8.0), + child: child, + ); + }).toList(), + ), + ), + ); } } diff --git a/web/gallery/lib/demo/material/chip_demo.dart b/web/gallery/lib/demo/material/chip_demo.dart index a71cbfb89..6c464db70 100644 --- a/web/gallery/lib/demo/material/chip_demo.dart +++ b/web/gallery/lib/demo/material/chip_demo.dart @@ -1,79 +1,335 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; + import '../../gallery/demo.dart'; +const List _defaultMaterials = [ + 'poker', + 'tortilla', + 'fish and', + 'micro', + 'wood', +]; + +const List _defaultActions = [ + 'flake', + 'cut', + 'fragment', + 'splinter', + 'nick', + 'fry', + 'solder', + 'cash in', + 'eat', +]; + +const Map _results = { + 'flake': 'flaking', + 'cut': 'cutting', + 'fragment': 'fragmenting', + 'splinter': 'splintering', + 'nick': 'nicking', + 'fry': 'frying', + 'solder': 'soldering', + 'cash in': 'cashing in', + 'eat': 'eating', +}; + +const List _defaultTools = [ + 'hammer', + 'chisel', + 'fryer', + 'fabricator', + 'customer', +]; + +const Map _avatars = { + 'hammer': 'people/square/ali.png', + 'chisel': 'people/square/sandra.png', + 'fryer': 'people/square/trevor.png', + 'fabricator': 'people/square/stella.png', + 'customer': 'people/square/peter.png', +}; + +const Map> _toolActions = >{ + 'hammer': {'flake', 'fragment', 'splinter'}, + 'chisel': {'flake', 'nick', 'splinter'}, + 'fryer': {'fry'}, + 'fabricator': {'solder'}, + 'customer': {'cash in', 'eat'}, +}; + +const Map> _materialActions = >{ + 'poker': {'cash in'}, + 'tortilla': {'fry', 'eat'}, + 'fish and': {'fry', 'eat'}, + 'micro': {'solder', 'fragment'}, + 'wood': {'flake', 'cut', 'splinter', 'nick'}, +}; + +class _ChipsTile extends StatelessWidget { + const _ChipsTile({ + Key key, + this.label, + this.children, + }) : super(key: key); + + final String label; + final List children; + + // Wraps a list of chips into a ListTile for display as a section in the demo. + @override + Widget build(BuildContext context) { + final List cardChildren = [ + Container( + padding: const EdgeInsets.only(top: 16.0, bottom: 4.0), + alignment: Alignment.center, + child: Text(label, textAlign: TextAlign.start), + ), + ]; + if (children.isNotEmpty) { + cardChildren.add(Wrap( + children: children.map((Widget chip) { + return Padding( + padding: const EdgeInsets.all(2.0), + child: chip, + ); + }).toList())); + } else { + final TextStyle textStyle = Theme.of(context).textTheme.caption.copyWith(fontStyle: FontStyle.italic); + cardChildren.add( + Semantics( + container: true, + child: Container( + alignment: Alignment.center, + constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), + padding: const EdgeInsets.all(8.0), + child: Text('None', style: textStyle), + ), + )); + } + + return Card( + semanticContainer: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: cardChildren, + ), + ); + } +} + class ChipDemo extends StatefulWidget { - static const routeName = '/material/chip'; + static const String routeName = '/material/chip'; + @override - State createState() => _ChipDemoState(); + _ChipDemoState createState() => _ChipDemoState(); } class _ChipDemoState extends State { - bool _filterChipSelected = false; - bool _hasAvatar = true; + _ChipDemoState() { + _reset(); + } + + final Set _materials = {}; + String _selectedMaterial = ''; + String _selectedAction = ''; + final Set _tools = {}; + final Set _selectedTools = {}; + final Set _actions = {}; + bool _showShapeBorder = false; + + // Initialize members with the default data. + void _reset() { + _materials.clear(); + _materials.addAll(_defaultMaterials); + _actions.clear(); + _actions.addAll(_defaultActions); + _tools.clear(); + _tools.addAll(_defaultTools); + _selectedMaterial = ''; + _selectedAction = ''; + _selectedTools.clear(); + } + + void _removeMaterial(String name) { + _materials.remove(name); + if (_selectedMaterial == name) { + _selectedMaterial = ''; + } + } + + void _removeTool(String name) { + _tools.remove(name); + _selectedTools.remove(name); + } + + String _capitalize(String name) { + assert(name != null && name.isNotEmpty); + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + + // This converts a String to a unique color, based on the hash value of the + // String object. It takes the bottom 16 bits of the hash, and uses that to + // pick a hue for an HSV color, and then creates the color (with a preset + // saturation and value). This means that any unique strings will also have + // unique colors, but they'll all be readable, since they have the same + // saturation and value. + Color _nameToColor(String name) { + assert(name.length > 1); + final int hash = name.hashCode & 0xffff; + final double hue = (360.0 * hash / (1 << 15)) % 360.0; + return HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor(); + } + + AssetImage _nameToAvatar(String name) { + assert(_avatars.containsKey(name)); + return AssetImage( + _avatars[name], + package: 'flutter_gallery_assets', + ); + } - final GlobalKey _scaffoldKey = GlobalKey(); + String _createResult() { + if (_selectedAction.isEmpty) { + return ''; + } + return _capitalize(_results[_selectedAction]) + '!'; + } @override Widget build(BuildContext context) { - return wrapScaffold('Chip Demo', context, _scaffoldKey, _buildContents(), - ChipDemo.routeName); - } + final List chips = _materials.map((String name) { + return Chip( + key: ValueKey(name), + backgroundColor: _nameToColor(name), + label: Text(_capitalize(name)), + onDeleted: () { + setState(() { + _removeMaterial(name); + }); + }, + ); + }).toList(); - Widget _buildContents() { - return Material( - child: Column( - children: [ - addPadding(Chip( - label: Text('Chip'), - )), - addPadding(InputChip( - label: Text('InputChip'), - )), - addPadding(ChoiceChip( - label: Text('Selected ChoiceChip'), - selected: true, - )), - addPadding(ChoiceChip( - label: Text('Deselected ChoiceChip'), - selected: false, - )), - addPadding(FilterChip( - label: Text('FilterChip'), - selected: _filterChipSelected, - onSelected: (bool newValue) { + final List inputChips = _tools.map((String name) { + return InputChip( + key: ValueKey(name), + avatar: CircleAvatar( + backgroundImage: _nameToAvatar(name), + ), + label: Text(_capitalize(name)), + onDeleted: () { setState(() { - _filterChipSelected = newValue; + _removeTool(name); }); - }, - )), - addPadding(ActionChip( - label: Text('ActionChip'), - onPressed: () {}, - )), - addPadding(ActionChip( - label: Text('Chip with avatar'), - avatar: _hasAvatar - ? CircleAvatar( - backgroundColor: Colors.amber, - child: Text('Z'), - ) - : null, - onPressed: () { - setState(() { - _hasAvatar = !_hasAvatar; - }); - }, - )), - ], - )); - } -} + }); + }).toList(); + + final List choiceChips = _materials.map((String name) { + return ChoiceChip( + key: ValueKey(name), + backgroundColor: _nameToColor(name), + label: Text(_capitalize(name)), + selected: _selectedMaterial == name, + onSelected: (bool value) { + setState(() { + _selectedMaterial = value ? name : ''; + }); + }, + ); + }).toList(); + + final List filterChips = _defaultTools.map((String name) { + return FilterChip( + key: ValueKey(name), + label: Text(_capitalize(name)), + selected: _tools.contains(name) && _selectedTools.contains(name), + onSelected: !_tools.contains(name) + ? null + : (bool value) { + setState(() { + if (!value) { + _selectedTools.remove(name); + } else { + _selectedTools.add(name); + } + }); + }, + ); + }).toList(); -Padding addPadding(Widget widget) => Padding( - padding: EdgeInsets.all(10.0), - child: widget, + Set allowedActions = {}; + if (_selectedMaterial != null && _selectedMaterial.isNotEmpty) { + for (String tool in _selectedTools) { + allowedActions.addAll(_toolActions[tool]); + } + allowedActions = allowedActions.intersection(_materialActions[_selectedMaterial]); + } + + final List actionChips = allowedActions.map((String name) { + return ActionChip( + label: Text(_capitalize(name)), + onPressed: () { + setState(() { + _selectedAction = name; + }); + }, + ); + }).toList(); + + final ThemeData theme = Theme.of(context); + final List tiles = [ + const SizedBox(height: 8.0, width: 0.0), + _ChipsTile(label: 'Available Materials (Chip)', children: chips), + _ChipsTile(label: 'Available Tools (InputChip)', children: inputChips), + _ChipsTile(label: 'Choose a Material (ChoiceChip)', children: choiceChips), + _ChipsTile(label: 'Choose Tools (FilterChip)', children: filterChips), + _ChipsTile(label: 'Perform Allowed Action (ActionChip)', children: actionChips), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + _createResult(), + style: theme.textTheme.title, + ), + ), + ), + ]; + + return Scaffold( + appBar: AppBar( + title: const Text('Chips'), + actions: [ + MaterialDemoDocumentationButton(ChipDemo.routeName), + IconButton( + onPressed: () { + setState(() { + _showShapeBorder = !_showShapeBorder; + }); + }, + icon: const Icon(Icons.vignette, semanticLabel: 'Update border shape'), + ), + ], + ), + body: ChipTheme( + data: _showShapeBorder + ? theme.chipTheme.copyWith( + shape: BeveledRectangleBorder( + side: const BorderSide(width: 0.66, style: BorderStyle.solid, color: Colors.grey), + borderRadius: BorderRadius.circular(10.0), + )) + : theme.chipTheme, + child: Scrollbar(child: ListView(children: tiles)), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => setState(_reset), + child: const Icon(Icons.refresh, semanticLabel: 'Reset chips'), + ), ); + } +} diff --git a/web/gallery/lib/demo/material/data_table_demo.dart b/web/gallery/lib/demo/material/data_table_demo.dart index e6c5a2f0e..5611829d5 100644 --- a/web/gallery/lib/demo/material/data_table_demo.dart +++ b/web/gallery/lib/demo/material/data_table_demo.dart @@ -1,15 +1,14 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import '../../gallery/demo.dart'; class Dessert { - Dessert(this.name, this.calories, this.fat, this.carbs, this.protein, - this.sodium, this.calcium, this.iron); + Dessert(this.name, this.calories, this.fat, this.carbs, this.protein, this.sodium, this.calcium, this.iron); final String name; final int calories; final double fat; @@ -24,57 +23,60 @@ class Dessert { class DessertDataSource extends DataTableSource { final List _desserts = [ - Dessert('Frozen yogurt', 159, 6.0, 24, 4.0, 87, 14, 1), - Dessert('Ice cream sandwich', 237, 9.0, 37, 4.3, 129, 8, 1), - Dessert('Eclair', 262, 16.0, 24, 6.0, 337, 6, 7), - Dessert('Cupcake', 305, 3.7, 67, 4.3, 413, 3, 8), - Dessert('Gingerbread', 356, 16.0, 49, 3.9, 327, 7, 16), - Dessert('Jelly bean', 375, 0.0, 94, 0.0, 50, 0, 0), - Dessert('Lollipop', 392, 0.2, 98, 0.0, 38, 0, 2), - Dessert('Honeycomb', 408, 3.2, 87, 6.5, 562, 0, 45), - Dessert('Donut', 452, 25.0, 51, 4.9, 326, 2, 22), - Dessert('KitKat', 518, 26.0, 65, 7.0, 54, 12, 6), - Dessert('Frozen yogurt with sugar', 168, 6.0, 26, 4.0, 87, 14, 1), - Dessert('Ice cream sandwich with sugar', 246, 9.0, 39, 4.3, 129, 8, 1), - Dessert('Eclair with sugar', 271, 16.0, 26, 6.0, 337, 6, 7), - Dessert('Cupcake with sugar', 314, 3.7, 69, 4.3, 413, 3, 8), - Dessert('Gingerbread with sugar', 345, 16.0, 51, 3.9, 327, 7, 16), - Dessert('Jelly bean with sugar', 364, 0.0, 96, 0.0, 50, 0, 0), - Dessert('Lollipop with sugar', 401, 0.2, 100, 0.0, 38, 0, 2), - Dessert('Honeycomb with sugar', 417, 3.2, 89, 6.5, 562, 0, 45), - Dessert('Donut with sugar', 461, 25.0, 53, 4.9, 326, 2, 22), - Dessert('KitKat with sugar', 527, 26.0, 67, 7.0, 54, 12, 6), - Dessert('Frozen yogurt with honey', 223, 6.0, 36, 4.0, 87, 14, 1), - Dessert('Ice cream sandwich with honey', 301, 9.0, 49, 4.3, 129, 8, 1), - Dessert('Eclair with honey', 326, 16.0, 36, 6.0, 337, 6, 7), - Dessert('Cupcake with honey', 369, 3.7, 79, 4.3, 413, 3, 8), - Dessert('Gingerbread with honey', 420, 16.0, 61, 3.9, 327, 7, 16), - Dessert('Jelly bean with honey', 439, 0.0, 106, 0.0, 50, 0, 0), - Dessert('Lollipop with honey', 456, 0.2, 110, 0.0, 38, 0, 2), - Dessert('Honeycomb with honey', 472, 3.2, 99, 6.5, 562, 0, 45), - Dessert('Donut with honey', 516, 25.0, 63, 4.9, 326, 2, 22), - Dessert('KitKat with honey', 582, 26.0, 77, 7.0, 54, 12, 6), - Dessert('Frozen yogurt with milk', 262, 8.4, 36, 12.0, 194, 44, 1), - Dessert('Ice cream sandwich with milk', 339, 11.4, 49, 12.3, 236, 38, 1), - Dessert('Eclair with milk', 365, 18.4, 36, 14.0, 444, 36, 7), - Dessert('Cupcake with milk', 408, 6.1, 79, 12.3, 520, 33, 8), - Dessert('Gingerbread with milk', 459, 18.4, 61, 11.9, 434, 37, 16), - Dessert('Jelly bean with milk', 478, 2.4, 106, 8.0, 157, 30, 0), - Dessert('Lollipop with milk', 495, 2.6, 110, 8.0, 145, 30, 2), - Dessert('Honeycomb with milk', 511, 5.6, 99, 14.5, 669, 30, 45), - Dessert('Donut with milk', 555, 27.4, 63, 12.9, 433, 32, 22), - Dessert('KitKat with milk', 621, 28.4, 77, 15.0, 161, 42, 6), - Dessert('Coconut slice and frozen yogurt', 318, 21.0, 31, 5.5, 96, 14, 7), - Dessert( - 'Coconut slice and ice cream sandwich', 396, 24.0, 44, 5.8, 138, 8, 7), - Dessert('Coconut slice and eclair', 421, 31.0, 31, 7.5, 346, 6, 13), - Dessert('Coconut slice and cupcake', 464, 18.7, 74, 5.8, 422, 3, 14), - Dessert('Coconut slice and gingerbread', 515, 31.0, 56, 5.4, 316, 7, 22), - Dessert('Coconut slice and jelly bean', 534, 15.0, 101, 1.5, 59, 0, 6), - Dessert('Coconut slice and lollipop', 551, 15.2, 105, 1.5, 47, 0, 8), - Dessert('Coconut slice and honeycomb', 567, 18.2, 94, 8.0, 571, 0, 51), - Dessert('Coconut slice and donut', 611, 40.0, 58, 6.4, 335, 2, 28), - Dessert('Coconut slice and KitKat', 677, 41.0, 72, 8.5, 63, 12, 12), + Dessert('Frozen yogurt', 159, 6.0, 24, 4.0, 87, 14, 1), + Dessert('Ice cream sandwich', 237, 9.0, 37, 4.3, 129, 8, 1), + Dessert('Eclair', 262, 16.0, 24, 6.0, 337, 6, 7), + Dessert('Cupcake', 305, 3.7, 67, 4.3, 413, 3, 8), + Dessert('Gingerbread', 356, 16.0, 49, 3.9, 327, 7, 16), + Dessert('Jelly bean', 375, 0.0, 94, 0.0, 50, 0, 0), + Dessert('Lollipop', 392, 0.2, 98, 0.0, 38, 0, 2), + Dessert('Honeycomb', 408, 3.2, 87, 6.5, 562, 0, 45), + Dessert('Donut', 452, 25.0, 51, 4.9, 326, 2, 22), + Dessert('KitKat', 518, 26.0, 65, 7.0, 54, 12, 6), + + Dessert('Frozen yogurt with sugar', 168, 6.0, 26, 4.0, 87, 14, 1), + Dessert('Ice cream sandwich with sugar', 246, 9.0, 39, 4.3, 129, 8, 1), + Dessert('Eclair with sugar', 271, 16.0, 26, 6.0, 337, 6, 7), + Dessert('Cupcake with sugar', 314, 3.7, 69, 4.3, 413, 3, 8), + Dessert('Gingerbread with sugar', 345, 16.0, 51, 3.9, 327, 7, 16), + Dessert('Jelly bean with sugar', 364, 0.0, 96, 0.0, 50, 0, 0), + Dessert('Lollipop with sugar', 401, 0.2, 100, 0.0, 38, 0, 2), + Dessert('Honeycomb with sugar', 417, 3.2, 89, 6.5, 562, 0, 45), + Dessert('Donut with sugar', 461, 25.0, 53, 4.9, 326, 2, 22), + Dessert('KitKat with sugar', 527, 26.0, 67, 7.0, 54, 12, 6), + + Dessert('Frozen yogurt with honey', 223, 6.0, 36, 4.0, 87, 14, 1), + Dessert('Ice cream sandwich with honey', 301, 9.0, 49, 4.3, 129, 8, 1), + Dessert('Eclair with honey', 326, 16.0, 36, 6.0, 337, 6, 7), + Dessert('Cupcake with honey', 369, 3.7, 79, 4.3, 413, 3, 8), + Dessert('Gingerbread with honey', 420, 16.0, 61, 3.9, 327, 7, 16), + Dessert('Jelly bean with honey', 439, 0.0, 106, 0.0, 50, 0, 0), + Dessert('Lollipop with honey', 456, 0.2, 110, 0.0, 38, 0, 2), + Dessert('Honeycomb with honey', 472, 3.2, 99, 6.5, 562, 0, 45), + Dessert('Donut with honey', 516, 25.0, 63, 4.9, 326, 2, 22), + Dessert('KitKat with honey', 582, 26.0, 77, 7.0, 54, 12, 6), + + Dessert('Frozen yogurt with milk', 262, 8.4, 36, 12.0, 194, 44, 1), + Dessert('Ice cream sandwich with milk', 339, 11.4, 49, 12.3, 236, 38, 1), + Dessert('Eclair with milk', 365, 18.4, 36, 14.0, 444, 36, 7), + Dessert('Cupcake with milk', 408, 6.1, 79, 12.3, 520, 33, 8), + Dessert('Gingerbread with milk', 459, 18.4, 61, 11.9, 434, 37, 16), + Dessert('Jelly bean with milk', 478, 2.4, 106, 8.0, 157, 30, 0), + Dessert('Lollipop with milk', 495, 2.6, 110, 8.0, 145, 30, 2), + Dessert('Honeycomb with milk', 511, 5.6, 99, 14.5, 669, 30, 45), + Dessert('Donut with milk', 555, 27.4, 63, 12.9, 433, 32, 22), + Dessert('KitKat with milk', 621, 28.4, 77, 15.0, 161, 42, 6), + + Dessert('Coconut slice and frozen yogurt', 318, 21.0, 31, 5.5, 96, 14, 7), + Dessert('Coconut slice and ice cream sandwich', 396, 24.0, 44, 5.8, 138, 8, 7), + Dessert('Coconut slice and eclair', 421, 31.0, 31, 7.5, 346, 6, 13), + Dessert('Coconut slice and cupcake', 464, 18.7, 74, 5.8, 422, 3, 14), + Dessert('Coconut slice and gingerbread', 515, 31.0, 56, 5.4, 316, 7, 22), + Dessert('Coconut slice and jelly bean', 534, 15.0, 101, 1.5, 59, 0, 6), + Dessert('Coconut slice and lollipop', 551, 15.2, 105, 1.5, 47, 0, 8), + Dessert('Coconut slice and honeycomb', 567, 18.2, 94, 8.0, 571, 0, 51), + Dessert('Coconut slice and donut', 611, 40.0, 58, 6.4, 335, 2, 28), + Dessert('Coconut slice and KitKat', 677, 41.0, 72, 8.5, 63, 12, 12), ]; void _sort(Comparable getField(Dessert d), bool ascending) { @@ -96,29 +98,31 @@ class DessertDataSource extends DataTableSource { @override DataRow getRow(int index) { assert(index >= 0); - if (index >= _desserts.length) return null; + if (index >= _desserts.length) + return null; final Dessert dessert = _desserts[index]; return DataRow.byIndex( - index: index, - selected: dessert.selected, - onSelectChanged: (bool value) { - if (dessert.selected != value) { - _selectedCount += value ? 1 : -1; - assert(_selectedCount >= 0); - dessert.selected = value; - notifyListeners(); - } - }, - cells: [ - DataCell(Text('${dessert.name}')), - DataCell(Text('${dessert.calories}')), - DataCell(Text('${dessert.fat.toStringAsFixed(1)}')), - DataCell(Text('${dessert.carbs}')), - DataCell(Text('${dessert.protein.toStringAsFixed(1)}')), - DataCell(Text('${dessert.sodium}')), - DataCell(Text('${dessert.calcium}%')), - DataCell(Text('${dessert.iron}%')), - ]); + index: index, + selected: dessert.selected, + onSelectChanged: (bool value) { + if (dessert.selected != value) { + _selectedCount += value ? 1 : -1; + assert(_selectedCount >= 0); + dessert.selected = value; + notifyListeners(); + } + }, + cells: [ + DataCell(Text('${dessert.name}')), + DataCell(Text('${dessert.calories}')), + DataCell(Text('${dessert.fat.toStringAsFixed(1)}')), + DataCell(Text('${dessert.carbs}')), + DataCell(Text('${dessert.protein.toStringAsFixed(1)}')), + DataCell(Text('${dessert.sodium}')), + DataCell(Text('${dessert.calcium}%')), + DataCell(Text('${dessert.iron}%')), + ], + ); } @override @@ -131,7 +135,8 @@ class DessertDataSource extends DataTableSource { int get selectedRowCount => _selectedCount; void _selectAll(bool checked) { - for (Dessert dessert in _desserts) dessert.selected = checked; + for (Dessert dessert in _desserts) + dessert.selected = checked; _selectedCount = checked ? _desserts.length : 0; notifyListeners(); } @@ -150,8 +155,7 @@ class _DataTableDemoState extends State { bool _sortAscending = true; final DessertDataSource _dessertsDataSource = DessertDataSource(); - void _sort( - Comparable getField(Dessert d), int columnIndex, bool ascending) { + void _sort(Comparable getField(Dessert d), int columnIndex, bool ascending) { _dessertsDataSource._sort(getField, ascending); setState(() { _sortColumnIndex = columnIndex; @@ -162,70 +166,71 @@ class _DataTableDemoState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Data tables'), - actions: [ - MaterialDemoDocumentationButton(DataTableDemo.routeName), - ], - ), - body: ListView(padding: const EdgeInsets.all(20.0), children: [ - PaginatedDataTable( + appBar: AppBar( + title: const Text('Data tables'), + actions: [ + MaterialDemoDocumentationButton(DataTableDemo.routeName), + ], + ), + body: Scrollbar( + child: ListView( + padding: const EdgeInsets.all(20.0), + children: [ + PaginatedDataTable( header: const Text('Nutrition'), rowsPerPage: _rowsPerPage, - onRowsPerPageChanged: (int value) { - setState(() { - _rowsPerPage = value; - }); - }, + onRowsPerPageChanged: (int value) { setState(() { _rowsPerPage = value; }); }, sortColumnIndex: _sortColumnIndex, sortAscending: _sortAscending, onSelectAll: _dessertsDataSource._selectAll, columns: [ DataColumn( - label: const Text('Dessert (100g serving)'), - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.name, columnIndex, ascending)), + label: const Text('Dessert (100g serving)'), + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.name, columnIndex, ascending), + ), DataColumn( - label: const Text('Calories'), - tooltip: - 'The total amount of food energy in the given serving size.', - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.calories, columnIndex, ascending)), + label: const Text('Calories'), + tooltip: 'The total amount of food energy in the given serving size.', + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.calories, columnIndex, ascending), + ), DataColumn( - label: const Text('Fat (g)'), - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.fat, columnIndex, ascending)), + label: const Text('Fat (g)'), + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.fat, columnIndex, ascending), + ), DataColumn( - label: const Text('Carbs (g)'), - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.carbs, columnIndex, ascending)), + label: const Text('Carbs (g)'), + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.carbs, columnIndex, ascending), + ), DataColumn( - label: const Text('Protein (g)'), - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.protein, columnIndex, ascending)), + label: const Text('Protein (g)'), + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.protein, columnIndex, ascending), + ), DataColumn( - label: const Text('Sodium (mg)'), - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.sodium, columnIndex, ascending)), + label: const Text('Sodium (mg)'), + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.sodium, columnIndex, ascending), + ), DataColumn( - label: const Text('Calcium (%)'), - tooltip: - 'The amount of calcium as a percentage of the recommended daily amount.', - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.calcium, columnIndex, ascending)), + label: const Text('Calcium (%)'), + tooltip: 'The amount of calcium as a percentage of the recommended daily amount.', + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.calcium, columnIndex, ascending), + ), DataColumn( - label: const Text('Iron (%)'), - numeric: true, - onSort: (int columnIndex, bool ascending) => _sort( - (Dessert d) => d.iron, columnIndex, ascending)), + label: const Text('Iron (%)'), + numeric: true, + onSort: (int columnIndex, bool ascending) => _sort((Dessert d) => d.iron, columnIndex, ascending), + ), ], - source: _dessertsDataSource) - ])); + source: _dessertsDataSource, + ), + ], + ), + ), + ); } } diff --git a/web/gallery/lib/demo/material/date_and_time_picker_demo.dart b/web/gallery/lib/demo/material/date_and_time_picker_demo.dart index d4d091f4c..9dda962c5 100644 --- a/web/gallery/lib/demo/material/date_and_time_picker_demo.dart +++ b/web/gallery/lib/demo/material/date_and_time_picker_demo.dart @@ -1,23 +1,23 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../gallery/demo.dart'; class _InputDropdown extends StatelessWidget { - const _InputDropdown( - {Key key, - this.child, - this.labelText, - this.valueText, - this.valueStyle, - this.onPressed}) - : super(key: key); + const _InputDropdown({ + Key key, + this.child, + this.labelText, + this.valueText, + this.valueStyle, + this.onPressed, + }) : super(key: key); final String labelText; final String valueText; @@ -40,9 +40,8 @@ class _InputDropdown extends StatelessWidget { children: [ Text(valueText, style: valueStyle), Icon(Icons.arrow_drop_down, - color: Theme.of(context).brightness == Brightness.light - ? Colors.grey.shade700 - : Colors.white70), + color: Theme.of(context).brightness == Brightness.light ? Colors.grey.shade700 : Colors.white70, + ), ], ), ), @@ -51,14 +50,14 @@ class _InputDropdown extends StatelessWidget { } class _DateTimePicker extends StatelessWidget { - const _DateTimePicker( - {Key key, - this.labelText, - this.selectedDate, - this.selectedTime, - this.selectDate, - this.selectTime}) - : super(key: key); + const _DateTimePicker({ + Key key, + this.labelText, + this.selectedDate, + this.selectedTime, + this.selectDate, + this.selectTime, + }) : super(key: key); final String labelText; final DateTime selectedDate; @@ -68,17 +67,22 @@ class _DateTimePicker extends StatelessWidget { Future _selectDate(BuildContext context) async { final DateTime picked = await showDatePicker( - context: context, - initialDate: selectedDate, - firstDate: DateTime(2015, 8), - lastDate: DateTime(2101)); - if (picked != null && picked != selectedDate) selectDate(picked); + context: context, + initialDate: selectedDate, + firstDate: DateTime(2015, 8), + lastDate: DateTime(2101), + ); + if (picked != null && picked != selectedDate) + selectDate(picked); } Future _selectTime(BuildContext context) async { - final TimeOfDay picked = - await showTimePicker(context: context, initialTime: selectedTime); - if (picked != null && picked != selectedTime) selectTime(picked); + final TimeOfDay picked = await showTimePicker( + context: context, + initialTime: selectedTime, + ); + if (picked != null && picked != selectedTime) + selectTime(picked); } @override @@ -93,9 +97,7 @@ class _DateTimePicker extends StatelessWidget { labelText: labelText, valueText: DateFormat.yMMMd().format(selectedDate), valueStyle: valueStyle, - onPressed: () { - _selectDate(context); - }, + onPressed: () { _selectDate(context); }, ), ), const SizedBox(width: 12.0), @@ -104,9 +106,7 @@ class _DateTimePicker extends StatelessWidget { child: _InputDropdown( valueText: selectedTime.format(context), valueStyle: valueStyle, - onPressed: () { - _selectTime(context); - }, + onPressed: () { _selectTime(context); }, ), ), ], @@ -126,12 +126,7 @@ class _DateAndTimePickerDemoState extends State { TimeOfDay _fromTime = const TimeOfDay(hour: 7, minute: 28); DateTime _toDate = DateTime.now(); TimeOfDay _toTime = const TimeOfDay(hour: 7, minute: 28); - final List _allActivities = [ - 'hiking', - 'swimming', - 'boating', - 'fishing' - ]; + final List _allActivities = ['hiking', 'swimming', 'boating', 'fishing']; String _activity = 'fishing'; @override @@ -139,9 +134,7 @@ class _DateAndTimePickerDemoState extends State { return Scaffold( appBar: AppBar( title: const Text('Date and time pickers'), - actions: [ - MaterialDemoDocumentationButton(DateAndTimePickerDemo.routeName) - ], + actions: [MaterialDemoDocumentationButton(DateAndTimePickerDemo.routeName)], ), body: DropdownButtonHideUnderline( child: SafeArea( @@ -162,10 +155,7 @@ class _DateAndTimePickerDemoState extends State { decoration: const InputDecoration( labelText: 'Location', ), - style: Theme.of(context) - .textTheme - .display1 - .copyWith(fontSize: 20.0), + style: Theme.of(context).textTheme.display1.copyWith(fontSize: 20.0), ), _DateTimePicker( labelText: 'From', @@ -212,8 +202,7 @@ class _DateAndTimePickerDemoState extends State { _activity = newValue; }); }, - items: _allActivities - .map>((String value) { + items: _allActivities.map>((String value) { return DropdownMenuItem( value: value, child: Text(value), diff --git a/web/gallery/lib/demo/material/dialog_demo.dart b/web/gallery/lib/demo/material/dialog_demo.dart index adf86fe74..6f4116eb1 100644 --- a/web/gallery/lib/demo/material/dialog_demo.dart +++ b/web/gallery/lib/demo/material/dialog_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; import 'full_screen_dialog_demo.dart'; @@ -17,13 +17,11 @@ enum DialogDemoAction { const String _alertWithoutTitleText = 'Discard draft?'; const String _alertWithTitleText = - 'Let Google help apps determine location. This means sending anonymous location ' - 'data to Google, even when no apps are running.'; + 'Let Google help apps determine location. This means sending anonymous location ' + 'data to Google, even when no apps are running.'; class DialogDemoItem extends StatelessWidget { - const DialogDemoItem( - {Key key, this.icon, this.color, this.text, this.onPressed}) - : super(key: key); + const DialogDemoItem({ Key key, this.icon, this.color, this.text, this.onPressed }) : super(key: key); final IconData icon; final Color color; @@ -68,15 +66,16 @@ class DialogDemoState extends State { _selectedTime = TimeOfDay(hour: now.hour, minute: now.minute); } - void showDemoDialog({BuildContext context, Widget child}) { + void showDemoDialog({ BuildContext context, Widget child }) { showDialog( context: context, builder: (BuildContext context) => child, - ).then((T value) { - // The value passed to Navigator.pop() or null. + ) + .then((T value) { // The value passed to Navigator.pop() or null. if (value != null) { - _scaffoldKey.currentState - .showSnackBar(SnackBar(content: Text('You selected: $value'))); + _scaffoldKey.currentState.showSnackBar(SnackBar( + content: Text('You selected: $value'), + )); } }); } @@ -84,128 +83,132 @@ class DialogDemoState extends State { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - final TextStyle dialogTextStyle = - theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); + final TextStyle dialogTextStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - title: const Text('Dialogs'), - actions: [ - MaterialDemoDocumentationButton(DialogDemo.routeName) - ], - ), - body: ListView( - padding: - const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0), - children: [ - RaisedButton( - child: const Text('ALERT'), - onPressed: () { - showDemoDialog( - context: context, - child: AlertDialog( - content: Text(_alertWithoutTitleText, - style: dialogTextStyle), - actions: [ - FlatButton( - child: const Text('CANCEL'), - onPressed: () { - Navigator.pop( - context, DialogDemoAction.cancel); - }), - FlatButton( - child: const Text('DISCARD'), - onPressed: () { - Navigator.pop( - context, DialogDemoAction.discard); - }) - ])); - }), - RaisedButton( - child: const Text('ALERT WITH TITLE'), - onPressed: () { - showDemoDialog( - context: context, - child: AlertDialog( - title: - const Text('Use Google\'s location service?'), - content: Text(_alertWithTitleText, - style: dialogTextStyle), - actions: [ - FlatButton( - child: const Text('DISAGREE'), - onPressed: () { - Navigator.pop( - context, DialogDemoAction.disagree); - }), - FlatButton( - child: const Text('AGREE'), - onPressed: () { - Navigator.pop( - context, DialogDemoAction.agree); - }) - ])); - }), - RaisedButton( - child: const Text('SIMPLE'), - onPressed: () { - showDemoDialog( - context: context, - child: SimpleDialog( - title: const Text('Set backup account'), - children: [ - DialogDemoItem( - icon: Icons.account_circle, - color: theme.primaryColor, - text: 'username@gmail.com', - onPressed: () { - Navigator.pop( - context, 'username@gmail.com'); - }), - DialogDemoItem( - icon: Icons.account_circle, - color: theme.primaryColor, - text: 'user02@gmail.com', - onPressed: () { - Navigator.pop(context, 'user02@gmail.com'); - }), - DialogDemoItem( - icon: Icons.add_circle, - text: 'add account', - color: theme.disabledColor) - ])); - }), - RaisedButton( - child: const Text('CONFIRMATION'), - onPressed: () { - showTimePicker(context: context, initialTime: _selectedTime) - .then((TimeOfDay value) { - if (value != null && value != _selectedTime) { - _selectedTime = value; - _scaffoldKey.currentState.showSnackBar(SnackBar( - content: Text( - 'You selected: ${value.format(context)}'))); - } - }); - }), - RaisedButton( - child: const Text('FULLSCREEN'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - FullScreenDialogDemo(), - fullscreenDialog: true, - )); - }), - ] - // Add a little space between the buttons - .map((Widget button) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: button); - }).toList())); + key: _scaffoldKey, + appBar: AppBar( + title: const Text('Dialogs'), + actions: [MaterialDemoDocumentationButton(DialogDemo.routeName)], + ), + body: ListView( + padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 72.0), + children: [ + RaisedButton( + child: const Text('ALERT'), + onPressed: () { + showDemoDialog( + context: context, + child: AlertDialog( + content: Text( + _alertWithoutTitleText, + style: dialogTextStyle, + ), + actions: [ + FlatButton( + child: const Text('CANCEL'), + onPressed: () { Navigator.pop(context, DialogDemoAction.cancel); }, + ), + FlatButton( + child: const Text('DISCARD'), + onPressed: () { Navigator.pop(context, DialogDemoAction.discard); }, + ), + ], + ), + ); + }, + ), + RaisedButton( + child: const Text('ALERT WITH TITLE'), + onPressed: () { + showDemoDialog( + context: context, + child: AlertDialog( + title: const Text('Use Google\'s location service?'), + content: Text( + _alertWithTitleText, + style: dialogTextStyle, + ), + actions: [ + FlatButton( + child: const Text('DISAGREE'), + onPressed: () { Navigator.pop(context, DialogDemoAction.disagree); }, + ), + FlatButton( + child: const Text('AGREE'), + onPressed: () { Navigator.pop(context, DialogDemoAction.agree); }, + ), + ], + ), + ); + }, + ), + RaisedButton( + child: const Text('SIMPLE'), + onPressed: () { + showDemoDialog( + context: context, + child: SimpleDialog( + title: const Text('Set backup account'), + children: [ + DialogDemoItem( + icon: Icons.account_circle, + color: theme.primaryColor, + text: 'username@gmail.com', + onPressed: () { Navigator.pop(context, 'username@gmail.com'); }, + ), + DialogDemoItem( + icon: Icons.account_circle, + color: theme.primaryColor, + text: 'user02@gmail.com', + onPressed: () { Navigator.pop(context, 'user02@gmail.com'); }, + ), + DialogDemoItem( + icon: Icons.add_circle, + text: 'add account', + color: theme.disabledColor, + ), + ], + ), + ); + }, + ), + RaisedButton( + child: const Text('CONFIRMATION'), + onPressed: () { + showTimePicker( + context: context, + initialTime: _selectedTime, + ) + .then((TimeOfDay value) { + if (value != null && value != _selectedTime) { + _selectedTime = value; + _scaffoldKey.currentState.showSnackBar(SnackBar( + content: Text('You selected: ${value.format(context)}'), + )); + } + }); + }, + ), + RaisedButton( + child: const Text('FULLSCREEN'), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => FullScreenDialogDemo(), + fullscreenDialog: true, + )); + }, + ), + ] + // Add a little space between the buttons + .map((Widget button) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: button, + ); + }) + .toList(), + ), + ); } } diff --git a/web/gallery/lib/demo/material/drawer_demo.dart b/web/gallery/lib/demo/material/drawer_demo.dart index 8ef8567db..417b8c93b 100644 --- a/web/gallery/lib/demo/material/drawer_demo.dart +++ b/web/gallery/lib/demo/material/drawer_demo.dart @@ -1,11 +1,17 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../../gallery/demo.dart'; +const String _kAsset0 = 'people/square/trevor.png'; +const String _kAsset1 = 'people/square/stella.png'; +const String _kAsset2 = 'people/square/sandra.png'; +const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; + class DrawerDemo extends StatefulWidget { static const String routeName = '/material/drawer'; @@ -17,11 +23,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { final GlobalKey _scaffoldKey = GlobalKey(); static const List _drawerContents = [ - 'A', - 'B', - 'C', - 'D', - 'E', + 'A', 'B', 'C', 'D', 'E', ]; static final Animatable _drawerDetailsTween = Tween( @@ -70,13 +72,15 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { void _showNotImplementedMessage() { Navigator.pop(context); // Dismiss the drawer. - _scaffoldKey.currentState.showSnackBar( - const SnackBar(content: Text("The drawer's items don't do anything"))); + _scaffoldKey.currentState.showSnackBar(const SnackBar( + content: Text("The drawer's items don't do anything"), + )); } @override Widget build(BuildContext context) { return Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: _scaffoldKey, appBar: AppBar( leading: IconButton( @@ -88,9 +92,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { }, ), title: const Text('Navigation drawer'), - actions: [ - MaterialDemoDocumentationButton(DrawerDemo.routeName) - ], + actions: [MaterialDemoDocumentationButton(DrawerDemo.routeName)], ), drawer: Drawer( child: Column( @@ -98,6 +100,44 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { UserAccountsDrawerHeader( accountName: const Text('Trevor Widget'), accountEmail: const Text('trevor.widget@example.com'), + currentAccountPicture: const CircleAvatar( + backgroundImage: AssetImage( + _kAsset0, + package: _kGalleryAssetsPackage, + ), + ), + otherAccountsPictures: [ + GestureDetector( + dragStartBehavior: DragStartBehavior.down, + onTap: () { + _onOtherAccountsTap(context); + }, + child: Semantics( + label: 'Switch to Account B', + child: const CircleAvatar( + backgroundImage: AssetImage( + _kAsset1, + package: _kGalleryAssetsPackage, + ), + ), + ), + ), + GestureDetector( + dragStartBehavior: DragStartBehavior.down, + onTap: () { + _onOtherAccountsTap(context); + }, + child: Semantics( + label: 'Switch to Account C', + child: const CircleAvatar( + backgroundImage: AssetImage( + _kAsset2, + package: _kGalleryAssetsPackage, + ), + ), + ), + ), + ], margin: EdgeInsets.zero, onDetailsPressed: () { _showDrawerContents = !_showDrawerContents; @@ -113,6 +153,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { removeTop: true, child: Expanded( child: ListView( + dragStartBehavior: DragStartBehavior.down, padding: const EdgeInsets.only(top: 8.0), children: [ Stack( @@ -179,11 +220,19 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { Container( width: 100.0, height: 100.0, + decoration: const BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: AssetImage( + _kAsset0, + package: _kGalleryAssetsPackage, + ), + ), + ), ), Padding( padding: const EdgeInsets.only(top: 8.0), - child: Text( - 'Tap here to open the drawer', + child: Text('Tap here to open the drawer', style: Theme.of(context).textTheme.subhead, ), ), @@ -194,4 +243,23 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { ), ); } + + void _onOtherAccountsTap(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Account switching not implemented.'), + actions: [ + FlatButton( + child: const Text('OK'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ); + }, + ); + } } diff --git a/web/gallery/lib/demo/material/editable_text_demo.dart b/web/gallery/lib/demo/material/editable_text_demo.dart deleted file mode 100644 index 034db5bd7..000000000 --- a/web/gallery/lib/demo/material/editable_text_demo.dart +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -class EditableTextDemo extends StatefulWidget { - static String routeName = '/material/editable_text'; - - @override - State createState() => EditableTextDemoState(); -} - -class EditableTextDemoState extends State { - final cyanController = TextEditingController(text: 'Cyan'); - final orangeController = TextEditingController(text: 'Orange'); - final thickController = TextEditingController(text: 'Thick Rounded Cursor'); - final multiController = - TextEditingController(text: 'First line\nSecond line'); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Text Editing'), - centerTitle: true, - ), - body: ListView( - children: [ - field( - cyanController, - color: Colors.cyan.shade50, - selection: Colors.cyan.shade200, - cursor: Colors.cyan.shade900, - ), - field( - orangeController, - color: Colors.orange.shade50, - selection: Colors.orange.shade200, - cursor: Colors.orange.shade900, - center: true, - ), - field( - thickController, - color: Colors.white, - selection: Colors.grey.shade200, - cursor: Colors.red.shade900, - radius: const Radius.circular(2), - cursorWidth: 8, - ), - Banner( - child: TextField( - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - controller: multiController, - maxLines: 3, - ), - message: 'W.I.P', - textDirection: TextDirection.ltr, - location: BannerLocation.bottomEnd, - ), - ], - ), - ); - } -} - -Widget field( - TextEditingController controller, { - Color color, - Color selection, - Color cursor, - Radius radius = null, - double cursorWidth = 2, - bool center = false, -}) { - return Theme( - data: ThemeData(textSelectionColor: selection), - child: Container( - color: color, - child: TextField( - textAlign: center ? TextAlign.center : TextAlign.start, - decoration: InputDecoration( - contentPadding: EdgeInsets.fromLTRB(8, 16, 8, 16), - ), - controller: controller, - cursorColor: cursor, - cursorRadius: radius, - cursorWidth: cursorWidth, - ), - ), - ); -} diff --git a/web/gallery/lib/demo/material/elevation_demo.dart b/web/gallery/lib/demo/material/elevation_demo.dart index 7f3f83ba4..0198657bb 100644 --- a/web/gallery/lib/demo/material/elevation_demo.dart +++ b/web/gallery/lib/demo/material/elevation_demo.dart @@ -1,8 +1,4 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -58,12 +54,10 @@ class _ElevationDemoState extends State { onPressed: () { setState(() => _showElevation = !_showElevation); }, - ) + ), ], ), - body: ListView( - children: buildCards(), - ), + body: Scrollbar(child: ListView(children: buildCards())), ); } } diff --git a/web/gallery/lib/demo/material/expansion_panels_demo.dart b/web/gallery/lib/demo/material/expansion_panels_demo.dart index a70fb70fe..c65438f55 100644 --- a/web/gallery/lib/demo/material/expansion_panels_demo.dart +++ b/web/gallery/lib/demo/material/expansion_panels_demo.dart @@ -1,19 +1,28 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @visibleForTesting -enum Location { Barbados, Bahamas, Bermuda } +enum Location { + Barbados, + Bahamas, + Bermuda +} typedef DemoItemBodyBuilder = Widget Function(DemoItem item); typedef ValueToString = String Function(T value); class DualHeaderWithHint extends StatelessWidget { - const DualHeaderWithHint({this.name, this.value, this.hint, this.showHint}); + const DualHeaderWithHint({ + this.name, + this.value, + this.hint, + this.showHint, + }); final String name; final String value; @@ -27,8 +36,7 @@ class DualHeaderWithHint extends StatelessWidget { firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn), secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn), sizeCurve: Curves.fastOutSlowIn, - crossFadeState: - isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: const Duration(milliseconds: 200), ); } @@ -38,37 +46,45 @@ class DualHeaderWithHint extends StatelessWidget { final ThemeData theme = Theme.of(context); final TextTheme textTheme = theme.textTheme; - return Row(children: [ - Expanded( - flex: 2, - child: Container( - margin: const EdgeInsets.only(left: 24.0), - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text( - name, - style: textTheme.body1.copyWith(fontSize: 15.0), + return Row( + children: [ + Expanded( + flex: 2, + child: Container( + margin: const EdgeInsets.only(left: 24.0), + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + name, + style: textTheme.body1.copyWith(fontSize: 15.0), + ), ), ), ), - ), - Expanded( + Expanded( flex: 3, child: Container( - margin: const EdgeInsets.only(left: 24.0), - child: _crossFade( - Text(value, - style: textTheme.caption.copyWith(fontSize: 15.0)), - Text(hint, style: textTheme.caption.copyWith(fontSize: 15.0)), - showHint))) - ]); + margin: const EdgeInsets.only(left: 24.0), + child: _crossFade( + Text(value, style: textTheme.caption.copyWith(fontSize: 15.0)), + Text(hint, style: textTheme.caption.copyWith(fontSize: 15.0)), + showHint, + ), + ), + ), + ], + ); } } class CollapsibleBody extends StatelessWidget { - const CollapsibleBody( - {this.margin = EdgeInsets.zero, this.child, this.onSave, this.onCancel}); + const CollapsibleBody({ + this.margin = EdgeInsets.zero, + this.child, + this.onSave, + this.onCancel, + }); final EdgeInsets margin; final Widget child; @@ -80,42 +96,62 @@ class CollapsibleBody extends StatelessWidget { final ThemeData theme = Theme.of(context); final TextTheme textTheme = theme.textTheme; - return Column(children: [ - Container( - margin: const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0) - - margin, + return Column( + children: [ + Container( + margin: const EdgeInsets.only( + left: 24.0, + right: 24.0, + bottom: 24.0, + ) - margin, child: Center( - child: DefaultTextStyle( - style: textTheme.caption.copyWith(fontSize: 15.0), - child: child))), - const Divider(height: 1.0), - Container( + child: DefaultTextStyle( + style: textTheme.caption.copyWith(fontSize: 15.0), + child: child, + ), + ), + ), + const Divider(height: 1.0), + Container( padding: const EdgeInsets.symmetric(vertical: 16.0), - child: - Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( margin: const EdgeInsets.only(right: 8.0), child: FlatButton( - onPressed: onCancel, - child: const Text('CANCEL', - style: TextStyle( - color: Colors.black54, - fontSize: 15.0, - fontWeight: FontWeight.w500)))), - Container( + onPressed: onCancel, + child: const Text('CANCEL', style: TextStyle( + color: Colors.black54, + fontSize: 15.0, + fontWeight: FontWeight.w500, + )), + ), + ), + Container( margin: const EdgeInsets.only(right: 8.0), child: FlatButton( - onPressed: onSave, - textTheme: ButtonTextTheme.accent, - child: const Text('SAVE'))) - ])) - ]); + onPressed: onSave, + textTheme: ButtonTextTheme.accent, + child: const Text('SAVE'), + ), + ), + ], + ), + ), + ], + ); } } class DemoItem { - DemoItem({this.name, this.value, this.hint, this.builder, this.valueToString}) - : textController = TextEditingController(text: valueToString(value)); + DemoItem({ + this.name, + this.value, + this.hint, + this.builder, + this.valueToString, + }) : textController = TextEditingController(text: valueToString(value)); final String name; final String hint; @@ -128,10 +164,11 @@ class DemoItem { ExpansionPanelHeaderBuilder get headerBuilder { return (BuildContext context, bool isExpanded) { return DualHeaderWithHint( - name: name, - value: valueToString(value), - hint: hint, - showHint: isExpanded); + name: name, + value: valueToString(value), + hint: hint, + showHint: isExpanded, + ); }; } @@ -170,14 +207,8 @@ class _ExpansionPanelsDemoState extends State { builder: (BuildContext context) { return CollapsibleBody( margin: const EdgeInsets.symmetric(horizontal: 16.0), - onSave: () { - Form.of(context).save(); - close(); - }, - onCancel: () { - Form.of(context).reset(); - close(); - }, + onSave: () { Form.of(context).save(); close(); }, + onCancel: () { Form.of(context).reset(); close(); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextFormField( @@ -186,9 +217,7 @@ class _ExpansionPanelsDemoState extends State { hintText: item.hint, labelText: item.name, ), - onSaved: (String value) { - item.value = value; - }, + onSaved: (String value) { item.value = value; }, ), ), ); @@ -198,104 +227,101 @@ class _ExpansionPanelsDemoState extends State { }, ), DemoItem( - name: 'Location', - value: Location.Bahamas, - hint: 'Select location', - valueToString: (Location location) => - location.toString().split('.')[1], - builder: (DemoItem item) { - void close() { - setState(() { - item.isExpanded = false; - }); - } - - return Form(child: Builder(builder: (BuildContext context) { - return CollapsibleBody( - onSave: () { - Form.of(context).save(); - close(); - }, - onCancel: () { - Form.of(context).reset(); - close(); - }, - child: FormField( + name: 'Location', + value: Location.Bahamas, + hint: 'Select location', + valueToString: (Location location) => location.toString().split('.')[1], + builder: (DemoItem item) { + void close() { + setState(() { + item.isExpanded = false; + }); + } + return Form( + child: Builder( + builder: (BuildContext context) { + return CollapsibleBody( + onSave: () { Form.of(context).save(); close(); }, + onCancel: () { Form.of(context).reset(); close(); }, + child: FormField( initialValue: item.value, - onSaved: (Location result) { - item.value = result; - }, + onSaved: (Location result) { item.value = result; }, builder: (FormFieldState field) { return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RadioListTile( - value: Location.Bahamas, - title: const Text('Bahamas'), - groupValue: field.value, - onChanged: field.didChange, - ), - RadioListTile( - value: Location.Barbados, - title: const Text('Barbados'), - groupValue: field.value, - onChanged: field.didChange, - ), - RadioListTile( - value: Location.Bermuda, - title: const Text('Bermuda'), - groupValue: field.value, - onChanged: field.didChange, - ), - ]); - }), - ); - })); - }), + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RadioListTile( + value: Location.Bahamas, + title: const Text('Bahamas'), + groupValue: field.value, + onChanged: field.didChange, + ), + RadioListTile( + value: Location.Barbados, + title: const Text('Barbados'), + groupValue: field.value, + onChanged: field.didChange, + ), + RadioListTile( + value: Location.Bermuda, + title: const Text('Bermuda'), + groupValue: field.value, + onChanged: field.didChange, + ), + ], + ); + }, + ), + ); + } + ), + ); + }, + ), DemoItem( - name: 'Sun', - value: 80.0, - hint: 'Select sun level', - valueToString: (double amount) => '${amount.round()}', - builder: (DemoItem item) { - void close() { - setState(() { - item.isExpanded = false; - }); - } + name: 'Sun', + value: 80.0, + hint: 'Select sun level', + valueToString: (double amount) => '${amount.round()}', + builder: (DemoItem item) { + void close() { + setState(() { + item.isExpanded = false; + }); + } - return Form(child: Builder(builder: (BuildContext context) { - return CollapsibleBody( - onSave: () { - Form.of(context).save(); - close(); - }, - onCancel: () { - Form.of(context).reset(); - close(); - }, - child: FormField( - initialValue: item.value, - onSaved: (double value) { - item.value = value; - }, - builder: (FormFieldState field) { - return Slider( - min: 0.0, - max: 100.0, - divisions: 5, - activeColor: - Colors.orange[100 + (field.value * 5.0).round()], - label: '${field.value.round()}', - value: field.value, - onChanged: field.didChange, - ); - }, - ), - ); - })); - }) + return Form( + child: Builder( + builder: (BuildContext context) { + return CollapsibleBody( + onSave: () { Form.of(context).save(); close(); }, + onCancel: () { Form.of(context).reset(); close(); }, + child: FormField( + initialValue: item.value, + onSaved: (double value) { item.value = value; }, + builder: (FormFieldState field) { + return Container( + // Allow room for the value indicator. + padding: const EdgeInsets.only(top: 44.0), + child: Slider( + min: 0.0, + max: 100.0, + divisions: 5, + activeColor: Colors.orange[100 + (field.value * 5.0).round()], + label: '${field.value.round()}', + value: field.value, + onChanged: field.didChange, + ), + ); + }, + ), + ); + } + ), + ); + }, + ), ]; } @@ -315,18 +341,19 @@ class _ExpansionPanelsDemoState extends State { child: Container( margin: const EdgeInsets.all(24.0), child: ExpansionPanelList( - expansionCallback: (int index, bool isExpanded) { - setState(() { - _demoItems[index].isExpanded = !isExpanded; - }); - }, - children: - _demoItems.map((DemoItem item) { - return ExpansionPanel( - isExpanded: item.isExpanded, - headerBuilder: item.headerBuilder, - body: item.build()); - }).toList()), + expansionCallback: (int index, bool isExpanded) { + setState(() { + _demoItems[index].isExpanded = !isExpanded; + }); + }, + children: _demoItems.map((DemoItem item) { + return ExpansionPanel( + isExpanded: item.isExpanded, + headerBuilder: item.headerBuilder, + body: item.build(), + ); + }).toList(), + ), ), ), ), diff --git a/web/gallery/lib/demo/material/expansion_tile_list_demo.dart b/web/gallery/lib/demo/material/expansion_tile_list_demo.dart new file mode 100644 index 000000000..10ad11f2f --- /dev/null +++ b/web/gallery/lib/demo/material/expansion_tile_list_demo.dart @@ -0,0 +1,40 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../../gallery/demo.dart'; + +class ExpansionTileListDemo extends StatelessWidget { + static const String routeName = '/material/expansion-tile-list'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Expand/collapse list control'), + actions: [MaterialDemoDocumentationButton(routeName)], + ), + body: Scrollbar( + child: ListView( + children: [ + const ListTile(title: Text('Top')), + ExpansionTile( + title: const Text('Sublist'), + backgroundColor: Theme.of(context).accentColor.withOpacity(0.025), + children: const [ + ListTile(title: Text('One')), + ListTile(title: Text('Two')), + // https://en.wikipedia.org/wiki/Free_Four + ListTile(title: Text('Free')), + ListTile(title: Text('Four')), + ], + ), + const ListTile(title: Text('Bottom')), + ], + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/material/full_screen_dialog_demo.dart b/web/gallery/lib/demo/material/full_screen_dialog_demo.dart index 65831d9ca..ab4a29592 100644 --- a/web/gallery/lib/demo/material/full_screen_dialog_demo.dart +++ b/web/gallery/lib/demo/material/full_screen_dialog_demo.dart @@ -1,14 +1,14 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; // This demo is based on -// https://material.google.com/components/dialogs.html#dialogs-full-screen-dialogs +// https://material.io/design/components/dialogs.html#full-screen-dialog enum DismissDialogAction { cancel, @@ -17,11 +17,11 @@ enum DismissDialogAction { } class DateTimeItem extends StatelessWidget { - DateTimeItem({Key key, DateTime dateTime, @required this.onChanged}) - : assert(onChanged != null), - date = DateTime(dateTime.year, dateTime.month, dateTime.day), - time = TimeOfDay(hour: dateTime.hour, minute: dateTime.minute), - super(key: key); + DateTimeItem({ Key key, DateTime dateTime, @required this.onChanged }) + : assert(onChanged != null), + date = DateTime(dateTime.year, dateTime.month, dateTime.day), + time = TimeOfDay(hour: dateTime.hour, minute: dateTime.minute), + super(key: key); final DateTime date; final TimeOfDay time; @@ -32,55 +32,66 @@ class DateTimeItem extends StatelessWidget { final ThemeData theme = Theme.of(context); return DefaultTextStyle( - style: theme.textTheme.subhead, - child: Row(children: [ + style: theme.textTheme.subhead, + child: Row( + children: [ Expanded( - child: Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: theme.dividerColor))), - child: InkWell( - onTap: () { - showDatePicker( - context: context, - initialDate: date, - firstDate: - date.subtract(const Duration(days: 30)), - lastDate: date.add(const Duration(days: 30))) - .then((DateTime value) { - if (value != null) - onChanged(DateTime(value.year, value.month, - value.day, time.hour, time.minute)); - }); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(DateFormat('EEE, MMM d yyyy').format(date)), - const Icon(Icons.arrow_drop_down, - color: Colors.black54), - ])))), - Container( - margin: const EdgeInsets.only(left: 8.0), + child: Container( padding: const EdgeInsets.symmetric(vertical: 8.0), decoration: BoxDecoration( - border: - Border(bottom: BorderSide(color: theme.dividerColor))), + border: Border(bottom: BorderSide(color: theme.dividerColor)) + ), child: InkWell( - onTap: () { - showTimePicker(context: context, initialTime: time) - .then((TimeOfDay value) { - if (value != null) - onChanged(DateTime(date.year, date.month, date.day, - value.hour, value.minute)); - }); - }, - child: Row(children: [ - Text('${time.format(context)}'), + onTap: () { + showDatePicker( + context: context, + initialDate: date, + firstDate: date.subtract(const Duration(days: 30)), + lastDate: date.add(const Duration(days: 30)), + ) + .then((DateTime value) { + if (value != null) + onChanged(DateTime(value.year, value.month, value.day, time.hour, time.minute)); + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(DateFormat('EEE, MMM d yyyy').format(date)), const Icon(Icons.arrow_drop_down, color: Colors.black54), - ]))) - ])); + ], + ), + ), + ), + ), + Container( + margin: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: theme.dividerColor)) + ), + child: InkWell( + onTap: () { + showTimePicker( + context: context, + initialTime: time, + ) + .then((TimeOfDay value) { + if (value != null) + onChanged(DateTime(date.year, date.month, date.day, value.hour, value.minute)); + }); + }, + child: Row( + children: [ + Text('${time.format(context)}'), + const Icon(Icons.arrow_drop_down, color: Colors.black54), + ], + ), + ), + ), + ], + ), + ); } } @@ -100,35 +111,37 @@ class FullScreenDialogDemoState extends State { Future _onWillPop() async { _saveNeeded = _hasLocation || _hasName || _saveNeeded; - if (!_saveNeeded) return true; + if (!_saveNeeded) + return true; final ThemeData theme = Theme.of(context); - final TextStyle dialogTextStyle = - theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); + final TextStyle dialogTextStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); return await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: Text('Discard new event?', style: dialogTextStyle), - actions: [ - FlatButton( - child: const Text('CANCEL'), - onPressed: () { - Navigator.of(context).pop( - false); // Pops the confirmation dialog but not the page. - }), - FlatButton( - child: const Text('DISCARD'), - onPressed: () { - Navigator.of(context).pop( - true); // Returning true to _onWillPop will pop again. - }) - ], - ); - }, - ) ?? - false; + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text( + 'Discard new event?', + style: dialogTextStyle, + ), + actions: [ + FlatButton( + child: const Text('CANCEL'), + onPressed: () { + Navigator.of(context).pop(false); // Pops the confirmation dialog but not the page. + }, + ), + FlatButton( + child: const Text('DISCARD'), + onPressed: () { + Navigator.of(context).pop(true); // Returning true to _onWillPop will pop again. + }, + ), + ], + ); + }, + ) ?? false; } @override @@ -137,96 +150,119 @@ class FullScreenDialogDemoState extends State { return Scaffold( appBar: AppBar( - title: Text(_hasName ? _eventName : 'Event Name TBD'), - actions: [ - FlatButton( - child: Text('SAVE', - style: theme.textTheme.body1.copyWith(color: Colors.white)), - onPressed: () { - Navigator.pop(context, DismissDialogAction.save); - }) - ]), + title: Text(_hasName ? _eventName : 'Event Name TBD'), + actions: [ + FlatButton( + child: Text('SAVE', style: theme.textTheme.body1.copyWith(color: Colors.white)), + onPressed: () { + Navigator.pop(context, DismissDialogAction.save); + }, + ), + ], + ), body: Form( - onWillPop: _onWillPop, + onWillPop: _onWillPop, + child: Scrollbar( child: ListView( - padding: const EdgeInsets.all(16.0), - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), - alignment: Alignment.bottomLeft, - child: TextField( - decoration: const InputDecoration( - labelText: 'Event name', filled: true), - style: theme.textTheme.headline, - onChanged: (String value) { - setState(() { - _hasName = value.isNotEmpty; - if (_hasName) { - _eventName = value; - } - }); - })), - Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), - alignment: Alignment.bottomLeft, - child: TextField( - decoration: const InputDecoration( - labelText: 'Location', - hintText: 'Where is the event?', - filled: true), - onChanged: (String value) { - setState(() { - _hasLocation = value.isNotEmpty; - }); - })), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('From', style: theme.textTheme.caption), - DateTimeItem( - dateTime: _fromDateTime, - onChanged: (DateTime value) { - setState(() { - _fromDateTime = value; - _saveNeeded = true; - }); - }) - ]), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('To', style: theme.textTheme.caption), - DateTimeItem( - dateTime: _toDateTime, - onChanged: (DateTime value) { - setState(() { - _toDateTime = value; - _saveNeeded = true; - }); - }), - const Text('All-day'), - ]), - Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: theme.dividerColor))), - child: Row(children: [ - Checkbox( - value: _allDayValue, - onChanged: (bool value) { - setState(() { - _allDayValue = value; - _saveNeeded = true; - }); - }), - const Text('All-day'), - ])) - ].map((Widget child) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), - height: 96.0, - child: child); - }).toList())), + padding: const EdgeInsets.all(16.0), + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 8.0), + alignment: Alignment.bottomLeft, + child: TextField( + decoration: const InputDecoration( + labelText: 'Event name', + filled: true, + ), + style: theme.textTheme.headline, + onChanged: (String value) { + setState(() { + _hasName = value.isNotEmpty; + if (_hasName) { + _eventName = value; + } + }); + }, + ), + ), + Container( + padding: const EdgeInsets.symmetric(vertical: 8.0), + alignment: Alignment.bottomLeft, + child: TextField( + decoration: const InputDecoration( + labelText: 'Location', + hintText: 'Where is the event?', + filled: true, + ), + onChanged: (String value) { + setState(() { + _hasLocation = value.isNotEmpty; + }); + }, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('From', style: theme.textTheme.caption), + DateTimeItem( + dateTime: _fromDateTime, + onChanged: (DateTime value) { + setState(() { + _fromDateTime = value; + _saveNeeded = true; + }); + }, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('To', style: theme.textTheme.caption), + DateTimeItem( + dateTime: _toDateTime, + onChanged: (DateTime value) { + setState(() { + _toDateTime = value; + _saveNeeded = true; + }); + }, + ), + const Text('All-day'), + ], + ), + Container( + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: theme.dividerColor)) + ), + child: Row( + children: [ + Checkbox( + value: _allDayValue, + onChanged: (bool value) { + setState(() { + _allDayValue = value; + _saveNeeded = true; + }); + }, + ), + const Text('All-day'), + ], + ), + ), + ] + .map((Widget child) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8.0), + height: 96.0, + child: child, + ); + }) + .toList(), + ), + ), + ), ); } } diff --git a/web/gallery/lib/demo/material/grid_list_demo.dart b/web/gallery/lib/demo/material/grid_list_demo.dart index 42593ef8b..2267606cd 100644 --- a/web/gallery/lib/demo/material/grid_list_demo.dart +++ b/web/gallery/lib/demo/material/grid_list_demo.dart @@ -1,12 +1,16 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; -enum GridDemoTileStyle { imageOnly, oneLine, twoLine } +enum GridDemoTileStyle { + imageOnly, + oneLine, + twoLine +} typedef BannerTapCallback = void Function(Photo photo); @@ -30,15 +34,11 @@ class Photo { bool isFavorite; String get tag => assetName; // Assuming that all asset names are unique. - bool get isValid => - assetName != null && - title != null && - caption != null && - isFavorite != null; + bool get isValid => assetName != null && title != null && caption != null && isFavorite != null; } class GridPhotoViewer extends StatefulWidget { - const GridPhotoViewer({Key key, this.photo}) : super(key: key); + const GridPhotoViewer({ Key key, this.photo }) : super(key: key); final Photo photo; @@ -61,8 +61,7 @@ class _GridTitleText extends StatelessWidget { } } -class _GridPhotoViewerState extends State - with SingleTickerProviderStateMixin { +class _GridPhotoViewerState extends State with SingleTickerProviderStateMixin { AnimationController _controller; Animation _flingAnimation; Offset _offset = Offset.zero; @@ -88,8 +87,7 @@ class _GridPhotoViewerState extends State Offset _clampOffset(Offset offset) { final Size size = context.size; final Offset minOffset = Offset(size.width, size.height) * (1.0 - _scale); - return Offset( - offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0)); + return Offset(offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0)); } void _handleFlingAnimation() { @@ -117,11 +115,14 @@ class _GridPhotoViewerState extends State void _handleOnScaleEnd(ScaleEndDetails details) { final double magnitude = details.velocity.pixelsPerSecond.distance; - if (magnitude < _kMinFlingVelocity) return; + if (magnitude < _kMinFlingVelocity) + return; final Offset direction = details.velocity.pixelsPerSecond / magnitude; final double distance = (Offset.zero & context.size).shortestSide; _flingAnimation = _controller.drive(Tween( - begin: _offset, end: _clampOffset(_offset + direction * distance))); + begin: _offset, + end: _clampOffset(_offset + direction * distance), + )); _controller ..value = 0.0 ..fling(velocity: magnitude / 1000.0); @@ -139,8 +140,8 @@ class _GridPhotoViewerState extends State ..translate(_offset.dx, _offset.dy) ..scale(_scale), child: Image.asset( - '${widget.photo.assetName}', - // TODO(flutter_web): package: widget.photo.assetPackage, + widget.photo.assetName, + package: widget.photo.assetPackage, fit: BoxFit.cover, ), ), @@ -150,50 +151,52 @@ class _GridPhotoViewerState extends State } class GridDemoPhotoItem extends StatelessWidget { - GridDemoPhotoItem( - {Key key, - @required this.photo, - @required this.tileStyle, - @required this.onBannerTap}) - : assert(photo != null && photo.isValid), - assert(tileStyle != null), - assert(onBannerTap != null), - super(key: key); + GridDemoPhotoItem({ + Key key, + @required this.photo, + @required this.tileStyle, + @required this.onBannerTap, + }) : assert(photo != null && photo.isValid), + assert(tileStyle != null), + assert(onBannerTap != null), + super(key: key); final Photo photo; final GridDemoTileStyle tileStyle; - final BannerTapCallback - onBannerTap; // User taps on the photo's header or footer. + final BannerTapCallback onBannerTap; // User taps on the photo's header or footer. void showPhoto(BuildContext context) { - Navigator.push(context, - MaterialPageRoute(builder: (BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(photo.title)), - body: SizedBox.expand( - child: Hero( - tag: photo.tag, - child: GridPhotoViewer(photo: photo), + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(photo.title), ), - ), - ); - })); + body: SizedBox.expand( + child: Hero( + tag: photo.tag, + child: GridPhotoViewer(photo: photo), + ), + ), + ); + } + )); } @override Widget build(BuildContext context) { final Widget image = GestureDetector( - onTap: () { - showPhoto(context); - }, - child: Hero( - key: Key(photo.assetName), - tag: photo.tag, - child: Image.asset( - '${photo.assetName}', - // TODO(flutter_web): package: photo.assetPackage, - fit: BoxFit.cover, - ))); + onTap: () { showPhoto(context); }, + child: Hero( + key: Key(photo.assetName), + tag: photo.tag, + child: Image.asset( + photo.assetName, + package: photo.assetPackage, + fit: BoxFit.cover, + ), + ), + ); final IconData icon = photo.isFavorite ? Icons.star : Icons.star_border; @@ -204,9 +207,7 @@ class GridDemoPhotoItem extends StatelessWidget { case GridDemoTileStyle.oneLine: return GridTile( header: GestureDetector( - onTap: () { - onBannerTap(photo); - }, + onTap: () { onBannerTap(photo); }, child: GridTileBar( title: _GridTitleText(photo.title), backgroundColor: Colors.black45, @@ -222,9 +223,7 @@ class GridDemoPhotoItem extends StatelessWidget { case GridDemoTileStyle.twoLine: return GridTile( footer: GestureDetector( - onTap: () { - onBannerTap(photo); - }, + onTap: () { onBannerTap(photo); }, child: GridTileBar( backgroundColor: Colors.black45, title: _GridTitleText(photo.title), @@ -244,7 +243,7 @@ class GridDemoPhotoItem extends StatelessWidget { } class GridListDemo extends StatefulWidget { - const GridListDemo({Key key}) : super(key: key); + const GridListDemo({ Key key }) : super(key: key); static const String routeName = '/material/grid-list'; @@ -346,8 +345,7 @@ class GridListDemoState extends State { MaterialDemoDocumentationButton(GridListDemo.routeName), PopupMenuButton( onSelected: changeTileStyle, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: GridDemoTileStyle.imageOnly, child: Text('Image only'), @@ -375,17 +373,17 @@ class GridListDemoState extends State { mainAxisSpacing: 4.0, crossAxisSpacing: 4.0, padding: const EdgeInsets.all(4.0), - childAspectRatio: - (orientation == Orientation.portrait) ? 1.0 : 1.3, + childAspectRatio: (orientation == Orientation.portrait) ? 1.0 : 1.3, children: photos.map((Photo photo) { return GridDemoPhotoItem( - photo: photo, - tileStyle: _tileStyle, - onBannerTap: (Photo photo) { - setState(() { - photo.isFavorite = !photo.isFavorite; - }); + photo: photo, + tileStyle: _tileStyle, + onBannerTap: (Photo photo) { + setState(() { + photo.isFavorite = !photo.isFavorite; }); + }, + ); }).toList(), ), ), diff --git a/web/gallery/lib/demo/material/icons_demo.dart b/web/gallery/lib/demo/material/icons_demo.dart index 96ef1b1ee..b35a290a7 100644 --- a/web/gallery/lib/demo/material/icons_demo.dart +++ b/web/gallery/lib/demo/material/icons_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -58,15 +58,15 @@ class IconsDemoState extends State { child: SafeArea( top: false, bottom: false, - child: ListView( - padding: const EdgeInsets.all(24.0), - children: [ - _IconsDemoCard( - handleIconButtonPress, Icons.face), // direction-agnostic icon - const SizedBox(height: 24.0), - _IconsDemoCard(handleIconButtonPress, - Icons.battery_unknown), // direction-aware icon - ], + child: Scrollbar( + child: ListView( + padding: const EdgeInsets.all(24.0), + children: [ + _IconsDemoCard(handleIconButtonPress, Icons.face), // direction-agnostic icon + const SizedBox(height: 24.0), + _IconsDemoCard(handleIconButtonPress, Icons.battery_unknown), // direction-aware icon + ], + ), ), ), ), @@ -82,21 +82,23 @@ class _IconsDemoCard extends StatelessWidget { Widget _buildIconButton(double iconSize, IconData icon, bool enabled) { return IconButton( - icon: Icon(icon), - iconSize: iconSize, - tooltip: "${enabled ? 'Enabled' : 'Disabled'} icon button", - onPressed: enabled ? handleIconButtonPress : null); + icon: Icon(icon), + iconSize: iconSize, + tooltip: "${enabled ? 'Enabled' : 'Disabled'} icon button", + onPressed: enabled ? handleIconButtonPress : null, + ); } - Widget _centeredText(String label) => Padding( - // Match the default padding of IconButton. - padding: const EdgeInsets.all(8.0), - child: Text(label, textAlign: TextAlign.center), - ); + Widget _centeredText(String label) => + Padding( + // Match the default padding of IconButton. + padding: const EdgeInsets.all(8.0), + child: Text(label, textAlign: TextAlign.center), + ); TableRow _buildIconRow(double size) { return TableRow( - children: [ + children: [ _centeredText(size.floor().toString()), _buildIconButton(size, icon, true), _buildIconButton(size, icon, false), @@ -107,8 +109,7 @@ class _IconsDemoCard extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - final TextStyle textStyle = - theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); + final TextStyle textStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); return Card( child: DefaultTextStyle( style: textStyle, @@ -116,12 +117,14 @@ class _IconsDemoCard extends StatelessWidget { explicitChildNodes: true, child: Table( defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow(children: [ - _centeredText('Size'), - _centeredText('Enabled'), - _centeredText('Disabled'), - ]), + children: [ + TableRow( + children: [ + _centeredText('Size'), + _centeredText('Enabled'), + _centeredText('Disabled'), + ] + ), _buildIconRow(18.0), _buildIconRow(24.0), _buildIconRow(36.0), diff --git a/web/gallery/lib/demo/material/leave_behind_demo.dart b/web/gallery/lib/demo/material/leave_behind_demo.dart index abe42ca24..4ab956253 100644 --- a/web/gallery/lib/demo/material/leave_behind_demo.dart +++ b/web/gallery/lib/demo/material/leave_behind_demo.dart @@ -1,24 +1,27 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:collection/collection.dart' show lowerBound; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/semantics.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; import '../../gallery/demo.dart'; -enum LeaveBehindDemoAction { reset, horizontalSwipe, leftSwipe, rightSwipe } +enum LeaveBehindDemoAction { + reset, + horizontalSwipe, + leftSwipe, + rightSwipe, + confirmDismiss, +} class LeaveBehindItem implements Comparable { - LeaveBehindItem({this.index, this.name, this.subject, this.body}); + LeaveBehindItem({ this.index, this.name, this.subject, this.body }); LeaveBehindItem.from(LeaveBehindItem item) - : index = item.index, - name = item.name, - subject = item.subject, - body = item.body; + : index = item.index, name = item.name, subject = item.subject, body = item.body; final int index; final String name; @@ -30,7 +33,7 @@ class LeaveBehindItem implements Comparable { } class LeaveBehindDemo extends StatefulWidget { - const LeaveBehindDemo({Key key}) : super(key: key); + const LeaveBehindDemo({ Key key }) : super(key: key); static const String routeName = '/material/leave-behind'; @@ -39,18 +42,19 @@ class LeaveBehindDemo extends StatefulWidget { } class LeaveBehindDemoState extends State { - static final GlobalKey _scaffoldKey = - GlobalKey(); + static final GlobalKey _scaffoldKey = GlobalKey(); DismissDirection _dismissDirection = DismissDirection.horizontal; + bool _confirmDismiss = true; List leaveBehindItems; void initListItems() { leaveBehindItems = List.generate(16, (int index) { return LeaveBehindItem( - index: index, - name: 'Item $index Sender', - subject: 'Subject: $index', - body: "[$index] first line of the message's body..."); + index: index, + name: 'Item $index Sender', + subject: 'Subject: $index', + body: "[$index] first line of the message's body...", + ); }); } @@ -75,6 +79,9 @@ class LeaveBehindDemoState extends State { case LeaveBehindDemoAction.rightSwipe: _dismissDirection = DismissDirection.startToEnd; break; + case LeaveBehindDemoAction.confirmDismiss: + _confirmDismiss = !_confirmDismiss; + break; } }); } @@ -91,12 +98,12 @@ class LeaveBehindDemoState extends State { leaveBehindItems.remove(item); }); _scaffoldKey.currentState.showSnackBar(SnackBar( - content: Text('You archived item ${item.index}'), - action: SnackBarAction( - label: 'UNDO', - onPressed: () { - handleUndo(item); - }))); + content: Text('You archived item ${item.index}'), + action: SnackBarAction( + label: 'UNDO', + onPressed: () { handleUndo(item); }, + ), + )); } void _handleDelete(LeaveBehindItem item) { @@ -104,12 +111,12 @@ class LeaveBehindDemoState extends State { leaveBehindItems.remove(item); }); _scaffoldKey.currentState.showSnackBar(SnackBar( - content: Text('You deleted item ${item.index}'), - action: SnackBarAction( - label: 'UNDO', - onPressed: () { - handleUndo(item); - }))); + content: Text('You deleted item ${item.index}'), + action: SnackBarAction( + label: 'UNDO', + onPressed: () { handleUndo(item); }, + ), + )); } @override @@ -123,43 +130,59 @@ class LeaveBehindDemoState extends State { ), ); } else { - body = ListView( + body = Scrollbar( + child: ListView( children: leaveBehindItems.map((LeaveBehindItem item) { - return _LeaveBehindListItem( - item: item, - onArchive: _handleArchive, - onDelete: _handleDelete, - dismissDirection: _dismissDirection, - ); - }).toList()); + return _LeaveBehindListItem( + confirmDismiss: _confirmDismiss, + item: item, + onArchive: _handleArchive, + onDelete: _handleDelete, + dismissDirection: _dismissDirection, + ); + }).toList(), + ), + ); } return Scaffold( key: _scaffoldKey, - appBar: AppBar(title: const Text('Swipe to dismiss'), actions: [ - MaterialDemoDocumentationButton(LeaveBehindDemo.routeName), - PopupMenuButton( + appBar: AppBar( + title: const Text('Swipe to dismiss'), + actions: [ + MaterialDemoDocumentationButton(LeaveBehindDemo.routeName), + PopupMenuButton( onSelected: handleDemoAction, - itemBuilder: (BuildContext context) => - >[ - const PopupMenuItem( - value: LeaveBehindDemoAction.reset, - child: Text('Reset the list')), - const PopupMenuDivider(), - CheckedPopupMenuItem( - value: LeaveBehindDemoAction.horizontalSwipe, - checked: _dismissDirection == DismissDirection.horizontal, - child: const Text('Horizontal swipe')), - CheckedPopupMenuItem( - value: LeaveBehindDemoAction.leftSwipe, - checked: _dismissDirection == DismissDirection.endToStart, - child: const Text('Only swipe left')), - CheckedPopupMenuItem( - value: LeaveBehindDemoAction.rightSwipe, - checked: _dismissDirection == DismissDirection.startToEnd, - child: const Text('Only swipe right')) - ]) - ]), + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: LeaveBehindDemoAction.reset, + child: Text('Reset the list'), + ), + const PopupMenuDivider(), + CheckedPopupMenuItem( + value: LeaveBehindDemoAction.horizontalSwipe, + checked: _dismissDirection == DismissDirection.horizontal, + child: const Text('Horizontal swipe'), + ), + CheckedPopupMenuItem( + value: LeaveBehindDemoAction.leftSwipe, + checked: _dismissDirection == DismissDirection.endToStart, + child: const Text('Only swipe left'), + ), + CheckedPopupMenuItem( + value: LeaveBehindDemoAction.rightSwipe, + checked: _dismissDirection == DismissDirection.startToEnd, + child: const Text('Only swipe right'), + ), + CheckedPopupMenuItem( + value: LeaveBehindDemoAction.confirmDismiss, + checked: _confirmDismiss, + child: const Text('Confirm dismiss'), + ), + ], + ), + ], + ), body: body, ); } @@ -172,12 +195,14 @@ class _LeaveBehindListItem extends StatelessWidget { @required this.onArchive, @required this.onDelete, @required this.dismissDirection, + @required this.confirmDismiss, }) : super(key: key); final LeaveBehindItem item; final DismissDirection dismissDirection; final void Function(LeaveBehindItem) onArchive; final void Function(LeaveBehindItem) onDelete; + final bool confirmDismiss; void _handleArchive() { onArchive(item); @@ -204,25 +229,70 @@ class _LeaveBehindListItem extends StatelessWidget { else _handleDelete(); }, + confirmDismiss: !confirmDismiss ? null : (DismissDirection dismissDirection) async { + switch(dismissDirection) { + case DismissDirection.endToStart: + return await _showConfirmationDialog(context, 'archive') == true; + case DismissDirection.startToEnd: + return await _showConfirmationDialog(context, 'delete') == true; + case DismissDirection.horizontal: + case DismissDirection.vertical: + case DismissDirection.up: + case DismissDirection.down: + assert(false); + } + return false; + }, background: Container( - color: theme.primaryColor, - child: const ListTile( - leading: Icon(Icons.delete, color: Colors.white, size: 36.0))), + color: theme.primaryColor, + child: const ListTile( + leading: Icon(Icons.delete, color: Colors.white, size: 36.0), + ), + ), secondaryBackground: Container( - color: theme.primaryColor, - child: const ListTile( - trailing: - Icon(Icons.archive, color: Colors.white, size: 36.0))), + color: theme.primaryColor, + child: const ListTile( + trailing: Icon(Icons.archive, color: Colors.white, size: 36.0), + ), + ), child: Container( decoration: BoxDecoration( - color: theme.canvasColor, - border: Border(bottom: BorderSide(color: theme.dividerColor))), + color: theme.canvasColor, + border: Border(bottom: BorderSide(color: theme.dividerColor)), + ), child: ListTile( - title: Text(item.name), - subtitle: Text('${item.subject}\n${item.body}'), - isThreeLine: true), + title: Text(item.name), + subtitle: Text('${item.subject}\n${item.body}'), + isThreeLine: true, + ), ), ), ); } + + Future _showConfirmationDialog(BuildContext context, String action) { + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Do you want to $action this item?'), + actions: [ + FlatButton( + child: const Text('Yes'), + onPressed: () { + Navigator.pop(context, true); // showDialog() returns true + }, + ), + FlatButton( + child: const Text('No'), + onPressed: () { + Navigator.pop(context, false); // showDialog() returns false + }, + ), + ], + ); + }, + ); + } } diff --git a/web/gallery/lib/demo/material/list_demo.dart b/web/gallery/lib/demo/material/list_demo.dart index 489bdcb16..0e90ef69f 100644 --- a/web/gallery/lib/demo/material/list_demo.dart +++ b/web/gallery/lib/demo/material/list_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -21,7 +21,7 @@ enum _MaterialListType { } class ListDemo extends StatefulWidget { - const ListDemo({Key key}) : super(key: key); + const ListDemo({ Key key }) : super(key: key); static const String routeName = '/material/list'; @@ -30,8 +30,7 @@ class ListDemo extends StatefulWidget { } class _ListDemoState extends State { - static final GlobalKey scaffoldKey = - GlobalKey(); + static final GlobalKey scaffoldKey = GlobalKey(); PersistentBottomSheetController _bottomSheet; _MaterialListType _itemType = _MaterialListType.threeLine; @@ -41,33 +40,18 @@ class _ListDemoState extends State { bool _showDividers = false; bool _reverseSort = false; List items = [ - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', ]; void changeItemType(_MaterialListType type) { setState(() { _itemType = type; }); - _bottomSheet?.setState(() {}); + _bottomSheet?.setState(() { }); } void _showConfigurationSheet() { - final PersistentBottomSheetController bottomSheet = scaffoldKey - .currentState - .showBottomSheet((BuildContext bottomSheetContext) { + final PersistentBottomSheetController bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) { return Container( decoration: const BoxDecoration( border: Border(top: BorderSide(color: Colors.black26)), @@ -78,25 +62,25 @@ class _ListDemoState extends State { children: [ MergeSemantics( child: ListTile( - dense: true, - title: const Text('One-line'), - trailing: Radio<_MaterialListType>( - value: _showAvatars - ? _MaterialListType.oneLineWithAvatar - : _MaterialListType.oneLine, - groupValue: _itemType, - onChanged: changeItemType, - )), + dense: true, + title: const Text('One-line'), + trailing: Radio<_MaterialListType>( + value: _showAvatars ? _MaterialListType.oneLineWithAvatar : _MaterialListType.oneLine, + groupValue: _itemType, + onChanged: changeItemType, + ), + ), ), MergeSemantics( child: ListTile( - dense: true, - title: const Text('Two-line'), - trailing: Radio<_MaterialListType>( - value: _MaterialListType.twoLine, - groupValue: _itemType, - onChanged: changeItemType, - )), + dense: true, + title: const Text('Two-line'), + trailing: Radio<_MaterialListType>( + value: _MaterialListType.twoLine, + groupValue: _itemType, + onChanged: changeItemType, + ), + ), ), MergeSemantics( child: ListTile( @@ -119,7 +103,7 @@ class _ListDemoState extends State { setState(() { _showAvatars = value; }); - _bottomSheet?.setState(() {}); + _bottomSheet?.setState(() { }); }, ), ), @@ -134,7 +118,7 @@ class _ListDemoState extends State { setState(() { _showIcons = value; }); - _bottomSheet?.setState(() {}); + _bottomSheet?.setState(() { }); }, ), ), @@ -149,7 +133,7 @@ class _ListDemoState extends State { setState(() { _showDividers = value; }); - _bottomSheet?.setState(() {}); + _bottomSheet?.setState(() { }); }, ), ), @@ -164,7 +148,7 @@ class _ListDemoState extends State { setState(() { _dense = value; }); - _bottomSheet?.setState(() {}); + _bottomSheet?.setState(() { }); }, ), ), @@ -200,14 +184,10 @@ class _ListDemoState extends State { child: ListTile( isThreeLine: _itemType == _MaterialListType.threeLine, dense: _dense, - leading: _showAvatars - ? ExcludeSemantics(child: CircleAvatar(child: Text(item))) - : null, + leading: _showAvatars ? ExcludeSemantics(child: CircleAvatar(child: Text(item))) : null, title: Text('This item represents $item.'), subtitle: secondary, - trailing: _showIcons - ? Icon(Icons.info, color: Theme.of(context).disabledColor) - : null, + trailing: _showIcons ? Icon(Icons.info, color: Theme.of(context).disabledColor) : null, ), ); } @@ -229,8 +209,7 @@ class _ListDemoState extends State { break; } - Iterable listTiles = - items.map((String item) => buildListTile(context, item)); + Iterable listTiles = items.map((String item) => buildListTile(context, item)); if (_showDividers) listTiles = ListTile.divideTiles(context: context, tiles: listTiles); @@ -246,8 +225,7 @@ class _ListDemoState extends State { onPressed: () { setState(() { _reverseSort = !_reverseSort; - items.sort((String a, String b) => - _reverseSort ? b.compareTo(a) : a.compareTo(b)); + items.sort((String a, String b) => _reverseSort ? b.compareTo(a) : a.compareTo(b)); }); }, ), diff --git a/web/gallery/lib/demo/material/material.dart b/web/gallery/lib/demo/material/material.dart index 7c69b81f9..eff2d39ea 100644 --- a/web/gallery/lib/demo/material/material.dart +++ b/web/gallery/lib/demo/material/material.dart @@ -1,20 +1,21 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'backdrop_demo.dart'; +export 'banner_demo.dart'; export 'bottom_app_bar_demo.dart'; export 'bottom_navigation_demo.dart'; -export 'material_button_demo.dart'; +export 'buttons_demo.dart'; export 'cards_demo.dart'; export 'chip_demo.dart'; export 'data_table_demo.dart'; export 'date_and_time_picker_demo.dart'; export 'dialog_demo.dart'; export 'drawer_demo.dart'; -export 'editable_text_demo.dart'; export 'elevation_demo.dart'; export 'expansion_panels_demo.dart'; +export 'expansion_tile_list_demo.dart'; export 'grid_list_demo.dart'; export 'icons_demo.dart'; export 'leave_behind_demo.dart'; @@ -33,7 +34,5 @@ export 'slider_demo.dart'; export 'snack_bar_demo.dart'; export 'tabs_demo.dart'; export 'tabs_fab_demo.dart'; -export 'text_demo.dart'; export 'text_form_field_demo.dart'; export 'tooltip_demo.dart'; -export 'two_level_list_demo.dart'; diff --git a/web/gallery/lib/demo/material/material_button_demo.dart b/web/gallery/lib/demo/material/material_button_demo.dart deleted file mode 100644 index d1f3d49fc..000000000 --- a/web/gallery/lib/demo/material/material_button_demo.dart +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -import '../../gallery/demo.dart'; - -class ButtonsDemo extends StatelessWidget { - static const String routeName = '/material/buttons'; - final GlobalKey _scaffoldKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - IconData _backIcon() { - switch (Theme.of(context).platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - return Icons.arrow_back; - case TargetPlatform.iOS: - return Icons.arrow_back_ios; - } - assert(false); - return null; - } - - return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - leading: IconButton( - icon: Icon(_backIcon()), - alignment: Alignment.centerLeft, - tooltip: 'Back', - onPressed: () { - Navigator.pop(context); - }, - ), - title: const Text('Material buttons'), - actions: [ - MaterialDemoDocumentationButton(ButtonsDemo.routeName) - ], - ), - body: Center( - child: _buildButtons(), - ), - ); - } - - Widget _buildButtons() { - return Column( - children: [ - pad(MaterialButton( - onPressed: () { - print('MaterialButton pressed'); - }, - elevation: 3.0, - child: Text('MaterialButton'), - )), - pad(FlatButton( - onPressed: () { - print('FlatButton pressed'); - }, - child: Text('FlatButton'), - )), - pad(RaisedButton( - onPressed: () {}, - elevation: 0.0, - child: Text('RaisedButton 0.0'), - )), - pad(RaisedButton( - onPressed: () {}, - elevation: 1.0, - child: Text('RaisedButton 1.0'), - )), - pad(RaisedButton( - onPressed: () {}, - elevation: 2.0, - child: Text('RaisedButton 2.0'), - )), - pad(RaisedButton( - onPressed: () {}, - elevation: 3.0, - child: Text('RaisedButton 3.0'), - )), - pad(RaisedButton( - onPressed: () {}, - elevation: 4.0, - child: Text('RaisedButton 4.0'), - )), - pad(RaisedButton( - onPressed: () {}, - elevation: 8.0, - child: Text('RaisedButton 8.0'), - )), - ], - ); - } -} - -Padding pad(Widget widget) => Padding( - padding: EdgeInsets.all(10.0), - child: widget, - ); diff --git a/web/gallery/lib/demo/material/menu_demo.dart b/web/gallery/lib/demo/material/menu_demo.dart index 10c041e9a..2434b4de5 100644 --- a/web/gallery/lib/demo/material/menu_demo.dart +++ b/web/gallery/lib/demo/material/menu_demo.dart @@ -1,13 +1,13 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; class MenuDemo extends StatefulWidget { - const MenuDemo({Key key}) : super(key: key); + const MenuDemo({ Key key }) : super(key: key); static const String routeName = '/material/menu'; @@ -37,7 +37,9 @@ class MenuDemoState extends State { } void showInSnackBar(String value) { - _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(value))); + _scaffoldKey.currentState.showSnackBar(SnackBar( + content: Text(value), + )); } void showMenuSelection(String value) { @@ -60,122 +62,158 @@ class MenuDemoState extends State { @override Widget build(BuildContext context) { return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - title: const Text('Menus'), - actions: [ - MaterialDemoDocumentationButton(MenuDemo.routeName), - PopupMenuButton( + key: _scaffoldKey, + appBar: AppBar( + title: const Text('Menus'), + actions: [ + MaterialDemoDocumentationButton(MenuDemo.routeName), + PopupMenuButton( + onSelected: showMenuSelection, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: 'Toolbar menu', + child: Text('Toolbar menu'), + ), + const PopupMenuItem( + value: 'Right here', + child: Text('Right here'), + ), + const PopupMenuItem( + value: 'Hooray!', + child: Text('Hooray!'), + ), + ], + ), + ], + ), + body: ListView( + padding: kMaterialListPadding, + children: [ + // Pressing the PopupMenuButton on the right of this item shows + // a simple menu with one disabled item. Typically the contents + // of this "contextual menu" would reflect the app's state. + ListTile( + title: const Text('An item with a context menu button'), + trailing: PopupMenuButton( + padding: EdgeInsets.zero, onSelected: showMenuSelection, itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + value: _simpleValue1, + child: const Text('Context menu item one'), + ), const PopupMenuItem( - value: 'Toolbar menu', child: Text('Toolbar menu')), - const PopupMenuItem( - value: 'Right here', child: Text('Right here')), - const PopupMenuItem( - value: 'Hooray!', child: Text('Hooray!')), + enabled: false, + child: Text('A disabled menu item'), + ), + PopupMenuItem( + value: _simpleValue3, + child: const Text('Context menu item three'), + ), ], ), - ], - ), - body: ListView(padding: kMaterialListPadding, children: [ - // Pressing the PopupMenuButton on the right of this item shows - // a simple menu with one disabled item. Typically the contents - // of this "contextual menu" would reflect the app's state. - ListTile( - title: const Text('An item with a context menu button'), - trailing: PopupMenuButton( - padding: EdgeInsets.zero, - onSelected: showMenuSelection, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - value: _simpleValue1, - child: const Text('Context menu item one')), - const PopupMenuItem( - enabled: false, - child: Text('A disabled menu item')), - PopupMenuItem( - value: _simpleValue3, - child: const Text('Context menu item three')), - ])), + ), // Pressing the PopupMenuButton on the right of this item shows // a menu whose items have text labels and icons and a divider // That separates the first three items from the last one. ListTile( - title: const Text('An item with a sectioned menu'), - trailing: PopupMenuButton( - padding: EdgeInsets.zero, - onSelected: showMenuSelection, - itemBuilder: (BuildContext context) => - >[ - const PopupMenuItem( - value: 'Preview', - child: ListTile( - leading: Icon(Icons.visibility), - title: Text('Preview'))), - const PopupMenuItem( - value: 'Share', - child: ListTile( - leading: Icon(Icons.person_add), - title: Text('Share'))), - const PopupMenuItem( - value: 'Get Link', - child: ListTile( - leading: Icon(Icons.link), - title: Text('Get link'))), - const PopupMenuDivider(), - const PopupMenuItem( - value: 'Remove', - child: ListTile( - leading: Icon(Icons.delete), - title: Text('Remove'))) - ])), + title: const Text('An item with a sectioned menu'), + trailing: PopupMenuButton( + padding: EdgeInsets.zero, + onSelected: showMenuSelection, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: 'Preview', + child: ListTile( + leading: Icon(Icons.visibility), + title: Text('Preview'), + ), + ), + const PopupMenuItem( + value: 'Share', + child: ListTile( + leading: Icon(Icons.person_add), + title: Text('Share'), + ), + ), + const PopupMenuItem( + value: 'Get Link', + child: ListTile( + leading: Icon(Icons.link), + title: Text('Get link'), + ), + ), + const PopupMenuDivider(), + const PopupMenuItem( + value: 'Remove', + child: ListTile( + leading: Icon(Icons.delete), + title: Text('Remove'), + ), + ), + ], + ), + ), // This entire list item is a PopupMenuButton. Tapping anywhere shows // a menu whose current value is highlighted and aligned over the // list item's center line. PopupMenuButton( - padding: EdgeInsets.zero, - initialValue: _simpleValue, - onSelected: showMenuSelection, - child: ListTile( - title: const Text('An item with a simple menu'), - subtitle: Text(_simpleValue)), - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: _simpleValue1, child: Text(_simpleValue1)), - PopupMenuItem( - value: _simpleValue2, child: Text(_simpleValue2)), - PopupMenuItem( - value: _simpleValue3, child: Text(_simpleValue3)) - ]), + padding: EdgeInsets.zero, + initialValue: _simpleValue, + onSelected: showMenuSelection, + child: ListTile( + title: const Text('An item with a simple menu'), + subtitle: Text(_simpleValue), + ), + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + value: _simpleValue1, + child: Text(_simpleValue1), + ), + PopupMenuItem( + value: _simpleValue2, + child: Text(_simpleValue2), + ), + PopupMenuItem( + value: _simpleValue3, + child: Text(_simpleValue3), + ), + ], + ), // Pressing the PopupMenuButton on the right of this item shows a menu // whose items have checked icons that reflect this app's state. ListTile( - title: const Text('An item with a checklist menu'), - trailing: PopupMenuButton( - padding: EdgeInsets.zero, - onSelected: showCheckedMenuSelections, - itemBuilder: (BuildContext context) => - >[ - CheckedPopupMenuItem( - value: _checkedValue1, - checked: isChecked(_checkedValue1), - child: Text(_checkedValue1)), - CheckedPopupMenuItem( - value: _checkedValue2, - enabled: false, - checked: isChecked(_checkedValue2), - child: Text(_checkedValue2)), - CheckedPopupMenuItem( - value: _checkedValue3, - checked: isChecked(_checkedValue3), - child: Text(_checkedValue3)), - CheckedPopupMenuItem( - value: _checkedValue4, - checked: isChecked(_checkedValue4), - child: Text(_checkedValue4)) - ])) - ])); + title: const Text('An item with a checklist menu'), + trailing: PopupMenuButton( + padding: EdgeInsets.zero, + onSelected: showCheckedMenuSelections, + itemBuilder: (BuildContext context) => >[ + CheckedPopupMenuItem( + value: _checkedValue1, + checked: isChecked(_checkedValue1), + child: Text(_checkedValue1), + ), + CheckedPopupMenuItem( + value: _checkedValue2, + enabled: false, + checked: isChecked(_checkedValue2), + child: Text(_checkedValue2), + ), + CheckedPopupMenuItem( + value: _checkedValue3, + checked: isChecked(_checkedValue3), + child: Text(_checkedValue3), + ), + CheckedPopupMenuItem( + value: _checkedValue4, + checked: isChecked(_checkedValue4), + child: Text(_checkedValue4), + ), + ], + ), + ), + ], + ), + ); } } diff --git a/web/gallery/lib/demo/material/modal_bottom_sheet_demo.dart b/web/gallery/lib/demo/material/modal_bottom_sheet_demo.dart index 31d9c3d39..578f52637 100644 --- a/web/gallery/lib/demo/material/modal_bottom_sheet_demo.dart +++ b/web/gallery/lib/demo/material/modal_bottom_sheet_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -12,27 +12,31 @@ class ModalBottomSheetDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Modal bottom sheet'), - actions: [MaterialDemoDocumentationButton(routeName)], + appBar: AppBar( + title: const Text('Modal bottom sheet'), + actions: [MaterialDemoDocumentationButton(routeName)], + ), + body: Center( + child: RaisedButton( + child: const Text('SHOW BOTTOM SHEET'), + onPressed: () { + showModalBottomSheet(context: context, builder: (BuildContext context) { + return Container( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Text('This is the modal bottom sheet. Slide down to dismiss.', + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).accentColor, + fontSize: 24.0, + ), + ), + ), + ); + }); + }, ), - body: Center( - child: RaisedButton( - child: const Text('SHOW BOTTOM SHEET'), - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Container( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Text( - 'This is the modal bottom sheet. Tap anywhere to dismiss.', - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).accentColor, - fontSize: 24.0)))); - }); - }))); + ), + ); } } diff --git a/web/gallery/lib/demo/material/overscroll_demo.dart b/web/gallery/lib/demo/material/overscroll_demo.dart index 4195489fb..cb9ebfc26 100644 --- a/web/gallery/lib/demo/material/overscroll_demo.dart +++ b/web/gallery/lib/demo/material/overscroll_demo.dart @@ -1,17 +1,17 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; enum IndicatorType { overscroll, refresh } class OverscrollDemo extends StatefulWidget { - const OverscrollDemo({Key key}) : super(key: key); + const OverscrollDemo({ Key key }) : super(key: key); static const String routeName = '/material/overscroll'; @@ -21,38 +21,24 @@ class OverscrollDemo extends StatefulWidget { class OverscrollDemoState extends State { final GlobalKey _scaffoldKey = GlobalKey(); - final GlobalKey _refreshIndicatorKey = - GlobalKey(); + final GlobalKey _refreshIndicatorKey = GlobalKey(); static final List _items = [ - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N' + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', ]; Future _handleRefresh() { final Completer completer = Completer(); - Timer(const Duration(seconds: 3), () { - completer.complete(); - }); + Timer(const Duration(seconds: 3), () { completer.complete(); }); return completer.future.then((_) { _scaffoldKey.currentState?.showSnackBar(SnackBar( - content: const Text('Refresh complete'), - action: SnackBarAction( - label: 'RETRY', - onPressed: () { - _refreshIndicatorKey.currentState.show(); - }))); + content: const Text('Refresh complete'), + action: SnackBarAction( + label: 'RETRY', + onPressed: () { + _refreshIndicatorKey.currentState.show(); + }, + ), + )); }); } @@ -60,31 +46,36 @@ class OverscrollDemoState extends State { Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, - appBar: AppBar(title: const Text('Pull to refresh'), actions: [ - MaterialDemoDocumentationButton(OverscrollDemo.routeName), - IconButton( + appBar: AppBar( + title: const Text('Pull to refresh'), + actions: [ + MaterialDemoDocumentationButton(OverscrollDemo.routeName), + IconButton( icon: const Icon(Icons.refresh), tooltip: 'Refresh', onPressed: () { _refreshIndicatorKey.currentState.show(); - }), - ]), + }, + ), + ], + ), body: RefreshIndicator( key: _refreshIndicatorKey, onRefresh: _handleRefresh, - child: ListView.builder( - padding: kMaterialListPadding, - itemCount: _items.length, - itemBuilder: (BuildContext context, int index) { - final String item = _items[index]; - return ListTile( - isThreeLine: true, - leading: CircleAvatar(child: Text(item)), - title: Text('This item represents $item.'), - subtitle: const Text( - 'Even more additional list item information appears on line three.'), - ); - }, + child: Scrollbar( + child: ListView.builder( + padding: kMaterialListPadding, + itemCount: _items.length, + itemBuilder: (BuildContext context, int index) { + final String item = _items[index]; + return ListTile( + isThreeLine: true, + leading: CircleAvatar(child: Text(item)), + title: Text('This item represents $item.'), + subtitle: const Text('Even more additional list item information appears on line three.'), + ); + }, + ), ), ), ); diff --git a/web/gallery/lib/demo/material/page_selector_demo.dart b/web/gallery/lib/demo/material/page_selector_demo.dart index a4452749a..f18d1d921 100644 --- a/web/gallery/lib/demo/material/page_selector_demo.dart +++ b/web/gallery/lib/demo/material/page_selector_demo.dart @@ -1,21 +1,20 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; class _PageSelector extends StatelessWidget { - const _PageSelector({this.icons}); + const _PageSelector({ this.icons }); final List icons; void _handleArrowButtonPress(BuildContext context, int delta) { final TabController controller = DefaultTabController.of(context); if (!controller.indexIsChanging) - controller - .animateTo((controller.index + delta).clamp(0, icons.length - 1)); + controller.animateTo((controller.index + delta).clamp(0, icons.length - 1)); } @override @@ -28,24 +27,26 @@ class _PageSelector extends StatelessWidget { child: Column( children: [ Container( - margin: const EdgeInsets.only(top: 16.0), - child: Row(children: [ + margin: const EdgeInsets.only(top: 16.0), + child: Row( + children: [ IconButton( - icon: const Icon(Icons.chevron_left), - color: color, - onPressed: () { - _handleArrowButtonPress(context, -1); - }, - tooltip: 'Page back'), + icon: const Icon(Icons.chevron_left), + color: color, + onPressed: () { _handleArrowButtonPress(context, -1); }, + tooltip: 'Page back', + ), TabPageSelector(controller: controller), IconButton( - icon: const Icon(Icons.chevron_right), - color: color, - onPressed: () { - _handleArrowButtonPress(context, 1); - }, - tooltip: 'Page forward') - ], mainAxisAlignment: MainAxisAlignment.spaceBetween)), + icon: const Icon(Icons.chevron_right), + color: color, + onPressed: () { _handleArrowButtonPress(context, 1); }, + tooltip: 'Page forward', + ), + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + ), Expanded( child: IconTheme( data: IconThemeData( @@ -53,16 +54,17 @@ class _PageSelector extends StatelessWidget { color: color, ), child: TabBarView( - children: icons.map((Icon icon) { - return Container( - padding: const EdgeInsets.all(12.0), - child: Card( - child: Center( - child: icon, + children: icons.map((Icon icon) { + return Container( + padding: const EdgeInsets.all(12.0), + child: Card( + child: Center( + child: icon, + ), ), - ), - ); - }).toList()), + ); + }).toList(), + ), ), ), ], diff --git a/web/gallery/lib/demo/material/persistent_bottom_sheet_demo.dart b/web/gallery/lib/demo/material/persistent_bottom_sheet_demo.dart index 27dfb2380..7771c9d4f 100644 --- a/web/gallery/lib/demo/material/persistent_bottom_sheet_demo.dart +++ b/web/gallery/lib/demo/material/persistent_bottom_sheet_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -10,8 +10,7 @@ class PersistentBottomSheetDemo extends StatefulWidget { static const String routeName = '/material/persistent-bottom-sheet'; @override - _PersistentBottomSheetDemoState createState() => - _PersistentBottomSheetDemoState(); + _PersistentBottomSheetDemoState createState() => _PersistentBottomSheetDemoState(); } class _PersistentBottomSheetDemoState extends State { @@ -26,36 +25,34 @@ class _PersistentBottomSheetDemoState extends State { } void _showBottomSheet() { - setState(() { - // disable the button + setState(() { // disable the button _showBottomSheetCallback = null; }); - _scaffoldKey.currentState - .showBottomSheet((BuildContext context) { - final ThemeData themeData = Theme.of(context); - return Container( - decoration: BoxDecoration( - border: - Border(top: BorderSide(color: themeData.disabledColor))), - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Text( - 'This is a Material persistent bottom sheet. Drag downwards to dismiss it.', - textAlign: TextAlign.center, - style: TextStyle(color: themeData.accentColor, fontSize: 24.0), - ), + _scaffoldKey.currentState.showBottomSheet((BuildContext context) { + final ThemeData themeData = Theme.of(context); + return Container( + decoration: BoxDecoration( + border: Border(top: BorderSide(color: themeData.disabledColor)) + ), + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Text('This is a Material persistent bottom sheet. Drag downwards to dismiss it.', + textAlign: TextAlign.center, + style: TextStyle( + color: themeData.accentColor, + fontSize: 24.0, ), - ); - }) - .closed - .whenComplete(() { - if (mounted) { - setState(() { - // re-enable the button - _showBottomSheetCallback = _showBottomSheet; - }); - } + ), + ), + ); + }) + .closed.whenComplete(() { + if (mounted) { + setState(() { // re-enable the button + _showBottomSheetCallback = _showBottomSheet; }); + } + }); } void _showMessage() { @@ -66,10 +63,11 @@ class _PersistentBottomSheetDemoState extends State { content: const Text('You tapped the floating action button.'), actions: [ FlatButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('OK')) + onPressed: () { + Navigator.pop(context); + }, + child: const Text('OK'), + ), ], ); }, @@ -79,25 +77,27 @@ class _PersistentBottomSheetDemoState extends State { @override Widget build(BuildContext context) { return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - title: const Text('Persistent bottom sheet'), - actions: [ - MaterialDemoDocumentationButton( - PersistentBottomSheetDemo.routeName), - ], + key: _scaffoldKey, + appBar: AppBar( + title: const Text('Persistent bottom sheet'), + actions: [ + MaterialDemoDocumentationButton(PersistentBottomSheetDemo.routeName), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: _showMessage, + backgroundColor: Colors.redAccent, + child: const Icon( + Icons.add, + semanticLabel: 'Add', ), - floatingActionButton: FloatingActionButton( - onPressed: _showMessage, - backgroundColor: Colors.redAccent, - child: const Icon( - Icons.add, - semanticLabel: 'Add', - ), + ), + body: Center( + child: RaisedButton( + onPressed: _showBottomSheetCallback, + child: const Text('SHOW BOTTOM SHEET'), ), - body: Center( - child: RaisedButton( - onPressed: _showBottomSheetCallback, - child: const Text('SHOW BOTTOM SHEET')))); + ), + ); } } diff --git a/web/gallery/lib/demo/material/progress_indicator_demo.dart b/web/gallery/lib/demo/material/progress_indicator_demo.dart index e28f34b23..ae80842f2 100644 --- a/web/gallery/lib/demo/material/progress_indicator_demo.dart +++ b/web/gallery/lib/demo/material/progress_indicator_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -13,8 +13,7 @@ class ProgressIndicatorDemo extends StatefulWidget { _ProgressIndicatorDemoState createState() => _ProgressIndicatorDemoState(); } -class _ProgressIndicatorDemoState extends State - with SingleTickerProviderStateMixin { +class _ProgressIndicatorDemoState extends State with SingleTickerProviderStateMixin { AnimationController _controller; Animation _animation; @@ -28,14 +27,15 @@ class _ProgressIndicatorDemoState extends State )..forward(); _animation = CurvedAnimation( - parent: _controller, - curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn), - reverseCurve: Curves.fastOutSlowIn) - ..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.dismissed) - _controller.forward(); - else if (status == AnimationStatus.completed) _controller.reverse(); - }); + parent: _controller, + curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn), + reverseCurve: Curves.fastOutSlowIn, + )..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.dismissed) + _controller.forward(); + else if (status == AnimationStatus.completed) + _controller.reverse(); + }); } @override @@ -66,7 +66,10 @@ class _ProgressIndicatorDemoState extends State Widget _buildIndicators(BuildContext context, Widget child) { final List indicators = [ - const SizedBox(width: 200.0, child: LinearProgressIndicator()), + const SizedBox( + width: 200.0, + child: LinearProgressIndicator(), + ), const LinearProgressIndicator(), const LinearProgressIndicator(), LinearProgressIndicator(value: _animation.value), @@ -77,23 +80,22 @@ class _ProgressIndicatorDemoState extends State SizedBox( width: 20.0, height: 20.0, - child: CircularProgressIndicator(value: _animation.value)), + child: CircularProgressIndicator(value: _animation.value), + ), SizedBox( width: 100.0, height: 20.0, child: Text('${(_animation.value * 100.0).toStringAsFixed(1)}%', - textAlign: TextAlign.right), + textAlign: TextAlign.right, + ), ), ], ), ]; return Column( children: indicators - .map((Widget c) => Container( - child: c, - margin: - const EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0))) - .toList(), + .map((Widget c) => Container(child: c, margin: const EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0))) + .toList(), ); } @@ -102,9 +104,7 @@ class _ProgressIndicatorDemoState extends State return Scaffold( appBar: AppBar( title: const Text('Progress indicators'), - actions: [ - MaterialDemoDocumentationButton(ProgressIndicatorDemo.routeName) - ], + actions: [MaterialDemoDocumentationButton(ProgressIndicatorDemo.routeName)], ), body: Center( child: SingleChildScrollView( @@ -117,10 +117,11 @@ class _ProgressIndicatorDemoState extends State top: false, bottom: false, child: Container( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 8.0), + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0), child: AnimatedBuilder( - animation: _animation, builder: _buildIndicators), + animation: _animation, + builder: _buildIndicators, + ), ), ), ), diff --git a/web/gallery/lib/demo/material/reorderable_list_demo.dart b/web/gallery/lib/demo/material/reorderable_list_demo.dart index 40477955b..79b17be1d 100644 --- a/web/gallery/lib/demo/material/reorderable_list_demo.dart +++ b/web/gallery/lib/demo/material/reorderable_list_demo.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/foundation.dart'; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import '../../gallery/demo.dart'; @@ -20,7 +20,7 @@ enum _ReorderableListType { } class ReorderableListDemo extends StatefulWidget { - const ReorderableListDemo({Key key}) : super(key: key); + const ReorderableListDemo({ Key key }) : super(key: key); static const String routeName = '/material/reorderable-list'; @@ -37,27 +37,14 @@ class _ListItem { } class _ListDemoState extends State { - static final GlobalKey scaffoldKey = - GlobalKey(); + static final GlobalKey scaffoldKey = GlobalKey(); PersistentBottomSheetController _bottomSheet; _ReorderableListType _itemType = _ReorderableListType.threeLine; + bool _reverse = false; bool _reverseSort = false; final List<_ListItem> _items = [ - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', ].map<_ListItem>((String item) => _ListItem(item, false)).toList(); void changeItemType(_ReorderableListType type) { @@ -65,15 +52,28 @@ class _ListDemoState extends State { _itemType = type; }); // Rebuild the bottom sheet to reflect the selected list view. - _bottomSheet?.setState(() {}); + _bottomSheet?.setState(() { + // Trigger a rebuild. + }); + // Close the bottom sheet to give the user a clear view of the list. + _bottomSheet?.close(); + } + + void changeReverse(bool newValue) { + setState(() { + _reverse = newValue; + }); + // Rebuild the bottom sheet to reflect the selected list view. + _bottomSheet?.setState(() { + // Trigger a rebuild. + }); // Close the bottom sheet to give the user a clear view of the list. _bottomSheet?.close(); } void _showConfigurationSheet() { setState(() { - _bottomSheet = scaffoldKey.currentState - .showBottomSheet((BuildContext bottomSheetContext) { + _bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) { return DecoratedBox( decoration: const BoxDecoration( border: Border(top: BorderSide(color: Colors.black26)), @@ -82,6 +82,12 @@ class _ListDemoState extends State { shrinkWrap: true, primary: false, children: [ + CheckboxListTile( + dense: true, + title: const Text('Reverse'), + value: _reverse, + onChanged: changeReverse, + ), RadioListTile<_ReorderableListType>( dense: true, title: const Text('Horizontal Avatars'), @@ -146,8 +152,7 @@ class _ListDemoState extends State { key: Key(item.value), height: 100.0, width: 100.0, - child: CircleAvatar( - child: Text(item.value), + child: CircleAvatar(child: Text(item.value), backgroundColor: Colors.green, ), ); @@ -167,6 +172,7 @@ class _ListDemoState extends State { }); } + @override Widget build(BuildContext context) { return Scaffold( @@ -181,9 +187,7 @@ class _ListDemoState extends State { onPressed: () { setState(() { _reverseSort = !_reverseSort; - _items.sort((_ListItem a, _ListItem b) => _reverseSort - ? b.value.compareTo(a.value) - : a.value.compareTo(b.value)); + _items.sort((_ListItem a, _ListItem b) => _reverseSort ? b.value.compareTo(a.value) : a.value.compareTo(b.value)); }); }, ), @@ -203,13 +207,11 @@ class _ListDemoState extends State { header: _itemType != _ReorderableListType.threeLine ? Padding( padding: const EdgeInsets.all(8.0), - child: Text('Header of the list', - style: Theme.of(context).textTheme.headline)) + child: Text('Header of the list', style: Theme.of(context).textTheme.headline)) : null, onReorder: _onReorder, - scrollDirection: _itemType == _ReorderableListType.horizontalAvatar - ? Axis.horizontal - : Axis.vertical, + reverse: _reverse, + scrollDirection: _itemType == _ReorderableListType.horizontalAvatar ? Axis.horizontal : Axis.vertical, padding: const EdgeInsets.symmetric(vertical: 8.0), children: _items.map(buildListTile).toList(), ), diff --git a/web/gallery/lib/demo/material/scrollable_tabs_demo.dart b/web/gallery/lib/demo/material/scrollable_tabs_demo.dart index 0604784bd..7e32cb15f 100644 --- a/web/gallery/lib/demo/material/scrollable_tabs_demo.dart +++ b/web/gallery/lib/demo/material/scrollable_tabs_demo.dart @@ -1,15 +1,19 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; -enum TabsDemoStyle { iconsAndText, iconsOnly, textOnly } +enum TabsDemoStyle { + iconsAndText, + iconsOnly, + textOnly +} class _Page { - const _Page({this.icon, this.text}); + const _Page({ this.icon, this.text }); final IconData icon; final String text; } @@ -38,8 +42,7 @@ class ScrollableTabsDemo extends StatefulWidget { ScrollableTabsDemoState createState() => ScrollableTabsDemoState(); } -class ScrollableTabsDemoState extends State - with SingleTickerProviderStateMixin { +class ScrollableTabsDemoState extends State with SingleTickerProviderStateMixin { TabController _controller; TabsDemoStyle _demoStyle = TabsDemoStyle.iconsAndText; bool _customIndicator = false; @@ -63,57 +66,55 @@ class ScrollableTabsDemoState extends State } Decoration getIndicator() { - if (!_customIndicator) return const UnderlineTabIndicator(); + if (!_customIndicator) + return const UnderlineTabIndicator(); - switch (_demoStyle) { + switch(_demoStyle) { case TabsDemoStyle.iconsAndText: return ShapeDecoration( shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0)), - side: BorderSide( - color: Colors.white24, - width: 2.0, - ), - ) + - const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0)), - side: BorderSide( - color: Colors.transparent, - width: 4.0, - ), - ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + side: BorderSide( + color: Colors.white24, + width: 2.0, + ), + ) + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + side: BorderSide( + color: Colors.transparent, + width: 4.0, + ), + ), ); case TabsDemoStyle.iconsOnly: return ShapeDecoration( shape: const CircleBorder( - side: BorderSide( - color: Colors.white24, - width: 4.0, - ), - ) + - const CircleBorder( - side: BorderSide( - color: Colors.transparent, - width: 4.0, - ), - ), + side: BorderSide( + color: Colors.white24, + width: 4.0, + ), + ) + const CircleBorder( + side: BorderSide( + color: Colors.transparent, + width: 4.0, + ), + ), ); case TabsDemoStyle.textOnly: return ShapeDecoration( shape: const StadiumBorder( - side: BorderSide( - color: Colors.white24, - width: 2.0, - ), - ) + - const StadiumBorder( - side: BorderSide( - color: Colors.transparent, - width: 4.0, - ), - ), + side: BorderSide( + color: Colors.white24, + width: 2.0, + ), + ) + const StadiumBorder( + side: BorderSide( + color: Colors.transparent, + width: 4.0, + ), + ), ); } return null; @@ -137,15 +138,19 @@ class ScrollableTabsDemoState extends State ), PopupMenuButton( onSelected: changeDemoStyle, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (BuildContext context) => >[ const PopupMenuItem( - value: TabsDemoStyle.iconsAndText, - child: Text('Icons and text')), + value: TabsDemoStyle.iconsAndText, + child: Text('Icons and text'), + ), const PopupMenuItem( - value: TabsDemoStyle.iconsOnly, child: Text('Icons only')), + value: TabsDemoStyle.iconsOnly, + child: Text('Icons only'), + ), const PopupMenuItem( - value: TabsDemoStyle.textOnly, child: Text('Text only')), + value: TabsDemoStyle.textOnly, + child: Text('Text only'), + ), ], ), ], @@ -168,27 +173,28 @@ class ScrollableTabsDemoState extends State ), ), body: TabBarView( - controller: _controller, - children: _allPages.map((_Page page) { - return SafeArea( - top: false, - bottom: false, - child: Container( - key: ObjectKey(page.icon), - padding: const EdgeInsets.all(12.0), - child: Card( - child: Center( - child: Icon( - page.icon, - color: iconColor, - size: 128.0, - semanticLabel: 'Placeholder for ${page.text} tab', - ), + controller: _controller, + children: _allPages.map((_Page page) { + return SafeArea( + top: false, + bottom: false, + child: Container( + key: ObjectKey(page.icon), + padding: const EdgeInsets.all(12.0), + child: Card( + child: Center( + child: Icon( + page.icon, + color: iconColor, + size: 128.0, + semanticLabel: 'Placeholder for ${page.text} tab', ), ), ), - ); - }).toList()), + ), + ); + }).toList(), + ), ); } } diff --git a/web/gallery/lib/demo/material/search_demo.dart b/web/gallery/lib/demo/material/search_demo.dart index d691fe56a..d497bdd7d 100644 --- a/web/gallery/lib/demo/material/search_demo.dart +++ b/web/gallery/lib/demo/material/search_demo.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -60,7 +60,7 @@ class _SearchDemoState extends State { ? Icons.more_horiz : Icons.more_vert, ), - onPressed: () {}, + onPressed: () { }, ), ], ), @@ -86,13 +86,12 @@ class _SearchDemoState extends State { Text(' icon in the AppBar'), ], ), - const Text( - 'and search for an integer between 0 and 100,000.'), + const Text('and search for an integer between 0 and 100,000.'), ], ), ), const SizedBox(height: 64.0), - Text('Last selected integer: ${_lastIntegerSelected ?? 'NONE'}.') + Text('Last selected integer: ${_lastIntegerSelected ?? 'NONE' }.'), ], ), ), @@ -113,6 +112,7 @@ class _SearchDemoState extends State { currentAccountPicture: CircleAvatar( backgroundImage: AssetImage( 'people/square/peter.png', + package: 'flutter_gallery_assets', ), ), margin: EdgeInsets.zero, @@ -134,8 +134,7 @@ class _SearchDemoState extends State { } class _SearchDemoSearchDelegate extends SearchDelegate { - final List _data = - List.generate(100001, (int i) => i).reversed.toList(); + final List _data = List.generate(100001, (int i) => i).reversed.toList(); final List _history = [42607, 85604, 66374, 44, 174]; @override @@ -154,6 +153,7 @@ class _SearchDemoSearchDelegate extends SearchDelegate { @override Widget buildSuggestions(BuildContext context) { + final Iterable suggestions = query.isEmpty ? _history : _data.where((int i) => '$i'.startsWith(query)); @@ -219,7 +219,7 @@ class _SearchDemoSearchDelegate extends SearchDelegate { query = ''; showSuggestions(context); }, - ) + ), ]; } } @@ -275,8 +275,7 @@ class _SuggestionList extends StatelessWidget { title: RichText( text: TextSpan( text: suggestion.substring(0, query.length), - style: - theme.textTheme.subhead.copyWith(fontWeight: FontWeight.bold), + style: theme.textTheme.subhead.copyWith(fontWeight: FontWeight.bold), children: [ TextSpan( text: suggestion.substring(query.length), diff --git a/web/gallery/lib/demo/material/selection_controls_demo.dart b/web/gallery/lib/demo/material/selection_controls_demo.dart index c95e0f855..531ca2924 100644 --- a/web/gallery/lib/demo/material/selection_controls_demo.dart +++ b/web/gallery/lib/demo/material/selection_controls_demo.dart @@ -1,24 +1,77 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; +const String _checkboxText = + 'Checkboxes allow the user to select multiple options from a set. ' + 'A normal checkbox\'s value is true or false and a tristate checkbox\'s ' + 'value can also be null.'; + +const String _checkboxCode = 'selectioncontrols_checkbox'; + +const String _radioText = + 'Radio buttons allow the user to select one option from a set. Use radio ' + 'buttons for exclusive selection if you think that the user needs to see ' + 'all available options side-by-side.'; + +const String _radioCode = 'selectioncontrols_radio'; + +const String _switchText = + 'On/off switches toggle the state of a single settings option. The option ' + 'that the switch controls, as well as the state it’s in, should be made ' + 'clear from the corresponding inline label.'; + +const String _switchCode = 'selectioncontrols_switch'; + class SelectionControlsDemo extends StatefulWidget { - static const String routeName = '/material/selection'; + static const String routeName = '/material/selection-controls'; + @override _SelectionControlsDemoState createState() => _SelectionControlsDemoState(); } class _SelectionControlsDemoState extends State { - final GlobalKey _scaffoldKey = GlobalKey(); + @override + Widget build(BuildContext context) { + final List demos = [ + ComponentDemoTabData( + tabName: 'CHECKBOX', + description: _checkboxText, + demoWidget: buildCheckbox(), + exampleCodeTag: _checkboxCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/Checkbox-class.html', + ), + ComponentDemoTabData( + tabName: 'RADIO', + description: _radioText, + demoWidget: buildRadio(), + exampleCodeTag: _radioCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/Radio-class.html', + ), + ComponentDemoTabData( + tabName: 'SWITCH', + description: _switchText, + demoWidget: buildSwitch(), + exampleCodeTag: _switchCode, + documentationUrl: 'https://docs.flutter.io/flutter/material/Switch-class.html', + ), + ]; + + return TabbedComponentDemoScaffold( + title: 'Selection controls', + demos: demos, + ); + } bool checkboxValueA = true; bool checkboxValueB = false; bool checkboxValueC; int radioValue = 0; + bool switchValue = false; void handleRadioValueChanged(int value) { setState(() { @@ -26,23 +79,12 @@ class _SelectionControlsDemoState extends State { }); } - @override - Widget build(BuildContext context) { - return wrapScaffold('Selection Controls', context, _scaffoldKey, - _buildContents(), SelectionControlsDemo.routeName); - } - - Widget _buildContents() { - return Material( - color: Colors.white, - child: new Column( - children: [buildCheckbox(), Divider(), buildRadio()])); - } - Widget buildCheckbox() { return Align( - alignment: const Alignment(0.0, -0.2), - child: Column(mainAxisSize: MainAxisSize.min, children: [ + alignment: const Alignment(0.0, -0.2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ Row( mainAxisSize: MainAxisSize.min, children: [ @@ -73,39 +115,91 @@ class _SelectionControlsDemoState extends State { ), ], ), - Row(mainAxisSize: MainAxisSize.min, children: const [ - // Disabled checkboxes - Checkbox(value: true, onChanged: null), - Checkbox(value: false, onChanged: null), - Checkbox(value: null, tristate: true, onChanged: null), - ]) - ])); + Row( + mainAxisSize: MainAxisSize.min, + children: const [ + // Disabled checkboxes + Checkbox(value: true, onChanged: null), + Checkbox(value: false, onChanged: null), + Checkbox(value: null, tristate: true, onChanged: null), + ], + ), + ], + ), + ); } Widget buildRadio() { return Align( - alignment: const Alignment(0.0, -0.2), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Row(mainAxisSize: MainAxisSize.min, children: [ - Radio( + alignment: const Alignment(0.0, -0.2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Radio( value: 0, groupValue: radioValue, - onChanged: handleRadioValueChanged), - Radio( + onChanged: handleRadioValueChanged, + ), + Radio( value: 1, groupValue: radioValue, - onChanged: handleRadioValueChanged), - Radio( + onChanged: handleRadioValueChanged, + ), + Radio( value: 2, groupValue: radioValue, - onChanged: handleRadioValueChanged) - ]), + onChanged: handleRadioValueChanged, + ), + ], + ), // Disabled radio buttons - Row(mainAxisSize: MainAxisSize.min, children: const [ - Radio(value: 0, groupValue: 0, onChanged: null), - Radio(value: 1, groupValue: 0, onChanged: null), - Radio(value: 2, groupValue: 0, onChanged: null) - ]) - ])); + Row( + mainAxisSize: MainAxisSize.min, + children: const [ + Radio( + value: 0, + groupValue: 0, + onChanged: null, + ), + Radio( + value: 1, + groupValue: 0, + onChanged: null, + ), + Radio( + value: 2, + groupValue: 0, + onChanged: null, + ), + ], + ), + ], + ), + ); + } + + Widget buildSwitch() { + return Align( + alignment: const Alignment(0.0, -0.2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Switch.adaptive( + value: switchValue, + onChanged: (bool value) { + setState(() { + switchValue = value; + }); + }, + ), + // Disabled switches + const Switch.adaptive(value: true, onChanged: null), + const Switch.adaptive(value: false, onChanged: null), + ], + ), + ); } } diff --git a/web/gallery/lib/demo/material/slider_demo.dart b/web/gallery/lib/demo/material/slider_demo.dart index a2c2ca06c..3ba93ecc0 100644 --- a/web/gallery/lib/demo/material/slider_demo.dart +++ b/web/gallery/lib/demo/material/slider_demo.dart @@ -1,10 +1,10 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; @@ -15,30 +15,102 @@ class SliderDemo extends StatefulWidget { _SliderDemoState createState() => _SliderDemoState(); } -Path _triangle(double size, Offset thumbCenter, {bool invert = false}) { +Path _downTriangle(double size, Offset thumbCenter, { bool invert = false }) { final Path thumbPath = Path(); final double height = math.sqrt(3.0) / 2.0; - final double halfSide = size / 2.0; final double centerHeight = size * height / 3.0; + final double halfSize = size / 2.0; final double sign = invert ? -1.0 : 1.0; - thumbPath.moveTo( - thumbCenter.dx - halfSide, thumbCenter.dy + sign * centerHeight); + thumbPath.moveTo(thumbCenter.dx - halfSize, thumbCenter.dy + sign * centerHeight); thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2.0 * sign * centerHeight); - thumbPath.lineTo( - thumbCenter.dx + halfSide, thumbCenter.dy + sign * centerHeight); + thumbPath.lineTo(thumbCenter.dx + halfSize, thumbCenter.dy + sign * centerHeight); thumbPath.close(); return thumbPath; } +Path _rightTriangle(double size, Offset thumbCenter, { bool invert = false }) { + final Path thumbPath = Path(); + final double halfSize = size / 2.0; + final double sign = invert ? -1.0 : 1.0; + thumbPath.moveTo(thumbCenter.dx + halfSize * sign, thumbCenter.dy); + thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy - size); + thumbPath.lineTo(thumbCenter.dx - halfSize * sign, thumbCenter.dy + size); + thumbPath.close(); + return thumbPath; +} + +Path _upTriangle(double size, Offset thumbCenter) => _downTriangle(size, thumbCenter, invert: true); + +Path _leftTriangle(double size, Offset thumbCenter) => _rightTriangle(size, thumbCenter, invert: true); + +class _CustomRangeThumbShape extends RangeSliderThumbShape { + static const double _thumbSize = 4.0; + static const double _disabledThumbSize = 3.0; + + @override + Size getPreferredSize(bool isEnabled, bool isDiscrete) { + return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize); + } + + static final Animatable sizeTween = Tween( + begin: _disabledThumbSize, + end: _thumbSize, + ); + + @override + void paint( + PaintingContext context, + Offset center, { + @required Animation activationAnimation, + @required Animation enableAnimation, + bool isDiscrete = false, + bool isEnabled = false, + bool isOnTop, + @required SliderThemeData sliderTheme, + TextDirection textDirection, + Thumb thumb, + }) { + final Canvas canvas = context.canvas; + final ColorTween colorTween = ColorTween( + begin: sliderTheme.disabledThumbColor, + end: sliderTheme.thumbColor, + ); + + final double size = _thumbSize * sizeTween.evaluate(enableAnimation); + Path thumbPath; + switch (textDirection) { + case TextDirection.rtl: + switch (thumb) { + case Thumb.start: + thumbPath = _rightTriangle(size, center); + break; + case Thumb.end: + thumbPath = _leftTriangle(size, center); + break; + } + break; + case TextDirection.ltr: + switch (thumb) { + case Thumb.start: + thumbPath = _leftTriangle(size, center); + break; + case Thumb.end: + thumbPath = _rightTriangle(size, center); + break; + } + break; + } + canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation)); + } +} + class _CustomThumbShape extends SliderComponentShape { static const double _thumbSize = 4.0; static const double _disabledThumbSize = 3.0; @override Size getPreferredSize(bool isEnabled, bool isDiscrete) { - return isEnabled - ? const Size.fromRadius(_thumbSize) - : const Size.fromRadius(_disabledThumbSize); + return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize); } static final Animatable sizeTween = Tween( @@ -65,9 +137,8 @@ class _CustomThumbShape extends SliderComponentShape { end: sliderTheme.thumbColor, ); final double size = _thumbSize * sizeTween.evaluate(enableAnimation); - final Path thumbPath = _triangle(size, thumbCenter); - canvas.drawPath( - thumbPath, Paint()..color = colorTween.evaluate(enableAnimation)); + final Path thumbPath = _downTriangle(size, thumbCenter); + canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation)); } } @@ -109,16 +180,9 @@ class _CustomValueIndicatorShape extends SliderComponentShape { end: _slideUpHeight, ); final double size = _indicatorSize * sizeTween.evaluate(enableAnimation); - final Offset slideUpOffset = - Offset(0.0, -slideUpTween.evaluate(activationAnimation)); - final Path thumbPath = _triangle( - size, - thumbCenter + slideUpOffset, - invert: true, - ); - final Color paintColor = enableColor - .evaluate(enableAnimation) - .withAlpha((255.0 * activationAnimation.value).round()); + final Offset slideUpOffset = Offset(0.0, -slideUpTween.evaluate(activationAnimation)); + final Path thumbPath = _upTriangle(size, thumbCenter + slideUpOffset); + final Color paintColor = enableColor.evaluate(enableAnimation).withAlpha((255.0 * activationAnimation.value).round()); canvas.drawPath( thumbPath, Paint()..color = paintColor, @@ -130,27 +194,49 @@ class _CustomValueIndicatorShape extends SliderComponentShape { ..color = paintColor ..style = PaintingStyle.stroke ..strokeWidth = 2.0); - labelPainter.paint( - canvas, - thumbCenter + - slideUpOffset + - Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0)); + labelPainter.paint(canvas, thumbCenter + slideUpOffset + Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0)); } } class _SliderDemoState extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - - double _value = 25.0; - double _discreteValue = 40.0; - @override Widget build(BuildContext context) { - return wrapScaffold('Slider Demo', context, _scaffoldKey, - _buildContents(context), SliderDemo.routeName); + final List demos = [ + ComponentDemoTabData( + tabName: 'SINGLE', + description: 'Sliders containing 1 thumb', + demoWidget: _Sliders(), + documentationUrl: 'https://docs.flutter.io/flutter/material/Slider-class.html', + ), + ComponentDemoTabData( + tabName: 'RANGE', + description: 'Sliders containing 2 thumbs', + demoWidget: _RangeSliders(), + documentationUrl: 'https://docs.flutter.io/flutter/material/RangeSlider-class.html', + ), + ]; + + return TabbedComponentDemoScaffold( + title: 'Sliders', + demos: demos, + isScrollable: false, + showExampleCodeAction: false, + ); } +} + +class _Sliders extends StatefulWidget { + @override + _SlidersState createState() => _SlidersState(); +} + +class _SlidersState extends State<_Sliders> { + double _continuousValue = 25.0; + double _discreteValue = 20.0; + double _discreteCustomValue = 25.0; - Widget _buildContents(BuildContext context) { + @override + Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -160,30 +246,52 @@ class _SliderDemoState extends State { Column( mainAxisSize: MainAxisSize.min, children: [ - Slider( - value: _value, + Semantics( + label: 'Editable numerical value', + child: SizedBox( + width: 64, + height: 48, + child: TextField( + textAlign: TextAlign.center, + onSubmitted: (String value) { + final double newValue = double.tryParse(value); + if (newValue != null && newValue != _continuousValue) { + setState(() { + _continuousValue = newValue.clamp(0, 100); + }); + } + }, + keyboardType: TextInputType.number, + controller: TextEditingController( + text: _continuousValue.toStringAsFixed(0), + ), + ), + ), + ), + Slider.adaptive( + value: _continuousValue, min: 0.0, max: 100.0, onChanged: (double value) { setState(() { - _value = value; + _continuousValue = value; }); }, ), - const Text('Continuous'), + const Text('Continuous with Editable Numerical Value'), ], ), Column( mainAxisSize: MainAxisSize.min, - children: [ - Slider(value: 0.25, onChanged: (double val) {}), + children: const [ + Slider.adaptive(value: 0.25, onChanged: null), Text('Disabled'), ], ), Column( mainAxisSize: MainAxisSize.min, children: [ - Slider( + Slider.adaptive( value: _discreteValue, min: 0.0, max: 200.0, @@ -204,28 +312,120 @@ class _SliderDemoState extends State { SliderTheme( data: theme.sliderTheme.copyWith( activeTrackColor: Colors.deepPurple, - inactiveTrackColor: Colors.black26, - activeTickMarkColor: Colors.white70, - inactiveTickMarkColor: Colors.black, - overlayColor: Colors.black12, + inactiveTrackColor: theme.colorScheme.onSurface.withOpacity(0.5), + activeTickMarkColor: theme.colorScheme.onSurface.withOpacity(0.7), + inactiveTickMarkColor: theme.colorScheme.surface.withOpacity(0.7), + overlayColor: theme.colorScheme.onSurface.withOpacity(0.12), thumbColor: Colors.deepPurple, valueIndicatorColor: Colors.deepPurpleAccent, thumbShape: _CustomThumbShape(), valueIndicatorShape: _CustomValueIndicatorShape(), - valueIndicatorTextStyle: theme.accentTextTheme.body2 - .copyWith(color: Colors.black87), + valueIndicatorTextStyle: theme.accentTextTheme.body2.copyWith(color: theme.colorScheme.onSurface), ), child: Slider( - value: _discreteValue, + value: _discreteCustomValue, min: 0.0, max: 200.0, divisions: 5, - semanticFormatterCallback: (double value) => - value.round().toString(), - label: '${_discreteValue.round()}', + semanticFormatterCallback: (double value) => value.round().toString(), + label: '${_discreteCustomValue.round()}', onChanged: (double value) { setState(() { - _discreteValue = value; + _discreteCustomValue = value; + }); + }, + ), + ), + const Text('Discrete with Custom Theme'), + ], + ), + ], + ), + ); + } +} + +class _RangeSliders extends StatefulWidget { + @override + _RangeSlidersState createState() => _RangeSlidersState(); +} + +class _RangeSlidersState extends State<_RangeSliders> { + RangeValues _continuousValues = const RangeValues(25.0, 75.0); + RangeValues _discreteValues = const RangeValues(40.0, 120.0); + RangeValues _discreteCustomValues = const RangeValues(40.0, 160.0); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + RangeSlider( + values: _continuousValues, + min: 0.0, + max: 100.0, + onChanged: (RangeValues values) { + setState(() { + _continuousValues = values; + }); + }, + ), + const Text('Continuous'), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + RangeSlider(values: const RangeValues(0.25, 0.75), onChanged: null), + const Text('Disabled'), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + RangeSlider( + values: _discreteValues, + min: 0.0, + max: 200.0, + divisions: 5, + labels: RangeLabels('${_discreteValues.start.round()}', '${_discreteValues.end.round()}'), + onChanged: (RangeValues values) { + setState(() { + _discreteValues = values; + }); + }, + ), + const Text('Discrete'), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + SliderTheme( + data: SliderThemeData( + activeTrackColor: Colors.deepPurple, + inactiveTrackColor: Colors.black26, + activeTickMarkColor: Colors.white70, + inactiveTickMarkColor: Colors.black, + overlayColor: Colors.black12, + thumbColor: Colors.deepPurple, + rangeThumbShape: _CustomRangeThumbShape(), + showValueIndicator: ShowValueIndicator.never, + ), + child: RangeSlider( + values: _discreteCustomValues, + min: 0.0, + max: 200.0, + divisions: 5, + labels: RangeLabels('${_discreteCustomValues.start.round()}', '${_discreteCustomValues.end.round()}'), + onChanged: (RangeValues values) { + setState(() { + _discreteCustomValues = values; }); }, ), @@ -238,3 +438,4 @@ class _SliderDemoState extends State { ); } } + diff --git a/web/gallery/lib/demo/material/snack_bar_demo.dart b/web/gallery/lib/demo/material/snack_bar_demo.dart index 33ee7cc5e..011203d6f 100644 --- a/web/gallery/lib/demo/material/snack_bar_demo.dart +++ b/web/gallery/lib/demo/material/snack_bar_demo.dart @@ -1,24 +1,25 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; + import '../../gallery/demo.dart'; const String _text1 = - 'Snackbars provide lightweight feedback about an operation by ' - 'showing a brief message at the bottom of the screen. Snackbars ' - 'can contain an action.'; + 'Snackbars provide lightweight feedback about an operation by ' + 'showing a brief message at the bottom of the screen. Snackbars ' + 'can contain an action.'; const String _text2 = - 'Snackbars should contain a single line of text directly related ' - 'to the operation performed. They cannot contain icons.'; + 'Snackbars should contain a single line of text directly related ' + 'to the operation performed. They cannot contain icons.'; const String _text3 = - 'By default snackbars automatically disappear after a few seconds '; + 'By default snackbars automatically disappear after a few seconds '; class SnackBarDemo extends StatefulWidget { - const SnackBarDemo({Key key}) : super(key: key); + const SnackBarDemo({ Key key }) : super(key: key); static const String routeName = '/material/snack-bar'; @@ -34,50 +35,61 @@ class _SnackBarDemoState extends State { top: false, bottom: false, child: ListView( - padding: const EdgeInsets.all(24.0), - children: [ - const Text(_text1), - const Text(_text2), - Center( - child: Row(children: [ - RaisedButton( - child: const Text('SHOW A SNACKBAR'), + padding: const EdgeInsets.all(24.0), + children: [ + const Text(_text1), + const Text(_text2), + Center( + child: RaisedButton( + child: const Text('SHOW A SNACKBAR'), + onPressed: () { + final int thisSnackBarIndex = _snackBarIndex++; + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('This is snackbar #$thisSnackBarIndex.'), + action: SnackBarAction( + label: 'ACTION', onPressed: () { - final int thisSnackBarIndex = _snackBarIndex++; Scaffold.of(context).showSnackBar(SnackBar( - content: Text('This is snackbar #$thisSnackBarIndex.'), - action: SnackBarAction( - label: 'ACTION', - onPressed: () { - Scaffold.of(context).showSnackBar(SnackBar( - content: Text( - 'You pressed snackbar $thisSnackBarIndex\'s action.'))); - }), + content: Text('You pressed snackbar $thisSnackBarIndex\'s action.'), )); - }), - ]), + }, + ), + )); + }, ), - const Text(_text3), - ].map((Widget child) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 12.0), - child: child); - }).toList()), + ), + const Text(_text3), + ] + .map((Widget child) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 12.0), + child: child, + ); + }) + .toList(), + ), ); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Snackbar'), - actions: [ - MaterialDemoDocumentationButton(SnackBarDemo.routeName) - ], - ), - body: Builder( - // Create an inner BuildContext so that the snackBar onPressed methods - // can refer to the Scaffold with Scaffold.of(). - builder: buildBody)); + appBar: AppBar( + title: const Text('Snackbar'), + actions: [MaterialDemoDocumentationButton(SnackBarDemo.routeName)], + ), + body: Builder( + // Create an inner BuildContext so that the snackBar onPressed methods + // can refer to the Scaffold with Scaffold.of(). + builder: buildBody + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + tooltip: 'Create', + onPressed: () { + print('Floating Action Button was pressed'); + } + ), + ); } } diff --git a/web/gallery/lib/demo/material/stack_demo.dart b/web/gallery/lib/demo/material/stack_demo.dart deleted file mode 100644 index 61ac4a59d..000000000 --- a/web/gallery/lib/demo/material/stack_demo.dart +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -class StackDemo extends StatelessWidget { - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - border: Border.all( - color: Colors.greenAccent, - width: 1.0, - ), - ), - child: Stack(children: [ - Text('A'), - Text('B'), - ]), - ); - } -} diff --git a/web/gallery/lib/demo/material/switch_demo.dart b/web/gallery/lib/demo/material/switch_demo.dart deleted file mode 100644 index 9380c3475..000000000 --- a/web/gallery/lib/demo/material/switch_demo.dart +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -import '../../gallery/demo.dart'; - -class SwitchDemo extends StatefulWidget { - static const routeName = '/material/switch'; - - @override - SwitchDemoState createState() => SwitchDemoState(); -} - -class SwitchDemoState extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - return wrapScaffold('Switch Demo', context, _scaffoldKey, _buildContents(), - SwitchDemo.routeName); - } - - bool _value = true; - - Widget _buildContents() { - return Material( - child: Column( - children: [ - Switch( - value: _value, - onChanged: (bool newValue) { - setState(() { - _value = newValue; - }); - }), - ], - ), - ); - } -} diff --git a/web/gallery/lib/demo/material/tabs_demo.dart b/web/gallery/lib/demo/material/tabs_demo.dart index d3a747fd0..a9064b151 100644 --- a/web/gallery/lib/demo/material/tabs_demo.dart +++ b/web/gallery/lib/demo/material/tabs_demo.dart @@ -1,18 +1,18 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Each TabBarView contains a _Page and for each _Page there is a list -// of _CardData objects. Each _CardData object is displayed by a _CardItem. - -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; +// Each TabBarView contains a _Page and for each _Page there is a list +// of _CardData objects. Each _CardData object is displayed by a _CardItem. + const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; class _Page { - _Page({this.label}); + _Page({ this.label }); final String label; String get id => label[0]; @override @@ -20,7 +20,7 @@ class _Page { } class _CardData { - const _CardData({this.title, this.imageAsset, this.imageAssetPackage}); + const _CardData({ this.title, this.imageAsset, this.imageAssetPackage }); final String title; final String imageAsset; final String imageAssetPackage; @@ -94,7 +94,7 @@ final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{ }; class _CardDataItem extends StatelessWidget { - const _CardDataItem({this.page, this.data}); + const _CardDataItem({ this.page, this.data }); static const double height = 272.0; final _Page page; @@ -110,17 +110,20 @@ class _CardDataItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ Align( - alignment: - page.id == 'H' ? Alignment.centerLeft : Alignment.centerRight, + alignment: page.id == 'H' + ? Alignment.centerLeft + : Alignment.centerRight, child: CircleAvatar(child: Text('${page.id}')), ), - SizedBox(width: 144.0, height: 144.0, child: new Text('image') -// Image.asset( -// data.imageAsset, -// package: data.imageAssetPackage, -// fit: BoxFit.contain, -// ), - ), + SizedBox( + width: 144.0, + height: 144.0, + child: Image.asset( + data.imageAsset, + package: data.imageAssetPackage, + fit: BoxFit.contain, + ), + ), Center( child: Text( data.title, @@ -145,18 +148,19 @@ class TabsDemo extends StatelessWidget { body: NestedScrollView( headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ - SliverAppBar( - title: const Text('Tabs and scrolling'), - actions: [MaterialDemoDocumentationButton(routeName)], - pinned: true, - expandedHeight: 150.0, - forceElevated: innerBoxIsScrolled, - bottom: TabBar( - tabs: _allPages.keys - .map( - (_Page page) => Tab(text: page.label), - ) - .toList(), + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + child: SliverAppBar( + title: const Text('Tabs and scrolling'), + actions: [MaterialDemoDocumentationButton(routeName)], + pinned: true, + expandedHeight: 150.0, + forceElevated: innerBoxIsScrolled, + bottom: TabBar( + tabs: _allPages.keys.map( + (_Page page) => Tab(text: page.label), + ).toList(), + ), ), ), ]; @@ -171,6 +175,9 @@ class TabsDemo extends StatelessWidget { return CustomScrollView( key: PageStorageKey<_Page>(page), slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), SliverPadding( padding: const EdgeInsets.symmetric( vertical: 8.0, diff --git a/web/gallery/lib/demo/material/tabs_fab_demo.dart b/web/gallery/lib/demo/material/tabs_fab_demo.dart index 6120c4540..51a2e2fb8 100644 --- a/web/gallery/lib/demo/material/tabs_fab_demo.dart +++ b/web/gallery/lib/demo/material/tabs_fab_demo.dart @@ -1,26 +1,25 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; const String _explanatoryText = - "When the Scaffold's floating action button changes, the new button fades and " - 'turns into view. In this demo, changing tabs can cause the app to be rebuilt ' - 'with a FloatingActionButton that the Scaffold distinguishes from the others ' - 'by its key.'; + "When the Scaffold's floating action button changes, the new button fades and " + 'turns into view. In this demo, changing tabs can cause the app to be rebuilt ' + 'with a FloatingActionButton that the Scaffold distinguishes from the others ' + 'by its key.'; class _Page { - _Page({this.label, this.colors, this.icon}); + _Page({ this.label, this.colors, this.icon }); final String label; final MaterialColor colors; final IconData icon; - Color get labelColor => - colors != null ? colors.shade300 : Colors.grey.shade300; + Color get labelColor => colors != null ? colors.shade300 : Colors.grey.shade300; bool get fabDefined => colors != null && icon != null; Color get fabColor => colors.shade400; Icon get fabIcon => Icon(icon); @@ -42,8 +41,7 @@ class TabsFabDemo extends StatefulWidget { _TabsFabDemoState createState() => _TabsFabDemoState(); } -class _TabsFabDemoState extends State - with SingleTickerProviderStateMixin { +class _TabsFabDemoState extends State with SingleTickerProviderStateMixin { final GlobalKey _scaffoldKey = GlobalKey(); TabController _controller; @@ -71,50 +69,63 @@ class _TabsFabDemoState extends State } void _showExplanatoryText() { - _scaffoldKey.currentState.showBottomSheet((BuildContext context) { + _scaffoldKey.currentState.showBottomSheet((BuildContext context) { return Container( - decoration: BoxDecoration( - border: Border( - top: BorderSide(color: Theme.of(context).dividerColor))), - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Text(_explanatoryText, - style: Theme.of(context).textTheme.subhead))); + decoration: BoxDecoration( + border: Border(top: BorderSide(color: Theme.of(context).dividerColor)) + ), + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Text(_explanatoryText, style: Theme.of(context).textTheme.subhead), + ), + ); }); } Widget buildTabView(_Page page) { - return Builder(builder: (BuildContext context) { - return Container( + return Builder( + builder: (BuildContext context) { + return Container( key: ValueKey(page.label), padding: const EdgeInsets.fromLTRB(48.0, 48.0, 48.0, 96.0), child: Card( - child: Center( - child: Text(page.label, - style: TextStyle(color: page.labelColor, fontSize: 32.0), - textAlign: TextAlign.center)))); - }); + child: Center( + child: Text(page.label, + style: TextStyle( + color: page.labelColor, + fontSize: 32.0, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + } + ); } Widget buildFloatingActionButton(_Page page) { - if (!page.fabDefined) return null; + if (!page.fabDefined) + return null; if (_extendedButtons) { return FloatingActionButton.extended( - key: ValueKey(page.fabKey), - tooltip: 'Show explanation', - backgroundColor: page.fabColor, - icon: page.fabIcon, - label: Text(page.label.toUpperCase()), - onPressed: _showExplanatoryText); + key: ValueKey(page.fabKey), + tooltip: 'Show explanation', + backgroundColor: page.fabColor, + icon: page.fabIcon, + label: Text(page.label.toUpperCase()), + onPressed: _showExplanatoryText, + ); } return FloatingActionButton( - key: page.fabKey, - tooltip: 'Show explanation', - backgroundColor: page.fabColor, - child: page.fabIcon, - onPressed: _showExplanatoryText); + key: page.fabKey, + tooltip: 'Show explanation', + backgroundColor: page.fabColor, + child: page.fabIcon, + onPressed: _showExplanatoryText, + ); } @override @@ -125,14 +136,12 @@ class _TabsFabDemoState extends State title: const Text('FAB per tab'), bottom: TabBar( controller: _controller, - tabs: _allPages - .map((_Page page) => Tab(text: page.label.toUpperCase())) - .toList(), + tabs: _allPages.map((_Page page) => Tab(text: page.label.toUpperCase())).toList(), ), actions: [ MaterialDemoDocumentationButton(TabsFabDemo.routeName), IconButton( - icon: const Icon(Icons.sentiment_very_satisfied), + icon: const Icon(Icons.sentiment_very_satisfied, semanticLabel: 'Toggle extended buttons'), onPressed: () { setState(() { _extendedButtons = !_extendedButtons; @@ -143,8 +152,9 @@ class _TabsFabDemoState extends State ), floatingActionButton: buildFloatingActionButton(_selectedPage), body: TabBarView( - controller: _controller, - children: _allPages.map(buildTabView).toList()), + controller: _controller, + children: _allPages.map(buildTabView).toList(), + ), ); } } diff --git a/web/gallery/lib/demo/material/text_demo.dart b/web/gallery/lib/demo/material/text_demo.dart deleted file mode 100644 index 0254752a1..000000000 --- a/web/gallery/lib/demo/material/text_demo.dart +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -class TextDemo extends StatelessWidget { - static const routeName = '/material/text'; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Text'), - centerTitle: true, - ), - body: ListView( - children: [ - pad(Text('Single line of text')), - Divider(), - // Single line with many whitespaces in between. - pad(Text(' Text with a lot of whitespace ')), - Divider(), - // Forced multi-line because of the \n. - pad(Text('Text with a newline\ncharacter should render in 2 lines')), - Divider(), - // Multi-line with regular whitespace. - pad(Text( - '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas auctor -vel ligula eget fermentum. Integer mattis nulla vitae ullamcorper -dignissim. Donec vel velit vel eros lobortis laoreet at sit amet turpis. -Ut in orci blandit, rhoncus metus quis, finibus augue. Nullam a elit -venenatis metus accumsan dapibus. Vestibulum imperdiet tristique viverra.''', - )), - Divider(), - // Multi-line with a lot of whitespace in between. - pad(Text( - ''' - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Maecenas auctor vel ligula eget fermentum. - Integer mattis nulla vitae ullamcorper dignissim. - Donec vel velit vel eros lobortis laoreet at sit amet turpis.''', - )), - Divider(), - ], - ), - ); - } - - Padding pad(Widget child) => - Padding(padding: EdgeInsets.all(12), child: child); -} diff --git a/web/gallery/lib/demo/material/text_form_field_demo.dart b/web/gallery/lib/demo/material/text_form_field_demo.dart index 62dbd8f13..d69e9313b 100644 --- a/web/gallery/lib/demo/material/text_form_field_demo.dart +++ b/web/gallery/lib/demo/material/text_form_field_demo.dart @@ -1,16 +1,17 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/services.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import '../../gallery/demo.dart'; class TextFormFieldDemo extends StatefulWidget { - const TextFormFieldDemo({Key key}) : super(key: key); + const TextFormFieldDemo({ Key key }) : super(key: key); static const String routeName = '/material/text-form-field'; @@ -67,6 +68,7 @@ class _PasswordFieldState extends State { labelText: widget.labelText, helperText: widget.helperText, suffixIcon: GestureDetector( + dragStartBehavior: DragStartBehavior.down, onTap: () { setState(() { _obscureText = !_obscureText; @@ -88,17 +90,17 @@ class TextFormFieldDemoState extends State { PersonData person = PersonData(); void showInSnackBar(String value) { - _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(value))); + _scaffoldKey.currentState.showSnackBar(SnackBar( + content: Text(value), + )); } bool _autovalidate = false; bool _formWasEdited = false; final GlobalKey _formKey = GlobalKey(); - final GlobalKey> _passwordFieldKey = - GlobalKey>(); - final _UsNumberTextInputFormatter _phoneNumberFormatter = - _UsNumberTextInputFormatter(); + final GlobalKey> _passwordFieldKey = GlobalKey>(); + final _UsNumberTextInputFormatter _phoneNumberFormatter = _UsNumberTextInputFormatter(); void _handleSubmitted() { final FormState form = _formKey.currentState; if (!form.validate()) { @@ -112,7 +114,8 @@ class TextFormFieldDemoState extends State { String _validateName(String value) { _formWasEdited = true; - if (value.isEmpty) return 'Name is required.'; + if (value.isEmpty) + return 'Name is required.'; final RegExp nameExp = RegExp(r'^[A-Za-z ]+$'); if (!nameExp.hasMatch(value)) return 'Please enter only alphabetical characters.'; @@ -132,49 +135,45 @@ class TextFormFieldDemoState extends State { final FormFieldState passwordField = _passwordFieldKey.currentState; if (passwordField.value == null || passwordField.value.isEmpty) return 'Please enter a password.'; - if (passwordField.value != value) return 'The passwords don\'t match'; + if (passwordField.value != value) + return 'The passwords don\'t match'; return null; } Future _warnUserAboutInvalidData() async { final FormState form = _formKey.currentState; - if (form == null || !_formWasEdited || form.validate()) return true; + if (form == null || !_formWasEdited || form.validate()) + return true; return await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('This form has errors'), - content: const Text('Really leave this form?'), - actions: [ - FlatButton( - child: const Text('YES'), - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - FlatButton( - child: const Text('NO'), - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - ], - ); - }, - ) ?? - false; + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('This form has errors'), + content: const Text('Really leave this form?'), + actions: [ + FlatButton( + child: const Text('YES'), + onPressed: () { Navigator.of(context).pop(true); }, + ), + FlatButton( + child: const Text('NO'), + onPressed: () { Navigator.of(context).pop(false); }, + ), + ], + ); + }, + ) ?? false; } @override Widget build(BuildContext context) { return Scaffold( + drawerDragStartBehavior: DragStartBehavior.down, key: _scaffoldKey, appBar: AppBar( title: const Text('Text fields'), - actions: [ - MaterialDemoDocumentationButton(TextFormFieldDemo.routeName) - ], + actions: [MaterialDemoDocumentationButton(TextFormFieldDemo.routeName)], ), body: SafeArea( top: false, @@ -183,120 +182,118 @@ class TextFormFieldDemoState extends State { key: _formKey, autovalidate: _autovalidate, onWillPop: _warnUserAboutInvalidData, - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 24.0), - TextFormField( - textCapitalization: TextCapitalization.words, - decoration: const InputDecoration( - border: UnderlineInputBorder(), - filled: true, - icon: Icon(Icons.person), - hintText: 'What do people call you?', - labelText: 'Name * ', + child: Scrollbar( + child: SingleChildScrollView( + dragStartBehavior: DragStartBehavior.down, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24.0), + TextFormField( + textCapitalization: TextCapitalization.words, + decoration: const InputDecoration( + border: UnderlineInputBorder(), + filled: true, + icon: Icon(Icons.person), + hintText: 'What do people call you?', + labelText: 'Name *', + ), + onSaved: (String value) { person.name = value; }, + validator: _validateName, ), - onSaved: (String value) { - person.name = value; - }, - validator: _validateName, - ), - const SizedBox(height: 24.0), - TextFormField( - decoration: const InputDecoration( - border: UnderlineInputBorder(), - filled: true, - icon: Icon(Icons.phone), - hintText: 'Where can we reach you?', - labelText: 'Phone Number * ', - prefixText: '+1', + const SizedBox(height: 24.0), + TextFormField( + decoration: const InputDecoration( + border: UnderlineInputBorder(), + filled: true, + icon: Icon(Icons.phone), + hintText: 'Where can we reach you?', + labelText: 'Phone Number *', + prefixText: '+1', + ), + keyboardType: TextInputType.phone, + onSaved: (String value) { person.phoneNumber = value; }, + validator: _validatePhoneNumber, + // TextInputFormatters are applied in sequence. + inputFormatters: [ + WhitelistingTextInputFormatter.digitsOnly, + // Fit the validating format. + _phoneNumberFormatter, + ], ), - keyboardType: TextInputType.phone, - onSaved: (String value) { - person.phoneNumber = value; - }, - validator: _validatePhoneNumber, - // TextInputFormatters are applied in sequence. - inputFormatters: [ - WhitelistingTextInputFormatter.digitsOnly, - // Fit the validating format. - _phoneNumberFormatter, - ], - ), - const SizedBox(height: 24.0), - TextFormField( - decoration: const InputDecoration( - border: UnderlineInputBorder(), - filled: true, - icon: Icon(Icons.email), - hintText: 'Your email address', - labelText: 'E-mail', + const SizedBox(height: 24.0), + TextFormField( + decoration: const InputDecoration( + border: UnderlineInputBorder(), + filled: true, + icon: Icon(Icons.email), + hintText: 'Your email address', + labelText: 'E-mail', + ), + keyboardType: TextInputType.emailAddress, + onSaved: (String value) { person.email = value; }, ), - keyboardType: TextInputType.emailAddress, - onSaved: (String value) { - person.email = value; - }, - ), - const SizedBox(height: 24.0), - TextFormField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: - 'Tell us about yourself (e.g., write down what you do or what hobbies you have)', - helperText: 'Keep it short, this is just a demo.', - labelText: 'Life story', + const SizedBox(height: 24.0), + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Tell us about yourself (e.g., write down what you do or what hobbies you have)', + helperText: 'Keep it short, this is just a demo.', + labelText: 'Life story', + ), + maxLines: 3, ), - maxLines: 3, - ), - const SizedBox(height: 24.0), - TextFormField( - keyboardType: TextInputType.number, - decoration: const InputDecoration( + const SizedBox(height: 24.0), + TextFormField( + keyboardType: TextInputType.number, + decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Salary', prefixText: '\$', suffixText: 'USD', - suffixStyle: TextStyle(color: Colors.green)), - maxLines: 1, - ), - const SizedBox(height: 24.0), - PasswordField( - fieldKey: _passwordFieldKey, - helperText: 'No more than 8 characters.', - labelText: 'Password *', - onFieldSubmitted: (String value) { - setState(() { - person.password = value; - }); - }, - ), - const SizedBox(height: 24.0), - TextFormField( - enabled: - person.password != null && person.password.isNotEmpty, - decoration: const InputDecoration( - border: UnderlineInputBorder(), - filled: true, - labelText: 'Re-type password', + suffixStyle: TextStyle(color: Colors.green), + ), + maxLines: 1, + ), + const SizedBox(height: 24.0), + PasswordField( + fieldKey: _passwordFieldKey, + helperText: 'No more than 8 characters.', + labelText: 'Password *', + onFieldSubmitted: (String value) { + setState(() { + person.password = value; + }); + }, + ), + const SizedBox(height: 24.0), + TextFormField( + enabled: person.password != null && person.password.isNotEmpty, + decoration: const InputDecoration( + border: UnderlineInputBorder(), + filled: true, + labelText: 'Re-type password', + ), + maxLength: 8, + obscureText: true, + validator: _validatePassword, + ), + const SizedBox(height: 24.0), + Center( + child: RaisedButton( + child: const Text('SUBMIT'), + onPressed: _handleSubmitted, + ), ), - maxLength: 8, - obscureText: true, - validator: _validatePassword, - ), - const SizedBox(height: 24.0), - Center( - child: RaisedButton( - child: const Text('SUBMIT'), - onPressed: _handleSubmitted, + const SizedBox(height: 24.0), + Text( + '* indicates required field', + style: Theme.of(context).textTheme.caption, ), - ), - const SizedBox(height: 24.0), - Text('* indicates required field', - style: Theme.of(context).textTheme.caption), - const SizedBox(height: 24.0), - ], + const SizedBox(height: 24.0), + ], + ), ), ), ), @@ -309,26 +306,32 @@ class TextFormFieldDemoState extends State { class _UsNumberTextInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate( - TextEditingValue oldValue, TextEditingValue newValue) { + TextEditingValue oldValue, + TextEditingValue newValue, + ) { final int newTextLength = newValue.text.length; int selectionIndex = newValue.selection.end; int usedSubstringIndex = 0; final StringBuffer newText = StringBuffer(); if (newTextLength >= 1) { newText.write('('); - if (newValue.selection.end >= 1) selectionIndex++; + if (newValue.selection.end >= 1) + selectionIndex++; } if (newTextLength >= 4) { newText.write(newValue.text.substring(0, usedSubstringIndex = 3) + ') '); - if (newValue.selection.end >= 3) selectionIndex += 2; + if (newValue.selection.end >= 3) + selectionIndex += 2; } if (newTextLength >= 7) { newText.write(newValue.text.substring(3, usedSubstringIndex = 6) + '-'); - if (newValue.selection.end >= 6) selectionIndex++; + if (newValue.selection.end >= 6) + selectionIndex++; } if (newTextLength >= 11) { newText.write(newValue.text.substring(6, usedSubstringIndex = 10) + ' '); - if (newValue.selection.end >= 10) selectionIndex++; + if (newValue.selection.end >= 10) + selectionIndex++; } // Dump the rest. if (newTextLength >= usedSubstringIndex) diff --git a/web/gallery/lib/demo/material/tooltip_demo.dart b/web/gallery/lib/demo/material/tooltip_demo.dart index d83bc3792..40974a176 100644 --- a/web/gallery/lib/demo/material/tooltip_demo.dart +++ b/web/gallery/lib/demo/material/tooltip_demo.dart @@ -1,59 +1,75 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../../gallery/demo.dart'; const String _introText = - 'Tooltips are short identifying messages that briefly appear in response to ' - 'a long press. Tooltip messages are also used by services that make Flutter ' - 'apps accessible, like screen readers.'; + 'Tooltips are short identifying messages that briefly appear in response to ' + 'a long press. Tooltip messages are also used by services that make Flutter ' + 'apps accessible, like screen readers.'; class TooltipDemo extends StatelessWidget { + static const String routeName = '/material/tooltips'; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); return Scaffold( - appBar: AppBar( - title: const Text('Tooltips'), - actions: [MaterialDemoDocumentationButton(routeName)], - ), - body: Builder(builder: (BuildContext context) { + appBar: AppBar( + title: const Text('Tooltips'), + actions: [MaterialDemoDocumentationButton(routeName)], + ), + body: Builder( + builder: (BuildContext context) { return SafeArea( top: false, bottom: false, child: ListView( - children: [ - Text(_introText, style: theme.textTheme.subhead), - Row(children: [ - Text('Long press the ', style: theme.textTheme.subhead), - Tooltip( - message: 'call icon', - child: Icon(Icons.call, - size: 18.0, color: theme.iconTheme.color)), - Text(' icon.', style: theme.textTheme.subhead) - ]), - Center( + children: [ + Text(_introText, style: theme.textTheme.subhead), + Row( + children: [ + Text('Long press the ', style: theme.textTheme.subhead), + Tooltip( + message: 'call icon', + child: Icon( + Icons.call, + size: 18.0, + color: theme.iconTheme.color, + ), + ), + Text(' icon.', style: theme.textTheme.subhead), + ], + ), + Center( child: IconButton( - iconSize: 48.0, - icon: const Icon(Icons.call), - color: theme.iconTheme.color, - tooltip: 'Place a phone call', - onPressed: () { - Scaffold.of(context).showSnackBar(const SnackBar( - content: Text('That was an ordinary tap.'))); - })) - ].map((Widget widget) { - return Padding( - padding: - const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), - child: widget); - }).toList()), + iconSize: 48.0, + icon: const Icon(Icons.call), + color: theme.iconTheme.color, + tooltip: 'Place a phone call', + onPressed: () { + Scaffold.of(context).showSnackBar(const SnackBar( + content: Text('That was an ordinary tap.'), + )); + }, + ), + ), + ] + .map((Widget widget) { + return Padding( + padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), + child: widget, + ); + }) + .toList(), + ), ); - })); + } + ), + ); } } diff --git a/web/gallery/lib/demo/material/two_level_list_demo.dart b/web/gallery/lib/demo/material/two_level_list_demo.dart deleted file mode 100644 index 70a2f95e4..000000000 --- a/web/gallery/lib/demo/material/two_level_list_demo.dart +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -import '../../gallery/demo.dart'; - -class TwoLevelListDemo extends StatelessWidget { - static const String routeName = '/material/two-level-list'; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Expand/collapse list control'), - actions: [MaterialDemoDocumentationButton(routeName)], - ), - body: ListView(children: [ - const ListTile(title: Text('Top')), - ExpansionTile( - title: const Text('Sublist'), - backgroundColor: Theme.of(context).accentColor.withOpacity(0.025), - children: const [ - ListTile(title: Text('One')), - ListTile(title: Text('Two')), - // https://en.wikipedia.org/wiki/Free_Four - ListTile(title: Text('Free')), - ListTile(title: Text('Four')) - ]), - const ListTile(title: Text('Bottom')) - ])); - } -} diff --git a/web/gallery/lib/demo/pesto_demo.dart b/web/gallery/lib/demo/pesto_demo.dart index cf928f4b4..cff30f068 100644 --- a/web/gallery/lib/demo/pesto_demo.dart +++ b/web/gallery/lib/demo/pesto_demo.dart @@ -1,12 +1,12 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; class PestoDemo extends StatelessWidget { - const PestoDemo({Key key}) : super(key: key); + const PestoDemo({ Key key }) : super(key: key); static const String routeName = '/pesto'; @@ -14,13 +14,14 @@ class PestoDemo extends StatelessWidget { Widget build(BuildContext context) => PestoHome(); } + const String _kSmallLogoImage = 'logos/pesto/logo_small.png'; +const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; const double _kAppBarHeight = 128.0; -const double _kFabHalfSize = - 28.0; // TODO(mpcomplete): needs to adapt to screen size +const double _kFabHalfSize = 28.0; // TODO(mpcomplete): needs to adapt to screen size const double _kRecipePageMaxWidth = 500.0; -final Set _favoriteRecipes = Set(); +final Set _favoriteRecipes = {}; final ThemeData _kTheme = ThemeData( brightness: Brightness.light, @@ -50,20 +51,20 @@ class PestoStyle extends TextStyle { double letterSpacing, double height, }) : super( - inherit: false, - color: color, - fontFamily: 'Raleway', - fontSize: fontSize, - fontWeight: fontWeight, - textBaseline: TextBaseline.alphabetic, - letterSpacing: letterSpacing, - height: height, - ); + inherit: false, + color: color, + fontFamily: 'Raleway', + fontSize: fontSize, + fontWeight: fontWeight, + textBaseline: TextBaseline.alphabetic, + letterSpacing: letterSpacing, + height: height, + ); } // Displays a grid of recipe cards. class RecipeGridPage extends StatefulWidget { - const RecipeGridPage({Key key, this.recipes}) : super(key: key); + const RecipeGridPage({ Key key, this.recipes }) : super(key: key); final List recipes; @@ -119,10 +120,8 @@ class _RecipeGridPageState extends State { builder: (BuildContext context, BoxConstraints constraints) { final Size size = constraints.biggest; final double appBarHeight = size.height - statusBarHeight; - final double t = (appBarHeight - kToolbarHeight) / - (_kAppBarHeight - kToolbarHeight); - final double extraPadding = - Tween(begin: 10.0, end: 24.0).transform(t); + final double t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight); + final double extraPadding = Tween(begin: 10.0, end: 24.0).transform(t); final double logoHeight = appBarHeight - 1.5 * extraPadding; return Padding( padding: EdgeInsets.only( @@ -130,7 +129,8 @@ class _RecipeGridPageState extends State { bottom: extraPadding, ), child: Center( - child: PestoLogo(height: logoHeight, t: t.clamp(0.0, 1.0))), + child: PestoLogo(height: logoHeight, t: t.clamp(0.0, 1.0)), + ), ); }, ), @@ -140,10 +140,11 @@ class _RecipeGridPageState extends State { Widget _buildBody(BuildContext context, double statusBarHeight) { final EdgeInsets mediaPadding = MediaQuery.of(context).padding; final EdgeInsets padding = EdgeInsets.only( - top: 8.0, - left: 8.0 + mediaPadding.left, - right: 8.0 + mediaPadding.right, - bottom: 8.0); + top: 8.0, + left: 8.0 + mediaPadding.left, + right: 8.0 + mediaPadding.right, + bottom: 8.0, + ); return SliverPadding( padding: padding, sliver: SliverGrid( @@ -157,9 +158,7 @@ class _RecipeGridPageState extends State { final Recipe recipe = widget.recipes[index]; return RecipeCard( recipe: recipe, - onTap: () { - showRecipePage(context, recipe); - }, + onTap: () { showRecipePage(context, recipe); }, ); }, childCount: widget.recipes.length, @@ -169,26 +168,22 @@ class _RecipeGridPageState extends State { } void showFavoritesPage(BuildContext context) { - Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: '/pesto/favorites'), - builder: (BuildContext context) => PestoFavorites(), - )); + Navigator.push(context, MaterialPageRoute( + settings: const RouteSettings(name: '/pesto/favorites'), + builder: (BuildContext context) => PestoFavorites(), + )); } void showRecipePage(BuildContext context, Recipe recipe) { - Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: '/pesto/recipe'), - builder: (BuildContext context) { - return Theme( - data: _kTheme.copyWith(platform: Theme.of(context).platform), - child: RecipePage(recipe: recipe), - ); - }, - )); + Navigator.push(context, MaterialPageRoute( + settings: const RouteSettings(name: '/pesto/recipe'), + builder: (BuildContext context) { + return Theme( + data: _kTheme.copyWith(platform: Theme.of(context).platform), + child: RecipePage(recipe: recipe), + ); + }, + )); } } @@ -208,18 +203,15 @@ class _PestoLogoState extends State { static const double kLogoWidth = 220.0; static const double kImageHeight = 108.0; static const double kTextHeight = 48.0; - final TextStyle titleStyle = const PestoStyle( - fontSize: kTextHeight, - fontWeight: FontWeight.w900, - color: Colors.white, - letterSpacing: 3.0); + final TextStyle titleStyle = const PestoStyle(fontSize: kTextHeight, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 3.0); final RectTween _textRectTween = RectTween( - begin: Rect.fromLTWH(0.0, kLogoHeight, kLogoWidth, kTextHeight), - end: Rect.fromLTWH(0.0, kImageHeight, kLogoWidth, kTextHeight)); + begin: const Rect.fromLTWH(0.0, kLogoHeight, kLogoWidth, kTextHeight), + end: const Rect.fromLTWH(0.0, kImageHeight, kLogoWidth, kTextHeight), + ); final Curve _textOpacity = const Interval(0.4, 1.0, curve: Curves.easeInOut); final RectTween _imageRectTween = RectTween( - begin: Rect.fromLTWH(0.0, 0.0, kLogoWidth, kLogoHeight), - end: Rect.fromLTWH(0.0, 0.0, kLogoWidth, kImageHeight), + begin: const Rect.fromLTWH(0.0, 0.0, kLogoWidth, kLogoHeight), + end: const Rect.fromLTWH(0.0, 0.0, kLogoWidth, kImageHeight), ); @override @@ -237,7 +229,8 @@ class _PestoLogoState extends State { Positioned.fromRect( rect: _imageRectTween.lerp(widget.t), child: Image.asset( - '$_kSmallLogoImage', + _kSmallLogoImage, + package: _kGalleryAssetsPackage, fit: BoxFit.contain, ), ), @@ -245,8 +238,7 @@ class _PestoLogoState extends State { rect: _textRectTween.lerp(widget.t), child: Opacity( opacity: _textOpacity.transform(widget.t), - child: Text('PESTO', - style: titleStyle, textAlign: TextAlign.center), + child: Text('PESTO', style: titleStyle, textAlign: TextAlign.center), ), ), ], @@ -259,15 +251,13 @@ class _PestoLogoState extends State { // A card with the recipe's image, author, and title. class RecipeCard extends StatelessWidget { - const RecipeCard({Key key, this.recipe, this.onTap}) : super(key: key); + const RecipeCard({ Key key, this.recipe, this.onTap }) : super(key: key); final Recipe recipe; final VoidCallback onTap; - TextStyle get titleStyle => - const PestoStyle(fontSize: 24.0, fontWeight: FontWeight.w600); - TextStyle get authorStyle => - const PestoStyle(fontWeight: FontWeight.w500, color: Colors.black54); + TextStyle get titleStyle => const PestoStyle(fontSize: 24.0, fontWeight: FontWeight.w600); + TextStyle get authorStyle => const PestoStyle(fontWeight: FontWeight.w500, color: Colors.black54); @override Widget build(BuildContext context) { @@ -278,11 +268,11 @@ class RecipeCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Hero( - tag: '${recipe.imagePath}', + tag: 'packages/$_kGalleryAssetsPackage/${recipe.imagePath}', child: AspectRatio( aspectRatio: 4.0 / 3.0, child: Image.asset( - '${recipe.imagePath}', + recipe.imagePath, package: recipe.imagePackage, fit: BoxFit.cover, semanticLabel: recipe.name, @@ -295,7 +285,7 @@ class RecipeCard extends StatelessWidget { Padding( padding: const EdgeInsets.all(16.0), child: Image.asset( - '${recipe.ingredientsImagePath}', + recipe.ingredientsImagePath, package: recipe.ingredientsImagePackage, width: 48.0, height: 48.0, @@ -306,10 +296,7 @@ class RecipeCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(recipe.name, - style: titleStyle, - softWrap: false, - overflow: TextOverflow.ellipsis), + Text(recipe.name, style: titleStyle, softWrap: false, overflow: TextOverflow.ellipsis), Text(recipe.author, style: authorStyle), ], ), @@ -326,7 +313,7 @@ class RecipeCard extends StatelessWidget { // Displays one recipe. Includes the recipe sheet with a background image. class RecipePage extends StatefulWidget { - const RecipePage({Key key, this.recipe}) : super(key: key); + const RecipePage({ Key key, this.recipe }) : super(key: key); final Recipe recipe; @@ -336,11 +323,9 @@ class RecipePage extends StatefulWidget { class _RecipePageState extends State { final GlobalKey _scaffoldKey = GlobalKey(); - final TextStyle menuItemStyle = const PestoStyle( - fontSize: 15.0, color: Colors.black54, height: 24.0 / 15.0); + final TextStyle menuItemStyle = const PestoStyle(fontSize: 15.0, color: Colors.black54, height: 24.0/15.0); - double _getAppBarHeight(BuildContext context) => - MediaQuery.of(context).size.height * 0.3; + double _getAppBarHeight(BuildContext context) => MediaQuery.of(context).size.height * 0.3; @override Widget build(BuildContext context) { @@ -361,9 +346,9 @@ class _RecipePageState extends State { right: 0.0, height: appBarHeight + _kFabHalfSize, child: Hero( - tag: '${widget.recipe.imagePath}', + tag: 'packages/$_kGalleryAssetsPackage/${widget.recipe.imagePath}', child: Image.asset( - '${widget.recipe.imagePath}', + widget.recipe.imagePath, package: widget.recipe.imagePackage, fit: fullWidth ? BoxFit.fitWidth : BoxFit.cover, ), @@ -376,9 +361,8 @@ class _RecipePageState extends State { backgroundColor: Colors.transparent, actions: [ PopupMenuButton( - onSelected: (String item) {}, - itemBuilder: (BuildContext context) => - >[ + onSelected: (String item) { }, + itemBuilder: (BuildContext context) => >[ _buildMenuItem(Icons.share, 'Tweet recipe'), _buildMenuItem(Icons.email, 'Email recipe'), _buildMenuItem(Icons.message, 'Message recipe'), @@ -399,23 +383,23 @@ class _RecipePageState extends State { ), ), SliverToBoxAdapter( - child: Stack( - children: [ - Container( - padding: const EdgeInsets.only(top: _kFabHalfSize), - width: fullWidth ? null : _kRecipePageMaxWidth, - child: RecipeSheet(recipe: widget.recipe), - ), - Positioned( - right: 16.0, - child: FloatingActionButton( - child: Icon( - isFavorite ? Icons.favorite : Icons.favorite_border), - onPressed: _toggleFavorite, + child: Stack( + children: [ + Container( + padding: const EdgeInsets.only(top: _kFabHalfSize), + width: fullWidth ? null : _kRecipePageMaxWidth, + child: RecipeSheet(recipe: widget.recipe), ), - ), - ], - )), + Positioned( + right: 16.0, + child: FloatingActionButton( + child: Icon(isFavorite ? Icons.favorite : Icons.favorite_border), + onPressed: _toggleFavorite, + ), + ), + ], + ), + ), ], ), ], @@ -428,8 +412,9 @@ class _RecipePageState extends State { child: Row( children: [ Padding( - padding: const EdgeInsets.only(right: 24.0), - child: Icon(icon, color: Colors.black54)), + padding: const EdgeInsets.only(right: 24.0), + child: Icon(icon, color: Colors.black54), + ), Text(label, style: menuItemStyle), ], ), @@ -448,17 +433,13 @@ class _RecipePageState extends State { /// Displays the recipe's name and instructions. class RecipeSheet extends StatelessWidget { - RecipeSheet({Key key, this.recipe}) : super(key: key); + RecipeSheet({ Key key, this.recipe }) : super(key: key); final TextStyle titleStyle = const PestoStyle(fontSize: 34.0); - final TextStyle descriptionStyle = const PestoStyle( - fontSize: 15.0, color: Colors.black54, height: 24.0 / 15.0); - final TextStyle itemStyle = - const PestoStyle(fontSize: 15.0, height: 24.0 / 15.0); - final TextStyle itemAmountStyle = PestoStyle( - fontSize: 15.0, color: _kTheme.primaryColor, height: 24.0 / 15.0); - final TextStyle headingStyle = const PestoStyle( - fontSize: 16.0, fontWeight: FontWeight.bold, height: 24.0 / 15.0); + final TextStyle descriptionStyle = const PestoStyle(fontSize: 15.0, color: Colors.black54, height: 24.0/15.0); + final TextStyle itemStyle = const PestoStyle(fontSize: 15.0, height: 24.0/15.0); + final TextStyle itemAmountStyle = PestoStyle(fontSize: 15.0, color: _kTheme.primaryColor, height: 24.0/15.0); + final TextStyle headingStyle = const PestoStyle(fontSize: 16.0, fontWeight: FontWeight.bold, height: 24.0/15.0); final Recipe recipe; @@ -472,48 +453,62 @@ class RecipeSheet extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 40.0), child: Table( columnWidths: const { - 0: FixedColumnWidth(64.0) + 0: FixedColumnWidth(64.0), }, children: [ - TableRow(children: [ - TableCell( + TableRow( + children: [ + TableCell( verticalAlignment: TableCellVerticalAlignment.middle, - child: Image.asset('${recipe.ingredientsImagePath}', - package: recipe.ingredientsImagePackage, - width: 32.0, - height: 32.0, - alignment: Alignment.centerLeft, - fit: BoxFit.scaleDown)), - TableCell( + child: Image.asset( + recipe.ingredientsImagePath, + package: recipe.ingredientsImagePackage, + width: 32.0, + height: 32.0, + alignment: Alignment.centerLeft, + fit: BoxFit.scaleDown, + ), + ), + TableCell( verticalAlignment: TableCellVerticalAlignment.middle, - child: Text(recipe.name, style: titleStyle)), - ]), - TableRow(children: [ - const SizedBox(), - Padding( + child: Text(recipe.name, style: titleStyle), + ), + ] + ), + TableRow( + children: [ + const SizedBox(), + Padding( padding: const EdgeInsets.only(top: 8.0, bottom: 4.0), - child: Text(recipe.description, style: descriptionStyle)), - ]), - TableRow(children: [ - const SizedBox(), - Padding( + child: Text(recipe.description, style: descriptionStyle), + ), + ] + ), + TableRow( + children: [ + const SizedBox(), + Padding( padding: const EdgeInsets.only(top: 24.0, bottom: 4.0), - child: Text('Ingredients', style: headingStyle)), - ]), - ] - ..addAll(recipe.ingredients - .map((RecipeIngredient ingredient) { + child: Text('Ingredients', style: headingStyle), + ), + ] + ), + ...recipe.ingredients.map((RecipeIngredient ingredient) { return _buildItemRow(ingredient.amount, ingredient.description); - })) - ..add(TableRow(children: [ - const SizedBox(), - Padding( + }), + TableRow( + children: [ + const SizedBox(), + Padding( padding: const EdgeInsets.only(top: 24.0, bottom: 4.0), - child: Text('Steps', style: headingStyle)), - ])) - ..addAll(recipe.steps.map((RecipeStep step) { + child: Text('Steps', style: headingStyle), + ), + ] + ), + ...recipe.steps.map((RecipeStep step) { return _buildItemRow(step.duration ?? '', step.description); - })), + }), + ], ), ), ), @@ -537,16 +532,17 @@ class RecipeSheet extends StatelessWidget { } class Recipe { - const Recipe( - {this.name, - this.author, - this.description, - this.imagePath, - this.imagePackage, - this.ingredientsImagePath, - this.ingredientsImagePackage, - this.ingredients, - this.steps}); + const Recipe({ + this.name, + this.author, + this.description, + this.imagePath, + this.imagePackage, + this.ingredientsImagePath, + this.ingredientsImagePackage, + this.ingredients, + this.steps, + }); final String name; final String author; @@ -578,9 +574,10 @@ const List kPestoRecipes = [ name: 'Roasted Chicken', author: 'Peter Carlsson', ingredientsImagePath: 'food/icons/main.png', - description: - 'The perfect dish to welcome your family and friends with on a crisp autumn night. Pair with roasted veggies to truly impress them.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'The perfect dish to welcome your family and friends with on a crisp autumn night. Pair with roasted veggies to truly impress them.', imagePath: 'food/roasted_chicken.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ RecipeIngredient(amount: '1 whole', description: 'Chicken'), RecipeIngredient(amount: '1/2 cup', description: 'Butter'), @@ -597,11 +594,12 @@ const List kPestoRecipes = [ name: 'Chopped Beet Leaves', author: 'Trevor Hansen', ingredientsImagePath: 'food/icons/veggie.png', - description: - 'This vegetable has more to offer than just its root. Beet greens can be tossed into a salad to add some variety or sauteed on its own with some oil and garlic.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'This vegetable has more to offer than just its root. Beet greens can be tossed into a salad to add some variety or sauteed on its own with some oil and garlic.', imagePath: 'food/chopped_beet_leaves.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ - RecipeIngredient(amount: '3 cups', description: 'Beet greens'), + RecipeIngredient(amount: '3 cups', description: 'Beet greens'), ], steps: [ RecipeStep(duration: '5 min', description: 'Chop'), @@ -611,15 +609,15 @@ const List kPestoRecipes = [ name: 'Pesto Pasta', author: 'Ali Connors', ingredientsImagePath: 'food/icons/main.png', - description: - 'With this pesto recipe, you can quickly whip up a meal to satisfy your savory needs. And if you\'re feeling festive, you can add bacon to taste.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'With this pesto recipe, you can quickly whip up a meal to satisfy your savory needs. And if you\'re feeling festive, you can add bacon to taste.', imagePath: 'food/pesto_pasta.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ RecipeIngredient(amount: '1/4 cup ', description: 'Pasta'), RecipeIngredient(amount: '2 cups', description: 'Fresh basil leaves'), RecipeIngredient(amount: '1/2 cup', description: 'Parmesan cheese'), - RecipeIngredient( - amount: '1/2 cup', description: 'Extra virgin olive oil'), + RecipeIngredient(amount: '1/2 cup', description: 'Extra virgin olive oil'), RecipeIngredient(amount: '1/3 cup', description: 'Pine nuts'), RecipeIngredient(amount: '1/4 cup', description: 'Lemon juice'), RecipeIngredient(amount: '3 cloves', description: 'Garlic'), @@ -635,13 +633,13 @@ const List kPestoRecipes = [ name: 'Cherry Pie', author: 'Sandra Adams', ingredientsImagePath: 'food/icons/main.png', - description: - 'Sometimes when you\'re craving some cheer in your life you can jumpstart your day with some cherry pie. Dessert for breakfast is perfectly acceptable.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'Sometimes when you\'re craving some cheer in your life you can jumpstart your day with some cherry pie. Dessert for breakfast is perfectly acceptable.', imagePath: 'food/cherry_pie.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ RecipeIngredient(amount: '1', description: 'Pie crust'), - RecipeIngredient( - amount: '4 cups', description: 'Fresh or frozen cherries'), + RecipeIngredient(amount: '4 cups', description: 'Fresh or frozen cherries'), RecipeIngredient(amount: '1 cup', description: 'Granulated sugar'), RecipeIngredient(amount: '4 tbsp', description: 'Cornstarch'), RecipeIngredient(amount: '1½ tbsp', description: 'Butter'), @@ -655,9 +653,10 @@ const List kPestoRecipes = [ name: 'Spinach Salad', author: 'Peter Carlsson', ingredientsImagePath: 'food/icons/spicy.png', - description: - 'Everyone\'s favorite leafy green is back. Paired with fresh sliced onion, it\'s ready to tackle any dish, whether it be a salad or an egg scramble.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'Everyone\'s favorite leafy green is back. Paired with fresh sliced onion, it\'s ready to tackle any dish, whether it be a salad or an egg scramble.', imagePath: 'food/spinach_onion_salad.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ RecipeIngredient(amount: '4 cups', description: 'Spinach'), RecipeIngredient(amount: '1 cup', description: 'Sliced onion'), @@ -670,9 +669,10 @@ const List kPestoRecipes = [ name: 'Butternut Squash Soup', author: 'Ali Connors', ingredientsImagePath: 'food/icons/healthy.png', - description: - 'This creamy butternut squash soup will warm you on the chilliest of winter nights and bring a delightful pop of orange to the dinner table.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'This creamy butternut squash soup will warm you on the chilliest of winter nights and bring a delightful pop of orange to the dinner table.', imagePath: 'food/butternut_squash_soup.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ RecipeIngredient(amount: '1', description: 'Butternut squash'), RecipeIngredient(amount: '4 cups', description: 'Chicken stock'), @@ -686,16 +686,17 @@ const List kPestoRecipes = [ steps: [ RecipeStep(duration: '10 min', description: 'Prep vegetables'), RecipeStep(duration: '5 min', description: 'Stir'), - RecipeStep(duration: '1 hr 10 min', description: 'Cook') + RecipeStep(duration: '1 hr 10 min', description: 'Cook'), ], ), Recipe( name: 'Spanakopita', author: 'Trevor Hansen', ingredientsImagePath: 'food/icons/quick.png', - description: - 'You \'feta\' believe this is a crowd-pleaser! Flaky phyllo pastry surrounds a delicious mixture of spinach and cheeses to create the perfect appetizer.', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: 'You \'feta\' believe this is a crowd-pleaser! Flaky phyllo pastry surrounds a delicious mixture of spinach and cheeses to create the perfect appetizer.', imagePath: 'food/spanakopita.png', + imagePackage: _kGalleryAssetsPackage, ingredients: [ RecipeIngredient(amount: '1 lb', description: 'Spinach'), RecipeIngredient(amount: '½ cup', description: 'Feta cheese'), @@ -706,13 +707,9 @@ const List kPestoRecipes = [ ], steps: [ RecipeStep(duration: '5 min', description: 'Sauté vegetables'), - RecipeStep( - duration: '3 min', - description: 'Stir vegetables and other filling ingredients'), - RecipeStep( - duration: '10 min', - description: 'Fill phyllo squares half-full with filling and fold.'), - RecipeStep(duration: '40 min', description: 'Bake') + RecipeStep(duration: '3 min', description: 'Stir vegetables and other filling ingredients'), + RecipeStep(duration: '10 min', description: 'Fill phyllo squares half-full with filling and fold.'), + RecipeStep(duration: '40 min', description: 'Bake'), ], ), ]; diff --git a/web/gallery/lib/demo/shrine/app.dart b/web/gallery/lib/demo/shrine/app.dart new file mode 100644 index 000000000..cccf58834 --- /dev/null +++ b/web/gallery/lib/demo/shrine/app.dart @@ -0,0 +1,138 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; + +import 'package:flutter_gallery/demo/shrine/backdrop.dart'; +import 'package:flutter_gallery/demo/shrine/category_menu_page.dart'; +import 'package:flutter_gallery/demo/shrine/colors.dart'; +import 'package:flutter_gallery/demo/shrine/expanding_bottom_sheet.dart'; +import 'package:flutter_gallery/demo/shrine/home.dart'; +import 'package:flutter_gallery/demo/shrine/login.dart'; +import 'package:flutter_gallery/demo/shrine/supplemental/cut_corners_border.dart'; + +class ShrineApp extends StatefulWidget { + @override + _ShrineAppState createState() => _ShrineAppState(); +} + +class _ShrineAppState extends State with SingleTickerProviderStateMixin { + // Controller to coordinate both the opening/closing of backdrop and sliding + // of expanding bottom sheet + AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 450), + value: 1.0, + ); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Shrine', + home: HomePage( + backdrop: Backdrop( + frontLayer: const ProductPage(), + backLayer: CategoryMenuPage(onCategoryTap: () => _controller.forward()), + frontTitle: const Text('SHRINE'), + backTitle: const Text('MENU'), + controller: _controller, + ), + expandingBottomSheet: ExpandingBottomSheet(hideController: _controller), + ), + initialRoute: '/login', + onGenerateRoute: _getRoute, + // Copy the platform from the main theme in order to support platform + // toggling from the Gallery options menu. + theme: _kShrineTheme.copyWith(platform: Theme.of(context).platform), + ); + } +} + +Route _getRoute(RouteSettings settings) { + if (settings.name != '/login') { + return null; + } + + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) => LoginPage(), + fullscreenDialog: true, + ); +} + +final ThemeData _kShrineTheme = _buildShrineTheme(); + +IconThemeData _customIconTheme(IconThemeData original) { + return original.copyWith(color: kShrineBrown900); +} + +ThemeData _buildShrineTheme() { + final ThemeData base = ThemeData.light(); + return base.copyWith( + colorScheme: kShrineColorScheme, + accentColor: kShrineBrown900, + primaryColor: kShrinePink100, + buttonColor: kShrinePink100, + scaffoldBackgroundColor: kShrineBackgroundWhite, + cardColor: kShrineBackgroundWhite, + textSelectionColor: kShrinePink100, + errorColor: kShrineErrorRed, + buttonTheme: const ButtonThemeData( + colorScheme: kShrineColorScheme, + textTheme: ButtonTextTheme.normal, + ), + primaryIconTheme: _customIconTheme(base.iconTheme), + inputDecorationTheme: const InputDecorationTheme(border: CutCornersBorder()), + textTheme: _buildShrineTextTheme(base.textTheme), + primaryTextTheme: _buildShrineTextTheme(base.primaryTextTheme), + accentTextTheme: _buildShrineTextTheme(base.accentTextTheme), + iconTheme: _customIconTheme(base.iconTheme), + ); +} + +TextTheme _buildShrineTextTheme(TextTheme base) { + return base.copyWith( + headline: base.headline.copyWith(fontWeight: FontWeight.w500), + title: base.title.copyWith(fontSize: 18.0), + caption: base.caption.copyWith(fontWeight: FontWeight.w400, fontSize: 14.0), + body2: base.body2.copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), + button: base.button.copyWith(fontWeight: FontWeight.w500, fontSize: 14.0), + ).apply( + fontFamily: 'Raleway', + displayColor: kShrineBrown900, + bodyColor: kShrineBrown900, + ); +} + +const ColorScheme kShrineColorScheme = ColorScheme( + primary: kShrinePink100, + primaryVariant: kShrineBrown900, + secondary: kShrinePink50, + secondaryVariant: kShrineBrown900, + surface: kShrineSurfaceWhite, + background: kShrineBackgroundWhite, + error: kShrineErrorRed, + onPrimary: kShrineBrown900, + onSecondary: kShrineBrown900, + onSurface: kShrineBrown900, + onBackground: kShrineBrown900, + onError: kShrineSurfaceWhite, + brightness: Brightness.light, +); diff --git a/web/gallery/lib/demo/shrine/backdrop.dart b/web/gallery/lib/demo/shrine/backdrop.dart new file mode 100644 index 000000000..b041301d0 --- /dev/null +++ b/web/gallery/lib/demo/shrine/backdrop.dart @@ -0,0 +1,330 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; + +import 'package:flutter_gallery/demo/shrine/login.dart'; + +const Cubic _kAccelerateCurve = Cubic(0.548, 0.0, 0.757, 0.464); +const Cubic _kDecelerateCurve = Cubic(0.23, 0.94, 0.41, 1.0); +const double _kPeakVelocityTime = 0.248210; +const double _kPeakVelocityProgress = 0.379146; + +class _FrontLayer extends StatelessWidget { + const _FrontLayer({ + Key key, + this.onTap, + this.child, + }) : super(key: key); + + final VoidCallback onTap; + final Widget child; + + @override + Widget build(BuildContext context) { + return Material( + elevation: 16.0, + shape: const BeveledRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Container( + height: 40.0, + alignment: AlignmentDirectional.centerStart, + ), + ), + Expanded( + child: child, + ), + ], + ), + ); + } +} + +class _BackdropTitle extends AnimatedWidget { + const _BackdropTitle({ + Key key, + Listenable listenable, + this.onPress, + @required this.frontTitle, + @required this.backTitle, + }) : assert(frontTitle != null), + assert(backTitle != null), + super(key: key, listenable: listenable); + + final Function onPress; + final Widget frontTitle; + final Widget backTitle; + + @override + Widget build(BuildContext context) { + final Animation animation = CurvedAnimation( + parent: listenable, + curve: const Interval(0.0, 0.78), + ); + + return DefaultTextStyle( + style: Theme.of(context).primaryTextTheme.title, + softWrap: false, + overflow: TextOverflow.ellipsis, + child: Row(children: [ + // branded icon + SizedBox( + width: 72.0, + child: IconButton( + padding: const EdgeInsets.only(right: 8.0), + onPressed: onPress, + icon: Stack(children: [ + Opacity( + opacity: animation.value, + child: const ImageIcon(AssetImage('packages/shrine_images/slanted_menu.png')), + ), + FractionalTranslation( + translation: Tween( + begin: Offset.zero, + end: const Offset(1.0, 0.0), + ).evaluate(animation), + child: const ImageIcon(AssetImage('packages/shrine_images/diamond.png')), + ), + ]), + ), + ), + // Here, we do a custom cross fade between backTitle and frontTitle. + // This makes a smooth animation between the two texts. + Stack( + children: [ + Opacity( + opacity: CurvedAnimation( + parent: ReverseAnimation(animation), + curve: const Interval(0.5, 1.0), + ).value, + child: FractionalTranslation( + translation: Tween( + begin: Offset.zero, + end: const Offset(0.5, 0.0), + ).evaluate(animation), + child: backTitle, + ), + ), + Opacity( + opacity: CurvedAnimation( + parent: animation, + curve: const Interval(0.5, 1.0), + ).value, + child: FractionalTranslation( + translation: Tween( + begin: const Offset(-0.25, 0.0), + end: Offset.zero, + ).evaluate(animation), + child: frontTitle, + ), + ), + ], + ), + ]), + ); + } +} + +/// Builds a Backdrop. +/// +/// A Backdrop widget has two layers, front and back. The front layer is shown +/// by default, and slides down to show the back layer, from which a user +/// can make a selection. The user can also configure the titles for when the +/// front or back layer is showing. +class Backdrop extends StatefulWidget { + const Backdrop({ + @required this.frontLayer, + @required this.backLayer, + @required this.frontTitle, + @required this.backTitle, + @required this.controller, + }) : assert(frontLayer != null), + assert(backLayer != null), + assert(frontTitle != null), + assert(backTitle != null), + assert(controller != null); + + final Widget frontLayer; + final Widget backLayer; + final Widget frontTitle; + final Widget backTitle; + final AnimationController controller; + + @override + _BackdropState createState() => _BackdropState(); +} + +class _BackdropState extends State with SingleTickerProviderStateMixin { + final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop'); + AnimationController _controller; + Animation _layerAnimation; + + @override + void initState() { + super.initState(); + _controller = widget.controller; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + bool get _frontLayerVisible { + final AnimationStatus status = _controller.status; + return status == AnimationStatus.completed || status == AnimationStatus.forward; + } + + void _toggleBackdropLayerVisibility() { + // Call setState here to update layerAnimation if that's necessary + setState(() { + _frontLayerVisible ? _controller.reverse() : _controller.forward(); + }); + } + + // _layerAnimation animates the front layer between open and close. + // _getLayerAnimation adjusts the values in the TweenSequence so the + // curve and timing are correct in both directions. + Animation _getLayerAnimation(Size layerSize, double layerTop) { + Curve firstCurve; // Curve for first TweenSequenceItem + Curve secondCurve; // Curve for second TweenSequenceItem + double firstWeight; // Weight of first TweenSequenceItem + double secondWeight; // Weight of second TweenSequenceItem + Animation animation; // Animation on which TweenSequence runs + + if (_frontLayerVisible) { + firstCurve = _kAccelerateCurve; + secondCurve = _kDecelerateCurve; + firstWeight = _kPeakVelocityTime; + secondWeight = 1.0 - _kPeakVelocityTime; + animation = CurvedAnimation( + parent: _controller.view, + curve: const Interval(0.0, 0.78), + ); + } else { + // These values are only used when the controller runs from t=1.0 to t=0.0 + firstCurve = _kDecelerateCurve.flipped; + secondCurve = _kAccelerateCurve.flipped; + firstWeight = 1.0 - _kPeakVelocityTime; + secondWeight = _kPeakVelocityTime; + animation = _controller.view; + } + + return TweenSequence( + >[ + TweenSequenceItem( + tween: RelativeRectTween( + begin: RelativeRect.fromLTRB( + 0.0, + layerTop, + 0.0, + layerTop - layerSize.height, + ), + end: RelativeRect.fromLTRB( + 0.0, + layerTop * _kPeakVelocityProgress, + 0.0, + (layerTop - layerSize.height) * _kPeakVelocityProgress, + ), + ).chain(CurveTween(curve: firstCurve)), + weight: firstWeight, + ), + TweenSequenceItem( + tween: RelativeRectTween( + begin: RelativeRect.fromLTRB( + 0.0, + layerTop * _kPeakVelocityProgress, + 0.0, + (layerTop - layerSize.height) * _kPeakVelocityProgress, + ), + end: RelativeRect.fill, + ).chain(CurveTween(curve: secondCurve)), + weight: secondWeight, + ), + ], + ).animate(animation); + } + + Widget _buildStack(BuildContext context, BoxConstraints constraints) { + const double layerTitleHeight = 48.0; + final Size layerSize = constraints.biggest; + final double layerTop = layerSize.height - layerTitleHeight; + + _layerAnimation = _getLayerAnimation(layerSize, layerTop); + + return Stack( + key: _backdropKey, + children: [ + widget.backLayer, + PositionedTransition( + rect: _layerAnimation, + child: _FrontLayer( + onTap: _toggleBackdropLayerVisibility, + child: widget.frontLayer, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final AppBar appBar = AppBar( + brightness: Brightness.light, + elevation: 0.0, + titleSpacing: 0.0, + title: _BackdropTitle( + listenable: _controller.view, + onPress: _toggleBackdropLayerVisibility, + frontTitle: widget.frontTitle, + backTitle: widget.backTitle, + ), + actions: [ + IconButton( + icon: const Icon(Icons.search, semanticLabel: 'login'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (BuildContext context) => LoginPage()), + ); + }, + ), + IconButton( + icon: const Icon(Icons.tune, semanticLabel: 'login'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (BuildContext context) => LoginPage()), + ); + }, + ), + ], + ); + return Scaffold( + appBar: appBar, + body: LayoutBuilder( + builder: _buildStack, + ), + ); + } +} diff --git a/web/gallery/lib/demo/shrine/category_menu_page.dart b/web/gallery/lib/demo/shrine/category_menu_page.dart new file mode 100644 index 000000000..1ac62d572 --- /dev/null +++ b/web/gallery/lib/demo/shrine/category_menu_page.dart @@ -0,0 +1,85 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; +import 'package:scoped_model/scoped_model.dart'; + +import 'package:flutter_gallery/demo/shrine/colors.dart'; +import 'package:flutter_gallery/demo/shrine/model/app_state_model.dart'; +import 'package:flutter_gallery/demo/shrine/model/product.dart'; + +class CategoryMenuPage extends StatelessWidget { + const CategoryMenuPage({ + Key key, + this.onCategoryTap, + }) : super(key: key); + + final VoidCallback onCategoryTap; + + Widget _buildCategory(Category category, BuildContext context) { + final String categoryString = category.toString().replaceAll('Category.', '').toUpperCase(); + final ThemeData theme = Theme.of(context); + return ScopedModelDescendant( + builder: (BuildContext context, Widget child, AppStateModel model) => + GestureDetector( + onTap: () { + model.setCategory(category); + if (onCategoryTap != null) { + onCategoryTap(); + } + }, + child: model.selectedCategory == category + ? Column( + children: [ + const SizedBox(height: 16.0), + Text( + categoryString, + style: theme.textTheme.body2, + textAlign: TextAlign.center, + ), + const SizedBox(height: 14.0), + Container( + width: 70.0, + height: 2.0, + color: kShrinePink400, + ), + ], + ) + : Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Text( + categoryString, + style: theme.textTheme.body2.copyWith( + color: kShrineBrown900.withAlpha(153) + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + padding: const EdgeInsets.only(top: 40.0), + color: kShrinePink100, + child: ListView( + children: Category.values.map((Category c) => _buildCategory(c, context)).toList(), + ), + ), + ); + } +} diff --git a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_extents.dart b/web/gallery/lib/demo/shrine/colors.dart similarity index 50% rename from web/charts/common/lib/src/chart/cartesian/axis/time/date_time_extents.dart rename to web/gallery/lib/demo/shrine/colors.dart index f69875c09..66a472861 100644 --- a/web/charts/common/lib/src/chart/cartesian/axis/time/date_time_extents.dart +++ b/web/gallery/lib/demo/shrine/colors.dart @@ -1,5 +1,4 @@ -// Copyright 2018 the Charts project authors. Please see the AUTHORS file -// for details. +// Copyright 2018-present the Flutter authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,21 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:meta/meta.dart' show required; +import 'package:flutter/material.dart'; -import '../scale.dart' show Extents; +const Color kShrinePink50 = Color(0xFFFEEAE6); +const Color kShrinePink100 = Color(0xFFFEDBD0); +const Color kShrinePink300 = Color(0xFFFBB8AC); +const Color kShrinePink400 = Color(0xFFEAA4A4); -class DateTimeExtents extends Extents { - final DateTime start; - final DateTime end; +const Color kShrineBrown900 = Color(0xFF442B2D); +const Color kShrineBrown600 = Color(0xFF7D4F52); - DateTimeExtents({@required this.start, @required this.end}); +const Color kShrineErrorRed = Color(0xFFC5032B); - @override - bool operator ==(other) { - return other is DateTimeExtents && start == other.start && end == other.end; - } - - @override - int get hashCode => (start.hashCode + (end.hashCode * 37)); -} +const Color kShrineSurfaceWhite = Color(0xFFFFFBFA); +const Color kShrineBackgroundWhite = Colors.white; diff --git a/web/gallery/lib/demo/shrine/expanding_bottom_sheet.dart b/web/gallery/lib/demo/shrine/expanding_bottom_sheet.dart new file mode 100644 index 000000000..72047eb68 --- /dev/null +++ b/web/gallery/lib/demo/shrine/expanding_bottom_sheet.dart @@ -0,0 +1,656 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; +import 'package:scoped_model/scoped_model.dart'; + +import 'package:flutter_gallery/demo/shrine/colors.dart'; +import 'package:flutter_gallery/demo/shrine/model/app_state_model.dart'; +import 'package:flutter_gallery/demo/shrine/model/product.dart'; +import 'package:flutter_gallery/demo/shrine/shopping_cart.dart'; + +// These curves define the emphasized easing curve. +const Cubic _kAccelerateCurve = Cubic(0.548, 0.0, 0.757, 0.464); +const Cubic _kDecelerateCurve = Cubic(0.23, 0.94, 0.41, 1.0); +// The time at which the accelerate and decelerate curves switch off +const double _kPeakVelocityTime = 0.248210; +// Percent (as a decimal) of animation that should be completed at _peakVelocityTime +const double _kPeakVelocityProgress = 0.379146; +const double _kCartHeight = 56.0; +// Radius of the shape on the top left of the sheet. +const double _kCornerRadius = 24.0; +// Width for just the cart icon and no thumbnails. +const double _kWidthForCartIcon = 64.0; + +class ExpandingBottomSheet extends StatefulWidget { + const ExpandingBottomSheet({Key key, @required this.hideController}) + : assert(hideController != null), + super(key: key); + + final AnimationController hideController; + + @override + _ExpandingBottomSheetState createState() => _ExpandingBottomSheetState(); + + static _ExpandingBottomSheetState of(BuildContext context, {bool isNullOk = false}) { + assert(isNullOk != null); + assert(context != null); + final _ExpandingBottomSheetState result = context.ancestorStateOfType( + const TypeMatcher<_ExpandingBottomSheetState>() + ); + if (isNullOk || result != null) { + return result; + } + throw FlutterError( + 'ExpandingBottomSheet.of() called with a context that does not contain a ExpandingBottomSheet.\n'); + } +} + +// Emphasized Easing is a motion curve that has an organic, exciting feeling. +// It's very fast to begin with and then very slow to finish. Unlike standard +// curves, like [Curves.fastOutSlowIn], it can't be expressed in a cubic bezier +// curve formula. It's quintic, not cubic. But it _can_ be expressed as one +// curve followed by another, which we do here. +Animation _getEmphasizedEasingAnimation({ + @required T begin, + @required T peak, + @required T end, + @required bool isForward, + @required Animation parent, +}) { + Curve firstCurve; + Curve secondCurve; + double firstWeight; + double secondWeight; + + if (isForward) { + firstCurve = _kAccelerateCurve; + secondCurve = _kDecelerateCurve; + firstWeight = _kPeakVelocityTime; + secondWeight = 1.0 - _kPeakVelocityTime; + } else { + firstCurve = _kDecelerateCurve.flipped; + secondCurve = _kAccelerateCurve.flipped; + firstWeight = 1.0 - _kPeakVelocityTime; + secondWeight = _kPeakVelocityTime; + } + + return TweenSequence( + >[ + TweenSequenceItem( + weight: firstWeight, + tween: Tween( + begin: begin, + end: peak, + ).chain(CurveTween(curve: firstCurve)), + ), + TweenSequenceItem( + weight: secondWeight, + tween: Tween( + begin: peak, + end: end, + ).chain(CurveTween(curve: secondCurve)), + ), + ], + ).animate(parent); +} + +// Calculates the value where two double Animations should be joined. Used by +// callers of _getEmphasisedEasing(). +double _getPeakPoint({double begin, double end}) { + return begin + (end - begin) * _kPeakVelocityProgress; +} + +class _ExpandingBottomSheetState extends State with TickerProviderStateMixin { + final GlobalKey _expandingBottomSheetKey = GlobalKey(debugLabel: 'Expanding bottom sheet'); + + // The width of the Material, calculated by _widthFor() & based on the number + // of products in the cart. 64.0 is the width when there are 0 products + // (_kWidthForZeroProducts) + double _width = _kWidthForCartIcon; + + // Controller for the opening and closing of the ExpandingBottomSheet + AnimationController _controller; + + // Animations for the opening and closing of the ExpandingBottomSheet + Animation _widthAnimation; + Animation _heightAnimation; + Animation _thumbnailOpacityAnimation; + Animation _cartOpacityAnimation; + Animation _shapeAnimation; + Animation _slideAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Animation _getWidthAnimation(double screenWidth) { + if (_controller.status == AnimationStatus.forward) { + // Opening animation + return Tween(begin: _width, end: screenWidth).animate( + CurvedAnimation( + parent: _controller.view, + curve: const Interval(0.0, 0.3, curve: Curves.fastOutSlowIn), + ), + ); + } else { + // Closing animation + return _getEmphasizedEasingAnimation( + begin: _width, + peak: _getPeakPoint(begin: _width, end: screenWidth), + end: screenWidth, + isForward: false, + parent: CurvedAnimation(parent: _controller.view, curve: const Interval(0.0, 0.87)), + ); + } + } + + Animation _getHeightAnimation(double screenHeight) { + if (_controller.status == AnimationStatus.forward) { + // Opening animation + + return _getEmphasizedEasingAnimation( + begin: _kCartHeight, + peak: _kCartHeight + (screenHeight - _kCartHeight) * _kPeakVelocityProgress, + end: screenHeight, + isForward: true, + parent: _controller.view, + ); + } else { + // Closing animation + return Tween( + begin: _kCartHeight, + end: screenHeight, + ).animate( + CurvedAnimation( + parent: _controller.view, + curve: const Interval(0.434, 1.0, curve: Curves.linear), // not used + // only the reverseCurve will be used + reverseCurve: Interval(0.434, 1.0, curve: Curves.fastOutSlowIn.flipped), + ), + ); + } + } + + // Animation of the cut corner. It's cut when closed and not cut when open. + Animation _getShapeAnimation() { + if (_controller.status == AnimationStatus.forward) { + return Tween(begin: _kCornerRadius, end: 0.0).animate( + CurvedAnimation( + parent: _controller.view, + curve: const Interval(0.0, 0.3, curve: Curves.fastOutSlowIn), + ), + ); + } else { + return _getEmphasizedEasingAnimation( + begin: _kCornerRadius, + peak: _getPeakPoint(begin: _kCornerRadius, end: 0.0), + end: 0.0, + isForward: false, + parent: _controller.view, + ); + } + } + + Animation _getThumbnailOpacityAnimation() { + return Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation( + parent: _controller.view, + curve: _controller.status == AnimationStatus.forward + ? const Interval(0.0, 0.3) + : const Interval(0.532, 0.766), + ), + ); + } + + Animation _getCartOpacityAnimation() { + return CurvedAnimation( + parent: _controller.view, + curve: _controller.status == AnimationStatus.forward + ? const Interval(0.3, 0.6) + : const Interval(0.766, 1.0), + ); + } + + // Returns the correct width of the ExpandingBottomSheet based on the number of + // products in the cart. + double _widthFor(int numProducts) { + switch (numProducts) { + case 0: + return _kWidthForCartIcon; + case 1: + return 136.0; + case 2: + return 192.0; + case 3: + return 248.0; + default: + return 278.0; + } + } + + // Returns true if the cart is open or opening and false otherwise. + bool get _isOpen { + final AnimationStatus status = _controller.status; + return status == AnimationStatus.completed || status == AnimationStatus.forward; + } + + // Opens the ExpandingBottomSheet if it's closed, otherwise does nothing. + void open() { + if (!_isOpen) { + _controller.forward(); + } + } + + // Closes the ExpandingBottomSheet if it's open or opening, otherwise does nothing. + void close() { + if (_isOpen) { + _controller.reverse(); + } + } + + // Changes the padding between the start edge of the Material and the cart icon + // based on the number of products in the cart (padding increases when > 0 + // products.) + EdgeInsetsDirectional _cartPaddingFor(int numProducts) { + return (numProducts == 0) + ? const EdgeInsetsDirectional.only(start: 20.0, end: 8.0) + : const EdgeInsetsDirectional.only(start: 32.0, end: 8.0); + } + + bool get _cartIsVisible => _thumbnailOpacityAnimation.value == 0.0; + + Widget _buildThumbnails(int numProducts) { + return ExcludeSemantics( + child: Opacity( + opacity: _thumbnailOpacityAnimation.value, + child: Column( + children: [ + Row( + children: [ + AnimatedPadding( + padding: _cartPaddingFor(numProducts), + child: const Icon(Icons.shopping_cart), + duration: const Duration(milliseconds: 225), + ), + Container( + // Accounts for the overflow number + width: numProducts > 3 ? _width - 94.0 : _width - 64.0, + height: _kCartHeight, + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ProductThumbnailRow(), + ), + ExtraProductsNumber(), + ], + ), + ], + ), + ), + ); + } + + Widget _buildShoppingCartPage() { + return Opacity( + opacity: _cartOpacityAnimation.value, + child: ShoppingCartPage(), + ); + } + + Widget _buildCart(BuildContext context, Widget child) { + // numProducts is the number of different products in the cart (does not + // include multiples of the same product). + final AppStateModel model = ScopedModel.of(context); + final int numProducts = model.productsInCart.keys.length; + final int totalCartQuantity = model.totalCartQuantity; + final Size screenSize = MediaQuery.of(context).size; + final double screenWidth = screenSize.width; + final double screenHeight = screenSize.height; + + _width = _widthFor(numProducts); + _widthAnimation = _getWidthAnimation(screenWidth); + _heightAnimation = _getHeightAnimation(screenHeight); + _shapeAnimation = _getShapeAnimation(); + _thumbnailOpacityAnimation = _getThumbnailOpacityAnimation(); + _cartOpacityAnimation = _getCartOpacityAnimation(); + + return Semantics( + button: true, + value: 'Shopping cart, $totalCartQuantity items', + child: Container( + width: _widthAnimation.value, + height: _heightAnimation.value, + child: Material( + animationDuration: const Duration(milliseconds: 0), + shape: BeveledRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(_shapeAnimation.value), + ), + ), + elevation: 4.0, + color: kShrinePink50, + child: _cartIsVisible + ? _buildShoppingCartPage() + : _buildThumbnails(numProducts), + ), + ), + ); + } + + // Builder for the hide and reveal animation when the backdrop opens and closes + Widget _buildSlideAnimation(BuildContext context, Widget child) { + _slideAnimation = _getEmphasizedEasingAnimation( + begin: const Offset(1.0, 0.0), + peak: const Offset(_kPeakVelocityProgress, 0.0), + end: const Offset(0.0, 0.0), + isForward: widget.hideController.status == AnimationStatus.forward, + parent: widget.hideController, + ); + + return SlideTransition( + position: _slideAnimation, + child: child, + ); + } + + // Closes the cart if the cart is open, otherwise exits the app (this should + // only be relevant for Android). + Future _onWillPop() async { + if (!_isOpen) { + await SystemNavigator.pop(); + return true; + } + + close(); + return true; + } + + @override + Widget build(BuildContext context) { + return AnimatedSize( + key: _expandingBottomSheetKey, + duration: const Duration(milliseconds: 225), + curve: Curves.easeInOut, + vsync: this, + alignment: FractionalOffset.topLeft, + child: WillPopScope( + onWillPop: _onWillPop, + child: AnimatedBuilder( + animation: widget.hideController, + builder: _buildSlideAnimation, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: open, + child: ScopedModelDescendant( + builder: (BuildContext context, Widget child, AppStateModel model) { + return AnimatedBuilder( + builder: _buildCart, + animation: _controller, + ); + }, + ), + ), + ), + ), + ); + } +} + +class ProductThumbnailRow extends StatefulWidget { + @override + _ProductThumbnailRowState createState() => _ProductThumbnailRowState(); +} + +class _ProductThumbnailRowState extends State { + final GlobalKey _listKey = GlobalKey(); + + // _list represents what's currently on screen. If _internalList updates, + // it will need to be updated to match it. + _ListModel _list; + + // _internalList represents the list as it is updated by the AppStateModel. + List _internalList; + + @override + void initState() { + super.initState(); + _list = _ListModel( + listKey: _listKey, + initialItems: ScopedModel.of(context).productsInCart.keys.toList(), + removedItemBuilder: _buildRemovedThumbnail, + ); + _internalList = List.from(_list.list); + } + + Product _productWithId(int productId) { + final AppStateModel model = ScopedModel.of(context); + final Product product = model.getProductById(productId); + assert(product != null); + return product; + } + + Widget _buildRemovedThumbnail(int item, BuildContext context, Animation animation) { + return ProductThumbnail(animation, animation, _productWithId(item)); + } + + Widget _buildThumbnail(BuildContext context, int index, Animation animation) { + final Animation thumbnailSize = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation( + curve: const Interval(0.33, 1.0, curve: Curves.easeIn), + parent: animation, + ), + ); + + final Animation opacity = CurvedAnimation( + curve: const Interval(0.33, 1.0, curve: Curves.linear), + parent: animation, + ); + + return ProductThumbnail(thumbnailSize, opacity, _productWithId(_list[index])); + } + + // If the lists are the same length, assume nothing has changed. + // If the internalList is shorter than the ListModel, an item has been removed. + // If the internalList is longer, then an item has been added. + void _updateLists() { + // Update _internalList based on the model + _internalList = ScopedModel.of(context).productsInCart.keys.toList(); + final Set internalSet = Set.from(_internalList); + final Set listSet = Set.from(_list.list); + + final Set difference = internalSet.difference(listSet); + if (difference.isEmpty) { + return; + } + + for (int product in difference) { + if (_internalList.length < _list.length) { + _list.remove(product); + } else if (_internalList.length > _list.length) { + _list.add(product); + } + } + + while (_internalList.length != _list.length) { + int index = 0; + // Check bounds and that the list elements are the same + while (_internalList.isNotEmpty && + _list.length > 0 && + index < _internalList.length && + index < _list.length && + _internalList[index] == _list[index]) { + index++; + } + } + } + + Widget _buildAnimatedList() { + return AnimatedList( + key: _listKey, + shrinkWrap: true, + itemBuilder: _buildThumbnail, + initialItemCount: _list.length, + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), // Cart shouldn't scroll + ); + } + + @override + Widget build(BuildContext context) { + _updateLists(); + return ScopedModelDescendant( + builder: (BuildContext context, Widget child, AppStateModel model) => _buildAnimatedList(), + ); + } +} + +class ExtraProductsNumber extends StatelessWidget { + // Calculates the number to be displayed at the end of the row if there are + // more than three products in the cart. This calculates overflow products, + // including their duplicates (but not duplicates of products shown as + // thumbnails). + int _calculateOverflow(AppStateModel model) { + final Map productMap = model.productsInCart; + // List created to be able to access products by index instead of ID. + // Order is guaranteed because productsInCart returns a LinkedHashMap. + final List products = productMap.keys.toList(); + int overflow = 0; + final int numProducts = products.length; + if (numProducts > 3) { + for (int i = 3; i < numProducts; i++) { + overflow += productMap[products[i]]; + } + } + return overflow; + } + + Widget _buildOverflow(AppStateModel model, BuildContext context) { + if (model.productsInCart.length <= 3) + return Container(); + + final int numOverflowProducts = _calculateOverflow(model); + // Maximum of 99 so padding doesn't get messy. + final int displayedOverflowProducts = numOverflowProducts <= 99 ? numOverflowProducts : 99; + return Container( + child: Text( + '+$displayedOverflowProducts', + style: Theme.of(context).primaryTextTheme.button, + ), + ); + } + + @override + Widget build(BuildContext context) { + return ScopedModelDescendant( + builder: (BuildContext builder, Widget child, AppStateModel model) => _buildOverflow(model, context), + ); + } +} + +class ProductThumbnail extends StatelessWidget { + const ProductThumbnail(this.animation, this.opacityAnimation, this.product); + + final Animation animation; + final Animation opacityAnimation; + final Product product; + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: opacityAnimation, + child: ScaleTransition( + scale: animation, + child: Container( + width: 40.0, + height: 40.0, + decoration: BoxDecoration( + image: DecorationImage( + image: ExactAssetImage( + product.assetName, // asset name + package: product.assetPackage, // asset package + ), + fit: BoxFit.cover, + ), + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + ), + margin: const EdgeInsets.only(left: 16.0), + ), + ), + ); + } +} + +// _ListModel manipulates an internal list and an AnimatedList +class _ListModel { + _ListModel({ + @required this.listKey, + @required this.removedItemBuilder, + Iterable initialItems, + }) : assert(listKey != null), + assert(removedItemBuilder != null), + _items = initialItems?.toList() ?? []; + + final GlobalKey listKey; + final dynamic removedItemBuilder; + final List _items; + + AnimatedListState get _animatedList => listKey.currentState; + + void add(int product) { + _insert(_items.length, product); + } + + void _insert(int index, int item) { + _items.insert(index, item); + _animatedList.insertItem(index, duration: const Duration(milliseconds: 225)); + } + + void remove(int product) { + final int index = _items.indexOf(product); + if (index >= 0) { + _removeAt(index); + } + } + + void _removeAt(int index) { + final int removedItem = _items.removeAt(index); + if (removedItem != null) { + _animatedList.removeItem(index, (BuildContext context, Animation animation) { + return removedItemBuilder(removedItem, context, animation); + }); + } + } + + int get length => _items.length; + + int operator [](int index) => _items[index]; + + int indexOf(int item) => _items.indexOf(item); + + List get list => _items; +} diff --git a/web/gallery/lib/demo/shrine/home.dart b/web/gallery/lib/demo/shrine/home.dart new file mode 100644 index 000000000..c180c48bd --- /dev/null +++ b/web/gallery/lib/demo/shrine/home.dart @@ -0,0 +1,57 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; +import 'package:scoped_model/scoped_model.dart'; + +import 'package:flutter_gallery/demo/shrine/backdrop.dart'; +import 'package:flutter_gallery/demo/shrine/expanding_bottom_sheet.dart'; +import 'package:flutter_gallery/demo/shrine/model/app_state_model.dart'; +import 'package:flutter_gallery/demo/shrine/model/product.dart'; +import 'package:flutter_gallery/demo/shrine/supplemental/asymmetric_view.dart'; + +class ProductPage extends StatelessWidget { + const ProductPage({this.category = Category.all}); + + final Category category; + + @override + Widget build(BuildContext context) { + return ScopedModelDescendant( + builder: (BuildContext context, Widget child, AppStateModel model) { + return AsymmetricView(products: model.getProducts()); + }); + } +} + +class HomePage extends StatelessWidget { + const HomePage({ + this.expandingBottomSheet, + this.backdrop, + Key key, + }) : super(key: key); + + final ExpandingBottomSheet expandingBottomSheet; + final Backdrop backdrop; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + backdrop, + Align(child: expandingBottomSheet, alignment: Alignment.bottomRight), + ], + ); + } +} diff --git a/web/gallery/lib/demo/shrine/login.dart b/web/gallery/lib/demo/shrine/login.dart new file mode 100644 index 000000000..23da0668a --- /dev/null +++ b/web/gallery/lib/demo/shrine/login.dart @@ -0,0 +1,144 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; + +import 'package:flutter_gallery/demo/shrine/colors.dart'; + +class LoginPage extends StatefulWidget { + @override + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + static const ShapeDecoration _decoration = ShapeDecoration( + shape: BeveledRectangleBorder( + side: BorderSide(color: kShrineBrown900, width: 0.5), + borderRadius: BorderRadius.all(Radius.circular(7.0)), + ), + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.white, + brightness: Brightness.light, + leading: IconButton( + icon: const BackButtonIcon(), + tooltip: MaterialLocalizations.of(context).backButtonTooltip, + onPressed: () { + // The login screen is immediately displayed on top of the Shrine + // home screen using onGenerateRoute and so rootNavigator must be + // set to true in order to get out of Shrine completely. + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + ), + body: SafeArea( + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + children: [ + const SizedBox(height: 80.0), + Column( + children: [ + Image.asset('packages/shrine_images/diamond.png'), + const SizedBox(height: 16.0), + Text( + 'SHRINE', + style: Theme.of(context).textTheme.headline, + ), + ], + ), + const SizedBox(height: 120.0), + PrimaryColorOverride( + color: kShrineBrown900, + child: Container( + decoration: _decoration, + child: TextField( + controller: _usernameController, + decoration: const InputDecoration( + labelText: 'Username', + ), + ), + ), + ), + const SizedBox(height: 12.0), + PrimaryColorOverride( + color: kShrineBrown900, + child: Container( + decoration: _decoration, + child: TextField( + controller: _passwordController, + decoration: const InputDecoration( + labelText: 'Password', + ), + ), + ), + ), + Wrap( + children: [ + ButtonBar( + children: [ + FlatButton( + child: const Text('CANCEL'), + shape: const BeveledRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(7.0)), + ), + onPressed: () { + // The login screen is immediately displayed on top of + // the Shrine home screen using onGenerateRoute and so + // rootNavigator must be set to true in order to get out + // of Shrine completely. + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + RaisedButton( + child: const Text('NEXT'), + elevation: 8.0, + shape: const BeveledRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(7.0)), + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ], + ), + ], + ), + ), + ); + } +} + +class PrimaryColorOverride extends StatelessWidget { + const PrimaryColorOverride({Key key, this.color, this.child}) : super(key: key); + + final Color color; + final Widget child; + + @override + Widget build(BuildContext context) { + return Theme( + child: child, + data: Theme.of(context).copyWith(primaryColor: color), + ); + } +} diff --git a/web/gallery/lib/demo/shrine/model/app_state_model.dart b/web/gallery/lib/demo/shrine/model/app_state_model.dart new file mode 100644 index 000000000..55d4806d8 --- /dev/null +++ b/web/gallery/lib/demo/shrine/model/app_state_model.dart @@ -0,0 +1,123 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:scoped_model/scoped_model.dart'; + +import 'package:flutter_gallery/demo/shrine/model/product.dart'; +import 'package:flutter_gallery/demo/shrine/model/products_repository.dart'; + +double _salesTaxRate = 0.06; +double _shippingCostPerItem = 7.0; + +class AppStateModel extends Model { + // All the available products. + List _availableProducts; + + // The currently selected category of products. + Category _selectedCategory = Category.all; + + // The IDs and quantities of products currently in the cart. + final Map _productsInCart = {}; + + Map get productsInCart => Map.from(_productsInCart); + + // Total number of items in the cart. + int get totalCartQuantity => _productsInCart.values.fold(0, (int v, int e) => v + e); + + Category get selectedCategory => _selectedCategory; + + // Totaled prices of the items in the cart. + double get subtotalCost { + return _productsInCart.keys + .map((int id) => _availableProducts[id].price * _productsInCart[id]) + .fold(0.0, (double sum, int e) => sum + e); + } + + // Total shipping cost for the items in the cart. + double get shippingCost { + return _shippingCostPerItem * _productsInCart.values.fold(0.0, (num sum, int e) => sum + e); + } + + // Sales tax for the items in the cart + double get tax => subtotalCost * _salesTaxRate; + + // Total cost to order everything in the cart. + double get totalCost => subtotalCost + shippingCost + tax; + + // Returns a copy of the list of available products, filtered by category. + List getProducts() { + if (_availableProducts == null) { + return []; + } + + if (_selectedCategory == Category.all) { + return List.from(_availableProducts); + } else { + return _availableProducts + .where((Product p) => p.category == _selectedCategory) + .toList(); + } + } + + // Adds a product to the cart. + void addProductToCart(int productId) { + if (!_productsInCart.containsKey(productId)) { + _productsInCart[productId] = 1; + } else { + _productsInCart[productId]++; + } + + notifyListeners(); + } + + // Removes an item from the cart. + void removeItemFromCart(int productId) { + if (_productsInCart.containsKey(productId)) { + if (_productsInCart[productId] == 1) { + _productsInCart.remove(productId); + } else { + _productsInCart[productId]--; + } + } + + notifyListeners(); + } + + // Returns the Product instance matching the provided id. + Product getProductById(int id) { + return _availableProducts.firstWhere((Product p) => p.id == id); + } + + // Removes everything from the cart. + void clearCart() { + _productsInCart.clear(); + notifyListeners(); + } + + // Loads the list of available products from the repo. + void loadProducts() { + _availableProducts = ProductsRepository.loadProducts(Category.all); + notifyListeners(); + } + + void setCategory(Category newCategory) { + _selectedCategory = newCategory; + notifyListeners(); + } + + @override + String toString() { + return 'AppStateModel(totalCost: $totalCost)'; + } +} diff --git a/web/gallery/lib/demo/shrine/model/product.dart b/web/gallery/lib/demo/shrine/model/product.dart new file mode 100644 index 000000000..c19d4d58f --- /dev/null +++ b/web/gallery/lib/demo/shrine/model/product.dart @@ -0,0 +1,48 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/foundation.dart'; + +enum Category { + all, + accessories, + clothing, + home, +} + +class Product { + const Product({ + @required this.category, + @required this.id, + @required this.isFeatured, + @required this.name, + @required this.price, + }) : assert(category != null), + assert(id != null), + assert(isFeatured != null), + assert(name != null), + assert(price != null); + + final Category category; + final int id; + final bool isFeatured; + final String name; + final int price; + + String get assetName => '$id-0.jpg'; + String get assetPackage => 'shrine_images'; + + @override + String toString() => '$name (id=$id)'; +} diff --git a/web/gallery/lib/demo/shrine/model/products_repository.dart b/web/gallery/lib/demo/shrine/model/products_repository.dart new file mode 100644 index 000000000..75bf4b3a3 --- /dev/null +++ b/web/gallery/lib/demo/shrine/model/products_repository.dart @@ -0,0 +1,293 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter_gallery/demo/shrine/model/product.dart'; + +class ProductsRepository { + static List loadProducts(Category category) { + const List allProducts = [ + Product( + category: Category.accessories, + id: 0, + isFeatured: true, + name: 'Vagabond sack', + price: 120, + ), + Product( + category: Category.accessories, + id: 1, + isFeatured: true, + name: 'Stella sunglasses', + price: 58, + ), + Product( + category: Category.accessories, + id: 2, + isFeatured: false, + name: 'Whitney belt', + price: 35, + ), + Product( + category: Category.accessories, + id: 3, + isFeatured: true, + name: 'Garden strand', + price: 98, + ), + Product( + category: Category.accessories, + id: 4, + isFeatured: false, + name: 'Strut earrings', + price: 34, + ), + Product( + category: Category.accessories, + id: 5, + isFeatured: false, + name: 'Varsity socks', + price: 12, + ), + Product( + category: Category.accessories, + id: 6, + isFeatured: false, + name: 'Weave keyring', + price: 16, + ), + Product( + category: Category.accessories, + id: 7, + isFeatured: true, + name: 'Gatsby hat', + price: 40, + ), + Product( + category: Category.accessories, + id: 8, + isFeatured: true, + name: 'Shrug bag', + price: 198, + ), + Product( + category: Category.home, + id: 9, + isFeatured: true, + name: 'Gilt desk trio', + price: 58, + ), + Product( + category: Category.home, + id: 10, + isFeatured: false, + name: 'Copper wire rack', + price: 18, + ), + Product( + category: Category.home, + id: 11, + isFeatured: false, + name: 'Soothe ceramic set', + price: 28, + ), + Product( + category: Category.home, + id: 12, + isFeatured: false, + name: 'Hurrahs tea set', + price: 34, + ), + Product( + category: Category.home, + id: 13, + isFeatured: true, + name: 'Blue stone mug', + price: 18, + ), + Product( + category: Category.home, + id: 14, + isFeatured: true, + name: 'Rainwater tray', + price: 27, + ), + Product( + category: Category.home, + id: 15, + isFeatured: true, + name: 'Chambray napkins', + price: 16, + ), + Product( + category: Category.home, + id: 16, + isFeatured: true, + name: 'Succulent planters', + price: 16, + ), + Product( + category: Category.home, + id: 17, + isFeatured: false, + name: 'Quartet table', + price: 175, + ), + Product( + category: Category.home, + id: 18, + isFeatured: true, + name: 'Kitchen quattro', + price: 129, + ), + Product( + category: Category.clothing, + id: 19, + isFeatured: false, + name: 'Clay sweater', + price: 48, + ), + Product( + category: Category.clothing, + id: 20, + isFeatured: false, + name: 'Sea tunic', + price: 45, + ), + Product( + category: Category.clothing, + id: 21, + isFeatured: false, + name: 'Plaster tunic', + price: 38, + ), + Product( + category: Category.clothing, + id: 22, + isFeatured: false, + name: 'White pinstripe shirt', + price: 70, + ), + Product( + category: Category.clothing, + id: 23, + isFeatured: false, + name: 'Chambray shirt', + price: 70, + ), + Product( + category: Category.clothing, + id: 24, + isFeatured: true, + name: 'Seabreeze sweater', + price: 60, + ), + Product( + category: Category.clothing, + id: 25, + isFeatured: false, + name: 'Gentry jacket', + price: 178, + ), + Product( + category: Category.clothing, + id: 26, + isFeatured: false, + name: 'Navy trousers', + price: 74, + ), + Product( + category: Category.clothing, + id: 27, + isFeatured: true, + name: 'Walter henley (white)', + price: 38, + ), + Product( + category: Category.clothing, + id: 28, + isFeatured: true, + name: 'Surf and perf shirt', + price: 48, + ), + Product( + category: Category.clothing, + id: 29, + isFeatured: true, + name: 'Ginger scarf', + price: 98, + ), + Product( + category: Category.clothing, + id: 30, + isFeatured: true, + name: 'Ramona crossover', + price: 68, + ), + Product( + category: Category.clothing, + id: 31, + isFeatured: false, + name: 'Chambray shirt', + price: 38, + ), + Product( + category: Category.clothing, + id: 32, + isFeatured: false, + name: 'Classic white collar', + price: 58, + ), + Product( + category: Category.clothing, + id: 33, + isFeatured: true, + name: 'Cerise scallop tee', + price: 42, + ), + Product( + category: Category.clothing, + id: 34, + isFeatured: false, + name: 'Shoulder rolls tee', + price: 27, + ), + Product( + category: Category.clothing, + id: 35, + isFeatured: false, + name: 'Grey slouch tank', + price: 24, + ), + Product( + category: Category.clothing, + id: 36, + isFeatured: false, + name: 'Sunshirt dress', + price: 58, + ), + Product( + category: Category.clothing, + id: 37, + isFeatured: true, + name: 'Fine lines tee', + price: 58, + ), + ]; + if (category == Category.all) { + return allProducts; + } else { + return allProducts.where((Product p) => p.category == category).toList(); + } + } +} diff --git a/web/gallery/lib/demo/shrine/shopping_cart.dart b/web/gallery/lib/demo/shrine/shopping_cart.dart new file mode 100644 index 000000000..076e6573a --- /dev/null +++ b/web/gallery/lib/demo/shrine/shopping_cart.dart @@ -0,0 +1,275 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:scoped_model/scoped_model.dart'; + +import 'package:flutter_gallery/demo/shrine/colors.dart'; +import 'package:flutter_gallery/demo/shrine/expanding_bottom_sheet.dart'; +import 'package:flutter_gallery/demo/shrine/model/app_state_model.dart'; +import 'package:flutter_gallery/demo/shrine/model/product.dart'; + +const double _leftColumnWidth = 60.0; + +class ShoppingCartPage extends StatefulWidget { + @override + _ShoppingCartPageState createState() => _ShoppingCartPageState(); +} + +class _ShoppingCartPageState extends State { + List _createShoppingCartRows(AppStateModel model) { + return model.productsInCart.keys + .map((int id) => ShoppingCartRow( + product: model.getProductById(id), + quantity: model.productsInCart[id], + onPressed: () { + model.removeItemFromCart(id); + }, + ), + ) + .toList(); + } + + @override + Widget build(BuildContext context) { + final ThemeData localTheme = Theme.of(context); + + return Scaffold( + backgroundColor: kShrinePink50, + body: SafeArea( + child: Container( + child: ScopedModelDescendant( + builder: (BuildContext context, Widget child, AppStateModel model) { + return Stack( + children: [ + ListView( + children: [ + Row( + children: [ + SizedBox( + width: _leftColumnWidth, + child: IconButton( + icon: const Icon(Icons.keyboard_arrow_down), + onPressed: () => ExpandingBottomSheet.of(context).close(), + ), + ), + Text( + 'CART', + style: localTheme.textTheme.subhead.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(width: 16.0), + Text('${model.totalCartQuantity} ITEMS'), + ], + ), + const SizedBox(height: 16.0), + Column( + children: _createShoppingCartRows(model), + ), + ShoppingCartSummary(model: model), + const SizedBox(height: 100.0), + ], + ), + Positioned( + bottom: 16.0, + left: 16.0, + right: 16.0, + child: RaisedButton( + shape: const BeveledRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(7.0)), + ), + color: kShrinePink100, + splashColor: kShrineBrown600, + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 12.0), + child: Text('CLEAR CART'), + ), + onPressed: () { + model.clearCart(); + ExpandingBottomSheet.of(context).close(); + }, + ), + ), + ], + ); + }, + ), + ), + ), + ); + } +} + +class ShoppingCartSummary extends StatelessWidget { + const ShoppingCartSummary({this.model}); + + final AppStateModel model; + + @override + Widget build(BuildContext context) { + final TextStyle smallAmountStyle = Theme.of(context).textTheme.body1.copyWith(color: kShrineBrown600); + final TextStyle largeAmountStyle = Theme.of(context).textTheme.display1; + final NumberFormat formatter = NumberFormat.simpleCurrency( + decimalDigits: 2, + locale: Localizations.localeOf(context).toString(), + ); + + return Row( + children: [ + const SizedBox(width: _leftColumnWidth), + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Expanded( + child: Text('TOTAL'), + ), + Text( + formatter.format(model.totalCost), + style: largeAmountStyle, + ), + ], + ), + const SizedBox(height: 16.0), + Row( + children: [ + const Expanded( + child: Text('Subtotal:'), + ), + Text( + formatter.format(model.subtotalCost), + style: smallAmountStyle, + ), + ], + ), + const SizedBox(height: 4.0), + Row( + children: [ + const Expanded( + child: Text('Shipping:'), + ), + Text( + formatter.format(model.shippingCost), + style: smallAmountStyle, + ), + ], + ), + const SizedBox(height: 4.0), + Row( + children: [ + const Expanded( + child: Text('Tax:'), + ), + Text( + formatter.format(model.tax), + style: smallAmountStyle, + ), + ], + ), + ], + ), + ), + ), + ], + ); + } +} + +class ShoppingCartRow extends StatelessWidget { + const ShoppingCartRow({ + @required this.product, + @required this.quantity, + this.onPressed, + }); + + final Product product; + final int quantity; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + final NumberFormat formatter = NumberFormat.simpleCurrency( + decimalDigits: 0, + locale: Localizations.localeOf(context).toString(), + ); + final ThemeData localTheme = Theme.of(context); + + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Row( + key: ValueKey(product.id), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: _leftColumnWidth, + child: IconButton( + icon: const Icon(Icons.remove_circle_outline), + onPressed: onPressed, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + product.assetName, + package: product.assetPackage, + fit: BoxFit.cover, + width: 75.0, + height: 75.0, + ), + const SizedBox(width: 16.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text('Quantity: $quantity'), + ), + Text('x ${formatter.format(product.price)}'), + ], + ), + Text( + product.name, + style: localTheme.textTheme.subhead.copyWith(fontWeight: FontWeight.w600), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 16.0), + const Divider( + color: kShrineBrown900, + height: 10.0, + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/web/gallery/lib/demo/shrine/shrine_data.dart b/web/gallery/lib/demo/shrine/shrine_data.dart deleted file mode 100644 index bb2a2c1dc..000000000 --- a/web/gallery/lib/demo/shrine/shrine_data.dart +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'shrine_types.dart'; - -const String _kGalleryAssetsPackage = null; - -const Vendor _ali = Vendor( - name: 'Ali’s shop', - avatarAsset: 'people/square/ali.png', - avatarAssetPackage: _kGalleryAssetsPackage, - description: - 'Ali Connor’s makes custom goods for folks of all shapes and sizes ' - 'made by hand and sometimes by machine, but always with love and care. ' - 'Custom orders are available upon request if you need something extra special.'); - -const Vendor _peter = Vendor( - name: 'Peter’s shop', - avatarAsset: 'people/square/peter.png', - avatarAssetPackage: _kGalleryAssetsPackage, - description: - 'Peter makes great stuff for awesome people like you. Super cool and extra ' - 'awesome all of his shop’s goods are handmade with love. Custom orders are ' - 'available upon request if you need something extra special.'); - -const Vendor _sandra = Vendor( - name: 'Sandra’s shop', - avatarAsset: 'people/square/sandra.png', - avatarAssetPackage: _kGalleryAssetsPackage, - description: - 'Sandra specializes in furniture, beauty and travel products with a classic vibe. ' - 'Custom orders are available if you’re looking for a certain color or material.'); - -const Vendor _stella = Vendor( - name: 'Stella’s shop', - avatarAsset: 'people/square/stella.png', - avatarAssetPackage: _kGalleryAssetsPackage, - description: - 'Stella sells awesome stuff at lovely prices. made by hand and sometimes by ' - 'machine, but always with love and care. Custom orders are available upon request ' - 'if you need something extra special.'); - -const Vendor _trevor = Vendor( - name: 'Trevor’s shop', - avatarAsset: 'people/square/trevor.png', - avatarAssetPackage: _kGalleryAssetsPackage, - description: - 'Trevor makes great stuff for awesome people like you. Super cool and extra ' - 'awesome all of his shop’s goods are handmade with love. Custom orders are ' - 'available upon request if you need something extra special.'); - -const List _allProducts = [ - Product( - name: 'Vintage Brown Belt', - imageAsset: 'products/belt.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion', 'latest'], - price: 300.00, - vendor: _sandra, - description: - 'Isn’t it cool when things look old, but they\'re not. Looks Old But Not makes ' - 'awesome vintage goods that are super smart. This ol’ belt just got an upgrade. '), - Product( - name: 'Sunglasses', - imageAsset: 'products/sunnies.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['travel', 'fashion', 'beauty'], - price: 20.00, - vendor: _trevor, - description: - 'Be an optimist. Carry Sunglasses with you at all times. All Tints and ' - 'Shades products come with polarized lenses and super duper UV protection ' - 'so you can look at the sun for however long you want. Sunglasses make you ' - 'look cool, wear them.'), - Product( - name: 'Flatwear', - imageAsset: 'products/flatwear.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['furniture'], - price: 30.00, - vendor: _trevor, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Salmon Sweater', - imageAsset: 'products/sweater.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion'], - price: 300.00, - vendor: _stella, - description: - 'Looks can be deceiving. This sweater comes in a wide variety of ' - 'flavors, including salmon, that pop as soon as they hit your eyes. ' - 'Sweaters heat quickly, so savor the warmth.'), - Product( - name: 'Pine Table', - imageAsset: 'products/table.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['furniture'], - price: 63.00, - vendor: _stella, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Green Comfort Jacket', - imageAsset: 'products/jacket.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion'], - price: 36.00, - vendor: _ali, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Chambray Top', - imageAsset: 'products/top.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion'], - price: 125.00, - vendor: _peter, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Blue Cup', - imageAsset: 'products/cup.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['travel', 'furniture'], - price: 75.00, - vendor: _sandra, - description: - 'Drinksy has been making extraordinary mugs for decades. With each ' - 'cup purchased Drinksy donates a cup to those in need. Buy yourself a mug, ' - 'buy someone else a mug.'), - Product( - name: 'Tea Set', - imageAsset: 'products/teaset.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['furniture', 'fashion'], - price: 70.00, - vendor: _trevor, - featureTitle: 'Beautiful glass teapot', - featureDescription: - 'Teapot holds extremely hot liquids and pours them from the spout.', - description: - 'Impress your guests with Tea Set by Kitchen Stuff. Teapot holds extremely ' - 'hot liquids and pours them from the spout. Use the handle, shown on the right, ' - 'so your fingers don’t get burnt while pouring.'), - Product( - name: 'Blue linen napkins', - imageAsset: 'products/napkins.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['furniture', 'fashion'], - price: 89.00, - vendor: _trevor, - description: - 'Blue linen napkins were meant to go with friends, so you may want to pick ' - 'up a bunch of these. These things are absorbant.'), - Product( - name: 'Dipped Earrings', - imageAsset: 'products/earrings.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion', 'beauty'], - price: 25.00, - vendor: _stella, - description: - 'WeDipIt does it again. These hand-dipped 4 inch earrings are perfect for ' - 'the office or the beach. Just be sure you don’t drop it in a bucket of ' - 'red paint, then they won’t look dipped anymore.'), - Product( - name: 'Perfect Planters', - imageAsset: 'products/planters.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['latest', 'furniture'], - price: 30.00, - vendor: _ali, - description: - 'The Perfect Planter Co makes the best vessels for just about anything you ' - 'can pot. This set of Perfect Planters holds succulents and cuttings perfectly. ' - 'Looks great in any room. Keep out of reach from cats.'), - Product( - name: 'Cloud-White Dress', - imageAsset: 'products/dress.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion'], - price: 54.00, - vendor: _sandra, - description: - 'Trying to find the perfect outift to match your mood? Try no longer. ' - 'This Cloud-White Dress has you covered for those nights when you need ' - 'to get out, or even if you’re just headed to work.'), - Product( - name: 'Backpack', - imageAsset: 'products/backpack.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['travel', 'fashion'], - price: 25.00, - vendor: _peter, - description: - 'This backpack by Bags ‘n’ stuff can hold just about anything: a laptop, ' - 'a pen, a protractor, notebooks, small animals, plugs for your devices, ' - 'sunglasses, gym clothes, shoes, gloves, two kittens, and even lunch!'), - Product( - name: 'Charcoal Straw Hat', - imageAsset: 'products/hat.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['travel', 'fashion', 'latest'], - price: 25.00, - vendor: _ali, - description: - 'This is the helmet for those warm summer days on the road. ' - 'Jetset approved, these hats have been rigorously tested. Keep that face ' - 'protected from the sun.'), - Product( - name: 'Ginger Scarf', - imageAsset: 'products/scarf.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['latest', 'fashion'], - price: 17.00, - vendor: _peter, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Blush Sweats', - imageAsset: 'products/sweats.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['travel', 'fashion', 'latest'], - price: 25.00, - vendor: _stella, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Mint Jumper', - imageAsset: 'products/jumper.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['travel', 'fashion', 'beauty'], - price: 25.00, - vendor: _peter, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait'), - Product( - name: 'Ochre Shirt', - imageAsset: 'products/shirt.png', - imageAssetPackage: _kGalleryAssetsPackage, - categories: ['fashion', 'latest'], - price: 120.00, - vendor: _stella, - description: - 'Leave the tunnel and the rain is fallin amazing things happen when you wait') -]; - -List allProducts() { - assert(_allProducts.every((Product product) => product.isValid())); - return List.unmodifiable(_allProducts); -} diff --git a/web/gallery/lib/demo/shrine/shrine_home.dart b/web/gallery/lib/demo/shrine/shrine_home.dart deleted file mode 100644 index dfcfcc24a..000000000 --- a/web/gallery/lib/demo/shrine/shrine_home.dart +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; -import 'package:meta/meta.dart'; - -import 'shrine_data.dart'; -import 'shrine_order.dart'; -import 'shrine_page.dart'; -import 'shrine_theme.dart'; -import 'shrine_types.dart'; - -const double unitSize = kToolbarHeight; - -final List _products = List.from(allProducts()); -final Map _shoppingCart = {}; - -const int _childrenPerBlock = 8; -const int _rowsPerBlock = 5; - -int _minIndexInRow(int rowIndex) { - final int blockIndex = rowIndex ~/ _rowsPerBlock; - return const [0, 2, 4, 6, 7][rowIndex % _rowsPerBlock] + - blockIndex * _childrenPerBlock; -} - -int _maxIndexInRow(int rowIndex) { - final int blockIndex = rowIndex ~/ _rowsPerBlock; - return const [1, 3, 5, 6, 7][rowIndex % _rowsPerBlock] + - blockIndex * _childrenPerBlock; -} - -int _rowAtIndex(int index) { - final int blockCount = index ~/ _childrenPerBlock; - return const [ - 0, - 0, - 1, - 1, - 2, - 2, - 3, - 4 - ][index - blockCount * _childrenPerBlock] + - blockCount * _rowsPerBlock; -} - -int _columnAtIndex(int index) { - return const [0, 1, 0, 1, 0, 1, 0, 0][index % _childrenPerBlock]; -} - -int _columnSpanAtIndex(int index) { - return const [1, 1, 1, 1, 1, 1, 2, 2][index % _childrenPerBlock]; -} - -// The Shrine home page arranges the product cards into two columns. The card -// on every 4th and 5th row spans two columns. -class _ShrineGridLayout extends SliverGridLayout { - const _ShrineGridLayout({ - @required this.rowStride, - @required this.columnStride, - @required this.tileHeight, - @required this.tileWidth, - }); - - final double rowStride; - final double columnStride; - final double tileHeight; - final double tileWidth; - - @override - int getMinChildIndexForScrollOffset(double scrollOffset) { - return _minIndexInRow(scrollOffset ~/ rowStride); - } - - @override - int getMaxChildIndexForScrollOffset(double scrollOffset) { - return _maxIndexInRow(scrollOffset ~/ rowStride); - } - - @override - SliverGridGeometry getGeometryForChildIndex(int index) { - final int row = _rowAtIndex(index); - final int column = _columnAtIndex(index); - final int columnSpan = _columnSpanAtIndex(index); - return SliverGridGeometry( - scrollOffset: row * rowStride, - crossAxisOffset: column * columnStride, - mainAxisExtent: tileHeight, - crossAxisExtent: tileWidth + (columnSpan - 1) * columnStride, - ); - } - - @override - double computeMaxScrollOffset(int childCount) { - if (childCount == 0) return 0.0; - final int rowCount = _rowAtIndex(childCount - 1) + 1; - final double rowSpacing = rowStride - tileHeight; - return rowStride * rowCount - rowSpacing; - } -} - -class _ShrineGridDelegate extends SliverGridDelegate { - static const double _spacing = 8.0; - - @override - SliverGridLayout getLayout(SliverConstraints constraints) { - final double tileWidth = (constraints.crossAxisExtent - _spacing) / 2.0; - const double tileHeight = 40.0 + 144.0 + 40.0; - return _ShrineGridLayout( - tileWidth: tileWidth, - tileHeight: tileHeight, - rowStride: tileHeight + _spacing, - columnStride: tileWidth + _spacing, - ); - } - - @override - bool shouldRelayout(covariant SliverGridDelegate oldDelegate) => false; -} - -// Displays the Vendor's name and avatar. -class _VendorItem extends StatelessWidget { - const _VendorItem({Key key, @required this.vendor}) - : assert(vendor != null), - super(key: key); - - final Vendor vendor; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 24.0, - child: Row( - children: [ - SizedBox( - width: 24.0, - child: ClipRRect( - borderRadius: BorderRadius.circular(12.0), - child: Image.asset( - vendor.avatarAsset, - package: vendor.avatarAssetPackage, - fit: BoxFit.cover, - ), - ), - ), - const SizedBox(width: 8.0), - Expanded( - child: Text(vendor.name, - style: ShrineTheme.of(context).vendorItemStyle), - ), - ], - ), - ); - } -} - -// Displays the product's price. If the product is in the shopping cart then the -// background is highlighted. -abstract class _PriceItem extends StatelessWidget { - const _PriceItem({Key key, @required this.product}) - : assert(product != null), - super(key: key); - - final Product product; - - Widget buildItem(BuildContext context, TextStyle style, EdgeInsets padding) { - BoxDecoration decoration; - if (_shoppingCart[product] != null) - decoration = - BoxDecoration(color: ShrineTheme.of(context).priceHighlightColor); - - return Container( - padding: padding, - decoration: decoration, - child: Text(product.priceString, style: style), - ); - } -} - -class _ProductPriceItem extends _PriceItem { - const _ProductPriceItem({Key key, Product product}) - : super(key: key, product: product); - - @override - Widget build(BuildContext context) { - return buildItem( - context, - ShrineTheme.of(context).priceStyle, - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - ); - } -} - -class _FeaturePriceItem extends _PriceItem { - const _FeaturePriceItem({Key key, Product product}) - : super(key: key, product: product); - - @override - Widget build(BuildContext context) { - return buildItem( - context, - ShrineTheme.of(context).featurePriceStyle, - const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), - ); - } -} - -class _HeadingLayout extends MultiChildLayoutDelegate { - _HeadingLayout(); - - static const String price = 'price'; - static const String image = 'image'; - static const String title = 'title'; - static const String description = 'description'; - static const String vendor = 'vendor'; - - @override - void performLayout(Size size) { - final Size priceSize = layoutChild(price, BoxConstraints.loose(size)); - positionChild(price, Offset(size.width - priceSize.width, 0.0)); - - final double halfWidth = size.width / 2.0; - final double halfHeight = size.height / 2.0; - const double halfUnit = unitSize / 2.0; - const double margin = 16.0; - - final Size imageSize = layoutChild(image, BoxConstraints.loose(size)); - final double imageX = imageSize.width < halfWidth - halfUnit - ? halfWidth / 2.0 - imageSize.width / 2.0 - halfUnit - : halfWidth - imageSize.width; - positionChild(image, Offset(imageX, halfHeight - imageSize.height / 2.0)); - - final double maxTitleWidth = halfWidth + unitSize - margin; - final BoxConstraints titleBoxConstraints = - BoxConstraints(maxWidth: maxTitleWidth); - final Size titleSize = layoutChild(title, titleBoxConstraints); - final double titleX = halfWidth - unitSize; - final double titleY = halfHeight - titleSize.height; - positionChild(title, Offset(titleX, titleY)); - - final Size descriptionSize = layoutChild(description, titleBoxConstraints); - final double descriptionY = titleY + titleSize.height + margin; - positionChild(description, Offset(titleX, descriptionY)); - - layoutChild(vendor, titleBoxConstraints); - final double vendorY = descriptionY + descriptionSize.height + margin; - positionChild(vendor, Offset(titleX, vendorY)); - } - - @override - bool shouldRelayout(_HeadingLayout oldDelegate) => false; -} - -// A card that highlights the "featured" catalog item. -class _Heading extends StatelessWidget { - _Heading({Key key, @required this.product}) - : assert(product != null), - assert(product.featureTitle != null), - assert(product.featureDescription != null), - super(key: key); - - final Product product; - - @override - Widget build(BuildContext context) { - final Size screenSize = MediaQuery.of(context).size; - final ShrineTheme theme = ShrineTheme.of(context); - return MergeSemantics( - child: SizedBox( - height: screenSize.width > screenSize.height - ? (screenSize.height - kToolbarHeight) * 0.85 - : (screenSize.height - kToolbarHeight) * 0.70, - child: Container( - decoration: BoxDecoration( - color: theme.cardBackgroundColor, - border: Border(bottom: BorderSide(color: theme.dividerColor)), - ), - child: CustomMultiChildLayout( - delegate: _HeadingLayout(), - children: [ - LayoutId( - id: _HeadingLayout.price, - child: _FeaturePriceItem(product: product), - ), - LayoutId( - id: _HeadingLayout.image, - child: Image.asset( - product.imageAsset, - package: product.imageAssetPackage, - fit: BoxFit.cover, - ), - ), - LayoutId( - id: _HeadingLayout.title, - child: - Text(product.featureTitle, style: theme.featureTitleStyle), - ), - LayoutId( - id: _HeadingLayout.description, - child: - Text(product.featureDescription, style: theme.featureStyle), - ), - LayoutId( - id: _HeadingLayout.vendor, - child: _VendorItem(vendor: product.vendor), - ), - ], - ), - ), - ), - ); - } -} - -// A card that displays a product's image, price, and vendor. The _ProductItem -// cards appear in a grid below the heading. -class _ProductItem extends StatelessWidget { - const _ProductItem({Key key, @required this.product, this.onPressed}) - : assert(product != null), - super(key: key); - - final Product product; - final VoidCallback onPressed; - - @override - Widget build(BuildContext context) { - return MergeSemantics( - child: Card( - child: Stack( - children: [ - Column( - children: [ - Align( - alignment: Alignment.centerRight, - child: _ProductPriceItem(product: product), - ), - Container( - width: 144.0, - height: 144.0, - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Hero( - tag: product.tag, - child: Image.asset( - product.imageAsset, - package: product.imageAssetPackage, - fit: BoxFit.contain, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: _VendorItem(vendor: product.vendor), - ), - ], - ), - Material( - type: MaterialType.transparency, - child: InkWell(onTap: onPressed), - ), - ], - ), - ), - ); - } -} - -// The Shrine app's home page. Displays the featured item above a grid -// of the product items. -class ShrineHome extends StatefulWidget { - @override - _ShrineHomeState createState() => _ShrineHomeState(); -} - -class _ShrineHomeState extends State { - static final GlobalKey _scaffoldKey = - GlobalKey(debugLabel: 'Shrine Home'); - static final _ShrineGridDelegate gridDelegate = _ShrineGridDelegate(); - - Future _showOrderPage(Product product) async { - final Order order = _shoppingCart[product] ?? Order(product: product); - final Order completedOrder = await Navigator.push( - context, - ShrineOrderRoute( - order: order, - builder: (BuildContext context) { - return OrderPage( - order: order, - products: _products, - shoppingCart: _shoppingCart, - ); - })); - assert(completedOrder.product != null); - if (completedOrder.quantity == 0) - _shoppingCart.remove(completedOrder.product); - } - - @override - Widget build(BuildContext context) { - final Product featured = _products - .firstWhere((Product product) => product.featureDescription != null); - return ShrinePage( - scaffoldKey: _scaffoldKey, - products: _products, - shoppingCart: _shoppingCart, - body: CustomScrollView( - slivers: [ - SliverToBoxAdapter(child: _Heading(product: featured)), - SliverSafeArea( - top: false, - minimum: const EdgeInsets.all(16.0), - sliver: SliverGrid( - gridDelegate: gridDelegate, - delegate: SliverChildListDelegate( - _products.map((Product product) { - return _ProductItem( - product: product, - onPressed: () { - _showOrderPage(product); - }, - ); - }).toList(), - ), - ), - ), - ], - ), - ); - } -} diff --git a/web/gallery/lib/demo/shrine/shrine_order.dart b/web/gallery/lib/demo/shrine/shrine_order.dart deleted file mode 100644 index 788c9b0df..000000000 --- a/web/gallery/lib/demo/shrine/shrine_order.dart +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; - -import '../shrine_demo.dart' show ShrinePageRoute; -import 'shrine_page.dart'; -import 'shrine_theme.dart'; -import 'shrine_types.dart'; - -// Displays the product title's, description, and order quantity dropdown. -class _ProductItem extends StatelessWidget { - const _ProductItem({ - Key key, - @required this.product, - @required this.quantity, - @required this.onChanged, - }) : assert(product != null), - assert(quantity != null), - assert(onChanged != null), - super(key: key); - - final Product product; - final int quantity; - final ValueChanged onChanged; - - @override - Widget build(BuildContext context) { - final ShrineTheme theme = ShrineTheme.of(context); - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(product.name, style: theme.featureTitleStyle), - const SizedBox(height: 24.0), - Text(product.description, style: theme.featureStyle), - const SizedBox(height: 16.0), - Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 8.0, right: 88.0), - child: DropdownButtonHideUnderline( - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: const Color(0xFFD9D9D9), - ), - ), - child: DropdownButton( - items: [0, 1, 2, 3, 4, 5] - .map>((int value) { - return DropdownMenuItem( - value: value, - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Text('Quantity $value', - style: theme.quantityMenuStyle), - ), - ); - }).toList(), - value: quantity, - onChanged: onChanged, - ), - ), - ), - ), - ], - ); - } -} - -// Vendor name and description -class _VendorItem extends StatelessWidget { - const _VendorItem({Key key, @required this.vendor}) - : assert(vendor != null), - super(key: key); - - final Vendor vendor; - - @override - Widget build(BuildContext context) { - final ShrineTheme theme = ShrineTheme.of(context); - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox( - height: 24.0, - child: Align( - alignment: Alignment.bottomLeft, - child: Text(vendor.name, style: theme.vendorTitleStyle), - ), - ), - const SizedBox(height: 16.0), - Text(vendor.description, style: theme.vendorStyle), - ], - ); - } -} - -// Layout the order page's heading: the product's image, the -// title/description/dropdown product item, and the vendor item. -class _HeadingLayout extends MultiChildLayoutDelegate { - _HeadingLayout(); - - static const String image = 'image'; - static const String icon = 'icon'; - static const String product = 'product'; - static const String vendor = 'vendor'; - - @override - void performLayout(Size size) { - const double margin = 56.0; - final bool landscape = size.width > size.height; - final double imageWidth = - (landscape ? size.width / 2.0 : size.width) - margin * 2.0; - final BoxConstraints imageConstraints = - BoxConstraints(maxHeight: 224.0, maxWidth: imageWidth); - final Size imageSize = layoutChild(image, imageConstraints); - const double imageY = 0.0; - positionChild(image, const Offset(margin, imageY)); - - final double productWidth = - landscape ? size.width / 2.0 : size.width - margin; - final BoxConstraints productConstraints = - BoxConstraints(maxWidth: productWidth); - final Size productSize = layoutChild(product, productConstraints); - final double productX = landscape ? size.width / 2.0 : margin; - final double productY = landscape ? 0.0 : imageY + imageSize.height + 16.0; - positionChild(product, Offset(productX, productY)); - - final Size iconSize = layoutChild(icon, BoxConstraints.loose(size)); - positionChild( - icon, Offset(productX - iconSize.width - 16.0, productY + 8.0)); - - final double vendorWidth = landscape ? size.width - margin : productWidth; - layoutChild(vendor, BoxConstraints(maxWidth: vendorWidth)); - final double vendorX = landscape ? margin : productX; - final double vendorY = productY + productSize.height + 16.0; - positionChild(vendor, Offset(vendorX, vendorY)); - } - - @override - bool shouldRelayout(_HeadingLayout oldDelegate) => true; -} - -// Describes a product and vendor in detail, supports specifying -// a order quantity (0-5). Appears at the top of the OrderPage. -class _Heading extends StatelessWidget { - const _Heading({ - Key key, - @required this.product, - @required this.quantity, - this.quantityChanged, - }) : assert(product != null), - assert(quantity != null && quantity >= 0 && quantity <= 5), - super(key: key); - - final Product product; - final int quantity; - final ValueChanged quantityChanged; - - @override - Widget build(BuildContext context) { - final Size screenSize = MediaQuery.of(context).size; - return SizedBox( - height: (screenSize.height - kToolbarHeight) * 1.35, - child: Material( - type: MaterialType.card, - elevation: 0.0, - child: Padding( - padding: const EdgeInsets.only( - left: 16.0, top: 18.0, right: 16.0, bottom: 24.0), - child: CustomMultiChildLayout( - delegate: _HeadingLayout(), - children: [ - LayoutId( - id: _HeadingLayout.image, - child: Hero( - tag: product.tag, - child: Image.asset( - product.imageAsset, - package: product.imageAssetPackage, - fit: BoxFit.contain, - alignment: Alignment.center, - ), - ), - ), - LayoutId( - id: _HeadingLayout.icon, - child: const Icon( - Icons.info_outline, - size: 24.0, - color: Color(0xFFFFE0E0), - ), - ), - LayoutId( - id: _HeadingLayout.product, - child: _ProductItem( - product: product, - quantity: quantity, - onChanged: quantityChanged, - ), - ), - LayoutId( - id: _HeadingLayout.vendor, - child: _VendorItem(vendor: product.vendor), - ), - ], - ), - ), - ), - ); - } -} - -class OrderPage extends StatefulWidget { - OrderPage({ - Key key, - @required this.order, - @required this.products, - @required this.shoppingCart, - }) : assert(order != null), - assert(products != null && products.isNotEmpty), - assert(shoppingCart != null), - super(key: key); - - final Order order; - final List products; - final Map shoppingCart; - - @override - _OrderPageState createState() => _OrderPageState(); -} - -// Displays a product's heading above photos of all of the other products -// arranged in two columns. Enables the user to specify a quantity and add an -// order to the shopping cart. -class _OrderPageState extends State { - GlobalKey scaffoldKey; - - @override - void initState() { - super.initState(); - scaffoldKey = - GlobalKey(debugLabel: 'Shrine Order ${widget.order}'); - } - - Order get currentOrder => ShrineOrderRoute.of(context).order; - - set currentOrder(Order value) { - ShrineOrderRoute.of(context).order = value; - } - - void updateOrder({int quantity, bool inCart}) { - final Order newOrder = - currentOrder.copyWith(quantity: quantity, inCart: inCart); - if (currentOrder != newOrder) { - setState(() { - widget.shoppingCart[newOrder.product] = newOrder; - currentOrder = newOrder; - }); - } - } - - void showSnackBarMessage(String message) { - scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message))); - } - - @override - Widget build(BuildContext context) { - return ShrinePage( - scaffoldKey: scaffoldKey, - products: widget.products, - shoppingCart: widget.shoppingCart, - floatingActionButton: FloatingActionButton( - onPressed: () { - updateOrder(inCart: true); - final int n = currentOrder.quantity; - final String item = currentOrder.product.name; - showSnackBarMessage( - 'There ${n == 1 ? "is one $item item" : "are $n $item items"} in the shopping cart.'); - }, - backgroundColor: const Color(0xFF16F0F0), - tooltip: 'Add to cart', - child: const Icon( - Icons.add_shopping_cart, - color: Colors.black, - ), - ), - body: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: _Heading( - product: widget.order.product, - quantity: currentOrder.quantity, - quantityChanged: (int value) { - updateOrder(quantity: value); - }, - ), - ), - SliverSafeArea( - top: false, - minimum: const EdgeInsets.fromLTRB(8.0, 32.0, 8.0, 8.0), - sliver: SliverGrid( - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 248.0, - mainAxisSpacing: 8.0, - crossAxisSpacing: 8.0, - ), - delegate: SliverChildListDelegate( - widget.products - .where((Product product) => product != widget.order.product) - .map((Product product) { - return Card( - elevation: 1.0, - child: Image.asset( - product.imageAsset, - package: product.imageAssetPackage, - fit: BoxFit.contain, - ), - ); - }).toList(), - ), - ), - ), - ], - ), - ); - } -} - -// Displays a full-screen modal OrderPage. -// -// The order field will be replaced each time the user reconfigures the order. -// When the user backs out of this route the completer's value will be the -// final value of the order field. -class ShrineOrderRoute extends ShrinePageRoute { - ShrineOrderRoute({ - @required this.order, - WidgetBuilder builder, - RouteSettings settings, - }) : assert(order != null), - super(builder: builder, settings: settings); - - Order order; - - @override - Order get currentResult => order; - - static ShrineOrderRoute of(BuildContext context) => - ModalRoute.of(context); -} diff --git a/web/gallery/lib/demo/shrine/shrine_page.dart b/web/gallery/lib/demo/shrine/shrine_page.dart deleted file mode 100644 index 506588b1b..000000000 --- a/web/gallery/lib/demo/shrine/shrine_page.dart +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -import 'shrine_theme.dart'; -import 'shrine_types.dart'; - -enum ShrineAction { sortByPrice, sortByProduct, emptyCart } - -class ShrinePage extends StatefulWidget { - const ShrinePage( - {Key key, - @required this.scaffoldKey, - @required this.body, - this.floatingActionButton, - this.products, - this.shoppingCart}) - : assert(body != null), - assert(scaffoldKey != null), - super(key: key); - - final GlobalKey scaffoldKey; - final Widget body; - final Widget floatingActionButton; - final List products; - final Map shoppingCart; - - @override - ShrinePageState createState() => ShrinePageState(); -} - -/// Defines the Scaffold, AppBar, etc that the demo pages have in common. -class ShrinePageState extends State { - double _appBarElevation = 0.0; - - bool _handleScrollNotification(ScrollNotification notification) { - final double elevation = - notification.metrics.extentBefore <= 0.0 ? 0.0 : 1.0; - if (elevation != _appBarElevation) { - setState(() { - _appBarElevation = elevation; - }); - } - return false; - } - - void _showShoppingCart() { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - if (widget.shoppingCart.isEmpty) { - return const Padding( - padding: EdgeInsets.all(24.0), - child: Text('The shopping cart is empty')); - } - return ListView( - padding: kMaterialListPadding, - children: widget.shoppingCart.values.map((Order order) { - return ListTile( - title: Text(order.product.name), - leading: Text('${order.quantity}'), - subtitle: Text(order.product.vendor.name)); - }).toList(), - ); - }); - } - - void _sortByPrice() { - widget.products.sort((Product a, Product b) => a.price.compareTo(b.price)); - } - - void _sortByProduct() { - widget.products.sort((Product a, Product b) => a.name.compareTo(b.name)); - } - - void _emptyCart() { - widget.shoppingCart.clear(); - widget.scaffoldKey.currentState - .showSnackBar(const SnackBar(content: Text('Shopping cart is empty'))); - } - - @override - Widget build(BuildContext context) { - final ShrineTheme theme = ShrineTheme.of(context); - return Scaffold( - key: widget.scaffoldKey, - appBar: AppBar( - elevation: _appBarElevation, - backgroundColor: theme.appBarBackgroundColor, - iconTheme: Theme.of(context).iconTheme, - brightness: Brightness.light, - flexibleSpace: Container( - decoration: BoxDecoration( - border: - Border(bottom: BorderSide(color: theme.dividerColor)))), - title: - Text('SHRINE', style: ShrineTheme.of(context).appBarTitleStyle), - centerTitle: true, - actions: [ - IconButton( - icon: const Icon(Icons.shopping_cart), - tooltip: 'Shopping cart', - onPressed: _showShoppingCart), - PopupMenuButton( - itemBuilder: (BuildContext context) => - >[ - const PopupMenuItem( - value: ShrineAction.sortByPrice, - child: Text('Sort by price')), - const PopupMenuItem( - value: ShrineAction.sortByProduct, - child: Text('Sort by product')), - const PopupMenuItem( - value: ShrineAction.emptyCart, - child: Text('Empty shopping cart')) - ], - onSelected: (ShrineAction action) { - switch (action) { - case ShrineAction.sortByPrice: - setState(_sortByPrice); - break; - case ShrineAction.sortByProduct: - setState(_sortByProduct); - break; - case ShrineAction.emptyCart: - setState(_emptyCart); - break; - } - }) - ]), - floatingActionButton: widget.floatingActionButton, - body: NotificationListener( - onNotification: _handleScrollNotification, child: widget.body)); - } -} diff --git a/web/gallery/lib/demo/shrine/shrine_theme.dart b/web/gallery/lib/demo/shrine/shrine_theme.dart deleted file mode 100644 index 5f04f79ab..000000000 --- a/web/gallery/lib/demo/shrine/shrine_theme.dart +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/material.dart'; - -class ShrineStyle extends TextStyle { - const ShrineStyle.roboto(double size, FontWeight weight, Color color) - : super( - inherit: false, - color: color, - fontSize: size, - fontWeight: weight, - textBaseline: TextBaseline.alphabetic); - - const ShrineStyle.abrilFatface(double size, FontWeight weight, Color color) - : super( - inherit: false, - color: color, - fontFamily: 'AbrilFatface', - fontSize: size, - fontWeight: weight, - textBaseline: TextBaseline.alphabetic); -} - -TextStyle robotoRegular12(Color color) => - ShrineStyle.roboto(12.0, FontWeight.w500, color); -TextStyle robotoLight12(Color color) => - ShrineStyle.roboto(12.0, FontWeight.w300, color); -TextStyle robotoRegular14(Color color) => - ShrineStyle.roboto(14.0, FontWeight.w500, color); -TextStyle robotoMedium14(Color color) => - ShrineStyle.roboto(14.0, FontWeight.w600, color); -TextStyle robotoLight14(Color color) => - ShrineStyle.roboto(14.0, FontWeight.w300, color); -TextStyle robotoRegular16(Color color) => - ShrineStyle.roboto(16.0, FontWeight.w500, color); -TextStyle robotoRegular20(Color color) => - ShrineStyle.roboto(20.0, FontWeight.w500, color); -TextStyle abrilFatfaceRegular24(Color color) => - ShrineStyle.abrilFatface(24.0, FontWeight.w500, color); -TextStyle abrilFatfaceRegular34(Color color) => - ShrineStyle.abrilFatface(34.0, FontWeight.w500, color); - -/// The TextStyles and Colors used for titles, labels, and descriptions. This -/// InheritedWidget is shared by all of the routes and widgets created for -/// the Shrine app. -class ShrineTheme extends InheritedWidget { - ShrineTheme({Key key, @required Widget child}) - : assert(child != null), - super(key: key, child: child); - - final Color cardBackgroundColor = Colors.white; - final Color appBarBackgroundColor = Colors.white; - final Color dividerColor = const Color(0xFFD9D9D9); - final Color priceHighlightColor = const Color(0xFFFFE0E0); - - final TextStyle appBarTitleStyle = robotoRegular20(Colors.black87); - final TextStyle vendorItemStyle = robotoRegular12(const Color(0xFF81959D)); - final TextStyle priceStyle = robotoRegular14(Colors.black87); - final TextStyle featureTitleStyle = - abrilFatfaceRegular34(const Color(0xFF0A3142)); - final TextStyle featurePriceStyle = robotoRegular16(Colors.black87); - final TextStyle featureStyle = robotoLight14(Colors.black54); - final TextStyle orderTitleStyle = abrilFatfaceRegular24(Colors.black87); - final TextStyle orderStyle = robotoLight14(Colors.black54); - final TextStyle vendorTitleStyle = robotoMedium14(Colors.black87); - final TextStyle vendorStyle = robotoLight14(Colors.black54); - final TextStyle quantityMenuStyle = robotoLight14(Colors.black54); - - static ShrineTheme of(BuildContext context) => - context.inheritFromWidgetOfExactType(ShrineTheme); - - @override - bool updateShouldNotify(ShrineTheme oldWidget) => false; -} diff --git a/web/gallery/lib/demo/shrine/shrine_types.dart b/web/gallery/lib/demo/shrine/shrine_types.dart deleted file mode 100644 index dcf6d8839..000000000 --- a/web/gallery/lib/demo/shrine/shrine_types.dart +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/foundation.dart'; -import 'package:flutter_web_ui/ui.dart' show hashValues; - -class Vendor { - const Vendor({ - this.name, - this.description, - this.avatarAsset, - this.avatarAssetPackage, - }); - - final String name; - final String description; - final String avatarAsset; - final String avatarAssetPackage; - - bool isValid() { - return name != null && description != null && avatarAsset != null; - } - - @override - String toString() => 'Vendor($name)'; -} - -class Product { - const Product( - {this.name, - this.description, - this.featureTitle, - this.featureDescription, - this.imageAsset, - this.imageAssetPackage, - this.categories, - this.price, - this.vendor}); - - final String name; - final String description; - final String featureTitle; - final String featureDescription; - final String imageAsset; - final String imageAssetPackage; - final List categories; - final double price; - final Vendor vendor; - - String get tag => name; // Unique value for Heroes - String get priceString => '\$${price.floor()}'; - - bool isValid() { - return name != null && - description != null && - imageAsset != null && - categories != null && - categories.isNotEmpty && - price != null && - vendor.isValid(); - } - - @override - String toString() => 'Product($name)'; -} - -class Order { - Order({@required this.product, this.quantity = 1, this.inCart = false}) - : assert(product != null), - assert(quantity != null && quantity >= 0), - assert(inCart != null); - - final Product product; - final int quantity; - final bool inCart; - - Order copyWith({Product product, int quantity, bool inCart}) { - return Order( - product: product ?? this.product, - quantity: quantity ?? this.quantity, - inCart: inCart ?? this.inCart); - } - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - final Order typedOther = other; - return product == typedOther.product && - quantity == typedOther.quantity && - inCart == typedOther.inCart; - } - - @override - int get hashCode => hashValues(product, quantity, inCart); - - @override - String toString() => 'Order($product, quantity=$quantity, inCart=$inCart)'; -} diff --git a/web/gallery/lib/demo/shrine/supplemental/asymmetric_view.dart b/web/gallery/lib/demo/shrine/supplemental/asymmetric_view.dart new file mode 100644 index 000000000..10922a142 --- /dev/null +++ b/web/gallery/lib/demo/shrine/supplemental/asymmetric_view.dart @@ -0,0 +1,95 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; + +import 'package:flutter_gallery/demo/shrine/model/product.dart'; +import 'package:flutter_gallery/demo/shrine/supplemental/product_columns.dart'; + +class AsymmetricView extends StatelessWidget { + const AsymmetricView({Key key, this.products}) : super(key: key); + + final List products; + + List _buildColumns(BuildContext context) { + if (products == null || products.isEmpty) { + return const []; + } + + // This will return a list of columns. It will oscillate between the two + // kinds of columns. Even cases of the index (0, 2, 4, etc) will be + // TwoProductCardColumn and the odd cases will be OneProductCardColumn. + // + // Each pair of columns will advance us 3 products forward (2 + 1). That's + // some kinda awkward math so we use _evenCasesIndex and _oddCasesIndex as + // helpers for creating the index of the product list that will correspond + // to the index of the list of columns. + return List.generate(_listItemCount(products.length), (int index) { + double width = .59 * MediaQuery.of(context).size.width; + Widget column; + if (index % 2 == 0) { + /// Even cases + final int bottom = _evenCasesIndex(index); + column = TwoProductCardColumn( + bottom: products[bottom], + top: products.length - 1 >= bottom + 1 + ? products[bottom + 1] + : null, + ); + width += 32.0; + } else { + /// Odd cases + column = OneProductCardColumn( + product: products[_oddCasesIndex(index)], + ); + } + return Container( + width: width, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: column, + ), + ); + }).toList(); + } + + int _evenCasesIndex(int input) { + // The operator ~/ is a cool one. It's the truncating division operator. It + // divides the number and if there's a remainder / decimal, it cuts it off. + // This is like dividing and then casting the result to int. Also, it's + // functionally equivalent to floor() in this case. + return input ~/ 2 * 3; + } + + int _oddCasesIndex(int input) { + assert(input > 0); + return (input / 2).ceil() * 3 - 1; + } + + int _listItemCount(int totalItems) { + return (totalItems % 3 == 0) + ? totalItems ~/ 3 * 2 + : (totalItems / 3).ceil() * 2 - 1; + } + + @override + Widget build(BuildContext context) { + return ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.fromLTRB(0.0, 34.0, 16.0, 44.0), + children: _buildColumns(context), + physics: const AlwaysScrollableScrollPhysics(), + ); + } +} diff --git a/web/gallery/lib/demo/shrine/supplemental/cut_corners_border.dart b/web/gallery/lib/demo/shrine/supplemental/cut_corners_border.dart new file mode 100644 index 000000000..96e7e0e4c --- /dev/null +++ b/web/gallery/lib/demo/shrine/supplemental/cut_corners_border.dart @@ -0,0 +1,134 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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:ui' show lerpDouble; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class CutCornersBorder extends OutlineInputBorder { + const CutCornersBorder({ + BorderSide borderSide = BorderSide.none, + BorderRadius borderRadius = const BorderRadius.all(Radius.circular(2.0)), + this.cut = 7.0, + double gapPadding = 2.0, + }) : super( + borderSide: borderSide, + borderRadius: borderRadius, + gapPadding: gapPadding, + ); + + @override + CutCornersBorder copyWith({ + BorderSide borderSide, + BorderRadius borderRadius, + double gapPadding, + double cut, + }) { + return CutCornersBorder( + borderSide: borderSide ?? this.borderSide, + borderRadius: borderRadius ?? this.borderRadius, + gapPadding: gapPadding ?? this.gapPadding, + cut: cut ?? this.cut, + ); + } + + final double cut; + + @override + ShapeBorder lerpFrom(ShapeBorder a, double t) { + if (a is CutCornersBorder) { + final CutCornersBorder outline = a; + return CutCornersBorder( + borderRadius: BorderRadius.lerp(outline.borderRadius, borderRadius, t), + borderSide: BorderSide.lerp(outline.borderSide, borderSide, t), + cut: cut, + gapPadding: outline.gapPadding, + ); + } + return super.lerpFrom(a, t); + } + + @override + ShapeBorder lerpTo(ShapeBorder b, double t) { + if (b is CutCornersBorder) { + final CutCornersBorder outline = b; + return CutCornersBorder( + borderRadius: BorderRadius.lerp(borderRadius, outline.borderRadius, t), + borderSide: BorderSide.lerp(borderSide, outline.borderSide, t), + cut: cut, + gapPadding: outline.gapPadding, + ); + } + return super.lerpTo(b, t); + } + + Path _notchedCornerPath(Rect center, [double start = 0.0, double extent = 0.0]) { + final Path path = Path(); + if (start > 0.0 || extent > 0.0) { + path.relativeMoveTo(extent + start, center.top); + _notchedSidesAndBottom(center, path); + path..lineTo(center.left + cut, center.top)..lineTo(start, center.top); + } else { + path.moveTo(center.left + cut, center.top); + _notchedSidesAndBottom(center, path); + path.lineTo(center.left + cut, center.top); + } + return path; + } + + Path _notchedSidesAndBottom(Rect center, Path path) { + return path + ..lineTo(center.right - cut, center.top) + ..lineTo(center.right, center.top + cut) + ..lineTo(center.right, center.top + center.height - cut) + ..lineTo(center.right - cut, center.top + center.height) + ..lineTo(center.left + cut, center.top + center.height) + ..lineTo(center.left, center.top + center.height - cut) + ..lineTo(center.left, center.top + cut); + } + + @override + void paint( + Canvas canvas, + Rect rect, { + double gapStart, + double gapExtent = 0.0, + double gapPercentage = 0.0, + TextDirection textDirection, + }) { + assert(gapExtent != null); + assert(gapPercentage >= 0.0 && gapPercentage <= 1.0); + + final Paint paint = borderSide.toPaint(); + final RRect outer = borderRadius.toRRect(rect); + if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) { + canvas.drawPath(_notchedCornerPath(outer.middleRect), paint); + } else { + final double extent = lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage); + switch (textDirection) { + case TextDirection.rtl: { + final Path path = _notchedCornerPath(outer.middleRect, gapStart + gapPadding - extent, extent); + canvas.drawPath(path, paint); + break; + } + case TextDirection.ltr: { + final Path path = _notchedCornerPath(outer.middleRect, gapStart - gapPadding, extent); + canvas.drawPath(path, paint); + break; + } + } + } + } +} diff --git a/web/gallery/lib/demo/shrine/supplemental/product_card.dart b/web/gallery/lib/demo/shrine/supplemental/product_card.dart new file mode 100644 index 000000000..d2a71a310 --- /dev/null +++ b/web/gallery/lib/demo/shrine/supplemental/product_card.dart @@ -0,0 +1,97 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:scoped_model/scoped_model.dart'; + +import 'package:flutter_gallery/demo/shrine/model/app_state_model.dart'; +import 'package:flutter_gallery/demo/shrine/model/product.dart'; + +class ProductCard extends StatelessWidget { + const ProductCard({ this.imageAspectRatio = 33 / 49, this.product }) + : assert(imageAspectRatio == null || imageAspectRatio > 0); + + final double imageAspectRatio; + final Product product; + + static const double kTextBoxHeight = 65.0; + + @override + Widget build(BuildContext context) { + final NumberFormat formatter = NumberFormat.simpleCurrency( + decimalDigits: 0, + locale: Localizations.localeOf(context).toString(), + ); + + final ThemeData theme = Theme.of(context); + + final Image imageWidget = Image.asset( + product.assetName, + package: product.assetPackage, + fit: BoxFit.cover, + ); + + return ScopedModelDescendant( + builder: (BuildContext context, Widget child, AppStateModel model) { + return GestureDetector( + onTap: () { + model.addProductToCart(product.id); + }, + child: child, + ); + }, + child: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: imageAspectRatio, + child: imageWidget, + ), + SizedBox( + height: kTextBoxHeight * MediaQuery.of(context).textScaleFactor, + width: 121.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + product == null ? '' : product.name, + style: theme.textTheme.button, + softWrap: false, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(height: 4.0), + Text( + product == null ? '' : formatter.format(product.price), + style: theme.textTheme.caption, + ), + ], + ), + ), + ], + ), + const Padding( + padding: EdgeInsets.all(16.0), + child: Icon(Icons.add_shopping_cart), + ), + ], + ), + ); + } +} diff --git a/web/gallery/lib/demo/shrine/supplemental/product_columns.dart b/web/gallery/lib/demo/shrine/supplemental/product_columns.dart new file mode 100644 index 000000000..589f9c7e6 --- /dev/null +++ b/web/gallery/lib/demo/shrine/supplemental/product_columns.dart @@ -0,0 +1,89 @@ +// Copyright 2018-present the Flutter authors. All Rights Reserved. +// +// 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 'package:flutter/material.dart'; + +import 'package:flutter_gallery/demo/shrine/model/product.dart'; +import 'package:flutter_gallery/demo/shrine/supplemental/product_card.dart'; + +class TwoProductCardColumn extends StatelessWidget { + const TwoProductCardColumn({ + @required this.bottom, + this.top, + }) : assert(bottom != null); + + final Product bottom, top; + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { + const double spacerHeight = 44.0; + + final double heightOfCards = (constraints.biggest.height - spacerHeight) / 2.0; + final double availableHeightForImages = heightOfCards - ProductCard.kTextBoxHeight; + // Ensure the cards take up the available space as long as the screen is + // sufficiently tall, otherwise fallback on a constant aspect ratio. + final double imageAspectRatio = availableHeightForImages >= 0.0 + ? constraints.biggest.width / availableHeightForImages + : 49.0 / 33.0; + + return ListView( + physics: const ClampingScrollPhysics(), + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 28.0), + child: top != null + ? ProductCard( + imageAspectRatio: imageAspectRatio, + product: top, + ) + : SizedBox( + height: heightOfCards > 0 ? heightOfCards : spacerHeight, + ), + ), + const SizedBox(height: spacerHeight), + Padding( + padding: const EdgeInsetsDirectional.only(end: 28.0), + child: ProductCard( + imageAspectRatio: imageAspectRatio, + product: bottom, + ), + ), + ], + ); + }); + } +} + +class OneProductCardColumn extends StatelessWidget { + const OneProductCardColumn({this.product}); + + final Product product; + + @override + Widget build(BuildContext context) { + return ListView( + physics: const ClampingScrollPhysics(), + reverse: true, + children: [ + const SizedBox( + height: 40.0, + ), + ProductCard( + product: product, + ), + ], + ); + } +} diff --git a/web/gallery/lib/demo/shrine_demo.dart b/web/gallery/lib/demo/shrine_demo.dart index 270ce2bda..6c58ecdcd 100644 --- a/web/gallery/lib/demo/shrine_demo.dart +++ b/web/gallery/lib/demo/shrine_demo.dart @@ -1,43 +1,15 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; - -import 'shrine/shrine_home.dart' show ShrineHome; -import 'shrine/shrine_theme.dart' show ShrineTheme; - -// This code would ordinarily be part of the MaterialApp's home. It's being -// used by the ShrineDemo and by each route pushed from there because this -// isn't a standalone app with its own main() and MaterialApp. -Widget buildShrine(BuildContext context, Widget child) { - return Theme( - data: ThemeData( - primarySwatch: Colors.grey, - iconTheme: const IconThemeData(color: Color(0xFF707070)), - platform: Theme.of(context).platform, - ), - child: ShrineTheme(child: child)); -} - -// In a standalone version of this app, MaterialPageRoute could be used directly. -class ShrinePageRoute extends MaterialPageRoute { - ShrinePageRoute({ - WidgetBuilder builder, - RouteSettings settings, - }) : super(builder: builder, settings: settings); - - @override - Widget buildPage(BuildContext context, Animation animation, - Animation secondaryAnimation) { - return buildShrine( - context, super.buildPage(context, animation, secondaryAnimation)); - } -} +import 'package:flutter/material.dart'; +import 'package:flutter_gallery/demo/shrine/app.dart'; class ShrineDemo extends StatelessWidget { + const ShrineDemo({ Key key }) : super(key: key); + static const String routeName = '/shrine'; // Used by the Gallery app. @override - Widget build(BuildContext context) => buildShrine(context, ShrineHome()); + Widget build(BuildContext context) => ShrineApp(); } diff --git a/web/gallery/lib/demo/transformations/transformations_demo.dart b/web/gallery/lib/demo/transformations/transformations_demo.dart new file mode 100644 index 000000000..4a5bf9933 --- /dev/null +++ b/web/gallery/lib/demo/transformations/transformations_demo.dart @@ -0,0 +1,190 @@ +import 'dart:ui' show Vertices; +import 'package:flutter/material.dart'; +import 'transformations_demo_board.dart'; +import 'transformations_demo_edit_board_point.dart'; +import 'transformations_demo_gesture_transformable.dart'; + +class TransformationsDemo extends StatefulWidget { + const TransformationsDemo({ Key key }) : super(key: key); + + static const String routeName = '/transformations'; + + @override _TransformationsDemoState createState() => _TransformationsDemoState(); +} +class _TransformationsDemoState extends State { + // The radius of a hexagon tile in pixels. + static const double _kHexagonRadius = 32.0; + // The margin between hexagons. + static const double _kHexagonMargin = 1.0; + // The radius of the entire board in hexagons, not including the center. + static const int _kBoardRadius = 8; + + bool _reset = false; + Board _board = Board( + boardRadius: _kBoardRadius, + hexagonRadius: _kHexagonRadius, + hexagonMargin: _kHexagonMargin, + ); + + @override + Widget build (BuildContext context) { + final BoardPainter painter = BoardPainter( + board: _board, + ); + + // The scene is drawn by a CustomPaint, but user interaction is handled by + // the GestureTransformable parent widget. + return Scaffold( + appBar: AppBar( + title: const Text('2D Tranformations'), + actions: [ + IconButton( + icon: const Icon(Icons.help), + tooltip: 'Help', + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => instructionDialog, + ); + }, + ), + ], + ), + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // Draw the scene as big as is available, but allow the user to + // translate beyond that to a visibleSize that's a bit bigger. + final Size size = Size(constraints.maxWidth, constraints.maxHeight); + final Size visibleSize = Size(size.width * 3, size.height * 2); + return GestureTransformable( + reset: _reset, + onResetEnd: () { + setState(() { + _reset = false; + }); + }, + child: CustomPaint( + painter: painter, + ), + boundaryRect: Rect.fromLTWH( + -visibleSize.width / 2, + -visibleSize.height / 2, + visibleSize.width, + visibleSize.height, + ), + // Center the board in the middle of the screen. It's drawn centered + // at the origin, which is the top left corner of the + // GestureTransformable. + initialTranslation: Offset(size.width / 2, size.height / 2), + onTapUp: _onTapUp, + size: size, + ); + }, + ), + floatingActionButton: _board.selected == null ? resetButton : editButton, + ); + } + + Widget get instructionDialog { + return AlertDialog( + title: const Text('2D Transformations'), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: const [ + Text('Tap to edit hex tiles, and use gestures to move around the scene:\n'), + Text('- Drag to pan.'), + Text('- Pinch to zoom.'), + Text('- Rotate with two fingers.'), + Text('\nYou can always press the home button to return to the starting orientation!'), + ], + ), + actions: [ + FlatButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } + + FloatingActionButton get resetButton { + return FloatingActionButton( + onPressed: () { + setState(() { + _reset = true; + }); + }, + tooltip: 'Reset Transform', + backgroundColor: Theme.of(context).primaryColor, + child: const Icon(Icons.home), + ); + } + + FloatingActionButton get editButton { + return FloatingActionButton( + onPressed: () { + if (_board.selected == null) { + return; + } + showModalBottomSheet(context: context, builder: (BuildContext context) { + return Container( + width: double.infinity, + height: 150, + padding: const EdgeInsets.all(12.0), + child: EditBoardPoint( + boardPoint: _board.selected, + onColorSelection: (Color color) { + setState(() { + _board = _board.copyWithBoardPointColor(_board.selected, color); + Navigator.pop(context); + }); + }, + ), + ); + }); + }, + tooltip: 'Edit Tile', + child: const Icon(Icons.edit), + ); + } + + void _onTapUp(TapUpDetails details) { + final Offset scenePoint = details.globalPosition; + final BoardPoint boardPoint = _board.pointToBoardPoint(scenePoint); + setState(() { + _board = _board.copyWithSelected(boardPoint); + }); + } +} + +// CustomPainter is what is passed to CustomPaint and actually draws the scene +// when its `paint` method is called. +class BoardPainter extends CustomPainter { + const BoardPainter({ + this.board, + }); + + final Board board; + + @override + void paint(Canvas canvas, Size size) { + void drawBoardPoint(BoardPoint boardPoint) { + final Color color = boardPoint.color.withOpacity( + board.selected == boardPoint ? 0.2 : 1.0, + ); + final Vertices vertices = board.getVerticesForBoardPoint(boardPoint, color); + canvas.drawVertices(vertices, BlendMode.color, Paint()); + } + + board.forEach(drawBoardPoint); + } + + // We should repaint whenever the board changes, such as board.selected. + @override + bool shouldRepaint(BoardPainter oldDelegate) { + return oldDelegate.board != board; + } +} diff --git a/web/gallery/lib/demo/transformations/transformations_demo_board.dart b/web/gallery/lib/demo/transformations/transformations_demo_board.dart new file mode 100644 index 000000000..bf74076d4 --- /dev/null +++ b/web/gallery/lib/demo/transformations/transformations_demo_board.dart @@ -0,0 +1,286 @@ +import 'dart:collection' show IterableMixin; +import 'dart:math'; +import 'dart:ui' show Vertices; +import 'package:flutter/material.dart' hide Gradient; +import 'package:vector_math/vector_math_64.dart' show Vector3; + +// The entire state of the hex board and abstraction to get information about +// it. Iterable so that all BoardPoints on the board can be iterated over. +@immutable +class Board extends Object with IterableMixin { + Board({ + @required this.boardRadius, + @required this.hexagonRadius, + @required this.hexagonMargin, + this.selected, + List boardPoints, + }) : assert(boardRadius > 0), + assert(hexagonRadius > 0), + assert(hexagonMargin >= 0) { + // Set up the positions for the center hexagon where the entire board is + // centered on the origin. + // Start point of hexagon (top vertex). + final Point hexStart = Point(0, -hexagonRadius); + final double hexagonRadiusPadded = hexagonRadius - hexagonMargin; + final double centerToFlat = sqrt(3) / 2 * hexagonRadiusPadded; + positionsForHexagonAtOrigin.addAll([ + Offset(hexStart.x, hexStart.y), + Offset(hexStart.x + centerToFlat, hexStart.y + 0.5 * hexagonRadiusPadded), + Offset(hexStart.x + centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded), + Offset(hexStart.x + centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded), + Offset(hexStart.x, hexStart.y + 2 * hexagonRadiusPadded), + Offset(hexStart.x, hexStart.y + 2 * hexagonRadiusPadded), + Offset(hexStart.x - centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded), + Offset(hexStart.x - centerToFlat, hexStart.y + 1.5 * hexagonRadiusPadded), + Offset(hexStart.x - centerToFlat, hexStart.y + 0.5 * hexagonRadiusPadded), + ]); + + if (boardPoints != null) { + _boardPoints.addAll(boardPoints); + } else { + // Generate boardPoints for a fresh board. + BoardPoint boardPoint = _getNextBoardPoint(null); + while (boardPoint != null) { + _boardPoints.add(boardPoint); + boardPoint = _getNextBoardPoint(boardPoint); + } + } + } + + final int boardRadius; // Number of hexagons from center to edge. + final double hexagonRadius; // Pixel radius of a hexagon (center to vertex). + final double hexagonMargin; // Margin between hexagons. + final List positionsForHexagonAtOrigin = []; + final BoardPoint selected; + final List _boardPoints = []; + + @override + Iterator get iterator => _BoardIterator(_boardPoints); + + // For a given q axial coordinate, get the range of possible r values + // See the definition of BoardPoint for more information about hex grids and + // axial coordinates. + _Range _getRRangeForQ(int q) { + int rStart; + int rEnd; + if (q <= 0) { + rStart = -boardRadius - q; + rEnd = boardRadius; + } else { + rEnd = boardRadius - q; + rStart = -boardRadius; + } + + return _Range(rStart, rEnd); + } + + // Get the BoardPoint that comes after the given BoardPoint. If given null, + // returns the origin BoardPoint. If given BoardPoint is the last, returns + // null. + BoardPoint _getNextBoardPoint (BoardPoint boardPoint) { + // If before the first element. + if (boardPoint == null) { + return BoardPoint(-boardRadius, 0); + } + + final _Range rRange = _getRRangeForQ(boardPoint.q); + + // If at or after the last element. + if (boardPoint.q >= boardRadius && boardPoint.r >= rRange.max) { + return null; + } + + // If wrapping from one q to the next. + if (boardPoint.r >= rRange.max) { + return BoardPoint(boardPoint.q + 1, _getRRangeForQ(boardPoint.q + 1).min); + } + + // Otherwise we're just incrementing r. + return BoardPoint(boardPoint.q, boardPoint.r + 1); + } + + // Check if the board point is actually on the board. + bool _validateBoardPoint(BoardPoint boardPoint) { + const BoardPoint center = BoardPoint(0, 0); + final int distanceFromCenter = getDistance(center, boardPoint); + return distanceFromCenter <= boardRadius; + } + + // Get the distance between two BoardPoins. + static int getDistance(BoardPoint a, BoardPoint b) { + final Vector3 a3 = a.cubeCoordinates; + final Vector3 b3 = b.cubeCoordinates; + return + ((a3.x - b3.x).abs() + (a3.y - b3.y).abs() + (a3.z - b3.z).abs()) ~/ 2; + } + + // Return the q,r BoardPoint for a point in the scene, where the origin is in + // the center of the board in both coordinate systems. If no BoardPoint at the + // location, return null. + BoardPoint pointToBoardPoint(Offset point) { + final BoardPoint boardPoint = BoardPoint( + ((sqrt(3) / 3 * point.dx - 1 / 3 * point.dy) / hexagonRadius).round(), + ((2 / 3 * point.dy) / hexagonRadius).round(), + ); + + if (!_validateBoardPoint(boardPoint)) { + return null; + } + + return _boardPoints.firstWhere((BoardPoint boardPointI) { + return boardPointI.q == boardPoint.q && boardPointI.r == boardPoint.r; + }); + } + + // Return a scene point for the center of a hexagon given its q,r point. + Point boardPointToPoint(BoardPoint boardPoint) { + return Point( + sqrt(3) * hexagonRadius * boardPoint.q + sqrt(3) / 2 * hexagonRadius * boardPoint.r, + 1.5 * hexagonRadius * boardPoint.r, + ); + } + + // Get Vertices that can be drawn to a Canvas for the given BoardPoint. + Vertices getVerticesForBoardPoint(BoardPoint boardPoint, Color color) { + final Point centerOfHexZeroCenter = boardPointToPoint(boardPoint); + + final List positions = positionsForHexagonAtOrigin.map((Offset offset) { + return offset.translate(centerOfHexZeroCenter.x, centerOfHexZeroCenter.y); + }).toList(); + + return Vertices( + VertexMode.triangleFan, + positions, + colors: List.filled(positions.length, color), + ); + } + + // Return a new board with the given BoardPoint selected. + Board copyWithSelected(BoardPoint boardPoint) { + if (selected == boardPoint) { + return this; + } + final Board nextBoard = Board( + boardRadius: boardRadius, + hexagonRadius: hexagonRadius, + hexagonMargin: hexagonMargin, + selected: boardPoint, + boardPoints: _boardPoints, + ); + return nextBoard; + } + + // Return a new board where boardPoint has the given color. + Board copyWithBoardPointColor(BoardPoint boardPoint, Color color) { + final BoardPoint nextBoardPoint = boardPoint.copyWithColor(color); + final int boardPointIndex = _boardPoints.indexWhere((BoardPoint boardPointI) => + boardPointI.q == boardPoint.q && boardPointI.r == boardPoint.r + ); + + if (elementAt(boardPointIndex) == boardPoint && boardPoint.color == color) { + return this; + } + + final List nextBoardPoints = List.from(_boardPoints); + nextBoardPoints[boardPointIndex] = nextBoardPoint; + final BoardPoint selectedBoardPoint = boardPoint == selected + ? nextBoardPoint + : selected; + return Board( + boardRadius: boardRadius, + hexagonRadius: hexagonRadius, + hexagonMargin: hexagonMargin, + selected: selectedBoardPoint, + boardPoints: nextBoardPoints, + ); + } +} + +class _BoardIterator extends Iterator { + _BoardIterator(this.boardPoints); + + final List boardPoints; + int currentIndex; + + @override + BoardPoint current; + + @override + bool moveNext() { + if (currentIndex == null) { + currentIndex = 0; + } else { + currentIndex++; + } + + if (currentIndex >= boardPoints.length) { + current = null; + return false; + } + + current = boardPoints[currentIndex]; + return true; + } +} + +// A range of q/r board coordinate values. +@immutable +class _Range { + const _Range(this.min, this.max) + : assert(min != null), + assert(max != null), + assert(min <= max); + + final int min; + final int max; +} + +final Set boardPointColors = { + Colors.grey, + Colors.black, + Colors.red, + Colors.blue, +}; + +// A location on the board in axial coordinates. +// Axial coordinates use two integers, q and r, to locate a hexagon on a grid. +// https://www.redblobgames.com/grids/hexagons/#coordinates-axial +@immutable +class BoardPoint { + const BoardPoint(this.q, this.r, { + this.color = Colors.grey, + }); + + final int q; + final int r; + final Color color; + + @override + String toString() { + return 'BoardPoint($q, $r, $color)'; + } + + // Only compares by location. + @override + bool operator ==(dynamic other) { + if (other.runtimeType != runtimeType) { + return false; + } + final BoardPoint boardPoint = other; + return boardPoint.q == q && boardPoint.r == r; + } + + @override + int get hashCode => hashValues(q, r); + + BoardPoint copyWithColor(Color nextColor) => BoardPoint(q, r, color: nextColor); + + // Convert from q,r axial coords to x,y,z cube coords. + Vector3 get cubeCoordinates { + return Vector3( + q.toDouble(), + r.toDouble(), + (-q - r).toDouble(), + ); + } +} diff --git a/web/gallery/lib/demo/transformations/transformations_demo_color_picker.dart b/web/gallery/lib/demo/transformations/transformations_demo_color_picker.dart new file mode 100644 index 000000000..ebcaa0d3d --- /dev/null +++ b/web/gallery/lib/demo/transformations/transformations_demo_color_picker.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +// A generic widget for a list of selectable colors. +@immutable +class ColorPicker extends StatelessWidget { + const ColorPicker({ + @required this.colors, + @required this.selectedColor, + this.onColorSelection, + }) : assert(colors != null), + assert(selectedColor != null); + + final Set colors; + final Color selectedColor; + final ValueChanged onColorSelection; + + @override + Widget build (BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: colors.map((Color color) { + return _ColorPickerSwatch( + color: color, + selected: color == selectedColor, + onTap: () { + if (onColorSelection != null) { + onColorSelection(color); + } + }, + ); + }).toList(), + ); + } +} + +// A single selectable color widget in the ColorPicker. +@immutable +class _ColorPickerSwatch extends StatelessWidget { + const _ColorPickerSwatch({ + @required this.color, + @required this.selected, + this.onTap, + }) : assert(color != null), + assert(selected != null); + + final Color color; + final bool selected; + final Function onTap; + + @override + Widget build (BuildContext context) { + return Container( + width: 60.0, + height: 60.0, + padding: const EdgeInsets.fromLTRB(2.0, 0.0, 2.0, 0.0), + child: RawMaterialButton( + fillColor: color, + onPressed: () { + if (onTap != null) { + onTap(); + } + }, + child: !selected ? null : const Icon( + Icons.check, + color: Colors.white, + ), + ), + ); + } +} diff --git a/web/gallery/lib/demo/transformations/transformations_demo_edit_board_point.dart b/web/gallery/lib/demo/transformations/transformations_demo_edit_board_point.dart new file mode 100644 index 000000000..160054b05 --- /dev/null +++ b/web/gallery/lib/demo/transformations/transformations_demo_edit_board_point.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'transformations_demo_board.dart'; +import 'transformations_demo_color_picker.dart'; + +// The panel for editing a board point. +@immutable +class EditBoardPoint extends StatelessWidget { + const EditBoardPoint({ + Key key, + @required this.boardPoint, + this.onColorSelection, + }) : assert(boardPoint != null), + super(key: key); + + final BoardPoint boardPoint; + final ValueChanged onColorSelection; + + @override + Widget build (BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + '${boardPoint.q}, ${boardPoint.r}', + textAlign: TextAlign.right, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ColorPicker( + colors: boardPointColors, + selectedColor: boardPoint.color, + onColorSelection: onColorSelection, + ), + ], + ); + } +} diff --git a/web/gallery/lib/demo/transformations/transformations_demo_gesture_transformable.dart b/web/gallery/lib/demo/transformations/transformations_demo_gesture_transformable.dart new file mode 100644 index 000000000..c8bd21d1b --- /dev/null +++ b/web/gallery/lib/demo/transformations/transformations_demo_gesture_transformable.dart @@ -0,0 +1,571 @@ +import 'package:flutter/material.dart'; +import 'package:vector_math/vector_math_64.dart' show Vector3; +import 'transformations_demo_inertial_motion.dart'; + +// This widget allows 2D transform interactions on its child in relation to its +// parent. The user can transform the child by dragging to pan or pinching to +// zoom and rotate. All event callbacks for GestureDetector are supported, and +// the coordinates that are given are untransformed and in relation to the +// original position of the child. +@immutable +class GestureTransformable extends StatefulWidget { + const GestureTransformable({ + Key key, + // The child to perform the transformations on. + @required this.child, + // The desired visible size of the widget and the area that is receptive to + // gestures. If a widget that's as big as possible is desired, then wrap + // this in a LayoutBuilder and pass + // `Size(constraints.maxWidth, constraints.maxHeight)`. + @required this.size, + // The scale will be clamped to between these values. A maxScale of null has + // no bounds. minScale must be greater than zero. + this.maxScale = 2.5, + this.minScale = 0.8, + // Transforms will be limited so that the viewport can not view beyond this + // Rect. The Rect does not rotate with the rest of the scene, so it is + // always aligned with the viewport. A null boundaryRect results in no + // limits to the distance that the viewport can be transformed to see. + this.boundaryRect, + // Initial values for the transform can be provided. + this.initialTranslation, + this.initialScale, + this.initialRotation, + // Any and all of the possible transformations can be disabled. + this.disableTranslation = false, + this.disableScale = false, + this.disableRotation = false, + // If set to true, this widget will animate back to its initial transform + // and call onResetEnd when done. When utilizing reset, onResetEnd should + // also be implemented, and it should set reset to false when called. + this.reset = false, + // Access to event callbacks from GestureDetector. Called with untransformed + // coordinates in an Offset. + this.onTapDown, + this.onTapUp, + this.onTap, + this.onTapCancel, + this.onDoubleTap, + this.onLongPress, + this.onLongPressUp, + this.onVerticalDragDown, + this.onVerticalDragStart, + this.onVerticalDragUpdate, + this.onVerticalDragEnd, + this.onVerticalDragCancel, + this.onHorizontalDragDown, + this.onHorizontalDragStart, + this.onHorizontalDragUpdate, + this.onHorizontalDragEnd, + this.onHorizontalDragCancel, + this.onPanDown, + this.onPanStart, + this.onPanUpdate, + this.onPanEnd, + this.onPanCancel, + this.onResetEnd, + this.onScaleStart, + this.onScaleUpdate, + this.onScaleEnd, + }) : assert(child != null), + assert(size != null), + assert(minScale != null), + assert(minScale > 0), + assert(disableTranslation != null), + assert(disableScale != null), + assert(disableRotation != null), + assert(reset != null), + assert( + !reset || onResetEnd != null, + 'Must implement onResetEnd to use reset.', + ), + super(key: key); + + final Widget child; + final Size size; + final bool reset; + final GestureTapDownCallback onTapDown; + final GestureTapUpCallback onTapUp; + final GestureTapCallback onTap; + final GestureTapCancelCallback onTapCancel; + final GestureTapCallback onDoubleTap; + final GestureLongPressCallback onLongPress; + final GestureLongPressUpCallback onLongPressUp; + final GestureDragDownCallback onVerticalDragDown; + final GestureDragStartCallback onVerticalDragStart; + final GestureDragUpdateCallback onVerticalDragUpdate; + final GestureDragEndCallback onVerticalDragEnd; + final GestureDragCancelCallback onVerticalDragCancel; + final GestureDragDownCallback onHorizontalDragDown; + final GestureDragStartCallback onHorizontalDragStart; + final GestureDragUpdateCallback onHorizontalDragUpdate; + final GestureDragEndCallback onHorizontalDragEnd; + final GestureDragCancelCallback onHorizontalDragCancel; + final GestureDragDownCallback onPanDown; + final GestureDragStartCallback onPanStart; + final GestureDragUpdateCallback onPanUpdate; + final GestureDragEndCallback onPanEnd; + final GestureDragCancelCallback onPanCancel; + final VoidCallback onResetEnd; + final GestureScaleStartCallback onScaleStart; + final GestureScaleUpdateCallback onScaleUpdate; + final GestureScaleEndCallback onScaleEnd; + final double maxScale; + final double minScale; + final Rect boundaryRect; + final bool disableTranslation; + final bool disableScale; + final bool disableRotation; + final Offset initialTranslation; + final double initialScale; + final double initialRotation; + + @override _GestureTransformableState createState() => _GestureTransformableState(); +} + +// A single user event can only represent one of these gestures. The user can't +// do multiple at the same time, which results in more precise transformations. +enum _GestureType { + translate, + scale, + rotate, +} + +// This is public only for access from a unit test. +class _GestureTransformableState extends State with TickerProviderStateMixin { + Animation _animation; + AnimationController _controller; + Animation _animationReset; + AnimationController _controllerReset; + // The translation that will be applied to the scene (not viewport). + // A positive x offset moves the scene right, viewport left. + // A positive y offset moves the scene down, viewport up. + Offset _translateFromScene; // Point where a single translation began. + double _scaleStart; // Scale value at start of scaling gesture. + double _rotationStart = 0.0; // Rotation at start of rotation gesture. + Rect _boundaryRect; + Matrix4 _transform = Matrix4.identity(); + double _currentRotation = 0.0; + _GestureType gestureType; + + // The transformation matrix that gives the initial home position. + Matrix4 get _initialTransform { + Matrix4 matrix = Matrix4.identity(); + if (widget.initialTranslation != null) { + matrix = matrixTranslate(matrix, widget.initialTranslation); + } + if (widget.initialScale != null) { + matrix = matrixScale(matrix, widget.initialScale); + } + if (widget.initialRotation != null) { + matrix = matrixRotate(matrix, widget.initialRotation, Offset.zero); + } + return matrix; + } + + // Return the scene point at the given viewport point. + static Offset fromViewport(Offset viewportPoint, Matrix4 transform) { + // On viewportPoint, perform the inverse transformation of the scene to get + // where the point would be in the scene before the transformation. + final Matrix4 inverseMatrix = Matrix4.inverted(transform); + final Vector3 untransformed = inverseMatrix.transform3(Vector3( + viewportPoint.dx, + viewportPoint.dy, + 0, + )); + return Offset(untransformed.x, untransformed.y); + } + + // Get the offset of the current widget from the global screen coordinates. + // TODO(justinmc): Protect against calling this during first build. + static Offset getOffset(BuildContext context) { + final RenderBox renderObject = context.findRenderObject(); + return renderObject.localToGlobal(Offset.zero); + } + + @override + void initState() { + super.initState(); + _boundaryRect = widget.boundaryRect ?? Offset.zero & widget.size; + _transform = _initialTransform; + _controller = AnimationController( + vsync: this, + ); + _controllerReset = AnimationController( + vsync: this, + ); + if (widget.reset) { + _animateResetInitialize(); + } + } + + @override + void didUpdateWidget(GestureTransformable oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.reset && !oldWidget.reset && _animationReset == null) { + _animateResetInitialize(); + } else if (!widget.reset && oldWidget.reset && _animationReset != null) { + _animateResetStop(); + } + } + + @override + Widget build(BuildContext context) { + // A GestureDetector allows the detection of panning and zooming gestures on + // its child, which is the CustomPaint. + return GestureDetector( + behavior: HitTestBehavior.opaque, // Necessary when translating off screen + onTapDown: widget.onTapDown == null ? null : (TapDownDetails details) { + widget.onTapDown(TapDownDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onTapUp: widget.onTapUp == null ? null : (TapUpDetails details) { + widget.onTapUp(TapUpDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onTap: widget.onTap, + onTapCancel: widget.onTapCancel, + onDoubleTap: widget.onDoubleTap, + onLongPress: widget.onLongPress, + onLongPressUp: widget.onLongPressUp, + onVerticalDragDown: widget.onVerticalDragDown == null ? null : (DragDownDetails details) { + widget.onVerticalDragDown(DragDownDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onVerticalDragStart: widget.onVerticalDragStart == null ? null : (DragStartDetails details) { + widget.onVerticalDragStart(DragStartDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onVerticalDragUpdate: widget.onVerticalDragUpdate == null ? null : (DragUpdateDetails details) { + widget.onVerticalDragUpdate(DragUpdateDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onVerticalDragEnd: widget.onVerticalDragEnd, + onVerticalDragCancel: widget.onVerticalDragCancel, + onHorizontalDragDown: widget.onHorizontalDragDown == null ? null : (DragDownDetails details) { + widget.onHorizontalDragDown(DragDownDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onHorizontalDragStart: widget.onHorizontalDragStart == null ? null : (DragStartDetails details) { + widget.onHorizontalDragStart(DragStartDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onHorizontalDragUpdate: widget.onHorizontalDragUpdate == null ? null : (DragUpdateDetails details) { + widget.onHorizontalDragUpdate(DragUpdateDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onHorizontalDragEnd: widget.onHorizontalDragEnd, + onHorizontalDragCancel: widget.onHorizontalDragCancel, + onPanDown: widget.onPanDown == null ? null : (DragDownDetails details) { + widget.onPanDown(DragDownDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onPanStart: widget.onPanStart == null ? null : (DragStartDetails details) { + widget.onPanStart(DragStartDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onPanUpdate: widget.onPanUpdate == null ? null : (DragUpdateDetails details) { + widget.onPanUpdate(DragUpdateDetails( + globalPosition: fromViewport(details.globalPosition - getOffset(context), _transform), + )); + }, + onPanEnd: widget.onPanEnd, + onPanCancel: widget.onPanCancel, + onScaleEnd: _onScaleEnd, + onScaleStart: _onScaleStart, + onScaleUpdate: _onScaleUpdate, + child: ClipRect( + // The scene is panned/zoomed/rotated using this Transform widget. + child: Transform( + transform: _transform, + child: Container( + child: widget.child, + height: widget.size.height, + width: widget.size.width, + ), + ), + ), + ); + } + + // Return a new matrix representing the given matrix after applying the given + // translation. + Matrix4 matrixTranslate(Matrix4 matrix, Offset translation) { + if (widget.disableTranslation || translation == Offset.zero) { + return matrix; + } + + // Clamp translation so the viewport remains inside _boundaryRect. + final double scale = _transform.getMaxScaleOnAxis(); + final Size scaledSize = widget.size / scale; + final Rect viewportBoundaries = Rect.fromLTRB( + _boundaryRect.left, + _boundaryRect.top, + _boundaryRect.right - scaledSize.width, + _boundaryRect.bottom - scaledSize.height, + ); + // Translation is reversed (a positive translation moves the scene to the + // right, viewport to the left). + final Rect translationBoundaries = Rect.fromLTRB( + -scale * viewportBoundaries.right, + -scale * viewportBoundaries.bottom, + -scale * viewportBoundaries.left, + -scale * viewportBoundaries.top, + ); + final Matrix4 nextMatrix = matrix.clone()..translate( + translation.dx, + translation.dy, + ); + final Vector3 nextTranslationVector = nextMatrix.getTranslation(); + final Offset nextTranslation = Offset( + nextTranslationVector.x, + nextTranslationVector.y, + ); + final bool inBoundaries = translationBoundaries.contains( + Offset(nextTranslation.dx, nextTranslation.dy), + ); + if (!inBoundaries) { + // TODO(justinmc): Instead of canceling translation when it goes out of + // bounds, stop translation at boundary. + return matrix; + } + + return nextMatrix; + } + + // Return a new matrix representing the given matrix after applying the given + // scale transform. + Matrix4 matrixScale(Matrix4 matrix, double scale) { + if (widget.disableScale || scale == 1) { + return matrix; + } + assert(scale != 0); + + // Don't allow a scale that moves the viewport outside of _boundaryRect. + final Offset tl = fromViewport(const Offset(0, 0), _transform); + final Offset tr = fromViewport(Offset(widget.size.width, 0), _transform); + final Offset bl = fromViewport(Offset(0, widget.size.height), _transform); + final Offset br = fromViewport( + Offset(widget.size.width, widget.size.height), + _transform, + ); + if (!_boundaryRect.contains(tl) + || !_boundaryRect.contains(tr) + || !_boundaryRect.contains(bl) + || !_boundaryRect.contains(br)) { + return matrix; + } + + // Don't allow a scale that results in an overall scale beyond min/max + // scale. + final double currentScale = _transform.getMaxScaleOnAxis(); + final double totalScale = currentScale * scale; + final double clampedTotalScale = totalScale.clamp( + widget.minScale, + widget.maxScale, + ); + final double clampedScale = clampedTotalScale / currentScale; + return matrix..scale(clampedScale); + } + + // Return a new matrix representing the given matrix after applying the given + // rotation transform. + // Rotating the scene cannot cause the viewport to view beyond _boundaryRect. + Matrix4 matrixRotate(Matrix4 matrix, double rotation, Offset focalPoint) { + if (widget.disableRotation || rotation == 0) { + return matrix; + } + final Offset focalPointScene = fromViewport(focalPoint, matrix); + return matrix + ..translate(focalPointScene.dx, focalPointScene.dy) + ..rotateZ(-rotation) + ..translate(-focalPointScene.dx, -focalPointScene.dy); + } + + // Handle the start of a gesture of _GestureType. + void _onScaleStart(ScaleStartDetails details) { + if (widget.onScaleStart != null) { + widget.onScaleStart(details); + } + + if (_controller.isAnimating) { + _controller.stop(); + _controller.reset(); + _animation?.removeListener(_onAnimate); + _animation = null; + } + if (_controllerReset.isAnimating) { + _animateResetStop(); + } + + gestureType = null; + setState(() { + _scaleStart = _transform.getMaxScaleOnAxis(); + _translateFromScene = fromViewport(details.focalPoint, _transform); + _rotationStart = _currentRotation; + }); + } + + // Handle an update to an ongoing gesture of _GestureType. + void _onScaleUpdate(ScaleUpdateDetails details) { + double scale = _transform.getMaxScaleOnAxis(); + if (widget.onScaleUpdate != null) { + widget.onScaleUpdate(ScaleUpdateDetails( + focalPoint: fromViewport(details.focalPoint, _transform), + scale: details.scale, + rotation: details.rotation, + )); + } + final Offset focalPointScene = fromViewport( + details.focalPoint, + _transform, + ); + if (gestureType == null) { + // Decide which type of gesture this is by comparing the amount of scale + // and rotation in the gesture, if any. Scale starts at 1 and rotation + // starts at 0. Translate will have 0 scale and 0 rotation because it uses + // only one finger. + if ((details.scale - 1).abs() > details.rotation.abs()) { + gestureType = _GestureType.scale; + } else if (details.rotation != 0) { + gestureType = _GestureType.rotate; + } else { + gestureType = _GestureType.translate; + } + } + setState(() { + if (gestureType == _GestureType.scale && _scaleStart != null) { + // details.scale gives us the amount to change the scale as of the + // start of this gesture, so calculate the amount to scale as of the + // previous call to _onScaleUpdate. + final double desiredScale = _scaleStart * details.scale; + final double scaleChange = desiredScale / scale; + _transform = matrixScale(_transform, scaleChange); + scale = _transform.getMaxScaleOnAxis(); + + // While scaling, translate such that the user's two fingers stay on the + // same places in the scene. That means that the focal point of the + // scale should be on the same place in the scene before and after the + // scale. + final Offset focalPointSceneNext = fromViewport( + details.focalPoint, + _transform, + ); + _transform = matrixTranslate(_transform, focalPointSceneNext - focalPointScene); + } else if (gestureType == _GestureType.rotate && details.rotation != 0.0) { + final double desiredRotation = _rotationStart + details.rotation; + _transform = matrixRotate(_transform, _currentRotation - desiredRotation, details.focalPoint); + _currentRotation = desiredRotation; + } else if (_translateFromScene != null && details.scale == 1.0) { + // Translate so that the same point in the scene is underneath the + // focal point before and after the movement. + final Offset translationChange = focalPointScene - _translateFromScene; + _transform = matrixTranslate(_transform, translationChange); + _translateFromScene = fromViewport(details.focalPoint, _transform); + } + }); + } + + // Handle the end of a gesture of _GestureType. + void _onScaleEnd(ScaleEndDetails details) { + if (widget.onScaleEnd != null) { + widget.onScaleEnd(details); + } + setState(() { + _scaleStart = null; + _rotationStart = null; + _translateFromScene = null; + }); + + _animation?.removeListener(_onAnimate); + _controller.reset(); + + // If the scale ended with velocity, animate inertial movement + final double velocityTotal = details.velocity.pixelsPerSecond.dx.abs() + + details.velocity.pixelsPerSecond.dy.abs(); + if (velocityTotal == 0) { + return; + } + + final Vector3 translationVector = _transform.getTranslation(); + final Offset translation = Offset(translationVector.x, translationVector.y); + final InertialMotion inertialMotion = InertialMotion(details.velocity, translation); + _animation = Tween( + begin: translation, + end: inertialMotion.finalPosition, + ).animate(_controller); + _controller.duration = Duration(milliseconds: inertialMotion.duration.toInt()); + _animation.addListener(_onAnimate); + _controller.fling(); + } + + // Handle inertia drag animation. + void _onAnimate() { + setState(() { + // Translate _transform such that the resulting translation is + // _animation.value. + final Vector3 translationVector = _transform.getTranslation(); + final Offset translation = Offset(translationVector.x, translationVector.y); + final Offset translationScene = fromViewport(translation, _transform); + final Offset animationScene = fromViewport(_animation.value, _transform); + final Offset translationChangeScene = animationScene - translationScene; + _transform = matrixTranslate(_transform, translationChangeScene); + }); + if (!_controller.isAnimating) { + _animation?.removeListener(_onAnimate); + _animation = null; + _controller.reset(); + } + } + + // Handle reset to home transform animation. + void _onAnimateReset() { + setState(() { + _transform = _animationReset.value; + }); + if (!_controllerReset.isAnimating) { + _animationReset?.removeListener(_onAnimateReset); + _animationReset = null; + _controllerReset.reset(); + widget.onResetEnd(); + } + } + + // Initialize the reset to home transform animation. + void _animateResetInitialize() { + _controllerReset.reset(); + _animationReset = Matrix4Tween( + begin: _transform, + end: _initialTransform, + ).animate(_controllerReset); + _controllerReset.duration = const Duration(milliseconds: 400); + _animationReset.addListener(_onAnimateReset); + _controllerReset.forward(); + } + + // Stop a running reset to home transform animation. + void _animateResetStop() { + _controllerReset.stop(); + _animationReset?.removeListener(_onAnimateReset); + _animationReset = null; + _controllerReset.reset(); + widget.onResetEnd(); + } + + @override + void dispose() { + _controller.dispose(); + _controllerReset.dispose(); + super.dispose(); + } +} diff --git a/web/gallery/lib/demo/transformations/transformations_demo_inertial_motion.dart b/web/gallery/lib/demo/transformations/transformations_demo_inertial_motion.dart new file mode 100644 index 000000000..8e45c00b2 --- /dev/null +++ b/web/gallery/lib/demo/transformations/transformations_demo_inertial_motion.dart @@ -0,0 +1,68 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:vector_math/vector_math.dart' show Vector2; + +// Provides calculations for an object moving with inertia and friction using +// the equation of motion from physics. +// https://en.wikipedia.org/wiki/Equations_of_motion#Constant_translational_acceleration_in_a_straight_line +// TODO(justinmc): Can this be replaced with friction_simulation.dart? +@immutable +class InertialMotion { + const InertialMotion(this._initialVelocity, this._initialPosition); + + static const double _kFrictionalAcceleration = 0.01; // How quickly to stop + final Velocity _initialVelocity; + final Offset _initialPosition; + + // The position when the motion stops. + Offset get finalPosition { + return _getPositionAt(Duration(milliseconds: duration.toInt())); + } + + // The total time that the animation takes start to stop in milliseconds. + double get duration { + return (_initialVelocity.pixelsPerSecond.dx / 1000 / _acceleration.x).abs(); + } + + // The acceleration opposing the initial velocity in x and y components. + Vector2 get _acceleration { + // TODO(justinmc): Find actual velocity instead of summing? + final double velocityTotal = _initialVelocity.pixelsPerSecond.dx.abs() + + _initialVelocity.pixelsPerSecond.dy.abs(); + final double vRatioX = _initialVelocity.pixelsPerSecond.dx / velocityTotal; + final double vRatioY = _initialVelocity.pixelsPerSecond.dy / velocityTotal; + return Vector2( + _kFrictionalAcceleration * vRatioX, + _kFrictionalAcceleration * vRatioY, + ); + } + + // The position at a given time. + Offset _getPositionAt(Duration time) { + final double xf = _getPosition( + r0: _initialPosition.dx, + v0: _initialVelocity.pixelsPerSecond.dx / 1000, + t: time.inMilliseconds, + a: _acceleration.x, + ); + final double yf = _getPosition( + r0: _initialPosition.dy, + v0: _initialVelocity.pixelsPerSecond.dy / 1000, + t: time.inMilliseconds, + a: _acceleration.y, + ); + return Offset(xf, yf); + } + + // Solve the equation of motion to find the position at a given point in time + // in one dimension. + double _getPosition({double r0, double v0, int t, double a}) { + // Stop movement when it would otherwise reverse direction. + final double stopTime = (v0 / a).abs(); + if (t > stopTime) { + t = stopTime.toInt(); + } + + return r0 + v0 * t + 0.5 * a * pow(t, 2); + } +} diff --git a/web/gallery/lib/demo/typography_demo.dart b/web/gallery/lib/demo/typography_demo.dart index 4b7e92115..18c3e3a96 100644 --- a/web/gallery/lib/demo/typography_demo.dart +++ b/web/gallery/lib/demo/typography_demo.dart @@ -1,8 +1,8 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class TextStyleItem extends StatelessWidget { const TextStyleItem({ @@ -10,10 +10,10 @@ class TextStyleItem extends StatelessWidget { @required this.name, @required this.style, @required this.text, - }) : assert(name != null), - assert(style != null), - assert(text != null), - super(key: key); + }) : assert(name != null), + assert(style != null), + assert(text != null), + super(key: key); final String name; final TextStyle style; @@ -22,16 +22,22 @@ class TextStyleItem extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - final TextStyle nameStyle = - theme.textTheme.caption.copyWith(color: theme.textTheme.caption.color); + final TextStyle nameStyle = theme.textTheme.caption.copyWith(color: theme.textTheme.caption.color); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(width: 72.0, child: Text(name, style: nameStyle)), - Expanded(child: Text(text, style: style.copyWith(height: 1.0))) - ])); + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 72.0, + child: Text(name, style: nameStyle), + ), + Expanded( + child: Text(text, style: style.copyWith(height: 1.0)), + ), + ], + ), + ); } } @@ -42,36 +48,24 @@ class TypographyDemo extends StatelessWidget { Widget build(BuildContext context) { final TextTheme textTheme = Theme.of(context).textTheme; final List styleItems = [ - TextStyleItem( - name: 'Display 3', style: textTheme.display3, text: 'Regular 56sp'), - TextStyleItem( - name: 'Display 2', style: textTheme.display2, text: 'Regular 45sp'), - TextStyleItem( - name: 'Display 1', style: textTheme.display1, text: 'Regular 34sp'), - TextStyleItem( - name: 'Headline', style: textTheme.headline, text: 'Regular 24sp'), + TextStyleItem(name: 'Display 3', style: textTheme.display3, text: 'Regular 56sp'), + TextStyleItem(name: 'Display 2', style: textTheme.display2, text: 'Regular 45sp'), + TextStyleItem(name: 'Display 1', style: textTheme.display1, text: 'Regular 34sp'), + TextStyleItem(name: 'Headline', style: textTheme.headline, text: 'Regular 24sp'), TextStyleItem(name: 'Title', style: textTheme.title, text: 'Medium 20sp'), - TextStyleItem( - name: 'Subheading', style: textTheme.subhead, text: 'Regular 16sp'), - TextStyleItem( - name: 'Body 2', style: textTheme.body2, text: 'Medium 14sp'), - TextStyleItem( - name: 'Body 1', style: textTheme.body1, text: 'Regular 14sp'), - TextStyleItem( - name: 'Caption', style: textTheme.caption, text: 'Regular 12sp'), - TextStyleItem( - name: 'Button', - style: textTheme.button, - text: 'MEDIUM (ALL CAPS) 14sp'), + TextStyleItem(name: 'Subheading', style: textTheme.subhead, text: 'Regular 16sp'), + TextStyleItem(name: 'Body 2', style: textTheme.body2, text: 'Medium 14sp'), + TextStyleItem(name: 'Body 1', style: textTheme.body1, text: 'Regular 14sp'), + TextStyleItem(name: 'Caption', style: textTheme.caption, text: 'Regular 12sp'), + TextStyleItem(name: 'Button', style: textTheme.button, text: 'MEDIUM (ALL CAPS) 14sp'), ]; if (MediaQuery.of(context).size.width > 500.0) { - styleItems.insert( - 0, - TextStyleItem( - name: 'Display 4', - style: textTheme.display4, - text: 'Light 112sp')); + styleItems.insert(0, TextStyleItem( + name: 'Display 4', + style: textTheme.display4, + text: 'Light 112sp', + )); } return Scaffold( @@ -79,7 +73,7 @@ class TypographyDemo extends StatelessWidget { body: SafeArea( top: false, bottom: false, - child: ListView(children: styleItems), + child: Scrollbar(child: ListView(children: styleItems)), ), ); } diff --git a/web/gallery/lib/demo/video_demo.dart b/web/gallery/lib/demo/video_demo.dart new file mode 100644 index 000000000..81f7bfcec --- /dev/null +++ b/web/gallery/lib/demo/video_demo.dart @@ -0,0 +1,434 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'package:connectivity/connectivity.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; +import 'package:device_info/device_info.dart'; + +class VideoCard extends StatelessWidget { + const VideoCard({ Key key, this.controller, this.title, this.subtitle }) : super(key: key); + + final VideoPlayerController controller; + final String title; + final String subtitle; + + Widget _buildInlineVideo() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0), + child: Center( + child: AspectRatio( + aspectRatio: 3 / 2, + child: Hero( + tag: controller, + child: VideoPlayerLoading(controller), + ), + ), + ), + ); + } + + Widget _buildFullScreenVideo() { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: Center( + child: AspectRatio( + aspectRatio: 3 / 2, + child: Hero( + tag: controller, + child: VideoPlayPause(controller), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + Widget fullScreenRoutePageBuilder( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return _buildFullScreenVideo(); + } + + void pushFullScreenWidget() { + final TransitionRoute route = PageRouteBuilder( + settings: RouteSettings(name: title, isInitialRoute: false), + pageBuilder: fullScreenRoutePageBuilder, + ); + + route.completed.then((void value) { + controller.setVolume(0.0); + }); + + controller.setVolume(1.0); + Navigator.of(context).push(route); + } + + return SafeArea( + top: false, + bottom: false, + child: Card( + child: Column( + children: [ + ListTile(title: Text(title), subtitle: Text(subtitle)), + GestureDetector( + onTap: pushFullScreenWidget, + child: _buildInlineVideo(), + ), + ], + ), + ), + ); + } +} + +class VideoPlayerLoading extends StatefulWidget { + const VideoPlayerLoading(this.controller); + + final VideoPlayerController controller; + + @override + _VideoPlayerLoadingState createState() => _VideoPlayerLoadingState(); +} + +class _VideoPlayerLoadingState extends State { + bool _initialized; + + @override + void initState() { + super.initState(); + _initialized = widget.controller.value.initialized; + widget.controller.addListener(() { + if (!mounted) { + return; + } + final bool controllerInitialized = widget.controller.value.initialized; + if (_initialized != controllerInitialized) { + setState(() { + _initialized = controllerInitialized; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + if (_initialized) { + return VideoPlayer(widget.controller); + } + return Stack( + children: [ + VideoPlayer(widget.controller), + const Center(child: CircularProgressIndicator()), + ], + fit: StackFit.expand, + ); + } +} + +class VideoPlayPause extends StatefulWidget { + const VideoPlayPause(this.controller); + + final VideoPlayerController controller; + + @override + State createState() => _VideoPlayPauseState(); +} + +class _VideoPlayPauseState extends State { + _VideoPlayPauseState() { + listener = () { + if (mounted) + setState(() { }); + }; + } + + FadeAnimation imageFadeAnimation; + VoidCallback listener; + + VideoPlayerController get controller => widget.controller; + + @override + void initState() { + super.initState(); + controller.addListener(listener); + } + + @override + void deactivate() { + controller.removeListener(listener); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.bottomCenter, + fit: StackFit.expand, + children: [ + GestureDetector( + child: VideoPlayerLoading(controller), + onTap: () { + if (!controller.value.initialized) { + return; + } + if (controller.value.isPlaying) { + imageFadeAnimation = const FadeAnimation( + child: Icon(Icons.pause, size: 100.0), + ); + controller.pause(); + } else { + imageFadeAnimation = const FadeAnimation( + child: Icon(Icons.play_arrow, size: 100.0), + ); + controller.play(); + } + }, + ), + Center(child: imageFadeAnimation), + ], + ); + } +} + +class FadeAnimation extends StatefulWidget { + const FadeAnimation({ + this.child, + this.duration = const Duration(milliseconds: 500), + }); + + final Widget child; + final Duration duration; + + @override + _FadeAnimationState createState() => _FadeAnimationState(); +} + +class _FadeAnimationState extends State with SingleTickerProviderStateMixin { + AnimationController animationController; + + @override + void initState() { + super.initState(); + animationController = AnimationController( + duration: widget.duration, + vsync: this, + ); + animationController.addListener(() { + if (mounted) { + setState(() { }); + } + }); + animationController.forward(from: 0.0); + } + + @override + void deactivate() { + animationController.stop(); + super.deactivate(); + } + + @override + void didUpdateWidget(FadeAnimation oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.child != widget.child) { + animationController.forward(from: 0.0); + } + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return animationController.isAnimating + ? Opacity( + opacity: 1.0 - animationController.value, + child: widget.child, + ) + : Container(); + } +} + +class ConnectivityOverlay extends StatefulWidget { + const ConnectivityOverlay({ + this.child, + this.connectedCompleter, + this.scaffoldKey, + }); + + final Widget child; + final Completer connectedCompleter; + final GlobalKey scaffoldKey; + + @override + _ConnectivityOverlayState createState() => _ConnectivityOverlayState(); +} + +class _ConnectivityOverlayState extends State { + StreamSubscription connectivitySubscription; + bool connected = true; + + static const Widget errorSnackBar = SnackBar( + backgroundColor: Colors.red, + content: ListTile( + title: Text('No network'), + subtitle: Text( + 'To load the videos you must have an active network connection', + ), + ), + ); + + Stream connectivityStream() async* { + final Connectivity connectivity = Connectivity(); + ConnectivityResult previousResult = await connectivity.checkConnectivity(); + yield previousResult; + await for (ConnectivityResult result + in connectivity.onConnectivityChanged) { + if (result != previousResult) { + yield result; + previousResult = result; + } + } + } + + @override + void initState() { + super.initState(); + connectivitySubscription = connectivityStream().listen( + (ConnectivityResult connectivityResult) { + if (!mounted) { + return; + } + if (connectivityResult == ConnectivityResult.none) { + widget.scaffoldKey.currentState.showSnackBar(errorSnackBar); + } else { + if (!widget.connectedCompleter.isCompleted) { + widget.connectedCompleter.complete(null); + } + } + }, + ); + } + + @override + void dispose() { + connectivitySubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => widget.child; +} + +class VideoDemo extends StatefulWidget { + const VideoDemo({ Key key }) : super(key: key); + + static const String routeName = '/video'; + + @override + _VideoDemoState createState() => _VideoDemoState(); +} + +final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + +Future isIOSSimulator() async { + return Platform.isIOS && !(await deviceInfoPlugin.iosInfo).isPhysicalDevice; +} + +class _VideoDemoState extends State with SingleTickerProviderStateMixin { + final VideoPlayerController butterflyController = VideoPlayerController.asset( + 'videos/butterfly.mp4', + package: 'flutter_gallery_assets', + ); + + // TODO(sigurdm): This should not be stored here. + static const String beeUri = 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'; + final VideoPlayerController beeController = VideoPlayerController.network(beeUri); + + final GlobalKey scaffoldKey = GlobalKey(); + final Completer connectedCompleter = Completer(); + bool isSupported = true; + bool isDisposed = false; + + @override + void initState() { + super.initState(); + + Future initController(VideoPlayerController controller, String name) async { + print('> VideoDemo initController "$name" ${isDisposed ? "DISPOSED" : ""}'); + controller.setLooping(true); + controller.setVolume(0.0); + controller.play(); + await connectedCompleter.future; + await controller.initialize(); + if (mounted) { + print('< VideoDemo initController "$name" done ${isDisposed ? "DISPOSED" : ""}'); + setState(() { }); + } + } + + initController(butterflyController, 'butterfly'); + initController(beeController, 'bee'); + isIOSSimulator().then((bool result) { + isSupported = !result; + }); + } + + @override + void dispose() { + print('> VideoDemo dispose'); + isDisposed = true; + butterflyController.dispose(); + beeController.dispose(); + print('< VideoDemo dispose'); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: scaffoldKey, + appBar: AppBar( + title: const Text('Videos'), + ), + body: isSupported + ? ConnectivityOverlay( + child: Scrollbar( + child: ListView( + children: [ + VideoCard( + title: 'Butterfly', + subtitle: '… flutters by', + controller: butterflyController, + ), + VideoCard( + title: 'Bee', + subtitle: '… gently buzzing', + controller: beeController, + ), + ], + ), + ), + connectedCompleter: connectedCompleter, + scaffoldKey: scaffoldKey, + ) + : const Center( + child: Text( + 'Video playback not supported on the iOS Simulator.', + ), + ), + ); + } +} diff --git a/web/gallery/lib/gallery/about.dart b/web/gallery/lib/gallery/about.dart index 3c04d6090..0658fdaa4 100644 --- a/web/gallery/lib/gallery/about.dart +++ b/web/gallery/lib/gallery/about.dart @@ -2,10 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/gestures.dart'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/foundation.dart' show defaultTargetPlatform; +import 'package:flutter/material.dart'; + +import 'package:url_launcher/url_launcher.dart'; class _LinkTextSpan extends TextSpan { + // Beware! // // This class is only safe because the TapGestureRecognizer is not @@ -19,27 +23,25 @@ class _LinkTextSpan extends TextSpan { // manage the recognizer from outside the TextSpan, e.g. in the State of a // stateful widget that then hands the recognizer to the TextSpan. - _LinkTextSpan({TextStyle style, String url, String text}) - : super( - style: style, - text: text ?? url, - recognizer: TapGestureRecognizer() - ..onTap = () { - //launch(url, forceSafariVC: false); - }); + _LinkTextSpan({ TextStyle style, String url, String text }) : super( + style: style, + text: text ?? url, + recognizer: TapGestureRecognizer()..onTap = () { + launch(url, forceSafariVC: false); + } + ); } void showGalleryAboutDialog(BuildContext context) { final ThemeData themeData = Theme.of(context); final TextStyle aboutTextStyle = themeData.textTheme.body2; - final TextStyle linkStyle = - themeData.textTheme.body2.copyWith(color: themeData.accentColor); + final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor); showAboutDialog( context: context, - applicationVersion: '2018 Preview', - //applicationIcon: const FlutterLogo(), - applicationLegalese: '© 2018 The Chromium Authors', + applicationVersion: 'January 2019', + applicationIcon: const FlutterLogo(), + applicationLegalese: '© 2019 The Chromium Authors', children: [ Padding( padding: const EdgeInsets.only(top: 24.0), @@ -47,18 +49,21 @@ void showGalleryAboutDialog(BuildContext context) { text: TextSpan( children: [ TextSpan( - style: aboutTextStyle, - text: 'Flutter web is an early-stage, web framework. ' - 'This gallery is a preview of ' - "Flutter's many widgets, behaviors, animations, layouts, " - 'and more. Learn more about Flutter at '), + style: aboutTextStyle, + text: 'Flutter is an open-source project to help developers ' + 'build high-performance, high-fidelity, mobile apps for ' + '${defaultTargetPlatform == TargetPlatform.iOS ? 'multiple platforms' : 'iOS and Android'} ' + 'from a single codebase. This design lab is a playground ' + "and showcase of Flutter's many widgets, behaviors, " + 'animations, layouts, and more. Learn more about Flutter at ', + ), _LinkTextSpan( style: linkStyle, - url: 'https://flutter.io', + url: 'https://flutter.dev', ), TextSpan( style: aboutTextStyle, - text: '.\n\nTo see the source code for flutter ', + text: '.\n\nTo see the source code for this app, please visit the ', ), _LinkTextSpan( style: linkStyle, diff --git a/web/gallery/lib/gallery/app.dart b/web/gallery/lib/gallery/app.dart index 8a0fa5d9e..b21f77fa8 100644 --- a/web/gallery/lib/gallery/app.dart +++ b/web/gallery/lib/gallery/app.dart @@ -1,22 +1,29 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; -import 'package:flutter_web/foundation.dart' show defaultTargetPlatform; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/scheduler.dart' show timeDilation; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart' show defaultTargetPlatform; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart' show timeDilation; +import 'package:flutter_gallery/demo/shrine/model/app_state_model.dart'; +import 'package:scoped_model/scoped_model.dart'; + +import 'package:url_launcher/url_launcher.dart'; import 'demos.dart'; import 'home.dart'; import 'options.dart'; import 'scales.dart'; import 'themes.dart'; +import 'updater.dart'; class GalleryApp extends StatefulWidget { const GalleryApp({ Key key, + this.updateUrlFetcher, this.enablePerformanceOverlay = true, this.enableRasterCacheImagesCheckerboard = true, this.enableOffscreenLayersCheckerboard = true, @@ -24,6 +31,7 @@ class GalleryApp extends StatefulWidget { this.testMode = false, }) : super(key: key); + final UpdateUrlFetcher updateUrlFetcher; final bool enablePerformanceOverlay; final bool enableRasterCacheImagesCheckerboard; final bool enableOffscreenLayersCheckerboard; @@ -37,6 +45,7 @@ class GalleryApp extends StatefulWidget { class _GalleryAppState extends State { GalleryOptions _options; Timer _timeDilationTimer; + AppStateModel model; Map _buildRoutes() { // For a different example of how to set up an application routing table @@ -53,11 +62,12 @@ class _GalleryAppState extends State { void initState() { super.initState(); _options = GalleryOptions( - theme: kLightGalleryTheme, + themeMode: ThemeMode.system, textScaleFactor: kAllGalleryTextScaleValues[0], timeDilation: timeDilation, platform: defaultTargetPlatform, ); + model = AppStateModel()..loadProducts(); } @override @@ -108,28 +118,51 @@ class _GalleryAppState extends State { optionsPage: GalleryOptionsPage( options: _options, onOptionsChanged: _handleOptionsChanged, - onSendFeedback: widget.onSendFeedback ?? - () { - // TODO: launch('https://github.com/flutter/flutter/issues/new', forceSafariVC: false); - }, + onSendFeedback: widget.onSendFeedback ?? () { + launch('https://github.com/flutter/flutter/issues/new/choose', forceSafariVC: false); + }, ), ); - return MaterialApp( - theme: _options.theme.data.copyWith(platform: _options.platform), - title: 'Flutter Web Gallery', - color: Colors.grey, - showPerformanceOverlay: _options.showPerformanceOverlay, - checkerboardOffscreenLayers: _options.showOffscreenLayersCheckerboard, - checkerboardRasterCacheImages: _options.showRasterCacheImagesCheckerboard, - routes: _buildRoutes(), - builder: (BuildContext context, Widget child) { - return Directionality( - textDirection: _options.textDirection, - child: _applyTextScaleFactor(child), - ); - }, - home: home, + if (widget.updateUrlFetcher != null) { + home = Updater( + updateUrlFetcher: widget.updateUrlFetcher, + child: home, + ); + } + + return ScopedModel( + model: model, + child: MaterialApp( + theme: kLightGalleryTheme.copyWith(platform: _options.platform), + darkTheme: kDarkGalleryTheme.copyWith(platform: _options.platform), + themeMode: _options.themeMode, + title: 'Flutter Gallery', + color: Colors.grey, + showPerformanceOverlay: _options.showPerformanceOverlay, + checkerboardOffscreenLayers: _options.showOffscreenLayersCheckerboard, + checkerboardRasterCacheImages: _options.showRasterCacheImagesCheckerboard, + routes: _buildRoutes(), + builder: (BuildContext context, Widget child) { + return Directionality( + textDirection: _options.textDirection, + child: _applyTextScaleFactor( + // Specifically use a blank Cupertino theme here and do not transfer + // over the Material primary color etc except the brightness to + // showcase standard iOS looks. + Builder(builder: (BuildContext context) { + return CupertinoTheme( + data: CupertinoThemeData( + brightness: Theme.of(context).brightness, + ), + child: child, + ); + }), + ), + ); + }, + home: home, + ), ); } } diff --git a/web/gallery/lib/gallery/backdrop.dart b/web/gallery/lib/gallery/backdrop.dart index 78c31dddd..974e6afee 100644 --- a/web/gallery/lib/gallery/backdrop.dart +++ b/web/gallery/lib/gallery/backdrop.dart @@ -4,8 +4,8 @@ import 'dart:math' as math; -import 'package:flutter_web/rendering.dart'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/material.dart'; const double _kFrontHeadingHeight = 32.0; // front layer beveled rectangle const double _kFrontClosedHeight = 92.0; // front layer height when closed @@ -130,9 +130,7 @@ class _BackAppBar extends StatelessWidget { this.leading = const SizedBox(width: 56.0), @required this.title, this.trailing, - }) : assert(leading != null), - assert(title != null), - super(key: key); + }) : assert(leading != null), assert(title != null), super(key: key); final Widget leading; final Widget title; @@ -197,15 +195,13 @@ class Backdrop extends StatefulWidget { _BackdropState createState() => _BackdropState(); } -class _BackdropState extends State - with SingleTickerProviderStateMixin { +class _BackdropState extends State with SingleTickerProviderStateMixin { final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop'); AnimationController _controller; Animation _frontOpacity; - static final Animatable _frontOpacityTween = - Tween(begin: 0.2, end: 1.0).chain( - CurveTween(curve: const Interval(0.0, 0.4, curve: Curves.easeInOut))); + static final Animatable _frontOpacityTween = Tween(begin: 0.2, end: 1.0) + .chain(CurveTween(curve: const Interval(0.0, 0.4, curve: Curves.easeInOut))); @override void initState() { @@ -228,21 +224,18 @@ class _BackdropState extends State // Warning: this can be safely called from the event handlers but it may // not be called at build time. final RenderBox renderBox = _backdropKey.currentContext.findRenderObject(); - return math.max( - 0.0, renderBox.size.height - _kBackAppBarHeight - _kFrontClosedHeight); + return math.max(0.0, renderBox.size.height - _kBackAppBarHeight - _kFrontClosedHeight); } void _handleDragUpdate(DragUpdateDetails details) { - _controller.value -= - details.primaryDelta / (_backdropHeight ?? details.primaryDelta); + _controller.value -= details.primaryDelta / (_backdropHeight ?? details.primaryDelta); } void _handleDragEnd(DragEndDetails details) { - if (_controller.isAnimating || - _controller.status == AnimationStatus.completed) return; + if (_controller.isAnimating || _controller.status == AnimationStatus.completed) + return; - final double flingVelocity = - details.velocity.pixelsPerSecond.dy / _backdropHeight; + final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight; if (flingVelocity < 0.0) _controller.fling(velocity: math.max(2.0, -flingVelocity)); else if (flingVelocity > 0.0) @@ -253,16 +246,13 @@ class _BackdropState extends State void _toggleFrontLayer() { final AnimationStatus status = _controller.status; - final bool isOpen = status == AnimationStatus.completed || - status == AnimationStatus.forward; + final bool isOpen = status == AnimationStatus.completed || status == AnimationStatus.forward; _controller.fling(velocity: isOpen ? -2.0 : 2.0); } Widget _buildStack(BuildContext context, BoxConstraints constraints) { - final Animation frontRelativeRect = - _controller.drive(RelativeRectTween( - begin: RelativeRect.fromLTRB( - 0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0), + final Animation frontRelativeRect = _controller.drive(RelativeRectTween( + begin: RelativeRect.fromLTRB(0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0), end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0), )); @@ -289,11 +279,12 @@ class _BackdropState extends State ), ), Expanded( - child: Visibility( - child: widget.backLayer, - visible: _controller.status != AnimationStatus.completed, - maintainState: true, - )), + child: Visibility( + child: widget.backLayer, + visible: _controller.status != AnimationStatus.completed, + maintainState: true, + ), + ), ], ), // Front layer @@ -306,12 +297,9 @@ class _BackdropState extends State elevation: 12.0, color: Theme.of(context).canvasColor, clipper: ShapeBorderClipper( - shape: RoundedRectangleBorder( - borderRadius: - _kFrontHeadingBevelRadius.transform(_controller.value)), -// BeveledRectangleBorder( -// borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value), -// ), + shape: BeveledRectangleBorder( + borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value), + ), ), clipBehavior: Clip.antiAlias, child: child, diff --git a/web/gallery/lib/gallery/demo.dart b/web/gallery/lib/gallery/demo.dart index f3be9fdee..178d8dea7 100644 --- a/web/gallery/lib/gallery/demo.dart +++ b/web/gallery/lib/gallery/demo.dart @@ -1,9 +1,16 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/cupertino.dart'; +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'demos.dart'; +import 'example_code_parser.dart'; +import 'syntax_highlighter.dart'; class ComponentDemoTabData { ComponentDemoTabData({ @@ -21,12 +28,13 @@ class ComponentDemoTabData { final String documentationUrl; @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; + bool operator==(Object other) { + if (other.runtimeType != runtimeType) + return false; final ComponentDemoTabData typedOther = other; - return typedOther.tabName == tabName && - typedOther.description == description && - typedOther.documentationUrl == documentationUrl; + return typedOther.tabName == tabName + && typedOther.description == description + && typedOther.documentationUrl == documentationUrl; } @override @@ -38,29 +46,47 @@ class TabbedComponentDemoScaffold extends StatelessWidget { this.title, this.demos, this.actions, + this.isScrollable = true, + this.showExampleCodeAction = true, }); final List demos; final String title; final List actions; + final bool isScrollable; + final bool showExampleCodeAction; void _showExampleCode(BuildContext context) { - final String tag = - demos[DefaultTabController.of(context).index].exampleCodeTag; + final String tag = demos[DefaultTabController.of(context).index].exampleCodeTag; if (tag != null) { - throw new UnimplementedError(); - // TODO: -// Navigator.push(context, MaterialPageRoute( -// builder: (BuildContext context) => FullScreenCodeDialog(exampleCodeTag: tag) -// )); + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => FullScreenCodeDialog(exampleCodeTag: tag) + )); } } - void _showApiDocumentation(BuildContext context) { - final String url = - demos[DefaultTabController.of(context).index].documentationUrl; - if (url != null) { - // launch(url, forceWebView: true); + Future _showApiDocumentation(BuildContext context) async { + final String url = demos[DefaultTabController.of(context).index].documentationUrl; + if (url == null) + return; + + if (await canLaunch(url)) { + await launch(url); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return SimpleDialog( + title: const Text('Couldn\'t display URL:'), + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text(url), + ), + ], + ); + }, + ); } } @@ -71,34 +97,30 @@ class TabbedComponentDemoScaffold extends StatelessWidget { child: Scaffold( appBar: AppBar( title: Text(title), - actions: (actions ?? []) - ..addAll( - [ - Builder( - builder: (BuildContext context) { - return IconButton( - icon: const Icon(Icons.library_books), - onPressed: () => _showApiDocumentation(context), - ); - }, - ), - Builder( - builder: (BuildContext context) { - return IconButton( - icon: const Icon(Icons.code), - tooltip: 'Show example code', - onPressed: () => _showExampleCode(context), - ); - }, - ) - ], + actions: [ + ...?actions, + Builder( + builder: (BuildContext context) { + return IconButton( + icon: const Icon(Icons.library_books, semanticLabel: 'Show documentation'), + onPressed: () => _showApiDocumentation(context), + ); + }, ), + if (showExampleCodeAction) + Builder( + builder: (BuildContext context) { + return IconButton( + icon: const Icon(Icons.code), + tooltip: 'Show example code', + onPressed: () => _showExampleCode(context), + ); + }, + ), + ], bottom: TabBar( - isScrollable: true, - tabs: demos - .map( - (ComponentDemoTabData data) => Tab(text: data.tabName)) - .toList(), + isScrollable: isScrollable, + tabs: demos.map((ComponentDemoTabData data) => Tab(text: data.tabName)).toList(), ), ), body: TabBarView( @@ -109,10 +131,12 @@ class TabbedComponentDemoScaffold extends StatelessWidget { child: Column( children: [ Padding( - padding: const EdgeInsets.all(16.0), - child: Text(demo.description, - style: Theme.of(context).textTheme.subhead)), - Expanded(child: demo.demoWidget) + padding: const EdgeInsets.all(16.0), + child: Text(demo.description, + style: Theme.of(context).textTheme.subhead, + ), + ), + Expanded(child: demo.demoWidget), ], ), ); @@ -123,79 +147,115 @@ class TabbedComponentDemoScaffold extends StatelessWidget { } } -class MaterialDemoDocumentationButton extends StatelessWidget { - MaterialDemoDocumentationButton(String routeName, {Key key}) - : documentationUrl = 'todo', - assert( - 'todo' != null, - 'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos', - ), - super(key: key); +class FullScreenCodeDialog extends StatefulWidget { + const FullScreenCodeDialog({ this.exampleCodeTag }); - final String documentationUrl; + final String exampleCodeTag; @override - Widget build(BuildContext context) { - return IconButton( - icon: const Icon(Icons.library_books), - tooltip: 'API documentation', - // TODO(flutter_web): launch(documentationUrl, forceWebView: true) - onPressed: () => {}); - } + FullScreenCodeDialogState createState() => FullScreenCodeDialogState(); } -Widget wrapScaffold(String title, BuildContext context, Key key, Widget child, - String routeName) { - IconData _backIcon() { - switch (Theme.of(context).platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - return Icons.arrow_back; - case TargetPlatform.iOS: - return Icons.arrow_back_ios; +class FullScreenCodeDialogState extends State { + + String _exampleCode; + + @override + void didChangeDependencies() { + getExampleCode(widget.exampleCodeTag, DefaultAssetBundle.of(context)).then((String code) { + if (mounted) { + setState(() { + _exampleCode = code ?? 'Example code not found'; + }); + } + }); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final SyntaxHighlighterStyle style = Theme.of(context).brightness == Brightness.dark + ? SyntaxHighlighterStyle.darkThemeStyle() + : SyntaxHighlighterStyle.lightThemeStyle(); + + Widget body; + if (_exampleCode == null) { + body = const Center( + child: CircularProgressIndicator(), + ); + } else { + body = SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: RichText( + text: TextSpan( + style: const TextStyle(fontFamily: 'monospace', fontSize: 10.0), + children: [ + DartSyntaxHighlighter(style).format(_exampleCode), + ], + ), + ), + ), + ); } - assert(false); - return null; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon( + Icons.clear, + semanticLabel: 'Close', + ), + onPressed: () { Navigator.pop(context); }, + ), + title: const Text('Example code'), + ), + body: body, + ); } +} - return Scaffold( - key: key, - appBar: AppBar( - leading: IconButton( - icon: Icon(_backIcon()), - alignment: Alignment.centerLeft, - tooltip: 'Back', - onPressed: () { - Navigator.pop(context); - }, +class MaterialDemoDocumentationButton extends StatelessWidget { + MaterialDemoDocumentationButton(String routeName, { Key key }) + : documentationUrl = kDemoDocumentationUrl[routeName], + assert( + kDemoDocumentationUrl[routeName] != null, + 'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos', ), - title: Text(title), - actions: [MaterialDemoDocumentationButton(routeName)], - ), - body: Material(child: Center(child: child)), - ); + super(key: key); + + final String documentationUrl; + + @override + Widget build(BuildContext context) { + return IconButton( + icon: const Icon(Icons.library_books), + tooltip: 'API documentation', + onPressed: () => launch(documentationUrl, forceWebView: true), + ); + } } class CupertinoDemoDocumentationButton extends StatelessWidget { - CupertinoDemoDocumentationButton(String routeName, {Key key}) - : documentationUrl = 'todo', - assert( - 'todo' != null, - 'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos', - ), - super(key: key); + CupertinoDemoDocumentationButton(String routeName, { Key key }) + : documentationUrl = kDemoDocumentationUrl[routeName], + assert( + kDemoDocumentationUrl[routeName] != null, + 'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos', + ), + super(key: key); final String documentationUrl; @override Widget build(BuildContext context) { return CupertinoButton( - padding: EdgeInsets.zero, - child: Semantics( - label: 'API documentation', - child: const Icon(CupertinoIcons.book), - ), - // TODO(flutter_web): launch(documentationUrl, forceWebView: true) - onPressed: () => {}); + padding: EdgeInsets.zero, + child: Semantics( + label: 'API documentation', + child: const Icon(CupertinoIcons.book), + ), + onPressed: () => launch(documentationUrl, forceWebView: true), + ); } } diff --git a/web/gallery/lib/gallery/demos.dart b/web/gallery/lib/gallery/demos.dart index e94af4aac..a212305b2 100644 --- a/web/gallery/lib/gallery/demos.dart +++ b/web/gallery/lib/gallery/demos.dart @@ -2,24 +2,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import '../demo/all.dart'; import 'icons.dart'; -// TODO: As Demos are added and complete, uncomment _buildGalleryDemos sections. - class GalleryDemoCategory { - const GalleryDemoCategory._({this.name, this.icon}); - @required + const GalleryDemoCategory._({ + @required this.name, + @required this.icon, + }); + final String name; - @required final IconData icon; @override bool operator ==(dynamic other) { - if (identical(this, other)) return true; - if (runtimeType != other.runtimeType) return false; + if (identical(this, other)) + return true; + if (runtimeType != other.runtimeType) + return false; final GalleryDemoCategory typedOther = other; return typedOther.name == name && typedOther.icon == icon; } @@ -43,20 +45,20 @@ const GalleryDemoCategory _kStyle = GalleryDemoCategory._( icon: GalleryIcons.custom_typography, ); -//const GalleryDemoCategory _kCupertinoComponents = GalleryDemoCategory._( -// name: 'Cupertino', -// icon: GalleryIcons.phone_iphone, -//); - const GalleryDemoCategory _kMaterialComponents = GalleryDemoCategory._( name: 'Material', icon: GalleryIcons.category_mdc, ); -//const GalleryDemoCategory _kMedia = GalleryDemoCategory._( -// name: 'Media', -// icon: GalleryIcons.drive_video, -//); +const GalleryDemoCategory _kCupertinoComponents = GalleryDemoCategory._( + name: 'Cupertino', + icon: GalleryIcons.phone_iphone, +); + +const GalleryDemoCategory _kMedia = GalleryDemoCategory._( + name: 'Media', + icon: GalleryIcons.drive_video, +); class GalleryDemo { const GalleryDemo({ @@ -67,10 +69,10 @@ class GalleryDemo { @required this.routeName, this.documentationUrl, @required this.buildRoute, - }) : assert(title != null), - assert(category != null), - assert(routeName != null), - assert(buildRoute != null); + }) : assert(title != null), + assert(category != null), + assert(routeName != null), + assert(buildRoute != null); final String title; final IconData icon; @@ -89,6 +91,22 @@ class GalleryDemo { List _buildGalleryDemos() { final List galleryDemos = [ // Demos + GalleryDemo( + title: 'Shrine', + subtitle: 'Basic shopping app', + icon: GalleryIcons.shrine, + category: _kDemos, + routeName: ShrineDemo.routeName, + buildRoute: (BuildContext context) => const ShrineDemo(), + ), + GalleryDemo( + title: 'Fortnightly', + subtitle: 'Newspaper typography app', + icon: GalleryIcons.custom_typography, + category: _kDemos, + routeName: FortnightlyDemo.routeName, + buildRoute: (BuildContext context) => FortnightlyDemo(), + ), GalleryDemo( title: 'Contact profile', subtitle: 'Address book entry with a flexible appbar', @@ -97,14 +115,6 @@ List _buildGalleryDemos() { routeName: ContactsDemo.routeName, buildRoute: (BuildContext context) => ContactsDemo(), ), - GalleryDemo( - title: 'Shrine', - subtitle: 'Basic shopping app', - icon: GalleryIcons.shrine, - category: _kDemos, - routeName: ShrineDemo.routeName, - buildRoute: (BuildContext context) => ShrineDemo(), - ), GalleryDemo( title: 'Animation', subtitle: 'Section organizer', @@ -113,6 +123,14 @@ List _buildGalleryDemos() { routeName: AnimationDemo.routeName, buildRoute: (BuildContext context) => const AnimationDemo(), ), + GalleryDemo( + title: '2D Transformations', + subtitle: 'Pan, Zoom, Rotate', + icon: GalleryIcons.grid_on, + category: _kDemos, + routeName: TransformationsDemo.routeName, + buildRoute: (BuildContext context) => const TransformationsDemo(), + ), // Style GalleryDemo( @@ -131,6 +149,7 @@ List _buildGalleryDemos() { routeName: TypographyDemo.routeName, buildRoute: (BuildContext context) => TypographyDemo(), ), + // Material Components GalleryDemo( title: 'Backdrop', @@ -140,14 +159,22 @@ List _buildGalleryDemos() { routeName: BackdropDemo.routeName, buildRoute: (BuildContext context) => BackdropDemo(), ), + GalleryDemo( + title: 'Banner', + subtitle: 'Displaying a banner within a list', + icon: GalleryIcons.lists_leave_behind, + category: _kMaterialComponents, + routeName: BannerDemo.routeName, + documentationUrl: 'https://api.flutter.dev/flutter/material/MaterialBanner-class.html', + buildRoute: (BuildContext context) => const BannerDemo(), + ), GalleryDemo( title: 'Bottom app bar', subtitle: 'Optional floating action button notch', icon: GalleryIcons.bottom_app_bar, category: _kMaterialComponents, routeName: BottomAppBarDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/BottomAppBar-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/BottomAppBar-class.html', buildRoute: (BuildContext context) => BottomAppBarDemo(), ), GalleryDemo( @@ -156,18 +183,16 @@ List _buildGalleryDemos() { icon: GalleryIcons.bottom_navigation, category: _kMaterialComponents, routeName: BottomNavigationDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/BottomNavigationBar-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/BottomNavigationBar-class.html', buildRoute: (BuildContext context) => BottomNavigationDemo(), ), GalleryDemo( title: 'Bottom sheet: Modal', - subtitle: 'A dismissable bottom sheet', + subtitle: 'A dismissible bottom sheet', icon: GalleryIcons.bottom_sheets, category: _kMaterialComponents, routeName: ModalBottomSheetDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/showModalBottomSheet.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/showModalBottomSheet.html', buildRoute: (BuildContext context) => ModalBottomSheetDemo(), ), GalleryDemo( @@ -176,8 +201,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.bottom_sheet_persistent, category: _kMaterialComponents, routeName: PersistentBottomSheetDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/ScaffoldState/showBottomSheet.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/ScaffoldState/showBottomSheet.html', buildRoute: (BuildContext context) => PersistentBottomSheetDemo(), ), GalleryDemo( @@ -194,8 +218,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.buttons, category: _kMaterialComponents, routeName: TabsFabDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/FloatingActionButton-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/FloatingActionButton-class.html', buildRoute: (BuildContext context) => TabsFabDemo(), ), GalleryDemo( @@ -204,8 +227,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.cards, category: _kMaterialComponents, routeName: CardsDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/Card-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/Card-class.html', buildRoute: (BuildContext context) => CardsDemo(), ), GalleryDemo( @@ -214,8 +236,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.chips, category: _kMaterialComponents, routeName: ChipDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/Chip-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/Chip-class.html', buildRoute: (BuildContext context) => ChipDemo(), ), GalleryDemo( @@ -224,8 +245,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.data_table, category: _kMaterialComponents, routeName: DataTableDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/PaginatedDataTable-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/PaginatedDataTable-class.html', buildRoute: (BuildContext context) => DataTableDemo(), ), GalleryDemo( @@ -234,8 +254,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.dialogs, category: _kMaterialComponents, routeName: DialogDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/showDialog.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/showDialog.html', buildRoute: (BuildContext context) => DialogDemo(), ), GalleryDemo( @@ -245,8 +264,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.cupertino_progress, category: _kMaterialComponents, routeName: ElevationDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/Material/elevation.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/Material/elevation.html', buildRoute: (BuildContext context) => ElevationDemo(), ), GalleryDemo( @@ -254,10 +272,9 @@ List _buildGalleryDemos() { subtitle: 'A list with one sub-list level', icon: GalleryIcons.expand_all, category: _kMaterialComponents, - routeName: TwoLevelListDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/ExpansionTile-class.html', - buildRoute: (BuildContext context) => TwoLevelListDemo(), + routeName: ExpansionTileListDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/material/ExpansionTile-class.html', + buildRoute: (BuildContext context) => ExpansionTileListDemo(), ), GalleryDemo( title: 'Expansion panels', @@ -265,8 +282,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.expand_all, category: _kMaterialComponents, routeName: ExpansionPanelsDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/ExpansionPanel-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/ExpansionPanel-class.html', buildRoute: (BuildContext context) => ExpansionPanelsDemo(), ), GalleryDemo( @@ -275,8 +291,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.grid_on, category: _kMaterialComponents, routeName: GridListDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/widgets/GridView-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/widgets/GridView-class.html', buildRoute: (BuildContext context) => const GridListDemo(), ), GalleryDemo( @@ -285,8 +300,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.sentiment_very_satisfied, category: _kMaterialComponents, routeName: IconsDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/IconButton-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/IconButton-class.html', buildRoute: (BuildContext context) => IconsDemo(), ), GalleryDemo( @@ -295,8 +309,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.list_alt, category: _kMaterialComponents, routeName: ListDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/ListTile-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/ListTile-class.html', buildRoute: (BuildContext context) => const ListDemo(), ), GalleryDemo( @@ -305,8 +318,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.lists_leave_behind, category: _kMaterialComponents, routeName: LeaveBehindDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/widgets/Dismissible-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/widgets/Dismissible-class.html', buildRoute: (BuildContext context) => const LeaveBehindDemo(), ), GalleryDemo( @@ -315,8 +327,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.list_alt, category: _kMaterialComponents, routeName: ReorderableListDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/ReorderableListView-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/ReorderableListView-class.html', buildRoute: (BuildContext context) => const ReorderableListDemo(), ), GalleryDemo( @@ -325,8 +336,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.more_vert, category: _kMaterialComponents, routeName: MenuDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/PopupMenuButton-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/PopupMenuButton-class.html', buildRoute: (BuildContext context) => const MenuDemo(), ), GalleryDemo( @@ -335,8 +345,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.menu, category: _kMaterialComponents, routeName: DrawerDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/Drawer-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/Drawer-class.html', buildRoute: (BuildContext context) => DrawerDemo(), ), GalleryDemo( @@ -345,8 +354,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.page_control, category: _kMaterialComponents, routeName: PageSelectorDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/TabBarView-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/TabBarView-class.html', buildRoute: (BuildContext context) => PageSelectorDemo(), ), GalleryDemo( @@ -355,8 +363,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.event, category: _kMaterialComponents, routeName: DateAndTimePickerDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/showDatePicker.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/showDatePicker.html', buildRoute: (BuildContext context) => DateAndTimePickerDemo(), ), GalleryDemo( @@ -365,8 +372,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.progress_activity, category: _kMaterialComponents, routeName: ProgressIndicatorDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/LinearProgressIndicator-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/LinearProgressIndicator-class.html', buildRoute: (BuildContext context) => ProgressIndicatorDemo(), ), GalleryDemo( @@ -375,8 +381,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.refresh, category: _kMaterialComponents, routeName: OverscrollDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/RefreshIndicator-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/RefreshIndicator-class.html', buildRoute: (BuildContext context) => const OverscrollDemo(), ), GalleryDemo( @@ -385,8 +390,7 @@ List _buildGalleryDemos() { icon: Icons.search, category: _kMaterialComponents, routeName: SearchDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/showSearch.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/showSearch.html', buildRoute: (BuildContext context) => SearchDemo(), ), GalleryDemo( @@ -403,8 +407,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.sliders, category: _kMaterialComponents, routeName: SliderDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/Slider-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/Slider-class.html', buildRoute: (BuildContext context) => SliderDemo(), ), GalleryDemo( @@ -413,8 +416,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.snackbar, category: _kMaterialComponents, routeName: SnackBarDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/ScaffoldState/showSnackBar.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/ScaffoldState/showSnackBar.html', buildRoute: (BuildContext context) => const SnackBarDemo(), ), GalleryDemo( @@ -423,38 +425,16 @@ List _buildGalleryDemos() { icon: GalleryIcons.tabs, category: _kMaterialComponents, routeName: TabsDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/TabBarView-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/TabBarView-class.html', buildRoute: (BuildContext context) => TabsDemo(), ), - GalleryDemo( - title: 'Text', - subtitle: 'Single-line text and multiline paragraphs', - icon: Icons.text_fields, - category: _kMaterialComponents, - routeName: TextDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/widgets/Text-class.html', - buildRoute: (BuildContext context) => TextDemo(), - ), - GalleryDemo( - title: 'Text Editing', - subtitle: 'EditableText with a TextEditingController', - icon: Icons.text_fields, - category: _kMaterialComponents, - routeName: EditableTextDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/widgets/EditableText-class.html', - buildRoute: (BuildContext context) => EditableTextDemo(), - ), GalleryDemo( title: 'Tabs: Scrolling', subtitle: 'Tab bar that scrolls', category: _kMaterialComponents, icon: GalleryIcons.tabs, routeName: ScrollableTabsDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/TabBar-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/TabBar-class.html', buildRoute: (BuildContext context) => ScrollableTabsDemo(), ), GalleryDemo( @@ -463,8 +443,7 @@ List _buildGalleryDemos() { icon: GalleryIcons.text_fields_alt, category: _kMaterialComponents, routeName: TextFormFieldDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/TextFormField-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/TextFormField-class.html', buildRoute: (BuildContext context) => const TextFormFieldDemo(), ), GalleryDemo( @@ -473,134 +452,114 @@ List _buildGalleryDemos() { icon: GalleryIcons.tooltip, category: _kMaterialComponents, routeName: TooltipDemo.routeName, - documentationUrl: - 'https://docs.flutter.io/flutter/material/Tooltip-class.html', + documentationUrl: 'https://docs.flutter.io/flutter/material/Tooltip-class.html', buildRoute: (BuildContext context) => TooltipDemo(), ), - // Media -// GalleryDemo( -// title: 'Animated images', -// subtitle: 'GIF and WebP animations', -// icon: GalleryIcons.animation, -// category: _kMedia, -// routeName: ImagesDemo.routeName, -// buildRoute: (BuildContext context) => ImagesDemo(), -// ), -// GalleryDemo( -// title: 'Video', -// subtitle: 'Video playback', -// icon: GalleryIcons.drive_video, -// category: _kMedia, -// routeName: VideoDemo.routeName, -// buildRoute: (BuildContext context) => const VideoDemo(), -// ), // Cupertino Components -// GalleryDemo( -// title: 'Activity Indicator', -// icon: GalleryIcons.cupertino_progress, -// category: _kCupertinoComponents, -// routeName: CupertinoProgressIndicatorDemo.routeName, -// documentationUrl: -// 'https://docs.flutter.io/flutter/cupertino/CupertinoActivityIndicator-class.html', -// buildRoute: (BuildContext context) => CupertinoProgressIndicatorDemo(), -// ), -// GalleryDemo( -// title: 'Alerts', -// icon: GalleryIcons.dialogs, -// category: _kCupertinoComponents, -// routeName: CupertinoAlertDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/showCupertinoDialog.html', -// buildRoute: (BuildContext context) => CupertinoAlertDemo(), -// ), -// GalleryDemo( -// title: 'Buttons', -// icon: GalleryIcons.generic_buttons, -// category: _kCupertinoComponents, -// routeName: CupertinoButtonsDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoButton-class.html', -// buildRoute: (BuildContext context) => CupertinoButtonsDemo(), -// ), -// GalleryDemo( -// title: 'Navigation', -// icon: GalleryIcons.bottom_navigation, -// category: _kCupertinoComponents, -// routeName: CupertinoNavigationDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoTabScaffold-class.html', -// buildRoute: (BuildContext context) => CupertinoNavigationDemo(), -// ), -// GalleryDemo( -// title: 'Pickers', -// icon: GalleryIcons.event, -// category: _kCupertinoComponents, -// routeName: CupertinoPickerDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoPicker-class.html', -// buildRoute: (BuildContext context) => CupertinoPickerDemo(), -// ), -// GalleryDemo( -// title: 'Pull to refresh', -// icon: GalleryIcons.cupertino_pull_to_refresh, -// category: _kCupertinoComponents, -// routeName: CupertinoRefreshControlDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSliverRefreshControl-class.html', -// buildRoute: (BuildContext context) => CupertinoRefreshControlDemo(), -// ), -// GalleryDemo( -// title: 'Segmented Control', -// icon: GalleryIcons.tabs, -// category: _kCupertinoComponents, -// routeName: CupertinoSegmentedControlDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSegmentedControl-class.html', -// buildRoute: (BuildContext context) => CupertinoSegmentedControlDemo(), -// ), -// GalleryDemo( -// title: 'Sliders', -// icon: GalleryIcons.sliders, -// category: _kCupertinoComponents, -// routeName: CupertinoSliderDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSlider-class.html', -// buildRoute: (BuildContext context) => CupertinoSliderDemo(), -// ), -// GalleryDemo( -// title: 'Switches', -// icon: GalleryIcons.cupertino_switch, -// category: _kCupertinoComponents, -// routeName: CupertinoSwitchDemo.routeName, -// documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSwitch-class.html', -// buildRoute: (BuildContext context) => CupertinoSwitchDemo(), -// ), -// GalleryDemo( -// title: 'Text Fields', -// icon: GalleryIcons.text_fields_alt, -// category: _kCupertinoComponents, -// routeName: CupertinoTextFieldDemo.routeName, -// buildRoute: (BuildContext context) => CupertinoTextFieldDemo(), -// ), -// -// // Media -// GalleryDemo( -// title: 'Animated images', -// subtitle: 'GIF and WebP animations', -// icon: GalleryIcons.animation, -// category: _kMedia, -// routeName: ImagesDemo.routeName, -// buildRoute: (BuildContext context) => ImagesDemo(), -// ), -// GalleryDemo( -// title: 'Video', -// subtitle: 'Video playback', -// icon: GalleryIcons.drive_video, -// category: _kMedia, -// routeName: VideoDemo.routeName, -// buildRoute: (BuildContext context) => const VideoDemo(), -// ), + GalleryDemo( + title: 'Activity Indicator', + icon: GalleryIcons.cupertino_progress, + category: _kCupertinoComponents, + routeName: CupertinoProgressIndicatorDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoActivityIndicator-class.html', + buildRoute: (BuildContext context) => CupertinoProgressIndicatorDemo(), + ), + GalleryDemo( + title: 'Alerts', + icon: GalleryIcons.dialogs, + category: _kCupertinoComponents, + routeName: CupertinoAlertDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/showCupertinoDialog.html', + buildRoute: (BuildContext context) => CupertinoAlertDemo(), + ), + GalleryDemo( + title: 'Buttons', + icon: GalleryIcons.generic_buttons, + category: _kCupertinoComponents, + routeName: CupertinoButtonsDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoButton-class.html', + buildRoute: (BuildContext context) => CupertinoButtonsDemo(), + ), + GalleryDemo( + title: 'Navigation', + icon: GalleryIcons.bottom_navigation, + category: _kCupertinoComponents, + routeName: CupertinoNavigationDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoTabScaffold-class.html', + buildRoute: (BuildContext context) => CupertinoNavigationDemo(), + ), + GalleryDemo( + title: 'Pickers', + icon: GalleryIcons.event, + category: _kCupertinoComponents, + routeName: CupertinoPickerDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoPicker-class.html', + buildRoute: (BuildContext context) => CupertinoPickerDemo(), + ), + GalleryDemo( + title: 'Pull to refresh', + icon: GalleryIcons.cupertino_pull_to_refresh, + category: _kCupertinoComponents, + routeName: CupertinoRefreshControlDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSliverRefreshControl-class.html', + buildRoute: (BuildContext context) => CupertinoRefreshControlDemo(), + ), + GalleryDemo( + title: 'Segmented Control', + icon: GalleryIcons.tabs, + category: _kCupertinoComponents, + routeName: CupertinoSegmentedControlDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSegmentedControl-class.html', + buildRoute: (BuildContext context) => CupertinoSegmentedControlDemo(), + ), + GalleryDemo( + title: 'Sliders', + icon: GalleryIcons.sliders, + category: _kCupertinoComponents, + routeName: CupertinoSliderDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSlider-class.html', + buildRoute: (BuildContext context) => CupertinoSliderDemo(), + ), + GalleryDemo( + title: 'Switches', + icon: GalleryIcons.cupertino_switch, + category: _kCupertinoComponents, + routeName: CupertinoSwitchDemo.routeName, + documentationUrl: 'https://docs.flutter.io/flutter/cupertino/CupertinoSwitch-class.html', + buildRoute: (BuildContext context) => CupertinoSwitchDemo(), + ), + GalleryDemo( + title: 'Text Fields', + icon: GalleryIcons.text_fields_alt, + category: _kCupertinoComponents, + routeName: CupertinoTextFieldDemo.routeName, + buildRoute: (BuildContext context) => CupertinoTextFieldDemo(), + ), + + // Media + GalleryDemo( + title: 'Animated images', + subtitle: 'GIF and WebP animations', + icon: GalleryIcons.animation, + category: _kMedia, + routeName: ImagesDemo.routeName, + buildRoute: (BuildContext context) => ImagesDemo(), + ), + GalleryDemo( + title: 'Video', + subtitle: 'Video playback', + icon: GalleryIcons.drive_video, + category: _kMedia, + routeName: VideoDemo.routeName, + buildRoute: (BuildContext context) => const VideoDemo(), + ), ]; // Keep Pesto around for its regression test value. It is not included // in (release builds) the performance tests. assert(() { - galleryDemos.insert( - 0, + galleryDemos.insert(0, GalleryDemo( title: 'Pesto', subtitle: 'Simple recipe browser', @@ -618,23 +577,20 @@ List _buildGalleryDemos() { final List kAllGalleryDemos = _buildGalleryDemos(); -final Set kAllGalleryDemoCategories = kAllGalleryDemos - .map((GalleryDemo demo) => demo.category) - .toSet(); +final Set kAllGalleryDemoCategories = + kAllGalleryDemos.map((GalleryDemo demo) => demo.category).toSet(); final Map> kGalleryCategoryToDemos = - Map>.fromIterable( - kAllGalleryDemoCategories, - value: (dynamic category) { - return kAllGalleryDemos - .where((GalleryDemo demo) => demo.category == category) - .toList(); - }, -); + Map>.fromIterable( + kAllGalleryDemoCategories, + value: (dynamic category) { + return kAllGalleryDemos.where((GalleryDemo demo) => demo.category == category).toList(); + }, + ); final Map kDemoDocumentationUrl = Map.fromIterable( - kAllGalleryDemos.where((GalleryDemo demo) => demo.documentationUrl != null), - key: (dynamic demo) => demo.routeName, - value: (dynamic demo) => demo.documentationUrl, -); + kAllGalleryDemos.where((GalleryDemo demo) => demo.documentationUrl != null), + key: (dynamic demo) => demo.routeName, + value: (dynamic demo) => demo.documentationUrl, + ); diff --git a/web/gallery/lib/gallery/example_code.dart b/web/gallery/lib/gallery/example_code.dart new file mode 100644 index 000000000..c20be2e1a --- /dev/null +++ b/web/gallery/lib/gallery/example_code.dart @@ -0,0 +1,286 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note: This code is not runnable, it contains code snippets displayed in the +// gallery. + +import 'package:flutter/material.dart'; + +class ButtonsDemo { + void setState(VoidCallback callback) { } + BuildContext context; + + void buttons() { + +// START buttons_raised +// Create a raised button. +RaisedButton( + child: const Text('BUTTON TITLE'), + onPressed: () { + // Perform some action + }, +); + +// Create a disabled button. +// Buttons are disabled when onPressed isn't +// specified or is null. +const RaisedButton( + child: Text('BUTTON TITLE'), + onPressed: null, +); + +// Create a button with an icon and a +// title. +RaisedButton.icon( + icon: const Icon(Icons.add, size: 18.0), + label: const Text('BUTTON TITLE'), + onPressed: () { + // Perform some action + }, +); +// END + +// START buttons_outline +// Create an outline button. +OutlineButton( + child: const Text('BUTTON TITLE'), + onPressed: () { + // Perform some action + }, +); + +// Create a disabled button. +// Buttons are disabled when onPressed isn't +// specified or is null. +const OutlineButton( + child: Text('BUTTON TITLE'), + onPressed: null, +); + +// Create a button with an icon and a +// title. +OutlineButton.icon( + icon: const Icon(Icons.add, size: 18.0), + label: const Text('BUTTON TITLE'), + onPressed: () { + // Perform some action + }, +); +// END + +// START buttons_flat +// Create a flat button. +FlatButton( + child: const Text('BUTTON TITLE'), + onPressed: () { + // Perform some action + }, +); + +// Create a disabled button. +// Buttons are disabled when onPressed isn't +// specified or is null. +const FlatButton( + child: Text('BUTTON TITLE'), + onPressed: null, +); +// END + + +// START buttons_dropdown +// Member variable holding value. +String dropdownValue; + +// Dropdown button with string values. +DropdownButton( + value: dropdownValue, + onChanged: (String newValue) { + // null indicates the user didn't select a + // new value. + setState(() { + if (newValue != null) + dropdownValue = newValue; + }); + }, + items: ['One', 'Two', 'Free', 'Four'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value)); + }) + .toList(), +); +// END + + +// START buttons_icon +// Member variable holding toggle value. +bool value; + +// Toggleable icon button. +IconButton( + icon: const Icon(Icons.thumb_up), + onPressed: () { + setState(() => value = !value); + }, + color: value ? Theme.of(context).primaryColor : null, +); +// END + + +// START buttons_action +// Floating action button in Scaffold. +Scaffold( + appBar: AppBar( + title: const Text('Demo'), + ), + floatingActionButton: const FloatingActionButton( + child: Icon(Icons.add), + onPressed: null, + ), +); +// END + } +} + + +class SelectionControls { + void setState(VoidCallback callback) { } + + void selectionControls() { + +// START selectioncontrols_checkbox +// Member variable holding the checkbox's value. +bool checkboxValue = false; + +// Create a checkbox. +Checkbox( + value: checkboxValue, + onChanged: (bool value) { + setState(() { + checkboxValue = value; + }); + }, +); + +// Create a tristate checkbox. +Checkbox( + tristate: true, + value: checkboxValue, + onChanged: (bool value) { + setState(() { + checkboxValue = value; + }); + }, +); + +// Create a disabled checkbox. +// Checkboxes are disabled when onChanged isn't +// specified or null. +const Checkbox(value: false, onChanged: null); +// END + + +// START selectioncontrols_radio +// Member variable holding value. +int radioValue = 0; + +// Method setting value. +void handleRadioValueChanged(int value) { + setState(() { + radioValue = value; + }); +} + +// Creates a set of radio buttons. +Row( + children: [ + Radio( + value: 0, + groupValue: radioValue, + onChanged: handleRadioValueChanged, + ), + Radio( + value: 1, + groupValue: radioValue, + onChanged: handleRadioValueChanged, + ), + Radio( + value: 2, + groupValue: radioValue, + onChanged: handleRadioValueChanged, + ), + ], +); + +// Creates a disabled radio button. +const Radio( + value: 0, + groupValue: 0, + onChanged: null, +); +// END + + +// START selectioncontrols_switch +// Member variable holding value. +bool switchValue = false; + +// Create a switch. +Switch( + value: switchValue, + onChanged: (bool value) { + setState(() { + switchValue = value; + } + ); +}); + +// Create a disabled switch. +// Switches are disabled when onChanged isn't +// specified or null. +const Switch(value: false, onChanged: null); +// END + } +} + + +class GridLists { + void gridlists() { +// START gridlists +// Creates a scrollable grid list with images +// loaded from the web. +GridView.count( + crossAxisCount: 3, + childAspectRatio: 1.0, + padding: const EdgeInsets.all(4.0), + mainAxisSpacing: 4.0, + crossAxisSpacing: 4.0, + children: [ + 'https://example.com/image-0.jpg', + 'https://example.com/image-1.jpg', + 'https://example.com/image-2.jpg', + '...', + 'https://example.com/image-n.jpg', + ].map((String url) { + return GridTile( + footer: GridTileBar( + title: Text(url), + ), + child: Image.network(url, fit: BoxFit.cover), + ); + }).toList(), +); +// END + } +} + + +class AnimatedImage { + void animatedImage() { +// START animated_image +Image.network('https://example.com/animated-image.gif'); +// END + } +} diff --git a/web/gallery/lib/gallery/example_code_parser.dart b/web/gallery/lib/gallery/example_code_parser.dart new file mode 100644 index 000000000..7f5029249 --- /dev/null +++ b/web/gallery/lib/gallery/example_code_parser.dart @@ -0,0 +1,55 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; + +const String _kStartTag = '// START '; +const String _kEndTag = '// END'; + +Map _exampleCode; + +Future getExampleCode(String tag, AssetBundle bundle) async { + if (_exampleCode == null) + await _parseExampleCode(bundle); + return _exampleCode[tag]; +} + +Future _parseExampleCode(AssetBundle bundle) async { + final String code = await bundle.loadString('lib/gallery/example_code.dart') ?? + '// lib/gallery/example_code.dart not found\n'; + _exampleCode = {}; + + final List lines = code.split('\n'); + + List codeBlock; + String codeTag; + + for (String line in lines) { + if (codeBlock == null) { + // Outside a block. + if (line.startsWith(_kStartTag)) { + // Starting a new code block. + codeBlock = []; + codeTag = line.substring(_kStartTag.length).trim(); + } else { + // Just skipping the line. + } + } else { + // Inside a block. + if (line.startsWith(_kEndTag)) { + // Add the block. + _exampleCode[codeTag] = codeBlock.join('\n'); + codeBlock = null; + codeTag = null; + } else { + // Add to the current block + // trimRight() to remove any \r on Windows + // without removing any useful indentation + codeBlock.add(line.trimRight()); + } + } + } +} diff --git a/web/gallery/lib/gallery/home.dart b/web/gallery/lib/gallery/home.dart index 9598d8ef1..38810876c 100644 --- a/web/gallery/lib/gallery/home.dart +++ b/web/gallery/lib/gallery/home.dart @@ -2,20 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math' as math; +import 'dart:async'; import 'dart:developer'; +import 'dart:math' as math; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; import 'backdrop.dart'; import 'demos.dart'; +const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; const Color _kFlutterBlue = Color(0xFF003D75); const double _kDemoItemHeight = 64.0; const Duration _kFrontLayerSwitchDuration = Duration(milliseconds: 300); class _FlutterLogo extends StatelessWidget { - const _FlutterLogo({Key key}) : super(key: key); + const _FlutterLogo({ Key key }) : super(key: key); @override Widget build(BuildContext context) { @@ -27,7 +31,7 @@ class _FlutterLogo extends StatelessWidget { image: DecorationImage( image: AssetImage( 'logos/flutter_white/logo.png', - //package: _kGalleryAssetsPackage, + package: _kGalleryAssetsPackage, ), ), ), @@ -41,7 +45,7 @@ class _CategoryItem extends StatelessWidget { Key key, this.category, this.onTap, - }) : super(key: key); + }) : super (key: key); final GalleryDemoCategory category; final VoidCallback onTap; @@ -56,6 +60,7 @@ class _CategoryItem extends StatelessWidget { return RepaintBoundary( child: RawMaterialButton( padding: EdgeInsets.zero, + hoverColor: theme.primaryColor.withOpacity(0.05), splashColor: theme.primaryColor.withOpacity(0.12), highlightColor: Colors.transparent, onPressed: onTap, @@ -105,8 +110,7 @@ class _CategoriesPage extends StatelessWidget { Widget build(BuildContext context) { const double aspectRatio = 160.0 / 180.0; final List categoriesList = categories.toList(); - final int columnCount = - (MediaQuery.of(context).orientation == Orientation.portrait) ? 2 : 3; + final int columnCount = (MediaQuery.of(context).orientation == Orientation.portrait) ? 2 : 3; return Semantics( scopesRoute: true, @@ -117,11 +121,9 @@ class _CategoriesPage extends StatelessWidget { key: const PageStorageKey('categories'), child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - final double columnWidth = - constraints.biggest.width / columnCount.toDouble(); + final double columnWidth = constraints.biggest.width / columnCount.toDouble(); final double rowHeight = math.min(225.0, columnWidth * aspectRatio); - final int rowCount = - (categories.length + columnCount - 1) ~/ columnCount; + final int rowCount = (categories.length + columnCount - 1) ~/ columnCount; // This repaint boundary prevents the inner contents of the front layer // from repainting when the backdrop toggle triggers a repaint on the @@ -132,16 +134,13 @@ class _CategoriesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: List.generate(rowCount, (int rowIndex) { final int columnCountForRow = rowIndex == rowCount - 1 - ? categories.length - - columnCount * math.max(0, rowCount - 1) - : columnCount; + ? categories.length - columnCount * math.max(0, rowCount - 1) + : columnCount; return Row( - children: List.generate(columnCountForRow, - (int columnIndex) { + children: List.generate(columnCountForRow, (int columnIndex) { final int index = rowIndex * columnCount + columnIndex; - final GalleryDemoCategory category = - categoriesList[index]; + final GalleryDemoCategory category = categoriesList[index]; return SizedBox( width: columnWidth, @@ -166,7 +165,7 @@ class _CategoriesPage extends StatelessWidget { } class _DemoItem extends StatelessWidget { - const _DemoItem({Key key, this.demo}) : super(key: key); + const _DemoItem({ Key key, this.demo }) : super(key: key); final GalleryDemo demo; @@ -198,8 +197,9 @@ class _DemoItem extends StatelessWidget { titleChildren.add( Text( demo.subtitle, - style: theme.textTheme.body1 - .copyWith(color: isDark ? Colors.white : const Color(0xFF60646B)), + style: theme.textTheme.body1.copyWith( + color: isDark ? Colors.white : const Color(0xFF60646B) + ), ), ); } @@ -212,8 +212,7 @@ class _DemoItem extends StatelessWidget { _launchDemo(context); }, child: Container( - constraints: - BoxConstraints(minHeight: _kDemoItemHeight * textScaleFactor), + constraints: BoxConstraints(minHeight: _kDemoItemHeight * textScaleFactor), child: Row( children: [ Container( @@ -252,18 +251,17 @@ class _DemosPage extends StatelessWidget { // safe areas. final double windowBottomPadding = MediaQuery.of(context).padding.bottom; return KeyedSubtree( - key: const ValueKey( - 'GalleryDemoList'), // So the tests can find this ListView + key: const ValueKey('GalleryDemoList'), // So the tests can find this ListView child: Semantics( scopesRoute: true, namesRoute: true, label: category.name, explicitChildNodes: true, child: ListView( + dragStartBehavior: DragStartBehavior.down, key: PageStorageKey(category.name), padding: EdgeInsets.only(top: 8.0, bottom: windowBottomPadding), - children: - kGalleryCategoryToDemos[category].map((GalleryDemo demo) { + children: kGalleryCategoryToDemos[category].map((GalleryDemo demo) { return _DemoItem(demo: demo); }).toList(), ), @@ -290,25 +288,22 @@ class GalleryHome extends StatefulWidget { _GalleryHomeState createState() => _GalleryHomeState(); } -class _GalleryHomeState extends State - with SingleTickerProviderStateMixin { - static final GlobalKey _scaffoldKey = - GlobalKey(); +class _GalleryHomeState extends State with SingleTickerProviderStateMixin { + static final GlobalKey _scaffoldKey = GlobalKey(); AnimationController _controller; GalleryDemoCategory _category; - static Widget _topHomeLayout( - Widget currentChild, List previousChildren) { + static Widget _topHomeLayout(Widget currentChild, List previousChildren) { List children = previousChildren; - if (currentChild != null) children = children.toList()..add(currentChild); + if (currentChild != null) + children = children.toList()..add(currentChild); return Stack( children: children, alignment: Alignment.topCenter, ); } - static const AnimatedSwitcherLayoutBuilder _centerHomeLayout = - AnimatedSwitcher.defaultLayoutBuilder; + static const AnimatedSwitcherLayoutBuilder _centerHomeLayout = AnimatedSwitcher.defaultLayoutBuilder; @override void initState() { @@ -331,11 +326,9 @@ class _GalleryHomeState extends State final ThemeData theme = Theme.of(context); final bool isDark = theme.brightness == Brightness.dark; final MediaQueryData media = MediaQuery.of(context); - final bool centerHome = - media.orientation == Orientation.portrait && media.size.height < 800.0; + final bool centerHome = media.orientation == Orientation.portrait && media.size.height < 800.0; - const Curve switchOutCurve = - Interval(0.4, 1.0, curve: Curves.fastOutSlowIn); + const Curve switchOutCurve = Interval(0.4, 1.0, curve: Curves.fastOutSlowIn); const Curve switchInCurve = Interval(0.4, 1.0, curve: Curves.fastOutSlowIn); Widget home = Scaffold( @@ -360,18 +353,18 @@ class _GalleryHomeState extends State switchOutCurve: switchOutCurve, switchInCurve: switchInCurve, child: _category == null - ? const _FlutterLogo() - : IconButton( - icon: const BackButtonIcon(), - tooltip: 'Back', - onPressed: () => setState(() => _category = null), - ), + ? const _FlutterLogo() + : IconButton( + icon: const BackButtonIcon(), + tooltip: 'Back', + onPressed: () => setState(() => _category = null), + ), ), frontTitle: AnimatedSwitcher( duration: _kFrontLayerSwitchDuration, child: _category == null - ? const Text('Flutter web gallery') - : Text(_category.name), + ? const Text('Flutter gallery') + : Text(_category.name), ), frontHeading: widget.testMode ? null : Container(height: 24.0), frontLayer: AnimatedSwitcher( @@ -380,13 +373,13 @@ class _GalleryHomeState extends State switchInCurve: switchInCurve, layoutBuilder: centerHome ? _centerHomeLayout : _topHomeLayout, child: _category != null - ? _DemosPage(_category) - : _CategoriesPage( - categories: kAllGalleryDemoCategories, - onCategoryTap: (GalleryDemoCategory category) { - setState(() => _category = category); - }, - ), + ? _DemosPage(_category) + : _CategoriesPage( + categories: kAllGalleryDemoCategories, + onCategoryTap: (GalleryDemoCategory category) { + setState(() => _category = category); + }, + ), ), ), ), @@ -399,19 +392,24 @@ class _GalleryHomeState extends State }()); if (GalleryHome.showPreviewBanner) { - home = Stack(fit: StackFit.expand, children: [ - home, - FadeTransition( - opacity: - CurvedAnimation(parent: _controller, curve: Curves.easeInOut), + home = Stack( + fit: StackFit.expand, + children: [ + home, + FadeTransition( + opacity: CurvedAnimation(parent: _controller, curve: Curves.easeInOut), child: const Banner( message: 'PREVIEW', location: BannerLocation.topEnd, - )), - ]); + ), + ), + ], + ); } home = AnnotatedRegion( - child: home, value: SystemUiOverlayStyle.light); + child: home, + value: SystemUiOverlayStyle.light, + ); return home; } diff --git a/web/gallery/lib/gallery/icons.dart b/web/gallery/lib/gallery/icons.dart index d571a5449..c82ba7377 100644 --- a/web/gallery/lib/gallery/icons.dart +++ b/web/gallery/lib/gallery/icons.dart @@ -2,72 +2,49 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class GalleryIcons { GalleryIcons._(); static const IconData tooltip = IconData(0xe900, fontFamily: 'GalleryIcons'); - static const IconData text_fields_alt = - IconData(0xe901, fontFamily: 'GalleryIcons'); + static const IconData text_fields_alt = IconData(0xe901, fontFamily: 'GalleryIcons'); static const IconData tabs = IconData(0xe902, fontFamily: 'GalleryIcons'); static const IconData switches = IconData(0xe903, fontFamily: 'GalleryIcons'); static const IconData sliders = IconData(0xe904, fontFamily: 'GalleryIcons'); static const IconData shrine = IconData(0xe905, fontFamily: 'GalleryIcons'); - static const IconData sentiment_very_satisfied = - IconData(0xe906, fontFamily: 'GalleryIcons'); + static const IconData sentiment_very_satisfied = IconData(0xe906, fontFamily: 'GalleryIcons'); static const IconData refresh = IconData(0xe907, fontFamily: 'GalleryIcons'); - static const IconData progress_activity = - IconData(0xe908, fontFamily: 'GalleryIcons'); - static const IconData phone_iphone = - IconData(0xe909, fontFamily: 'GalleryIcons'); - static const IconData page_control = - IconData(0xe90a, fontFamily: 'GalleryIcons'); - static const IconData more_vert = - IconData(0xe90b, fontFamily: 'GalleryIcons'); + static const IconData progress_activity = IconData(0xe908, fontFamily: 'GalleryIcons'); + static const IconData phone_iphone = IconData(0xe909, fontFamily: 'GalleryIcons'); + static const IconData page_control = IconData(0xe90a, fontFamily: 'GalleryIcons'); + static const IconData more_vert = IconData(0xe90b, fontFamily: 'GalleryIcons'); static const IconData menu = IconData(0xe90c, fontFamily: 'GalleryIcons'); static const IconData list_alt = IconData(0xe90d, fontFamily: 'GalleryIcons'); static const IconData grid_on = IconData(0xe90e, fontFamily: 'GalleryIcons'); - static const IconData expand_all = - IconData(0xe90f, fontFamily: 'GalleryIcons'); + static const IconData expand_all = IconData(0xe90f, fontFamily: 'GalleryIcons'); static const IconData event = IconData(0xe910, fontFamily: 'GalleryIcons'); - static const IconData drive_video = - IconData(0xe911, fontFamily: 'GalleryIcons'); + static const IconData drive_video = IconData(0xe911, fontFamily: 'GalleryIcons'); static const IconData dialogs = IconData(0xe912, fontFamily: 'GalleryIcons'); - static const IconData data_table = - IconData(0xe913, fontFamily: 'GalleryIcons'); - static const IconData custom_typography = - IconData(0xe914, fontFamily: 'GalleryIcons'); + static const IconData data_table = IconData(0xe913, fontFamily: 'GalleryIcons'); + static const IconData custom_typography = IconData(0xe914, fontFamily: 'GalleryIcons'); static const IconData colors = IconData(0xe915, fontFamily: 'GalleryIcons'); static const IconData chips = IconData(0xe916, fontFamily: 'GalleryIcons'); - static const IconData check_box = - IconData(0xe917, fontFamily: 'GalleryIcons'); + static const IconData check_box = IconData(0xe917, fontFamily: 'GalleryIcons'); static const IconData cards = IconData(0xe918, fontFamily: 'GalleryIcons'); static const IconData buttons = IconData(0xe919, fontFamily: 'GalleryIcons'); - static const IconData bottom_sheets = - IconData(0xe91a, fontFamily: 'GalleryIcons'); - static const IconData bottom_navigation = - IconData(0xe91b, fontFamily: 'GalleryIcons'); - static const IconData animation = - IconData(0xe91c, fontFamily: 'GalleryIcons'); - static const IconData account_box = - IconData(0xe91d, fontFamily: 'GalleryIcons'); + static const IconData bottom_sheets = IconData(0xe91a, fontFamily: 'GalleryIcons'); + static const IconData bottom_navigation = IconData(0xe91b, fontFamily: 'GalleryIcons'); + static const IconData animation = IconData(0xe91c, fontFamily: 'GalleryIcons'); + static const IconData account_box = IconData(0xe91d, fontFamily: 'GalleryIcons'); static const IconData snackbar = IconData(0xe91e, fontFamily: 'GalleryIcons'); - static const IconData category_mdc = - IconData(0xe91f, fontFamily: 'GalleryIcons'); - static const IconData cupertino_progress = - IconData(0xe920, fontFamily: 'GalleryIcons'); - static const IconData cupertino_pull_to_refresh = - IconData(0xe921, fontFamily: 'GalleryIcons'); - static const IconData cupertino_switch = - IconData(0xe922, fontFamily: 'GalleryIcons'); - static const IconData generic_buttons = - IconData(0xe923, fontFamily: 'GalleryIcons'); + static const IconData category_mdc = IconData(0xe91f, fontFamily: 'GalleryIcons'); + static const IconData cupertino_progress = IconData(0xe920, fontFamily: 'GalleryIcons'); + static const IconData cupertino_pull_to_refresh = IconData(0xe921, fontFamily: 'GalleryIcons'); + static const IconData cupertino_switch = IconData(0xe922, fontFamily: 'GalleryIcons'); + static const IconData generic_buttons = IconData(0xe923, fontFamily: 'GalleryIcons'); static const IconData backdrop = IconData(0xe924, fontFamily: 'GalleryIcons'); - static const IconData bottom_app_bar = - IconData(0xe925, fontFamily: 'GalleryIcons'); - static const IconData bottom_sheet_persistent = - IconData(0xe926, fontFamily: 'GalleryIcons'); - static const IconData lists_leave_behind = - IconData(0xe927, fontFamily: 'GalleryIcons'); + static const IconData bottom_app_bar = IconData(0xe925, fontFamily: 'GalleryIcons'); + static const IconData bottom_sheet_persistent = IconData(0xe926, fontFamily: 'GalleryIcons'); + static const IconData lists_leave_behind = IconData(0xe927, fontFamily: 'GalleryIcons'); } diff --git a/web/gallery/lib/gallery/options.dart b/web/gallery/lib/gallery/options.dart index f49569b34..6076d0108 100644 --- a/web/gallery/lib/gallery/options.dart +++ b/web/gallery/lib/gallery/options.dart @@ -2,15 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'about.dart'; import 'scales.dart'; -import 'themes.dart'; class GalleryOptions { GalleryOptions({ - this.theme, + this.themeMode, this.textScaleFactor, this.textDirection = TextDirection.ltr, this.timeDilation = 1.0, @@ -20,7 +19,7 @@ class GalleryOptions { this.showPerformanceOverlay = false, }); - final GalleryTheme theme; + final ThemeMode themeMode; final GalleryTextScaleValue textScaleFactor; final TextDirection textDirection; final double timeDilation; @@ -30,7 +29,7 @@ class GalleryOptions { final bool showOffscreenLayersCheckerboard; GalleryOptions copyWith({ - GalleryTheme theme, + ThemeMode themeMode, GalleryTextScaleValue textScaleFactor, TextDirection textDirection, double timeDilation, @@ -40,59 +39,54 @@ class GalleryOptions { bool showOffscreenLayersCheckerboard, }) { return GalleryOptions( - theme: theme ?? this.theme, + themeMode: themeMode ?? this.themeMode, textScaleFactor: textScaleFactor ?? this.textScaleFactor, textDirection: textDirection ?? this.textDirection, timeDilation: timeDilation ?? this.timeDilation, platform: platform ?? this.platform, - showPerformanceOverlay: - showPerformanceOverlay ?? this.showPerformanceOverlay, - showOffscreenLayersCheckerboard: showOffscreenLayersCheckerboard ?? - this.showOffscreenLayersCheckerboard, - showRasterCacheImagesCheckerboard: showRasterCacheImagesCheckerboard ?? - this.showRasterCacheImagesCheckerboard, + showPerformanceOverlay: showPerformanceOverlay ?? this.showPerformanceOverlay, + showOffscreenLayersCheckerboard: showOffscreenLayersCheckerboard ?? this.showOffscreenLayersCheckerboard, + showRasterCacheImagesCheckerboard: showRasterCacheImagesCheckerboard ?? this.showRasterCacheImagesCheckerboard, ); } @override bool operator ==(dynamic other) { - if (runtimeType != other.runtimeType) return false; + if (runtimeType != other.runtimeType) + return false; final GalleryOptions typedOther = other; - return theme == typedOther.theme && - textScaleFactor == typedOther.textScaleFactor && - textDirection == typedOther.textDirection && - platform == typedOther.platform && - showPerformanceOverlay == typedOther.showPerformanceOverlay && - showRasterCacheImagesCheckerboard == - typedOther.showRasterCacheImagesCheckerboard && - showOffscreenLayersCheckerboard == - typedOther.showRasterCacheImagesCheckerboard; + return themeMode == typedOther.themeMode + && textScaleFactor == typedOther.textScaleFactor + && textDirection == typedOther.textDirection + && platform == typedOther.platform + && showPerformanceOverlay == typedOther.showPerformanceOverlay + && showRasterCacheImagesCheckerboard == typedOther.showRasterCacheImagesCheckerboard + && showOffscreenLayersCheckerboard == typedOther.showRasterCacheImagesCheckerboard; } @override int get hashCode => hashValues( - theme, - textScaleFactor, - textDirection, - timeDilation, - platform, - showPerformanceOverlay, - showRasterCacheImagesCheckerboard, - showOffscreenLayersCheckerboard, - ); + themeMode, + textScaleFactor, + textDirection, + timeDilation, + platform, + showPerformanceOverlay, + showRasterCacheImagesCheckerboard, + showOffscreenLayersCheckerboard, + ); @override String toString() { - return '$runtimeType($theme)'; + return '$runtimeType($themeMode)'; } } const double _kItemHeight = 48.0; -const EdgeInsetsDirectional _kItemPadding = - EdgeInsetsDirectional.only(start: 56.0); +const EdgeInsetsDirectional _kItemPadding = EdgeInsetsDirectional.only(start: 56.0); class _OptionsItem extends StatelessWidget { - const _OptionsItem({Key key, this.child}) : super(key: key); + const _OptionsItem({ Key key, this.child }) : super(key: key); final Widget child; @@ -120,7 +114,7 @@ class _OptionsItem extends StatelessWidget { } class _BooleanItem extends StatelessWidget { - const _BooleanItem(this.title, this.value, this.onChanged, {this.switchKey}); + const _BooleanItem(this.title, this.value, this.onChanged, { this.switchKey }); final String title; final bool value; @@ -166,7 +160,7 @@ class _ActionItem extends StatelessWidget { } class _FlatButton extends StatelessWidget { - const _FlatButton({Key key, this.onPressed, this.child}) : super(key: key); + const _FlatButton({ Key key, this.onPressed, this.child }) : super(key: key); final VoidCallback onPressed; final Widget child; @@ -207,25 +201,55 @@ class _Heading extends StatelessWidget { } } -class _ThemeItem extends StatelessWidget { - const _ThemeItem(this.options, this.onOptionsChanged); +class _ThemeModeItem extends StatelessWidget { + const _ThemeModeItem(this.options, this.onOptionsChanged); final GalleryOptions options; final ValueChanged onOptionsChanged; + static final Map modeLabels = { + ThemeMode.system: 'System Default', + ThemeMode.light: 'Light', + ThemeMode.dark: 'Dark', + }; + @override Widget build(BuildContext context) { - return _BooleanItem( - 'Dark Theme', - options.theme == kDarkGalleryTheme, - (bool value) { - onOptionsChanged( - options.copyWith( - theme: value ? kDarkGalleryTheme : kLightGalleryTheme, + return _OptionsItem( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Theme'), + Text( + '${modeLabels[options.themeMode]}', + style: Theme.of(context).primaryTextTheme.body1, + ), + ], + ), ), - ); - }, - switchKey: const Key('dark_theme'), + PopupMenuButton( + padding: const EdgeInsetsDirectional.only(end: 16.0), + icon: const Icon(Icons.arrow_drop_down), + initialValue: options.themeMode, + itemBuilder: (BuildContext context) { + return ThemeMode.values.map>((ThemeMode mode) { + return PopupMenuItem( + value: mode, + child: Text(modeLabels[mode]), + ); + }).toList(); + }, + onSelected: (ThemeMode mode) { + onOptionsChanged( + options.copyWith(themeMode: mode), + ); + }, + ), + ], + ), ); } } @@ -257,9 +281,7 @@ class _TextScaleFactorItem extends StatelessWidget { padding: const EdgeInsetsDirectional.only(end: 16.0), icon: const Icon(Icons.arrow_drop_down), itemBuilder: (BuildContext context) { - return kAllGalleryTextScaleValues - .map>( - (GalleryTextScaleValue scaleValue) { + return kAllGalleryTextScaleValues.map>((GalleryTextScaleValue scaleValue) { return PopupMenuItem( value: scaleValue, child: Text(scaleValue.label), @@ -331,7 +353,7 @@ class _PlatformItem extends StatelessWidget { final ValueChanged onOptionsChanged; String _platformLabel(TargetPlatform platform) { - switch (platform) { + switch(platform) { case TargetPlatform.android: return 'Mountain View'; case TargetPlatform.fuchsia: @@ -353,10 +375,10 @@ class _PlatformItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Platform mechanics'), - Text( - '${_platformLabel(options.platform)}', - style: Theme.of(context).primaryTextTheme.body1, - ), + Text( + '${_platformLabel(options.platform)}', + style: Theme.of(context).primaryTextTheme.body1, + ), ], ), ), @@ -398,9 +420,10 @@ class GalleryOptionsPage extends StatelessWidget { List _enabledDiagnosticItems() { // Boolean showFoo options with a value of null: don't display // the showFoo option at all. - if (null == options.showOffscreenLayersCheckerboard ?? - options.showRasterCacheImagesCheckerboard ?? - options.showPerformanceOverlay) return const []; + if (options.showOffscreenLayersCheckerboard == null && + options.showRasterCacheImagesCheckerboard == null && + options.showPerformanceOverlay == null) + return const []; final List items = [ const Divider(), @@ -409,11 +432,13 @@ class GalleryOptionsPage extends StatelessWidget { if (options.showOffscreenLayersCheckerboard != null) { items.add( - _BooleanItem('Highlight offscreen layers', - options.showOffscreenLayersCheckerboard, (bool value) { - onOptionsChanged( - options.copyWith(showOffscreenLayersCheckerboard: value)); - }), + _BooleanItem( + 'Highlight offscreen layers', + options.showOffscreenLayersCheckerboard, + (bool value) { + onOptionsChanged(options.copyWith(showOffscreenLayersCheckerboard: value)); + }, + ), ); } if (options.showRasterCacheImagesCheckerboard != null) { @@ -422,8 +447,7 @@ class GalleryOptionsPage extends StatelessWidget { 'Highlight raster cache images', options.showRasterCacheImagesCheckerboard, (bool value) { - onOptionsChanged( - options.copyWith(showRasterCacheImagesCheckerboard: value)); + onOptionsChanged(options.copyWith(showRasterCacheImagesCheckerboard: value)); }, ), ); @@ -453,27 +477,21 @@ class GalleryOptionsPage extends StatelessWidget { padding: const EdgeInsets.only(bottom: 124.0), children: [ const _Heading('Display'), - _ThemeItem(options, onOptionsChanged), + _ThemeModeItem(options, onOptionsChanged), _TextScaleFactorItem(options, onOptionsChanged), _TextDirectionItem(options, onOptionsChanged), _TimeDilationItem(options, onOptionsChanged), const Divider(), const _Heading('Platform mechanics'), _PlatformItem(options, onOptionsChanged), - ] - ..addAll( - _enabledDiagnosticItems(), - ) - ..addAll( - [ - const Divider(), - const _Heading('Flutter Web gallery'), - _ActionItem('About Flutter Web Gallery', () { - showGalleryAboutDialog(context); - }), - _ActionItem('Send feedback', onSendFeedback), - ], - ), + ..._enabledDiagnosticItems(), + const Divider(), + const _Heading('Flutter gallery'), + _ActionItem('About Flutter Gallery', () { + showGalleryAboutDialog(context); + }), + _ActionItem('Send feedback', onSendFeedback), + ], ), ); } diff --git a/web/gallery/lib/gallery/scales.dart b/web/gallery/lib/gallery/scales.dart index 34236909c..3e96db11e 100644 --- a/web/gallery/lib/gallery/scales.dart +++ b/web/gallery/lib/gallery/scales.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class GalleryTextScaleValue { const GalleryTextScaleValue(this.scale, this.label); @@ -12,7 +12,8 @@ class GalleryTextScaleValue { @override bool operator ==(dynamic other) { - if (runtimeType != other.runtimeType) return false; + if (runtimeType != other.runtimeType) + return false; final GalleryTextScaleValue typedOther = other; return scale == typedOther.scale && label == typedOther.label; } @@ -24,10 +25,10 @@ class GalleryTextScaleValue { String toString() { return '$runtimeType($label)'; } + } -const List kAllGalleryTextScaleValues = - [ +const List kAllGalleryTextScaleValues = [ GalleryTextScaleValue(null, 'System Default'), GalleryTextScaleValue(0.8, 'Small'), GalleryTextScaleValue(1.0, 'Normal'), diff --git a/web/gallery/lib/gallery/syntax_highlighter.dart b/web/gallery/lib/gallery/syntax_highlighter.dart new file mode 100644 index 000000000..7d4e7d6ef --- /dev/null +++ b/web/gallery/lib/gallery/syntax_highlighter.dart @@ -0,0 +1,356 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:string_scanner/string_scanner.dart'; + +class SyntaxHighlighterStyle { + SyntaxHighlighterStyle({ + this.baseStyle, + this.numberStyle, + this.commentStyle, + this.keywordStyle, + this.stringStyle, + this.punctuationStyle, + this.classStyle, + this.constantStyle, + }); + + static SyntaxHighlighterStyle lightThemeStyle() { + return SyntaxHighlighterStyle( + baseStyle: const TextStyle(color: Color(0xFF000000)), + numberStyle: const TextStyle(color: Color(0xFF1565C0)), + commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), + keywordStyle: const TextStyle(color: Color(0xFF9C27B0)), + stringStyle: const TextStyle(color: Color(0xFF43A047)), + punctuationStyle: const TextStyle(color: Color(0xFF000000)), + classStyle: const TextStyle(color: Color(0xFF512DA8)), + constantStyle: const TextStyle(color: Color(0xFF795548)), + ); + } + + static SyntaxHighlighterStyle darkThemeStyle() { + return SyntaxHighlighterStyle( + baseStyle: const TextStyle(color: Color(0xFFFFFFFF)), + numberStyle: const TextStyle(color: Color(0xFF1565C0)), + commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), + keywordStyle: const TextStyle(color: Color(0xFF80CBC4)), + stringStyle: const TextStyle(color: Color(0xFF009688)), + punctuationStyle: const TextStyle(color: Color(0xFFFFFFFF)), + classStyle: const TextStyle(color: Color(0xFF009688)), + constantStyle: const TextStyle(color: Color(0xFF795548)), + ); + } + + final TextStyle baseStyle; + final TextStyle numberStyle; + final TextStyle commentStyle; + final TextStyle keywordStyle; + final TextStyle stringStyle; + final TextStyle punctuationStyle; + final TextStyle classStyle; + final TextStyle constantStyle; +} + +abstract class SyntaxHighlighter { + TextSpan format(String src); +} + +class DartSyntaxHighlighter extends SyntaxHighlighter { + DartSyntaxHighlighter([this._style]) { + _spans = <_HighlightSpan>[]; + _style ??= SyntaxHighlighterStyle.darkThemeStyle(); + } + + SyntaxHighlighterStyle _style; + + static const List _keywords = [ + 'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch', + 'class', 'const', 'continue', 'default', 'deferred', 'do', 'dynamic', 'else', + 'enum', 'export', 'external', 'extends', 'factory', 'false', 'final', + 'finally', 'for', 'get', 'if', 'implements', 'import', 'in', 'is', 'library', + 'new', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static', + 'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var', + 'void', 'while', 'with', 'yield', + ]; + + static const List _builtInTypes = [ + 'int', 'double', 'num', 'bool', + ]; + + String _src; + StringScanner _scanner; + + List<_HighlightSpan> _spans; + + @override + TextSpan format(String src) { + _src = src; + _scanner = StringScanner(_src); + + if (_generateSpans()) { + // Successfully parsed the code + final List formattedText = []; + int currentPosition = 0; + + for (_HighlightSpan span in _spans) { + if (currentPosition != span.start) + formattedText.add(TextSpan(text: _src.substring(currentPosition, span.start))); + + formattedText.add(TextSpan(style: span.textStyle(_style), text: span.textForSpan(_src))); + + currentPosition = span.end; + } + + if (currentPosition != _src.length) + formattedText.add(TextSpan(text: _src.substring(currentPosition, _src.length))); + + return TextSpan(style: _style.baseStyle, children: formattedText); + } else { + // Parsing failed, return with only basic formatting + return TextSpan(style: _style.baseStyle, text: src); + } + } + + bool _generateSpans() { + int lastLoopPosition = _scanner.position; + + while (!_scanner.isDone) { + // Skip White space + _scanner.scan(RegExp(r'\s+')); + + // Block comments + if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) { + _spans.add(_HighlightSpan( + _HighlightType.comment, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Line comments + if (_scanner.scan('//')) { + final int startComment = _scanner.lastMatch.start; + + bool eof = false; + int endComment; + if (_scanner.scan(RegExp(r'.*\n'))) { + endComment = _scanner.lastMatch.end - 1; + } else { + eof = true; + endComment = _src.length; + } + + _spans.add(_HighlightSpan( + _HighlightType.comment, + startComment, + endComment, + )); + + if (eof) + break; + + continue; + } + + // Raw r"String" + if (_scanner.scan(RegExp(r'r".*"'))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Raw r'String' + if (_scanner.scan(RegExp(r"r'.*'"))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Multiline """String""" + if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Multiline '''String''' + if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // "String" + if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // 'String' + if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) { + _spans.add(_HighlightSpan( + _HighlightType.string, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Double + if (_scanner.scan(RegExp(r'\d+\.\d+'))) { + _spans.add(_HighlightSpan( + _HighlightType.number, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Integer + if (_scanner.scan(RegExp(r'\d+'))) { + _spans.add(_HighlightSpan( + _HighlightType.number, + _scanner.lastMatch.start, + _scanner.lastMatch.end) + ); + continue; + } + + // Punctuation + if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) { + _spans.add(_HighlightSpan( + _HighlightType.punctuation, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Meta data + if (_scanner.scan(RegExp(r'@\w+'))) { + _spans.add(_HighlightSpan( + _HighlightType.keyword, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + continue; + } + + // Words + if (_scanner.scan(RegExp(r'\w+'))) { + _HighlightType type; + + String word = _scanner.lastMatch[0]; + if (word.startsWith('_')) + word = word.substring(1); + + if (_keywords.contains(word)) + type = _HighlightType.keyword; + else if (_builtInTypes.contains(word)) + type = _HighlightType.keyword; + else if (_firstLetterIsUpperCase(word)) + type = _HighlightType.klass; + else if (word.length >= 2 && word.startsWith('k') && _firstLetterIsUpperCase(word.substring(1))) + type = _HighlightType.constant; + + if (type != null) { + _spans.add(_HighlightSpan( + type, + _scanner.lastMatch.start, + _scanner.lastMatch.end, + )); + } + } + + // Check if this loop did anything + if (lastLoopPosition == _scanner.position) { + // Failed to parse this file, abort gracefully + return false; + } + lastLoopPosition = _scanner.position; + } + + _simplify(); + return true; + } + + void _simplify() { + for (int i = _spans.length - 2; i >= 0; i -= 1) { + if (_spans[i].type == _spans[i + 1].type && _spans[i].end == _spans[i + 1].start) { + _spans[i] = _HighlightSpan( + _spans[i].type, + _spans[i].start, + _spans[i + 1].end, + ); + _spans.removeAt(i + 1); + } + } + } + + bool _firstLetterIsUpperCase(String str) { + if (str.isNotEmpty) { + final String first = str.substring(0, 1); + return first == first.toUpperCase(); + } + return false; + } +} + +enum _HighlightType { + number, + comment, + keyword, + string, + punctuation, + klass, + constant +} + +class _HighlightSpan { + _HighlightSpan(this.type, this.start, this.end); + final _HighlightType type; + final int start; + final int end; + + String textForSpan(String src) { + return src.substring(start, end); + } + + TextStyle textStyle(SyntaxHighlighterStyle style) { + if (type == _HighlightType.number) + return style.numberStyle; + else if (type == _HighlightType.comment) + return style.commentStyle; + else if (type == _HighlightType.keyword) + return style.keywordStyle; + else if (type == _HighlightType.string) + return style.stringStyle; + else if (type == _HighlightType.punctuation) + return style.punctuationStyle; + else if (type == _HighlightType.klass) + return style.classStyle; + else if (type == _HighlightType.constant) + return style.constantStyle; + else + return style.baseStyle; + } +} diff --git a/web/gallery/lib/gallery/themes.dart b/web/gallery/lib/gallery/themes.dart index ba04c47ab..78430128b 100644 --- a/web/gallery/lib/gallery/themes.dart +++ b/web/gallery/lib/gallery/themes.dart @@ -2,19 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; -class GalleryTheme { - const GalleryTheme._(this.name, this.data); - - final String name; - final ThemeData data; -} - -final GalleryTheme kDarkGalleryTheme = - GalleryTheme._('Dark', _buildDarkTheme()); -final GalleryTheme kLightGalleryTheme = - GalleryTheme._('Light', _buildLightTheme()); +final ThemeData kLightGalleryTheme = _buildLightTheme(); +final ThemeData kDarkGalleryTheme = _buildDarkTheme(); TextTheme _buildTextTheme(TextTheme base) { return base.copyWith( @@ -27,15 +18,19 @@ TextTheme _buildTextTheme(TextTheme base) { ThemeData _buildDarkTheme() { const Color primaryColor = Color(0xFF0175c2); const Color secondaryColor = Color(0xFF13B9FD); - final ThemeData base = ThemeData.dark(); final ColorScheme colorScheme = const ColorScheme.dark().copyWith( primary: primaryColor, secondary: secondaryColor, ); - return base.copyWith( + final ThemeData base = ThemeData( + brightness: Brightness.dark, + accentColorBrightness: Brightness.dark, primaryColor: primaryColor, + primaryColorDark: const Color(0xFF0050a0), + primaryColorLight: secondaryColor, buttonColor: primaryColor, indicatorColor: Colors.white, + toggleableActiveColor: const Color(0xFF6997DF), accentColor: secondaryColor, canvasColor: const Color(0xFF202124), scaffoldBackgroundColor: const Color(0xFF202124), @@ -45,6 +40,8 @@ ThemeData _buildDarkTheme() { colorScheme: colorScheme, textTheme: ButtonTextTheme.primary, ), + ); + return base.copyWith( textTheme: _buildTextTheme(base.textTheme), primaryTextTheme: _buildTextTheme(base.primaryTextTheme), accentTextTheme: _buildTextTheme(base.accentTextTheme), @@ -58,12 +55,14 @@ ThemeData _buildLightTheme() { primary: primaryColor, secondary: secondaryColor, ); - final ThemeData base = ThemeData.light(); - return base.copyWith( + final ThemeData base = ThemeData( + brightness: Brightness.light, + accentColorBrightness: Brightness.dark, colorScheme: colorScheme, primaryColor: primaryColor, buttonColor: primaryColor, indicatorColor: Colors.white, + toggleableActiveColor: const Color(0xFF1E88E5), splashColor: Colors.white24, splashFactory: InkRipple.splashFactory, accentColor: secondaryColor, @@ -75,6 +74,8 @@ ThemeData _buildLightTheme() { colorScheme: colorScheme, textTheme: ButtonTextTheme.primary, ), + ); + return base.copyWith( textTheme: _buildTextTheme(base.textTheme), primaryTextTheme: _buildTextTheme(base.primaryTextTheme), accentTextTheme: _buildTextTheme(base.accentTextTheme), diff --git a/web/gallery/lib/gallery/updater.dart b/web/gallery/lib/gallery/updater.dart new file mode 100644 index 000000000..c27a109b5 --- /dev/null +++ b/web/gallery/lib/gallery/updater.dart @@ -0,0 +1,75 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:url_launcher/url_launcher.dart'; + +typedef UpdateUrlFetcher = Future Function(); + +class Updater extends StatefulWidget { + const Updater({ @required this.updateUrlFetcher, this.child, Key key }) + : assert(updateUrlFetcher != null), + super(key: key); + + final UpdateUrlFetcher updateUrlFetcher; + final Widget child; + + @override + State createState() => UpdaterState(); +} + +class UpdaterState extends State { + @override + void initState() { + super.initState(); + _checkForUpdates(); + } + + static DateTime _lastUpdateCheck; + Future _checkForUpdates() async { + // Only prompt once a day + if (_lastUpdateCheck != null && + DateTime.now().difference(_lastUpdateCheck) < const Duration(days: 1)) { + return; // We already checked for updates recently + } + _lastUpdateCheck = DateTime.now(); + + final String updateUrl = await widget.updateUrlFetcher(); + if (updateUrl != null) { + final bool wantsUpdate = await showDialog(context: context, builder: _buildDialog); + if (wantsUpdate != null && wantsUpdate) + launch(updateUrl); + } + } + + Widget _buildDialog(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TextStyle dialogTextStyle = + theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); + return AlertDialog( + title: const Text('Update Flutter Gallery?'), + content: Text('A newer version is available.', style: dialogTextStyle), + actions: [ + FlatButton( + child: const Text('NO THANKS'), + onPressed: () { + Navigator.pop(context, false); + }, + ), + FlatButton( + child: const Text('UPDATE'), + onPressed: () { + Navigator.pop(context, true); + }, + ), + ], + ); + } + + @override + Widget build(BuildContext context) => widget.child; +} diff --git a/web/gallery/lib/main.dart b/web/gallery/lib/main.dart index 6877ddabe..0f0093d76 100644 --- a/web/gallery/lib/main.dart +++ b/web/gallery/lib/main.dart @@ -1,11 +1,14 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +// Thanks for checking out Flutter! +// Like what you see? Tweet us @FlutterDev + +import 'package:flutter/material.dart'; import 'gallery/app.dart'; void main() { - runApp(GalleryApp()); + runApp(const GalleryApp()); } diff --git a/web/gallery/lib/main_houdini.dart b/web/gallery/lib/main_houdini.dart deleted file mode 100644 index a199749a3..000000000 --- a/web/gallery/lib/main_houdini.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web_ui/ui.dart' as ui; - -import 'main.dart' as app; - -void main() { - ui.persistedPictureFactory = ui.houdiniPictureFactory; - app.main(); -} diff --git a/web/gallery/lib/main_publish.dart b/web/gallery/lib/main_publish.dart new file mode 100644 index 000000000..5b4c37f7c --- /dev/null +++ b/web/gallery/lib/main_publish.dart @@ -0,0 +1,13 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'gallery/home.dart'; +import 'main.dart' as other_main; + +// This main chain-calls main.dart's main. This file is used for publishing +// the gallery and removes the 'PREVIEW' banner. +void main() { + GalleryHome.showPreviewBanner = false; + other_main.main(); +} diff --git a/web/gallery/pubspec.lock b/web/gallery/pubspec.lock index ce7e1157d..691da9a03 100644 --- a/web/gallery/pubspec.lock +++ b/web/gallery/pubspec.lock @@ -2,256 +2,169 @@ # See https://dart.dev/tools/pub/glossary#lockfile packages: analyzer: - dependency: transitive + dependency: "direct dev" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.37.0" + version: "0.36.4" archive: - dependency: transitive + dependency: "direct dev" description: name: archive url: "https://pub.dartlang.org" source: hosted version: "2.0.10" args: - dependency: transitive + dependency: "direct dev" description: name: args url: "https://pub.dartlang.org" source: hosted version: "1.5.2" async: - dependency: transitive + dependency: "direct dev" description: name: async url: "https://pub.dartlang.org" source: hosted version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" boolean_selector: - dependency: transitive + dependency: "direct dev" description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted version: "1.0.5" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" charcode: - dependency: transitive + dependency: "direct main" description: name: charcode url: "https://pub.dartlang.org" source: hosted version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + connectivity: + dependency: "direct main" + description: + name: connectivity + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.3+7" convert: - dependency: transitive + dependency: "direct dev" description: name: convert url: "https://pub.dartlang.org" source: hosted version: "2.1.1" crypto: - dependency: transitive + dependency: "direct dev" description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.2" csslib: - dependency: transitive + dependency: "direct dev" description: name: csslib url: "https://pub.dartlang.org" source: hosted version: "0.16.1" - dart_style: - dependency: transitive + cupertino_icons: + dependency: "direct main" description: - name: dart_style + name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.2.9" - fixnum: - dependency: transitive + version: "0.1.2" + device_info: + dependency: "direct main" description: - name: fixnum + name: device_info url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" - flutter_web: - dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - flutter_web_test: + version: "0.4.0+2" + file: dependency: "direct dev" description: - path: "packages/flutter_web_test" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.8+1" + flutter: + dependency: "direct main" + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: + flutter_gallery_assets: dependency: "direct main" description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + name: flutter_gallery_assets + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.9+2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk version: "0.0.0" front_end: - dependency: transitive + dependency: "direct dev" description: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.20" + version: "0.1.19" glob: - dependency: transitive + dependency: "direct dev" description: name: glob url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" html: - dependency: transitive + dependency: "direct dev" description: name: html url: "https://pub.dartlang.org" source: hosted version: "0.14.0+2" http: - dependency: transitive + dependency: "direct dev" description: name: http url: "https://pub.dartlang.org" source: hosted version: "0.12.0+2" http_multi_server: - dependency: transitive + dependency: "direct dev" description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted version: "2.1.0" http_parser: - dependency: transitive + dependency: "direct dev" description: name: http_parser url: "https://pub.dartlang.org" source: hosted version: "3.1.3" + image: + dependency: "direct dev" + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" intl: dependency: "direct main" description: @@ -260,224 +173,229 @@ packages: source: hosted version: "0.15.8" io: - dependency: transitive + dependency: "direct dev" description: name: io url: "https://pub.dartlang.org" source: hosted version: "0.3.3" js: - dependency: transitive + dependency: "direct dev" description: name: js url: "https://pub.dartlang.org" source: hosted version: "0.6.1+1" - json_annotation: - dependency: transitive + json_rpc_2: + dependency: "direct dev" description: - name: json_annotation + name: json_rpc_2 url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.1.0" kernel: - dependency: transitive + dependency: "direct dev" description: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" + version: "0.3.19" matcher: - dependency: transitive + dependency: "direct dev" description: name: matcher url: "https://pub.dartlang.org" source: hosted version: "0.12.5" meta: - dependency: transitive + dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted version: "1.1.7" mime: - dependency: transitive + dependency: "direct dev" description: name: mime url: "https://pub.dartlang.org" source: hosted version: "0.9.6+3" multi_server_socket: - dependency: transitive + dependency: "direct dev" description: name: multi_server_socket url: "https://pub.dartlang.org" source: hosted version: "1.0.2" node_preamble: - dependency: transitive + dependency: "direct dev" description: name: node_preamble url: "https://pub.dartlang.org" source: hosted version: "1.4.6" package_config: - dependency: transitive + dependency: "direct dev" description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" package_resolver: - dependency: transitive + dependency: "direct dev" description: name: package_resolver url: "https://pub.dartlang.org" source: hosted version: "1.0.10" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" source: hosted version: "1.6.4" pedantic: - dependency: transitive + dependency: "direct dev" description: name: pedantic url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" + petitparser: + dependency: "direct dev" + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + platform: + dependency: "direct dev" + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" pool: - dependency: transitive + dependency: "direct dev" description: name: pool url: "https://pub.dartlang.org" source: hosted version: "1.4.0" - protobuf: - dependency: transitive + process: + dependency: "direct dev" description: - name: protobuf + name: process url: "https://pub.dartlang.org" source: hosted - version: "0.13.15" + version: "3.0.11" pub_semver: - dependency: transitive + dependency: "direct dev" description: name: pub_semver url: "https://pub.dartlang.org" source: hosted version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" quiver: - dependency: transitive + dependency: "direct dev" description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive + version: "2.0.5" + scoped_model: + dependency: "direct main" description: - name: scratch_space + name: scoped_model url: "https://pub.dartlang.org" source: hosted - version: "0.0.4" + version: "1.0.1" shelf: - dependency: transitive + dependency: "direct dev" description: name: shelf url: "https://pub.dartlang.org" source: hosted version: "0.7.5" shelf_packages_handler: - dependency: transitive + dependency: "direct dev" description: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted version: "1.0.4" shelf_static: - dependency: transitive + dependency: "direct dev" description: name: shelf_static url: "https://pub.dartlang.org" source: hosted version: "0.2.8" shelf_web_socket: - dependency: transitive + dependency: "direct dev" description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted version: "0.2.3" - source_map_stack_trace: + shrine_images: + dependency: "direct main" + description: + name: shrine_images + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + sky_engine: dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: "direct dev" description: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted version: "1.1.5" source_maps: - dependency: transitive + dependency: "direct dev" description: name: source_maps url: "https://pub.dartlang.org" source: hosted version: "0.10.8" source_span: - dependency: transitive + dependency: "direct main" description: name: source_span url: "https://pub.dartlang.org" source: hosted version: "1.5.5" stack_trace: - dependency: transitive + dependency: "direct dev" description: name: stack_trace url: "https://pub.dartlang.org" source: hosted version: "1.9.3" stream_channel: - dependency: transitive + dependency: "direct dev" description: name: stream_channel url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" string_scanner: - dependency: transitive + dependency: "direct main" description: name: string_scanner url: "https://pub.dartlang.org" source: hosted version: "1.0.5" term_glyph: - dependency: transitive + dependency: "direct main" description: name: term_glyph url: "https://pub.dartlang.org" @@ -489,69 +407,84 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.6.5" + version: "1.6.3" test_api: - dependency: transitive + dependency: "direct dev" description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.6" + version: "0.2.5" test_core: - dependency: transitive + dependency: "direct dev" description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.2.7" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" + version: "0.2.5" typed_data: - dependency: transitive + dependency: "direct main" description: name: typed_data url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.2" vector_math: - dependency: transitive + dependency: "direct main" description: name: vector_math url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - vm_service_lib: - dependency: transitive + video_player: + dependency: "direct main" description: - name: vm_service_lib + name: video_player url: "https://pub.dartlang.org" source: hosted - version: "3.22.2+1" + version: "0.10.1+6" + vm_service_client: + dependency: "direct dev" + description: + name: vm_service_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.6+2" watcher: - dependency: transitive + dependency: "direct dev" description: name: watcher url: "https://pub.dartlang.org" source: hosted version: "0.9.7+12" web_socket_channel: - dependency: transitive + dependency: "direct dev" description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.14" + version: "1.0.15" + xml: + dependency: "direct dev" + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" yaml: - dependency: transitive + dependency: "direct dev" description: name: yaml url: "https://pub.dartlang.org" source: hosted version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.4.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/web/gallery/pubspec.yaml b/web/gallery/pubspec.yaml index 077f1c1b3..f255482c2 100644 --- a/web/gallery/pubspec.yaml +++ b/web/gallery/pubspec.yaml @@ -1,31 +1,258 @@ -name: flutter_web.examples.gallery +name: flutter_gallery environment: - sdk: ">=2.2.0 <3.0.0" + # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. + sdk: ">=2.2.2 <3.0.0" dependencies: - flutter_web: any - flutter_web_ui: any - intl: ^0.15.7 + flutter: + sdk: flutter + collection: 1.14.11 + device_info: 0.4.0+2 + intl: 0.15.8 + connectivity: 0.4.3+7 + string_scanner: 1.0.5 + url_launcher: 5.1.2 + cupertino_icons: 0.1.2 + video_player: 0.10.1+6 + scoped_model: 1.0.1 + shrine_images: 1.1.2 + + # Also update dev/benchmarks/complex_layout/pubspec.yaml + flutter_gallery_assets: 0.1.9+2 + + charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - build_runner: any - build_web_compilers: any - flutter_web_test: any - test: ^1.0.0 - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_test: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_test - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui + flutter_test: + sdk: flutter + test: 1.6.3 + + analyzer: 0.36.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + archive: 2.0.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + async: 2.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 5.0.8+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + front_end: 0.1.19 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 1.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + html: 0.14.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http: 0.12.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + kernel: 0.3.19 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 1.4.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_resolver: 1.0.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + platform: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + process: 3.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.2.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.2.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 1.0.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 2.1.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + assets: + - lib/gallery/example_code.dart + - packages/flutter_gallery_assets/people/ali_landscape.png + - packages/flutter_gallery_assets/monochrome/red-square-1024x1024.png + - packages/flutter_gallery_assets/logos/flutter_white/logo.png + - packages/flutter_gallery_assets/logos/fortnightly/fortnightly_logo.png + - packages/flutter_gallery_assets/videos/bee.mp4 + - packages/flutter_gallery_assets/videos/butterfly.mp4 + - packages/flutter_gallery_assets/animated_images/animated_flutter_lgtm.gif + - packages/flutter_gallery_assets/animated_images/animated_flutter_stickers.webp + - packages/flutter_gallery_assets/food/butternut_squash_soup.png + - packages/flutter_gallery_assets/food/cherry_pie.png + - packages/flutter_gallery_assets/food/chopped_beet_leaves.png + - packages/flutter_gallery_assets/food/fruits.png + - packages/flutter_gallery_assets/food/pesto_pasta.png + - packages/flutter_gallery_assets/food/roasted_chicken.png + - packages/flutter_gallery_assets/food/spanakopita.png + - packages/flutter_gallery_assets/food/spinach_onion_salad.png + - packages/flutter_gallery_assets/food/icons/fish.png + - packages/flutter_gallery_assets/food/icons/healthy.png + - packages/flutter_gallery_assets/food/icons/main.png + - packages/flutter_gallery_assets/food/icons/meat.png + - packages/flutter_gallery_assets/food/icons/quick.png + - packages/flutter_gallery_assets/food/icons/spicy.png + - packages/flutter_gallery_assets/food/icons/veggie.png + - packages/flutter_gallery_assets/logos/pesto/logo_small.png + - packages/flutter_gallery_assets/places/india_chennai_flower_market.png + - packages/flutter_gallery_assets/places/india_thanjavur_market.png + - packages/flutter_gallery_assets/places/india_tanjore_bronze_works.png + - packages/flutter_gallery_assets/places/india_tanjore_market_merchant.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple.png + - packages/flutter_gallery_assets/places/india_pondicherry_salt_farm.png + - packages/flutter_gallery_assets/places/india_chennai_highway.png + - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple_carvings.png + - packages/flutter_gallery_assets/places/india_chettinad_produce.png + - packages/flutter_gallery_assets/places/india_tanjore_market_technology.png + - packages/flutter_gallery_assets/places/india_pondicherry_beach.png + - packages/flutter_gallery_assets/places/india_pondicherry_fisherman.png + - packages/flutter_gallery_assets/products/backpack.png + - packages/flutter_gallery_assets/products/belt.png + - packages/flutter_gallery_assets/products/cup.png + - packages/flutter_gallery_assets/products/deskset.png + - packages/flutter_gallery_assets/products/dress.png + - packages/flutter_gallery_assets/products/earrings.png + - packages/flutter_gallery_assets/products/flatwear.png + - packages/flutter_gallery_assets/products/hat.png + - packages/flutter_gallery_assets/products/jacket.png + - packages/flutter_gallery_assets/products/jumper.png + - packages/flutter_gallery_assets/products/kitchen_quattro.png + - packages/flutter_gallery_assets/products/napkins.png + - packages/flutter_gallery_assets/products/planters.png + - packages/flutter_gallery_assets/products/platter.png + - packages/flutter_gallery_assets/products/scarf.png + - packages/flutter_gallery_assets/products/shirt.png + - packages/flutter_gallery_assets/products/sunnies.png + - packages/flutter_gallery_assets/products/sweater.png + - packages/flutter_gallery_assets/products/sweats.png + - packages/flutter_gallery_assets/products/table.png + - packages/flutter_gallery_assets/products/teaset.png + - packages/flutter_gallery_assets/products/top.png + - packages/flutter_gallery_assets/people/ali.png + - packages/flutter_gallery_assets/people/square/ali.png + - packages/flutter_gallery_assets/people/square/peter.png + - packages/flutter_gallery_assets/people/square/sandra.png + - packages/flutter_gallery_assets/people/square/stella.png + - packages/flutter_gallery_assets/people/square/trevor.png + - packages/shrine_images/diamond.png + - packages/shrine_images/slanted_menu.png + - packages/shrine_images/0-0.jpg + - packages/shrine_images/1-0.jpg + - packages/shrine_images/2-0.jpg + - packages/shrine_images/3-0.jpg + - packages/shrine_images/4-0.jpg + - packages/shrine_images/5-0.jpg + - packages/shrine_images/6-0.jpg + - packages/shrine_images/7-0.jpg + - packages/shrine_images/8-0.jpg + - packages/shrine_images/9-0.jpg + - packages/shrine_images/10-0.jpg + - packages/shrine_images/11-0.jpg + - packages/shrine_images/12-0.jpg + - packages/shrine_images/13-0.jpg + - packages/shrine_images/14-0.jpg + - packages/shrine_images/15-0.jpg + - packages/shrine_images/16-0.jpg + - packages/shrine_images/17-0.jpg + - packages/shrine_images/18-0.jpg + - packages/shrine_images/19-0.jpg + - packages/shrine_images/20-0.jpg + - packages/shrine_images/21-0.jpg + - packages/shrine_images/22-0.jpg + - packages/shrine_images/23-0.jpg + - packages/shrine_images/24-0.jpg + - packages/shrine_images/25-0.jpg + - packages/shrine_images/26-0.jpg + - packages/shrine_images/27-0.jpg + - packages/shrine_images/28-0.jpg + - packages/shrine_images/29-0.jpg + - packages/shrine_images/30-0.jpg + - packages/shrine_images/31-0.jpg + - packages/shrine_images/32-0.jpg + - packages/shrine_images/33-0.jpg + - packages/shrine_images/34-0.jpg + - packages/shrine_images/35-0.jpg + - packages/shrine_images/36-0.jpg + - packages/shrine_images/37-0.jpg + - preview.png + + fonts: + - family: Raleway + fonts: + - asset: packages/flutter_gallery_assets/fonts/raleway/Raleway-Regular.ttf + - asset: packages/flutter_gallery_assets/fonts/raleway/Raleway-Medium.ttf + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/raleway/Raleway-SemiBold.ttf + weight: 600 + - family: AbrilFatface + fonts: + - asset: packages/flutter_gallery_assets/fonts/abrilfatface/AbrilFatface-Regular.ttf + - family: GalleryIcons + fonts: + - asset: packages/flutter_gallery_assets/fonts/private/gallery_icons/GalleryIcons.ttf + - family: GoogleSans + fonts: + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-BoldItalic.ttf + weight: 700 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Bold.ttf + weight: 700 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Italic.ttf + weight: 400 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-MediumItalic.ttf + weight: 500 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Medium.ttf + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Regular.ttf + weight: 400 + - family: GoogleSansDisplay + fonts: + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-BoldItalic.ttf + weight: 700 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Bold.ttf + weight: 700 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Italic.ttf + weight: 400 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-MediumItalic.ttf + style: italic + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Medium.ttf + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Regular.ttf + weight: 400 + - family: LibreFranklin + fonts: + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Bold.ttf + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Light.ttf + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Medium.ttf + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Regular.ttf + - family: Merriweather + fonts: + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-BlackItalic.ttf + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Italic.ttf + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf + +# PUBSPEC CHECKSUM: ba0e diff --git a/web/gallery/test/demo/material/text_form_field_demo_test.dart b/web/gallery/test/demo/material/text_form_field_demo_test.dart index 1083a2def..27594e85d 100644 --- a/web/gallery/test/demo/material/text_form_field_demo_test.dart +++ b/web/gallery/test/demo/material/text_form_field_demo_test.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; -import 'package:flutter_web.examples.gallery/demo/material/text_form_field_demo.dart'; -import 'package:flutter_web_test/flutter_web_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gallery/demo/material/text_form_field_demo.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('validates name field correctly', (WidgetTester tester) async { diff --git a/web/gallery/test/gallery_test.dart b/web/gallery/test/gallery_test.dart index b3f9d7fcd..2204418e9 100644 --- a/web/gallery/test/gallery_test.dart +++ b/web/gallery/test/gallery_test.dart @@ -2,9 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web_test/flutter_web_test.dart'; - -import 'package:flutter_web.examples.gallery/gallery/app.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_gallery/gallery/app.dart'; void main() { testWidgets('Gallery starts', (WidgetTester tester) async { diff --git a/web/gallery/web/assets/AbrilFatface-Regular.ttf b/web/gallery/web/assets/AbrilFatface-Regular.ttf deleted file mode 100644 index e761f7b9c..000000000 Binary files a/web/gallery/web/assets/AbrilFatface-Regular.ttf and /dev/null differ diff --git a/web/gallery/web/assets/FontManifest.json b/web/gallery/web/assets/FontManifest.json deleted file mode 100644 index da07ed4f7..000000000 --- a/web/gallery/web/assets/FontManifest.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "family": "MaterialIcons", - "fonts": [ - { - "asset": "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" - } - ] - }, - { - "family": "GoogleSans", - "fonts": [ - { - "asset": "GoogleSans-Regular.ttf" - } - ] - }, - { - "family": "GalleryIcons", - "fonts": [ - { - "asset": "GalleryIcons.ttf" - } - ] - }, - { - "family": "AbrilFatface", - "fonts": [ - { - "asset": "AbrilFatface-Regular.ttf" - } - ] - }, - { - "family": "LibreFranklin", - "fonts": [ - { - "asset": "LibreFranklin-Regular.ttf" - } - ] - }, - { - "family": "Merriweather", - "fonts": [ - { - "asset": "Merriweather-Regular.ttf" - } - ] - }, - { - "family": "Raleway", - "fonts": [ - { - "asset": "Raleway-Regular.ttf" - } - ] - } -] diff --git a/web/gallery/web/assets/GalleryIcons.ttf b/web/gallery/web/assets/GalleryIcons.ttf deleted file mode 100644 index 53c5d2d0c..000000000 Binary files a/web/gallery/web/assets/GalleryIcons.ttf and /dev/null differ diff --git a/web/gallery/web/assets/GoogleSans-Regular.ttf b/web/gallery/web/assets/GoogleSans-Regular.ttf deleted file mode 100644 index 5bb6e03c7..000000000 Binary files a/web/gallery/web/assets/GoogleSans-Regular.ttf and /dev/null differ diff --git a/web/gallery/web/assets/LibreFranklin-Regular.ttf b/web/gallery/web/assets/LibreFranklin-Regular.ttf deleted file mode 100644 index 487c31e2a..000000000 Binary files a/web/gallery/web/assets/LibreFranklin-Regular.ttf and /dev/null differ diff --git a/web/gallery/web/assets/Merriweather-Regular.ttf b/web/gallery/web/assets/Merriweather-Regular.ttf deleted file mode 100644 index 2ee9a6978..000000000 Binary files a/web/gallery/web/assets/Merriweather-Regular.ttf and /dev/null differ diff --git a/web/gallery/web/assets/README.md b/web/gallery/web/assets/README.md deleted file mode 100644 index 29de56321..000000000 --- a/web/gallery/web/assets/README.md +++ /dev/null @@ -1,34 +0,0 @@ -Note: a reference to `MaterialIcons` is intentionally omitted because the -corresponding font is not included in this source. - -If you add `MaterialIcons-Extended.ttf` to this directory, you can update -`FontManifest.json` as follows: - -```json -[ - { - "family": "MaterialIcons", - "fonts": [ - { - "asset": "MaterialIcons-Extended.ttf" - } - ] - }, - { - "family": "GoogleSans", - "fonts": [ - { - "asset": "GoogleSans-Regular.ttf" - } - ] - }, - { - "family": "GalleryIcons", - "fonts": [ - { - "asset": "GalleryIcons.ttf" - } - ] - } -] -``` diff --git a/web/gallery/web/assets/Raleway-Regular.ttf b/web/gallery/web/assets/Raleway-Regular.ttf deleted file mode 100644 index e570a2d5c..000000000 Binary files a/web/gallery/web/assets/Raleway-Regular.ttf and /dev/null differ diff --git a/web/gallery/web/assets/food/butternut_squash_soup.png b/web/gallery/web/assets/food/butternut_squash_soup.png deleted file mode 100644 index e980d2fe0..000000000 Binary files a/web/gallery/web/assets/food/butternut_squash_soup.png and /dev/null differ diff --git a/web/gallery/web/assets/food/cherry_pie.png b/web/gallery/web/assets/food/cherry_pie.png deleted file mode 100644 index c8fff3882..000000000 Binary files a/web/gallery/web/assets/food/cherry_pie.png and /dev/null differ diff --git a/web/gallery/web/assets/food/chopped_beet_leaves.png b/web/gallery/web/assets/food/chopped_beet_leaves.png deleted file mode 100644 index 59585c97a..000000000 Binary files a/web/gallery/web/assets/food/chopped_beet_leaves.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/fish.png b/web/gallery/web/assets/food/icons/fish.png deleted file mode 100644 index c57ffc8d6..000000000 Binary files a/web/gallery/web/assets/food/icons/fish.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/healthy.png b/web/gallery/web/assets/food/icons/healthy.png deleted file mode 100644 index 8fbbf49a1..000000000 Binary files a/web/gallery/web/assets/food/icons/healthy.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/main.png b/web/gallery/web/assets/food/icons/main.png deleted file mode 100644 index 1f9ada263..000000000 Binary files a/web/gallery/web/assets/food/icons/main.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/meat.png b/web/gallery/web/assets/food/icons/meat.png deleted file mode 100644 index a84a4f436..000000000 Binary files a/web/gallery/web/assets/food/icons/meat.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/quick.png b/web/gallery/web/assets/food/icons/quick.png deleted file mode 100644 index c5728dcf5..000000000 Binary files a/web/gallery/web/assets/food/icons/quick.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/spicy.png b/web/gallery/web/assets/food/icons/spicy.png deleted file mode 100644 index 0c59eea8e..000000000 Binary files a/web/gallery/web/assets/food/icons/spicy.png and /dev/null differ diff --git a/web/gallery/web/assets/food/icons/veggie.png b/web/gallery/web/assets/food/icons/veggie.png deleted file mode 100644 index b826733f0..000000000 Binary files a/web/gallery/web/assets/food/icons/veggie.png and /dev/null differ diff --git a/web/gallery/web/assets/food/pesto_pasta.png b/web/gallery/web/assets/food/pesto_pasta.png deleted file mode 100644 index dace4d3a8..000000000 Binary files a/web/gallery/web/assets/food/pesto_pasta.png and /dev/null differ diff --git a/web/gallery/web/assets/food/roasted_chicken.png b/web/gallery/web/assets/food/roasted_chicken.png deleted file mode 100644 index 800a4a5f1..000000000 Binary files a/web/gallery/web/assets/food/roasted_chicken.png and /dev/null differ diff --git a/web/gallery/web/assets/food/spanakopita.png b/web/gallery/web/assets/food/spanakopita.png deleted file mode 100644 index 58a22b653..000000000 Binary files a/web/gallery/web/assets/food/spanakopita.png and /dev/null differ diff --git a/web/gallery/web/assets/food/spinach_onion_salad.png b/web/gallery/web/assets/food/spinach_onion_salad.png deleted file mode 100644 index e05b05c08..000000000 Binary files a/web/gallery/web/assets/food/spinach_onion_salad.png and /dev/null differ diff --git a/web/gallery/web/assets/logos/flutter_white/1.5x/logo.png b/web/gallery/web/assets/logos/flutter_white/1.5x/logo.png deleted file mode 100644 index 1449289d0..000000000 Binary files a/web/gallery/web/assets/logos/flutter_white/1.5x/logo.png and /dev/null differ diff --git a/web/gallery/web/assets/logos/flutter_white/2.5x/logo.png b/web/gallery/web/assets/logos/flutter_white/2.5x/logo.png deleted file mode 100644 index 2020e760d..000000000 Binary files a/web/gallery/web/assets/logos/flutter_white/2.5x/logo.png and /dev/null differ diff --git a/web/gallery/web/assets/logos/flutter_white/3.0x/logo.png b/web/gallery/web/assets/logos/flutter_white/3.0x/logo.png deleted file mode 100644 index b42acc11c..000000000 Binary files a/web/gallery/web/assets/logos/flutter_white/3.0x/logo.png and /dev/null differ diff --git a/web/gallery/web/assets/logos/flutter_white/4.0x/logo.png b/web/gallery/web/assets/logos/flutter_white/4.0x/logo.png deleted file mode 100644 index 58bc2d906..000000000 Binary files a/web/gallery/web/assets/logos/flutter_white/4.0x/logo.png and /dev/null differ diff --git a/web/gallery/web/assets/logos/flutter_white/logo.png b/web/gallery/web/assets/logos/flutter_white/logo.png deleted file mode 100644 index 3277025d9..000000000 Binary files a/web/gallery/web/assets/logos/flutter_white/logo.png and /dev/null differ diff --git a/web/gallery/web/assets/logos/pesto/logo_small.png b/web/gallery/web/assets/logos/pesto/logo_small.png deleted file mode 100644 index f41fbb869..000000000 Binary files a/web/gallery/web/assets/logos/pesto/logo_small.png and /dev/null differ diff --git a/web/gallery/web/assets/people/ali_landscape.png b/web/gallery/web/assets/people/ali_landscape.png deleted file mode 100644 index d886764e4..000000000 Binary files a/web/gallery/web/assets/people/ali_landscape.png and /dev/null differ diff --git a/web/gallery/web/assets/people/square/ali.png b/web/gallery/web/assets/people/square/ali.png deleted file mode 100644 index c4f2b8c8f..000000000 Binary files a/web/gallery/web/assets/people/square/ali.png and /dev/null differ diff --git a/web/gallery/web/assets/people/square/peter.png b/web/gallery/web/assets/people/square/peter.png deleted file mode 100644 index fdb5fa0a3..000000000 Binary files a/web/gallery/web/assets/people/square/peter.png and /dev/null differ diff --git a/web/gallery/web/assets/people/square/sandra.png b/web/gallery/web/assets/people/square/sandra.png deleted file mode 100644 index 098891ddc..000000000 Binary files a/web/gallery/web/assets/people/square/sandra.png and /dev/null differ diff --git a/web/gallery/web/assets/people/square/stella.png b/web/gallery/web/assets/people/square/stella.png deleted file mode 100644 index aaf0d260e..000000000 Binary files a/web/gallery/web/assets/people/square/stella.png and /dev/null differ diff --git a/web/gallery/web/assets/people/square/trevor.png b/web/gallery/web/assets/people/square/trevor.png deleted file mode 100644 index c290d3ac6..000000000 Binary files a/web/gallery/web/assets/people/square/trevor.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_chennai_flower_market.png b/web/gallery/web/assets/places/india_chennai_flower_market.png deleted file mode 100644 index 52d495022..000000000 Binary files a/web/gallery/web/assets/places/india_chennai_flower_market.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_chennai_highway.png b/web/gallery/web/assets/places/india_chennai_highway.png deleted file mode 100644 index 3d202ca44..000000000 Binary files a/web/gallery/web/assets/places/india_chennai_highway.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_chettinad_produce.png b/web/gallery/web/assets/places/india_chettinad_produce.png deleted file mode 100644 index a915d37b7..000000000 Binary files a/web/gallery/web/assets/places/india_chettinad_produce.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_chettinad_silk_maker.png b/web/gallery/web/assets/places/india_chettinad_silk_maker.png deleted file mode 100644 index 32d1f3aeb..000000000 Binary files a/web/gallery/web/assets/places/india_chettinad_silk_maker.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_pondicherry_beach.png b/web/gallery/web/assets/places/india_pondicherry_beach.png deleted file mode 100644 index 207defa27..000000000 Binary files a/web/gallery/web/assets/places/india_pondicherry_beach.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_pondicherry_fisherman.png b/web/gallery/web/assets/places/india_pondicherry_fisherman.png deleted file mode 100644 index 72b38c403..000000000 Binary files a/web/gallery/web/assets/places/india_pondicherry_fisherman.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_pondicherry_salt_farm.png b/web/gallery/web/assets/places/india_pondicherry_salt_farm.png deleted file mode 100644 index b363ef61a..000000000 Binary files a/web/gallery/web/assets/places/india_pondicherry_salt_farm.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_tanjore_bronze_works.png b/web/gallery/web/assets/places/india_tanjore_bronze_works.png deleted file mode 100644 index d4e0b121c..000000000 Binary files a/web/gallery/web/assets/places/india_tanjore_bronze_works.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_tanjore_market_merchant.png b/web/gallery/web/assets/places/india_tanjore_market_merchant.png deleted file mode 100644 index 013971754..000000000 Binary files a/web/gallery/web/assets/places/india_tanjore_market_merchant.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_tanjore_market_technology.png b/web/gallery/web/assets/places/india_tanjore_market_technology.png deleted file mode 100644 index 1bc629159..000000000 Binary files a/web/gallery/web/assets/places/india_tanjore_market_technology.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_tanjore_thanjavur_temple.png b/web/gallery/web/assets/places/india_tanjore_thanjavur_temple.png deleted file mode 100644 index 9cd0562a9..000000000 Binary files a/web/gallery/web/assets/places/india_tanjore_thanjavur_temple.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_tanjore_thanjavur_temple_carvings.png b/web/gallery/web/assets/places/india_tanjore_thanjavur_temple_carvings.png deleted file mode 100644 index 4ba27dec6..000000000 Binary files a/web/gallery/web/assets/places/india_tanjore_thanjavur_temple_carvings.png and /dev/null differ diff --git a/web/gallery/web/assets/places/india_thanjavur_market.png b/web/gallery/web/assets/places/india_thanjavur_market.png deleted file mode 100644 index 62349af18..000000000 Binary files a/web/gallery/web/assets/places/india_thanjavur_market.png and /dev/null differ diff --git a/web/gallery/web/assets/products/backpack.png b/web/gallery/web/assets/products/backpack.png deleted file mode 100644 index cb93dcc6b..000000000 Binary files a/web/gallery/web/assets/products/backpack.png and /dev/null differ diff --git a/web/gallery/web/assets/products/belt.png b/web/gallery/web/assets/products/belt.png deleted file mode 100644 index eda9b484d..000000000 Binary files a/web/gallery/web/assets/products/belt.png and /dev/null differ diff --git a/web/gallery/web/assets/products/cup.png b/web/gallery/web/assets/products/cup.png deleted file mode 100644 index 9ad1a3a9c..000000000 Binary files a/web/gallery/web/assets/products/cup.png and /dev/null differ diff --git a/web/gallery/web/assets/products/deskset.png b/web/gallery/web/assets/products/deskset.png deleted file mode 100644 index 76e6ec171..000000000 Binary files a/web/gallery/web/assets/products/deskset.png and /dev/null differ diff --git a/web/gallery/web/assets/products/dress.png b/web/gallery/web/assets/products/dress.png deleted file mode 100644 index 6b52bad60..000000000 Binary files a/web/gallery/web/assets/products/dress.png and /dev/null differ diff --git a/web/gallery/web/assets/products/earrings.png b/web/gallery/web/assets/products/earrings.png deleted file mode 100644 index 80c3bf863..000000000 Binary files a/web/gallery/web/assets/products/earrings.png and /dev/null differ diff --git a/web/gallery/web/assets/products/flatwear.png b/web/gallery/web/assets/products/flatwear.png deleted file mode 100644 index 98b6be32b..000000000 Binary files a/web/gallery/web/assets/products/flatwear.png and /dev/null differ diff --git a/web/gallery/web/assets/products/hat.png b/web/gallery/web/assets/products/hat.png deleted file mode 100644 index 7bf2a658a..000000000 Binary files a/web/gallery/web/assets/products/hat.png and /dev/null differ diff --git a/web/gallery/web/assets/products/jacket.png b/web/gallery/web/assets/products/jacket.png deleted file mode 100644 index a2c4bbe21..000000000 Binary files a/web/gallery/web/assets/products/jacket.png and /dev/null differ diff --git a/web/gallery/web/assets/products/jumper.png b/web/gallery/web/assets/products/jumper.png deleted file mode 100644 index 741d158ff..000000000 Binary files a/web/gallery/web/assets/products/jumper.png and /dev/null differ diff --git a/web/gallery/web/assets/products/kitchen_quattro.png b/web/gallery/web/assets/products/kitchen_quattro.png deleted file mode 100644 index 76dbf5377..000000000 Binary files a/web/gallery/web/assets/products/kitchen_quattro.png and /dev/null differ diff --git a/web/gallery/web/assets/products/napkins.png b/web/gallery/web/assets/products/napkins.png deleted file mode 100644 index e949fb32e..000000000 Binary files a/web/gallery/web/assets/products/napkins.png and /dev/null differ diff --git a/web/gallery/web/assets/products/planters.png b/web/gallery/web/assets/products/planters.png deleted file mode 100644 index de2c08044..000000000 Binary files a/web/gallery/web/assets/products/planters.png and /dev/null differ diff --git a/web/gallery/web/assets/products/platter.png b/web/gallery/web/assets/products/platter.png deleted file mode 100644 index 52fb19b01..000000000 Binary files a/web/gallery/web/assets/products/platter.png and /dev/null differ diff --git a/web/gallery/web/assets/products/scarf.png b/web/gallery/web/assets/products/scarf.png deleted file mode 100644 index 68ba83ec5..000000000 Binary files a/web/gallery/web/assets/products/scarf.png and /dev/null differ diff --git a/web/gallery/web/assets/products/shirt.png b/web/gallery/web/assets/products/shirt.png deleted file mode 100644 index 7b5320b0b..000000000 Binary files a/web/gallery/web/assets/products/shirt.png and /dev/null differ diff --git a/web/gallery/web/assets/products/sunnies.png b/web/gallery/web/assets/products/sunnies.png deleted file mode 100644 index 10c6355e6..000000000 Binary files a/web/gallery/web/assets/products/sunnies.png and /dev/null differ diff --git a/web/gallery/web/assets/products/sweater.png b/web/gallery/web/assets/products/sweater.png deleted file mode 100644 index d65629e1d..000000000 Binary files a/web/gallery/web/assets/products/sweater.png and /dev/null differ diff --git a/web/gallery/web/assets/products/sweats.png b/web/gallery/web/assets/products/sweats.png deleted file mode 100644 index 6c206a5ef..000000000 Binary files a/web/gallery/web/assets/products/sweats.png and /dev/null differ diff --git a/web/gallery/web/assets/products/table.png b/web/gallery/web/assets/products/table.png deleted file mode 100644 index 354c73e1d..000000000 Binary files a/web/gallery/web/assets/products/table.png and /dev/null differ diff --git a/web/gallery/web/assets/products/teaset.png b/web/gallery/web/assets/products/teaset.png deleted file mode 100644 index 7ff5c423f..000000000 Binary files a/web/gallery/web/assets/products/teaset.png and /dev/null differ diff --git a/web/gallery/web/assets/products/top.png b/web/gallery/web/assets/products/top.png deleted file mode 100644 index 923fc1bd2..000000000 Binary files a/web/gallery/web/assets/products/top.png and /dev/null differ diff --git a/web/gallery/web/frame.html b/web/gallery/web/frame.html deleted file mode 100644 index 6e2549210..000000000 --- a/web/gallery/web/frame.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/web/gallery/web/index.html b/web/gallery/web/index.html index 9e054ef45..bee334924 100644 --- a/web/gallery/web/index.html +++ b/web/gallery/web/index.html @@ -1,37 +1,10 @@ - + - - - + gallery -
- -
+ diff --git a/web/gallery/web/main.dart b/web/gallery/web/main.dart deleted file mode 100644 index fcabacbde..000000000 --- a/web/gallery/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:flutter_web.examples.gallery/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/github_dataviz/web/preview.png b/web/github_dataviz/assets/preview.png similarity index 100% rename from web/github_dataviz/web/preview.png rename to web/github_dataviz/assets/preview.png diff --git a/web/github_dataviz/lib/constants.dart b/web/github_dataviz/lib/constants.dart index d27f99271..d7e2d3c3e 100644 --- a/web/github_dataviz/lib/constants.dart +++ b/web/github_dataviz/lib/constants.dart @@ -1,5 +1,5 @@ -import 'package:flutter_web/material.dart'; -import 'package:flutter_web_ui/ui.dart'; +import 'package:flutter/material.dart'; +import 'dart:ui'; class Constants { static final Color backgroundColor = const Color(0xFF000020); diff --git a/web/github_dataviz/lib/layered_chart.dart b/web/github_dataviz/lib/layered_chart.dart index a66628dbf..8075563d6 100644 --- a/web/github_dataviz/lib/layered_chart.dart +++ b/web/github_dataviz/lib/layered_chart.dart @@ -1,8 +1,8 @@ import 'dart:math'; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/painting.dart'; -import 'package:flutter_web/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/widgets.dart'; import 'package:github_dataviz/catmull.dart'; import 'package:github_dataviz/constants.dart'; import 'package:github_dataviz/data/data_series.dart'; diff --git a/web/github_dataviz/lib/main.dart b/web/github_dataviz/lib/main.dart index aaf78fe8a..806b635e7 100644 --- a/web/github_dataviz/lib/main.dart +++ b/web/github_dataviz/lib/main.dart @@ -4,9 +4,10 @@ import 'dart:collection'; import 'dart:convert'; -import 'dart:html'; -import 'package:flutter_web/material.dart'; +import 'package:http/http.dart' as http; + +import 'package:flutter/material.dart'; import 'package:github_dataviz/constants.dart'; import 'package:github_dataviz/data/contribution_data.dart'; import 'package:github_dataviz/data/data_series.dart'; @@ -181,7 +182,7 @@ class _MainLayoutState extends State with TickerProviderStateMixin { Future loadGitHubData() async { String contributorsJsonStr = - await HttpRequest.getString("github_data/contributors.json"); + (await http.get("github_data/contributors.json")).body; List jsonObjs = jsonDecode(contributorsJsonStr) as List; List contributionList = jsonObjs.map((e) => UserContribution.fromJson(e)).toList(); @@ -190,28 +191,25 @@ class _MainLayoutState extends State with TickerProviderStateMixin { int numWeeksTotal = contributionList[0].contributions.length; - String starsByWeekStr = - await HttpRequest.getString("github_data/stars.tsv"); + String starsByWeekStr = (await http.get("github_data/stars.tsv")).body; List starsByWeekLoaded = summarizeWeeksFromTSV(starsByWeekStr, numWeeksTotal); - String forksByWeekStr = - await HttpRequest.getString("github_data/forks.tsv"); + String forksByWeekStr = (await http.get("github_data/forks.tsv")).body; List forksByWeekLoaded = summarizeWeeksFromTSV(forksByWeekStr, numWeeksTotal); - String commitsByWeekStr = - await HttpRequest.getString("github_data/commits.tsv"); + String commitsByWeekStr = (await http.get("github_data/commits.tsv")).body; List commitsByWeekLoaded = summarizeWeeksFromTSV(commitsByWeekStr, numWeeksTotal); String commentsByWeekStr = - await HttpRequest.getString("github_data/comments.tsv"); + (await http.get("github_data/comments.tsv")).body; List commentsByWeekLoaded = summarizeWeeksFromTSV(commentsByWeekStr, numWeeksTotal); String pullRequestActivityByWeekStr = - await HttpRequest.getString("github_data/pull_requests.tsv"); + (await http.get("github_data/pull_requests.tsv")).body; List pullRequestActivityByWeekLoaded = summarizeWeeksFromTSV(pullRequestActivityByWeekStr, numWeeksTotal); diff --git a/web/github_dataviz/lib/timeline.dart b/web/github_dataviz/lib/timeline.dart index 85e851d73..9582cf542 100644 --- a/web/github_dataviz/lib/timeline.dart +++ b/web/github_dataviz/lib/timeline.dart @@ -1,6 +1,6 @@ import 'dart:collection'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:github_dataviz/constants.dart'; import 'package:github_dataviz/data/week_label.dart'; import 'package:github_dataviz/mathutils.dart'; diff --git a/web/github_dataviz/pubspec.lock b/web/github_dataviz/pubspec.lock index 76051ba3a..e61a36f5f 100644 --- a/web/github_dataviz/pubspec.lock +++ b/web/github_dataviz/pubspec.lock @@ -1,27 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" async: dependency: transitive description: @@ -29,83 +8,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" charcode: dependency: transitive description: @@ -113,20 +15,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,101 +22,18 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: - dependency: "direct overridden" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" http: - dependency: transitive + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" http_parser: dependency: transitive description: @@ -237,54 +42,12 @@ packages: source: hosted version: "3.1.3" intl: - dependency: transitive + dependency: "direct main" description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" + version: "0.16.0" meta: dependency: transitive description: @@ -292,27 +55,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" path: dependency: transitive description: @@ -327,69 +69,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" - pool: + sky_engine: dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" + description: flutter + source: sdk + version: "0.0.99" source_span: dependency: transitive description: @@ -397,27 +81,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" string_scanner: dependency: transitive description: @@ -432,13 +95,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" typed_data: dependency: transitive description: @@ -453,26 +109,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/web/github_dataviz/pubspec.yaml b/web/github_dataviz/pubspec.yaml index 1d89f087b..79d42da50 100644 --- a/web/github_dataviz/pubspec.yaml +++ b/web/github_dataviz/pubspec.yaml @@ -4,20 +4,10 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any - -dev_dependencies: - build_runner: any - build_web_compilers: any - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui + flutter: + sdk: flutter + intl: ^0.16.0 + http: ^0.12.0 +flutter: + assets: + - preview.png diff --git a/web/github_dataviz/web/main.dart b/web/github_dataviz/web/main.dart deleted file mode 100644 index 1eaf7e4f2..000000000 --- a/web/github_dataviz/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:github_dataviz/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/particle_background/web/preview.png b/web/particle_background/assets/preview.png similarity index 100% rename from web/particle_background/web/preview.png rename to web/particle_background/assets/preview.png diff --git a/web/particle_background/lib/main.dart b/web/particle_background/lib/main.dart index 18b8fd44d..0feda00e0 100644 --- a/web/particle_background/lib/main.dart +++ b/web/particle_background/lib/main.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; import 'package:particle_background/simple_animations_package.dart'; void main() => runApp(ParticleApp()); diff --git a/web/particle_background/lib/simple_animations_package.dart b/web/particle_background/lib/simple_animations_package.dart index 09a5d094b..929b366f2 100644 --- a/web/particle_background/lib/simple_animations_package.dart +++ b/web/particle_background/lib/simple_animations_package.dart @@ -2,8 +2,8 @@ // https://pub.dev/packages/simple_animations import 'dart:math'; -import 'package:flutter_web/widgets.dart'; -import 'package:flutter_web/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/scheduler.dart'; /// Widget to easily create a continuous animation. /// diff --git a/web/particle_background/pubspec.lock b/web/particle_background/pubspec.lock index ae60194b8..040daf466 100644 --- a/web/particle_background/pubspec.lock +++ b/web/particle_background/pubspec.lock @@ -1,132 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,157 +8,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: - dependency: "direct main" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" meta: dependency: transitive description: @@ -292,153 +20,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: + sky_engine: dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" + description: flutter + source: sdk + version: "0.0.99" typed_data: dependency: transitive description: @@ -453,26 +39,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/web/particle_background/pubspec.yaml b/web/particle_background/pubspec.yaml index aaf13687f..acc57d297 100644 --- a/web/particle_background/pubspec.yaml +++ b/web/particle_background/pubspec.yaml @@ -7,21 +7,8 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any - flutter_web_ui: any - -dev_dependencies: - build_runner: any - build_web_compilers: any - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui + flutter: + sdk: flutter +flutter: + assets: + - preview.png diff --git a/web/particle_background/web/main.dart b/web/particle_background/web/main.dart deleted file mode 100644 index bd55290c7..000000000 --- a/web/particle_background/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:particle_background/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/peanut.yaml b/web/peanut.yaml index a98fb7957..770416cb9 100644 --- a/web/peanut.yaml +++ b/web/peanut.yaml @@ -1,30 +1,16 @@ # Configuration for https://pub.dartlang.org/packages/peanut directories: -- charts/example/web -- dad_jokes/web -- filipino_cuisine/web +- charts/web +# package:http doesn't work yet: https://github.com/flutter/flutter/issues/34858 +#- dad_jokes/web +#- filipino_cuisine/web - gallery/web -- github_dataviz/web +#- github_dataviz/web - particle_background/web - slide_puzzle/web - spinning_square/web - timeflow/web - vision_challenge/web -builder-options: - build_web_compilers|entrypoint: - dart2js_args: - # Generate `.info.json` files for compiled libraries. - - --dump-info - # Reduces diffs between builds, but increases JS output size slightly. - - --no-frequency-based-minification - # Deployed example does not include Dart source. Not needed. - - --no-source-maps - # Enable all dart2js optimizations. Not recommended without thorough testing. - - -O4 - build_web_compilers|dart2js_archive_extractor: - # Ensures .info.json file is preserved. Useful for tracking output details. - filter_outputs: false - post-build-dart-script: _tool/peanut_post_build.dart diff --git a/web/readme.md b/web/readme.md index f7d1ecbcb..a497d9623 100644 --- a/web/readme.md +++ b/web/readme.md @@ -1,22 +1,28 @@ -Samples for [Flutter for web](https://flutter.dev/web). +Web samples ## See the samples in action -Compiled versions of the samples are hosted at [flutter.github.io/samples](https://flutter.github.io/samples/). +Compiled versions of the samples are hosted at +[flutter.github.io/samples][samples]. ## Building samples code -Make sure you have configured the `webdev` tool. - -- [With the Flutter SDK installed](https://github.com/flutter/flutter_web#install-the-flutter_web-build-tools). -- [With the Dart SDK installed](https://dart.dev/tools/webdev#using-webdev-and-build_runner-commands). - -Go into one of the sample directories, get packages, and run. +Go into one of the sample directories, get packages, and run using the `chrome` +device: ```console +$ flutter channel dev +$ flutter upgrade $ cd gallery -$ flutter pub get # or `pub get` with the Dart SDK -$ webdev serve +$ flutter pub get +$ flutter run -d chrome ``` +Web support is available as a technical preview and is only available in the +`dev` or `master` channels. + You should see a message printing the URL to access: `http://localhost:8080` + +[web]: https://flutter.dev/web +[samples]: https://flutter.github.io/samples/ + diff --git a/web/slide_puzzle/analysis_options.yaml b/web/slide_puzzle/analysis_options.yaml deleted file mode 100644 index d7de72eb6..000000000 --- a/web/slide_puzzle/analysis_options.yaml +++ /dev/null @@ -1,93 +0,0 @@ -include: package:pedantic/analysis_options.yaml -analyzer: - strong-mode: - implicit-casts: false -linter: - rules: - - always_declare_return_types - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_classes_with_only_static_members - - avoid_empty_else - - avoid_function_literals_in_foreach_calls - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_future - - avoid_returning_null_for_void - - avoid_returning_this - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_types_as_parameter_names - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_types - - cancel_subscriptions - - cascade_invocations - - comment_references - - constant_identifier_names - - control_flow_in_finally - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - - file_names - - hash_and_equals - - implementation_imports - - invariant_booleans - - iterable_contains_unrelated_type - - join_return_with_assignment - - library_names - - library_prefixes - - list_remove_unrelated_type - - literal_only_boolean_expressions - - no_adjacent_strings_in_list - - no_duplicate_case_values - - non_constant_identifier_names - - null_closures - - omit_local_variable_types - - only_throw_errors - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - - prefer_adjacent_string_concatenation - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_contains - - prefer_equal_for_default_values - - prefer_final_fields - - prefer_final_locals - - prefer_generic_function_type_aliases - - prefer_initializing_formals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_typing_uninitialized_variables - - recursive_getters - - slash_for_doc_comments - - test_types_in_equals - - throw_in_finally - - type_init_formals - - unawaited_futures - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_parenthesis - - unnecessary_statements - - unnecessary_this - - unrelated_type_equality_checks - - use_function_type_syntax_for_parameters - - use_rethrow_when_possible - - valid_regexps - - void_checks diff --git a/web/slide_puzzle/asset/fonts/plaster/OFL.txt b/web/slide_puzzle/asset/fonts/plaster/OFL.txt new file mode 100755 index 000000000..f074fc5df --- /dev/null +++ b/web/slide_puzzle/asset/fonts/plaster/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2011 by Sorkin Type Co (www.sorkintype.com), +with Reserved Font Name "Plaster". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/web/slide_puzzle/asset/fonts/plaster/Plaster-Regular.ttf b/web/slide_puzzle/asset/fonts/plaster/Plaster-Regular.ttf new file mode 100755 index 000000000..eb49b39c6 Binary files /dev/null and b/web/slide_puzzle/asset/fonts/plaster/Plaster-Regular.ttf differ diff --git a/web/slide_puzzle/asset/seattle.jpg b/web/slide_puzzle/asset/seattle.jpg new file mode 100644 index 000000000..18f3880be Binary files /dev/null and b/web/slide_puzzle/asset/seattle.jpg differ diff --git a/web/slide_puzzle/web/preview.png b/web/slide_puzzle/assets/preview.png similarity index 100% rename from web/slide_puzzle/web/preview.png rename to web/slide_puzzle/assets/preview.png diff --git a/web/slide_puzzle/lib/main.dart b/web/slide_puzzle/lib/main.dart index fa2bc3a0d..dc9b8aa08 100644 --- a/web/slide_puzzle/lib/main.dart +++ b/web/slide_puzzle/lib/main.dart @@ -1,9 +1,5 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - +import 'package:flutter/material.dart'; import 'src/core/puzzle_animator.dart'; -import 'src/flutter.dart'; import 'src/puzzle_home_state.dart'; void main() => runApp(PuzzleApp()); @@ -25,7 +21,7 @@ class PuzzleApp extends StatelessWidget { class _PuzzleHome extends StatefulWidget { final int _rows, _columns; - const _PuzzleHome(this._rows, this._columns); + const _PuzzleHome(this._rows, this._columns, {Key key}) : super(key: key); @override PuzzleHomeState createState() => diff --git a/web/slide_puzzle/lib/src/app_state.dart b/web/slide_puzzle/lib/src/app_state.dart index 2d9f20d72..efef589ed 100644 --- a/web/slide_puzzle/lib/src/app_state.dart +++ b/web/slide_puzzle/lib/src/app_state.dart @@ -1,13 +1,29 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +import 'core/puzzle_animator.dart'; +import 'package:flutter/material.dart'; +import 'shared_theme.dart'; -import 'package:flutter_web/foundation.dart'; +abstract class AppState { + TabController get tabController; -import 'core/puzzle_proxy.dart'; + Animation get shuffleOffsetAnimation; -abstract class AppState { PuzzleProxy get puzzle; - Listenable get animationNotifier; + bool get autoPlay; + + void setAutoPlay(bool newValue); + + AnimationNotifier get animationNotifier; + + Iterable get themeData; + + SharedTheme get currentTheme; + + set currentTheme(SharedTheme theme); +} + +abstract class AnimationNotifier implements Listenable { + void animate(); + + void dispose(); } diff --git a/web/slide_puzzle/lib/src/core/body.dart b/web/slide_puzzle/lib/src/core/body.dart index 76fd7ffe4..91c5ca5d5 100644 --- a/web/slide_puzzle/lib/src/core/body.dart +++ b/web/slide_puzzle/lib/src/core/body.dart @@ -1,7 +1,3 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - import 'dart:math' show Point; const zeroPoint = Point(0, 0); diff --git a/web/slide_puzzle/lib/src/core/point_int.dart b/web/slide_puzzle/lib/src/core/point_int.dart index 9854ebe90..65aaebe4e 100644 --- a/web/slide_puzzle/lib/src/core/point_int.dart +++ b/web/slide_puzzle/lib/src/core/point_int.dart @@ -1,7 +1,3 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - import 'dart:math' as math; class Point extends math.Point { diff --git a/web/slide_puzzle/lib/src/core/puzzle.dart b/web/slide_puzzle/lib/src/core/puzzle.dart index 059d1f21d..4419dc9e6 100644 --- a/web/slide_puzzle/lib/src/core/puzzle.dart +++ b/web/slide_puzzle/lib/src/core/puzzle.dart @@ -1,7 +1,3 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - import 'dart:collection'; import 'dart:convert'; import 'dart:math' show Random, max; @@ -127,7 +123,8 @@ abstract class Puzzle { value += delta * delta; } } - return value * incorrectTiles; + value *= incorrectTiles; + return value; } Puzzle clickRandom({bool vertical}) { @@ -140,8 +137,8 @@ abstract class Puzzle { List clickableValues({bool vertical}) { final open = openPosition(); - final doRow = vertical == null || vertical == false; - final doColumn = vertical == null || vertical; + final doRow = (vertical == null || vertical == false); + final doColumn = (vertical == null || vertical); final values = Uint8List((doRow ? (width - 1) : 0) + (doColumn ? (height - 1) : 0)); diff --git a/web/slide_puzzle/lib/src/core/puzzle_animator.dart b/web/slide_puzzle/lib/src/core/puzzle_animator.dart index 3088c7f8b..3646e4d6e 100644 --- a/web/slide_puzzle/lib/src/core/puzzle_animator.dart +++ b/web/slide_puzzle/lib/src/core/puzzle_animator.dart @@ -1,13 +1,34 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - import 'dart:async'; import 'dart:math' show Point, Random; import 'body.dart'; import 'puzzle.dart'; -import 'puzzle_proxy.dart'; + +enum PuzzleEvent { click, reset, noop } + +abstract class PuzzleProxy { + int get width; + + int get height; + + int get length; + + bool get solved; + + void reset(); + + void clickOrShake(int tileValue); + + int get tileCount; + + int get clickCount; + + int get incorrectTiles; + + Point location(int index); + + bool isCorrectPosition(int value); +} class PuzzleAnimator implements PuzzleProxy { final _rnd = Random(); @@ -36,10 +57,13 @@ class PuzzleAnimator implements PuzzleProxy { @override int get tileCount => _puzzle.tileCount; + @override int get incorrectTiles => _puzzle.incorrectTiles; + @override int get clickCount => _clickCount; + @override void reset() => _resetCore(); Stream get onEvent => _controller.stream; @@ -69,7 +93,7 @@ class PuzzleAnimator implements PuzzleProxy { _puzzle = _puzzle.clickRandom(vertical: _nextRandomVertical); _nextRandomVertical = !_nextRandomVertical; _clickCount++; - _controller.add(PuzzleEvent.random); + _controller.add(PuzzleEvent.click); } @override @@ -141,7 +165,7 @@ class PuzzleAnimator implements PuzzleProxy { final delta = _puzzle.openPosition() - _puzzle.coordinatesOf(tileValue); deltaDouble = Point(delta.x.toDouble(), delta.y.toDouble()); } - deltaDouble *= 0.5 / deltaDouble.magnitude; + deltaDouble *= (0.5 / deltaDouble.magnitude); _locations[tileValue].kick(deltaDouble); } diff --git a/web/slide_puzzle/lib/src/core/puzzle_proxy.dart b/web/slide_puzzle/lib/src/core/puzzle_proxy.dart deleted file mode 100644 index 3ac3ea416..000000000 --- a/web/slide_puzzle/lib/src/core/puzzle_proxy.dart +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:math' show Point; - -enum PuzzleEvent { click, random, reset, noop } - -abstract class PuzzleProxy { - int get width; - - int get height; - - int get length; - - bool get solved; - - void clickOrShake(int tileValue); - - int get tileCount; - - Point location(int index); - - bool isCorrectPosition(int value); -} diff --git a/web/slide_puzzle/lib/src/core/puzzle_simple.dart b/web/slide_puzzle/lib/src/core/puzzle_simple.dart index ee87c0e52..7af7eed42 100644 --- a/web/slide_puzzle/lib/src/core/puzzle_simple.dart +++ b/web/slide_puzzle/lib/src/core/puzzle_simple.dart @@ -1,7 +1,3 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - part of 'puzzle.dart'; class _PuzzleSimple extends Puzzle { diff --git a/web/slide_puzzle/lib/src/core/puzzle_smart.dart b/web/slide_puzzle/lib/src/core/puzzle_smart.dart index 7bbae8562..1c5da3a33 100644 --- a/web/slide_puzzle/lib/src/core/puzzle_smart.dart +++ b/web/slide_puzzle/lib/src/core/puzzle_smart.dart @@ -1,7 +1,3 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - part of 'puzzle.dart'; mixin _SliceListMixin on ListMixin { diff --git a/web/slide_puzzle/lib/src/core/util.dart b/web/slide_puzzle/lib/src/core/util.dart index 992b31c26..b820c47ce 100644 --- a/web/slide_puzzle/lib/src/core/util.dart +++ b/web/slide_puzzle/lib/src/core/util.dart @@ -1,7 +1,3 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - void requireArgument(bool truth, String argName, [String message]) { if (!truth) { if (message == null || message.isEmpty) { diff --git a/web/slide_puzzle/lib/src/flutter.dart b/web/slide_puzzle/lib/src/flutter.dart deleted file mode 100644 index 01f8b1cc4..000000000 --- a/web/slide_puzzle/lib/src/flutter.dart +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -export 'package:flutter_web/material.dart'; - -export 'package:flutter_web/scheduler.dart' show Ticker; diff --git a/web/slide_puzzle/lib/src/frame_nanny.dart b/web/slide_puzzle/lib/src/frame_nanny.dart new file mode 100644 index 000000000..8c58e7de0 --- /dev/null +++ b/web/slide_puzzle/lib/src/frame_nanny.dart @@ -0,0 +1,63 @@ +import 'dart:collection'; + +class FrameNanny { + static const _bufferSize = 200; + static const _maxFrameDuration = Duration(milliseconds: 34); + final _buffer = ListQueue(_bufferSize); + final _watch = Stopwatch(); + + Duration tick(Duration source) { + _watch.start(); + _buffer.add(source); + + while (_buffer.length > _bufferSize) { + _buffer.removeFirst(); + } + + if (source > _maxFrameDuration) { + source = _maxFrameDuration; + } + + if (_watch.elapsed > const Duration(seconds: 2)) { + var goodCount = 0; + var sum = const Duration(); + Duration best, worst; + + for (var e in _buffer) { + sum += e; + if (e <= _maxFrameDuration) { + goodCount++; + } + + if (best == null || e < best) { + best = e; + } + + if (worst == null || e > worst) { + worst = e; + } + } + + _watch.reset(); + print([ + '**Nanny**', + '${(100 * goodCount / _buffer.length).toStringAsFixed(1)}%', + '<= ${_maxFrameDuration.inMilliseconds}ms', + 'best:', + best?.inMilliseconds, + 'avg:', + _safeDivide(sum, _buffer.length), + 'worst', + worst?.inMilliseconds + ].join(' ')); + } + return source; + } +} + +Object _safeDivide(Duration source, int divisor) { + if (divisor == 0) { + return double.nan; + } + return (source ~/ divisor).inMilliseconds; +} diff --git a/web/slide_puzzle/lib/src/puzzle_controls.dart b/web/slide_puzzle/lib/src/puzzle_controls.dart deleted file mode 100644 index 83be15310..000000000 --- a/web/slide_puzzle/lib/src/puzzle_controls.dart +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web/foundation.dart'; - -abstract class PuzzleControls implements Listenable { - void reset(); - - int get clickCount; - - int get incorrectTiles; - - bool get autoPlay; - - void Function(bool newValue) get setAutoPlayFunction; -} diff --git a/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart b/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart index 45bb63a32..348dbd6e2 100644 --- a/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart +++ b/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart @@ -1,9 +1,5 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'core/puzzle_proxy.dart'; -import 'flutter.dart'; +import 'core/puzzle_animator.dart'; +import 'package:flutter/material.dart'; class PuzzleFlowDelegate extends FlowDelegate { final Size _tileSize; diff --git a/web/slide_puzzle/lib/src/puzzle_home_state.dart b/web/slide_puzzle/lib/src/puzzle_home_state.dart index c44f5e660..8b69c73c8 100644 --- a/web/slide_puzzle/lib/src/puzzle_home_state.dart +++ b/web/slide_puzzle/lib/src/puzzle_home_state.dart @@ -1,84 +1,101 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - import 'dart:async'; +import 'dart:math' as math; -import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'app_state.dart'; import 'core/puzzle_animator.dart'; -import 'core/puzzle_proxy.dart'; -import 'flutter.dart'; -import 'puzzle_controls.dart'; -import 'puzzle_flow_delegate.dart'; +import 'frame_nanny.dart'; import 'shared_theme.dart'; -import 'themes.dart'; -import 'value_tab_controller.dart'; - -class _PuzzleControls extends ChangeNotifier implements PuzzleControls { - final PuzzleHomeState _parent; +import 'theme_plaster.dart'; +import 'theme_seattle.dart'; +import 'theme_simple.dart'; - _PuzzleControls(this._parent); +class PuzzleHomeState extends State + with TickerProviderStateMixin + implements AppState { + TabController _tabController; + AnimationController _controller; + Animation _shuffleOffsetAnimation; @override - bool get autoPlay => _parent._autoPlay; - - void _notify() => notifyListeners(); + Animation get shuffleOffsetAnimation => _shuffleOffsetAnimation; @override - void Function(bool newValue) get setAutoPlayFunction { - if (_parent.puzzle.solved) { - return null; - } - return _parent._setAutoPlay; - } + final PuzzleAnimator puzzle; @override - int get clickCount => _parent.puzzle.clickCount; + final animationNotifier = _AnimationNotifier(); @override - int get incorrectTiles => _parent.puzzle.incorrectTiles; + TabController get tabController => _tabController; - @override - void reset() => _parent.puzzle.reset(); -} + final _nanny = FrameNanny(); + + SharedTheme _currentTheme; -class PuzzleHomeState extends State - with SingleTickerProviderStateMixin, AppState { @override - final PuzzleAnimator puzzle; + SharedTheme get currentTheme => _currentTheme; @override - final _AnimationNotifier animationNotifier = _AnimationNotifier(); + set currentTheme(SharedTheme theme) { + setState(() { + _currentTheme = theme; + }); + } Duration _tickerTimeSinceLastEvent = Duration.zero; Ticker _ticker; Duration _lastElapsed; - StreamSubscription _puzzleEventSubscription; + StreamSubscription sub; - bool _autoPlay = false; - _PuzzleControls _autoPlayListenable; + @override + bool autoPlay = false; PuzzleHomeState(this.puzzle) { - _puzzleEventSubscription = puzzle.onEvent.listen(_onPuzzleEvent); + sub = puzzle.onEvent.listen(_onPuzzleEvent); + + _themeDataCache = List.unmodifiable([ + ThemeSimple(this), + ThemeSeattle(this), + ThemePlaster(this), + ]); + + _currentTheme = themeData.first; } @override void initState() { super.initState(); - _autoPlayListenable = _PuzzleControls(this); _ticker ??= createTicker(_onTick); _ensureTicking(); + + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + + _shuffleOffsetAnimation = _controller.drive(const _Shake()); + _tabController = TabController(vsync: this, length: _themeDataCache.length); + + _tabController.addListener(() { + currentTheme = _themeDataCache[_tabController.index]; + }); } - void _setAutoPlay(bool newValue) { - if (newValue != _autoPlay) { + List _themeDataCache; + + @override + Iterable get themeData => _themeDataCache; + + @override + void setAutoPlay(bool newValue) { + if (newValue != autoPlay) { setState(() { // Only allow enabling autoPlay if the puzzle is not solved - _autoPlayListenable._notify(); - _autoPlay = newValue && !puzzle.solved; - if (_autoPlay) { + autoPlay = newValue && !puzzle.solved; + if (autoPlay) { _ensureTicking(); } }); @@ -86,46 +103,26 @@ class PuzzleHomeState extends State } @override - Widget build(BuildContext context) => MultiProvider( - providers: [ - Provider.value(value: this), - ListenableProvider.value( - listenable: _autoPlayListenable, - ) - ], - child: Material( - child: Stack( - children: [ - const SizedBox.expand( - child: FittedBox( - fit: BoxFit.cover, - child: Image( - image: AssetImage('seattle.jpg'), - ), - ), - ), - const LayoutBuilder(builder: _doBuild), - ], - ), - ), - ); + Widget build(BuildContext context) => _currentTheme.build(context); @override void dispose() { animationNotifier.dispose(); + _tabController.dispose(); + _controller?.dispose(); _ticker?.dispose(); - _autoPlayListenable?.dispose(); - _puzzleEventSubscription.cancel(); + sub.cancel(); super.dispose(); } void _onPuzzleEvent(PuzzleEvent e) { - _autoPlayListenable._notify(); - if (e != PuzzleEvent.random) { - _setAutoPlay(false); - } _tickerTimeSinceLastEvent = Duration.zero; _ensureTicking(); + if (e == PuzzleEvent.noop) { + assert(e == PuzzleEvent.noop); + _controller.reset(); + _controller.forward(); + } setState(() { // noop }); @@ -151,141 +148,40 @@ class PuzzleHomeState extends State } _tickerTimeSinceLastEvent += delta; - puzzle.update(delta > _maxFrameDuration ? _maxFrameDuration : delta); + puzzle.update(_nanny.tick(delta)); if (!puzzle.stable) { animationNotifier.animate(); } else { - if (!_autoPlay) { + if (!autoPlay) { _ticker.stop(); _lastElapsed = null; } } - if (_autoPlay && + if (autoPlay && _tickerTimeSinceLastEvent > const Duration(milliseconds: 200)) { puzzle.playRandom(); if (puzzle.solved) { - _setAutoPlay(false); + setAutoPlay(false); } } } } -class _AnimationNotifier extends ChangeNotifier { - void animate() { - notifyListeners(); - } -} +class _Shake extends Animatable { + const _Shake(); -const _maxFrameDuration = Duration(milliseconds: 34); - -Widget _updateConstraints( - BoxConstraints constraints, Widget Function(bool small) builder) { - const _smallWidth = 580; - - final constraintWidth = - constraints.hasBoundedWidth ? constraints.maxWidth : 1000.0; + @override + Offset transform(double t) => Offset(0.01 * math.sin(t * math.pi * 3), 0); +} - final constraintHeight = - constraints.hasBoundedHeight ? constraints.maxHeight : 1000.0; +class _AnimationNotifier extends ChangeNotifier implements AnimationNotifier { + _AnimationNotifier(); - return builder(constraintWidth < _smallWidth || constraintHeight < 690); + @override + void animate() { + notifyListeners(); + } } - -Widget _doBuild(BuildContext _, BoxConstraints constraints) => - _updateConstraints(constraints, _doBuildCore); - -Widget _doBuildCore(bool small) => ValueTabController( - values: themes, - child: Consumer( - builder: (_, theme, __) => AnimatedContainer( - duration: puzzleAnimationDuration, - color: theme.puzzleThemeBackground, - child: Center( - child: theme.styledWrapper( - small, - SizedBox( - width: 580, - child: Consumer( - builder: (context, appState, _) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.black26, - width: 1, - ), - ), - ), - margin: - const EdgeInsets.symmetric(horizontal: 20), - child: TabBar( - controller: ValueTabController.of(context), - labelPadding: - const EdgeInsets.fromLTRB(0, 20, 0, 12), - labelColor: theme.puzzleAccentColor, - indicatorColor: theme.puzzleAccentColor, - indicatorWeight: 1.5, - unselectedLabelColor: - Colors.black.withOpacity(0.6), - tabs: themes - .map((st) => Text( - st.name.toUpperCase(), - style: const TextStyle( - letterSpacing: 0.5, - ), - )) - .toList(), - ), - ), - Flexible( - child: Container( - padding: const EdgeInsets.all(10), - child: Flow( - delegate: PuzzleFlowDelegate( - small - ? const Size(90, 90) - : const Size(140, 140), - appState.puzzle, - appState.animationNotifier, - ), - children: List.generate( - appState.puzzle.length, - (i) => theme.tileButtonCore( - i, appState.puzzle, small), - ), - ), - ), - ), - Container( - decoration: const BoxDecoration( - border: Border( - top: BorderSide( - color: Colors.black26, width: 1), - ), - ), - padding: const EdgeInsets.only( - left: 10, - bottom: 6, - top: 2, - right: 10, - ), - child: Consumer( - builder: (_, controls, __) => Row( - children: theme.bottomControls(controls)), - ), - ) - ], - ), - ), - ), - ), - ), - ), - ), - ); diff --git a/web/slide_puzzle/lib/src/shared_theme.dart b/web/slide_puzzle/lib/src/shared_theme.dart index b49cbe5f4..3240ce4ab 100644 --- a/web/slide_puzzle/lib/src/shared_theme.dart +++ b/web/slide_puzzle/lib/src/shared_theme.dart @@ -1,30 +1,30 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +import 'package:flutter/material.dart'; -import 'core/puzzle_proxy.dart'; -import 'flutter.dart'; -import 'puzzle_controls.dart'; +import 'app_state.dart'; +import 'core/puzzle_animator.dart'; +import 'puzzle_flow_delegate.dart'; import 'widgets/material_interior_alt.dart'; -final puzzleAnimationDuration = kThemeAnimationDuration * 3; - abstract class SharedTheme { - const SharedTheme(); + SharedTheme(this._appState); + + final AppState _appState; + + PuzzleProxy get puzzle => _appState.puzzle; String get name; Color get puzzleThemeBackground; - RoundedRectangleBorder puzzleBorder(bool small); + RoundedRectangleBorder get puzzleBorder; Color get puzzleBackgroundColor; Color get puzzleAccentColor; - EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => const EdgeInsets.all(6); + EdgeInsetsGeometry get tilePadding => const EdgeInsets.all(6); - Widget tileButton(int i, PuzzleProxy puzzle, bool small); + Widget tileButton(int i); Ink createInk( Widget child, { @@ -40,57 +40,159 @@ abstract class SharedTheme { ); Widget createButton( - PuzzleProxy puzzle, - bool small, int tileValue, Widget content, { Color color, RoundedRectangleBorder shape, }) => AnimatedContainer( - duration: puzzleAnimationDuration, - padding: tilePadding(puzzle), + duration: _puzzleAnimationDuration, + padding: tilePadding, child: RaisedButton( elevation: 4, clipBehavior: Clip.hardEdge, - animationDuration: puzzleAnimationDuration, - onPressed: () => puzzle.clickOrShake(tileValue), - shape: shape ?? puzzleBorder(small), + animationDuration: _puzzleAnimationDuration, + onPressed: () => _tilePress(tileValue), + shape: shape ?? puzzleBorder, padding: const EdgeInsets.symmetric(), child: content, color: color, ), ); + Widget build(BuildContext context) => Material( + child: Stack( + children: [ + const SizedBox.expand( + child: FittedBox( + fit: BoxFit.cover, + child: Image( + image: AssetImage('asset/seattle.jpg'), + ), + ), + ), + AnimatedContainer( + duration: _puzzleAnimationDuration, + color: puzzleThemeBackground, + child: Center( + child: _styledWrapper( + SizedBox( + width: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black26, + width: 1, + ), + ), + ), + margin: const EdgeInsets.symmetric(horizontal: 20), + child: TabBar( + controller: _appState.tabController, + labelPadding: const EdgeInsets.fromLTRB(0, 20, 0, 12), + labelColor: puzzleAccentColor, + indicatorColor: puzzleAccentColor, + indicatorWeight: 1.5, + unselectedLabelColor: Colors.black.withOpacity(0.6), + tabs: _appState.themeData + .map((st) => Text( + st.name.toUpperCase(), + style: const TextStyle( + letterSpacing: 0.5, + ), + )) + .toList(), + ), + ), + Container( + constraints: const BoxConstraints.tightForFinite(), + padding: const EdgeInsets.all(10), + child: Flow( + delegate: PuzzleFlowDelegate( + _tileSize, + puzzle, + _appState.animationNotifier, + ), + children: List.generate( + puzzle.length, + _tileButton, + ), + ), + ), + Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide(color: Colors.black26, width: 1), + ), + ), + padding: const EdgeInsets.only( + left: 10, + bottom: 6, + top: 2, + right: 10, + ), + child: Row(children: _bottomControls(context)), + ) + ], + ), + ), + ), + ), + ) + ], + )); + + Duration get _puzzleAnimationDuration => kThemeAnimationDuration * 3; + // Thought about using AnimatedContainer here, but it causes some weird // resizing behavior - Widget styledWrapper(bool small, Widget child) => MaterialInterior( - duration: puzzleAnimationDuration, - shape: puzzleBorder(small), + Widget _styledWrapper(Widget child) => MaterialInterior( + duration: _puzzleAnimationDuration, + shape: puzzleBorder, color: puzzleBackgroundColor, child: child, ); + Size get _tileSize => const Size(140.0, 140.0); + + void Function(bool newValue) get _setAutoPlay { + if (puzzle.solved) { + return null; + } + return _appState.setAutoPlay; + } + + void _tilePress(int tileValue) { + _appState.setAutoPlay(false); + _appState.puzzle.clickOrShake(tileValue); + } + TextStyle get _infoStyle => TextStyle( color: puzzleAccentColor, fontWeight: FontWeight.bold, ); - List bottomControls(PuzzleControls controls) => [ + List _bottomControls(BuildContext context) => [ IconButton( - onPressed: controls.reset, + onPressed: puzzle.reset, icon: Icon(Icons.refresh, color: puzzleAccentColor), + //Icons.refresh, ), Checkbox( - value: controls.autoPlay, - onChanged: controls.setAutoPlayFunction, + value: _appState.autoPlay, + onChanged: _setAutoPlay, activeColor: puzzleAccentColor, ), Expanded( child: Container(), ), Text( - controls.clickCount.toString(), + puzzle.clickCount.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -98,7 +200,7 @@ abstract class SharedTheme { SizedBox( width: 28, child: Text( - controls.incorrectTiles.toString(), + puzzle.incorrectTiles.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -106,11 +208,11 @@ abstract class SharedTheme { const Text(' Tiles left ') ]; - Widget tileButtonCore(int i, PuzzleProxy puzzle, bool small) { + Widget _tileButton(int i) { if (i == puzzle.tileCount && !puzzle.solved) { return const Center(); } - return tileButton(i, puzzle, small); + return tileButton(i); } } diff --git a/web/slide_puzzle/lib/src/theme_plaster.dart b/web/slide_puzzle/lib/src/theme_plaster.dart index ecdf7b133..e80b6fe02 100644 --- a/web/slide_puzzle/lib/src/theme_plaster.dart +++ b/web/slide_puzzle/lib/src/theme_plaster.dart @@ -1,9 +1,6 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +import 'package:flutter/material.dart'; -import 'core/puzzle_proxy.dart'; -import 'flutter.dart'; +import 'app_state.dart'; import 'shared_theme.dart'; const _yellowIsh = Color.fromARGB(255, 248, 244, 233); @@ -14,7 +11,7 @@ class ThemePlaster extends SharedTheme { @override String get name => 'Plaster'; - const ThemePlaster(); + ThemePlaster(AppState baseTheme) : super(baseTheme); @override Color get puzzleThemeBackground => _chocolate; @@ -26,18 +23,18 @@ class ThemePlaster extends SharedTheme { Color get puzzleAccentColor => _orangeIsh; @override - RoundedRectangleBorder puzzleBorder(bool small) => RoundedRectangleBorder( - side: const BorderSide( + RoundedRectangleBorder get puzzleBorder => const RoundedRectangleBorder( + side: BorderSide( color: Color.fromARGB(255, 103, 103, 105), width: 8, ), borderRadius: BorderRadius.all( - Radius.circular(small ? 10 : 18), + Radius.circular(18), ), ); @override - Widget tileButton(int i, PuzzleProxy puzzle, bool small) { + Widget tileButton(int i) { final correctColumn = i % puzzle.width; final correctRow = i ~/ puzzle.width; @@ -45,10 +42,10 @@ class ThemePlaster extends SharedTheme { if (i == puzzle.tileCount) { assert(puzzle.solved); - return Center( + return const Center( child: Icon( Icons.thumb_up, - size: small ? 50 : 72, + size: 72, color: _orangeIsh, ), ); @@ -59,13 +56,11 @@ class ThemePlaster extends SharedTheme { style: TextStyle( color: primary ? _yellowIsh : _chocolate, fontFamily: 'Plaster', - fontSize: small ? 40 : 77, + fontSize: 77, ), ); return createButton( - puzzle, - small, i, content, color: primary ? _orangeIsh : _yellowIsh, diff --git a/web/slide_puzzle/lib/src/theme_seattle.dart b/web/slide_puzzle/lib/src/theme_seattle.dart index c5e44158a..2a9505d9d 100644 --- a/web/slide_puzzle/lib/src/theme_seattle.dart +++ b/web/slide_puzzle/lib/src/theme_seattle.dart @@ -1,9 +1,6 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +import 'package:flutter/material.dart'; -import 'core/puzzle_proxy.dart'; -import 'flutter.dart'; +import 'app_state.dart'; import 'shared_theme.dart'; import 'widgets/decoration_image_plus.dart'; @@ -11,7 +8,7 @@ class ThemeSeattle extends SharedTheme { @override String get name => 'Seattle'; - const ThemeSeattle(); + ThemeSeattle(AppState proxy) : super(proxy); @override Color get puzzleThemeBackground => const Color.fromARGB(153, 90, 135, 170); @@ -23,19 +20,18 @@ class ThemeSeattle extends SharedTheme { Color get puzzleAccentColor => const Color(0xff000579f); @override - RoundedRectangleBorder puzzleBorder(bool small) => - const RoundedRectangleBorder( + RoundedRectangleBorder get puzzleBorder => const RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(1), ), ); @override - EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => + EdgeInsetsGeometry get tilePadding => puzzle.solved ? const EdgeInsets.all(1) : const EdgeInsets.all(4); @override - Widget tileButton(int i, PuzzleProxy puzzle, bool small) { + Widget tileButton(int i) { if (i == puzzle.tileCount && !puzzle.solved) { assert(puzzle.solved); } @@ -45,7 +41,7 @@ class ThemeSeattle extends SharedTheme { puzzleHeight: puzzle.height, pieceIndex: i, fit: BoxFit.cover, - image: const AssetImage('seattle.jpg')); + image: const AssetImage('asset/seattle.jpg')); final correctPosition = puzzle.isCorrectPosition(i); final content = createInk( @@ -62,14 +58,14 @@ class ThemeSeattle extends SharedTheme { style: TextStyle( fontWeight: FontWeight.normal, color: correctPosition ? Colors.white : Colors.black, - fontSize: small ? 25 : 42, + fontSize: 42, ), ), ), image: decorationImage, - padding: EdgeInsets.all(small ? 20 : 32), + padding: const EdgeInsets.all(32), ); - return createButton(puzzle, small, i, content); + return createButton(i, content); } } diff --git a/web/slide_puzzle/lib/src/theme_simple.dart b/web/slide_puzzle/lib/src/theme_simple.dart index 70cc73037..c962da38f 100644 --- a/web/slide_puzzle/lib/src/theme_simple.dart +++ b/web/slide_puzzle/lib/src/theme_simple.dart @@ -1,9 +1,6 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +import 'package:flutter/material.dart'; -import 'core/puzzle_proxy.dart'; -import 'flutter.dart'; +import 'app_state.dart'; import 'shared_theme.dart'; const _accentBlue = Color(0xff000579e); @@ -12,7 +9,7 @@ class ThemeSimple extends SharedTheme { @override String get name => 'Simple'; - const ThemeSimple(); + ThemeSimple(AppState proxy) : super(proxy); @override Color get puzzleThemeBackground => Colors.white; @@ -24,8 +21,7 @@ class ThemeSimple extends SharedTheme { Color get puzzleAccentColor => _accentBlue; @override - RoundedRectangleBorder puzzleBorder(bool small) => - const RoundedRectangleBorder( + RoundedRectangleBorder get puzzleBorder => const RoundedRectangleBorder( side: BorderSide(color: Colors.black26, width: 1), borderRadius: BorderRadius.all( Radius.circular(4), @@ -33,7 +29,7 @@ class ThemeSimple extends SharedTheme { ); @override - Widget tileButton(int i, PuzzleProxy puzzle, bool small) { + Widget tileButton(int i) { if (i == puzzle.tileCount) { assert(puzzle.solved); return const Center( @@ -54,15 +50,13 @@ class ThemeSimple extends SharedTheme { style: TextStyle( color: Colors.white, fontWeight: correctPosition ? FontWeight.bold : FontWeight.normal, - fontSize: small ? 30 : 49, + fontSize: 49, ), ), ), ); return createButton( - puzzle, - small, i, content, color: const Color.fromARGB(255, 13, 87, 155), diff --git a/web/slide_puzzle/lib/src/themes.dart b/web/slide_puzzle/lib/src/themes.dart deleted file mode 100644 index 7889db6c3..000000000 --- a/web/slide_puzzle/lib/src/themes.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'theme_plaster.dart'; -import 'theme_seattle.dart'; -import 'theme_simple.dart'; - -const themes = [ - ThemeSimple(), - ThemeSeattle(), - ThemePlaster(), -]; diff --git a/web/slide_puzzle/lib/src/value_tab_controller.dart b/web/slide_puzzle/lib/src/value_tab_controller.dart deleted file mode 100644 index 095c29ca6..000000000 --- a/web/slide_puzzle/lib/src/value_tab_controller.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter_web/material.dart'; -import 'package:provider/provider.dart'; - -class ValueTabController extends StatefulWidget { - /// Creates a default tab controller for the given [child] widget. - const ValueTabController({ - Key key, - @required this.child, - @required this.values, - }) : super(key: key); - - /// The widget below this widget in the tree. - /// - /// Typically a [Scaffold] whose [AppBar] includes a [TabBar]. - /// - /// {@macro flutter.widgets.child} - final Widget child; - - final List values; - - /// The closest instance of this class that encloses the given context. - /// - /// Typical usage: - /// - /// ```dart - /// TabController controller = DefaultTabBarController.of(context); - /// ``` - static TabController of(BuildContext context) { - final scope = context.inheritFromWidgetOfExactType(_ValueTabControllerScope) - as _ValueTabControllerScope; - return scope?.controller; - } - - @override - _ValueTabControllerState createState() => _ValueTabControllerState(); -} - -class _ValueTabControllerState extends State> - with SingleTickerProviderStateMixin { - final _notifier = ValueNotifier(null); - - TabController _controller; - - @override - void initState() { - super.initState(); - _controller = TabController( - vsync: this, - length: widget.values.length, - initialIndex: 0, - ); - - _notifier.value = widget.values.first; - - _controller.addListener(() { - _notifier.value = widget.values[_controller.index]; - }); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) => _ValueTabControllerScope( - controller: _controller, - enabled: TickerMode.of(context), - child: ValueListenableProvider.value( - valueListenable: _notifier, - child: widget.child, - ), - ); -} - -class _ValueTabControllerScope extends InheritedWidget { - const _ValueTabControllerScope( - {Key key, this.controller, this.enabled, Widget child}) - : super(key: key, child: child); - - final TabController controller; - final bool enabled; - - @override - bool updateShouldNotify(_ValueTabControllerScope old) => - enabled != old.enabled || controller != old.controller; -} diff --git a/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart b/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart index 280e30e7e..f48803c75 100644 --- a/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart +++ b/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart @@ -1,12 +1,8 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // ignore_for_file: omit_local_variable_types, annotate_overrides -import 'package:flutter_web_ui/ui.dart' as ui show Image; +import 'dart:ui' as ui show Image; -import '../flutter.dart'; +import 'package:flutter/material.dart'; // A model on top of DecorationImage that supports slicing up the source image // efficiently to draw it as tiles in the puzzle game @@ -142,13 +138,13 @@ class DecorationImagePlus implements DecorationImage { @override String toString() { - final List properties = ['$image']; + final List properties = []; + properties.add('$image'); if (colorFilter != null) properties.add('$colorFilter'); if (fit != null && !(fit == BoxFit.fill && centerSlice != null) && - !(fit == BoxFit.scaleDown && centerSlice == null)) { + !(fit == BoxFit.scaleDown && centerSlice == null)) properties.add('$fit'); - } properties.add('$alignment'); if (centerSlice != null) properties.add('centerSlice: $centerSlice'); if (repeat != ImageRepeat.noRepeat) properties.add('$repeat'); @@ -170,13 +166,16 @@ class DecorationImagePlus implements DecorationImage { /// longer needed. class DecorationImagePainterPlus implements DecorationImagePainter { DecorationImagePainterPlus._(this._details, this._onChanged) - : assert(_details != null); + : assert(_details != null) { + _imageStreamListener = ImageStreamListener(_imageListener); + } final DecorationImagePlus _details; final VoidCallback _onChanged; ImageStream _imageStream; ImageInfo _image; + ImageStreamListener _imageStreamListener; /// Draw the image onto the given canvas. /// @@ -218,15 +217,14 @@ class DecorationImagePainterPlus implements DecorationImagePainter { final ImageStream newImageStream = _details.image.resolve(configuration); if (newImageStream.key != _imageStream?.key) { - _imageStream?.removeListener(_imageListener); - _imageStream = newImageStream..addListener(_imageListener); + _imageStream?.removeListener(_imageStreamListener); + _imageStream = newImageStream..addListener(_imageStreamListener); } if (_image == null) return; if (clipPath != null) { - canvas - ..save() - ..clipPath(clipPath); + canvas.save(); + canvas.clipPath(clipPath); } _paintImage( @@ -259,7 +257,7 @@ class DecorationImagePainterPlus implements DecorationImagePainter { /// After this method has been called, the object is no longer usable. @mustCallSuper void dispose() { - _imageStream?.removeListener(_imageListener); + _imageStream?.removeListener(_imageStreamListener); } @override diff --git a/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart b/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart index 8b2640ce0..9673cebae 100644 --- a/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart +++ b/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart @@ -1,8 +1,4 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import '../flutter.dart'; +import 'package:flutter/material.dart'; // Copied from // https://github.com/flutter/flutter/blob/f5b02e3c05ed1ab31e890add84fb56e35de2d392/packages/flutter/lib/src/material/material.dart#L593-L715 diff --git a/web/slide_puzzle/pubspec.lock b/web/slide_puzzle/pubspec.lock index 53677fd3a..45e194161 100644 --- a/web/slide_puzzle/pubspec.lock +++ b/web/slide_puzzle/pubspec.lock @@ -7,7 +7,7 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.37.0" + version: "0.36.0" archive: dependency: transitive description: @@ -29,83 +29,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.3.0" - bazel_worker: + boolean_selector: dependency: transitive description: - name: bazel_worker + name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" + version: "1.0.5" charcode: dependency: transitive description: @@ -113,20 +43,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -147,45 +63,16 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + version: "2.1.2" + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: - dependency: "direct main" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk version: "0.0.0" front_end: dependency: transitive @@ -193,7 +80,7 @@ packages: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.20" + version: "0.1.15" glob: dependency: transitive description: @@ -201,20 +88,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" http: dependency: transitive description: @@ -228,7 +101,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.0.5" http_parser: dependency: transitive description: @@ -236,13 +109,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.3" - intl: + image: dependency: transitive description: - name: intl + name: image url: "https://pub.dartlang.org" source: hosted - version: "0.15.8" + version: "2.1.4" io: dependency: transitive description: @@ -257,27 +130,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.1+1" - json_annotation: + json_rpc_2: dependency: transitive description: - name: json_annotation + name: json_rpc_2 url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.0.10" kernel: dependency: transitive description: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" + version: "0.3.15" matcher: dependency: transitive description: @@ -298,7 +164,21 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.6+3" + version: "0.9.6+2" + multi_server_socket: + dependency: transitive + description: + name: multi_server_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" package_config: dependency: transitive description: @@ -327,29 +207,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" - pool: + petitparser: dependency: transitive description: - name: pool + name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" - protobuf: + version: "2.4.0" + pool: dependency: transitive description: - name: protobuf + name: pool url: "https://pub.dartlang.org" source: hosted - version: "0.13.15" - provider: - dependency: "direct main" - description: - path: "." - ref: "5cf4521d4d6" - resolved-ref: "5cf4521d4d635d7d7ca8ddbd6e28048a7f319ee0" - url: "https://github.com/kevmoo/provider" - source: git - version: "2.1.0" + version: "1.4.0" pub_semver: dependency: transitive description: @@ -357,41 +228,53 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.2" - pubspec_parse: + quiver: dependency: transitive description: - name: pubspec_parse + name: quiver url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" - quiver: + version: "2.0.5" + shelf: dependency: transitive description: - name: quiver + name: shelf url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" - scratch_space: + version: "0.7.5" + shelf_packages_handler: dependency: transitive description: - name: scratch_space + name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "0.0.4" - shelf: + version: "1.0.4" + shelf_static: dependency: transitive description: - name: shelf + name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "0.2.8" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.2+5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.5" source_maps: dependency: transitive description: @@ -420,13 +303,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" string_scanner: dependency: transitive description: @@ -441,13 +317,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - timing: + test: + dependency: "direct dev" + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.3" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" + test_core: dependency: transitive description: - name: timing + name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+1" + version: "0.2.5" typed_data: dependency: transitive description: @@ -462,26 +352,40 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + vm_service_client: + dependency: transitive + description: + name: vm_service_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.6+1" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+12" + version: "0.9.7+10" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.14" + version: "1.0.12" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.1.16" + version: "2.1.15" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.4.0 <3.0.0" diff --git a/web/slide_puzzle/pubspec.yaml b/web/slide_puzzle/pubspec.yaml index 1742063f0..e782f2387 100644 --- a/web/slide_puzzle/pubspec.yaml +++ b/web/slide_puzzle/pubspec.yaml @@ -1,31 +1,27 @@ name: slide_puzzle +version: 1.0.0 + environment: - sdk: ">=2.2.0 <3.0.0" + sdk: ">=2.0.0 <3.0.0" dependencies: - flutter_web: any - flutter_web_ui: any - provider: any + flutter: + sdk: flutter dev_dependencies: + flutter_test: + sdk: flutter pedantic: ^1.3.0 + test: ^1.3.4 - build_runner: any - build_web_compilers: any +flutter: + uses-material-design: true + assets: + - asset/ + - preview.png -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui - provider: - git: - url: https://github.com/kevmoo/provider - ref: 5cf4521d4d6 # flutter_web, but v2 + fonts: + - family: Plaster + fonts: + - asset: asset/fonts/plaster/Plaster-Regular.ttf diff --git a/web/slide_puzzle/web/assets/FontManifest.json b/web/slide_puzzle/web/assets/FontManifest.json deleted file mode 100644 index 18f3f2f0e..000000000 --- a/web/slide_puzzle/web/assets/FontManifest.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "family": "MaterialIcons", - "fonts": [ - { - "asset": "https://fonts.gstatic.com/s/materialicons/v47/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" - } - ] - }, - { - "family": "Plaster", - "fonts": [ - { - "asset": "https://fonts.gstatic.com/l/font?kit=DdTm79QatW80eRh4EitJPNLPUrnLdNiMw9JVKCON&skey=6748ccde38e8a5e5&v=v10" - } - ] - } -] diff --git a/web/slide_puzzle/web/assets/readme.md b/web/slide_puzzle/web/assets/readme.md deleted file mode 100644 index da3a24263..000000000 --- a/web/slide_puzzle/web/assets/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -### Material Icon font - -Via https://google.github.io/material-design-icons/#icon-font-for-the-web - -Via https://fonts.googleapis.com/icon?family=Material+Icons - -### Plaster font - -Via https://fonts.google.com/specimen/Plaster - -Via https://fonts.googleapis.com/css?family=Plaster&text=0123456789 - -*Note `text=` includes only the used characters – 0-9.* - -*HT to https://twitter.com/csswizardry/status/1139435442506469376* diff --git a/web/slide_puzzle/web/assets/seattle.jpg b/web/slide_puzzle/web/assets/seattle.jpg deleted file mode 100644 index c2942becd..000000000 Binary files a/web/slide_puzzle/web/assets/seattle.jpg and /dev/null differ diff --git a/web/slide_puzzle/web/index.html b/web/slide_puzzle/web/index.html index 1785ebb8b..6bfc669e9 100644 --- a/web/slide_puzzle/web/index.html +++ b/web/slide_puzzle/web/index.html @@ -1,11 +1,10 @@ - + - - - + slide_puzzle + diff --git a/web/slide_puzzle/web/main.dart b/web/slide_puzzle/web/main.dart deleted file mode 100644 index 436328329..000000000 --- a/web/slide_puzzle/web/main.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:slide_puzzle/main.dart' as app; - -void main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/spinning_square/web/preview.png b/web/spinning_square/assets/preview.png similarity index 100% rename from web/spinning_square/web/preview.png rename to web/spinning_square/assets/preview.png diff --git a/web/spinning_square/lib/main.dart b/web/spinning_square/lib/main.dart index 3bc3c485f..bc468dc4b 100644 --- a/web/spinning_square/lib/main.dart +++ b/web/spinning_square/lib/main.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_web/material.dart'; +import 'package:flutter/material.dart'; class SpinningSquare extends StatefulWidget { @override diff --git a/web/spinning_square/pubspec.lock b/web/spinning_square/pubspec.lock index 76051ba3a..040daf466 100644 --- a/web/spinning_square/pubspec.lock +++ b/web/spinning_square/pubspec.lock @@ -1,132 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,157 +8,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - flutter_web_ui: - dependency: "direct overridden" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" meta: dependency: transitive description: @@ -292,153 +20,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: + sky_engine: dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" + description: flutter + source: sdk + version: "0.0.99" typed_data: dependency: transitive description: @@ -453,26 +39,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/web/spinning_square/pubspec.yaml b/web/spinning_square/pubspec.yaml index d031400b5..16273591f 100644 --- a/web/spinning_square/pubspec.yaml +++ b/web/spinning_square/pubspec.yaml @@ -4,20 +4,8 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any - -dev_dependencies: - build_runner: any - build_web_compilers: any - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui + flutter: + sdk: flutter +flutter: + assets: + - preview.png \ No newline at end of file diff --git a/web/spinning_square/web/main.dart b/web/spinning_square/web/main.dart deleted file mode 100644 index 6ba47e2d6..000000000 --- a/web/spinning_square/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:flutter_web.examples.spinning_square/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/timeflow/web/preview.png b/web/timeflow/assets/preview.png similarity index 100% rename from web/timeflow/web/preview.png rename to web/timeflow/assets/preview.png diff --git a/web/timeflow/lib/infinite_listview.dart b/web/timeflow/lib/infinite_listview.dart index bb9342ff3..33e52ef8f 100644 --- a/web/timeflow/lib/infinite_listview.dart +++ b/web/timeflow/lib/infinite_listview.dart @@ -3,8 +3,8 @@ import 'dart:math' as math; -import 'package:flutter_web/rendering.dart'; -import 'package:flutter_web/widgets.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; /// Infinite ListView /// diff --git a/web/timeflow/lib/main.dart b/web/timeflow/lib/main.dart index f32d65e0c..1c3443a37 100644 --- a/web/timeflow/lib/main.dart +++ b/web/timeflow/lib/main.dart @@ -1,7 +1,7 @@ import 'dart:core'; import 'dart:math'; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/scheduler.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'numberpicker.dart'; diff --git a/web/timeflow/lib/numberpicker.dart b/web/timeflow/lib/numberpicker.dart index 2cbff7255..fa4cff6b8 100644 --- a/web/timeflow/lib/numberpicker.dart +++ b/web/timeflow/lib/numberpicker.dart @@ -3,9 +3,9 @@ import 'dart:math' as math; -import 'package:flutter_web/foundation.dart'; -import 'package:flutter_web/material.dart'; -import 'package:flutter_web/rendering.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'infinite_listview.dart'; diff --git a/web/timeflow/pubspec.lock b/web/timeflow/pubspec.lock index ae60194b8..040daf466 100644 --- a/web/timeflow/pubspec.lock +++ b/web/timeflow/pubspec.lock @@ -1,132 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,157 +8,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: - dependency: "direct main" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.5" meta: dependency: transitive description: @@ -292,153 +20,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: + sky_engine: dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" + description: flutter + source: sdk + version: "0.0.99" typed_data: dependency: transitive description: @@ -453,26 +39,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/web/timeflow/pubspec.yaml b/web/timeflow/pubspec.yaml index 1e2ea6ece..dd3aa6641 100644 --- a/web/timeflow/pubspec.yaml +++ b/web/timeflow/pubspec.yaml @@ -4,20 +4,9 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any - flutter_web_ui: any - -dev_dependencies: - build_runner: any - build_web_compilers: any -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui + flutter: + sdk: flutter +flutter: + uses-material-design: true + assets: + - preview.png \ No newline at end of file diff --git a/web/timeflow/web/assets/FontManifest.json b/web/timeflow/web/assets/FontManifest.json deleted file mode 100644 index 01fc0852f..000000000 --- a/web/timeflow/web/assets/FontManifest.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "family": "MaterialIcons", - "fonts": [ - { - "asset": "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" - } - ] - } - ] \ No newline at end of file diff --git a/web/timeflow/web/main.dart b/web/timeflow/web/main.dart deleted file mode 100644 index e8472a48c..000000000 --- a/web/timeflow/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:timeflow/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -} diff --git a/web/vision_challenge/web/assets/10.png b/web/vision_challenge/assets/10.png similarity index 100% rename from web/vision_challenge/web/assets/10.png rename to web/vision_challenge/assets/10.png diff --git a/web/vision_challenge/web/assets/20.png b/web/vision_challenge/assets/20.png similarity index 100% rename from web/vision_challenge/web/assets/20.png rename to web/vision_challenge/assets/20.png diff --git a/web/vision_challenge/web/assets/30.png b/web/vision_challenge/assets/30.png similarity index 100% rename from web/vision_challenge/web/assets/30.png rename to web/vision_challenge/assets/30.png diff --git a/web/vision_challenge/web/assets/35.png b/web/vision_challenge/assets/35.png similarity index 100% rename from web/vision_challenge/web/assets/35.png rename to web/vision_challenge/assets/35.png diff --git a/web/vision_challenge/web/assets/40.png b/web/vision_challenge/assets/40.png similarity index 100% rename from web/vision_challenge/web/assets/40.png rename to web/vision_challenge/assets/40.png diff --git a/web/vision_challenge/web/assets/45.png b/web/vision_challenge/assets/45.png similarity index 100% rename from web/vision_challenge/web/assets/45.png rename to web/vision_challenge/assets/45.png diff --git a/web/vision_challenge/web/assets/99.png b/web/vision_challenge/assets/99.png similarity index 100% rename from web/vision_challenge/web/assets/99.png rename to web/vision_challenge/assets/99.png diff --git a/web/vision_challenge/web/assets/FontManifest.json b/web/vision_challenge/assets/FontManifest.json similarity index 100% rename from web/vision_challenge/web/assets/FontManifest.json rename to web/vision_challenge/assets/FontManifest.json diff --git a/web/vision_challenge/web/assets/p0.jpg b/web/vision_challenge/assets/p0.jpg similarity index 100% rename from web/vision_challenge/web/assets/p0.jpg rename to web/vision_challenge/assets/p0.jpg diff --git a/web/vision_challenge/web/assets/p1.jpg b/web/vision_challenge/assets/p1.jpg similarity index 100% rename from web/vision_challenge/web/assets/p1.jpg rename to web/vision_challenge/assets/p1.jpg diff --git a/web/vision_challenge/web/preview.png b/web/vision_challenge/assets/preview.png similarity index 100% rename from web/vision_challenge/web/preview.png rename to web/vision_challenge/assets/preview.png diff --git a/web/vision_challenge/lib/game.dart b/web/vision_challenge/lib/game.dart index 7833ad82c..3cfcdcf98 100644 --- a/web/vision_challenge/lib/game.dart +++ b/web/vision_challenge/lib/game.dart @@ -1,8 +1,8 @@ import 'dart:math'; -import 'package:flutter_web/material.dart'; -import 'package:vision_challenge/packages/flutter_redux.dart'; -import 'package:vision_challenge/packages/redux.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:redux/redux.dart'; setText(text, size, color) => Text(text, style: TextStyle( diff --git a/web/vision_challenge/lib/main.dart b/web/vision_challenge/lib/main.dart index c7b45ab46..60bda4aed 100644 --- a/web/vision_challenge/lib/main.dart +++ b/web/vision_challenge/lib/main.dart @@ -1,6 +1,6 @@ -import 'package:flutter_web/material.dart'; -import 'package:vision_challenge/packages/flutter_redux.dart'; -import 'package:vision_challenge/packages/redux.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:redux/redux.dart'; import 'game.dart'; void main() { diff --git a/web/vision_challenge/lib/packages/flutter_redux.dart b/web/vision_challenge/lib/packages/flutter_redux.dart deleted file mode 100644 index 54cb7461f..000000000 --- a/web/vision_challenge/lib/packages/flutter_redux.dart +++ /dev/null @@ -1,513 +0,0 @@ -// Package flutter_redux: -// https://pub.dev/packages/flutter_redux - -import 'dart:async'; - -import 'package:flutter_web/widgets.dart'; -import 'package:meta/meta.dart'; -import 'redux.dart'; - -/// Provides a Redux [Store] to all descendants of this Widget. This should -/// generally be a root widget in your App. Connect to the Store provided -/// by this Widget using a [StoreConnector] or [StoreBuilder]. -class StoreProvider extends InheritedWidget { - final Store _store; - - /// Create a [StoreProvider] by passing in the required [store] and [child] - /// parameters. - const StoreProvider({ - Key key, - @required Store store, - @required Widget child, - }) : assert(store != null), - assert(child != null), - _store = store, - super(key: key, child: child); - - /// A method that can be called by descendant Widgets to retrieve the Store - /// from the StoreProvider. - /// - /// Important: When using this method, pass through complete type information - /// or Flutter will be unable to find the correct StoreProvider! - /// - /// ### Example - /// - /// ``` - /// class MyWidget extends StatelessWidget { - /// @override - /// Widget build(BuildContext context) { - /// final store = StoreProvider.of(context); - /// - /// return Text('${store.state}'); - /// } - /// } - /// ``` - static Store of(BuildContext context) { - final type = _typeOf>(); - final provider = - context.inheritFromWidgetOfExactType(type) as StoreProvider; - - if (provider == null) throw StoreProviderError(type); - - return provider._store; - } - - // Workaround to capture generics - static Type _typeOf() => T; - - @override - bool updateShouldNotify(StoreProvider oldWidget) => - _store != oldWidget._store; -} - -/// Build a Widget using the [BuildContext] and [ViewModel]. The [ViewModel] is -/// derived from the [Store] using a [StoreConverter]. -typedef ViewModelBuilder = Widget Function( - BuildContext context, - ViewModel vm, -); - -/// Convert the entire [Store] into a [ViewModel]. The [ViewModel] will be used -/// to build a Widget using the [ViewModelBuilder]. -typedef StoreConverter = ViewModel Function( - Store store, -); - -/// A function that will be run when the [StoreConnector] is initialized (using -/// the [State.initState] method). This can be useful for dispatching actions -/// that fetch data for your Widget when it is first displayed. -typedef OnInitCallback = void Function( - Store store, -); - -/// A function that will be run when the StoreConnector is removed from the -/// Widget Tree. -/// -/// It is run in the [State.dispose] method. -/// -/// This can be useful for dispatching actions that remove stale data from -/// your State tree. -typedef OnDisposeCallback = void Function( - Store store, -); - -/// A test of whether or not your `converter` function should run in response -/// to a State change. For advanced use only. -/// -/// Some changes to the State of your application will mean your `converter` -/// function can't produce a useful ViewModel. In these cases, such as when -/// performing exit animations on data that has been removed from your Store, -/// it can be best to ignore the State change while your animation completes. -/// -/// To ignore a change, provide a function that returns true or false. If the -/// returned value is true, the change will be ignored. -/// -/// If you ignore a change, and the framework needs to rebuild the Widget, the -/// `builder` function will be called with the latest `ViewModel` produced by -/// your `converter` function. -typedef IgnoreChangeTest = bool Function(S state); - -/// A function that will be run on State change, before the build method. -/// -/// This function is passed the `ViewModel`, and if `distinct` is `true`, -/// it will only be called if the `ViewModel` changes. -/// -/// This can be useful for imperative calls to things like Navigator, -/// TabController, etc -typedef OnWillChangeCallback = void Function(ViewModel viewModel); - -/// A function that will be run on State change, after the build method. -/// -/// This function is passed the `ViewModel`, and if `distinct` is `true`, -/// it will only be called if the `ViewModel` changes. -/// -/// This can be useful for running certain animations after the build is -/// complete. -/// -/// Note: Using a [BuildContext] inside this callback can cause problems if -/// the callback performs navigation. For navigation purposes, please use -/// an [OnWillChangeCallback]. -typedef OnDidChangeCallback = void Function(ViewModel viewModel); - -/// A function that will be run after the Widget is built the first time. -/// -/// This function is passed the initial `ViewModel` created by the `converter` -/// function. -/// -/// This can be useful for starting certain animations, such as showing -/// Snackbars, after the Widget is built the first time. -typedef OnInitialBuildCallback = void Function(ViewModel viewModel); - -/// Build a widget based on the state of the [Store]. -/// -/// Before the [builder] is run, the [converter] will convert the store into a -/// more specific `ViewModel` tailored to the Widget being built. -/// -/// Every time the store changes, the Widget will be rebuilt. As a performance -/// optimization, the Widget can be rebuilt only when the [ViewModel] changes. -/// In order for this to work correctly, you must implement [==] and [hashCode] -/// for the [ViewModel], and set the [distinct] option to true when creating -/// your StoreConnector. -class StoreConnector extends StatelessWidget { - /// Build a Widget using the [BuildContext] and [ViewModel]. The [ViewModel] - /// is created by the [converter] function. - final ViewModelBuilder builder; - - /// Convert the [Store] into a [ViewModel]. The resulting [ViewModel] will be - /// passed to the [builder] function. - final StoreConverter converter; - - /// As a performance optimization, the Widget can be rebuilt only when the - /// [ViewModel] changes. In order for this to work correctly, you must - /// implement [==] and [hashCode] for the [ViewModel], and set the [distinct] - /// option to true when creating your StoreConnector. - final bool distinct; - - /// A function that will be run when the StoreConnector is initially created. - /// It is run in the [State.initState] method. - /// - /// This can be useful for dispatching actions that fetch data for your Widget - /// when it is first displayed. - final OnInitCallback onInit; - - /// A function that will be run when the StoreConnector is removed from the - /// Widget Tree. - /// - /// It is run in the [State.dispose] method. - /// - /// This can be useful for dispatching actions that remove stale data from - /// your State tree. - final OnDisposeCallback onDispose; - - /// Determines whether the Widget should be rebuilt when the Store emits an - /// onChange event. - final bool rebuildOnChange; - - /// A test of whether or not your [converter] function should run in response - /// to a State change. For advanced use only. - /// - /// Some changes to the State of your application will mean your [converter] - /// function can't produce a useful ViewModel. In these cases, such as when - /// performing exit animations on data that has been removed from your Store, - /// it can be best to ignore the State change while your animation completes. - /// - /// To ignore a change, provide a function that returns true or false. If the - /// returned value is true, the change will be ignored. - /// - /// If you ignore a change, and the framework needs to rebuild the Widget, the - /// [builder] function will be called with the latest [ViewModel] produced by - /// your [converter] function. - final IgnoreChangeTest ignoreChange; - - /// A function that will be run on State change, before the Widget is built. - /// - /// This function is passed the `ViewModel`, and if `distinct` is `true`, - /// it will only be called if the `ViewModel` changes. - /// - /// This can be useful for imperative calls to things like Navigator, - /// TabController, etc - final OnWillChangeCallback onWillChange; - - /// A function that will be run on State change, after the Widget is built. - /// - /// This function is passed the `ViewModel`, and if `distinct` is `true`, - /// it will only be called if the `ViewModel` changes. - /// - /// This can be useful for running certain animations after the build is - /// complete. - /// - /// Note: Using a [BuildContext] inside this callback can cause problems if - /// the callback performs navigation. For navigation purposes, please use - /// [onWillChange]. - final OnDidChangeCallback onDidChange; - - /// A function that will be run after the Widget is built the first time. - /// - /// This function is passed the initial `ViewModel` created by the [converter] - /// function. - /// - /// This can be useful for starting certain animations, such as showing - /// Snackbars, after the Widget is built the first time. - final OnInitialBuildCallback onInitialBuild; - - /// Create a [StoreConnector] by passing in the required [converter] and - /// [builder] functions. - /// - /// You can also specify a number of additional parameters that allow you to - /// modify the behavior of the StoreConnector. Please see the documentation - /// for each option for more info. - StoreConnector({ - Key key, - @required this.builder, - @required this.converter, - this.distinct = false, - this.onInit, - this.onDispose, - this.rebuildOnChange = true, - this.ignoreChange, - this.onWillChange, - this.onDidChange, - this.onInitialBuild, - }) : assert(builder != null), - assert(converter != null), - super(key: key); - - @override - Widget build(BuildContext context) { - return _StoreStreamListener( - store: StoreProvider.of(context), - builder: builder, - converter: converter, - distinct: distinct, - onInit: onInit, - onDispose: onDispose, - rebuildOnChange: rebuildOnChange, - ignoreChange: ignoreChange, - onWillChange: onWillChange, - onDidChange: onDidChange, - onInitialBuild: onInitialBuild, - ); - } -} - -/// Build a Widget by passing the [Store] directly to the build function. -/// -/// Generally, it's considered best practice to use the [StoreConnector] and to -/// build a `ViewModel` specifically for your Widget rather than passing through -/// the entire [Store], but this is provided for convenience when that isn't -/// necessary. -class StoreBuilder extends StatelessWidget { - static Store _identity(Store store) => store; - - /// Builds a Widget using the [BuildContext] and your [Store]. - final ViewModelBuilder> builder; - - /// Indicates whether or not the Widget should rebuild when the [Store] emits - /// an `onChange` event. - final bool rebuildOnChange; - - /// A function that will be run when the StoreConnector is initially created. - /// It is run in the [State.initState] method. - /// - /// This can be useful for dispatching actions that fetch data for your Widget - /// when it is first displayed. - final OnInitCallback onInit; - - /// A function that will be run when the StoreBuilder is removed from the - /// Widget Tree. - /// - /// It is run in the [State.dispose] method. - /// - /// This can be useful for dispatching actions that remove stale data from - /// your State tree. - final OnDisposeCallback onDispose; - - /// A function that will be run on State change, before the Widget is built. - /// - /// This can be useful for imperative calls to things like Navigator, - /// TabController, etc - final OnWillChangeCallback> onWillChange; - - /// A function that will be run on State change, after the Widget is built. - /// - /// This can be useful for running certain animations after the build is - /// complete - /// - /// Note: Using a [BuildContext] inside this callback can cause problems if - /// the callback performs navigation. For navigation purposes, please use - /// [onWillChange]. - final OnDidChangeCallback> onDidChange; - - /// A function that will be run after the Widget is built the first time. - /// - /// This can be useful for starting certain animations, such as showing - /// Snackbars, after the Widget is built the first time. - final OnInitialBuildCallback> onInitialBuild; - - /// Create's a Widget based on the Store. - StoreBuilder({ - Key key, - @required this.builder, - this.onInit, - this.onDispose, - this.rebuildOnChange = true, - this.onWillChange, - this.onDidChange, - this.onInitialBuild, - }) : assert(builder != null), - super(key: key); - - @override - Widget build(BuildContext context) { - return StoreConnector>( - builder: builder, - converter: _identity, - rebuildOnChange: rebuildOnChange, - onInit: onInit, - onDispose: onDispose, - onWillChange: onWillChange, - onDidChange: onDidChange, - onInitialBuild: onInitialBuild, - ); - } -} - -/// Listens to the [Store] and calls [builder] whenever [store] changes. -class _StoreStreamListener extends StatefulWidget { - final ViewModelBuilder builder; - final StoreConverter converter; - final Store store; - final bool rebuildOnChange; - final bool distinct; - final OnInitCallback onInit; - final OnDisposeCallback onDispose; - final IgnoreChangeTest ignoreChange; - final OnWillChangeCallback onWillChange; - final OnDidChangeCallback onDidChange; - final OnInitialBuildCallback onInitialBuild; - - _StoreStreamListener({ - Key key, - @required this.builder, - @required this.store, - @required this.converter, - this.distinct = false, - this.onInit, - this.onDispose, - this.rebuildOnChange = true, - this.ignoreChange, - this.onWillChange, - this.onDidChange, - this.onInitialBuild, - }) : super(key: key); - - @override - State createState() { - return _StoreStreamListenerState(); - } -} - -class _StoreStreamListenerState - extends State<_StoreStreamListener> { - Stream stream; - ViewModel latestValue; - - @override - void initState() { - _init(); - - super.initState(); - } - - @override - void dispose() { - if (widget.onDispose != null) { - widget.onDispose(widget.store); - } - - super.dispose(); - } - - @override - void didUpdateWidget(_StoreStreamListener oldWidget) { - if (widget.store != oldWidget.store) { - _init(); - } - - super.didUpdateWidget(oldWidget); - } - - void _init() { - if (widget.onInit != null) { - widget.onInit(widget.store); - } - - latestValue = widget.converter(widget.store); - - if (widget.onInitialBuild != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onInitialBuild(latestValue); - }); - } - - var _stream = widget.store.onChange; - - if (widget.ignoreChange != null) { - _stream = _stream.where((state) => !widget.ignoreChange(state)); - } - - stream = _stream.map((_) => widget.converter(widget.store)); - - // Don't use `Stream.distinct` because it cannot capture the initial - // ViewModel produced by the `converter`. - if (widget.distinct) { - stream = stream.where((vm) { - final isDistinct = vm != latestValue; - - return isDistinct; - }); - } - - // After each ViewModel is emitted from the Stream, we update the - // latestValue. Important: This must be done after all other optional - // transformations, such as ignoreChange. - stream = - stream.transform(StreamTransformer.fromHandlers(handleData: (vm, sink) { - latestValue = vm; - - if (widget.onWillChange != null) { - widget.onWillChange(latestValue); - } - - if (widget.onDidChange != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onDidChange(latestValue); - }); - } - - sink.add(vm); - })); - } - - @override - Widget build(BuildContext context) { - return widget.rebuildOnChange - ? StreamBuilder( - stream: stream, - builder: (context, snapshot) => widget.builder( - context, - snapshot.hasData ? snapshot.data : latestValue, - ), - ) - : widget.builder(context, latestValue); - } -} - -/// If the StoreProvider.of method fails, this error will be thrown. -/// -/// Often, when the `of` method fails, it is difficult to understand why since -/// there can be multiple causes. This error explains those causes so the user -/// can understand and fix the issue. -class StoreProviderError extends Error { - /// The type of the class the user tried to retrieve - Type type; - - /// Creates a StoreProviderError - StoreProviderError(this.type); - - @override - String toString() { - return '''Error: No $type found. To fix, please try: - - * Wrapping your MaterialApp with the StoreProvider, - rather than an individual Route - * Providing full type information to your Store, - StoreProvider and StoreConnector - * Ensure you are using consistent and complete imports. - E.g. always use `import 'package:my_app/app_state.dart'; - -If none of these solutions work, please file a bug at: -https://github.com/brianegan/flutter_redux/issues/new - '''; - } -} diff --git a/web/vision_challenge/lib/packages/redux.dart b/web/vision_challenge/lib/packages/redux.dart deleted file mode 100644 index 75ffc8121..000000000 --- a/web/vision_challenge/lib/packages/redux.dart +++ /dev/null @@ -1,519 +0,0 @@ -// Package redux: -// https://pub.dev/packages/redux - -import 'dart:async'; - -/// Defines an application's state change -/// -/// Implement this typedef to modify your app state in response to a given -/// action. -/// -/// ### Example -/// -/// int counterReducer(int state, action) { -/// switch (action) { -/// case 'INCREMENT': -/// return state + 1; -/// case 'DECREMENT': -/// return state - 1; -/// default: -/// return state; -/// } -/// } -/// -/// final store = new Store(counterReducer); -typedef Reducer = State Function(State state, dynamic action); - -/// Defines a [Reducer] using a class interface. -/// -/// Implement this class to modify your app state in response to a given action. -/// -/// For some use cases, a class may be preferred to a function. In these -/// instances, a ReducerClass can be used. -/// -/// ### Example -/// -/// class CounterReducer extends ReducerClass { -/// int call(int state, action) { -/// switch (action) { -/// case 'INCREMENT': -/// return state + 1; -/// case 'DECREMENT': -/// return state - 1; -/// default: -/// return state; -/// } -/// } -/// } -/// -/// final store = new Store(new CounterReducer()); -abstract class ReducerClass { - State call(State state, dynamic action); -} - -/// A function that intercepts actions and potentially transform actions before -/// they reach the reducer. -/// -/// Middleware intercept actions before they reach the reducer. This gives them -/// the ability to produce side-effects or modify the passed in action before -/// they reach the reducer. -/// -/// ### Example -/// -/// loggingMiddleware(Store store, action, NextDispatcher next) { -/// print('${new DateTime.now()}: $action'); -/// -/// next(action); -/// } -/// -/// // Create your store with the loggingMiddleware -/// final store = new Store( -/// counterReducer, -/// middleware: [loggingMiddleware], -/// ); -typedef Middleware = void Function( - Store store, - dynamic action, - NextDispatcher next, -); - -/// Defines a [Middleware] using a Class interface. -/// -/// Middleware intercept actions before they reach the reducer. This gives them -/// the ability to produce side-effects or modify the passed in action before -/// they reach the reducer. -/// -/// For some use cases, a class may be preferred to a function. In these -/// instances, a MiddlewareClass can be used. -/// -/// ### Example -/// class LoggingMiddleware extends MiddlewareClass { -/// call(Store store, action, NextDispatcher next) { -/// print('${new DateTime.now()}: $action'); -/// -/// next(action); -/// } -/// } -/// -/// // Create your store with the loggingMiddleware -/// final store = new Store( -/// counterReducer, -/// middleware: [new LoggingMiddleware()], -/// ); -abstract class MiddlewareClass { - void call(Store store, dynamic action, NextDispatcher next); -} - -/// The contract between one piece of middleware and the next in the chain. Use -/// it to send the current action in your [Middleware] to the next piece of -/// [Middleware] in the chain. -/// -/// Middleware can optionally pass the original action or a modified action to -/// the next piece of middleware, or never call the next piece of middleware at -/// all. -typedef NextDispatcher = void Function(dynamic action); - -/// Creates a Redux store that holds the app state tree. -/// -/// The only way to change the state tree in the store is to [dispatch] an -/// action. the action will then be intercepted by any provided [Middleware]. -/// After running through the middleware, the action will be sent to the given -/// [Reducer] to update the state tree. -/// -/// To access the state tree, call the [state] getter or listen to the -/// [onChange] stream. -/// -/// ### Basic Example -/// -/// // Create a reducer -/// final increment = 'INCREMENT'; -/// final decrement = 'DECREMENT'; -/// -/// int counterReducer(int state, action) { -/// switch (action) { -/// case increment: -/// return state + 1; -/// case decrement: -/// return state - 1; -/// default: -/// return state; -/// } -/// } -/// -/// // Create the store -/// final store = new Store(counterReducer, initialState: 0); -/// -/// // Print the Store's state. -/// print(store.state); // prints "0" -/// -/// // Dispatch an action. This will be sent to the reducer to update the -/// // state. -/// store.dispatch(increment); -/// -/// // Print the updated state. As an alternative, you can use the -/// // `store.onChange.listen` to respond to all state change events. -/// print(store.state); // prints "1" -class Store { - /// The [Reducer] for your Store. Allows you to get the current reducer or - /// replace it with a new one if need be. - Reducer reducer; - - final StreamController _changeController; - State _state; - List _dispatchers; - - Store( - this.reducer, { - State initialState, - List> middleware = const [], - bool syncStream = false, - - /// If set to true, the Store will not emit onChange events if the new State - /// that is returned from your [reducer] in response to an Action is equal - /// to the previous state. - /// - /// Under the hood, it will use the `==` method from your State class to - /// determine whether or not the two States are equal. - bool distinct = false, - }) : _changeController = StreamController.broadcast(sync: syncStream) { - _state = initialState; - _dispatchers = _createDispatchers( - middleware, - _createReduceAndNotify(distinct), - ); - } - - /// Returns the current state of the app - State get state => _state; - - /// A stream that emits the current state when it changes. - /// - /// ### Example - /// - /// // First, create the Store - /// final store = new Store(counterReducer, 0); - /// - /// // Next, listen to the Store's onChange stream, and print the latest - /// // state to your console whenever the reducer produces a new State. - /// // - /// // We'll store the StreamSubscription as a variable so we can stop - /// // listening later. - /// final subscription = store.onChange.listen(print); - /// - /// // Dispatch some actions, and see the printing magic! - /// store.dispatch("INCREMENT"); // prints 1 - /// store.dispatch("INCREMENT"); // prints 2 - /// store.dispatch("DECREMENT"); // prints 1 - /// - /// // When you want to stop printing the state to the console, simply - /// `cancel` your `subscription`. - /// subscription.cancel(); - Stream get onChange => _changeController.stream; - - // Creates the base [NextDispatcher]. - // - // The base NextDispatcher will be called after all other middleware provided - // by the user have been run. Its job is simple: Run the current state through - // the reducer, save the result, and notify any subscribers. - NextDispatcher _createReduceAndNotify(bool distinct) { - return (dynamic action) { - final state = reducer(_state, action); - - if (distinct && state == _state) return; - - _state = state; - _changeController.add(state); - }; - } - - List _createDispatchers( - List> middleware, - NextDispatcher reduceAndNotify, - ) { - final dispatchers = []..add(reduceAndNotify); - - // Convert each [Middleware] into a [NextDispatcher] - for (var nextMiddleware in middleware.reversed) { - final next = dispatchers.last; - - dispatchers.add( - (dynamic action) => nextMiddleware(this, action, next), - ); - } - - return dispatchers.reversed.toList(); - } - - /// Runs the action through all provided [Middleware], then applies an action - /// to the state using the given [Reducer]. Please note: [Middleware] can - /// intercept actions, and can modify actions or stop them from passing - /// through to the reducer. - void dispatch(dynamic action) { - _dispatchers[0](action); - } - - /// Closes down the Store so it will no longer be operational. Only use this - /// if you want to destroy the Store while your app is running. Do not use - /// this method as a way to stop listening to [onChange] state changes. For - /// that purpose, view the [onChange] documentation. - Future teardown() async { - _state = null; - return _changeController.close(); - } -} - -/// A convenience class for binding Reducers to Actions of a given Type. This -/// allows for type safe [Reducer]s and reduces boilerplate. -/// -/// ### Example -/// -/// In order to see what this utility function does, let's take a look at a -/// regular example of using reducers based on the Type of an action. -/// -/// ``` -/// // We define out State and Action classes. -/// class AppState { -/// final List items; -/// -/// AppState(this.items); -/// } -/// -/// class LoadItemsAction {} -/// class UpdateItemsAction {} -/// class AddItemAction{} -/// class RemoveItemAction {} -/// class ShuffleItemsAction {} -/// class ReverseItemsAction {} -/// class ItemsLoadedAction { -/// final List items; -/// -/// ItemsLoadedAction(this.items); -/// } -/// -/// // Then we define our reducer. Since we handle different actions in our -/// // reducer, we need to determine what kind of action we're working with -/// // using if statements, and then run some computation in response. -/// // -/// // This isn't a big deal if we have relatively few cases to handle, but your -/// // reducer function can quickly grow large and take on too many -/// // responsibilities as demonstrated here with pseudo-code. -/// final appReducer = (AppState state, action) { -/// if (action is ItemsLoadedAction) { -/// return new AppState(action.items); -/// } else if (action is UpdateItemsAction) { -/// return ...; -/// } else if (action is AddItemAction) { -/// return ...; -/// } else if (action is RemoveItemAction) { -/// return ...; -/// } else if (action is ShuffleItemsAction) { -/// return ...; -/// } else if (action is ReverseItemsAction) { -/// return ...; -/// } else { -/// return state; -/// } -/// }; -/// ``` -/// -/// What would be nice would be to break our big reducer up into smaller -/// reducers. It would also be nice to bind specific Types of Actions to -/// specific reducers so we can ensure type safety for our reducers while -/// avoiding large trees of `if` statements. -/// -/// ``` -/// // First, we'll break out all of our individual State Changes into -/// // individual reducers. These can be easily tested or composed! -/// final loadItemsReducer = (AppState state, LoadTodosAction action) => -/// return new AppState(action.items); -/// -/// final updateItemsReducer = (AppState state, UpdateItemsAction action) { -/// return ...; -/// } -/// -/// final addItemReducer = (AppState state, AddItemAction action) { -/// return ...; -/// } -/// -/// final removeItemReducer = (AppState state, RemoveItemAction action) { -/// return ...; -/// } -/// -/// final shuffleItemsReducer = (AppState state, ShuffleItemAction action) { -/// return ...; -/// } -/// -/// final reverseItemsReducer = (AppState state, ReverseItemAction action) { -/// return ...; -/// } -/// -/// // We will then wire up specific types of actions to our reducer functions -/// // above. This will return a new Reducer which puts everything -/// // together!. -/// final Reducer appReducer = combineReducers([ -/// new TypedReducer(loadItemsReducer), -/// new TypedReducer(updateItemsReducer), -/// new TypedReducer(addItemReducer), -/// new TypedReducer(removeItemReducer), -/// new TypedReducer(shuffleItemsReducer), -/// new TypedReducer(reverseItemsReducer), -/// ]); -/// ``` -class TypedReducer implements ReducerClass { - final State Function(State state, Action action) reducer; - - TypedReducer(this.reducer); - - @override - State call(State state, dynamic action) { - if (action is Action) { - return reducer(state, action); - } - - return state; - } -} - -/// A convenience type for binding a piece of Middleware to an Action -/// of a specific type. Allows for Type Safe Middleware and reduces boilerplate. -/// -/// ### Example -/// -/// In order to see what this utility function does, let's take a look at a -/// regular example of running Middleware based on the Type of an action. -/// -/// ``` -/// class AppState { -/// final List items; -/// -/// AppState(this.items); -/// } -/// class LoadItemsAction {} -/// class UpdateItemsAction {} -/// class AddItemAction{} -/// class RemoveItemAction {} -/// class ShuffleItemsAction {} -/// class ReverseItemsAction {} -/// class ItemsLoadedAction { -/// final List items; -/// -/// ItemsLoadedAction(this.items); -/// } -/// -/// final loadItems = () { /* Function that loads a Future> */} -/// final saveItems = (List items) { /* Function that persists items */} -/// -/// final middleware = (Store store, action, NextDispatcher next) { -/// if (action is LoadItemsAction) { -/// loadItems() -/// .then((items) => store.dispatch(new ItemsLoaded(items)) -/// .catchError((_) => store.dispatch(new ItemsNotLoaded()); -/// -/// next(action); -/// } else if (action is UpdateItemsAction || -/// action is AddItemAction || -/// action is RemoveItemAction || -/// action is ShuffleItemsAction || -/// action is ReverseItemsAction) { -/// next(action); -/// -/// saveItems(store.state.items); -/// } else { -/// next(action); -/// } -/// }; -/// ``` -/// -/// This works fine if you have one or two actions to handle, but you might -/// notice it's getting a bit messy already. Let's see how this lib helps clean -/// it up. -/// -/// ``` -/// // First, let's start by breaking up our functionality into two middleware -/// // functions. -/// // -/// // The loadItemsMiddleware will only handle the `LoadItemsAction`s that -/// // are dispatched, so we can annotate the Type of action. -/// final loadItemsMiddleware = ( -/// Store store, -/// LoadItemsAction action, -/// NextDispatcher next, -/// ) { -/// loadItems() -/// .then((items) => store.dispatch(new ItemsLoaded(items)) -/// .catchError((_) => store.dispatch(new ItemsNotLoaded()); -/// -/// next(action); -/// } -/// -/// // The saveItemsMiddleware handles all actions that change the Items, but -/// // does not depend on the payload of the action. Therefore, `action` will -/// // remain dynamic. -/// final saveItemsMiddleware = ( -/// Store store, -/// dynamic action, -/// NextDispatcher next, -/// ) { -/// next(action); -/// -/// saveItems(store.state.items); -/// } -/// -/// // We will then wire up specific types of actions to a List of Middleware -/// // that handle those actions. -/// final List> middleware = [ -/// new TypedMiddleware(loadItemsMiddleware), -/// new TypedMiddleware(saveItemsMiddleware), -/// new TypedMiddleware(saveItemsMiddleware), -/// new TypedMiddleware(saveItemsMiddleware), -/// new TypedMiddleware(saveItemsMiddleware), -/// new TypedMiddleware(saveItemsMiddleware), -/// ]; -/// ``` -class TypedMiddleware implements MiddlewareClass { - final void Function( - Store store, - Action action, - NextDispatcher next, - ) middleware; - - TypedMiddleware(this.middleware); - - @override - void call(Store store, dynamic action, NextDispatcher next) { - if (action is Action) { - middleware(store, action, next); - } else { - next(action); - } - } -} - -/// Defines a utility function that combines several reducers. -/// -/// In order to prevent having one large, monolithic reducer in your app, it can -/// be convenient to break reducers up into smaller parts that handle more -/// specific functionality that can be decoupled and easily tested. -/// -/// ### Example -/// -/// helloReducer(state, action) { -/// return "hello"; -/// } -/// -/// friendReducer(state, action) { -/// return state + " friend"; -/// } -/// -/// final helloFriendReducer = combineReducers( -/// helloReducer, -/// friendReducer, -/// ); -Reducer combineReducers(Iterable> reducers) { - return (State state, dynamic action) { - for (final reducer in reducers) { - state = reducer(state, action); - } - return state; - }; -} diff --git a/web/vision_challenge/pubspec.lock b/web/vision_challenge/pubspec.lock index 76051ba3a..657e7fce0 100644 --- a/web/vision_challenge/pubspec.lock +++ b/web/vision_challenge/pubspec.lock @@ -1,132 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.37.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.21" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "6.7.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" collection: dependency: transitive description: @@ -134,157 +8,18 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.9" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.9" - flutter_web: + flutter: dependency: "direct main" - description: - path: "packages/flutter_web" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git + description: flutter + source: sdk version: "0.0.0" - flutter_web_ui: - dependency: "direct overridden" - description: - path: "packages/flutter_web_ui" - ref: HEAD - resolved-ref: "88a6325290b75326c072ae63bf3016e15a08fccc" - url: "https://github.com/flutter/flutter_web" - source: git - version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.20" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.0+2" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0+2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.8" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.20" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" - matcher: - dependency: transitive + flutter_redux: + dependency: "direct main" description: - name: matcher + name: flutter_redux url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" + version: "0.5.3" meta: dependency: transitive description: @@ -292,153 +27,18 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+3" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.4" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.15" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.5" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.8" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.5" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.3" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.19" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive + redux: + dependency: "direct main" description: - name: term_glyph + name: redux url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - timing: + version: "3.0.0" + sky_engine: dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" + description: flutter + source: sdk + version: "0.0.99" typed_data: dependency: transitive description: @@ -453,26 +53,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+12" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.14" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.16" sdks: - dart: ">=2.3.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/web/vision_challenge/pubspec.yaml b/web/vision_challenge/pubspec.yaml index 28770c8a4..bf186277f 100644 --- a/web/vision_challenge/pubspec.yaml +++ b/web/vision_challenge/pubspec.yaml @@ -5,20 +5,21 @@ environment: sdk: ">=2.2.0 <3.0.0" dependencies: - flutter_web: any + flutter: + sdk: flutter + redux: ^3.0.0 + flutter_redux: ^0.5.3 -dev_dependencies: - build_runner: any - build_web_compilers: any - -# flutter_web packages are not published to pub.dartlang.org -# These overrides tell the package tools to get them from GitHub -dependency_overrides: - flutter_web: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web - flutter_web_ui: - git: - url: https://github.com/flutter/flutter_web - path: packages/flutter_web_ui +flutter: + uses-material-design: true + assets: + - 10.png + - 20.png + - 30.png + - 35.png + - 40.png + - 45.png + - 99.png + - p0.jpg + - p1.jpg + - preview.png diff --git a/web/vision_challenge/web/main.dart b/web/vision_challenge/web/main.dart deleted file mode 100644 index cd4683039..000000000 --- a/web/vision_challenge/web/main.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -import 'package:flutter_web_ui/ui.dart' as ui; -import 'package:vision_challenge/main.dart' as app; - -main() async { - await ui.webOnlyInitializePlatform(); - app.main(); -}