first commit
This commit is contained in:
341
lib/src/behaviors/legend/datum_legend.dart
Normal file
341
lib/src/behaviors/legend/datum_legend.dart
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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,
|
||||
ChartBehavior,
|
||||
DatumLegend,
|
||||
InsideJustification,
|
||||
LegendEntry,
|
||||
MeasureFormatter,
|
||||
LegendDefaultMeasure,
|
||||
OutsideJustification,
|
||||
SelectionModelType,
|
||||
TextStyleSpec;
|
||||
import 'package:flutter/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<D> extends ChartBehavior<D> {
|
||||
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({
|
||||
required this.contentBuilder,
|
||||
this.selectionModelType,
|
||||
required this.position,
|
||||
required this.outsideJustification,
|
||||
required this.insideJustification,
|
||||
required this.showMeasures,
|
||||
this.legendDefaultMeasure,
|
||||
this.measureFormatter,
|
||||
this.secondaryMeasureFormatter,
|
||||
this.entryTextStyle,
|
||||
});
|
||||
|
||||
@override
|
||||
common.DatumLegend<D> createCommonBehavior() =>
|
||||
new _FlutterDatumLegend<D>(this);
|
||||
|
||||
@override
|
||||
void updateCommonBehavior(common.ChartBehavior 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 Object.hash(
|
||||
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
lib/src/behaviors/legend/legend.dart
Normal file
22
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);
|
||||
}
|
||||
92
lib/src/behaviors/legend/legend_content_builder.dart
Normal file
92
lib/src/behaviors/legend/legend_content_builder.dart
Normal file
@@ -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/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 = false}) {
|
||||
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 => Object.hash(legendEntryLayout, legendLayout);
|
||||
}
|
||||
146
lib/src/behaviors/legend/legend_entry_layout.dart
Normal file
146
lib/src/behaviors/legend/legend_entry_layout.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/widgets.dart';
|
||||
import 'package:flutter/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;
|
||||
final color = entryColor == null ? null : ColorUtil.toDartColor(entryColor);
|
||||
|
||||
// Get the SymbolRendererBuilder wrapping a common.SymbolRenderer if needed.
|
||||
final SymbolRendererBuilder symbolRendererBuilder =
|
||||
legendEntry.symbolRenderer! is SymbolRendererBuilder
|
||||
? legendEntry.symbolRenderer! as SymbolRendererBuilder
|
||||
: new SymbolRendererCanvas(
|
||||
legendEntry.symbolRenderer!, legendEntry.dashPattern);
|
||||
|
||||
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 = false}) {
|
||||
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.bodyMedium!.color;
|
||||
color = color!.withOpacity(0.26);
|
||||
}
|
||||
|
||||
return new TextStyle(
|
||||
inherit: true,
|
||||
fontFamily: textStyle?.fontFamily,
|
||||
fontSize: textStyle?.fontSize != null
|
||||
? textStyle!.fontSize!.toDouble()
|
||||
: null,
|
||||
color: color);
|
||||
}
|
||||
}
|
||||
162
lib/src/behaviors/legend/legend_layout.dart
Normal file
162
lib/src/behaviors/legend/legend_layout.dart
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/rendering.dart';
|
||||
import 'package:flutter/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;
|
||||
|
||||
/// Default EdgeInsets for padding rows to the max column count
|
||||
static const defaultCellPadding = const EdgeInsets.all(8.0);
|
||||
|
||||
final bool isHorizontalFirst;
|
||||
final int desiredMaxRows;
|
||||
final int desiredMaxColumns;
|
||||
final EdgeInsets? cellPadding;
|
||||
|
||||
TabularLegendLayout._internal(
|
||||
{required this.isHorizontalFirst,
|
||||
required this.desiredMaxRows,
|
||||
required 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 => Object.hash(
|
||||
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 = Padding(padding: cellPadding ?? defaultCellPadding);
|
||||
|
||||
// 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<int>(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<Padding>.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());
|
||||
}
|
||||
}
|
||||
383
lib/src/behaviors/legend/series_legend.dart
Normal file
383
lib/src/behaviors/legend/series_legend.dart
Normal file
@@ -0,0 +1,383 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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,
|
||||
ChartBehavior,
|
||||
InsideJustification,
|
||||
LegendEntry,
|
||||
LegendTapHandling,
|
||||
MeasureFormatter,
|
||||
LegendDefaultMeasure,
|
||||
OutsideJustification,
|
||||
SeriesLegend,
|
||||
SelectionModelType,
|
||||
TextStyleSpec;
|
||||
import 'package:collection/collection.dart' show ListEquality;
|
||||
import 'package:flutter/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<D> extends ChartBehavior<D> {
|
||||
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({
|
||||
required this.contentBuilder,
|
||||
this.selectionModelType,
|
||||
required this.position,
|
||||
required this.outsideJustification,
|
||||
required this.insideJustification,
|
||||
this.defaultHiddenSeries,
|
||||
required this.showMeasures,
|
||||
this.legendDefaultMeasure,
|
||||
this.measureFormatter,
|
||||
this.secondaryMeasureFormatter,
|
||||
this.entryTextStyle,
|
||||
});
|
||||
|
||||
@override
|
||||
common.SeriesLegend<D> createCommonBehavior() =>
|
||||
new _FlutterSeriesLegend<D>(this);
|
||||
|
||||
@override
|
||||
void updateCommonBehavior(common.ChartBehavior 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 Object.hash(
|
||||
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 != null &&
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user