From 317d459a589bba458719799c024672f76abf0ad0 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Tue, 10 Sep 2019 09:49:58 -0700 Subject: [PATCH] Update web/ samples to work with Flutter SDK (#134) * add package:http dependency in dad_jokes * add package:http dependency in filipino_cuisine * don't build package:http demos until flutter/flutter#34858 is resolved * update gallery * update github_dataviz * update particle_background * don't build github_dataviz (uses package:http) * update slide_puzzle * update spinning_square * update timeflow * update vision_challenge * update charts * update dad_jokes * update filipino cuisine * ignore build output * update timeflow and vision_challenge * update slide_puzzle * don't commit build/ directory * move preview.png images to assets * fix path url join * update readme * update web/readme.md --- web/.gitignore | 1 + web/_tool/peanut_post_build.dart | 2 +- web/charts/{example => }/README.md | 0 .../{example/web => assets}/preview.png | Bin web/charts/common/.gitignore | 1 - web/charts/common/CHANGELOG.md | 48 - web/charts/common/LICENSE | 202 --- web/charts/common/README.md | 5 - web/charts/common/analysis_options.yaml | 32 - web/charts/common/lib/common.dart | 240 --- .../common/lib/src/chart/bar/bar_chart.dart | 42 - .../src/chart/bar/bar_label_decorator.dart | 234 --- .../lib/src/chart/bar/bar_lane_renderer.dart | 365 ---- .../chart/bar/bar_lane_renderer_config.dart | 103 -- .../lib/src/chart/bar/bar_renderer.dart | 556 ------ .../src/chart/bar/bar_renderer_config.dart | 99 -- .../src/chart/bar/bar_renderer_decorator.dart | 34 - .../chart/bar/bar_target_line_renderer.dart | 422 ----- .../bar/bar_target_line_renderer_config.dart | 91 - .../lib/src/chart/bar/base_bar_renderer.dart | 797 --------- .../chart/bar/base_bar_renderer_config.dart | 151 -- .../chart/bar/base_bar_renderer_element.dart | 128 -- .../lib/src/chart/cartesian/axis/axis.dart | 572 ------ .../src/chart/cartesian/axis/axis_tick.dart | 112 -- .../cartesian/axis/collision_report.dart | 38 - .../base_tick_draw_strategy.dart | 436 ----- .../draw_strategy/gridline_draw_strategy.dart | 174 -- .../draw_strategy/none_draw_strategy.dart | 136 -- .../small_tick_draw_strategy.dart | 168 -- .../draw_strategy/tick_draw_strategy.dart | 59 - .../axis/end_points_tick_provider.dart | 111 -- .../axis/linear/bucketing_numeric_axis.dart | 75 - .../bucketing_numeric_tick_provider.dart | 151 -- .../cartesian/axis/linear/linear_scale.dart | 246 --- .../axis/linear/linear_scale_domain_info.dart | 118 -- .../axis/linear/linear_scale_function.dart | 201 --- .../axis/linear/linear_scale_viewport.dart | 141 -- .../chart/cartesian/axis/numeric_extents.dart | 105 -- .../chart/cartesian/axis/numeric_scale.dart | 57 - .../cartesian/axis/numeric_tick_provider.dart | 584 ------ .../chart/cartesian/axis/ordinal_extents.dart | 44 - .../chart/cartesian/axis/ordinal_scale.dart | 40 - .../axis/ordinal_scale_domain_info.dart | 77 - .../cartesian/axis/ordinal_tick_provider.dart | 58 - .../lib/src/chart/cartesian/axis/scale.dart | 313 ---- .../cartesian/axis/simple_ordinal_scale.dart | 344 ---- .../chart/cartesian/axis/spec/axis_spec.dart | 181 -- .../axis/spec/bucketing_axis_spec.dart | 170 -- .../axis/spec/date_time_axis_spec.dart | 327 ---- .../axis/spec/end_points_time_axis_spec.dart | 65 - .../axis/spec/numeric_axis_spec.dart | 253 --- .../axis/spec/ordinal_axis_spec.dart | 139 -- .../axis/spec/percent_axis_spec.dart | 54 - .../chart/cartesian/axis/spec/tick_spec.dart | 32 - .../cartesian/axis/static_tick_provider.dart | 106 -- .../lib/src/chart/cartesian/axis/tick.dart | 47 - .../chart/cartesian/axis/tick_formatter.dart | 107 -- .../chart/cartesian/axis/tick_provider.dart | 103 -- ...uto_adjusting_date_time_tick_provider.dart | 176 -- .../axis/time/base_time_stepper.dart | 141 -- .../cartesian/axis/time/date_time_axis.dart | 40 - .../cartesian/axis/time/date_time_scale.dart | 138 -- .../axis/time/date_time_tick_formatter.dart | 218 --- .../cartesian/axis/time/day_time_stepper.dart | 81 - .../axis/time/hour_tick_formatter.dart | 45 - .../axis/time/hour_time_stepper.dart | 87 - .../axis/time/minute_time_stepper.dart | 77 - .../axis/time/month_time_stepper.dart | 76 - .../axis/time/time_range_tick_provider.dart | 29 - .../time/time_range_tick_provider_impl.dart | 129 -- .../cartesian/axis/time/time_stepper.dart | 60 - .../axis/time/time_tick_formatter.dart | 31 - .../axis/time/time_tick_formatter_impl.dart | 100 -- .../axis/time/year_time_stepper.dart | 62 - .../src/chart/cartesian/cartesian_chart.dart | 466 ----- .../chart/cartesian/cartesian_renderer.dart | 264 --- .../lib/src/chart/common/base_chart.dart | 707 -------- .../behavior/a11y/a11y_explore_behavior.dart | 97 - .../chart/common/behavior/a11y/a11y_node.dart | 32 - .../a11y/domain_a11y_explore_behavior.dart | 195 -- .../calculation/percent_injector.dart | 235 --- .../chart/common/behavior/chart_behavior.dart | 66 - .../behavior/chart_title/chart_title.dart | 828 --------- .../common/behavior/domain_highlighter.dart | 83 - .../common/behavior/initial_selection.dart | 74 - .../common/behavior/legend/datum_legend.dart | 104 -- .../chart/common/behavior/legend/legend.dart | 377 ---- .../common/behavior/legend/legend_entry.dart | 85 - .../legend/legend_entry_generator.dart | 68 - .../per_datum_legend_entry_generator.dart | 145 -- .../per_series_legend_entry_generator.dart | 190 -- .../common/behavior/legend/series_legend.dart | 171 -- .../behavior/line_point_highlighter.dart | 689 ------- .../common/behavior/range_annotation.dart | 1309 -------------- .../behavior/selection/lock_selection.dart | 126 -- .../behavior/selection/select_nearest.dart | 300 ---- .../behavior/selection/selection_trigger.dart | 22 - .../chart/common/behavior/slider/slider.dart | 803 --------- .../common/behavior/sliding_viewport.dart | 75 - .../behavior/zoom/initial_hint_behavior.dart | 264 --- .../behavior/zoom/pan_and_zoom_behavior.dart | 119 -- .../common/behavior/zoom/pan_behavior.dart | 221 --- .../behavior/zoom/panning_tick_provider.dart | 90 - .../lib/src/chart/common/canvas_shapes.dart | 125 -- .../lib/src/chart/common/chart_canvas.dart | 163 -- .../lib/src/chart/common/chart_context.dart | 61 - .../lib/src/chart/common/datum_details.dart | 222 --- .../src/chart/common/processed_series.dart | 232 --- .../selection_model/selection_model.dart | 231 --- .../lib/src/chart/common/series_datum.dart | 58 - .../lib/src/chart/common/series_renderer.dart | 387 ---- .../chart/common/series_renderer_config.dart | 40 - .../unitconverter/identity_converter.dart | 27 - .../common/unitconverter/unit_converter.dart | 26 - .../lib/src/chart/layout/layout_config.dart | 121 -- .../lib/src/chart/layout/layout_manager.dart | 70 - .../src/chart/layout/layout_manager_impl.dart | 366 ---- .../chart/layout/layout_margin_strategy.dart | 273 --- .../lib/src/chart/layout/layout_view.dart | 207 --- .../common/lib/src/chart/line/line_chart.dart | 42 - .../lib/src/chart/line/line_renderer.dart | 1583 ----------------- .../src/chart/line/line_renderer_config.dart | 92 - .../src/chart/pie/arc_label_decorator.dart | 408 ----- .../lib/src/chart/pie/arc_renderer.dart | 709 -------- .../src/chart/pie/arc_renderer_config.dart | 94 - .../src/chart/pie/arc_renderer_decorator.dart | 37 - .../common/lib/src/chart/pie/pie_chart.dart | 84 - .../comparison_points_decorator.dart | 239 --- .../chart/scatter_plot/point_renderer.dart | 860 --------- .../scatter_plot/point_renderer_config.dart | 80 - .../point_renderer_decorator.dart | 37 - .../scatter_plot/scatter_plot_chart.dart | 66 - .../symbol_annotation_renderer.dart | 262 --- .../symbol_annotation_renderer_config.dart | 72 - .../chart/time_series/time_series_chart.dart | 65 - web/charts/common/lib/src/common/color.dart | 113 -- .../lib/src/common/date_time_factory.dart | 98 - .../lib/src/common/gesture_listener.dart | 105 -- .../lib/src/common/graphics_factory.dart | 29 - .../common/lib/src/common/line_style.dart | 24 - .../lib/src/common/material_palette.dart | 232 --- web/charts/common/lib/src/common/math.dart | 60 - .../common/lib/src/common/paint_style.dart | 23 - web/charts/common/lib/src/common/palette.dart | 58 - .../common/lib/src/common/performance.dart | 21 - .../src/common/proxy_gesture_listener.dart | 143 -- .../common/lib/src/common/rtl_spec.dart | 47 - .../lib/src/common/style/material_style.dart | 99 -- .../common/lib/src/common/style/style.dart | 90 - .../lib/src/common/style/style_factory.dart | 32 - .../lib/src/common/symbol_renderer.dart | 348 ---- .../common/lib/src/common/text_element.dart | 80 - .../lib/src/common/text_measurement.dart | 32 - .../common/lib/src/common/text_style.dart | 25 - .../common/lib/src/common/typed_registry.dart | 41 - web/charts/common/lib/src/data/series.dart | 222 --- web/charts/common/pubspec.lock | 376 ---- web/charts/common/pubspec.yaml | 20 - .../chart/bar/bar_label_decorator_test.dart | 400 ----- .../test/chart/bar/bar_renderer_test.dart | 881 --------- .../bar/bar_target_line_renderer_test.dart | 653 ------- .../bar/renderer_nearest_detail_test.dart | 1418 --------------- .../test/chart/cartesian/axis/axis_test.dart | 60 - .../chart/cartesian/axis/axis_tick_test.dart | 185 -- .../bucketing_numeric_tick_provider_test.dart | 179 -- .../tick_draw_strategy_test.dart | 408 ----- .../axis/end_points_tick_provider_test.dart | 237 --- .../axis/linear/linear_scale_test.dart | 307 ---- .../axis/numeric_tick_provider_test.dart | 496 ------ .../cartesian/axis/ordinal_scale_test.dart | 249 --- .../axis/static_tick_provider_test.dart | 180 -- .../time/date_time_tick_formatter_test.dart | 254 --- .../axis/time/simple_date_time_factory.dart | 42 - .../axis/time/time_stepper_test.dart | 483 ----- .../axis/time/time_tick_provider_test.dart | 67 - .../chart/cartesian/cartesian_chart_test.dart | 105 -- .../cartesian/cartesian_renderer_test.dart | 295 --- .../domain_a11y_explore_behavior_test.dart | 252 --- .../calculation/percent_injector_test.dart | 593 ------ .../common/behavior/chart_behavior_test.dart | 153 -- .../behavior/domain_highlighter_test.dart | 185 -- .../behavior/initial_selection_test.dart | 212 --- .../behavior/line_point_highlighter_test.dart | 269 --- .../behavior/range_annotation_test.dart | 351 ---- .../selection/lock_selection_test.dart | 160 -- .../selection/select_nearest_test.dart | 491 ----- .../behavior/series_legend_behavior_test.dart | 473 ----- .../common/behavior/slider/slider_test.dart | 611 ------- .../chart/common/gesture_listener_test.dart | 249 --- .../selection_model/selection_model_test.dart | 327 ---- .../layout/layout_manager_impl_test.dart | 48 - .../test/chart/line/line_renderer_test.dart | 640 ------- .../line/renderer_nearest_detail_test.dart | 352 ---- .../chart/pie/arc_label_decorator_test.dart | 323 ---- .../comparison_points_decorator_test.dart | 220 --- .../scatter_plot/point_renderer_test.dart | 191 -- .../symbol_annotation_renderer_test.dart | 109 -- web/charts/example/pubspec.lock | 492 ----- web/charts/example/pubspec.yaml | 24 - .../example/web/assets/FontManifest.json | 10 - web/charts/example/web/main.dart | 10 - web/charts/flutter/CHANGELOG.md | 53 - web/charts/flutter/LICENSE | 202 --- web/charts/flutter/README.md | 14 - web/charts/flutter/lib/flutter.dart | 191 -- web/charts/flutter/lib/src/bar_chart.dart | 104 -- web/charts/flutter/lib/src/base_chart.dart | 279 --- .../flutter/lib/src/base_chart_state.dart | 179 -- .../a11y/domain_a11y_explore_behavior.dart | 112 -- .../calculation/percent_injector.dart | 72 - .../lib/src/behaviors/chart_behavior.dart | 72 - .../behaviors/chart_title/chart_title.dart | 200 --- .../lib/src/behaviors/domain_highlighter.dart | 54 - .../lib/src/behaviors/initial_selection.dart | 68 - .../src/behaviors/legend/datum_legend.dart | 340 ---- .../lib/src/behaviors/legend/legend.dart | 22 - .../legend/legend_content_builder.dart | 92 - .../behaviors/legend/legend_entry_layout.dart | 144 -- .../src/behaviors/legend/legend_layout.dart | 158 -- .../src/behaviors/legend/series_legend.dart | 382 ---- .../src/behaviors/line_point_highlighter.dart | 127 -- .../lib/src/behaviors/range_annotation.dart | 117 -- .../lib/src/behaviors/select_nearest.dart | 147 -- .../lib/src/behaviors/slider/slider.dart | 196 -- .../lib/src/behaviors/sliding_viewport.dart | 53 - .../behaviors/zoom/initial_hint_behavior.dart | 131 -- .../behaviors/zoom/pan_and_zoom_behavior.dart | 64 - .../lib/src/behaviors/zoom/pan_behavior.dart | 186 -- .../lib/src/canvas/circle_sector_painter.dart | 104 -- .../flutter/lib/src/canvas/line_painter.dart | 242 --- .../flutter/lib/src/canvas/pie_painter.dart | 88 - .../flutter/lib/src/canvas/point_painter.dart | 56 - .../lib/src/canvas/polygon_painter.dart | 96 - .../flutter/lib/src/cartesian_chart.dart | 125 -- web/charts/flutter/lib/src/chart_canvas.dart | 442 ----- .../flutter/lib/src/chart_container.dart | 419 ----- .../lib/src/chart_gesture_detector.dart | 136 -- web/charts/flutter/lib/src/chart_state.dart | 36 - .../lib/src/combo_chart/combo_chart.dart | 122 -- .../flutter/lib/src/graphics_factory.dart | 50 - web/charts/flutter/lib/src/line_chart.dart | 90 - web/charts/flutter/lib/src/line_style.dart | 25 - web/charts/flutter/lib/src/pie_chart.dart | 54 - .../flutter/lib/src/scatter_plot_chart.dart | 82 - .../lib/src/selection_model_config.dart | 34 - .../flutter/lib/src/symbol_renderer.dart | 104 -- web/charts/flutter/lib/src/text_element.dart | 183 -- web/charts/flutter/lib/src/text_style.dart | 33 - .../flutter/lib/src/time_series_chart.dart | 94 - .../flutter/lib/src/user_managed_state.dart | 78 - web/charts/flutter/lib/src/util.dart | 45 - web/charts/flutter/lib/src/util/color.dart | 28 - .../lib/src/widget_layout_delegate.dart | 219 --- web/charts/flutter/pubspec.lock | 417 ----- web/charts/flutter/pubspec.yaml | 45 - .../behaviors/legend/legend_layout_test.dart | 116 -- .../flutter/test/text_element_test.dart | 39 - .../flutter/test/user_managed_state_test.dart | 128 -- .../test/widget_layout_delegate_test.dart | 548 ------ .../{example => }/lib/a11y/a11y_gallery.dart | 2 +- .../a11y/domain_a11y_explore_bar_chart.dart | 2 +- web/charts/{example => }/lib/app_config.dart | 2 +- .../{example => }/lib/axes/axes_gallery.dart | 2 +- .../lib/axes/bar_secondary_axis.dart | 2 +- .../lib/axes/bar_secondary_axis_only.dart | 2 +- .../lib/axes/custom_axis_tick_formatters.dart | 2 +- .../lib/axes/custom_font_size_and_color.dart | 2 +- .../lib/axes/custom_measure_tick_count.dart | 2 +- .../lib/axes/flipped_vertical_axis.dart | 2 +- .../lib/axes/gridline_dash_pattern.dart | 2 +- .../axes/hidden_ticks_and_labels_axis.dart | 2 +- .../axes/horizontal_bar_secondary_axis.dart | 2 +- .../lib/axes/integer_only_measure_axis.dart | 2 +- .../lib/axes/line_disjoint_axis.dart | 2 +- .../axes/measure_axis_label_alignment.dart | 2 +- .../lib/axes/nonzero_bound_measure_axis.dart | 2 +- .../lib/axes/numeric_initial_viewport.dart | 2 +- .../lib/axes/ordinal_initial_viewport.dart | 2 +- .../lib/axes/short_tick_length_axis.dart | 2 +- .../lib/axes/statically_provided_ticks.dart | 2 +- .../lib/bar_chart/bar_gallery.dart | 2 +- .../lib/bar_chart/custom_rounded_bars.dart | 2 +- .../{example => }/lib/bar_chart/grouped.dart | 2 +- .../lib/bar_chart/grouped_fill_color.dart | 2 +- .../bar_chart/grouped_single_target_line.dart | 2 +- .../lib/bar_chart/grouped_stacked.dart | 2 +- .../grouped_stacked_weight_pattern.dart | 2 +- .../lib/bar_chart/grouped_target_line.dart | 2 +- .../lib/bar_chart/horizontal.dart | 2 +- .../lib/bar_chart/horizontal_bar_label.dart | 2 +- .../horizontal_bar_label_custom.dart | 2 +- .../horizontal_pattern_forward_hatch.dart | 2 +- .../lib/bar_chart/pattern_forward_hatch.dart | 2 +- .../{example => }/lib/bar_chart/simple.dart | 2 +- .../lib/bar_chart/spark_bar.dart | 2 +- .../{example => }/lib/bar_chart/stacked.dart | 2 +- .../lib/bar_chart/stacked_fill_color.dart | 2 +- .../lib/bar_chart/stacked_horizontal.dart | 2 +- .../lib/bar_chart/stacked_target_line.dart | 2 +- .../lib/behaviors/behaviors_gallery.dart | 2 +- .../lib/behaviors/chart_title.dart | 2 +- .../lib/behaviors/initial_hint_animation.dart | 2 +- .../lib/behaviors/initial_selection.dart | 2 +- .../lib/behaviors/percent_of_domain.dart | 2 +- .../percent_of_domain_by_category.dart | 2 +- .../lib/behaviors/percent_of_series.dart | 2 +- .../behaviors/selection_bar_highlight.dart | 2 +- .../behaviors/selection_callback_example.dart | 2 +- .../behaviors/selection_line_highlight.dart | 2 +- ...selection_line_highlight_custom_shape.dart | 2 +- .../selection_scatter_plot_highlight.dart | 2 +- .../lib/behaviors/selection_user_managed.dart | 2 +- .../{example => }/lib/behaviors/slider.dart | 6 +- .../sliding_viewport_on_selection.dart | 2 +- .../lib/combo_chart/combo_gallery.dart | 2 +- .../lib/combo_chart/date_time_line_point.dart | 2 +- .../lib/combo_chart/numeric_line_bar.dart | 2 +- .../lib/combo_chart/numeric_line_point.dart | 2 +- .../lib/combo_chart/ordinal_bar_line.dart | 2 +- .../lib/combo_chart/scatter_plot_line.dart | 2 +- web/charts/{example => }/lib/drawer.dart | 2 +- .../{example => }/lib/gallery_scaffold.dart | 2 +- web/charts/{example => }/lib/home.dart | 2 +- .../{example => }/lib/i18n/i18n_gallery.dart | 2 +- .../{example => }/lib/i18n/rtl_bar_chart.dart | 2 +- .../lib/i18n/rtl_line_chart.dart | 2 +- .../lib/i18n/rtl_line_segments.dart | 2 +- .../lib/i18n/rtl_series_legend.dart | 2 +- .../lib/legends/datum_legend_options.dart | 2 +- .../legends/datum_legend_with_measures.dart | 2 +- .../legends/default_hidden_series_legend.dart | 2 +- .../lib/legends/legend_custom_symbol.dart | 2 +- .../lib/legends/legends_gallery.dart | 2 +- .../lib/legends/series_legend_options.dart | 2 +- .../legends/series_legend_with_measures.dart | 2 +- .../lib/legends/simple_datum_legend.dart | 2 +- .../lib/legends/simple_series_legend.dart | 2 +- .../lib/line_chart/animation_zoom.dart | 2 +- .../lib/line_chart/area_and_line.dart | 2 +- .../lib/line_chart/dash_pattern.dart | 2 +- .../lib/line_chart/line_annotation.dart | 2 +- .../lib/line_chart/line_gallery.dart | 2 +- .../{example => }/lib/line_chart/points.dart | 2 +- .../lib/line_chart/range_annotation.dart | 2 +- .../line_chart/range_annotation_margin.dart | 2 +- .../lib/line_chart/segments.dart | 2 +- .../{example => }/lib/line_chart/simple.dart | 2 +- .../lib/line_chart/simple_nulls.dart | 2 +- .../lib/line_chart/stacked_area.dart | 2 +- .../line_chart/stacked_area_custom_color.dart | 2 +- .../lib/line_chart/stacked_area_nulls.dart | 2 +- web/charts/{example => }/lib/main.dart | 2 +- .../lib/pie_chart/auto_label.dart | 2 +- .../{example => }/lib/pie_chart/donut.dart | 2 +- .../{example => }/lib/pie_chart/gauge.dart | 2 +- .../lib/pie_chart/outside_label.dart | 2 +- .../lib/pie_chart/partial_pie.dart | 2 +- .../lib/pie_chart/pie_gallery.dart | 2 +- .../{example => }/lib/pie_chart/simple.dart | 2 +- .../scatter_plot_chart/animation_zoom.dart | 2 +- .../scatter_plot_chart/bucketing_axis.dart | 2 +- .../scatter_plot_chart/comparison_points.dart | 2 +- .../scatter_plot_gallery.dart | 2 +- .../lib/scatter_plot_chart/shapes.dart | 2 +- .../lib/scatter_plot_chart/simple.dart | 2 +- .../confidence_interval.dart | 2 +- .../time_series_chart/end_points_axis.dart | 2 +- .../time_series_chart/line_annotation.dart | 2 +- .../time_series_chart/range_annotation.dart | 2 +- .../range_annotation_margin.dart | 2 +- .../lib/time_series_chart/simple.dart | 2 +- .../time_series_chart/symbol_annotation.dart | 2 +- .../time_series_gallery.dart | 2 +- .../time_series_chart/with_bar_renderer.dart | 2 +- web/charts/pubspec.lock | 78 + web/charts/pubspec.yaml | 13 + web/charts/{example => }/web/index.html | 0 .../{web => }/assets/FontManifest.json | 0 .../assets/fonts/PatrickHand-Regular.ttf | Bin web/dad_jokes/{web => }/assets/icon.png | Bin web/dad_jokes/{web => assets}/preview.png | Bin web/dad_jokes/lib/auto_size_text.dart | 2 +- web/dad_jokes/lib/main.dart | 2 +- web/dad_jokes/lib/main_page.dart | 2 +- web/dad_jokes/pubspec.lock | 390 +--- web/dad_jokes/pubspec.yaml | 24 +- web/dad_jokes/web/main.dart | 10 - web/filipino_cuisine/assets/banana.png | Bin 0 -> 206490 bytes web/filipino_cuisine/assets/beef.png | Bin 0 -> 393869 bytes .../{web => }/assets/beef_caldereta.jpg | Bin .../{web => }/assets/black_pepper.png | Bin web/filipino_cuisine/assets/bokchoy.png | Bin 0 -> 197020 bytes web/filipino_cuisine/assets/butter.png | Bin 0 -> 168429 bytes web/filipino_cuisine/assets/cabbage.png | Bin 0 -> 505259 bytes .../{web => }/assets/calamares.jpg | Bin web/filipino_cuisine/assets/carrot.png | Bin 0 -> 207790 bytes web/filipino_cuisine/assets/cheese.png | Bin 0 -> 161320 bytes .../{web => }/assets/chicken_adobo.jpg | Bin web/filipino_cuisine/assets/chili.png | Bin 0 -> 297485 bytes .../{web => }/assets/crispy_pata.jpg | Bin web/filipino_cuisine/assets/egg.png | Bin 0 -> 398120 bytes .../{web => }/assets/embutido.jpg | Bin web/filipino_cuisine/assets/flour.png | Bin 0 -> 231867 bytes web/filipino_cuisine/assets/garlic.png | Bin 0 -> 230031 bytes web/filipino_cuisine/assets/green_beans.png | Bin 0 -> 312116 bytes web/filipino_cuisine/assets/green_bell.png | Bin 0 -> 229484 bytes .../{web => }/assets/grilled_pork_ribs.jpg | Bin .../{web => }/assets/grilled_seafood.jpg | Bin web/filipino_cuisine/assets/ground_pork.png | Bin 0 -> 663574 bytes web/filipino_cuisine/assets/icon/fc_icon.png | Bin 0 -> 15420 bytes web/filipino_cuisine/assets/lemon.png | Bin 0 -> 360042 bytes web/filipino_cuisine/assets/oil.png | Bin 0 -> 179080 bytes web/filipino_cuisine/assets/onion.png | Bin 0 -> 273597 bytes .../{web => }/assets/pancit_canton.jpg | Bin .../{web => }/assets/pochero.jpg | Bin web/filipino_cuisine/assets/pork.png | Bin 0 -> 434280 bytes .../{web => }/assets/pork_sisig.jpg | Bin web/filipino_cuisine/assets/potato.png | Bin 0 -> 287753 bytes .../{web => assets}/preview.png | Bin web/filipino_cuisine/assets/raisins.png | Bin 0 -> 396519 bytes web/filipino_cuisine/assets/red_bell.png | Bin 0 -> 286992 bytes web/filipino_cuisine/assets/red_pepper.png | Bin 0 -> 409056 bytes web/filipino_cuisine/assets/salt.png | Bin 0 -> 174180 bytes web/filipino_cuisine/assets/sausage.png | Bin 0 -> 139946 bytes .../{web => }/assets/smoked_salmon.jpg | Bin web/filipino_cuisine/assets/squid.png | Bin 0 -> 764489 bytes .../assets/sweet_and_sour_chicken_poultry.jpg | Bin web/filipino_cuisine/assets/tomato.png | Bin 0 -> 373015 bytes web/filipino_cuisine/assets/yellow_onion.png | Bin 0 -> 356811 bytes .../{web/assets => }/fonts/Arkipelago.otf | Bin .../{web/assets => }/fonts/OpenSans-Bold.ttf | Bin .../assets => }/fonts/OpenSans-Regular.ttf | Bin web/filipino_cuisine/lib/cook.dart | 2 +- .../lib/flutter_page_indicator.dart | 4 +- web/filipino_cuisine/lib/flutter_swiper.dart | 4 +- web/filipino_cuisine/lib/main.dart | 2 +- .../lib/transformer_page_view.dart | 4 +- web/filipino_cuisine/pubspec.lock | 390 +--- web/filipino_cuisine/pubspec.yaml | 71 +- .../web/assets/FontManifest.json | 34 - web/filipino_cuisine/web/assets/banana.png | Bin 64676 -> 0 bytes web/filipino_cuisine/web/assets/beef.png | Bin 138901 -> 0 bytes web/filipino_cuisine/web/assets/bokchoy.png | Bin 63656 -> 0 bytes web/filipino_cuisine/web/assets/butter.png | Bin 58172 -> 0 bytes web/filipino_cuisine/web/assets/cabbage.png | Bin 185128 -> 0 bytes web/filipino_cuisine/web/assets/carrot.png | Bin 83715 -> 0 bytes web/filipino_cuisine/web/assets/cheese.png | Bin 63138 -> 0 bytes web/filipino_cuisine/web/assets/chili.png | Bin 100472 -> 0 bytes web/filipino_cuisine/web/assets/egg.png | Bin 151308 -> 0 bytes web/filipino_cuisine/web/assets/flour.png | Bin 103927 -> 0 bytes web/filipino_cuisine/web/assets/garlic.png | Bin 92587 -> 0 bytes .../web/assets/green_beans.png | Bin 101267 -> 0 bytes .../web/assets/green_bell.png | Bin 82121 -> 0 bytes .../web/assets/ground_pork.png | Bin 245793 -> 0 bytes web/filipino_cuisine/web/assets/lemon.png | Bin 119101 -> 0 bytes web/filipino_cuisine/web/assets/oil.png | Bin 46835 -> 0 bytes web/filipino_cuisine/web/assets/onion.png | Bin 78198 -> 0 bytes web/filipino_cuisine/web/assets/pork.png | Bin 169863 -> 0 bytes web/filipino_cuisine/web/assets/potato.png | Bin 101708 -> 0 bytes web/filipino_cuisine/web/assets/raisins.png | Bin 124963 -> 0 bytes web/filipino_cuisine/web/assets/recipes.json | 366 ---- web/filipino_cuisine/web/assets/red_bell.png | Bin 88967 -> 0 bytes .../web/assets/red_pepper.png | Bin 116301 -> 0 bytes web/filipino_cuisine/web/assets/salt.png | Bin 91128 -> 0 bytes web/filipino_cuisine/web/assets/sausage.png | Bin 50461 -> 0 bytes web/filipino_cuisine/web/assets/squid.png | Bin 431200 -> 0 bytes web/filipino_cuisine/web/assets/tomato.png | Bin 109956 -> 0 bytes .../web/assets/yellow_onion.png | Bin 110093 -> 0 bytes web/filipino_cuisine/web/main.dart | 10 - web/gallery/.gitignore | 73 + web/gallery/.metadata | 10 + web/gallery/README.md | 4 +- web/gallery/{web => assets}/preview.png | Bin web/gallery/build.yaml | 7 - web/gallery/lib/demo/all.dart | 12 +- web/gallery/lib/demo/animation/home.dart | 268 ++- web/gallery/lib/demo/animation/sections.dart | 13 +- web/gallery/lib/demo/animation/widgets.dart | 45 +- web/gallery/lib/demo/animation_demo.dart | 4 +- web/gallery/lib/demo/calculator/home.dart | 270 +++ web/gallery/lib/demo/calculator/logic.dart | 342 ++++ web/gallery/lib/demo/calculator_demo.dart | 16 + web/gallery/lib/demo/colors_demo.dart | 171 +- web/gallery/lib/demo/contacts_demo.dart | 127 +- web/gallery/lib/demo/cupertino/cupertino.dart | 14 + .../cupertino_activity_indicator_demo.dart | 28 + .../demo/cupertino/cupertino_alert_demo.dart | 275 +++ .../cupertino/cupertino_buttons_demo.dart | 89 + .../cupertino/cupertino_navigation_demo.dart | 804 +++++++++ .../demo/cupertino/cupertino_picker_demo.dart | 275 +++ .../cupertino/cupertino_refresh_demo.dart | 244 +++ .../cupertino_segmented_control_demo.dart | 127 ++ .../demo/cupertino/cupertino_slider_demo.dart | 78 + .../demo/cupertino/cupertino_switch_demo.dart | 91 + .../cupertino/cupertino_text_field_demo.dart | 194 ++ web/gallery/lib/demo/fortnightly/README.md | 52 + .../lib/demo/fortnightly/fortnightly.dart | 236 +++ web/gallery/lib/demo/images_demo.dart | 40 + .../lib/demo/material/backdrop_demo.dart | 100 +- .../lib/demo/material/banner_demo.dart | 109 ++ .../demo/material/bottom_app_bar_demo.dart | 251 ++- .../demo/material/bottom_navigation_demo.dart | 74 +- .../lib/demo/material/buttons_demo.dart | 386 ++++ web/gallery/lib/demo/material/cards_demo.dart | 456 +++-- web/gallery/lib/demo/material/chip_demo.dart | 376 +++- .../lib/demo/material/data_table_demo.dart | 265 +-- .../material/date_and_time_picker_demo.dart | 89 +- .../lib/demo/material/dialog_demo.dart | 269 +-- .../lib/demo/material/drawer_demo.dart | 96 +- .../lib/demo/material/editable_text_demo.dart | 92 - .../lib/demo/material/elevation_demo.dart | 12 +- .../demo/material/expansion_panels_demo.dart | 375 ++-- .../material/expansion_tile_list_demo.dart | 40 + .../material/full_screen_dialog_demo.dart | 366 ++-- .../lib/demo/material/grid_list_demo.dart | 132 +- web/gallery/lib/demo/material/icons_demo.dart | 61 +- .../lib/demo/material/leave_behind_demo.dart | 212 ++- web/gallery/lib/demo/material/list_demo.dart | 84 +- web/gallery/lib/demo/material/material.dart | 9 +- .../demo/material/material_button_demo.dart | 103 -- web/gallery/lib/demo/material/menu_demo.dart | 248 +-- .../material/modal_bottom_sheet_demo.dart | 50 +- .../lib/demo/material/overscroll_demo.dart | 83 +- .../lib/demo/material/page_selector_demo.dart | 60 +- .../persistent_bottom_sheet_demo.dart | 104 +- .../material/progress_indicator_demo.dart | 53 +- .../demo/material/reorderable_list_demo.dart | 68 +- .../demo/material/scrollable_tabs_demo.dart | 140 +- .../lib/demo/material/search_demo.dart | 19 +- .../material/selection_controls_demo.dart | 178 +- .../lib/demo/material/slider_demo.dart | 311 +++- .../lib/demo/material/snack_bar_demo.dart | 100 +- web/gallery/lib/demo/material/stack_demo.dart | 23 - .../lib/demo/material/switch_demo.dart | 42 - web/gallery/lib/demo/material/tabs_demo.dart | 65 +- .../lib/demo/material/tabs_fab_demo.dart | 98 +- web/gallery/lib/demo/material/text_demo.dart | 52 - .../demo/material/text_form_field_demo.dart | 297 ++-- .../lib/demo/material/tooltip_demo.dart | 88 +- .../demo/material/two_level_list_demo.dart | 34 - web/gallery/lib/demo/pesto_demo.dart | 351 ++-- web/gallery/lib/demo/shrine/app.dart | 138 ++ web/gallery/lib/demo/shrine/backdrop.dart | 330 ++++ .../lib/demo/shrine/category_menu_page.dart | 85 + .../lib/demo/shrine/colors.dart} | 27 +- .../demo/shrine/expanding_bottom_sheet.dart | 656 +++++++ web/gallery/lib/demo/shrine/home.dart | 57 + web/gallery/lib/demo/shrine/login.dart | 144 ++ .../demo/shrine/model/app_state_model.dart | 123 ++ .../lib/demo/shrine/model/product.dart | 48 + .../shrine/model/products_repository.dart | 293 +++ .../lib/demo/shrine/shopping_cart.dart | 275 +++ web/gallery/lib/demo/shrine/shrine_data.dart | 254 --- web/gallery/lib/demo/shrine/shrine_home.dart | 434 ----- web/gallery/lib/demo/shrine/shrine_order.dart | 353 ---- web/gallery/lib/demo/shrine/shrine_page.dart | 137 -- web/gallery/lib/demo/shrine/shrine_theme.dart | 76 - web/gallery/lib/demo/shrine/shrine_types.dart | 100 -- .../shrine/supplemental/asymmetric_view.dart | 95 + .../supplemental/cut_corners_border.dart | 134 ++ .../shrine/supplemental/product_card.dart | 97 + .../shrine/supplemental/product_columns.dart | 89 + web/gallery/lib/demo/shrine_demo.dart | 40 +- .../transformations/transformations_demo.dart | 190 ++ .../transformations_demo_board.dart | 286 +++ .../transformations_demo_color_picker.dart | 70 + ...transformations_demo_edit_board_point.dart | 36 + ...formations_demo_gesture_transformable.dart | 571 ++++++ .../transformations_demo_inertial_motion.dart | 68 + web/gallery/lib/demo/typography_demo.dart | 78 +- web/gallery/lib/demo/video_demo.dart | 434 +++++ web/gallery/lib/gallery/about.dart | 49 +- web/gallery/lib/gallery/app.dart | 81 +- web/gallery/lib/gallery/backdrop.dart | 58 +- web/gallery/lib/gallery/demo.dart | 272 +-- web/gallery/lib/gallery/demos.dart | 444 +++-- web/gallery/lib/gallery/example_code.dart | 286 +++ .../lib/gallery/example_code_parser.dart | 55 + web/gallery/lib/gallery/home.dart | 124 +- web/gallery/lib/gallery/icons.dart | 71 +- web/gallery/lib/gallery/options.dart | 182 +- web/gallery/lib/gallery/scales.dart | 9 +- .../lib/gallery/syntax_highlighter.dart | 356 ++++ web/gallery/lib/gallery/themes.dart | 33 +- web/gallery/lib/gallery/updater.dart | 75 + web/gallery/lib/main.dart | 9 +- web/gallery/lib/main_houdini.dart | 12 - web/gallery/lib/main_publish.dart | 13 + web/gallery/pubspec.lock | 395 ++-- web/gallery/pubspec.yaml | 277 ++- .../material/text_form_field_demo_test.dart | 6 +- web/gallery/test/gallery_test.dart | 5 +- .../web/assets/AbrilFatface-Regular.ttf | Bin 67364 -> 0 bytes web/gallery/web/assets/FontManifest.json | 58 - web/gallery/web/assets/GalleryIcons.ttf | Bin 6108 -> 0 bytes web/gallery/web/assets/GoogleSans-Regular.ttf | Bin 157784 -> 0 bytes .../web/assets/LibreFranklin-Regular.ttf | Bin 78768 -> 0 bytes .../web/assets/Merriweather-Regular.ttf | Bin 156096 -> 0 bytes web/gallery/web/assets/README.md | 34 - web/gallery/web/assets/Raleway-Regular.ttf | Bin 178520 -> 0 bytes .../web/assets/food/butternut_squash_soup.png | Bin 66414 -> 0 bytes web/gallery/web/assets/food/cherry_pie.png | Bin 66719 -> 0 bytes .../web/assets/food/chopped_beet_leaves.png | Bin 79052 -> 0 bytes web/gallery/web/assets/food/icons/fish.png | Bin 738 -> 0 bytes web/gallery/web/assets/food/icons/healthy.png | Bin 679 -> 0 bytes web/gallery/web/assets/food/icons/main.png | Bin 617 -> 0 bytes web/gallery/web/assets/food/icons/meat.png | Bin 729 -> 0 bytes web/gallery/web/assets/food/icons/quick.png | Bin 725 -> 0 bytes web/gallery/web/assets/food/icons/spicy.png | Bin 620 -> 0 bytes web/gallery/web/assets/food/icons/veggie.png | Bin 667 -> 0 bytes web/gallery/web/assets/food/pesto_pasta.png | Bin 42806 -> 0 bytes .../web/assets/food/roasted_chicken.png | Bin 78033 -> 0 bytes web/gallery/web/assets/food/spanakopita.png | Bin 72394 -> 0 bytes .../web/assets/food/spinach_onion_salad.png | Bin 74405 -> 0 bytes .../assets/logos/flutter_white/1.5x/logo.png | Bin 503 -> 0 bytes .../assets/logos/flutter_white/2.5x/logo.png | Bin 545 -> 0 bytes .../assets/logos/flutter_white/3.0x/logo.png | Bin 681 -> 0 bytes .../assets/logos/flutter_white/4.0x/logo.png | Bin 836 -> 0 bytes .../web/assets/logos/flutter_white/logo.png | Bin 423 -> 0 bytes .../web/assets/logos/pesto/logo_small.png | Bin 2059 -> 0 bytes .../web/assets/people/ali_landscape.png | Bin 423497 -> 0 bytes web/gallery/web/assets/people/square/ali.png | Bin 54615 -> 0 bytes .../web/assets/people/square/peter.png | Bin 59614 -> 0 bytes .../web/assets/people/square/sandra.png | Bin 65384 -> 0 bytes .../web/assets/people/square/stella.png | Bin 49879 -> 0 bytes .../web/assets/people/square/trevor.png | Bin 59494 -> 0 bytes .../places/india_chennai_flower_market.png | Bin 367559 -> 0 bytes .../assets/places/india_chennai_highway.png | Bin 319932 -> 0 bytes .../assets/places/india_chettinad_produce.png | Bin 264312 -> 0 bytes .../places/india_chettinad_silk_maker.png | Bin 330268 -> 0 bytes .../assets/places/india_pondicherry_beach.png | Bin 274624 -> 0 bytes .../places/india_pondicherry_fisherman.png | Bin 259937 -> 0 bytes .../places/india_pondicherry_salt_farm.png | Bin 381993 -> 0 bytes .../places/india_tanjore_bronze_works.png | Bin 407527 -> 0 bytes .../places/india_tanjore_market_merchant.png | Bin 388810 -> 0 bytes .../india_tanjore_market_technology.png | Bin 272415 -> 0 bytes .../places/india_tanjore_thanjavur_temple.png | Bin 419650 -> 0 bytes ...ndia_tanjore_thanjavur_temple_carvings.png | Bin 328556 -> 0 bytes .../assets/places/india_thanjavur_market.png | Bin 100670 -> 0 bytes web/gallery/web/assets/products/backpack.png | Bin 229715 -> 0 bytes web/gallery/web/assets/products/belt.png | Bin 160334 -> 0 bytes web/gallery/web/assets/products/cup.png | Bin 80705 -> 0 bytes web/gallery/web/assets/products/deskset.png | Bin 178907 -> 0 bytes web/gallery/web/assets/products/dress.png | Bin 132341 -> 0 bytes web/gallery/web/assets/products/earrings.png | Bin 202662 -> 0 bytes web/gallery/web/assets/products/flatwear.png | Bin 87162 -> 0 bytes web/gallery/web/assets/products/hat.png | Bin 132500 -> 0 bytes web/gallery/web/assets/products/jacket.png | Bin 136354 -> 0 bytes web/gallery/web/assets/products/jumper.png | Bin 122470 -> 0 bytes .../web/assets/products/kitchen_quattro.png | Bin 201701 -> 0 bytes web/gallery/web/assets/products/napkins.png | Bin 185728 -> 0 bytes web/gallery/web/assets/products/planters.png | Bin 151271 -> 0 bytes web/gallery/web/assets/products/platter.png | Bin 184360 -> 0 bytes web/gallery/web/assets/products/scarf.png | Bin 161861 -> 0 bytes web/gallery/web/assets/products/shirt.png | Bin 126532 -> 0 bytes web/gallery/web/assets/products/sunnies.png | Bin 62554 -> 0 bytes web/gallery/web/assets/products/sweater.png | Bin 128041 -> 0 bytes web/gallery/web/assets/products/sweats.png | Bin 157713 -> 0 bytes web/gallery/web/assets/products/table.png | Bin 56400 -> 0 bytes web/gallery/web/assets/products/teaset.png | Bin 149999 -> 0 bytes web/gallery/web/assets/products/top.png | Bin 143844 -> 0 bytes web/gallery/web/frame.html | 19 - web/gallery/web/index.html | 33 +- web/gallery/web/main.dart | 10 - .../{web => assets}/preview.png | Bin web/github_dataviz/lib/constants.dart | 4 +- web/github_dataviz/lib/layered_chart.dart | 6 +- web/github_dataviz/lib/main.dart | 20 +- web/github_dataviz/lib/timeline.dart | 2 +- web/github_dataviz/pubspec.lock | 387 +--- web/github_dataviz/pubspec.yaml | 24 +- web/github_dataviz/web/main.dart | 10 - .../{web => assets}/preview.png | Bin web/particle_background/lib/main.dart | 2 +- .../lib/simple_animations_package.dart | 4 +- web/particle_background/pubspec.lock | 451 +---- web/particle_background/pubspec.yaml | 23 +- web/particle_background/web/main.dart | 10 - web/peanut.yaml | 24 +- web/readme.md | 26 +- web/slide_puzzle/analysis_options.yaml | 93 - web/slide_puzzle/asset/fonts/plaster/OFL.txt | 94 + .../asset/fonts/plaster/Plaster-Regular.ttf | Bin 0 -> 32852 bytes web/slide_puzzle/asset/seattle.jpg | Bin 0 -> 743057 bytes web/slide_puzzle/{web => assets}/preview.png | Bin web/slide_puzzle/lib/main.dart | 8 +- web/slide_puzzle/lib/src/app_state.dart | 30 +- web/slide_puzzle/lib/src/core/body.dart | 4 - web/slide_puzzle/lib/src/core/point_int.dart | 4 - web/slide_puzzle/lib/src/core/puzzle.dart | 11 +- .../lib/src/core/puzzle_animator.dart | 38 +- .../lib/src/core/puzzle_proxy.dart | 25 - .../lib/src/core/puzzle_simple.dart | 4 - .../lib/src/core/puzzle_smart.dart | 4 - web/slide_puzzle/lib/src/core/util.dart | 4 - web/slide_puzzle/lib/src/flutter.dart | 7 - web/slide_puzzle/lib/src/frame_nanny.dart | 63 + web/slide_puzzle/lib/src/puzzle_controls.dart | 17 - .../lib/src/puzzle_flow_delegate.dart | 8 +- .../lib/src/puzzle_home_state.dart | 272 +-- web/slide_puzzle/lib/src/shared_theme.dart | 162 +- web/slide_puzzle/lib/src/theme_plaster.dart | 25 +- web/slide_puzzle/lib/src/theme_seattle.dart | 24 +- web/slide_puzzle/lib/src/theme_simple.dart | 18 +- web/slide_puzzle/lib/src/themes.dart | 9 - .../lib/src/value_tab_controller.dart | 88 - .../src/widgets/decoration_image_plus.dart | 30 +- .../src/widgets/material_interior_alt.dart | 6 +- web/slide_puzzle/pubspec.lock | 300 ++-- web/slide_puzzle/pubspec.yaml | 38 +- web/slide_puzzle/web/assets/FontManifest.json | 18 - web/slide_puzzle/web/assets/readme.md | 15 - web/slide_puzzle/web/assets/seattle.jpg | Bin 390673 -> 0 bytes web/slide_puzzle/web/index.html | 7 +- web/slide_puzzle/web/main.dart | 11 - .../{web => assets}/preview.png | Bin web/spinning_square/lib/main.dart | 2 +- web/spinning_square/pubspec.lock | 451 +---- web/spinning_square/pubspec.yaml | 22 +- web/spinning_square/web/main.dart | 10 - web/timeflow/{web => assets}/preview.png | Bin web/timeflow/lib/infinite_listview.dart | 4 +- web/timeflow/lib/main.dart | 4 +- web/timeflow/lib/numberpicker.dart | 6 +- web/timeflow/pubspec.lock | 451 +---- web/timeflow/pubspec.yaml | 23 +- web/timeflow/web/assets/FontManifest.json | 10 - web/timeflow/web/main.dart | 10 - web/vision_challenge/{web => }/assets/10.png | Bin web/vision_challenge/{web => }/assets/20.png | Bin web/vision_challenge/{web => }/assets/30.png | Bin web/vision_challenge/{web => }/assets/35.png | Bin web/vision_challenge/{web => }/assets/40.png | Bin web/vision_challenge/{web => }/assets/45.png | Bin web/vision_challenge/{web => }/assets/99.png | Bin .../{web => }/assets/FontManifest.json | 0 web/vision_challenge/{web => }/assets/p0.jpg | Bin web/vision_challenge/{web => }/assets/p1.jpg | Bin .../{web => assets}/preview.png | Bin web/vision_challenge/lib/game.dart | 6 +- web/vision_challenge/lib/main.dart | 6 +- .../lib/packages/flutter_redux.dart | 513 ------ web/vision_challenge/lib/packages/redux.dart | 519 ------ web/vision_challenge/pubspec.lock | 453 +---- web/vision_challenge/pubspec.yaml | 33 +- web/vision_challenge/web/main.dart | 10 - 746 files changed, 14573 insertions(+), 61576 deletions(-) rename web/charts/{example => }/README.md (100%) rename web/charts/{example/web => assets}/preview.png (100%) delete mode 100644 web/charts/common/.gitignore delete mode 100644 web/charts/common/CHANGELOG.md delete mode 100644 web/charts/common/LICENSE delete mode 100644 web/charts/common/README.md delete mode 100644 web/charts/common/analysis_options.yaml delete mode 100644 web/charts/common/lib/common.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_chart.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_label_decorator.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_lane_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_lane_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_renderer_decorator.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_target_line_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/bar/bar_target_line_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/bar/base_bar_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/bar/base_bar_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/bar/base_bar_renderer_element.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/axis.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/axis_tick.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/collision_report.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/end_points_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_axis.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_domain_info.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_function.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/linear/linear_scale_viewport.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/numeric_extents.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/numeric_scale.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/numeric_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/ordinal_extents.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/ordinal_scale_domain_info.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/ordinal_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/scale.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/simple_ordinal_scale.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/bucketing_axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/date_time_axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/numeric_axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/percent_axis_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/spec/tick_spec.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/static_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/tick.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/tick_formatter.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/base_time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/date_time_axis.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/date_time_scale.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/date_time_tick_formatter.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/day_time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/hour_tick_formatter.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/hour_time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/minute_time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/month_time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/time_range_tick_provider_impl.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/time_tick_formatter_impl.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/axis/time/year_time_stepper.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/cartesian_chart.dart delete mode 100644 web/charts/common/lib/src/chart/cartesian/cartesian_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/common/base_chart.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/a11y/a11y_explore_behavior.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/a11y/a11y_node.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/calculation/percent_injector.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/chart_behavior.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/chart_title/chart_title.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/domain_highlighter.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/initial_selection.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/datum_legend.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/legend.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/legend_entry.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/legend_entry_generator.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/per_datum_legend_entry_generator.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/per_series_legend_entry_generator.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/legend/series_legend.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/line_point_highlighter.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/range_annotation.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/selection/lock_selection.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/selection/select_nearest.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/selection/selection_trigger.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/slider/slider.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/sliding_viewport.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/zoom/initial_hint_behavior.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/zoom/pan_behavior.dart delete mode 100644 web/charts/common/lib/src/chart/common/behavior/zoom/panning_tick_provider.dart delete mode 100644 web/charts/common/lib/src/chart/common/canvas_shapes.dart delete mode 100644 web/charts/common/lib/src/chart/common/chart_canvas.dart delete mode 100644 web/charts/common/lib/src/chart/common/chart_context.dart delete mode 100644 web/charts/common/lib/src/chart/common/datum_details.dart delete mode 100644 web/charts/common/lib/src/chart/common/processed_series.dart delete mode 100644 web/charts/common/lib/src/chart/common/selection_model/selection_model.dart delete mode 100644 web/charts/common/lib/src/chart/common/series_datum.dart delete mode 100644 web/charts/common/lib/src/chart/common/series_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/common/series_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/common/unitconverter/identity_converter.dart delete mode 100644 web/charts/common/lib/src/chart/common/unitconverter/unit_converter.dart delete mode 100644 web/charts/common/lib/src/chart/layout/layout_config.dart delete mode 100644 web/charts/common/lib/src/chart/layout/layout_manager.dart delete mode 100644 web/charts/common/lib/src/chart/layout/layout_manager_impl.dart delete mode 100644 web/charts/common/lib/src/chart/layout/layout_margin_strategy.dart delete mode 100644 web/charts/common/lib/src/chart/layout/layout_view.dart delete mode 100644 web/charts/common/lib/src/chart/line/line_chart.dart delete mode 100644 web/charts/common/lib/src/chart/line/line_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/line/line_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/pie/arc_label_decorator.dart delete mode 100644 web/charts/common/lib/src/chart/pie/arc_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/pie/arc_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/pie/arc_renderer_decorator.dart delete mode 100644 web/charts/common/lib/src/chart/pie/pie_chart.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/comparison_points_decorator.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/point_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/point_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/point_renderer_decorator.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/scatter_plot_chart.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer.dart delete mode 100644 web/charts/common/lib/src/chart/scatter_plot/symbol_annotation_renderer_config.dart delete mode 100644 web/charts/common/lib/src/chart/time_series/time_series_chart.dart delete mode 100644 web/charts/common/lib/src/common/color.dart delete mode 100644 web/charts/common/lib/src/common/date_time_factory.dart delete mode 100644 web/charts/common/lib/src/common/gesture_listener.dart delete mode 100644 web/charts/common/lib/src/common/graphics_factory.dart delete mode 100644 web/charts/common/lib/src/common/line_style.dart delete mode 100644 web/charts/common/lib/src/common/material_palette.dart delete mode 100644 web/charts/common/lib/src/common/math.dart delete mode 100644 web/charts/common/lib/src/common/paint_style.dart delete mode 100644 web/charts/common/lib/src/common/palette.dart delete mode 100644 web/charts/common/lib/src/common/performance.dart delete mode 100644 web/charts/common/lib/src/common/proxy_gesture_listener.dart delete mode 100644 web/charts/common/lib/src/common/rtl_spec.dart delete mode 100644 web/charts/common/lib/src/common/style/material_style.dart delete mode 100644 web/charts/common/lib/src/common/style/style.dart delete mode 100644 web/charts/common/lib/src/common/style/style_factory.dart delete mode 100644 web/charts/common/lib/src/common/symbol_renderer.dart delete mode 100644 web/charts/common/lib/src/common/text_element.dart delete mode 100644 web/charts/common/lib/src/common/text_measurement.dart delete mode 100644 web/charts/common/lib/src/common/text_style.dart delete mode 100644 web/charts/common/lib/src/common/typed_registry.dart delete mode 100644 web/charts/common/lib/src/data/series.dart delete mode 100644 web/charts/common/pubspec.lock delete mode 100644 web/charts/common/pubspec.yaml delete mode 100644 web/charts/common/test/chart/bar/bar_label_decorator_test.dart delete mode 100644 web/charts/common/test/chart/bar/bar_renderer_test.dart delete mode 100644 web/charts/common/test/chart/bar/bar_target_line_renderer_test.dart delete mode 100644 web/charts/common/test/chart/bar/renderer_nearest_detail_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/axis_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/axis_tick_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/bucketing_numeric_tick_provider_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/draw_strategy/tick_draw_strategy_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/end_points_tick_provider_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/linear/linear_scale_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/numeric_tick_provider_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/ordinal_scale_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/static_tick_provider_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/time/date_time_tick_formatter_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/time/simple_date_time_factory.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/time/time_stepper_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/axis/time/time_tick_provider_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/cartesian_chart_test.dart delete mode 100644 web/charts/common/test/chart/cartesian/cartesian_renderer_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/a11y/domain_a11y_explore_behavior_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/calculation/percent_injector_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/chart_behavior_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/domain_highlighter_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/initial_selection_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/line_point_highlighter_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/range_annotation_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/selection/lock_selection_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/selection/select_nearest_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/series_legend_behavior_test.dart delete mode 100644 web/charts/common/test/chart/common/behavior/slider/slider_test.dart delete mode 100644 web/charts/common/test/chart/common/gesture_listener_test.dart delete mode 100644 web/charts/common/test/chart/common/selection_model/selection_model_test.dart delete mode 100644 web/charts/common/test/chart/layout/layout_manager_impl_test.dart delete mode 100644 web/charts/common/test/chart/line/line_renderer_test.dart delete mode 100644 web/charts/common/test/chart/line/renderer_nearest_detail_test.dart delete mode 100644 web/charts/common/test/chart/pie/arc_label_decorator_test.dart delete mode 100644 web/charts/common/test/chart/scatter_plot/comparison_points_decorator_test.dart delete mode 100644 web/charts/common/test/chart/scatter_plot/point_renderer_test.dart delete mode 100644 web/charts/common/test/chart/scatter_plot/symbol_annotation_renderer_test.dart delete mode 100644 web/charts/example/pubspec.lock delete mode 100644 web/charts/example/pubspec.yaml delete mode 100644 web/charts/example/web/assets/FontManifest.json delete mode 100644 web/charts/example/web/main.dart delete mode 100644 web/charts/flutter/CHANGELOG.md delete mode 100644 web/charts/flutter/LICENSE delete mode 100644 web/charts/flutter/README.md delete mode 100644 web/charts/flutter/lib/flutter.dart delete mode 100644 web/charts/flutter/lib/src/bar_chart.dart delete mode 100644 web/charts/flutter/lib/src/base_chart.dart delete mode 100644 web/charts/flutter/lib/src/base_chart_state.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/a11y/domain_a11y_explore_behavior.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/calculation/percent_injector.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/chart_behavior.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/chart_title/chart_title.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/domain_highlighter.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/initial_selection.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/legend/datum_legend.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/legend/legend.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/legend/legend_content_builder.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/legend/legend_entry_layout.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/legend/legend_layout.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/legend/series_legend.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/line_point_highlighter.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/range_annotation.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/select_nearest.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/slider/slider.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/sliding_viewport.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/zoom/initial_hint_behavior.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/zoom/pan_and_zoom_behavior.dart delete mode 100644 web/charts/flutter/lib/src/behaviors/zoom/pan_behavior.dart delete mode 100644 web/charts/flutter/lib/src/canvas/circle_sector_painter.dart delete mode 100644 web/charts/flutter/lib/src/canvas/line_painter.dart delete mode 100644 web/charts/flutter/lib/src/canvas/pie_painter.dart delete mode 100644 web/charts/flutter/lib/src/canvas/point_painter.dart delete mode 100644 web/charts/flutter/lib/src/canvas/polygon_painter.dart delete mode 100644 web/charts/flutter/lib/src/cartesian_chart.dart delete mode 100644 web/charts/flutter/lib/src/chart_canvas.dart delete mode 100644 web/charts/flutter/lib/src/chart_container.dart delete mode 100644 web/charts/flutter/lib/src/chart_gesture_detector.dart delete mode 100644 web/charts/flutter/lib/src/chart_state.dart delete mode 100644 web/charts/flutter/lib/src/combo_chart/combo_chart.dart delete mode 100644 web/charts/flutter/lib/src/graphics_factory.dart delete mode 100644 web/charts/flutter/lib/src/line_chart.dart delete mode 100644 web/charts/flutter/lib/src/line_style.dart delete mode 100644 web/charts/flutter/lib/src/pie_chart.dart delete mode 100644 web/charts/flutter/lib/src/scatter_plot_chart.dart delete mode 100644 web/charts/flutter/lib/src/selection_model_config.dart delete mode 100644 web/charts/flutter/lib/src/symbol_renderer.dart delete mode 100644 web/charts/flutter/lib/src/text_element.dart delete mode 100644 web/charts/flutter/lib/src/text_style.dart delete mode 100644 web/charts/flutter/lib/src/time_series_chart.dart delete mode 100644 web/charts/flutter/lib/src/user_managed_state.dart delete mode 100644 web/charts/flutter/lib/src/util.dart delete mode 100644 web/charts/flutter/lib/src/util/color.dart delete mode 100644 web/charts/flutter/lib/src/widget_layout_delegate.dart delete mode 100644 web/charts/flutter/pubspec.lock delete mode 100644 web/charts/flutter/pubspec.yaml delete mode 100644 web/charts/flutter/test/behaviors/legend/legend_layout_test.dart delete mode 100644 web/charts/flutter/test/text_element_test.dart delete mode 100644 web/charts/flutter/test/user_managed_state_test.dart delete mode 100644 web/charts/flutter/test/widget_layout_delegate_test.dart rename web/charts/{example => }/lib/a11y/a11y_gallery.dart (96%) rename web/charts/{example => }/lib/a11y/domain_a11y_explore_bar_chart.dart (99%) rename web/charts/{example => }/lib/app_config.dart (96%) rename web/charts/{example => }/lib/axes/axes_gallery.dart (99%) rename web/charts/{example => }/lib/axes/bar_secondary_axis.dart (99%) rename web/charts/{example => }/lib/axes/bar_secondary_axis_only.dart (98%) rename web/charts/{example => }/lib/axes/custom_axis_tick_formatters.dart (99%) rename web/charts/{example => }/lib/axes/custom_font_size_and_color.dart (99%) rename web/charts/{example => }/lib/axes/custom_measure_tick_count.dart (98%) rename web/charts/{example => }/lib/axes/flipped_vertical_axis.dart (98%) rename web/charts/{example => }/lib/axes/gridline_dash_pattern.dart (98%) rename web/charts/{example => }/lib/axes/hidden_ticks_and_labels_axis.dart (98%) rename web/charts/{example => }/lib/axes/horizontal_bar_secondary_axis.dart (99%) rename web/charts/{example => }/lib/axes/integer_only_measure_axis.dart (99%) rename web/charts/{example => }/lib/axes/line_disjoint_axis.dart (99%) rename web/charts/{example => }/lib/axes/measure_axis_label_alignment.dart (98%) rename web/charts/{example => }/lib/axes/nonzero_bound_measure_axis.dart (98%) rename web/charts/{example => }/lib/axes/numeric_initial_viewport.dart (99%) rename web/charts/{example => }/lib/axes/ordinal_initial_viewport.dart (99%) rename web/charts/{example => }/lib/axes/short_tick_length_axis.dart (98%) rename web/charts/{example => }/lib/axes/statically_provided_ticks.dart (99%) rename web/charts/{example => }/lib/bar_chart/bar_gallery.dart (99%) rename web/charts/{example => }/lib/bar_chart/custom_rounded_bars.dart (98%) rename web/charts/{example => }/lib/bar_chart/grouped.dart (99%) rename web/charts/{example => }/lib/bar_chart/grouped_fill_color.dart (99%) rename web/charts/{example => }/lib/bar_chart/grouped_single_target_line.dart (99%) rename web/charts/{example => }/lib/bar_chart/grouped_stacked.dart (99%) rename web/charts/{example => }/lib/bar_chart/grouped_stacked_weight_pattern.dart (99%) rename web/charts/{example => }/lib/bar_chart/grouped_target_line.dart (99%) rename web/charts/{example => }/lib/bar_chart/horizontal.dart (98%) rename web/charts/{example => }/lib/bar_chart/horizontal_bar_label.dart (99%) rename web/charts/{example => }/lib/bar_chart/horizontal_bar_label_custom.dart (99%) rename web/charts/{example => }/lib/bar_chart/horizontal_pattern_forward_hatch.dart (99%) rename web/charts/{example => }/lib/bar_chart/pattern_forward_hatch.dart (99%) rename web/charts/{example => }/lib/bar_chart/simple.dart (98%) rename web/charts/{example => }/lib/bar_chart/spark_bar.dart (99%) rename web/charts/{example => }/lib/bar_chart/stacked.dart (99%) rename web/charts/{example => }/lib/bar_chart/stacked_fill_color.dart (99%) rename web/charts/{example => }/lib/bar_chart/stacked_horizontal.dart (99%) rename web/charts/{example => }/lib/bar_chart/stacked_target_line.dart (99%) rename web/charts/{example => }/lib/behaviors/behaviors_gallery.dart (99%) rename web/charts/{example => }/lib/behaviors/chart_title.dart (99%) rename web/charts/{example => }/lib/behaviors/initial_hint_animation.dart (99%) rename web/charts/{example => }/lib/behaviors/initial_selection.dart (98%) rename web/charts/{example => }/lib/behaviors/percent_of_domain.dart (99%) rename web/charts/{example => }/lib/behaviors/percent_of_domain_by_category.dart (99%) rename web/charts/{example => }/lib/behaviors/percent_of_series.dart (98%) rename web/charts/{example => }/lib/behaviors/selection_bar_highlight.dart (98%) rename web/charts/{example => }/lib/behaviors/selection_callback_example.dart (99%) rename web/charts/{example => }/lib/behaviors/selection_line_highlight.dart (99%) rename web/charts/{example => }/lib/behaviors/selection_line_highlight_custom_shape.dart (99%) rename web/charts/{example => }/lib/behaviors/selection_scatter_plot_highlight.dart (99%) rename web/charts/{example => }/lib/behaviors/selection_user_managed.dart (99%) rename web/charts/{example => }/lib/behaviors/slider.dart (98%) rename web/charts/{example => }/lib/behaviors/sliding_viewport_on_selection.dart (99%) rename web/charts/{example => }/lib/combo_chart/combo_gallery.dart (98%) rename web/charts/{example => }/lib/combo_chart/date_time_line_point.dart (99%) rename web/charts/{example => }/lib/combo_chart/numeric_line_bar.dart (99%) rename web/charts/{example => }/lib/combo_chart/numeric_line_point.dart (99%) rename web/charts/{example => }/lib/combo_chart/ordinal_bar_line.dart (99%) rename web/charts/{example => }/lib/combo_chart/scatter_plot_line.dart (99%) rename web/charts/{example => }/lib/drawer.dart (97%) rename web/charts/{example => }/lib/gallery_scaffold.dart (97%) rename web/charts/{example => }/lib/home.dart (99%) rename web/charts/{example => }/lib/i18n/i18n_gallery.dart (97%) rename web/charts/{example => }/lib/i18n/rtl_bar_chart.dart (98%) rename web/charts/{example => }/lib/i18n/rtl_line_chart.dart (98%) rename web/charts/{example => }/lib/i18n/rtl_line_segments.dart (99%) rename web/charts/{example => }/lib/i18n/rtl_series_legend.dart (99%) rename web/charts/{example => }/lib/legends/datum_legend_options.dart (99%) rename web/charts/{example => }/lib/legends/datum_legend_with_measures.dart (99%) rename web/charts/{example => }/lib/legends/default_hidden_series_legend.dart (99%) rename web/charts/{example => }/lib/legends/legend_custom_symbol.dart (99%) rename web/charts/{example => }/lib/legends/legends_gallery.dart (98%) rename web/charts/{example => }/lib/legends/series_legend_options.dart (99%) rename web/charts/{example => }/lib/legends/series_legend_with_measures.dart (99%) rename web/charts/{example => }/lib/legends/simple_datum_legend.dart (98%) rename web/charts/{example => }/lib/legends/simple_series_legend.dart (99%) rename web/charts/{example => }/lib/line_chart/animation_zoom.dart (98%) rename web/charts/{example => }/lib/line_chart/area_and_line.dart (99%) rename web/charts/{example => }/lib/line_chart/dash_pattern.dart (99%) rename web/charts/{example => }/lib/line_chart/line_annotation.dart (98%) rename web/charts/{example => }/lib/line_chart/line_gallery.dart (99%) rename web/charts/{example => }/lib/line_chart/points.dart (98%) rename web/charts/{example => }/lib/line_chart/range_annotation.dart (98%) rename web/charts/{example => }/lib/line_chart/range_annotation_margin.dart (99%) rename web/charts/{example => }/lib/line_chart/segments.dart (99%) rename web/charts/{example => }/lib/line_chart/simple.dart (98%) rename web/charts/{example => }/lib/line_chart/simple_nulls.dart (99%) rename web/charts/{example => }/lib/line_chart/stacked_area.dart (99%) rename web/charts/{example => }/lib/line_chart/stacked_area_custom_color.dart (99%) rename web/charts/{example => }/lib/line_chart/stacked_area_nulls.dart (99%) rename web/charts/{example => }/lib/main.dart (97%) rename web/charts/{example => }/lib/pie_chart/auto_label.dart (98%) rename web/charts/{example => }/lib/pie_chart/donut.dart (98%) rename web/charts/{example => }/lib/pie_chart/gauge.dart (98%) rename web/charts/{example => }/lib/pie_chart/outside_label.dart (98%) rename web/charts/{example => }/lib/pie_chart/partial_pie.dart (98%) rename web/charts/{example => }/lib/pie_chart/pie_gallery.dart (98%) rename web/charts/{example => }/lib/pie_chart/simple.dart (98%) rename web/charts/{example => }/lib/scatter_plot_chart/animation_zoom.dart (99%) rename web/charts/{example => }/lib/scatter_plot_chart/bucketing_axis.dart (99%) rename web/charts/{example => }/lib/scatter_plot_chart/comparison_points.dart (99%) rename web/charts/{example => }/lib/scatter_plot_chart/scatter_plot_gallery.dart (98%) rename web/charts/{example => }/lib/scatter_plot_chart/shapes.dart (99%) rename web/charts/{example => }/lib/scatter_plot_chart/simple.dart (99%) rename web/charts/{example => }/lib/time_series_chart/confidence_interval.dart (99%) rename web/charts/{example => }/lib/time_series_chart/end_points_axis.dart (98%) rename web/charts/{example => }/lib/time_series_chart/line_annotation.dart (98%) rename web/charts/{example => }/lib/time_series_chart/range_annotation.dart (98%) rename web/charts/{example => }/lib/time_series_chart/range_annotation_margin.dart (99%) rename web/charts/{example => }/lib/time_series_chart/simple.dart (98%) rename web/charts/{example => }/lib/time_series_chart/symbol_annotation.dart (99%) rename web/charts/{example => }/lib/time_series_chart/time_series_gallery.dart (98%) rename web/charts/{example => }/lib/time_series_chart/with_bar_renderer.dart (99%) create mode 100644 web/charts/pubspec.lock create mode 100644 web/charts/pubspec.yaml rename web/charts/{example => }/web/index.html (100%) rename web/dad_jokes/{web => }/assets/FontManifest.json (100%) rename web/dad_jokes/{web => }/assets/fonts/PatrickHand-Regular.ttf (100%) rename web/dad_jokes/{web => }/assets/icon.png (100%) rename web/dad_jokes/{web => assets}/preview.png (100%) delete mode 100644 web/dad_jokes/web/main.dart create mode 100644 web/filipino_cuisine/assets/banana.png create mode 100644 web/filipino_cuisine/assets/beef.png rename web/filipino_cuisine/{web => }/assets/beef_caldereta.jpg (100%) rename web/filipino_cuisine/{web => }/assets/black_pepper.png (100%) create mode 100644 web/filipino_cuisine/assets/bokchoy.png create mode 100644 web/filipino_cuisine/assets/butter.png create mode 100644 web/filipino_cuisine/assets/cabbage.png rename web/filipino_cuisine/{web => }/assets/calamares.jpg (100%) create mode 100644 web/filipino_cuisine/assets/carrot.png create mode 100644 web/filipino_cuisine/assets/cheese.png rename web/filipino_cuisine/{web => }/assets/chicken_adobo.jpg (100%) create mode 100644 web/filipino_cuisine/assets/chili.png rename web/filipino_cuisine/{web => }/assets/crispy_pata.jpg (100%) create mode 100644 web/filipino_cuisine/assets/egg.png rename web/filipino_cuisine/{web => }/assets/embutido.jpg (100%) create mode 100644 web/filipino_cuisine/assets/flour.png create mode 100644 web/filipino_cuisine/assets/garlic.png create mode 100644 web/filipino_cuisine/assets/green_beans.png create mode 100644 web/filipino_cuisine/assets/green_bell.png rename web/filipino_cuisine/{web => }/assets/grilled_pork_ribs.jpg (100%) rename web/filipino_cuisine/{web => }/assets/grilled_seafood.jpg (100%) create mode 100644 web/filipino_cuisine/assets/ground_pork.png create mode 100644 web/filipino_cuisine/assets/icon/fc_icon.png create mode 100644 web/filipino_cuisine/assets/lemon.png create mode 100644 web/filipino_cuisine/assets/oil.png create mode 100644 web/filipino_cuisine/assets/onion.png rename web/filipino_cuisine/{web => }/assets/pancit_canton.jpg (100%) rename web/filipino_cuisine/{web => }/assets/pochero.jpg (100%) create mode 100644 web/filipino_cuisine/assets/pork.png rename web/filipino_cuisine/{web => }/assets/pork_sisig.jpg (100%) create mode 100644 web/filipino_cuisine/assets/potato.png rename web/filipino_cuisine/{web => assets}/preview.png (100%) create mode 100644 web/filipino_cuisine/assets/raisins.png create mode 100644 web/filipino_cuisine/assets/red_bell.png create mode 100644 web/filipino_cuisine/assets/red_pepper.png create mode 100644 web/filipino_cuisine/assets/salt.png create mode 100644 web/filipino_cuisine/assets/sausage.png rename web/filipino_cuisine/{web => }/assets/smoked_salmon.jpg (100%) create mode 100644 web/filipino_cuisine/assets/squid.png rename web/filipino_cuisine/{web => }/assets/sweet_and_sour_chicken_poultry.jpg (100%) create mode 100644 web/filipino_cuisine/assets/tomato.png create mode 100644 web/filipino_cuisine/assets/yellow_onion.png rename web/filipino_cuisine/{web/assets => }/fonts/Arkipelago.otf (100%) rename web/filipino_cuisine/{web/assets => }/fonts/OpenSans-Bold.ttf (100%) rename web/filipino_cuisine/{web/assets => }/fonts/OpenSans-Regular.ttf (100%) delete mode 100644 web/filipino_cuisine/web/assets/FontManifest.json delete mode 100644 web/filipino_cuisine/web/assets/banana.png delete mode 100644 web/filipino_cuisine/web/assets/beef.png delete mode 100644 web/filipino_cuisine/web/assets/bokchoy.png delete mode 100644 web/filipino_cuisine/web/assets/butter.png delete mode 100644 web/filipino_cuisine/web/assets/cabbage.png delete mode 100644 web/filipino_cuisine/web/assets/carrot.png delete mode 100644 web/filipino_cuisine/web/assets/cheese.png delete mode 100644 web/filipino_cuisine/web/assets/chili.png delete mode 100644 web/filipino_cuisine/web/assets/egg.png delete mode 100644 web/filipino_cuisine/web/assets/flour.png delete mode 100644 web/filipino_cuisine/web/assets/garlic.png delete mode 100644 web/filipino_cuisine/web/assets/green_beans.png delete mode 100644 web/filipino_cuisine/web/assets/green_bell.png delete mode 100644 web/filipino_cuisine/web/assets/ground_pork.png delete mode 100644 web/filipino_cuisine/web/assets/lemon.png delete mode 100644 web/filipino_cuisine/web/assets/oil.png delete mode 100644 web/filipino_cuisine/web/assets/onion.png delete mode 100644 web/filipino_cuisine/web/assets/pork.png delete mode 100644 web/filipino_cuisine/web/assets/potato.png delete mode 100644 web/filipino_cuisine/web/assets/raisins.png delete mode 100644 web/filipino_cuisine/web/assets/recipes.json delete mode 100644 web/filipino_cuisine/web/assets/red_bell.png delete mode 100644 web/filipino_cuisine/web/assets/red_pepper.png delete mode 100644 web/filipino_cuisine/web/assets/salt.png delete mode 100644 web/filipino_cuisine/web/assets/sausage.png delete mode 100644 web/filipino_cuisine/web/assets/squid.png delete mode 100644 web/filipino_cuisine/web/assets/tomato.png delete mode 100644 web/filipino_cuisine/web/assets/yellow_onion.png delete mode 100644 web/filipino_cuisine/web/main.dart create mode 100644 web/gallery/.gitignore create mode 100644 web/gallery/.metadata rename web/gallery/{web => assets}/preview.png (100%) delete mode 100644 web/gallery/build.yaml create mode 100644 web/gallery/lib/demo/calculator/home.dart create mode 100644 web/gallery/lib/demo/calculator/logic.dart create mode 100644 web/gallery/lib/demo/calculator_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_activity_indicator_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_alert_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_buttons_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_navigation_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_picker_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_refresh_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_segmented_control_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_slider_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_switch_demo.dart create mode 100644 web/gallery/lib/demo/cupertino/cupertino_text_field_demo.dart create mode 100644 web/gallery/lib/demo/fortnightly/README.md create mode 100644 web/gallery/lib/demo/fortnightly/fortnightly.dart create mode 100644 web/gallery/lib/demo/images_demo.dart create mode 100644 web/gallery/lib/demo/material/banner_demo.dart create mode 100644 web/gallery/lib/demo/material/buttons_demo.dart delete mode 100644 web/gallery/lib/demo/material/editable_text_demo.dart create mode 100644 web/gallery/lib/demo/material/expansion_tile_list_demo.dart delete mode 100644 web/gallery/lib/demo/material/material_button_demo.dart delete mode 100644 web/gallery/lib/demo/material/stack_demo.dart delete mode 100644 web/gallery/lib/demo/material/switch_demo.dart delete mode 100644 web/gallery/lib/demo/material/text_demo.dart delete mode 100644 web/gallery/lib/demo/material/two_level_list_demo.dart create mode 100644 web/gallery/lib/demo/shrine/app.dart create mode 100644 web/gallery/lib/demo/shrine/backdrop.dart create mode 100644 web/gallery/lib/demo/shrine/category_menu_page.dart rename web/{charts/common/lib/src/chart/cartesian/axis/time/date_time_extents.dart => gallery/lib/demo/shrine/colors.dart} (50%) create mode 100644 web/gallery/lib/demo/shrine/expanding_bottom_sheet.dart create mode 100644 web/gallery/lib/demo/shrine/home.dart create mode 100644 web/gallery/lib/demo/shrine/login.dart create mode 100644 web/gallery/lib/demo/shrine/model/app_state_model.dart create mode 100644 web/gallery/lib/demo/shrine/model/product.dart create mode 100644 web/gallery/lib/demo/shrine/model/products_repository.dart create mode 100644 web/gallery/lib/demo/shrine/shopping_cart.dart delete mode 100644 web/gallery/lib/demo/shrine/shrine_data.dart delete mode 100644 web/gallery/lib/demo/shrine/shrine_home.dart delete mode 100644 web/gallery/lib/demo/shrine/shrine_order.dart delete mode 100644 web/gallery/lib/demo/shrine/shrine_page.dart delete mode 100644 web/gallery/lib/demo/shrine/shrine_theme.dart delete mode 100644 web/gallery/lib/demo/shrine/shrine_types.dart create mode 100644 web/gallery/lib/demo/shrine/supplemental/asymmetric_view.dart create mode 100644 web/gallery/lib/demo/shrine/supplemental/cut_corners_border.dart create mode 100644 web/gallery/lib/demo/shrine/supplemental/product_card.dart create mode 100644 web/gallery/lib/demo/shrine/supplemental/product_columns.dart create mode 100644 web/gallery/lib/demo/transformations/transformations_demo.dart create mode 100644 web/gallery/lib/demo/transformations/transformations_demo_board.dart create mode 100644 web/gallery/lib/demo/transformations/transformations_demo_color_picker.dart create mode 100644 web/gallery/lib/demo/transformations/transformations_demo_edit_board_point.dart create mode 100644 web/gallery/lib/demo/transformations/transformations_demo_gesture_transformable.dart create mode 100644 web/gallery/lib/demo/transformations/transformations_demo_inertial_motion.dart create mode 100644 web/gallery/lib/demo/video_demo.dart create mode 100644 web/gallery/lib/gallery/example_code.dart create mode 100644 web/gallery/lib/gallery/example_code_parser.dart create mode 100644 web/gallery/lib/gallery/syntax_highlighter.dart create mode 100644 web/gallery/lib/gallery/updater.dart delete mode 100644 web/gallery/lib/main_houdini.dart create mode 100644 web/gallery/lib/main_publish.dart delete mode 100644 web/gallery/web/assets/AbrilFatface-Regular.ttf delete mode 100644 web/gallery/web/assets/FontManifest.json delete mode 100644 web/gallery/web/assets/GalleryIcons.ttf delete mode 100644 web/gallery/web/assets/GoogleSans-Regular.ttf delete mode 100644 web/gallery/web/assets/LibreFranklin-Regular.ttf delete mode 100644 web/gallery/web/assets/Merriweather-Regular.ttf delete mode 100644 web/gallery/web/assets/README.md delete mode 100644 web/gallery/web/assets/Raleway-Regular.ttf delete mode 100644 web/gallery/web/assets/food/butternut_squash_soup.png delete mode 100644 web/gallery/web/assets/food/cherry_pie.png delete mode 100644 web/gallery/web/assets/food/chopped_beet_leaves.png delete mode 100644 web/gallery/web/assets/food/icons/fish.png delete mode 100644 web/gallery/web/assets/food/icons/healthy.png delete mode 100644 web/gallery/web/assets/food/icons/main.png delete mode 100644 web/gallery/web/assets/food/icons/meat.png delete mode 100644 web/gallery/web/assets/food/icons/quick.png delete mode 100644 web/gallery/web/assets/food/icons/spicy.png delete mode 100644 web/gallery/web/assets/food/icons/veggie.png delete mode 100644 web/gallery/web/assets/food/pesto_pasta.png delete mode 100644 web/gallery/web/assets/food/roasted_chicken.png delete mode 100644 web/gallery/web/assets/food/spanakopita.png delete mode 100644 web/gallery/web/assets/food/spinach_onion_salad.png delete mode 100644 web/gallery/web/assets/logos/flutter_white/1.5x/logo.png delete mode 100644 web/gallery/web/assets/logos/flutter_white/2.5x/logo.png delete mode 100644 web/gallery/web/assets/logos/flutter_white/3.0x/logo.png delete mode 100644 web/gallery/web/assets/logos/flutter_white/4.0x/logo.png delete mode 100644 web/gallery/web/assets/logos/flutter_white/logo.png delete mode 100644 web/gallery/web/assets/logos/pesto/logo_small.png delete mode 100644 web/gallery/web/assets/people/ali_landscape.png delete mode 100644 web/gallery/web/assets/people/square/ali.png delete mode 100644 web/gallery/web/assets/people/square/peter.png delete mode 100644 web/gallery/web/assets/people/square/sandra.png delete mode 100644 web/gallery/web/assets/people/square/stella.png delete mode 100644 web/gallery/web/assets/people/square/trevor.png delete mode 100644 web/gallery/web/assets/places/india_chennai_flower_market.png delete mode 100644 web/gallery/web/assets/places/india_chennai_highway.png delete mode 100644 web/gallery/web/assets/places/india_chettinad_produce.png delete mode 100644 web/gallery/web/assets/places/india_chettinad_silk_maker.png delete mode 100644 web/gallery/web/assets/places/india_pondicherry_beach.png delete mode 100644 web/gallery/web/assets/places/india_pondicherry_fisherman.png delete mode 100644 web/gallery/web/assets/places/india_pondicherry_salt_farm.png delete mode 100644 web/gallery/web/assets/places/india_tanjore_bronze_works.png delete mode 100644 web/gallery/web/assets/places/india_tanjore_market_merchant.png delete mode 100644 web/gallery/web/assets/places/india_tanjore_market_technology.png delete mode 100644 web/gallery/web/assets/places/india_tanjore_thanjavur_temple.png delete mode 100644 web/gallery/web/assets/places/india_tanjore_thanjavur_temple_carvings.png delete mode 100644 web/gallery/web/assets/places/india_thanjavur_market.png delete mode 100644 web/gallery/web/assets/products/backpack.png delete mode 100644 web/gallery/web/assets/products/belt.png delete mode 100644 web/gallery/web/assets/products/cup.png delete mode 100644 web/gallery/web/assets/products/deskset.png delete mode 100644 web/gallery/web/assets/products/dress.png delete mode 100644 web/gallery/web/assets/products/earrings.png delete mode 100644 web/gallery/web/assets/products/flatwear.png delete mode 100644 web/gallery/web/assets/products/hat.png delete mode 100644 web/gallery/web/assets/products/jacket.png delete mode 100644 web/gallery/web/assets/products/jumper.png delete mode 100644 web/gallery/web/assets/products/kitchen_quattro.png delete mode 100644 web/gallery/web/assets/products/napkins.png delete mode 100644 web/gallery/web/assets/products/planters.png delete mode 100644 web/gallery/web/assets/products/platter.png delete mode 100644 web/gallery/web/assets/products/scarf.png delete mode 100644 web/gallery/web/assets/products/shirt.png delete mode 100644 web/gallery/web/assets/products/sunnies.png delete mode 100644 web/gallery/web/assets/products/sweater.png delete mode 100644 web/gallery/web/assets/products/sweats.png delete mode 100644 web/gallery/web/assets/products/table.png delete mode 100644 web/gallery/web/assets/products/teaset.png delete mode 100644 web/gallery/web/assets/products/top.png delete mode 100644 web/gallery/web/frame.html delete mode 100644 web/gallery/web/main.dart rename web/github_dataviz/{web => assets}/preview.png (100%) delete mode 100644 web/github_dataviz/web/main.dart rename web/particle_background/{web => assets}/preview.png (100%) delete mode 100644 web/particle_background/web/main.dart delete mode 100644 web/slide_puzzle/analysis_options.yaml create mode 100755 web/slide_puzzle/asset/fonts/plaster/OFL.txt create mode 100755 web/slide_puzzle/asset/fonts/plaster/Plaster-Regular.ttf create mode 100644 web/slide_puzzle/asset/seattle.jpg rename web/slide_puzzle/{web => assets}/preview.png (100%) delete mode 100644 web/slide_puzzle/lib/src/core/puzzle_proxy.dart delete mode 100644 web/slide_puzzle/lib/src/flutter.dart create mode 100644 web/slide_puzzle/lib/src/frame_nanny.dart delete mode 100644 web/slide_puzzle/lib/src/puzzle_controls.dart delete mode 100644 web/slide_puzzle/lib/src/themes.dart delete mode 100644 web/slide_puzzle/lib/src/value_tab_controller.dart delete mode 100644 web/slide_puzzle/web/assets/FontManifest.json delete mode 100644 web/slide_puzzle/web/assets/readme.md delete mode 100644 web/slide_puzzle/web/assets/seattle.jpg delete mode 100644 web/slide_puzzle/web/main.dart rename web/spinning_square/{web => assets}/preview.png (100%) delete mode 100644 web/spinning_square/web/main.dart rename web/timeflow/{web => assets}/preview.png (100%) delete mode 100644 web/timeflow/web/assets/FontManifest.json delete mode 100644 web/timeflow/web/main.dart rename web/vision_challenge/{web => }/assets/10.png (100%) rename web/vision_challenge/{web => }/assets/20.png (100%) rename web/vision_challenge/{web => }/assets/30.png (100%) rename web/vision_challenge/{web => }/assets/35.png (100%) rename web/vision_challenge/{web => }/assets/40.png (100%) rename web/vision_challenge/{web => }/assets/45.png (100%) rename web/vision_challenge/{web => }/assets/99.png (100%) rename web/vision_challenge/{web => }/assets/FontManifest.json (100%) rename web/vision_challenge/{web => }/assets/p0.jpg (100%) rename web/vision_challenge/{web => }/assets/p1.jpg (100%) rename web/vision_challenge/{web => assets}/preview.png (100%) delete mode 100644 web/vision_challenge/lib/packages/flutter_redux.dart delete mode 100644 web/vision_challenge/lib/packages/redux.dart delete mode 100644 web/vision_challenge/web/main.dart 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 0000000000000000000000000000000000000000..2ffe61bd7dc5bd8c1acca4e7e875b1bf334b7a12 GIT binary patch literal 206490 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%0D?{~Wva}8WHkm@HAZUxG< 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