Created
November 19, 2023 06:54
-
-
Save m8811163008/1346d2a9dfae983395256a6dba271b8b to your computer and use it in GitHub Desktop.
Create a pie chart with rounded ends
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
import 'package:component_library/component_library.dart'; | |
import 'package:domain_models/domain_models.dart'; | |
import 'package:flutter/material.dart'; | |
class AppPieChart extends StatefulWidget { | |
const AppPieChart({ | |
super.key, | |
required this.subAccount, | |
}); | |
final SubAccount subAccount; | |
@override | |
State<StatefulWidget> createState() => AppPieChartState(); | |
} | |
class AppPieChartState extends State<AppPieChart> { | |
@override | |
Widget build(BuildContext context) { | |
final l10n = ComponentLibraryLocalizations.of(context); | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
Expanded( | |
flex: 2, | |
child: Align( | |
alignment: AlignmentDirectional.centerStart, | |
child: Stack( | |
alignment: Alignment.center, | |
children: [ | |
AppPieChartCircleShape( | |
onlineDevice: | |
widget.subAccount.subaccountDashboard.onlineCount, | |
offlineDevice: | |
widget.subAccount.subaccountDashboard.offlineCount, | |
), | |
Text( | |
l10n.appPieChartTitle( | |
widget.subAccount.subaccountDashboard.workersCount, | |
), | |
style: context.textThemeExtension.mobileBold16, | |
), | |
], | |
), | |
), | |
), | |
Expanded( | |
flex: 1, | |
child: Padding( | |
padding: const EdgeInsets.only(right: 16.0), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.end, | |
children: [ | |
const Spacer(), | |
ChartIndicator( | |
color: context.colorThemeExtension.lightBlue!, | |
title: l10n.appPieChartIndicatorAll, | |
value: widget.subAccount.subaccountDashboard.workersCount), | |
Spaces.smallVerticalSizedBox, | |
ChartIndicator( | |
color: context.colorThemeExtension.green!, | |
title: l10n.appPieChartIndicatorOnline, | |
value: widget.subAccount.subaccountDashboard.onlineCount), | |
Spaces.smallVerticalSizedBox, | |
ChartIndicator( | |
color: context.colorThemeExtension.darkOrange!, | |
title: l10n.appPieChartIndicatorOffline, | |
value: widget.subAccount.subaccountDashboard.offlineCount), | |
const Spacer(), | |
MoreInfoTextButton( | |
onPressed: DashboardMoreInfoButtonsNavigation.of(context) | |
.onPieChartMoreInfoPreesed, | |
), | |
], | |
), | |
), | |
) | |
], | |
); | |
} | |
} | |
/// Create custome painter | |
import 'dart:math'; | |
import 'dart:ui'; | |
import 'package:component_library/component_library.dart'; | |
import 'package:flutter/material.dart'; | |
class AppPieChartCircleShape extends StatelessWidget { | |
const AppPieChartCircleShape( | |
{super.key, required this.onlineDevice, required this.offlineDevice}); | |
final int onlineDevice; | |
final int offlineDevice; | |
@override | |
Widget build(BuildContext context) { | |
int allDevice = onlineDevice + offlineDevice; | |
if (allDevice == 0) { | |
allDevice = 1; | |
} | |
final offlineShare = offlineDevice / allDevice; | |
const startAngle = 3 * pi / 2; | |
final space = (onlineDevice == 0 || offlineDevice == 0) ? 0 : pi / 18; | |
final offlineSwip = offlineShare * 2 * pi; | |
final onlineStartAngle = startAngle + offlineShare * 2 * pi; | |
final onlineSwip = 2 * pi - offlineSwip; | |
final pieSize = Size.fromRadius(90); | |
return Stack( | |
alignment: Alignment.center, | |
children: [ | |
if (offlineDevice != 0) | |
CustomPaint( | |
size: pieSize, | |
painter: CurvePainter( | |
startAngle: startAngle, | |
swipAngle: offlineSwip - space, | |
isSolid: true, | |
offlineColor: context.colorThemeExtension.darkOrange!, | |
gradient: ParsePoolThemeData.hashrateChartLinearGradient, | |
), | |
), | |
if (onlineDevice != 0 || (onlineDevice == 0 && offlineDevice == 0)) | |
CustomPaint( | |
size: pieSize, | |
painter: CurvePainter( | |
startAngle: onlineStartAngle, | |
swipAngle: onlineSwip - space, | |
offlineColor: context.colorThemeExtension.darkOrange!, | |
gradient: ParsePoolThemeData.hashrateChartLinearGradient, | |
), | |
), | |
ImageFiltered( | |
imageFilter: ImageFilter.blur(sigmaX: 4.0, sigmaY: 4.0), | |
child: Stack( | |
children: [ | |
if (offlineDevice != 0) | |
CustomPaint( | |
size: pieSize, | |
painter: CurvePainter( | |
startAngle: startAngle, | |
swipAngle: offlineSwip - space, | |
isSolid: true, | |
offlineColor: context.colorThemeExtension.darkOrange!, | |
gradient: ParsePoolThemeData.hashrateChartLinearGradient, | |
), | |
), | |
if (onlineDevice != 0 || | |
(onlineDevice == 0 && offlineDevice == 0)) | |
CustomPaint( | |
size: pieSize, | |
painter: CurvePainter( | |
startAngle: onlineStartAngle, | |
swipAngle: onlineSwip - space, | |
offlineColor: context.colorThemeExtension.darkOrange!, | |
gradient: ParsePoolThemeData.hashrateChartLinearGradient, | |
), | |
), | |
], | |
), | |
), | |
], | |
); | |
} | |
} | |
class CurvePainter extends CustomPainter { | |
const CurvePainter({ | |
this.startAngle = pi / 5, | |
this.swipAngle = pi / 6, | |
this.arcRadius = 180, | |
this.innerLength = 10, | |
this.isSolid = false, | |
required this.offlineColor, | |
required this.gradient, | |
this.stroke = 5.0, | |
}); | |
final double startAngle; | |
final double swipAngle; | |
final double arcRadius; | |
final double innerLength; | |
final bool isSolid; | |
final Color offlineColor; | |
final LinearGradient gradient; | |
final double stroke; | |
@override | |
void paint(Canvas canvas, Size size) { | |
Paint paint = Paint(); | |
Path path = Path(); | |
final rect = Rect.fromCenter( | |
center: Offset(size.width / 2, size.height / 2), | |
width: arcRadius, | |
height: arcRadius, | |
); | |
paint.strokeWidth = stroke; | |
paint.style = PaintingStyle.stroke; | |
if (isSolid) { | |
paint.color = offlineColor; | |
} else { | |
paint.shader = gradient.createShader(rect); | |
} | |
// blurPaint.maskFilter = | |
// MaskFilter.blur(BlurStyle.normal, convertRadiusToSigma(5)); | |
final center = Offset(size.width / 2, size.height / 2); | |
final startPoint = _calculateStartPoint( | |
center: center, radius: arcRadius / 2, angle: startAngle); | |
path.moveTo(startPoint.dx, startPoint.dy); | |
path.relativeArcToPoint( | |
_calculateRelativeArcPoint( | |
angle: startAngle, | |
isStart: true, | |
lineLenght: innerLength / 2, | |
), | |
radius: const Radius.circular(2.0), | |
clockwise: false, | |
); | |
path.addArc(rect, startAngle, swipAngle); | |
path.relativeArcToPoint( | |
_calculateRelativeArcPoint( | |
angle: startAngle + swipAngle, lineLenght: innerLength / 2), | |
radius: const Radius.circular(2.0)); | |
path.addArc( | |
Rect.fromCenter( | |
center: Offset(size.width / 2, size.height / 2), | |
width: arcRadius - innerLength, | |
height: arcRadius - innerLength), | |
startAngle, | |
swipAngle); | |
canvas.drawPath(path, paint); | |
// canvas.drawShadow(path, gradient.colors.first, 0.5, true); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return oldDelegate != this; | |
} | |
double convertRadiusToSigma(double radius) { | |
return radius * 0.57735 + 0.5; | |
} | |
Offset _calculateRelativeArcPoint( | |
{required double angle, double lineLenght = 5, bool isStart = false}) { | |
int kharejGhesmat = angle ~/ (2 * pi); | |
angle = angle - kharejGhesmat * (2 * pi); | |
late final double x; | |
late final double y; | |
if (angle >= 0 && angle <= pi / 2) { | |
x = isStart ? -lineLenght * cos(angle) : -lineLenght * cos(angle); | |
y = isStart ? -lineLenght * sin(angle) : -lineLenght * sin(angle); | |
} else if (angle > pi / 2 && angle < pi) { | |
x = lineLenght * sin(angle - pi / 2); | |
y = -lineLenght * cos(angle - pi / 2); | |
} else if (angle >= pi && angle <= 3 * pi / 2) { | |
x = lineLenght * cos(angle - pi); | |
y = lineLenght * sin(angle - pi); | |
} else if (angle > 3 * pi / 2 && angle < 2 * pi) { | |
x = -lineLenght * sin(angle - 3 * pi / 2); | |
y = lineLenght * cos(angle - 3 * pi / 2); | |
} else { | |
throw Exception('degree is not valid'); | |
} | |
return Offset(x, y); | |
} | |
Offset _calculateStartPoint( | |
{required Offset center, required double radius, required double angle}) { | |
final double x = radius * cos(angle); | |
final double y = radius * sin(angle); | |
return Offset(x, y) + center; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment