Files
charts_flutter/lib/src/behaviors/legend/legend_layout.dart
2025-04-03 14:27:30 +07:00

163 lines
5.5 KiB
Dart

// Copyright 2018 the Charts project authors. Please see the AUTHORS file
// for details.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES 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());
}
}