flet-charts 0.2.0.dev13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flet-charts might be problematic. Click here for more details.
- flet_charts/__init__.py +35 -0
- flet_charts/bar_chart.py +237 -0
- flet_charts/bar_chart_group.py +33 -0
- flet_charts/bar_chart_rod.py +111 -0
- flet_charts/bar_chart_rod_stack_item.py +31 -0
- flet_charts/chart_axis.py +64 -0
- flet_charts/line_chart.py +251 -0
- flet_charts/line_chart_data.py +140 -0
- flet_charts/line_chart_data_point.py +85 -0
- flet_charts/matplotlib_chart.py +64 -0
- flet_charts/pie_chart.py +89 -0
- flet_charts/pie_chart_section.py +89 -0
- flet_charts/plotly_chart.py +56 -0
- flet_charts/scatter_chart.py +218 -0
- flet_charts/scatter_chart_spot.py +102 -0
- flet_charts/types.py +288 -0
- flet_charts-0.2.0.dev13.dist-info/METADATA +69 -0
- flet_charts-0.2.0.dev13.dist-info/RECORD +38 -0
- flet_charts-0.2.0.dev13.dist-info/WHEEL +5 -0
- flet_charts-0.2.0.dev13.dist-info/licenses/LICENSE +201 -0
- flet_charts-0.2.0.dev13.dist-info/top_level.txt +2 -0
- flutter/flet_charts/CHANGELOG.md +3 -0
- flutter/flet_charts/LICENSE +201 -0
- flutter/flet_charts/README.md +3 -0
- flutter/flet_charts/analysis_options.yaml +5 -0
- flutter/flet_charts/lib/flet_charts.dart +3 -0
- flutter/flet_charts/lib/src/bar_chart.dart +95 -0
- flutter/flet_charts/lib/src/extension.dart +25 -0
- flutter/flet_charts/lib/src/line_chart.dart +236 -0
- flutter/flet_charts/lib/src/pie_chart.dart +71 -0
- flutter/flet_charts/lib/src/scatter_chart.dart +140 -0
- flutter/flet_charts/lib/src/utils/bar_chart.dart +177 -0
- flutter/flet_charts/lib/src/utils/charts.dart +173 -0
- flutter/flet_charts/lib/src/utils/line_chart.dart +208 -0
- flutter/flet_charts/lib/src/utils/pie_chart.dart +56 -0
- flutter/flet_charts/lib/src/utils/scatter_chart.dart +85 -0
- flutter/flet_charts/pubspec.lock +776 -0
- flutter/flet_charts/pubspec.yaml +24 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import 'package:fl_chart/fl_chart.dart';
|
|
2
|
+
import 'package:flet/flet.dart';
|
|
3
|
+
import 'package:flutter/material.dart';
|
|
4
|
+
|
|
5
|
+
import 'utils/charts.dart';
|
|
6
|
+
import 'utils/scatter_chart.dart';
|
|
7
|
+
|
|
8
|
+
class ScatterChartControl extends StatefulWidget {
|
|
9
|
+
final Control control;
|
|
10
|
+
|
|
11
|
+
ScatterChartControl({Key? key, required this.control})
|
|
12
|
+
: super(key: ValueKey("control_${control.id}"));
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
State<ScatterChartControl> createState() => _ScatterChartControlState();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class _ScatterChartControlState extends State<ScatterChartControl> {
|
|
19
|
+
@override
|
|
20
|
+
Widget build(BuildContext context) {
|
|
21
|
+
final theme = Theme.of(context);
|
|
22
|
+
var animation = widget.control.getAnimation(
|
|
23
|
+
"animation",
|
|
24
|
+
ImplicitAnimationDetails(
|
|
25
|
+
duration: const Duration(milliseconds: 150),
|
|
26
|
+
curve: Curves.linear))!;
|
|
27
|
+
var border = widget.control.getBorder("border", theme);
|
|
28
|
+
|
|
29
|
+
var leftTitles = parseAxisTitles(widget.control.child("left_axis"));
|
|
30
|
+
var topTitles = parseAxisTitles(widget.control.child("top_axis"));
|
|
31
|
+
var rightTitles = parseAxisTitles(widget.control.child("right_axis"));
|
|
32
|
+
var bottomTitles = parseAxisTitles(widget.control.child("bottom_axis"));
|
|
33
|
+
|
|
34
|
+
var interactive = widget.control.getBool("interactive", true)!;
|
|
35
|
+
|
|
36
|
+
// Build list of ScatterSpotData
|
|
37
|
+
final spots = widget.control.children('spots').map((spot) {
|
|
38
|
+
var x = spot.getDouble('x', 0)!;
|
|
39
|
+
var y = spot.getDouble('y', 0)!;
|
|
40
|
+
return ScatterSpot(x, y,
|
|
41
|
+
show: spot.getBool('visible', true)!,
|
|
42
|
+
renderPriority: spot.getInt('render_priority', 0)!,
|
|
43
|
+
xError: spot.get('x_error'),
|
|
44
|
+
yError: spot.get('y_error'),
|
|
45
|
+
dotPainter: spot.get("point") != null
|
|
46
|
+
? parseChartDotPainter(spot.get("point"), theme, 0, null, null)
|
|
47
|
+
: FlDotCirclePainter(
|
|
48
|
+
radius: spot.getDouble("radius"),
|
|
49
|
+
color: spot.getColor(
|
|
50
|
+
"color",
|
|
51
|
+
context,
|
|
52
|
+
Colors.primaries[
|
|
53
|
+
((x * y) % Colors.primaries.length).toInt()])!,
|
|
54
|
+
));
|
|
55
|
+
}).toList();
|
|
56
|
+
|
|
57
|
+
final chart = ScatterChart(
|
|
58
|
+
ScatterChartData(
|
|
59
|
+
scatterSpots: spots,
|
|
60
|
+
backgroundColor: widget.control.getColor("bgcolor", context),
|
|
61
|
+
minX: widget.control.getDouble("min_x"),
|
|
62
|
+
maxX: widget.control.getDouble("max_x"),
|
|
63
|
+
minY: widget.control.getDouble("min_y"),
|
|
64
|
+
maxY: widget.control.getDouble("max_y"),
|
|
65
|
+
baselineX: widget.control.getDouble("baseline_x"),
|
|
66
|
+
baselineY: widget.control.getDouble("baseline_y"),
|
|
67
|
+
titlesData: FlTitlesData(
|
|
68
|
+
show: (leftTitles.sideTitles.showTitles ||
|
|
69
|
+
topTitles.sideTitles.showTitles ||
|
|
70
|
+
rightTitles.sideTitles.showTitles ||
|
|
71
|
+
bottomTitles.sideTitles.showTitles),
|
|
72
|
+
leftTitles: leftTitles,
|
|
73
|
+
topTitles: topTitles,
|
|
74
|
+
rightTitles: rightTitles,
|
|
75
|
+
bottomTitles: bottomTitles,
|
|
76
|
+
),
|
|
77
|
+
borderData: FlBorderData(show: border != null, border: border),
|
|
78
|
+
gridData: parseChartGridData(
|
|
79
|
+
widget.control.get("horizontal_grid_lines"),
|
|
80
|
+
widget.control.get("vertical_grid_lines"),
|
|
81
|
+
theme),
|
|
82
|
+
scatterTouchData: ScatterTouchData(
|
|
83
|
+
enabled: interactive,
|
|
84
|
+
touchCallback: widget.control.getBool("on_event", false)!
|
|
85
|
+
? (evt, resp) {
|
|
86
|
+
var eventData =
|
|
87
|
+
ScatterChartEventData.fromDetails(evt, resp);
|
|
88
|
+
widget.control.triggerEvent("event", eventData.toMap());
|
|
89
|
+
}
|
|
90
|
+
: null,
|
|
91
|
+
longPressDuration:
|
|
92
|
+
widget.control.getDuration("long_press_duration"),
|
|
93
|
+
handleBuiltInTouches:
|
|
94
|
+
widget.control.getBool("handle_built_in_touches", true)!,
|
|
95
|
+
touchTooltipData:
|
|
96
|
+
parseScatterTouchTooltipData(context, widget.control, spots)),
|
|
97
|
+
scatterLabelSettings: ScatterLabelSettings(
|
|
98
|
+
showLabel: true,
|
|
99
|
+
getLabelFunction: (spotIndex, spot) {
|
|
100
|
+
var dp = widget.control.children("spots")[spotIndex];
|
|
101
|
+
return dp.getString("label_text", "")!;
|
|
102
|
+
},
|
|
103
|
+
getLabelTextStyleFunction: (spotIndex, spot) {
|
|
104
|
+
var dp = widget.control.children("spots")[spotIndex];
|
|
105
|
+
var labelStyle =
|
|
106
|
+
dp.getTextStyle("label_style", theme, const TextStyle())!;
|
|
107
|
+
if (labelStyle.color == null) {
|
|
108
|
+
labelStyle =
|
|
109
|
+
labelStyle.copyWith(color: spot.dotPainter.mainColor);
|
|
110
|
+
}
|
|
111
|
+
return labelStyle;
|
|
112
|
+
},
|
|
113
|
+
),
|
|
114
|
+
showingTooltipIndicators: widget.control
|
|
115
|
+
.children('spots')
|
|
116
|
+
.asMap()
|
|
117
|
+
.entries
|
|
118
|
+
.where((e) => e.value.getBool("selected", false)!)
|
|
119
|
+
.map((e) => e.key)
|
|
120
|
+
.toList(),
|
|
121
|
+
rotationQuarterTurns:
|
|
122
|
+
widget.control.getInt('rotation_quarter_turns', 0)!,
|
|
123
|
+
//errorIndicatorData: widget.control.get('error_indicator_data'),
|
|
124
|
+
),
|
|
125
|
+
duration: animation.duration,
|
|
126
|
+
curve: animation.curve,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return ConstrainedControl(
|
|
130
|
+
control: widget.control,
|
|
131
|
+
child: LayoutBuilder(
|
|
132
|
+
builder: (BuildContext context, BoxConstraints constraints) {
|
|
133
|
+
return (constraints.maxHeight == double.infinity)
|
|
134
|
+
? ConstrainedBox(
|
|
135
|
+
constraints: const BoxConstraints(maxHeight: 300),
|
|
136
|
+
child: chart)
|
|
137
|
+
: chart;
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import 'package:collection/collection.dart';
|
|
2
|
+
import 'package:equatable/equatable.dart';
|
|
3
|
+
import 'package:fl_chart/fl_chart.dart';
|
|
4
|
+
import 'package:flet/flet.dart';
|
|
5
|
+
import 'package:flutter/material.dart';
|
|
6
|
+
|
|
7
|
+
import 'charts.dart';
|
|
8
|
+
|
|
9
|
+
class BarChartEventData extends Equatable {
|
|
10
|
+
final String eventType;
|
|
11
|
+
final int? groupIndex;
|
|
12
|
+
final int? rodIndex;
|
|
13
|
+
final int? stackItemIndex;
|
|
14
|
+
|
|
15
|
+
const BarChartEventData(
|
|
16
|
+
{required this.eventType,
|
|
17
|
+
required this.groupIndex,
|
|
18
|
+
required this.rodIndex,
|
|
19
|
+
required this.stackItemIndex});
|
|
20
|
+
|
|
21
|
+
factory BarChartEventData.fromDetails(
|
|
22
|
+
FlTouchEvent event, BarTouchResponse? response) {
|
|
23
|
+
return BarChartEventData(
|
|
24
|
+
eventType: eventMap[event.runtimeType.toString()] ?? "undefined",
|
|
25
|
+
groupIndex: response != null && response.spot != null
|
|
26
|
+
? response.spot!.touchedBarGroupIndex
|
|
27
|
+
: null,
|
|
28
|
+
rodIndex: response != null && response.spot != null
|
|
29
|
+
? response.spot!.touchedRodDataIndex
|
|
30
|
+
: null,
|
|
31
|
+
stackItemIndex: response != null && response.spot != null
|
|
32
|
+
? response.spot!.touchedStackItemIndex
|
|
33
|
+
: null);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Map<String, dynamic> toMap() => <String, dynamic>{
|
|
37
|
+
'type': eventType,
|
|
38
|
+
'group_index': groupIndex,
|
|
39
|
+
'rod_index': rodIndex,
|
|
40
|
+
'stack_item_index': stackItemIndex
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
List<Object?> get props => [eventType, groupIndex, rodIndex, stackItemIndex];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
TooltipDirection? parseTooltipDirection(String? value,
|
|
48
|
+
[TooltipDirection? defaultValue]) {
|
|
49
|
+
if (value == null) return defaultValue;
|
|
50
|
+
return TooltipDirection.values.firstWhereOrNull(
|
|
51
|
+
(e) => e.name.toLowerCase() == value.toLowerCase()) ??
|
|
52
|
+
defaultValue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
BarTouchTooltipData? parseBarTouchTooltipData(
|
|
56
|
+
BuildContext context, Control control,
|
|
57
|
+
[BarTouchTooltipData? defaultValue]) {
|
|
58
|
+
var tooltip = control.get("tooltip");
|
|
59
|
+
if (tooltip == null) return defaultValue;
|
|
60
|
+
|
|
61
|
+
final theme = Theme.of(context);
|
|
62
|
+
|
|
63
|
+
return BarTouchTooltipData(
|
|
64
|
+
getTooltipColor: (BarChartGroupData group) =>
|
|
65
|
+
parseColor(tooltip["bgcolor"], theme, theme.colorScheme.secondary)!,
|
|
66
|
+
tooltipBorderRadius: parseBorderRadius(tooltip["border_radius"]),
|
|
67
|
+
tooltipMargin: parseDouble(tooltip["margin"]),
|
|
68
|
+
tooltipPadding: parsePadding(tooltip["padding"]),
|
|
69
|
+
maxContentWidth: parseDouble(tooltip["max_width"]),
|
|
70
|
+
rotateAngle: parseDouble(tooltip["rotate_angle"]),
|
|
71
|
+
tooltipHorizontalOffset: parseDouble(tooltip["horizontal_offset"]),
|
|
72
|
+
tooltipBorder: parseBorderSide(tooltip["border_side"], theme),
|
|
73
|
+
fitInsideHorizontally: parseBool(tooltip["fit_inside_horizontally"]),
|
|
74
|
+
fitInsideVertically: parseBool(tooltip["fit_inside_vertically"]),
|
|
75
|
+
direction: parseTooltipDirection(tooltip["direction"]),
|
|
76
|
+
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
|
77
|
+
var rod =
|
|
78
|
+
control.children("groups")[groupIndex].children("rods")[rodIndex];
|
|
79
|
+
return parseBarTooltipItem(rod, context);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
BarTooltipItem? parseBarTooltipItem(Control rod, BuildContext context) {
|
|
85
|
+
if (!rod.getBool("show_tooltip", true)!) return null;
|
|
86
|
+
|
|
87
|
+
final theme = Theme.of(context);
|
|
88
|
+
|
|
89
|
+
var tooltip = rod.get("tooltip");
|
|
90
|
+
var tooltipTextStyle =
|
|
91
|
+
parseTextStyle(tooltip["text_style"], theme, const TextStyle())!;
|
|
92
|
+
if (tooltipTextStyle.color == null) {
|
|
93
|
+
tooltipTextStyle = tooltipTextStyle.copyWith(
|
|
94
|
+
color: rod.getGradient("gradient", theme)?.colors.first ??
|
|
95
|
+
rod.getColor("color", context, Colors.blueGrey)!);
|
|
96
|
+
}
|
|
97
|
+
return BarTooltipItem(
|
|
98
|
+
tooltip["text"] ?? rod.getDouble("to_y", 0)!.toString(), tooltipTextStyle,
|
|
99
|
+
textAlign: parseTextAlign(tooltip["text_align"], TextAlign.center)!,
|
|
100
|
+
children: tooltip["text_spans"] != null
|
|
101
|
+
? parseTextSpans(tooltip["text_spans"], theme, (s, eventName,
|
|
102
|
+
[eventData]) {
|
|
103
|
+
s.triggerEvent(eventName, eventData);
|
|
104
|
+
})
|
|
105
|
+
: null);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
BarChartGroupData parseBarChartGroupData(
|
|
109
|
+
Control group, bool interactiveChart, BuildContext context) {
|
|
110
|
+
group.notifyParent = true;
|
|
111
|
+
return BarChartGroupData(
|
|
112
|
+
x: group.getInt("x", 0)!,
|
|
113
|
+
barsSpace: group.getDouble("spacing"),
|
|
114
|
+
groupVertically: group.getBool("group_vertically", false)!,
|
|
115
|
+
showingTooltipIndicators: group
|
|
116
|
+
.children("rods")
|
|
117
|
+
.asMap()
|
|
118
|
+
.entries
|
|
119
|
+
.where(
|
|
120
|
+
(rod) => !interactiveChart && rod.value.getBool("selected", false)!)
|
|
121
|
+
.map((rod) => rod.key)
|
|
122
|
+
.toList(),
|
|
123
|
+
barRods: group
|
|
124
|
+
.children("rods")
|
|
125
|
+
.map((rod) => parseBarChartRodData(rod, interactiveChart, context))
|
|
126
|
+
.toList(),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
BarChartRodData parseBarChartRodData(
|
|
131
|
+
Control rod, bool interactiveChart, BuildContext context) {
|
|
132
|
+
rod.notifyParent = true;
|
|
133
|
+
|
|
134
|
+
final theme = Theme.of(context);
|
|
135
|
+
var bgFromY = rod.getDouble("bg_from_y");
|
|
136
|
+
var bgToY = rod.getDouble("bg_to_y");
|
|
137
|
+
var bgcolor = rod.getColor("bgcolor", context);
|
|
138
|
+
var backgroundGradient = rod.getGradient("background_gradient", theme);
|
|
139
|
+
|
|
140
|
+
return BarChartRodData(
|
|
141
|
+
fromY: rod.getDouble("from_y"),
|
|
142
|
+
toY: rod.getDouble("to_y", 0)!,
|
|
143
|
+
width: rod.getDouble("width"),
|
|
144
|
+
color: rod.getColor("color", context),
|
|
145
|
+
gradient: rod.getGradient("gradient", theme),
|
|
146
|
+
borderRadius: rod.getBorderRadius("border_radius"),
|
|
147
|
+
borderSide: rod.getBorderSide("border_side", theme,
|
|
148
|
+
defaultValue: BorderSide.none),
|
|
149
|
+
backDrawRodData: BackgroundBarChartRodData(
|
|
150
|
+
show: (bgFromY != null ||
|
|
151
|
+
bgToY != null ||
|
|
152
|
+
bgcolor != null ||
|
|
153
|
+
backgroundGradient != null),
|
|
154
|
+
fromY: bgFromY,
|
|
155
|
+
toY: bgToY,
|
|
156
|
+
color: bgcolor,
|
|
157
|
+
gradient: backgroundGradient),
|
|
158
|
+
rodStackItems: rod
|
|
159
|
+
.children("stack_items")
|
|
160
|
+
.map((rodStackItem) => parseBarChartRodStackItem(
|
|
161
|
+
rodStackItem, interactiveChart, context))
|
|
162
|
+
.toList());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
BarChartRodStackItem parseBarChartRodStackItem(
|
|
166
|
+
Control rodStackItem,
|
|
167
|
+
bool interactiveChart,
|
|
168
|
+
BuildContext context,
|
|
169
|
+
) {
|
|
170
|
+
rodStackItem.notifyParent = true;
|
|
171
|
+
return BarChartRodStackItem(
|
|
172
|
+
rodStackItem.getDouble("from_y")!,
|
|
173
|
+
rodStackItem.getDouble("to_y", 0)!,
|
|
174
|
+
rodStackItem.getColor("color", context)!,
|
|
175
|
+
rodStackItem.getBorderSide("border_side", Theme.of(context),
|
|
176
|
+
defaultValue: BorderSide.none)!);
|
|
177
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import 'package:collection/collection.dart';
|
|
2
|
+
import 'package:fl_chart/fl_chart.dart';
|
|
3
|
+
import 'package:flet/flet.dart';
|
|
4
|
+
import 'package:flutter/material.dart';
|
|
5
|
+
|
|
6
|
+
FlDotPainter invisibleDotPainter =
|
|
7
|
+
FlDotCirclePainter(radius: 0, strokeWidth: 0);
|
|
8
|
+
FlLine invisibleLine = const FlLine(strokeWidth: 0);
|
|
9
|
+
|
|
10
|
+
FlGridData parseChartGridData(
|
|
11
|
+
dynamic horizontal, dynamic vertical, ThemeData theme) {
|
|
12
|
+
if (horizontal == null && vertical == null) {
|
|
13
|
+
return const FlGridData(show: false);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var hLine = parseFlLine(horizontal, theme);
|
|
17
|
+
var vLine = parseFlLine(vertical, theme);
|
|
18
|
+
|
|
19
|
+
return FlGridData(
|
|
20
|
+
show: true,
|
|
21
|
+
drawHorizontalLine: horizontal != null,
|
|
22
|
+
horizontalInterval:
|
|
23
|
+
horizontal != null ? parseDouble(horizontal["interval"]) : null,
|
|
24
|
+
getDrawingHorizontalLine:
|
|
25
|
+
hLine == null ? defaultGridLine : (value) => hLine,
|
|
26
|
+
drawVerticalLine: vertical != null,
|
|
27
|
+
verticalInterval:
|
|
28
|
+
vertical != null ? parseDouble(vertical["interval"]) : null,
|
|
29
|
+
getDrawingVerticalLine: vLine == null ? defaultGridLine : (value) => vLine,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
FlLine? parseFlLine(dynamic value, ThemeData theme, [FlLine? defaultValue]) {
|
|
34
|
+
if (value == null ||
|
|
35
|
+
(value['color'] == null &&
|
|
36
|
+
value['width'] == null &&
|
|
37
|
+
value['dash_pattern'] == null)) {
|
|
38
|
+
return defaultValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return FlLine(
|
|
42
|
+
color: parseColor(value['color'], theme, Colors.black)!,
|
|
43
|
+
strokeWidth: parseDouble(value['width'], 2)!,
|
|
44
|
+
dashArray: (value['dash_pattern'] as List?)
|
|
45
|
+
?.map((e) => parseInt(e))
|
|
46
|
+
.nonNulls
|
|
47
|
+
.toList());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
FlLine? parseSelectedFlLine(
|
|
51
|
+
dynamic value, ThemeData theme, Color? color, Gradient? gradient,
|
|
52
|
+
[FlLine? defaultValue]) {
|
|
53
|
+
if (value == null) return defaultValue;
|
|
54
|
+
|
|
55
|
+
if (value == false) {
|
|
56
|
+
return invisibleLine;
|
|
57
|
+
} else if (value == true) {
|
|
58
|
+
return FlLine(
|
|
59
|
+
color: getDefaultPointColor(0, color, gradient), strokeWidth: 3);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return parseFlLine(value, theme, defaultValue)?.copyWith(
|
|
63
|
+
color: parseColor(
|
|
64
|
+
value['color'], theme, defaultGetDotStrokeColor(0, color, gradient)));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
FlDotPainter? parseChartDotPainter(dynamic value, ThemeData theme,
|
|
68
|
+
double percentage, Color? barColor, Gradient? barGradient,
|
|
69
|
+
{FlDotPainter? defaultValue, bool selected = false}) {
|
|
70
|
+
if (value == null) {
|
|
71
|
+
return defaultValue;
|
|
72
|
+
} else if (value == false) {
|
|
73
|
+
return invisibleDotPainter;
|
|
74
|
+
} else if (value == true) {
|
|
75
|
+
return getDefaultDotPainter(percentage, barColor, barGradient,
|
|
76
|
+
selected: selected);
|
|
77
|
+
}
|
|
78
|
+
var type = value["_type"];
|
|
79
|
+
var strokeWidth = parseDouble(value["stroke_width"]);
|
|
80
|
+
var size = parseDouble(value["size"]);
|
|
81
|
+
var color = parseColor(value['color'], theme);
|
|
82
|
+
var strokeColor = parseColor(value['stroke_color'], theme,
|
|
83
|
+
defaultGetDotStrokeColor(percentage, barColor, barGradient))!;
|
|
84
|
+
|
|
85
|
+
if (type == "ChartCirclePoint") {
|
|
86
|
+
return FlDotCirclePainter(
|
|
87
|
+
color: color ?? getDefaultPointColor(percentage, barColor, barGradient),
|
|
88
|
+
radius: parseDouble(value["radius"]),
|
|
89
|
+
strokeColor: strokeColor,
|
|
90
|
+
strokeWidth: strokeWidth ?? 0.0);
|
|
91
|
+
} else if (type == "ChartSquarePoint") {
|
|
92
|
+
return FlDotSquarePainter(
|
|
93
|
+
color: color ?? getDefaultPointColor(percentage, barColor, barGradient),
|
|
94
|
+
size: size ?? 4.0,
|
|
95
|
+
strokeColor: strokeColor,
|
|
96
|
+
strokeWidth: strokeWidth ?? 1.0);
|
|
97
|
+
} else if (type == "ChartCrossPoint") {
|
|
98
|
+
return FlDotCrossPainter(
|
|
99
|
+
color:
|
|
100
|
+
color ?? defaultGetDotStrokeColor(percentage, barColor, barGradient),
|
|
101
|
+
size: size ?? 8.0,
|
|
102
|
+
width: parseDouble(value["width"], 2.0)!,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return defaultValue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
FlDotPainter getDefaultDotPainter(
|
|
109
|
+
double percentage, Color? barColor, Gradient? barGradient,
|
|
110
|
+
{bool selected = false}) {
|
|
111
|
+
return FlDotCirclePainter(
|
|
112
|
+
radius: selected ? 8 : 4,
|
|
113
|
+
strokeWidth: selected ? 2 : 1,
|
|
114
|
+
color: getDefaultPointColor(percentage, barColor, barGradient),
|
|
115
|
+
strokeColor: defaultGetDotStrokeColor(percentage, barColor, barGradient),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Color getDefaultPointColor(
|
|
120
|
+
double percentage, Color? barColor, Gradient? barGradient) {
|
|
121
|
+
if (barGradient != null && barGradient is LinearGradient) {
|
|
122
|
+
return lerpGradient(
|
|
123
|
+
barGradient.colors, barGradient.getSafeColorStops(), percentage / 100);
|
|
124
|
+
}
|
|
125
|
+
return barGradient?.colors.first ?? barColor ?? Colors.blueGrey;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Color defaultGetDotStrokeColor(double percentage,
|
|
129
|
+
[Color? barColor, Gradient? barGradient]) {
|
|
130
|
+
Color color = getDefaultPointColor(percentage, barColor, barGradient);
|
|
131
|
+
return color.darken();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
AxisTitles parseAxisTitles(Control? control) {
|
|
135
|
+
if (control == null) {
|
|
136
|
+
return const AxisTitles(sideTitles: SideTitles(showTitles: false));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return AxisTitles(
|
|
140
|
+
axisNameWidget: control.buildWidget("title"),
|
|
141
|
+
axisNameSize: control.getDouble("title_size", 16)!,
|
|
142
|
+
sideTitles: SideTitles(
|
|
143
|
+
showTitles: control.getBool("show_labels", true)!,
|
|
144
|
+
reservedSize: control.getDouble("label_size", 22)!,
|
|
145
|
+
interval: control.getDouble("label_spacing"),
|
|
146
|
+
getTitlesWidget: control.children("labels").isEmpty
|
|
147
|
+
? defaultGetTitle
|
|
148
|
+
: (double value, TitleMeta meta) {
|
|
149
|
+
var label = control
|
|
150
|
+
.children("labels")
|
|
151
|
+
.firstWhereOrNull((l) => l.getDouble("value") == value);
|
|
152
|
+
return label?.buildTextOrWidget("label") ??
|
|
153
|
+
const SizedBox.shrink();
|
|
154
|
+
},
|
|
155
|
+
));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const eventMap = {
|
|
159
|
+
"FlPointerEnterEvent": "pointerEnter",
|
|
160
|
+
"FlPointerExitEvent": "pointerExit",
|
|
161
|
+
"FlPointerHoverEvent": "pointerHover",
|
|
162
|
+
"FlPanCancelEvent": "panCancel",
|
|
163
|
+
"FlPanDownEvent": "panDown",
|
|
164
|
+
"FlPanEndEvent": "panEnd",
|
|
165
|
+
"FlPanStartEvent": "panStart",
|
|
166
|
+
"FlPanUpdateEvent": "panUpdate",
|
|
167
|
+
"FlLongPressEnd": "longPressEnd",
|
|
168
|
+
"FlLongPressMoveUpdate": "longPressMoveUpdate",
|
|
169
|
+
"FlLongPressStart": "longPressStart",
|
|
170
|
+
"FlTapCancelEvent": "tapCancel",
|
|
171
|
+
"FlTapDownEvent": "tapDown",
|
|
172
|
+
"FlTapUpEvent": "tapUp",
|
|
173
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import 'package:equatable/equatable.dart';
|
|
2
|
+
import 'package:fl_chart/fl_chart.dart';
|
|
3
|
+
import 'package:flet/flet.dart';
|
|
4
|
+
import 'package:flutter/material.dart';
|
|
5
|
+
|
|
6
|
+
import 'charts.dart';
|
|
7
|
+
|
|
8
|
+
class LineChartEventData extends Equatable {
|
|
9
|
+
final String eventType;
|
|
10
|
+
final List<LineChartEventDataSpot> barSpots;
|
|
11
|
+
|
|
12
|
+
const LineChartEventData({required this.eventType, required this.barSpots});
|
|
13
|
+
|
|
14
|
+
factory LineChartEventData.fromDetails(
|
|
15
|
+
FlTouchEvent event, LineTouchResponse? response) {
|
|
16
|
+
return LineChartEventData(
|
|
17
|
+
eventType: eventMap[event.runtimeType.toString()] ?? "undefined",
|
|
18
|
+
barSpots: response != null && response.lineBarSpots != null
|
|
19
|
+
? response.lineBarSpots!
|
|
20
|
+
.map((bs) => LineChartEventDataSpot(
|
|
21
|
+
barIndex: bs.barIndex, spotIndex: bs.spotIndex))
|
|
22
|
+
.toList()
|
|
23
|
+
: []);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Map<String, dynamic> toMap() => <String, dynamic>{
|
|
27
|
+
'type': eventType,
|
|
28
|
+
'spots': barSpots,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
List<Object?> get props => [eventType, barSpots];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class LineChartEventDataSpot extends Equatable {
|
|
36
|
+
final int barIndex;
|
|
37
|
+
final int spotIndex;
|
|
38
|
+
|
|
39
|
+
const LineChartEventDataSpot(
|
|
40
|
+
{required this.barIndex, required this.spotIndex});
|
|
41
|
+
|
|
42
|
+
Map<String, dynamic> toMap() => <String, dynamic>{
|
|
43
|
+
'bar_index': barIndex,
|
|
44
|
+
'spot_index': spotIndex,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
List<Object?> get props => [barIndex, spotIndex];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
LineTooltipItem? parseLineTooltipItem(
|
|
52
|
+
Control dataPoint, LineBarSpot spot, BuildContext context) {
|
|
53
|
+
if (!dataPoint.getBool("show_tooltip", true)!) return null;
|
|
54
|
+
|
|
55
|
+
final theme = Theme.of(context);
|
|
56
|
+
|
|
57
|
+
var tooltip = dataPoint.get("tooltip");
|
|
58
|
+
var style = parseTextStyle(tooltip["text_style"], theme, const TextStyle())!;
|
|
59
|
+
if (style.color == null) {
|
|
60
|
+
style = style.copyWith(
|
|
61
|
+
color: spot.bar.gradient?.colors.first ??
|
|
62
|
+
spot.bar.color ??
|
|
63
|
+
Colors.blueGrey);
|
|
64
|
+
}
|
|
65
|
+
return LineTooltipItem(
|
|
66
|
+
tooltip["text"] ?? dataPoint.getDouble("y", 0)!.toString(), style,
|
|
67
|
+
textAlign: parseTextAlign(tooltip["text_align"], TextAlign.center)!,
|
|
68
|
+
children: tooltip["text_spans"] != null
|
|
69
|
+
? parseTextSpans(tooltip["text_spans"], theme, (s, eventName,
|
|
70
|
+
[eventData]) {
|
|
71
|
+
s.triggerEvent(eventName, eventData);
|
|
72
|
+
})
|
|
73
|
+
: null);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
LineTouchTooltipData? parseLineTouchTooltipData(
|
|
77
|
+
BuildContext context, Control control,
|
|
78
|
+
[LineTouchTooltipData? defaultValue]) {
|
|
79
|
+
final tooltip = control.get("tooltip");
|
|
80
|
+
if (tooltip == null) return defaultValue;
|
|
81
|
+
|
|
82
|
+
final theme = Theme.of(context);
|
|
83
|
+
|
|
84
|
+
return LineTouchTooltipData(
|
|
85
|
+
getTooltipColor: (LineBarSpot spot) => parseColor(
|
|
86
|
+
tooltip["bgcolor"], theme, const Color.fromRGBO(96, 125, 139, 1))!,
|
|
87
|
+
tooltipBorderRadius: parseBorderRadius(tooltip["border_radius"]),
|
|
88
|
+
tooltipMargin: parseDouble(tooltip["margin"], 16)!,
|
|
89
|
+
tooltipPadding: parsePadding(tooltip["padding"],
|
|
90
|
+
const EdgeInsets.symmetric(horizontal: 16, vertical: 8))!,
|
|
91
|
+
maxContentWidth: parseDouble(tooltip["max_width"], 120)!,
|
|
92
|
+
rotateAngle: parseDouble(tooltip["rotate_angle"], 0.0)!,
|
|
93
|
+
tooltipHorizontalOffset: parseDouble(tooltip["horizontal_offset"], 0)!,
|
|
94
|
+
tooltipBorder: parseBorderSide(tooltip["border_side"], theme,
|
|
95
|
+
defaultValue: BorderSide.none)!,
|
|
96
|
+
fitInsideHorizontally:
|
|
97
|
+
parseBool(tooltip["fit_inside_horizontally"], false)!,
|
|
98
|
+
fitInsideVertically: parseBool(tooltip["fit_inside_vertically"], false)!,
|
|
99
|
+
showOnTopOfTheChartBoxArea:
|
|
100
|
+
parseBool(tooltip["show_on_top_of_chart_box_area"], false)!,
|
|
101
|
+
getTooltipItems: (List<LineBarSpot> touchedSpots) {
|
|
102
|
+
return touchedSpots
|
|
103
|
+
.map((LineBarSpot spot) => parseLineTooltipItem(
|
|
104
|
+
control
|
|
105
|
+
.children("data_series")[spot.barIndex]
|
|
106
|
+
.children("points")[spot.spotIndex],
|
|
107
|
+
spot,
|
|
108
|
+
context))
|
|
109
|
+
.nonNulls
|
|
110
|
+
.toList();
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
LineChartBarData parseLineChartBarData(
|
|
116
|
+
Control parent,
|
|
117
|
+
Control chartData,
|
|
118
|
+
bool interactiveChart,
|
|
119
|
+
BuildContext context,
|
|
120
|
+
Map<int, List<FlSpot>> barSpots) {
|
|
121
|
+
final theme = Theme.of(context);
|
|
122
|
+
|
|
123
|
+
var aboveLineBgcolor = chartData.getColor("above_line_bgcolor", context);
|
|
124
|
+
var aboveLineGradient = chartData.getGradient("above_line_gradient", theme);
|
|
125
|
+
var belowLineBgcolor = chartData.getColor("below_line_bgcolor", context);
|
|
126
|
+
var belowLineGradient = chartData.getGradient("below_line_gradient", theme);
|
|
127
|
+
var dashPattern = chartData.get("dash_pattern");
|
|
128
|
+
var barColor = chartData.getColor("color", context, Colors.cyan)!;
|
|
129
|
+
var barGradient = chartData.getGradient("gradient", theme);
|
|
130
|
+
var aboveLine = parseFlLine(chartData.get("above_line"), Theme.of(context));
|
|
131
|
+
var belowLine = parseFlLine(chartData.get("below_line"), Theme.of(context));
|
|
132
|
+
var aboveLineCutoffY = chartData.getDouble("above_line_cutoff_y");
|
|
133
|
+
var belowLineCutoffY = chartData.getDouble("below_line_cutoff_y");
|
|
134
|
+
|
|
135
|
+
Map<FlSpot, Control> spots = {
|
|
136
|
+
for (var e in chartData.children("points"))
|
|
137
|
+
FlSpot(e.getDouble("x", 0)!, e.getDouble("y", 0)!): e
|
|
138
|
+
};
|
|
139
|
+
return LineChartBarData(
|
|
140
|
+
preventCurveOverShooting:
|
|
141
|
+
chartData.getBool("prevent_curve_over_shooting", false)!,
|
|
142
|
+
preventCurveOvershootingThreshold:
|
|
143
|
+
chartData.getDouble("prevent_curve_over_shooting_threshold", 10.0)!,
|
|
144
|
+
spots: barSpots[chartData.id] ?? [],
|
|
145
|
+
showingIndicators: chartData
|
|
146
|
+
.children("points")
|
|
147
|
+
.asMap()
|
|
148
|
+
.entries
|
|
149
|
+
.where(
|
|
150
|
+
(dp) => !interactiveChart && dp.value.getBool("selected", false)!)
|
|
151
|
+
.map((e) => e.key)
|
|
152
|
+
.toList(),
|
|
153
|
+
isCurved: chartData.getBool("curved", false)!,
|
|
154
|
+
isStrokeCapRound: chartData.getBool("rounded_stroke_cap", false)!,
|
|
155
|
+
barWidth: chartData.getDouble("stroke_width", 2.0)!,
|
|
156
|
+
dashArray: dashPattern != null
|
|
157
|
+
? (dashPattern as List).map((e) => parseInt(e)).nonNulls.toList()
|
|
158
|
+
: null,
|
|
159
|
+
shadow: parseBoxShadow(chartData.get("shadow"), Theme.of(context)) ??
|
|
160
|
+
const Shadow(color: Colors.transparent),
|
|
161
|
+
dotData: FlDotData(
|
|
162
|
+
show: true,
|
|
163
|
+
getDotPainter: (spot, percent, barData, index) {
|
|
164
|
+
var allDotsPainter = parseChartDotPainter(
|
|
165
|
+
chartData.get("point"), theme, percent, barColor, barGradient);
|
|
166
|
+
var dotPainter = parseChartDotPainter(
|
|
167
|
+
chartData.children("points")[index].get("point"),
|
|
168
|
+
theme,
|
|
169
|
+
percent,
|
|
170
|
+
barColor,
|
|
171
|
+
barGradient);
|
|
172
|
+
return dotPainter ?? allDotsPainter ?? invisibleDotPainter;
|
|
173
|
+
}),
|
|
174
|
+
aboveBarData: aboveLineBgcolor != null ||
|
|
175
|
+
aboveLineGradient != null ||
|
|
176
|
+
aboveLine != null
|
|
177
|
+
? BarAreaData(
|
|
178
|
+
show: true,
|
|
179
|
+
color: aboveLineBgcolor,
|
|
180
|
+
gradient: aboveLineGradient,
|
|
181
|
+
applyCutOffY: aboveLineCutoffY != null,
|
|
182
|
+
cutOffY: aboveLineCutoffY ?? 0,
|
|
183
|
+
spotsLine: BarAreaSpotsLine(
|
|
184
|
+
show: aboveLine != null,
|
|
185
|
+
flLineStyle: aboveLine ?? const FlLine(),
|
|
186
|
+
checkToShowSpotLine: (spot) =>
|
|
187
|
+
spots[spot]!.getBool("show_above_line", true)!,
|
|
188
|
+
))
|
|
189
|
+
: null,
|
|
190
|
+
belowBarData: belowLineBgcolor != null ||
|
|
191
|
+
belowLineGradient != null ||
|
|
192
|
+
belowLine != null
|
|
193
|
+
? BarAreaData(
|
|
194
|
+
show: true,
|
|
195
|
+
color: belowLineBgcolor,
|
|
196
|
+
gradient: belowLineGradient,
|
|
197
|
+
applyCutOffY: belowLineCutoffY != null,
|
|
198
|
+
cutOffY: belowLineCutoffY ?? 0,
|
|
199
|
+
spotsLine: BarAreaSpotsLine(
|
|
200
|
+
show: belowLine != null,
|
|
201
|
+
flLineStyle: belowLine ?? const FlLine(),
|
|
202
|
+
checkToShowSpotLine: (spot) =>
|
|
203
|
+
spots[spot]!.getBool("show_below_line", true)!,
|
|
204
|
+
))
|
|
205
|
+
: null,
|
|
206
|
+
color: barColor,
|
|
207
|
+
gradient: barGradient);
|
|
208
|
+
}
|