Created
March 8, 2022 07:35
-
-
Save dJani97/aed1dd8b2adc7ddac6447db2d56bd483 to your computer and use it in GitHub Desktop.
BorderedRectShape for the Flutter Graphic charting library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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