Skip to content

Commit

Permalink
improv: add support for custom modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardobelchior committed Apr 22, 2022
1 parent f0eae1e commit ce76632
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 45 deletions.
137 changes: 137 additions & 0 deletions example/lib/pages/polygon_custom.dart
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:graphic/graphic.dart';

Expand Down Expand Up @@ -637,6 +639,54 @@ class PolygonCustomPage extends StatelessWidget {
],
),
),
Container(
child: const Text(
'Custom Modifier',
style: TextStyle(fontSize: 20),
),
padding: const EdgeInsets.fromLTRB(20, 40, 20, 5),
),
Container(
child: const Text(
'- With dodge and size modifier that scales the interval element width to fit within its band',
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 0),
alignment: Alignment.centerLeft,
),
Container(
margin: const EdgeInsets.only(top: 10),
width: 350,
height: 300,
child: Chart(
padding: (_) => const EdgeInsets.fromLTRB(40, 5, 10, 40),
data: adjustData,
variables: {
'index': Variable(
accessor: (Map map) => map['index'].toString(),
),
'type': Variable(
accessor: (Map map) => map['type'] as String,
),
'value': Variable(
accessor: (Map map) => map['value'] as num,
),
},
elements: [
IntervalElement(
position:
Varset('index') * Varset('value') / Varset('type'),
color: ColorAttr(
variable: 'type', values: Defaults.colors10),
size: SizeAttr(value: 2),
modifiers: [DodgeSizeModifier()],
)
],
axes: [
Defaults.horizontalAxis..tickLine = TickLine(),
Defaults.verticalAxis,
],
),
),
Container(
child: const Text(
'Candlestick Chart',
Expand Down Expand Up @@ -733,3 +783,90 @@ class PolygonCustomPage extends StatelessWidget {
);
}
}

/// Changes the position of elements while also updating their width to match
/// the number of elements in a single band. Useful for bar charts when the
/// width of the bars can be dynamic.
@immutable
class DodgeSizeModifier extends Modifier {
@override
bool operator ==(Object other) => other is DodgeSizeModifier;

@override
DodgeSizeGeomModifierOp toGeomModifierOp(ToGeomModifierOpParams params) {
return DodgeSizeGeomModifierOp(<String, dynamic>{
'form': params.form,
'scales': params.scales,
'groups': params.groups,
'coord': params.coord,
});
}
}

const _kBaseGroupPaddingHorizontal = 32.0;
const _kXAxis = 1;
const _kMinBarSize = 4.0;

/// The dodge geometry modifier.
class DodgeSizeGeomModifier extends GeomModifier {
DodgeSizeGeomModifier(this.band, this.coord);

final CoordConv coord;

/// The band ratio of each value. It represents the width of the interval the
/// [Aes]es have to position themselves within.
/// Its range is ]0, 1].
final double band;

@override
void modify(AesGroups value) {
final ratio = 1 / value.length;
final numGroups = value.length;
final groupHorizontalPadding = _kBaseGroupPaddingHorizontal / numGroups;
final invertedGroupPaddingHorizontal =
coord.invertDistance(groupHorizontalPadding, _kXAxis);

final effectiveBand = band - 2 * invertedGroupPaddingHorizontal;

final maxWidth = coord.convert(const Offset(1, 0)).dx;
final maxWidthInBand = effectiveBand * maxWidth;
final maxWidthPerAes = maxWidthInBand / numGroups;
final barHorizontalPadding = groupHorizontalPadding / 2;
final size = max(maxWidthPerAes - barHorizontalPadding, _kMinBarSize);

final bias = ratio * effectiveBand;

// Negatively shift half of the total bias.
var accumulated = -bias * (numGroups + 1) / 2;

for (final group in value) {
for (final aes in group) {
final oldPosition = aes.position;
aes.position = oldPosition
.map(
(point) => Offset(point.dx + accumulated + bias, point.dy),
)
.toList();

aes.size = size;
}
accumulated += bias;
}
}
}

class DodgeSizeGeomModifierOp extends GeomModifierOp<DodgeSizeGeomModifier> {
DodgeSizeGeomModifierOp(Map<String, dynamic> params) : super(params);

@override
DodgeSizeGeomModifier evaluate() {
final form = params['form'] as AlgForm;
final scales = params['scales'] as Map<String, ScaleConv>;
final coord = params['coord'] as CoordConv;

final xField = form.first[0];
final band = (scales[xField]! as DiscreteScaleConv).band;

return DodgeSizeGeomModifier(band, coord);
}
}
17 changes: 9 additions & 8 deletions lib/graphic.dart
Expand Up @@ -125,12 +125,12 @@ export 'src/variable/transform/map.dart' show MapTrans;
export 'src/variable/transform/proportion.dart' show Proportion;
export 'src/variable/transform/sort.dart' show Sort;

export 'src/scale/scale.dart' show Scale;
export 'src/scale/discrete.dart' show DiscreteScale;
export 'src/scale/continuous.dart' show ContinuousScale;
export 'src/scale/linear.dart' show LinearScale;
export 'src/scale/ordinal.dart' show OrdinalScale;
export 'src/scale/time.dart' show TimeScale;
export 'src/scale/scale.dart' show Scale, ScaleConv;
export 'src/scale/discrete.dart' show DiscreteScale, DiscreteScaleConv;
export 'src/scale/continuous.dart' show ContinuousScale, ContinuousScaleConv;
export 'src/scale/linear.dart' show LinearScale, LinearScaleConv;
export 'src/scale/ordinal.dart' show OrdinalScale, OrdinalScaleConv;
export 'src/scale/time.dart' show TimeScale, TimeScaleConv;

export 'src/geom/element.dart' show GeomElement;
export 'src/geom/function.dart' show FunctionElement;
Expand All @@ -141,7 +141,8 @@ export 'src/geom/interval.dart' show IntervalElement;
export 'src/geom/line.dart' show LineElement;
export 'src/geom/point.dart' show PointElement;
export 'src/geom/polygon.dart' show PolygonElement;
export 'src/geom/modifier/modifier.dart' show Modifier;
export 'src/geom/modifier/modifier.dart'
show Modifier, GeomModifier, GeomModifierOp, ToGeomModifierOpParams;
export 'src/geom/modifier/dodge.dart' show DodgeModifier;
export 'src/geom/modifier/stack.dart' show StackModifier;
export 'src/geom/modifier/jitter.dart' show JitterModifier;
Expand Down Expand Up @@ -201,7 +202,7 @@ export 'src/common/label.dart'
export 'src/common/defaults.dart' show Defaults;
export 'src/common/dim.dart' show Dim;

export 'src/dataflow/tuple.dart' show Tuple, Aes;
export 'src/dataflow/tuple.dart' show Tuple, Aes, AesGroups;

export 'src/util/path.dart' show Paths;
export 'package:path_drawing/path_drawing.dart' show DashOffset;
2 changes: 1 addition & 1 deletion lib/src/dataflow/tuple.dart
Expand Up @@ -66,7 +66,7 @@ class Aes {
final Label? label;

/// The size of the tuple.
final double? size;
double? size;

/// The represent point of [position] points.
Offset get representPoint => shape.representPoint(position);
Expand Down
13 changes: 12 additions & 1 deletion lib/src/geom/modifier/dodge.dart
@@ -1,9 +1,9 @@
import 'dart:ui';

import 'package:graphic/src/algebra/varset.dart';
import 'package:graphic/src/dataflow/tuple.dart';
import 'package:graphic/src/scale/discrete.dart';
import 'package:graphic/src/scale/scale.dart';
import 'package:graphic/src/algebra/varset.dart';

import 'modifier.dart';

Expand Down Expand Up @@ -34,6 +34,17 @@ class DodgeModifier extends Modifier {
super == other &&
ratio == other.ratio &&
symmetric == other.symmetric;

@override
DodgeGeomModifierOp toGeomModifierOp(ToGeomModifierOpParams params) {
return DodgeGeomModifierOp({
'ratio': ratio,
'symmetric': symmetric ?? true,
'form': params.form,
'scales': params.scales,
'groups': params.groups,
});
}
}

/// The dodge geometry modifier.
Expand Down
13 changes: 11 additions & 2 deletions lib/src/geom/modifier/jitter.dart
@@ -1,10 +1,10 @@
import 'dart:ui';
import 'dart:math';
import 'dart:ui';

import 'package:graphic/src/algebra/varset.dart';
import 'package:graphic/src/dataflow/tuple.dart';
import 'package:graphic/src/scale/discrete.dart';
import 'package:graphic/src/scale/scale.dart';
import 'package:graphic/src/algebra/varset.dart';

import 'modifier.dart';

Expand All @@ -26,6 +26,15 @@ class JitterModifier extends Modifier {
@override
bool operator ==(Object other) =>
other is JitterModifier && super == other && ratio == other.ratio;

@override
JitterGeomModifierOp toGeomModifierOp(ToGeomModifierOpParams params) {
return JitterGeomModifierOp({
'ratio': ratio ?? 0.5,
'form': params.form,
'scales': params.scales,
});
}
}

/// The jitter geometry modifier.
Expand Down
27 changes: 27 additions & 0 deletions lib/src/geom/modifier/modifier.dart
@@ -1,6 +1,12 @@
import 'package:graphic/graphic.dart';
import 'package:graphic/src/common/modifier.dart' as common;
import 'package:graphic/src/dataflow/operator.dart';
import 'package:graphic/src/dataflow/tuple.dart';
import 'package:graphic/src/scale/scale.dart';

import '../../aes/position.dart';
import '../../algebra/varset.dart';
import '../../coord/coord.dart';

/// The specification of a collision modifier.
///
Expand All @@ -9,6 +15,27 @@ import 'package:graphic/src/dataflow/tuple.dart';
abstract class Modifier {
@override
bool operator ==(Object other) => other is Modifier;

GeomModifierOp<GeomModifier> toGeomModifierOp(ToGeomModifierOpParams params);
}

/// The only use of this class to pass parameters to [Modifier.toGeomModifierOp].
/// A class is used, instead of named arguments, to avoid breaking changes when
/// adding new fields to these parameters.
class ToGeomModifierOpParams {
final AlgForm? form;
final ScaleConvOp scales;
final Operator<AesGroups> groups;
final OriginOp origin;
final CoordConvOp coord;

ToGeomModifierOpParams({
required this.form,
required this.scales,
required this.groups,
required this.origin,
required this.coord,
});
}

/// The base class of geometry modifiers.
Expand Down
7 changes: 7 additions & 0 deletions lib/src/geom/modifier/stack.dart
Expand Up @@ -16,6 +16,13 @@ import 'modifier.dart';
class StackModifier extends Modifier {
@override
bool operator ==(Object other) => other is StackModifier && super == other;

@override
StackGeomModifierOp toGeomModifierOp(ToGeomModifierOpParams params) {
return StackGeomModifierOp({
'origin': params.origin,
});
}
}

/// The stack geometry modifier.
Expand Down
9 changes: 8 additions & 1 deletion lib/src/geom/modifier/symmetric.dart
@@ -1,5 +1,5 @@
import 'dart:ui';
import 'dart:math';
import 'dart:ui';

import 'package:graphic/src/dataflow/tuple.dart';

Expand All @@ -15,6 +15,13 @@ class SymmetricModifier extends Modifier {
@override
bool operator ==(Object other) =>
other is SymmetricModifier && super == other;

@override
SymmetricGeomModifierOp toGeomModifierOp(ToGeomModifierOpParams params) {
return SymmetricGeomModifierOp({
'origin': params.origin,
});
}
}

/// The symmetric geometry modifier.
Expand Down

0 comments on commit ce76632

Please sign in to comment.