Skip to content

Instantly share code, notes, and snippets.

@dJani97
Created March 8, 2022 07:35
Show Gist options
  • Save dJani97/aed1dd8b2adc7ddac6447db2d56bd483 to your computer and use it in GitHub Desktop.
Save dJani97/aed1dd8b2adc7ddac6447db2d56bd483 to your computer and use it in GitHub Desktop.
BorderedRectShape for the Flutter Graphic charting library
// ignore_for_file: implementation_imports
import 'package:flutter/material.dart';
import 'package:graphic/graphic.dart';
import 'package:graphic/src/guide/axis/radial.dart';
import 'package:graphic/src/util/math.dart';
/// A rectangle or sector shape with border.
class BorderedRectShape extends RectShape {
final double borderWidth;
final Color borderColor;
/// Creates a rectangle shape with border.
BorderedRectShape({
histogram = false,
labelPosition = 1.0,
borderRadius,
this.borderWidth = 1.0,
this.borderColor = Colors.black,
}) : super(histogram: histogram, labelPosition: labelPosition, borderRadius: borderRadius);
@override
List<Figure> renderGroup(
List<Aes> group,
CoordConv coord,
Offset origin,
) {
final rst = <Figure>[];
if (coord is RectCoordConv) {
if (histogram) {
// Histogram shape dosen't allow NaN value.
// First item.
Aes item = group.first;
List<Offset> position = item.position;
double bandStart = 0;
double bandEnd = (group[1].position.first.dx + position.first.dx) / 2;
rst.addAll(_renderRect(
item,
Rect.fromPoints(
coord.convert(Offset(bandStart, position[1].dy)),
coord.convert(Offset(bandEnd, position[0].dy)),
),
coord.convert(position[0] + (position[1] - position[0]) * labelPosition),
coord,
));
// Middle items.
for (var i = 1; i < group.length - 1; i++) {
item = group[i];
position = item.position;
bandStart = (group[i].position.first.dx + group[i - 1].position.first.dx) / 2;
bandEnd = (group[i + 1].position.first.dx + group[i].position.first.dx) / 2;
rst.addAll(_renderRect(
item,
Rect.fromPoints(
coord.convert(Offset(bandStart, position[1].dy)),
coord.convert(Offset(bandEnd, position[0].dy)),
),
coord.convert(position[0] + (position[1] - position[0]) * labelPosition),
coord,
));
}
// Last item.
item = group.last;
position = item.position;
bandStart = (position.first.dx + group[group.length - 2].position.first.dx) / 2;
bandEnd = 1;
rst.addAll(_renderRect(
item,
Rect.fromPoints(
coord.convert(Offset(bandStart, position[1].dy)),
coord.convert(Offset(bandEnd, position[0].dy)),
),
coord.convert(position[0] + (position[1] - position[0]) * labelPosition),
coord,
));
} else {
// Bar.
for (var item in group) {
bool nan = false;
for (var point in item.position) {
if (!point.dy.isFinite) {
nan = true;
break;
}
}
if (nan) {
continue;
}
final start = coord.convert(item.position[0]);
final end = coord.convert(item.position[1]);
final size = item.size ?? defaultSize;
Rect rect;
if (coord.transposed) {
rect = Rect.fromLTRB(
start.dx,
start.dy - size / 2,
end.dx,
start.dy + size / 2,
);
} else {
rect = Rect.fromLTRB(
end.dx - size / 2,
end.dy,
end.dx + size / 2,
start.dy,
);
}
rst.addAll(_renderRect(
item,
rect,
start + (end - start) * labelPosition,
coord,
));
}
}
} else if (coord is PolarCoordConv) {
// All sector interval shapes dosen't allow NaN value.
if (coord.transposed) {
if (coord.dimCount == 1) {
// Pie.
for (var item in group) {
final position = item.position;
rst.addAll(_renderSector(
item,
coord.radiuses.last,
coord.radiuses.first,
coord.convertAngle(position[0].dy),
coord.convertAngle(position[1].dy),
true,
coord.convert(Offset(
labelPosition,
(position[1].dy + position[0].dy) / 2,
)),
coord,
));
}
} else {
// Race track.
for (var item in group) {
final position = item.position;
final r = coord.convertRadius(position[0].dx);
final halfSize = (item.size ?? defaultSize) / 2;
rst.addAll(_renderSector(
item,
r - halfSize,
r + halfSize,
coord.convertAngle(position[0].dy),
coord.convertAngle(position[1].dy),
false,
coord.convert(Offset(
labelPosition,
(position[1].dy - position[0].dy) / 2,
)),
coord,
));
if (item.label != null && item.label!.haveText) {
final labelAnchor = coord.convert(position[0] + (position[1] - position[0]) * labelPosition);
final anchorOffset = labelAnchor - coord.center;
rst.add(renderLabel(
item.label!,
labelAnchor,
radialLabelAlign(anchorOffset) * -1,
));
}
}
}
} else {
if (coord.dimCount == 1) {
// Bull eye.
for (var item in group) {
rst.addAll(_renderSector(
item,
coord.convertRadius(item.position[1].dy),
coord.convertRadius(item.position[0].dy),
coord.angles.first,
coord.angles.last,
true,
coord.convert(item.position[0] + (item.position[1] - item.position[0]) * labelPosition),
coord,
));
}
} else {
// Rose.
// First item.
Aes item = group.first;
List<Offset> position = group.first.position;
double bandStart = 0;
double bandEnd = (group[1].position.first.dx + position.first.dx) / 2;
rst.addAll(_renderSector(
item,
coord.convertRadius(position[1].dy),
coord.convertRadius(position[0].dy),
coord.convertAngle(bandStart),
coord.convertAngle(bandEnd),
true,
coord.convert(position[0] + (position[1] - position[0]) * labelPosition),
coord,
));
// Middle items.
for (var i = 1; i < group.length - 1; i++) {
item = group[i];
position = item.position;
bandStart = (group[i].position.first.dx + group[i - 1].position.first.dx) / 2;
bandEnd = (group[i + 1].position.first.dx + group[i].position.first.dx) / 2;
rst.addAll(_renderSector(
item,
coord.convertRadius(position[1].dy),
coord.convertRadius(position[0].dy),
coord.convertAngle(bandStart),
coord.convertAngle(bandEnd),
true,
coord.convert(position[0] + (position[1] - position[0]) * labelPosition),
coord,
));
}
// Last item.
item = group.last;
position = item.position;
bandStart = (position.first.dx + group[group.length - 2].position.first.dx) / 2;
bandEnd = 1;
rst.addAll(_renderSector(
item,
coord.convertRadius(position[1].dy),
coord.convertRadius(position[0].dy),
coord.convertAngle(bandStart),
coord.convertAngle(bandEnd),
true,
coord.convert(position[0] + (position[1] - position[0]) * labelPosition),
coord,
));
}
}
}
return rst;
}
/// Renders a rectangle interval item.
///
/// It relaced [renderItem].
List<Figure> _renderRect(
Aes item,
Rect rect,
Offset labelAnchor,
CoordConv coord,
) {
assert(item.shape is BorderedRectShape);
final path = Path();
if (borderRadius != null) {
path.addRRect(RRect.fromRectAndCorners(
rect,
topLeft: borderRadius!.topLeft,
topRight: borderRadius!.topRight,
bottomRight: borderRadius!.bottomRight,
bottomLeft: borderRadius!.bottomLeft,
));
} else {
path.addRect(rect);
}
final rst = <Figure>[];
rst.addAll(renderBasicItem(
path,
item,
false,
0,
coord.region,
));
if (item.label != null) {
rst.add(renderLabel(
item.label!,
labelAnchor,
labelPosition.equalTo(1) ? (coord.transposed ? Alignment.centerRight : Alignment.topCenter) : Alignment.center,
));
}
return rst;
}
/// Renders a sector interval item.
///
/// It relaced [renderItem].
List<Figure> _renderSector(
Aes item,
double r,
double r0,
double startAngle,
double endAngle,
bool haveLabel,
Offset labelAnchor,
PolarCoordConv coord,
) {
assert(item.shape is BorderedRectShape);
Path path;
if (borderRadius != null) {
path = Paths.rsector(
center: coord.center,
r: r,
r0: r0,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true,
topLeft: borderRadius!.topLeft,
topRight: borderRadius!.topRight,
bottomRight: borderRadius!.bottomRight,
bottomLeft: borderRadius!.bottomLeft,
);
} else {
path = Paths.sector(
center: coord.center,
r: r,
r0: r0,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true,
);
}
final rst = <Figure>[];
rst.addAll(renderBasicItem(
path,
item,
false,
0,
coord.region,
));
rst.addAll(renderBasicItem(
path,
item.copyWith(color: borderColor),
true,
borderWidth,
coord.region,
));
if (haveLabel && item.label != null) {
Alignment defaultAlign;
if (labelPosition == 1) {
// Calculate default alignment according to anchor's quadrant.
final anchorOffset = labelAnchor - coord.center;
defaultAlign = Alignment(
anchorOffset.dx.equalTo(0) ? 0 : anchorOffset.dx / anchorOffset.dx.abs(),
anchorOffset.dy.equalTo(0) ? 0 : anchorOffset.dy / anchorOffset.dy.abs(),
);
} else {
defaultAlign = Alignment.center;
}
rst.add(renderLabel(
item.label!,
labelAnchor,
defaultAlign,
));
}
return rst;
}
}
extension AesCopyWith on Aes {
Aes copyWith({Color? color}) {
return Aes(
index: index,
position: position,
shape: shape,
color: color ?? this.color,
gradient: gradient,
elevation: elevation,
label: label,
size: size,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment