Flutter - 水平条形图堆叠在一个条形图中
Flutter - Horizontal Bar Chart stacked in one bar chart
我正在尝试实现这种堆叠在一个条形图中的水平条形图。我遇到了 fl_chart 包,但其中的 none 似乎具有我正在寻找的类型。如果有任何冠军可以支持我为我提供实现此目标的步骤,或者示例代码将非常有帮助。非常感谢您。
感谢@ChiragBargoojar 提供的代码,我只是添加了一些自定义项,图表按照我设计的方式工作。
如果还有人想知道,这里是代码:
class HorizontalBarChart extends StatelessWidget {
const HorizontalBarChart({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Map<String, dynamic>> chartData = [
{
"units": 50,
"color": cCoffee,
},
{
"units": 10,
"color": cCyan,
},
{
"units": 70,
"color": cGreen,
},
{
"units": 100,
"color": cOrange,
},
];
double maxWidth = MediaQuery.of(context).size.width - 36;
var totalUnitNum = 0;
for (int i = 0; i < chartData.length; i++) {
totalUnitNum = totalUnitNum + int.parse(chartData[i]["units"].toString());
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(90),
child: Row(
children: [
for (int i = 0; i < chartData.length; i++)
i == chartData.length - 1
? Expanded(
child: SizedBox(
height: 16,
child: ColoredBox(
color: chartData[i]["color"],
),
),
)
: Row(
children: [
SizedBox(
width:
chartData[i]["units"] / totalUnitNum * maxWidth,
height: 16,
child: ColoredBox(
color: chartData[i]["color"],
),
),
const SizedBox(width: 6),
],
)
],
),
),
);
}
}
List<int> acc = [500, 300, 400, 900, 800];
List<Color> col = [
Colors.red,
Colors.blue,
Colors.orange,
Colors.green,
Colors.pink
];
getSum() {
return acc.reduce((a, b) => a + b);
}
getAccAver(int index) {
return (acc[index] / getSum() * 100).toInt();
}
Padding(
padding: const EdgeInsets.all(5.0),
child: SizedBox(
height: 20,
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
for (var i = 0; i < acc.length; i++)
CardAccAve(
percentage: getAccAver(i),
leftBorder: i == 0 ? 10 : 0,
rightBorder: i == acc.length - 1 ? 10 : 0,
color: col[i],
),
],
),
),
),
class CardAccAve extends StatelessWidget {
CardAccAve({
Key? key,
required this.leftBorder,
required this.rightBorder,
required this.percentage,
required this.color,
}) : super(key: key);
double leftBorder;
double rightBorder;
final int percentage;
Color color;
@override
Widget build(BuildContext context) {
return Expanded(
flex: percentage,
child: SizedBox(
height: 20,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 1, vertical: 2),
color: color,
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(leftBorder),
topLeft: Radius.circular(leftBorder),
bottomRight: Radius.circular(rightBorder),
topRight: Radius.circular(rightBorder),
),
),
),
),
);
}
}
结果
您也可以使用 LinearGradient
来实现。
LinearGradient 采用 List<Color> colors
和 List<double> stops
。
为了具有清晰的颜色边界,您复制颜色并在边界处停止。
示例:
colors: [red, red, transparent, transparent, green, green]
stops: [0.0, 0.45, 0.45, 0.55, 0.55, 1]
完整代码示例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final chartData = [
Data(units: 15, color: const Color(0xFF8A5426)),
Data(units: 20, color: const Color(0xFF00BCD5)),
Data(units: 12, color: const Color(0xFF7B8700)),
Data(units: 10, color: const Color(0xFFDD8B11)),
Data(units: 50, color: const Color(0xFF673BB7)),
];
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SizedBox(
height: 20,
child: HorizontalBarChart(
data: chartData,
),
),
),
),
);
}
}
class HorizontalBarChart extends StatelessWidget {
final List<Data> data;
final double gap;
const HorizontalBarChart({
Key? key,
required this.data,
this.gap = .02,
}) : super(key: key);
List<double> get processedStops {
double totalGapsWith = gap * (data.length - 1);
double totalData = data.fold(0, (a, b) => a + b.units);
return data.fold(<double>[0.0], (List<double> l, d) {
l.add(l.last + d.units * (1 - totalGapsWith) / totalData);
l.add(l.last);
l.add(l.last + gap);
l.add(l.last);
return l;
})
..removeLast()
..removeLast()
..removeLast();
}
List<Color> get processedColors {
return data.fold(
<Color>[],
(List<Color> l, d) => [
...l,
d.color,
d.color,
Colors.transparent,
Colors.transparent,
])
..removeLast()
..removeLast();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(500),
),
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: processedStops,
colors: processedColors,
),
),
);
}
}
class Data {
final double units;
final Color color;
Data({required this.units, required this.color});
}
我正在尝试实现这种堆叠在一个条形图中的水平条形图。我遇到了 fl_chart 包,但其中的 none 似乎具有我正在寻找的类型。如果有任何冠军可以支持我为我提供实现此目标的步骤,或者示例代码将非常有帮助。非常感谢您。
感谢@ChiragBargoojar 提供的代码,我只是添加了一些自定义项,图表按照我设计的方式工作。
如果还有人想知道,这里是代码:
class HorizontalBarChart extends StatelessWidget {
const HorizontalBarChart({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Map<String, dynamic>> chartData = [
{
"units": 50,
"color": cCoffee,
},
{
"units": 10,
"color": cCyan,
},
{
"units": 70,
"color": cGreen,
},
{
"units": 100,
"color": cOrange,
},
];
double maxWidth = MediaQuery.of(context).size.width - 36;
var totalUnitNum = 0;
for (int i = 0; i < chartData.length; i++) {
totalUnitNum = totalUnitNum + int.parse(chartData[i]["units"].toString());
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(90),
child: Row(
children: [
for (int i = 0; i < chartData.length; i++)
i == chartData.length - 1
? Expanded(
child: SizedBox(
height: 16,
child: ColoredBox(
color: chartData[i]["color"],
),
),
)
: Row(
children: [
SizedBox(
width:
chartData[i]["units"] / totalUnitNum * maxWidth,
height: 16,
child: ColoredBox(
color: chartData[i]["color"],
),
),
const SizedBox(width: 6),
],
)
],
),
),
);
}
}
List<int> acc = [500, 300, 400, 900, 800];
List<Color> col = [
Colors.red,
Colors.blue,
Colors.orange,
Colors.green,
Colors.pink
];
getSum() {
return acc.reduce((a, b) => a + b);
}
getAccAver(int index) {
return (acc[index] / getSum() * 100).toInt();
}
Padding(
padding: const EdgeInsets.all(5.0),
child: SizedBox(
height: 20,
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
for (var i = 0; i < acc.length; i++)
CardAccAve(
percentage: getAccAver(i),
leftBorder: i == 0 ? 10 : 0,
rightBorder: i == acc.length - 1 ? 10 : 0,
color: col[i],
),
],
),
),
),
class CardAccAve extends StatelessWidget {
CardAccAve({
Key? key,
required this.leftBorder,
required this.rightBorder,
required this.percentage,
required this.color,
}) : super(key: key);
double leftBorder;
double rightBorder;
final int percentage;
Color color;
@override
Widget build(BuildContext context) {
return Expanded(
flex: percentage,
child: SizedBox(
height: 20,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 1, vertical: 2),
color: color,
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(leftBorder),
topLeft: Radius.circular(leftBorder),
bottomRight: Radius.circular(rightBorder),
topRight: Radius.circular(rightBorder),
),
),
),
),
);
}
}
结果
您也可以使用 LinearGradient
来实现。
LinearGradient 采用 List<Color> colors
和 List<double> stops
。
为了具有清晰的颜色边界,您复制颜色并在边界处停止。
示例:
colors: [red, red, transparent, transparent, green, green]
stops: [0.0, 0.45, 0.45, 0.55, 0.55, 1]
完整代码示例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final chartData = [
Data(units: 15, color: const Color(0xFF8A5426)),
Data(units: 20, color: const Color(0xFF00BCD5)),
Data(units: 12, color: const Color(0xFF7B8700)),
Data(units: 10, color: const Color(0xFFDD8B11)),
Data(units: 50, color: const Color(0xFF673BB7)),
];
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SizedBox(
height: 20,
child: HorizontalBarChart(
data: chartData,
),
),
),
),
);
}
}
class HorizontalBarChart extends StatelessWidget {
final List<Data> data;
final double gap;
const HorizontalBarChart({
Key? key,
required this.data,
this.gap = .02,
}) : super(key: key);
List<double> get processedStops {
double totalGapsWith = gap * (data.length - 1);
double totalData = data.fold(0, (a, b) => a + b.units);
return data.fold(<double>[0.0], (List<double> l, d) {
l.add(l.last + d.units * (1 - totalGapsWith) / totalData);
l.add(l.last);
l.add(l.last + gap);
l.add(l.last);
return l;
})
..removeLast()
..removeLast()
..removeLast();
}
List<Color> get processedColors {
return data.fold(
<Color>[],
(List<Color> l, d) => [
...l,
d.color,
d.color,
Colors.transparent,
Colors.transparent,
])
..removeLast()
..removeLast();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(500),
),
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: processedStops,
colors: processedColors,
),
),
);
}
}
class Data {
final double units;
final Color color;
Data({required this.units, required this.color});
}