mirror of
https://github.com/flutter/samples.git
synced 2026-04-05 03:01:19 +00:00
Add flutter_web samples (#75)
This commit is contained in:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
@@ -0,0 +1,112 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.DomainA11yExploreBehavior> {
|
||||
/// Returns a string for a11y vocalization from a list of series datum.
|
||||
final common.VocalizationCallback vocalizationCallback;
|
||||
|
||||
final Set<GestureType> 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<GestureType>();
|
||||
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<D> createCommonBehavior<D>() {
|
||||
return new common.DomainA11yExploreBehavior<D>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.PercentInjector> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
/// 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<D> createCommonBehavior<D>() =>
|
||||
new common.PercentInjector<D>(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;
|
||||
}
|
||||
72
web/charts/flutter/lib/src/behaviors/chart_behavior.dart
Normal file
72
web/charts/flutter/lib/src/behaviors/chart_behavior.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<B extends common.ChartBehavior> {
|
||||
Set<GestureType> get desiredGestures;
|
||||
|
||||
B createCommonBehavior<D>();
|
||||
|
||||
void updateCommonBehavior(B commonBehavior);
|
||||
|
||||
String get role;
|
||||
}
|
||||
|
||||
/// A chart behavior that depends on Flutter [State].
|
||||
abstract class ChartStateBehavior<B extends common.ChartBehavior> {
|
||||
set chartState(BaseChartState chartState);
|
||||
}
|
||||
|
||||
/// A chart behavior that can build a Flutter [Widget].
|
||||
abstract class BuildableBehavior<B extends common.ChartBehavior> {
|
||||
/// 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<int> get drawAreaBounds;
|
||||
}
|
||||
|
||||
/// Types of gestures accepted by a chart.
|
||||
enum GestureType {
|
||||
onLongPress,
|
||||
onTap,
|
||||
onHover,
|
||||
onDrag,
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.ChartTitle> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
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<D> createCommonBehavior<D>() =>
|
||||
new common.ChartTitle<D>(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);
|
||||
}
|
||||
}
|
||||
54
web/charts/flutter/lib/src/behaviors/domain_highlighter.dart
Normal file
54
web/charts/flutter/lib/src/behaviors/domain_highlighter.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.DomainHighlighter> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
final common.SelectionModelType selectionModelType;
|
||||
|
||||
DomainHighlighter([this.selectionModelType = common.SelectionModelType.info]);
|
||||
|
||||
@override
|
||||
common.DomainHighlighter<D> createCommonBehavior<D>() =>
|
||||
new common.DomainHighlighter<D>(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;
|
||||
}
|
||||
68
web/charts/flutter/lib/src/behaviors/initial_selection.dart
Normal file
68
web/charts/flutter/lib/src/behaviors/initial_selection.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.InitialSelection> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
final common.SelectionModelType selectionModelType;
|
||||
final List<String> selectedSeriesConfig;
|
||||
final List<common.SeriesDatumConfig> selectedDataConfig;
|
||||
|
||||
InitialSelection(
|
||||
{this.selectionModelType = common.SelectionModelType.info,
|
||||
this.selectedSeriesConfig,
|
||||
this.selectedDataConfig});
|
||||
|
||||
@override
|
||||
common.InitialSelection<D> createCommonBehavior<D>() =>
|
||||
new common.InitialSelection<D>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
340
web/charts/flutter/lib/src/behaviors/legend/datum_legend.dart
Normal file
340
web/charts/flutter/lib/src/behaviors/legend/datum_legend.dart
Normal file
@@ -0,0 +1,340 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.DatumLegend> {
|
||||
static const defaultBehaviorPosition = common.BehaviorPosition.top;
|
||||
static const defaultOutsideJustification =
|
||||
common.OutsideJustification.startDrawArea;
|
||||
static const defaultInsideJustification = common.InsideJustification.topStart;
|
||||
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
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<D> createCommonBehavior<D>() =>
|
||||
new _FlutterDatumLegend<D>(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<D> extends common.DatumLegend<D>
|
||||
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) {}
|
||||
}
|
||||
22
web/charts/flutter/lib/src/behaviors/legend/legend.dart
Normal file
22
web/charts/flutter/lib/src/behaviors/legend/legend.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<T, D> {
|
||||
/// Delegates handling of legend entry clicks according to the configured
|
||||
/// [LegendTapHandling] strategy.
|
||||
onLegendEntryTapUp(LegendEntry detail);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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 = <Widget>[];
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
158
web/charts/flutter/lib/src/behaviors/legend/legend_layout.dart
Normal file
158
web/charts/flutter/lib/src/behaviors/legend/legend_layout.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<Widget> 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<Widget> 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<Widget> legendEntries) {
|
||||
final maxColumns = (desiredMaxColumns == _noLimit)
|
||||
? legendEntries.length
|
||||
: min(legendEntries.length, desiredMaxColumns);
|
||||
|
||||
final rows = <TableRow>[];
|
||||
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<Widget> legendEntries) {
|
||||
final maxRows = (desiredMaxRows == _noLimit)
|
||||
? legendEntries.length
|
||||
: min(legendEntries.length, desiredMaxRows);
|
||||
|
||||
final rows =
|
||||
new List.generate(maxRows, (_) => new TableRow(children: <Widget>[]));
|
||||
for (var i = 0; i < legendEntries.length; i++) {
|
||||
rows[i % maxRows].children.add(legendEntries[i]);
|
||||
}
|
||||
|
||||
return _buildTableFromRows(rows);
|
||||
}
|
||||
|
||||
Table _buildTableFromRows(List<TableRow> 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());
|
||||
}
|
||||
}
|
||||
382
web/charts/flutter/lib/src/behaviors/legend/series_legend.dart
Normal file
382
web/charts/flutter/lib/src/behaviors/legend/series_legend.dart
Normal file
@@ -0,0 +1,382 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.SeriesLegend> {
|
||||
static const defaultBehaviorPosition = common.BehaviorPosition.top;
|
||||
static const defaultOutsideJustification =
|
||||
common.OutsideJustification.startDrawArea;
|
||||
static const defaultInsideJustification = common.InsideJustification.topStart;
|
||||
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
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<String> 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<String> 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<String> 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<D> createCommonBehavior<D>() =>
|
||||
new _FlutterSeriesLegend<D>(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<D> extends common.SeriesLegend<D>
|
||||
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);
|
||||
}
|
||||
}
|
||||
127
web/charts/flutter/lib/src/behaviors/line_point_highlighter.dart
Normal file
127
web/charts/flutter/lib/src/behaviors/line_point_highlighter.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.LinePointHighlighter> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
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<int> 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<D> createCommonBehavior<D>() =>
|
||||
new common.LinePointHighlighter<D>(
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
117
web/charts/flutter/lib/src/behaviors/range_annotation.dart
Normal file
117
web/charts/flutter/lib/src/behaviors/range_annotation.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.RangeAnnotation> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
/// List of annotations to render on the chart.
|
||||
final List<common.AnnotationSegment> 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<D> createCommonBehavior<D>() =>
|
||||
new common.RangeAnnotation<D>(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);
|
||||
}
|
||||
147
web/charts/flutter/lib/src/behaviors/select_nearest.dart
Normal file
147
web/charts/flutter/lib/src/behaviors/select_nearest.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.SelectNearest> {
|
||||
final Set<GestureType> 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<GestureType> _getDesiredGestures(
|
||||
common.SelectionTrigger eventTrigger) {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
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<D> createCommonBehavior<D>() {
|
||||
return new common.SelectNearest<D>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
196
web/charts/flutter/lib/src/behaviors/slider/slider.dart
Normal file
196
web/charts/flutter/lib/src/behaviors/slider/slider.dart
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.Slider> {
|
||||
final Set<GestureType> 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<int>(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<GestureType> _getDesiredGestures(
|
||||
common.SelectionTrigger eventTrigger) {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
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<D> createCommonBehavior<D>() => new common.Slider<D>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
53
web/charts/flutter/lib/src/behaviors/sliding_viewport.dart
Normal file
53
web/charts/flutter/lib/src/behaviors/sliding_viewport.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.SlidingViewport> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
final common.SelectionModelType selectionModelType;
|
||||
|
||||
SlidingViewport([this.selectionModelType = common.SelectionModelType.info]);
|
||||
|
||||
@override
|
||||
common.SlidingViewport<D> createCommonBehavior<D>() =>
|
||||
new common.SlidingViewport<D>(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;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.InitialHintBehavior> {
|
||||
final desiredGestures = new Set<GestureType>();
|
||||
|
||||
final Duration hintDuration;
|
||||
final double maxHintTranslate;
|
||||
final double maxHintScaleFactor;
|
||||
|
||||
InitialHintBehavior(
|
||||
{this.hintDuration, this.maxHintTranslate, this.maxHintScaleFactor});
|
||||
|
||||
@override
|
||||
common.InitialHintBehavior<D> createCommonBehavior<D>() {
|
||||
final behavior = new FlutterInitialHintBehavior<D>();
|
||||
|
||||
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<D> extends common.InitialHintBehavior<D>
|
||||
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<D> chart) {
|
||||
_chartState.disposeAnimationController(this);
|
||||
_hintAnimator = null;
|
||||
super.removeFrom(chart);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.PanAndZoomBehavior> {
|
||||
final _desiredGestures = new Set<GestureType>.from([
|
||||
GestureType.onDrag,
|
||||
]);
|
||||
|
||||
Set<GestureType> 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<D> createCommonBehavior<D>() {
|
||||
return new FlutterPanAndZoomBehavior<D>()
|
||||
..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<D> extends common.PanAndZoomBehavior<D>
|
||||
with FlutterPanBehaviorMixin {}
|
||||
186
web/charts/flutter/lib/src/behaviors/zoom/pan_behavior.dart
Normal file
186
web/charts/flutter/lib/src/behaviors/zoom/pan_behavior.dart
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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<common.PanBehavior> {
|
||||
final _desiredGestures = new Set<GestureType>.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<GestureType> get desiredGestures => _desiredGestures;
|
||||
|
||||
@override
|
||||
common.PanBehavior<D> createCommonBehavior<D>() {
|
||||
return new FlutterPanBehavior<D>()
|
||||
..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<D> = common.PanBehavior<D>
|
||||
with FlutterPanBehaviorMixin;
|
||||
|
||||
/// Mixin that adds fling gesture support to [common.PanBehavior] or subclasses
|
||||
/// thereof.
|
||||
mixin FlutterPanBehaviorMixin<D> on common.PanBehavior<D>
|
||||
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<D> chart) {
|
||||
stopFlingAnimation();
|
||||
_chartState.disposeAnimationController(this);
|
||||
_flingAnimator = null;
|
||||
super.removeFrom(chart);
|
||||
}
|
||||
|
||||
@override
|
||||
bool onTapTest(Point<double> chartPoint) {
|
||||
super.onTapTest(chartPoint);
|
||||
|
||||
stopFlingAnimation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool onDragEnd(
|
||||
Point<double> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user