Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions lib/src/chart/bar_chart/bar_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,92 @@ class BarChartGroupData with EquatableMixin {
];
}

class BarChartRodBadge with EquatableMixin {
const BarChartRodBadge({
required this.text,
this.color = const Color(0xFF006837),
this.textStyle = const TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 11,
fontWeight: FontWeight.bold,
),
this.radius = 12,
this.margin = 4,
this.points = 12,
this.innerRadiusRatio = 0.8,
this.scale = 1,
});

final String text;

final Color color;

final TextStyle textStyle;

final double radius;

final double margin;

final int points;

final double innerRadiusRatio;

final double scale;

BarChartRodBadge copyWith({
String? text,
Color? color,
TextStyle? textStyle,
double? radius,
double? margin,
int? points,
double? innerRadiusRatio,
double? scale,
}) =>
BarChartRodBadge(
text: text ?? this.text,
color: color ?? this.color,
textStyle: textStyle ?? this.textStyle,
radius: radius ?? this.radius,
margin: margin ?? this.margin,
points: points ?? this.points,
innerRadiusRatio: innerRadiusRatio ?? this.innerRadiusRatio,
scale: scale ?? this.scale,
);

static BarChartRodBadge? lerp(
BarChartRodBadge? a,
BarChartRodBadge? b,
double t,
) {
if (a == null || b == null) {
return b;
}
return BarChartRodBadge(
text: b.text,
color: Color.lerp(a.color, b.color, t)!,
textStyle: TextStyle.lerp(a.textStyle, b.textStyle, t)!,
radius: lerpDouble(a.radius, b.radius, t)!,
margin: lerpDouble(a.margin, b.margin, t)!,
points: b.points,
innerRadiusRatio: lerpDouble(a.innerRadiusRatio, b.innerRadiusRatio, t)!,
scale: lerpDouble(a.scale, b.scale, t)!,
);
}

@override
List<Object?> get props => [
text,
color,
textStyle,
radius,
margin,
points,
innerRadiusRatio,
scale,
];
}

/// Holds data about rendering each rod (or bar) in the [BarChart].
class BarChartRodData with EquatableMixin {
/// [BarChart] renders rods vertically from zero to [toY],
Expand Down Expand Up @@ -341,6 +427,7 @@ class BarChartRodData with EquatableMixin {
BackgroundBarChartRodData? backDrawRodData,
List<BarChartRodStackItem>? rodStackItems,
this.label = const BarChartRodLabel(show: false),
this.badge,
}) : fromY = fromY ?? 0,
color =
color ?? ((color == null && gradient == null) ? Colors.cyan : null),
Expand Down Expand Up @@ -399,6 +486,8 @@ class BarChartRodData with EquatableMixin {
/// Optional label to display near the rod tip.
final BarChartRodLabel label;

final BarChartRodBadge? badge;

/// Determines the upward or downward direction
bool isUpward() => toY >= fromY;

Expand All @@ -417,6 +506,7 @@ class BarChartRodData with EquatableMixin {
BackgroundBarChartRodData? backDrawRodData,
List<BarChartRodStackItem>? rodStackItems,
BarChartRodLabel? label,
BarChartRodBadge? badge,
}) =>
BarChartRodData(
fromY: fromY ?? this.fromY,
Expand All @@ -431,6 +521,7 @@ class BarChartRodData with EquatableMixin {
backDrawRodData: backDrawRodData ?? this.backDrawRodData,
rodStackItems: rodStackItems ?? this.rodStackItems,
label: label ?? this.label,
badge: badge ?? this.badge,
);

/// Lerps a [BarChartRodData] based on [t] value, check [Tween.lerp].
Expand All @@ -453,6 +544,7 @@ class BarChartRodData with EquatableMixin {
rodStackItems:
lerpBarChartRodStackList(a.rodStackItems, b.rodStackItems, t),
label: BarChartRodLabel.lerpBarChartRodLabel(a.label, b.label, t),
badge: BarChartRodBadge.lerp(a.badge, b.badge, t),
);

/// Used for equality check, see [EquatableMixin].
Expand All @@ -470,6 +562,7 @@ class BarChartRodData with EquatableMixin {
color,
gradient,
label,
badge,
];
}

Expand Down
84 changes: 84 additions & 0 deletions lib/src/chart/bar_chart/bar_chart_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class BarChartPainter extends AxisChartPainter<BarChartData> {

drawBars(canvasWrapper, _groupBarsPosition!, holder);
drawBarLabels(context, canvasWrapper, _groupBarsPosition!, holder);
drawRodBadges(canvasWrapper, _groupBarsPosition!, holder);

drawErrorIndicatorData(canvasWrapper, _groupBarsPosition!, holder);

Expand Down Expand Up @@ -416,6 +417,89 @@ class BarChartPainter extends AxisChartPainter<BarChartData> {
}
}

@visibleForTesting
void drawRodBadges(
CanvasWrapper canvasWrapper,
List<GroupBarsPosition> groupBarsPosition,
PaintHolder<BarChartData> holder,
) {
final data = holder.data;
final viewSize = canvasWrapper.size;

for (var i = 0; i < data.barGroups.length; i++) {
final barGroup = data.barGroups[i];
for (var j = 0; j < barGroup.barRods.length; j++) {
final barRod = barGroup.barRods[j];
final badge = barRod.badge;
if (badge == null || barRod.toY == barRod.fromY) {
continue;
}

final x = groupBarsPosition[i].barsX[j];
final tipY = getPixelY(barRod.toY, viewSize, holder);
final center = Offset(x, tipY - badge.margin - badge.radius);
_drawSealBadge(canvasWrapper, center, badge, holder);
}
}
}

void _drawSealBadge(
CanvasWrapper canvasWrapper,
Offset center,
BarChartRodBadge badge,
PaintHolder<BarChartData> holder,
) {
final scale = badge.scale <= 0 ? 0.0 : badge.scale;
if (scale <= 0.01) {
return;
}
final opacity = scale > 1 ? 1.0 : scale;

final radius = badge.radius * scale;
final innerRadius = radius * badge.innerRadiusRatio;
final angleStep = pi / badge.points;
final path = Path();
for (var k = 0; k < badge.points * 2; k++) {
final r = k.isEven ? radius : innerRadius;
final angle = k * angleStep - pi / 2;
final px = center.dx + r * cos(angle);
final py = center.dy + r * sin(angle);
if (k == 0) {
path.moveTo(px, py);
} else {
path.lineTo(px, py);
}
}
path.close();

canvasWrapper.drawPath(
path,
Paint()
..color = badge.color.withValues(alpha: badge.color.a * opacity)
..style = PaintingStyle.fill,
);

final baseStyle = badge.textStyle;
final baseColor = baseStyle.color ?? const Color(0xFFFFFFFF);
final textStyle = baseStyle.copyWith(
fontSize: (baseStyle.fontSize ?? 11) * scale,
color: baseColor.withValues(alpha: baseColor.a * opacity),
);
final textPainter = TextPainter(
text: TextSpan(text: badge.text, style: textStyle),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
textScaler: holder.textScaler,
)..layout();
textPainter.paint(
canvasWrapper.canvas,
Offset(
center.dx - textPainter.width / 2,
center.dy - textPainter.height / 2,
),
);
}

@visibleForTesting
void drawBarLabels(
BuildContext context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ class _AxisChartScaffoldWidgetState extends State<AxisChartScaffoldWidget> {
? widget.data.borderData.border
: null;

final borderRadius = widget.data.borderData.isVisible()
? widget.data.borderData.borderRadius
: null;

final borderWidth =
borderData == null ? 0 : borderData.dimensions.horizontal;
final borderHeight =
Expand Down Expand Up @@ -251,7 +255,8 @@ class _AxisChartScaffoldWidgetState extends State<AxisChartScaffoldWidget> {
final widgets = <Widget>[
Container(
margin: margin,
decoration: BoxDecoration(border: borderData),
decoration:
BoxDecoration(border: borderData, borderRadius: borderRadius),
child: child,
),
];
Expand Down
6 changes: 6 additions & 0 deletions lib/src/chart/base/base_chart/base_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ class FlBorderData with EquatableMixin {
FlBorderData({
bool? show,
Border? border,
this.borderRadius,
}) : show = show ?? true,
border = border ?? Border.all();
final bool show;
final Border border;
final BorderRadius? borderRadius;

/// returns false if all borders have 0 width or 0 opacity
bool isVisible() => show && border.isVisible();
Expand All @@ -51,24 +53,28 @@ class FlBorderData with EquatableMixin {
FlBorderData(
show: b.show,
border: Border.lerp(a.border, b.border, t),
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
);

/// Copies current [FlBorderData] to a new [FlBorderData],
/// and replaces provided values.
FlBorderData copyWith({
bool? show,
Border? border,
BorderRadius? borderRadius,
}) =>
FlBorderData(
show: show ?? this.show,
border: border ?? this.border,
borderRadius: borderRadius ?? this.borderRadius,
);

/// Used for equality check, see [EquatableMixin].
@override
List<Object?> get props => [
show,
border,
borderRadius,
];
}

Expand Down
Loading