diff --git a/game/addons/easy_charts/control_charts/BarChart/bar_chart.gd b/game/addons/easy_charts/control_charts/BarChart/bar_chart.gd
new file mode 100644
index 0000000..8c7fb6b
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/BarChart/bar_chart.gd
@@ -0,0 +1,184 @@
+extends Chart
+class_name BarChart
+
+signal bar_entered(bar)
+
+# Size of a horizontal vector, which is calculated by `plot_box.size.x / x.size()`
+var x_sector_size: float
+
+# List of all unordered bars belonging to this plot
+var bars: Array = []
+
+# List of all bars, grouped by function
+var function_bars: Array = []
+
+# Currently focused bar
+var focused_bar: Bar = null
+
+func _clear_bars() -> void:
+ bars.clear()
+ function_bars.clear()
+
+func _clear() -> void:
+ _clear_bars()
+
+func _calc_x_domain() -> void:
+ pass
+
+func _sample_x() -> void:
+ ### @sampled_domain, which are the domain relative to the sampled values
+ ### x (real value) --> sampling --> x_sampled (pixel value in canvas)
+ x_sampled_domain = Pair.new(plot_box.position.x, plot_box.end.x)
+
+ # samples
+ x_sampled = SampledAxis.new(x, x_sampled_domain)
+
+ x_sector_size = (x_sampled_domain.right - x_sampled_domain.left) / x.size()
+
+func sort_ascending(a: String, b: String):
+ if a.length() < b.length():
+ return true
+ return false
+
+func _find_longest_x() -> String:
+ var longest_x: String = ""
+ var x_str: Array = x.duplicate(true)
+ x_str.sort_custom(self, "sort_ascending")
+ return x_str.back()
+
+
+func _draw_bar(bar: Bar, function_index: int) -> void:
+ draw_rect(
+ bar.rect,
+ chart_properties.get_function_color(function_index),
+ true,
+ 1,
+ false
+ )
+
+func _draw_bars() -> void:
+ for function in function_bars.size():
+ for i in range(0, function_bars[function].size()):
+ _draw_bar(
+ function_bars[function][i],
+ function
+ )
+
+func _get_tick_label(line_index: int, line_value: float) -> String:
+ return x[line_index]
+
+func _get_vertical_tick_label_pos(base_position: Vector2, text: String) -> Vector2:
+ return ._get_vertical_tick_label_pos(base_position, text) + Vector2(x_sector_size / 2, 0)
+
+
+func _draw_vertical_grid() -> void:
+ # draw vertical lines
+
+ # 1. the amount of lines is equals to the X_scale: it identifies in how many sectors the x domain
+ # should be devided
+ # 2. calculate the spacing between each line in pixel. It is equals to x_sampled_domain / x_scale
+ # 3. calculate the offset in the real x domain, which is x_domain / x_scale.
+ for _x in x.size():
+ var top: Vector2 = Vector2(
+ (_x * x_sector_size) + plot_box.position.x,
+ bounding_box.position.y
+ )
+ var bottom: Vector2 = Vector2(
+ (_x * x_sector_size) + plot_box.position.x,
+ bounding_box.end.y
+ )
+
+ _draw_vertical_gridline_component(top, bottom, _x, x_sector_size)
+
+ ### Draw last gridline
+ var p1: Vector2 = Vector2(
+ (x.size() * x_sector_size) + plot_box.position.x,
+ bounding_box.position.y
+ )
+
+ var p2: Vector2 = Vector2(
+ (x.size() * x_sector_size) + plot_box.position.x,
+ bounding_box.end.y
+ )
+
+ # Draw V Ticks
+ if chart_properties.ticks:
+ _draw_tick(p2, p2 + Vector2(0, _x_tick_size), chart_properties.colors.bounding_box)
+
+ # Draw V Grid Lines
+ if chart_properties.grid:
+ draw_line(p1, p2, chart_properties.colors.grid, 1, true)
+
+func _calculate_bars() -> void:
+ var validation: int = _validate_sampled_axis(x_sampled, y_sampled)
+ if not validation == OK:
+ printerr("Cannot plot bars for invalid dataset! Error: %s" % validation)
+ return
+
+ if y_sampled.values[0] is Array:
+ for yxi in y_sampled.values.size():
+ var _function_bars: Array = []
+ for i in y_sampled.values[yxi].size():
+ var real_bar_value: Pair = Pair.new(x[i], y[yxi][i])
+ var sampled_bar_pos: Vector2 = Vector2(
+ (x_sector_size * i) + x_sampled_domain.left + (x_sector_size / 2) - (chart_properties.bar_width / 2),
+ y_sampled.values[yxi][i]
+ )
+ var sampled_bar_size: Vector2 = Vector2(
+ chart_properties.bar_width,
+ y_sampled_domain.left - y_sampled.values[yxi][i]
+ )
+ var bar: Bar = Bar.new(Rect2(sampled_bar_pos, sampled_bar_size), real_bar_value)
+ _function_bars.append(bar)
+ bars.append(bar)
+ function_bars.append(_function_bars)
+ else:
+ for i in y_sampled.values.size():
+ var real_bar_value: Pair = Pair.new(x[i], y[i])
+ var sampled_bar_pos: Vector2 = Vector2(
+ (x_sector_size * i) + x_sampled_domain.left + (x_sector_size / 2) - chart_properties.bar_width,
+ y_sampled.values[i]
+ )
+ var sampled_bar_size: Vector2 = Vector2(
+ chart_properties.bar_width,
+ y_sampled_domain.left - y_sampled.values[i]
+ )
+ var bar: Bar = Bar.new(Rect2(sampled_bar_pos, sampled_bar_size), real_bar_value)
+ bars.append(bar)
+ function_bars.append(bars)
+
+func _draw() -> void:
+ _calculate_bars()
+
+ _draw_bars()
+
+func _get_function_bar(bar: Bar) -> int:
+ var bar_f_index: int = -1
+ for f_bar in function_bars.size():
+ var found: int = function_bars[f_bar].find(bar)
+ if found != -1:
+ bar_f_index = f_bar
+ break
+ return bar_f_index
+
+func _input(event: InputEvent) -> void:
+ if event is InputEventMouse:
+ for bar in bars:
+ if bar.rect.abs().has_point(event.position):
+ if focused_bar == bar:
+ return
+ else:
+ focused_bar = bar
+ var func_index: int = _get_function_bar(focused_bar)
+ $Tooltip.update_values(
+ str(focused_bar.value.left),
+ str(focused_bar.value.right),
+ _get_function_name(func_index),
+ _get_function_color(func_index)
+ )
+ $Tooltip.show()
+ emit_signal("bar_entered", bar)
+ return
+ # Mouse is not in any bar's box
+ focused_bar = null
+ $Tooltip.hide()
diff --git a/game/addons/easy_charts/control_charts/BarChart/bar_chart.tscn b/game/addons/easy_charts/control_charts/BarChart/bar_chart.tscn
new file mode 100644
index 0000000..7c69b42
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/BarChart/bar_chart.tscn
@@ -0,0 +1,14 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/BarChart/bar_chart.gd" type="Script" id=1]
+[ext_resource path="res://addons/easy_charts/control_charts/chart.tscn" type="PackedScene" id=2]
+
+[node name="BarChart" instance=ExtResource( 2 )]
+script = ExtResource( 1 )
+
+[node name="Tooltip" parent="." index="1"]
+margin_right = 20.0
+margin_bottom = 16.0
+__meta__ = {
+"_edit_use_anchors_": true
+}
diff --git a/game/addons/easy_charts/control_charts/LineChart/line_chart.gd b/game/addons/easy_charts/control_charts/LineChart/line_chart.gd
new file mode 100644
index 0000000..1d448ea
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/LineChart/line_chart.gd
@@ -0,0 +1,46 @@
+extends ScatterChart
+class_name LineChart
+
+func _draw_line(from: Point, to: Point, function_index: int) -> void:
+ draw_line(
+ from.position,
+ to.position,
+ chart_properties.get_function_color(function_index),
+ chart_properties.line_width,
+ true
+ )
+
+func _draw_spline(points: Array, function: int, density: float = 10.0, tension: float = 1) -> void:
+ var spline_points: Array = []
+
+ var augmented: Array = points.duplicate(true)
+ var pi: Point = Point.new(points.front().position - Vector2(10, -10), Pair.new())
+ var pf: Point = Point.new(points.back().position + Vector2(10, 10), Pair.new())
+
+ augmented.insert(0, pi)
+ augmented.append(pf)
+
+ for p in range(1, augmented.size() - 2, 1) : #(inclusive)
+
+ for f in range(0, density + 1, 1):
+ spline_points.append(
+ augmented[p].position.cubic_interpolate(
+ augmented[p + 1].position,
+ augmented[p - 1].position,
+ augmented[p + 2].position,
+ f / density)
+ )
+
+ for i in range(1, spline_points.size()):
+ draw_line(spline_points[i-1], spline_points[i], chart_properties.get_function_color(function), chart_properties.line_width, true)
+
+func _draw_lines() -> void:
+ for function in function_points.size():
+ if chart_properties.use_splines:
+ _draw_spline(function_points[function], function)
+ else:
+ for i in range(1, function_points[function].size()):
+ _draw_line(function_points[function][i - 1], function_points[function][i], function)
+
+func _draw() -> void:
+ _draw_lines()
diff --git a/game/addons/easy_charts/control_charts/LineChart/line_chart.tscn b/game/addons/easy_charts/control_charts/LineChart/line_chart.tscn
new file mode 100644
index 0000000..88a0d37
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/LineChart/line_chart.tscn
@@ -0,0 +1,7 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/LineChart/line_chart.gd" type="Script" id=1]
+[ext_resource path="res://addons/easy_charts/control_charts/chart.tscn" type="PackedScene" id=2]
+
+[node name="LineChart" instance=ExtResource( 2 )]
+script = ExtResource( 1 )
diff --git a/game/addons/easy_charts/control_charts/ScatterChart/scatter_chart.gd b/game/addons/easy_charts/control_charts/ScatterChart/scatter_chart.gd
new file mode 100644
index 0000000..717ca34
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/ScatterChart/scatter_chart.gd
@@ -0,0 +1,124 @@
+extends Chart
+class_name ScatterChart
+
+signal point_entered(point)
+
+var _point_box_rad: int = 10
+
+# List of all unordered points belonging to this plot
+var points: Array = []
+
+# List of all points, grouped by function
+var function_points: Array = []
+
+# Currently focused point
+var focused_point: Point = null
+
+func _clear_points() -> void:
+ points.clear()
+ function_points.clear()
+
+func _clear() -> void:
+ _clear_points()
+
+func _get_point_box(point: Point, rad: int) -> Rect2:
+ return Rect2(point.position - (Vector2.ONE * rad), (Vector2.ONE * rad * 2))
+
+func _get_function_point(point: Point) -> int:
+ var point_f_index: int = -1
+ for f_point in function_points.size():
+ var found: int = function_points[f_point].find(point)
+ if found != -1:
+ point_f_index = f_point
+ break
+ return point_f_index
+
+func _input(event: InputEvent) -> void:
+ if event is InputEventMouse:
+ for point in points:
+ if _get_point_box(point, _point_box_rad).abs().has_point(event.position):
+ if focused_point == point:
+ return
+ else:
+ focused_point = point
+ var func_index: int = _get_function_point(focused_point)
+ $Tooltip.update_values(
+ str(point.value.left),
+ str(point.value.right),
+ _get_function_name(func_index),
+ _get_function_color(func_index)
+ )
+ $Tooltip.show()
+ emit_signal("point_entered", point)
+ return
+ # Mouse is not in any point's box
+ focused_point = null
+ $Tooltip.hide()
+
+func _draw_point(point: Point, function_index: int) -> void:
+ match chart_properties.get_point_shape(function_index):
+ Point.Shape.CIRCLE:
+ draw_circle(point.position, chart_properties.point_radius, chart_properties.get_function_color(function_index))
+ Point.Shape.SQUARE:
+ draw_rect(_get_point_box(point, chart_properties.point_radius), chart_properties.get_function_color(function_index), true, 1.0, false)
+ Point.Shape.TRIANGLE:
+ draw_colored_polygon(
+ PoolVector2Array([
+ point.position + (Vector2.UP * chart_properties.point_radius * 1.3),
+ point.position + (Vector2.ONE * chart_properties.point_radius * 1.3),
+ point.position - (Vector2(1, -1) * chart_properties.point_radius * 1.3)
+ ]), chart_properties.get_function_color(function_index), [], null, null, false
+ )
+ Point.Shape.CROSS:
+ draw_line(
+ point.position - (Vector2.ONE * chart_properties.point_radius),
+ point.position + (Vector2.ONE * chart_properties.point_radius),
+ chart_properties.get_function_color(function_index), chart_properties.point_radius, true
+ )
+ draw_line(
+ point.position + (Vector2(1, -1) * chart_properties.point_radius),
+ point.position + (Vector2(-1, 1) * chart_properties.point_radius),
+ chart_properties.get_function_color(function_index), chart_properties.point_radius / 2, true
+ )
+
+# # (debug)
+# draw_rect(
+# _get_point_box(point, _point_box_rad),
+# Color.red,
+# false, 1, true
+# )
+
+func _draw_points() -> void:
+ for function in function_points.size():
+ for point in function_points[function]:
+ _draw_point(point, function)
+
+func _calculate_points() -> void:
+ var validation: int = _validate_sampled_axis(x_sampled, y_sampled)
+ if not validation == OK:
+ printerr("Cannot plot points for invalid dataset! Error: %s" % validation)
+ return
+
+ if y_sampled.values[0] is Array:
+ for yxi in y_sampled.values.size():
+ var _function_points: Array = []
+ for i in y_sampled.values[yxi].size():
+ var real_point_val: Pair = Pair.new(x[i], y[yxi][i])
+ var sampled_point_pos: Vector2 = Vector2(x_sampled.values[i], y_sampled.values[yxi][i])
+ var point: Point = Point.new(sampled_point_pos, real_point_val)
+ _function_points.append(point)
+ points.append(point)
+ function_points.append(_function_points)
+ else:
+ for i in y_sampled.values.size():
+ var real_point_val: Pair = Pair.new(x[i], y[i])
+ var sampled_point_pos: Vector2 = Vector2(x_sampled.values[i], y_sampled.values[i])
+ var point: Point = Point.new(sampled_point_pos, real_point_val)
+ points.append(point)
+ function_points.append(points)
+
+func _draw() -> void:
+ _calculate_points()
+
+ if chart_properties.points:
+ _draw_points()
diff --git a/game/addons/easy_charts/control_charts/ScatterChart/scatter_chart.tscn b/game/addons/easy_charts/control_charts/ScatterChart/scatter_chart.tscn
new file mode 100644
index 0000000..009c249
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/ScatterChart/scatter_chart.tscn
@@ -0,0 +1,7 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/ScatterChart/scatter_chart.gd" type="Script" id=1]
+[ext_resource path="res://addons/easy_charts/control_charts/chart.tscn" type="PackedScene" id=2]
+
+[node name="ScatterChart" instance=ExtResource( 2 )]
+script = ExtResource( 1 )
diff --git a/game/addons/easy_charts/control_charts/chart.gd b/game/addons/easy_charts/control_charts/chart.gd
new file mode 100644
index 0000000..23c1e28
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/chart.gd
@@ -0,0 +1,503 @@
+extends Control
+class_name Chart
+
+var x: Array
+var y: Array
+
+var x_min_max: Pair = Pair.new() # Min and Max values of @x
+var x_domain: Pair = Pair.new() # Rounded domain of values of @x
+var y_min_max: Pair = Pair.new() # Min and Max values of @y
+var y_domain: Pair = Pair.new() # Rounded domain of values of @x
+
+var x_sampled: SampledAxis = SampledAxis.new()
+var y_sampled: SampledAxis = SampledAxis.new()
+
+var x_labels: Array = []
+var y_labels: Array = []
+var functions_names: Array = []
+
+###### STYLE
+var chart_properties: ChartProperties = ChartProperties.new()
+
+#### INTERNAL
+# The bounding_box of the chart
+var node_box: Rect2
+var bounding_box: Rect2
+var plot_offset: Vector2
+var plot_box: Rect2
+
+# The Reference Rectangle to plot samples
+# It is the @bounding_box Rectangle inverted on the Y axis
+var x_sampled_domain: Pair
+var y_sampled_domain: Pair
+
+var _padding_offset: Vector2 = Vector2(20.0, 20.0)
+var _internal_offset: Vector2 = Vector2(15.0, 15.0)
+
+var y_has_decimals: bool
+var _y_label_size: Vector2 = Vector2.ZERO # offset only on the X axis
+var _y_label_offset: int = 15 # offset only on the X axis
+var _y_ticklabel_size: Vector2 # offset only on the X axis
+var _y_ticklabel_offset: int = 5 # offset only on the X axis
+var _y_tick_size: int = 7
+
+var x_has_decimals: bool
+var _x_label_size: Vector2 = Vector2.ZERO # offset only on the X axis
+var _x_label_offset: int = 15 # offset only on the X axis
+var _x_ticklabel_size: Vector2 # offset only on the X axis
+var _x_ticklabel_offset: int = 5 # offset only on the X axis
+var _x_tick_size: int = 7
+
+###########
+func _ready() -> void:
+ set_process_input(false)
+ set_process(false)
+
+func validate_input_samples(samples: Array) -> bool:
+ if samples.size() > 1 and samples[0] is Array:
+ for sample in samples:
+ if (not sample is Array) or sample.size() != samples[0].size():
+ return false
+ return true
+
+func plot(x: Array, y: Array, properties: ChartProperties = self.chart_properties) -> void:
+ self.x = x
+ self.y = y
+
+ if properties != null:
+ self.chart_properties = properties
+
+ set_process_input(chart_properties.interactive)
+
+ update()
+
+func _map_pair(val: float, rel: Pair, ref: Pair) -> float:
+ return range_lerp(val, rel.left, rel.right, ref.left, ref.right)
+
+func _has_decimals(values: Array) -> bool:
+ var temp: Array = values.duplicate(true)
+
+ if temp[0] is Array:
+ for dim in temp:
+ for val in dim:
+ if val is String:
+ return false
+ if abs(fmod(val, 1)) > 0.0:
+ return true
+ else:
+ for val in temp:
+ if val is String:
+ return false
+ if abs(fmod(val, 1)) > 0.0:
+ return true
+
+ return false
+
+func _find_min_max(values: Array) -> Pair:
+ var temp: Array = values.duplicate(true)
+ var _min: float
+ var _max: float
+
+ if temp[0] is Array:
+ var min_ts: Array
+ var max_ts: Array
+ for dim in temp:
+ min_ts.append(dim.min())
+ max_ts.append(dim.max())
+ _min = min_ts.min()
+ _max = max_ts.max()
+ else:
+ _min = temp.min()
+ _max = temp.max()
+
+ return Pair.new(_min, _max)
+
+func _sample_values(values: Array, rel_values: Pair, ref_values: Pair) -> SampledAxis:
+ if values.empty():
+ printerr("Trying to plot an empty dataset!")
+ return SampledAxis.new()
+
+ if values[0] is Array:
+ if values.size() > 1:
+ for dim in values:
+ if values[0].size() != dim.size():
+ printerr("Cannot plot a dataset with dimensions of different size!")
+ return SampledAxis.new()
+
+ var temp: Array = values.duplicate(true)
+
+ var rels: Array = []
+ var division_size: float
+ if temp[0] is Array:
+ for t_dim in temp:
+ var rels_t: Array = []
+ for val in t_dim:
+ rels_t.append(_map_pair(val, rel_values, ref_values))
+ rels.append(rels_t)
+
+ else:
+ division_size = (ref_values.right - ref_values.left) / values.size()
+ for val_i in temp.size():
+ if temp[val_i] is String:
+ rels.append(val_i * division_size)
+ else:
+ rels.append(_map_pair(temp[val_i], rel_values, ref_values))
+
+ return SampledAxis.new(rels, rel_values)
+
+func _round_min(val: float) -> float:
+ return round(val) if abs(val) < 10 else floor(val / 10.0) * 10.0
+
+func _round_max(val: float) -> float:
+ return round(val) if abs(val) < 10 else ceil(val / 10.0) * 10.0
+
+
+func _calc_x_domain() -> void:
+ x_min_max = _find_min_max(x)
+ x_domain = Pair.new(_round_min(x_min_max.left), _round_max(x_min_max.right))
+
+func _sample_x() -> void:
+ ### @sampled_domain, which are the domain relative to the sampled values
+ ### x (real value) --> sampling --> x_sampled (pixel value in canvas)
+ x_sampled_domain = Pair.new(plot_box.position.x, plot_box.end.x)
+
+ # samples
+ x_sampled = _sample_values(x, x_min_max, x_sampled_domain)
+
+func _calc_y_domain() -> void:
+ y_min_max = _find_min_max(y)
+ y_domain = Pair.new(_round_min(y_min_max.left), _round_max(y_min_max.right))
+
+func _sample_y() -> void:
+ ### @sampled_domain, which are the domain relative to the sampled values
+ ### x (real value) --> sampling --> x_sampled (pixel value in canvas)
+ y_sampled_domain = Pair.new(plot_box.end.y, plot_box.position.y)
+
+ # samples
+ y_sampled = _sample_values(y, y_domain, y_sampled_domain)
+
+
+func _find_longest_x() -> String:
+ return ("%.2f" if x_has_decimals else "%s") % x_domain.right
+
+func _pre_process() -> void:
+ _calc_x_domain()
+ _calc_y_domain()
+
+ var frame: Rect2 = get_global_rect()
+
+
+ #### @node_box size, which is the whole "frame"
+ node_box = Rect2(Vector2.ZERO, frame.size - frame.position)
+
+ #### calculating offset from the @node_box for the @bounding_box.
+ plot_offset = _padding_offset
+
+ ### if @labels drawing is enabled, calcualte offsets
+ if chart_properties.labels:
+ ### labels (X, Y, Title)
+ _x_label_size = chart_properties.font.get_string_size(chart_properties.x_label)
+ _y_label_size = chart_properties.font.get_string_size(chart_properties.y_label)
+
+ ### tick labels
+
+ ###### --- X
+ x_has_decimals = _has_decimals(x)
+ # calculate the string length of the largest value on the X axis.
+ # remember that "-" sign adds additional pixels, and it is relative only to negative numbers!
+ var x_max_formatted: String = _find_longest_x()
+ _x_ticklabel_size = chart_properties.font.get_string_size(x_max_formatted)
+
+ plot_offset.y += _x_label_offset + _x_label_size.y + _x_ticklabel_offset + _x_ticklabel_size.y
+
+ ###### --- Y
+ y_has_decimals = _has_decimals(y)
+ # calculate the string length of the largest value on the Y axis.
+ # remember that "-" sign adds additional pixels, and it is relative only to negative numbers!
+ var y_max_formatted: String = ("%.2f" if y_has_decimals else "%s") % y_domain.right
+ if y_domain.left < 0:
+ # negative number
+ var y_min_formatted: String = ("%.2f" if y_has_decimals else "%s") % y_domain.left
+ if y_min_formatted.length() >= y_max_formatted.length():
+ _y_ticklabel_size = chart_properties.font.get_string_size(y_min_formatted)
+ else:
+ _y_ticklabel_size = chart_properties.font.get_string_size(y_max_formatted)
+ else:
+ _y_ticklabel_size = chart_properties.font.get_string_size(y_max_formatted)
+
+ plot_offset.x += _y_label_offset + _y_label_size.y + _y_ticklabel_offset + _y_ticklabel_size.x
+
+ ### if @ticks drawing is enabled, calculate offsets
+ if chart_properties.ticks:
+ plot_offset.x += _y_tick_size
+ plot_offset.y += _x_tick_size
+
+ ### @bounding_box, where the points will be plotted
+ bounding_box = Rect2(
+ plot_offset,
+ frame.size - (plot_offset * 2)
+ )
+
+ plot_box = Rect2(
+ bounding_box.position + _internal_offset,
+ bounding_box.size - (_internal_offset * 2)
+ )
+
+ _sample_x()
+ _sample_y()
+
+func _draw_borders() -> void:
+ draw_rect(node_box, Color.red, false, 1, true)
+
+func _draw_bounding_box() -> void:
+ draw_rect(bounding_box, chart_properties.colors.bounding_box, false, 1, true)
+
+# # (debug)
+# var half: Vector2 = (bounding_box.size) / 2
+# draw_line(bounding_box.position + Vector2(half.x, 0), bounding_box.position + Vector2(half.x, bounding_box.size.y), Color.red, 3, false)
+# draw_line(bounding_box.position + Vector2(0, half.y), bounding_box.position + Vector2(bounding_box.size.x, half.y), Color.red, 3, false)
+
+func _draw_origin() -> void:
+ var xorigin: float = _map_pair(0.0, x_min_max, x_sampled_domain)
+ var yorigin: float = _map_pair(0.0, y_domain, y_sampled_domain)
+ draw_line(Vector2(xorigin, bounding_box.position.y), Vector2(xorigin, bounding_box.position.y + bounding_box.size.y), Color.black, 1, 0)
+ draw_line(Vector2(bounding_box.position.x, yorigin), Vector2(bounding_box.position.x + bounding_box.size.x, yorigin), Color.black, 1, 0)
+ draw_string(chart_properties.font, Vector2(xorigin, yorigin) - Vector2(15, -15), "O", chart_properties.colors.bounding_box)
+
+func _draw_background() -> void:
+ draw_rect(node_box, Color.white, true, 1.0, false)
+
+# # (debug)
+# var half: Vector2 = node_box.size / 2
+# draw_line(Vector2(half.x, node_box.position.y), Vector2(half.x, node_box.size.y), Color.red, 3, false)
+# draw_line(Vector2(node_box.position.x, half.y), Vector2(node_box.size.x, half.y), Color.red, 3, false)
+
+func _draw_tick(from: Vector2, to: Vector2, color: Color) -> void:
+ draw_line(from, to, color, 1, true)
+
+
+func _get_vertical_tick_label_pos(base_position: Vector2, text: String) -> Vector2:
+ return base_position + Vector2(
+ - chart_properties.font.get_string_size(text).x / 2,
+ _x_label_size.y + _x_tick_size
+ )
+
+func _get_tick_label(line_index: int, line_value: float) -> String:
+ var tick_lbl: String = ""
+ if x_labels.empty():
+ tick_lbl = ("%.2f" if x_has_decimals else "%s") % [x_min_max.left + (line_index * line_value)]
+ else:
+ tick_lbl = x_labels[clamp(line_value * line_index, 0, x_labels.size() - 1)]
+
+ return tick_lbl
+
+func _draw_vertical_gridline_component(p1: Vector2, p2: Vector2, line_index: int, line_value: float) -> void:
+ if chart_properties.labels:
+ var tick_lbl: String = _get_tick_label(line_index, line_value)
+ draw_string(
+ chart_properties.font,
+ _get_vertical_tick_label_pos(p2, tick_lbl),
+ tick_lbl,
+ chart_properties.colors.bounding_box
+ )
+
+ # Draw V Ticks
+ if chart_properties.ticks:
+ _draw_tick(p2, p2 + Vector2(0, _x_tick_size), chart_properties.colors.bounding_box)
+
+ # Draw V Grid Lines
+ if chart_properties.grid:
+ draw_line(p1, p2, chart_properties.colors.grid, 1, true)
+
+
+func _draw_horizontal_tick_label(font: Font, position: Vector2, color: Color, line_index: int, line_value: float) -> void:
+ var tick_lbl: String = ""
+ if y_labels.empty():
+ tick_lbl = ("%.2f" if y_has_decimals else "%s") % [y_domain.left + (line_index * line_value)]
+ else:
+ tick_lbl = y_labels[clamp(y_labels.size() * line_index, 0, y_labels.size() - 1)]
+
+ draw_string(
+ chart_properties.font,
+ position - Vector2(
+ chart_properties.font.get_string_size(tick_lbl).x + _y_ticklabel_offset + _y_tick_size,
+ - _y_ticklabel_size.y * 0.35
+ ),
+ tick_lbl,
+ chart_properties.colors.bounding_box
+ )
+
+
+func _draw_horizontal_gridline_component(p1: Vector2, p2: Vector2, line_index: int, line_value: float) -> void:
+ # Draw H labels
+ if chart_properties.labels:
+ _draw_horizontal_tick_label(
+ chart_properties.font,
+ p1,
+ chart_properties.colors.bounding_box,
+ line_index,
+ line_value
+ )
+
+ # Draw H Ticks
+ if chart_properties.ticks:
+ _draw_tick(p1, p1 - Vector2(_y_tick_size, 0), chart_properties.colors.bounding_box)
+
+ # Draw H Grid Lines
+ if chart_properties.grid:
+ draw_line(p1, p2, chart_properties.colors.grid, 1, true)
+
+func _draw_vertical_grid() -> void:
+ # draw vertical lines
+
+ # 1. the amount of lines is equals to the X_scale: it identifies in how many sectors the x domain
+ # should be devided
+ # 2. calculate the spacing between each line in pixel. It is equals to x_sampled_domain / x_scale
+ # 3. calculate the offset in the real x domain, which is x_domain / x_scale.
+ var x_pixel_dist: float = (x_sampled.min_max.right - x_sampled.min_max.left) / (chart_properties.x_scale)
+ var x_lbl_val: float = (x_min_max.right - x_min_max.left) / (chart_properties.x_scale)
+ for _x in chart_properties.x_scale + 1:
+ var x_val: float = _x * x_pixel_dist + x_sampled.min_max.left
+ var top: Vector2 = Vector2(
+ range_lerp(x_val, x_sampled.min_max.left, x_sampled.min_max.right, x_sampled_domain.left, x_sampled_domain.right),
+ bounding_box.position.y
+ )
+ var bottom: Vector2 = Vector2(
+ range_lerp(x_val, x_sampled.min_max.left, x_sampled.min_max.right, x_sampled_domain.left, x_sampled_domain.right),
+ bounding_box.size.y + bounding_box.position.y
+ )
+
+ _draw_vertical_gridline_component(top, bottom, _x, x_lbl_val)
+
+func _draw_horizontal_grid() -> void:
+ # 1. the amount of lines is equals to the y_scale: it identifies in how many sectors the y domain
+ # should be devided
+ # 2. calculate the spacing between each line in pixel. It is equals to y_sampled_domain / y_scale
+ # 3. calculate the offset in the real y domain, which is y_domain / y_scale.
+ var y_pixel_dist: float = (y_sampled.min_max.right - y_sampled.min_max.left) / (chart_properties.y_scale)
+ var y_lbl_val: float = (y_domain.right - y_domain.left) / (chart_properties.y_scale)
+ for _y in chart_properties.y_scale + 1:
+ var y_val: float = (_y * y_pixel_dist) + y_sampled.min_max.left
+ var left: Vector2 = Vector2(
+ bounding_box.position.x,
+ range_lerp(y_val, y_sampled.min_max.left, y_sampled.min_max.right, y_sampled_domain.left, y_sampled_domain.right)
+ )
+ var right: Vector2 = Vector2(
+ bounding_box.size.x + bounding_box.position.x,
+ range_lerp(y_val, y_sampled.min_max.left, y_sampled.min_max.right, y_sampled_domain.left, y_sampled_domain.right)
+ )
+
+ _draw_horizontal_gridline_component(left, right, _y, y_lbl_val)
+
+func _draw_grid() -> void:
+ var validation: int = _validate_sampled_axis(x_sampled, y_sampled)
+ if not validation == OK:
+ printerr("Cannot draw grid for invalid dataset! Error: %s" % validation)
+ return
+
+ _draw_vertical_grid()
+ _draw_horizontal_grid()
+
+func _create_canvas_label(text: String, position: Vector2, rotation: float = 0.0) -> Label:
+ var lbl: Label = Label.new()
+ $Canvas.add_child(lbl)
+ lbl.set("custom_fonts/font", chart_properties.font)
+ lbl.set_text(text)
+ lbl.modulate = chart_properties.colors.bounding_box
+ lbl.rect_rotation = rotation
+ lbl.rect_position = position
+ return lbl
+
+func _update_canvas_label(canvas_label: Label, text: String, position: Vector2, rotation: float = 0.0) -> void:
+ canvas_label.set_text(text)
+ canvas_label.modulate = chart_properties.colors.bounding_box
+ canvas_label.rect_rotation = rotation
+ canvas_label.rect_position = position
+
+func _draw_yaxis_label() -> void:
+ _update_canvas_label(
+ $Canvas/YLabel,
+ chart_properties.y_label,
+ Vector2(_padding_offset.x, (node_box.size.y / 2) + (_y_label_size.x / 2)),
+ -90
+ )
+
+func _draw_xaxis_label() -> void:
+ _update_canvas_label(
+ $Canvas/XLabel,
+ chart_properties.x_label,
+ Vector2(
+ node_box.size.x/2 - (_x_label_size.x / 2),
+ node_box.size.y - _padding_offset.y - _x_label_size.y
+ )
+ )
+
+func _draw_title() -> void:
+ _update_canvas_label(
+ $Canvas/Title,
+ chart_properties.title,
+ Vector2(node_box.size.x / 2, _padding_offset.y*2) - (chart_properties.font.get_string_size(chart_properties.title) / 2)
+ )
+
+func _clear_canvas_labels() -> void:
+ for label in $Canvas.get_children():
+ label.queue_free()
+
+func _clear() -> void:
+ _clear_canvas_labels()
+
+# Draw Loop:
+# the drow loop gives order to what thigs will be drawn
+# each chart specifies its own draw loop that inherits from this one.
+# The draw loop also contains the "processing loop" which is where
+# everything is calculated in a separated function.
+func _draw():
+ if not (validate_input_samples(x) and validate_input_samples(y)):
+ printerr("Input samples are invalid!")
+ return
+
+ _clear()
+ _pre_process()
+
+ if chart_properties.background:
+ _draw_background()
+
+ if chart_properties.borders:
+ _draw_borders()
+
+ if chart_properties.grid or chart_properties.ticks or chart_properties.labels:
+ _draw_grid()
+
+ if chart_properties.bounding_box:
+ _draw_bounding_box()
+
+ if chart_properties.origin:
+ _draw_origin()
+
+ if chart_properties.labels:
+ _draw_xaxis_label()
+ _draw_yaxis_label()
+ _draw_title()
+
+func _validate_sampled_axis(x_data: SampledAxis, y_data: SampledAxis) -> int:
+ var error: int = 0 # OK
+ if x_data.values.empty() or y_data.values.empty():
+ # Either there are no X or Y
+ error = 1
+ elif y_data.values[0] is Array:
+ for dim in y_data.values:
+ if dim.size() != x_data.values.size():
+ error = 3 # one of Y dim has not X length
+ break
+ else:
+ if y_data.values.size() != x_data.values.size():
+ # X and Y samples don't have same length
+ error = 2
+ return error
+
+# ----- utilities
+func _get_function_name(function_idx: int) -> String:
+ return functions_names[function_idx] if functions_names.size() > 0 else "Function %s" % function_idx
+
+func _get_function_color(function_idx: int) -> Color:
+ return chart_properties.colors.functions[function_idx] if chart_properties.colors.functions.size() > 0 else Color.black
diff --git a/game/addons/easy_charts/control_charts/chart.tscn b/game/addons/easy_charts/control_charts/chart.tscn
new file mode 100644
index 0000000..ee1235c
--- /dev/null
+++ b/game/addons/easy_charts/control_charts/chart.tscn
@@ -0,0 +1,30 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/chart.gd" type="Script" id=1]
+[ext_resource path="res://addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.tscn" type="PackedScene" id=2]
+
+[node name="Chart" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+
+[node name="Canvas" type="Control" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+
+[node name="Title" type="Label" parent="Canvas"]
+margin_right = 40.0
+margin_bottom = 14.0
+
+[node name="XLabel" type="Label" parent="Canvas"]
+margin_right = 40.0
+margin_bottom = 14.0
+
+[node name="YLabel" type="Label" parent="Canvas"]
+margin_right = 40.0
+margin_bottom = 14.0
+
+[node name="Tooltip" parent="." instance=ExtResource( 2 )]
+visible = false
+margin_right = 20.0
+margin_bottom = 16.0
diff --git a/game/addons/easy_charts/examples/bar_chart/Control.gd b/game/addons/easy_charts/examples/bar_chart/Control.gd
new file mode 100644
index 0000000..e7b118d
--- /dev/null
+++ b/game/addons/easy_charts/examples/bar_chart/Control.gd
@@ -0,0 +1,50 @@
+extends Control
+
+onready var chart: BarChart = $BarChart
+
+func _ready():
+ # Let's create our @x values
+ var x: Array = ["Day 1", "Day 2", "Day 3", "Day 4", "Day 5"]
+ # And our y values. It can be an n-size array of arrays.
+ # NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
+ var y: Array = [
+ 20, 10, -15, 30, 42
+ ]
+
+ # Add some labels for the x axis, we don't want to use our x values array
+ # they will be printed on the chart ticks instead of the value of the x axis.
+ var x_labels: Array = ArrayOperations.suffix(x, "s")
+
+ # Let's customize the chart properties, which specify how the chart
+ # should look, plus some additional elements like labels, the scale, etc...
+ var cp: ChartProperties = ChartProperties.new()
+ cp.grid = true
+ cp.title = "Air Quality Monitoring"
+ cp.x_label = ("Days")
+ cp.x_scale = 20
+ cp.y_label = ("Sensor values")
+ cp.y_scale = 10
+ cp.points = false
+ cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
+ # and interecept clicks on the plot
+
+ # Set the x_labels
+# $LineChart.x_labels = x_labels
+
+ # Plot our data
+ chart.plot(x, y, cp)
+
+ # Uncommenting this line will show how real time data plotting works
+ set_process(false)
+
+func _process(delta: float):
+ # This function updates the values of chart x, y, and x_labels array
+ # and updaptes the plot
+ var new_val: String = "Day %s" % (chart.x.size() + 1)
+ chart.x.append(new_val)
+ chart.y.append(randi() % 40)
+ chart.update()
+
+
+func _on_CheckButton_pressed():
+ set_process(not is_processing())
diff --git a/game/addons/easy_charts/examples/bar_chart/Control.tscn b/game/addons/easy_charts/examples/bar_chart/Control.tscn
new file mode 100644
index 0000000..a8e7dc4
--- /dev/null
+++ b/game/addons/easy_charts/examples/bar_chart/Control.tscn
@@ -0,0 +1,49 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/BarChart/bar_chart.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/easy_charts/examples/bar_chart/Control.gd" type="Script" id=2]
+
+[sub_resource type="StyleBoxFlat" id=1]
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+draw_center = false
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color( 0, 0, 0, 1 )
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="BarChart" parent="." instance=ExtResource( 1 )]
+
+[node name="CheckButton" type="CheckButton" parent="."]
+margin_right = 223.0
+margin_bottom = 40.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+custom_colors/font_color_pressed = Color( 0, 0, 0, 1 )
+custom_colors/font_color_hover = Color( 0, 0, 0, 1 )
+custom_colors/font_color_hover_pressed = Color( 0, 0, 0, 1 )
+custom_colors/font_color_focus = Color( 0, 0, 0, 1 )
+custom_colors/font_color_disabled = Color( 0, 0, 0, 1 )
+text = "Start Relatime Plotting"
+
+[node name="Label" type="Label" parent="."]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = -159.0
+margin_top = -19.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+custom_styles/normal = SubResource( 1 )
+text = "Try to scale the window!"
+__meta__ = {
+"_edit_lock_": true
+}
+
+[connection signal="pressed" from="CheckButton" to="." method="_on_CheckButton_pressed"]
diff --git a/game/addons/easy_charts/examples/line_chart/Control.gd b/game/addons/easy_charts/examples/line_chart/Control.gd
new file mode 100644
index 0000000..80b38bb
--- /dev/null
+++ b/game/addons/easy_charts/examples/line_chart/Control.gd
@@ -0,0 +1,55 @@
+extends Control
+
+func _ready():
+ # Let's create our @x values
+ var x: Array = ArrayOperations.multiply_float(range(-2*PI, +2*PI, 1), 0.5)
+ # And our y values. It can be an n-size array of arrays.
+ # NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
+ var y: Array = [
+ ArrayOperations.multiply_float(ArrayOperations.cos(x), 1.0),
+ ArrayOperations.multiply_float(ArrayOperations.sin(x), 1.0)
+ ]
+
+ # Add some labels for the x axis, we don't want to use our x values array
+ # they will be printed on the chart ticks instead of the value of the x axis.
+ var x_labels: Array = ArrayOperations.suffix(x, "s")
+
+ # Let's customize the chart properties, which specify how the chart
+ # should look, plus some additional elements like labels, the scale, etc...
+ var cp: ChartProperties = ChartProperties.new()
+ cp.grid = false
+ cp.origin = true
+ cp.title = "Air Quality Monitoring"
+ cp.x_label = ("Time")
+ cp.x_scale = 10
+ cp.y_label = ("Sensor values")
+ cp.y_scale = 10
+ cp.points = true
+ cp.line_width = 2.0
+ cp.point_radius = 2.5
+ cp.use_splines = true
+ cp.interactive = false # false by default, it allows the chart to create a tooltip to show point values
+ # and interecept clicks on the plot
+
+ # Set the x_labels
+# $LineChart.x_labels = x_labels
+
+ # Plot our data
+ $LineChart.plot(x, y, cp)
+
+ # Uncommenting this line will show how real time data plotting works
+ set_process(false)
+
+func _process(delta: float):
+ # This function updates the values of chart x, y, and x_labels array
+ # and updaptes the plot
+ var new_val: float = $LineChart.x.back() + 1
+ $LineChart.x.append(new_val)
+ $LineChart.y[0].append(cos(new_val))
+ $LineChart.y[1].append(2 + sin(new_val))
+ $LineChart.x_labels.append(str(new_val) + "s")
+ $LineChart.update()
+
+
+func _on_CheckButton_pressed():
+ set_process(not is_processing())
diff --git a/game/addons/easy_charts/examples/line_chart/Control.tscn b/game/addons/easy_charts/examples/line_chart/Control.tscn
new file mode 100644
index 0000000..70e09b7
--- /dev/null
+++ b/game/addons/easy_charts/examples/line_chart/Control.tscn
@@ -0,0 +1,49 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/LineChart/line_chart.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/easy_charts/examples/line_chart/Control.gd" type="Script" id=2]
+
+[sub_resource type="StyleBoxFlat" id=1]
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+draw_center = false
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color( 0, 0, 0, 1 )
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="LineChart" parent="." instance=ExtResource( 1 )]
+
+[node name="CheckButton" type="CheckButton" parent="."]
+margin_right = 223.0
+margin_bottom = 40.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+custom_colors/font_color_pressed = Color( 0, 0, 0, 1 )
+custom_colors/font_color_hover = Color( 0, 0, 0, 1 )
+custom_colors/font_color_hover_pressed = Color( 0, 0, 0, 1 )
+custom_colors/font_color_focus = Color( 0, 0, 0, 1 )
+custom_colors/font_color_disabled = Color( 0, 0, 0, 1 )
+text = "Start Relatime Plotting"
+
+[node name="Label" type="Label" parent="."]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = -159.0
+margin_top = -19.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+custom_styles/normal = SubResource( 1 )
+text = "Try to scale the window!"
+__meta__ = {
+"_edit_lock_": true
+}
+
+[connection signal="pressed" from="CheckButton" to="." method="_on_CheckButton_pressed"]
diff --git a/game/addons/easy_charts/examples/scatter_chart/Control.gd b/game/addons/easy_charts/examples/scatter_chart/Control.gd
new file mode 100644
index 0000000..3f32c30
--- /dev/null
+++ b/game/addons/easy_charts/examples/scatter_chart/Control.gd
@@ -0,0 +1,50 @@
+extends Control
+
+func _ready():
+ # Let's create our @x values
+ var x: Array = ArrayOperations.multiply_float(range(-10, 10, 1), 0.5)
+ # And our y values. It can be an n-size array of arrays.
+ # NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
+ var y: Array = [
+ ArrayOperations.multiply_int(ArrayOperations.cos(x), 20),
+ ArrayOperations.add_float(ArrayOperations.multiply_int(ArrayOperations.sin(x), 20), 20)
+ ]
+ # Add some labels for the x axis, we don't want to use our x values array
+ # they will be printed on the chart ticks instead of the value of the x axis.
+ var x_labels: Array = ArrayOperations.suffix(x, "s")
+
+ # Let's customize the chart properties, which specify how the chart
+ # should look, plus some additional elements like labels, the scale, etc...
+ var cp: ChartProperties = ChartProperties.new()
+ cp.grid = true
+ cp.origin = false
+ cp.title = "Air Quality Monitoring"
+ cp.x_label = ("Time")
+ cp.x_scale = 10
+ cp.y_label = ("Sensor values")
+ cp.y_scale = 30
+ cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
+ # and interecept clicks on the plot
+
+ # Set the x_labels
+# $ScatterChart.x_labels = x_labels
+
+ # Plot our data
+ $ScatterChart.plot(x, y, cp)
+
+ # Uncommenting this line will show how real time data plotting works
+ set_process(false)
+
+func _process(delta: float):
+ # This function updates the values of chart x, y, and x_labels array
+ # and updaptes the plot
+ var new_val: float = $ScatterChart.x.back() + 1
+ $ScatterChart.x.append(new_val)
+ $ScatterChart.y[0].append(cos(new_val) * 20)
+ $ScatterChart.y[1].append(20 + sin(new_val) * 20)
+ $ScatterChart.x_labels.append(str(new_val) + "s")
+ $ScatterChart.update()
+
+
+func _on_CheckButton_pressed():
+ set_process(not is_processing())
diff --git a/game/addons/easy_charts/examples/scatter_chart/Control.tscn b/game/addons/easy_charts/examples/scatter_chart/Control.tscn
new file mode 100644
index 0000000..433d9b0
--- /dev/null
+++ b/game/addons/easy_charts/examples/scatter_chart/Control.tscn
@@ -0,0 +1,49 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/easy_charts/control_charts/ScatterChart/scatter_chart.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/easy_charts/examples/scatter_chart/Control.gd" type="Script" id=2]
+
+[sub_resource type="StyleBoxFlat" id=1]
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+draw_center = false
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color( 0, 0, 0, 1 )
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="ScatterChart" parent="." instance=ExtResource( 1 )]
+
+[node name="CheckButton" type="CheckButton" parent="."]
+margin_right = 223.0
+margin_bottom = 40.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+custom_colors/font_color_pressed = Color( 0, 0, 0, 1 )
+custom_colors/font_color_hover = Color( 0, 0, 0, 1 )
+custom_colors/font_color_hover_pressed = Color( 0, 0, 0, 1 )
+custom_colors/font_color_focus = Color( 0, 0, 0, 1 )
+custom_colors/font_color_disabled = Color( 0, 0, 0, 1 )
+text = "Start Relatime Plotting"
+
+[node name="Label" type="Label" parent="."]
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = -159.0
+margin_top = -19.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+custom_styles/normal = SubResource( 1 )
+text = "Try to scale the window!"
+__meta__ = {
+"_edit_lock_": true
+}
+
+[connection signal="pressed" from="CheckButton" to="." method="_on_CheckButton_pressed"]
diff --git a/game/addons/easy_charts/icon.png b/game/addons/easy_charts/icon.png
new file mode 100644
index 0000000..c1c6872
Binary files /dev/null and b/game/addons/easy_charts/icon.png differ
diff --git a/game/addons/easy_charts/icon.png.import b/game/addons/easy_charts/icon.png.import
new file mode 100644
index 0000000..2b03f03
--- /dev/null
+++ b/game/addons/easy_charts/icon.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-85017c6eecaf83ace12c82c530e61e9d.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/easy_charts/icon.png"
+dest_files=[ "res://.import/icon.png-85017c6eecaf83ace12c82c530e61e9d.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/game/addons/easy_charts/plugin.cfg b/game/addons/easy_charts/plugin.cfg
new file mode 100644
index 0000000..64b2573
--- /dev/null
+++ b/game/addons/easy_charts/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="EasyCharts"
+description=""
+author="Nicolò \"fenix\" Santilio"
+version="2.0.0"
+script="plugin.gd"
diff --git a/game/addons/easy_charts/plugin.gd b/game/addons/easy_charts/plugin.gd
new file mode 100644
index 0000000..7aa2099
--- /dev/null
+++ b/game/addons/easy_charts/plugin.gd
@@ -0,0 +1,9 @@
+tool
+extends EditorPlugin
+
+func _enter_tree():
+ add_autoload_singleton("ECUtilities","res://addons/easy_charts/utilities/scripts/ec_utilities.gd")
+
+func _exit_tree():
+ remove_autoload_singleton("ECUtilities")
+
diff --git a/game/addons/easy_charts/templates.json b/game/addons/easy_charts/templates.json
new file mode 100644
index 0000000..cdf9d1f
--- /dev/null
+++ b/game/addons/easy_charts/templates.json
@@ -0,0 +1,44 @@
+{
+ "default":
+ {
+ "function_colors" : ["#1e1e1e","#1e1e1e","#1e1e1e","#1e1e1e"],
+ "v_lines_color" : "#cacaca",
+ "h_lines_color" : "#cacaca",
+ "outline_color" : "#1e1e1e",
+ "font_color" : "#1e1e1e"
+ },
+ "clean":
+ {
+ "function_colors" : ["#f7aa29","#f4394a","#5a6b7b","#8fbf59","#504538","#B7A99A","#00D795","#FFECCC","#FF8981"],
+ "v_lines_color" : "#00000000",
+ "h_lines_color" : "#3cffffff",
+ "outline_color" : "#00000000",
+ "font_color" : "#3cffffff"
+ },
+ "gradient":
+ {
+ "function_colors" : ["#F7AA29","#B8A806","#79A117","#2C9433","#00854C","#006571","#2F4858","#2a364f","#27294a"],
+ "v_lines_color" : "#64ffffff",
+ "h_lines_color" : "#64ffffff",
+ "outline_color" : "#64ffffff",
+ "font_color" : "#64ffffff",
+ },
+ "minimal":
+ {
+ "function_colors" : ["#1e1e1e","#1e1e1e","#1e1e1e","#1e1e1e"],
+ "v_lines_color" : "#00000000",
+ "h_lines_color" : "#00000000",
+ "outline_color" : "#00000000",
+ "font_color" : "#00000000"
+ },
+ "invert":
+ {
+ "function_colors" : ["#ffffff","#ffffff","#ffffff","#ffffff"],
+ "v_lines_color" : "#3b3b3b",
+ "h_lines_color" : "#3b3b3b",
+ "outline_color" : "#ffffff",
+ "font_color" : "#ffffff"
+ },
+}
+
+
diff --git a/game/addons/easy_charts/utilities/classes/plotting/bar.gd b/game/addons/easy_charts/utilities/classes/plotting/bar.gd
new file mode 100644
index 0000000..b9491dc
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/plotting/bar.gd
@@ -0,0 +1,12 @@
+extends Reference
+class_name Bar
+
+var rect: Rect2
+var value: Pair
+
+func _init(rect: Rect2, value: Pair = Pair.new()) -> void:
+ self.value = value
+ self.rect = rect
+
+func _to_string() -> String:
+ return "Value: %s\nRect: %s" % [self.value, self.rect]
diff --git a/game/addons/easy_charts/utilities/classes/plotting/chart_properties.gd b/game/addons/easy_charts/utilities/classes/plotting/chart_properties.gd
new file mode 100644
index 0000000..7fba404
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/plotting/chart_properties.gd
@@ -0,0 +1,43 @@
+extends Reference
+class_name ChartProperties
+
+var title: String
+var x_label: String
+var y_label: String
+
+var x_scale: float = 5.0
+var y_scale: float = 2.0
+
+# Scale type, 0 = linear | 1 = logarithmic
+var x_scale_type: int = 0
+var y_scale_type: int = 0
+
+var borders: bool = false
+var background: bool = true
+var bounding_box: bool = true
+var grid: bool = false
+var ticks: bool = true
+var labels: bool = true
+var origin: bool = false
+var points: bool = true
+var interactive: bool = false
+
+var use_splines: bool = false
+
+var colors: Dictionary = {
+ "bounding_box": Color.black,
+ "grid": Color.gray,
+ "functions": [Color.red, Color.green, Color.blue, Color.black]
+}
+
+var point_radius: float = 3.0
+var line_width: float = 1.0
+var bar_width: float = 10.0
+var shapes: Array = [Point.Shape.CIRCLE, Point.Shape.SQUARE, Point.Shape.TRIANGLE, Point.Shape.CROSS]
+var font: BitmapFont = Label.new().get_theme_font("")
+
+func get_function_color(function_index: int) -> Color:
+ return colors.functions[function_index] if function_index < colors.functions.size() else Color.black
+
+func get_point_shape(function_index: int) -> int:
+ return shapes[function_index] if function_index < shapes.size() else Point.Shape.CIRCLE
diff --git a/game/addons/easy_charts/utilities/classes/plotting/point.gd b/game/addons/easy_charts/utilities/classes/plotting/point.gd
new file mode 100644
index 0000000..9e13b36
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/plotting/point.gd
@@ -0,0 +1,20 @@
+tool
+extends Reference
+class_name Point
+
+enum Shape {
+ CIRCLE,
+ TRIANGLE,
+ SQUARE,
+ CROSS
+}
+
+var position: Vector2
+var value: Pair
+
+func _init(position: Vector2, value: Pair = Pair.new()) -> void:
+ self.value = value
+ self.position = position
+
+func _to_string() -> String:
+ return "Value: %s\nPosition: %s" % [self.value, self.position]
diff --git a/game/addons/easy_charts/utilities/classes/plotting/sampled_axis.gd b/game/addons/easy_charts/utilities/classes/plotting/sampled_axis.gd
new file mode 100644
index 0000000..7fc9e03
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/plotting/sampled_axis.gd
@@ -0,0 +1,13 @@
+extends Reference
+class_name SampledAxis
+
+var values: Array
+var min_max: Pair
+
+func _init(values: Array = [], min_max: Pair = Pair.new()) -> void:
+ self.values = values
+ self.min_max = min_max
+
+func _to_string() -> String:
+ return "values: %s\nmin: %s, max: %s" % [self.values, self.min_max.left, self.min_max.right]
+
diff --git a/game/addons/easy_charts/utilities/classes/structures/array_operations.gd b/game/addons/easy_charts/utilities/classes/structures/array_operations.gd
new file mode 100644
index 0000000..05b3922
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/structures/array_operations.gd
@@ -0,0 +1,56 @@
+extends Reference
+class_name ArrayOperations
+
+static func add_int(array: Array, _int: int) -> Array:
+ var t: Array = array.duplicate(true)
+ for ti in t.size():
+ t[ti] = int(t[ti] + _int)
+ return t
+
+static func add_float(array: Array, _float: float) -> Array:
+ var t: Array = array.duplicate(true)
+ for ti in t.size():
+ t[ti] = float(t[ti] + _float)
+ return t
+
+static func multiply_int(array: Array, _int: int) -> Array:
+ var t: Array = array.duplicate(true)
+ for ti in t.size():
+ t[ti] = int(t[ti] * _int)
+ return t
+
+static func multiply_float(array: Array, _float: float) -> Array:
+ var t: Array = array.duplicate(true)
+ for ti in t.size():
+ t[ti] = float(t[ti] * _float)
+ return t
+
+static func pow(array: Array, _int: int) -> Array:
+ var t: Array = array.duplicate(true)
+ for ti in t.size():
+ t[ti] = float(pow(t[ti], _int))
+ return t
+
+static func cos(array: Array) -> Array:
+ var t: Array = array.duplicate(true)
+ for val in array.size():
+ t[val] = cos(t[val])
+ return t
+
+static func sin(array: Array) -> Array:
+ var t: Array = array.duplicate(true)
+ for val in array.size():
+ t[val] = sin(t[val])
+ return t
+
+static func affix(array: Array, _string: String) -> Array:
+ var t: Array = array.duplicate(true)
+ for val in array.size():
+ t[val] = str(t[val]) + _string
+ return t
+
+static func suffix(array: Array, _string: String) -> Array:
+ var t: Array = array.duplicate(true)
+ for val in array.size():
+ t[val] = _string + str(t[val])
+ return t
diff --git a/game/addons/easy_charts/utilities/classes/structures/data_frame.gd b/game/addons/easy_charts/utilities/classes/structures/data_frame.gd
new file mode 100644
index 0000000..a8ed422
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/structures/data_frame.gd
@@ -0,0 +1,191 @@
+tool
+extends Resource
+class_name DataFrame
+
+var table_name : String = ""
+var labels : PoolStringArray = []
+var headers : PoolStringArray = []
+var datamatrix : Matrix = null
+var dataset : Array = []
+
+func _init(datamatrix : Matrix, headers : PoolStringArray = [], labels : PoolStringArray = [] , table_name : String = "") -> void:
+ if datamatrix.empty(): datamatrix.resize(labels.size(), headers.size())
+ if labels.empty() : for label in range(datamatrix.get_size().x) : labels.append(label as String)
+ if headers.empty() : for header in range(datamatrix.get_size().y) : headers.append(MatrixGenerator.get_letter_index(header))
+ build_dataframe(datamatrix, headers, labels, table_name)
+
+func build_dataframe(datamatrix : Matrix, headers : PoolStringArray = [], labels : PoolStringArray = [] , table_name : String = "") -> void:
+ self.datamatrix = datamatrix
+ self.headers = headers
+ self.labels = labels
+ self.table_name = table_name
+ self.dataset = build_dataset_from_matrix(datamatrix, headers, labels)
+
+func build_dataset_from_matrix(datamatrix : Matrix, headers : PoolStringArray, labels : PoolStringArray) -> Array:
+ var data : Array = datamatrix.to_array()
+ return build_dataset(data, headers, labels)
+
+func build_dataset(data : Array, headers : PoolStringArray, labels : PoolStringArray) -> Array:
+ var dataset : Array = [Array([" "]) + Array(headers)]
+ for row_i in range(labels.size()): dataset.append(([labels[row_i]] + data[row_i]) if not data.empty() else [labels[row_i]])
+ return dataset
+
+func insert_column(column : Array, header : String = "", index : int = dataset[0].size() - 1) -> void:
+ assert(column.size() == (datamatrix.rows() if not datamatrix.empty() else labels.size()), "error: the column size must match the dataset column size")
+ headers.insert(index, header if header != "" else MatrixGenerator.get_letter_index(index))
+ datamatrix.insert_column(column, index)
+ dataset = build_dataset_from_matrix(datamatrix, headers, labels)
+
+func insert_row(row : Array, label : String = "", index : int = dataset.size() - 1) -> PoolStringArray:
+ assert(row.size() == (datamatrix.columns() if not datamatrix.empty() else headers.size()), "error: the row size must match the dataset row size")
+ labels.insert(index, label if label != "" else str(index))
+ datamatrix.insert_row(row, index)
+ dataset = build_dataset_from_matrix(datamatrix, headers, labels)
+ return PoolStringArray([label] + row)
+
+func get_datamatrix() -> Matrix:
+ return datamatrix
+
+func get_dataset() -> Array:
+ return dataset
+
+func get_labels() -> PoolStringArray:
+ return labels
+
+func transpose():
+ build_dataframe(MatrixGenerator.transpose(datamatrix), labels, headers, table_name)
+
+func _to_string() -> String:
+ var last_string_len : int
+ for row in dataset:
+ for column in row:
+ var string_len : int = str(column).length()
+ last_string_len = string_len if string_len > last_string_len else last_string_len
+ var string : String = ""
+ for row_i in dataset.size():
+ for column_i in dataset[row_i].size():
+ string+="%*s" % [last_string_len+1, dataset[row_i][column_i]]
+ string+="\n"
+ string+="\n['{table_name}' : {rows} rows x {columns} columns]\n".format({
+ "rows": datamatrix.rows(),
+ "columns": datamatrix.columns(),
+ "table_name": table_name})
+ return string
+
+# ...............................................................................
+
+# Return a list of headers corresponding to a list of indexes
+func get_headers_names(indexes : PoolIntArray) -> PoolStringArray:
+ var headers : PoolStringArray = []
+ for index in indexes:
+ headers.append(dataset[0][index])
+ return headers
+
+# Returns the index of an header
+func get_column_index(header : String) -> int:
+ for headers_ix in range(dataset[0].size()):
+ if dataset[0][headers_ix] == header:
+ return headers_ix
+ return -1
+
+# Get a column by its header
+func get_column(header : String) -> Array:
+ var headers_i : int = get_column_index(header)
+ if headers_i!=-1:
+ return datamatrix.get_column(headers_i)
+ else:
+ return []
+
+# Get a list of columns by their headers
+func columns(headers : PoolStringArray) -> Matrix:
+ var values : Array = []
+ for header in headers:
+ values.append(get_column(header))
+ return MatrixGenerator.transpose(Matrix.new(values))
+
+
+# Get a column by its index
+func get_icolumn(index : int) -> Array:
+ return datamatrix.get_column(index)
+
+# Get a list of columns by their indexes
+func get_icolumns(indexes : PoolIntArray) -> Array:
+ var values : Array = []
+ for index in indexes:
+ values.append(datamatrix.get_column(index))
+ return values
+
+# Returns the list of labels corresponding to the list of indexes
+func get_labels_names(indexes : PoolIntArray) -> PoolStringArray:
+ var headers : PoolStringArray = []
+ for index in indexes:
+ headers.append(dataset[index][0])
+ return headers
+
+# Returns the index of a label
+func get_row_index(label : String) -> int:
+ for row in dataset.size():
+ if dataset[row][0] == label:
+ return row
+ return -1
+
+# Get a row by its label
+func get_row(label : String) -> Array:
+ var index : int = get_row_index(label)
+ if index == -1 :
+ return []
+ else:
+ return datamatrix.get_row(index)
+
+# Get a list of rows by their labels
+func rows(labels : Array) -> Matrix:
+ var values : Array = []
+ for label in labels:
+ values.append(get_row(label))
+ return Matrix.new(values)
+
+# Get a row by its index
+func get_irow(index : int) -> Array:
+ return datamatrix.get_row(index)
+
+# Get a list of rows by their indexes
+func get_irows(indexes : PoolIntArray) -> Array:
+ var values : Array = []
+ for index in indexes:
+ values.append(datamatrix.get_row(index))
+ return values
+
+# Returns a a group of rows or a group of columns, using indexes or names
+# dataset["0;5"] ---> Returns an array containing all rows from the 1st to the 4th
+# dataset["0:5"] ---> Returns an array containing all columns from the 1st to the 4th
+# dataset["label0;label5"] ---> Returns an array containing all row from the one with label == "label0" to the one with label == "label5"
+# dataset["header0:header0"] ---> Returns an array containing all columns from the one with label == "label0" to the one with label == "label5"
+func _get(_property : StringName):
+ var propertystr : String = _property
+ # ":" --> Columns
+ if ":" in propertystr:
+ var property : PoolStringArray = propertystr.split(":")
+ if (property[0]).is_valid_integer():
+ if property[1] == "*":
+ return get_icolumns(range(property[0] as int, headers.size()-1))
+ else:
+ return get_icolumns(range(property[0] as int, property[1] as int +1))
+ else:
+ if property[1] == "*":
+ return get_icolumns(range(get_column_index(property[0]), headers.size()-1))
+ else:
+ return get_icolumns(range(get_column_index(property[0]), get_column_index(property[1])))
+ # ";" --> Rows
+ elif ";" in propertystr:
+ var property : PoolStringArray = propertystr.split(";")
+ if (property[0]).is_valid_integer():
+ return get_irows(range(property[0] as int, property[1] as int + 1 ))
+ else:
+ return get_irows(range(get_row_index(property[0]), get_row_index(property[1])))
+ elif "," in propertystr:
+ var property : PoolStringArray = propertystr.split(",")
+ else:
+ if (propertystr as String).is_valid_integer():
+ return get_icolumn(propertystr as int)
+ else:
+ return get_column(propertystr)
diff --git a/game/addons/easy_charts/utilities/classes/structures/matrix.gd b/game/addons/easy_charts/utilities/classes/structures/matrix.gd
new file mode 100644
index 0000000..311ad8e
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/structures/matrix.gd
@@ -0,0 +1,175 @@
+tool
+extends Resource
+class_name Matrix
+
+var values : Array = []
+
+func _init(matrix : Array = [], size : int = 0) -> void:
+ values = matrix
+
+func insert_row(row : Array, index : int = values.size()) -> void:
+ if rows() != 0:
+ assert(row.size() == columns(), "the row size must match matrix row size")
+ values.insert(index, row)
+
+func update_row(row : Array, index : int) -> void:
+ assert(rows() > index, "the row size must match matrix row size")
+ values[index] = row
+
+func remove_row(index: int) -> void:
+ assert(rows() > index, "the row size must match matrix row size")
+ values.remove(index)
+
+func insert_column(column : Array, index : int = values[0].size()) -> void:
+ if columns() != 0:
+ assert(column.size() == rows(), "the column size must match matrix column size")
+ for row_idx in column.size():
+ values[row_idx].insert(index, column[row_idx])
+
+func update_column(column : Array, index : int) -> void:
+ assert(columns() > index, "the column size must match matrix column size")
+ for row_idx in column.size():
+ values[row_idx][index] = column[row_idx]
+
+func remove_column(index: int) -> void:
+ assert(columns() > index, "the column index must be at least equals to the rows count")
+ for row in get_rows():
+ row.remove(index)
+
+func resize(rows: int, columns: int) -> void:
+ for row in range(rows):
+ var row_column: Array = []
+ row_column.resize(columns)
+ values.append(row_column)
+
+func to_array() -> Array:
+ return values.duplicate(true)
+
+func get_size() -> Vector2:
+ return Vector2(rows(), columns())
+
+func rows() -> int:
+ return values.size()
+
+func columns() -> int:
+ return values[0].size() if rows() != 0 else 0
+
+func value(row: int, column: int) -> float:
+ return values[row][column]
+
+func set_value(value: float, row: int, column: int) -> void:
+ values[row][column] = value
+
+func get_column(column : int) -> Array:
+ assert(column < columns(), "index of the column requested (%s) exceedes matrix columns (%s)"%[column, columns()])
+ var column_array : Array = []
+ for row in values:
+ column_array.append(row[column])
+ return column_array
+
+func get_columns(from : int = 0, to : int = columns()-1) -> Array:
+ var values : Array = []
+ for column in range(from, to):
+ values.append(get_column(column))
+ return values
+# return MatrixGenerator.from_array(values)
+
+func get_row(row : int) -> Array:
+ assert(row < rows(), "index of the row requested (%s) exceedes matrix rows (%s)"%[row, rows()])
+ return values[row]
+
+func get_rows(from : int = 0, to : int = rows()-1) -> Array:
+ return values.slice(from, to)
+# return MatrixGenerator.from_array(values)
+
+func is_empty() -> bool:
+ return rows() == 0 and columns() == 0
+
+
+func is_square() -> bool:
+ return columns() == rows()
+
+
+func is_diagonal() -> bool:
+ if not is_square():
+ return false
+
+ for i in rows():
+ for j in columns():
+ if i != j and values[i][j] != 0:
+ return false
+
+ return true
+
+
+func is_upper_triangular() -> bool:
+ if not is_square():
+ return false
+
+ for i in rows():
+ for j in columns():
+ if i > j and values[i][j] != 0:
+ return false
+
+ return true
+
+
+func is_lower_triangular() -> bool:
+ if not is_square():
+ return false
+
+ for i in rows():
+ for j in columns():
+ if i < j and values[i][j] != 0:
+ return false
+
+ return true
+
+
+func is_triangular() -> bool:
+ return is_upper_triangular() or is_lower_triangular()
+
+
+func is_identity() -> bool:
+ if not is_diagonal():
+ return false
+
+ for i in rows():
+ if values[i][i] != 1:
+ return false
+
+ return true
+
+func _to_string() -> String:
+ var last_string_len : int
+ for row in values:
+ for column in row:
+ var string_len : int = str(column).length()
+ last_string_len = string_len if string_len > last_string_len else last_string_len
+ var string : String = "\n"
+ for row_i in values.size():
+ for column_i in values[row_i].size():
+ string+="%*s" % [last_string_len+1 if column_i!=0 else last_string_len, values[row_i][column_i]]
+ string+="\n"
+ return string
+
+# ----
+func set(position: String, value) -> void:
+ var t_pos: Array = position.split(",")
+ values[t_pos[0]][t_pos[1]] = value
+
+# --------------
+func _get(_property : StringName):
+ var propertystr : String = _property
+ # ":" --> Columns
+ if ":" in propertystr:
+ var property : PoolStringArray = propertystr.split(":")
+ var from : PoolStringArray = property[0].split(",")
+ var to : PoolStringArray = property[1].split(",")
+ elif "," in propertystr:
+ var property : PoolStringArray = propertystr.split(",")
+ if property.size() == 2:
+ return get_row(property[0] as int)[property[1] as int]
+ else:
+ if propertystr.is_valid_integer():
+ return get_row(propertystr as int)
diff --git a/game/addons/easy_charts/utilities/classes/structures/matrix_generator.gd b/game/addons/easy_charts/utilities/classes/structures/matrix_generator.gd
new file mode 100644
index 0000000..f86805a
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/structures/matrix_generator.gd
@@ -0,0 +1,160 @@
+tool
+extends Reference
+class_name MatrixGenerator
+
+static func zeros(rows: int, columns: int) -> Matrix:
+ var zeros: Array = []
+ var t_rows: Array = []
+ t_rows.resize(columns)
+ t_rows.fill(0.0)
+ for row in rows:
+ zeros.append(t_rows.duplicate())
+ return Matrix.new(zeros)
+
+# Generates a Matrix with random values between [from; to] with a given @size (rows, columns)
+static func random_float_range(from : float, to : float, size : Vector2, _seed : int = 1234) -> Matrix:
+ seed(_seed)
+ randomize()
+ var array : Array = []
+ for row in range(size.x):
+ var matrix_row : Array = []
+ for column in range(size.y): matrix_row.append(rand_range(from,to))
+ array.append(matrix_row)
+ return Matrix.new(array)
+
+# Generates a Matrix giving an Array (Array must by Array[Array])
+static func from_array(array : Array = []) -> Matrix:
+ var matrix : Array = []
+ matrix.append(array)
+ return Matrix.new(matrix)
+
+# Generates a sub-Matrix giving a Matrix, a @from Array [row_i, column_i] and a @to Array [row_j, column_j]
+static func sub_matrix(_matrix : Matrix, from : PoolIntArray, to : PoolIntArray) -> Matrix:
+ assert( not (to[0] > _matrix.rows() or to[1] > _matrix.columns()),
+ "%s is not an acceptable size for the submatrix, giving a matrix of size %s"%[to, _matrix.get_size()])
+ var array : Array = []
+ var rows : Array = _matrix.get_rows(from[0], to[0])
+ for row in rows:
+ array.append(row.slice(from[1], to[1]))
+ return Matrix.new(array)
+
+# Duplicates a given Matrix
+static func duplicate(_matrix : Matrix) -> Matrix:
+ return Matrix.new(_matrix.to_array().duplicate())
+
+# Calculate the determinant of a matrix
+static func determinant(matrix: Matrix) -> float:
+ assert(matrix.is_square(), "Expected square matrix")
+
+ var determinant: float = 0.0
+
+ if matrix.rows() == 2 :
+ determinant = (matrix.value(0, 0) * matrix.value(1, 1)) - (matrix.value(0, 1) * matrix.value(1, 0))
+ elif matrix.is_diagonal() or matrix.is_triangular() :
+ for i in matrix.rows():
+ determinant *= matrix.value(i, i)
+ elif matrix.is_identity() :
+ determinant = 1.0
+ else:
+ # Laplace expansion
+ var multiplier: float = -1.0
+ var submatrix: Matrix = sub_matrix(matrix, [1, 0], [matrix.rows(), matrix.columns()])
+ for j in matrix.columns() :
+ var cofactor: Matrix = copy(submatrix)
+ cofactor.remove_column(j)
+ multiplier *= -1.0
+ determinant += multiplier * matrix.value(0, j) * determinant(cofactor)
+
+ return determinant
+
+
+# Calculate the inverse of a Matrix
+static func inverse(matrix: Matrix) -> Matrix:
+ var inverse: Matrix
+
+ # Minors and Cofactors
+ var minors_cofactors: Matrix = zeros(matrix.rows(), matrix.columns())
+ var multiplier: float = -1.0
+
+ for i in minors_cofactors.rows():
+ for j in minors_cofactors.columns():
+ var t_minor: Matrix = copy(matrix)
+ t_minor.remove_row(i)
+ t_minor.remove_column(j)
+ multiplier *= -1.0
+ minors_cofactors.set_value(multiplier * determinant(t_minor), i, j)
+
+ var transpose: Matrix = transpose(minors_cofactors)
+ var determinant: float = determinant(matrix)
+
+ inverse = multiply_float(transpose, 1 / determinant)
+
+ return inverse
+
+# Transpose a given Matrix
+static func transpose(_matrix : Matrix) -> Matrix:
+ var array : Array = []
+ array.resize(_matrix.get_size().y)
+ var row : Array = []
+ row.resize(_matrix.get_size().x)
+ for x in array.size():
+ array[x] = row.duplicate()
+ for i in range(_matrix.get_size().x):
+ for j in range(_matrix.get_size().y):
+ array[j][i] = (_matrix.to_array()[i][j])
+ return Matrix.new(array)
+
+# Calculates the dot product (A*B) matrix between two Matrixes
+static func dot(_matrix1 : Matrix, _matrix2 : Matrix) -> Matrix:
+ if _matrix1.get_size().y != _matrix2.get_size().x:
+ printerr("matrix1 number of columns: %s must be the same as matrix2 number of rows: %s"%[_matrix1.get_size().y, _matrix2.get_size().x])
+ return Matrix.new()
+ var array : Array = []
+ for x in range(_matrix1.get_size().x):
+ var row : Array = []
+ for y in range(_matrix2.get_size().y):
+ var sum : float
+ for k in range(_matrix1.get_size().y):
+ sum += (_matrix1.to_array()[x][k]*_matrix2.to_array()[k][y])
+ row.append(sum)
+ array.append(row)
+ return Matrix.new(array)
+
+# Calculates the hadamard (element-wise product) between two Matrixes
+static func hadamard(_matrix1 : Matrix, _matrix2 : Matrix) -> Matrix:
+ if _matrix1.get_size() != _matrix2.get_size():
+ printerr("matrix1 size: %s must be the same as matrix2 size: %s"%[_matrix1.get_size(), _matrix2.get_size()])
+ return Matrix.new()
+ var array : Array = []
+ for x in range(_matrix1.to_array().size()):
+ var row : Array = []
+ for y in range(_matrix1.to_array()[x].size()):
+ assert(typeof(_matrix1.to_array()[x][y]) != TYPE_STRING and typeof(_matrix2.to_array()[x][y]) != TYPE_STRING, "can't apply operations over a Matrix of Strings")
+ row.append(_matrix1.to_array()[x][y] * _matrix2.to_array()[x][y])
+ array.append(row)
+ return Matrix.new(array)
+
+# Multiply a given Matrix for an int value
+static func multiply_int(_matrix1 : Matrix, _int : int) -> Matrix:
+ var array : Array = _matrix1.to_array().duplicate()
+ for x in range(_matrix1.to_array().size()):
+ for y in range(_matrix1.to_array()[x].size()):
+ array[x][y]*=_int
+ array[x][y] = int(array[x][y])
+ return Matrix.new(array)
+
+# Multiply a given Matrix for a float value
+static func multiply_float(_matrix1 : Matrix, _float : float) -> Matrix:
+ var array : Array = _matrix1.to_array().duplicate()
+ for x in range(_matrix1.to_array().size()):
+ for y in range(_matrix1.to_array()[x].size()):
+ array[x][y]*=_float
+ return Matrix.new(array)
+
+
+static func copy(matrix: Matrix) -> Matrix:
+ return Matrix.new(matrix.values.duplicate(true))
+
+# ------------------------------------------------------------
+static func get_letter_index(index : int) -> String:
+ return "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ")[index]
diff --git a/game/addons/easy_charts/utilities/classes/structures/pair.gd b/game/addons/easy_charts/utilities/classes/structures/pair.gd
new file mode 100644
index 0000000..063e87d
--- /dev/null
+++ b/game/addons/easy_charts/utilities/classes/structures/pair.gd
@@ -0,0 +1,25 @@
+"""
+A class representing a Pair (or Tuple) of values.
+It is a lightweight class that can easily replace the improper and/or
+unnecessary usage of a 2d Array (ex. `var arr: Array = [0.5, 0.6]`)
+or of a Vector2 (ex. `var v2: Vector2 = Vector2(0.6, 0.8)`).
+"""
+extends Reference
+class_name Pair
+
+var left
+var right
+
+func _init(left = null, right = null) -> void:
+ self.left = left
+ self.right = right
+
+func _format(val) -> String:
+ var format: String = "%s"
+ match typeof(val):
+ TYPE_REAL:
+ "%.2f"
+ return format % val
+
+func _to_string() -> String:
+ return "[%s, %s]" % [_format(self.left), _format(self.right)]
diff --git a/game/addons/easy_charts/utilities/components/rect/rect.gd b/game/addons/easy_charts/utilities/components/rect/rect.gd
new file mode 100644
index 0000000..e04c27c
--- /dev/null
+++ b/game/addons/easy_charts/utilities/components/rect/rect.gd
@@ -0,0 +1,66 @@
+extends Control
+
+var OFFSET : Vector2 = Vector2()
+var point_value : Array
+var point_position : Vector2
+var color : Color
+var color_outline : Color
+var function : String
+
+var mouse_entered : bool = false
+
+
+signal _mouse_entered()
+signal _mouse_exited()
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ pass # Replace with function body.
+
+
+func _draw():
+ if mouse_entered:
+ draw_rect(Rect2(rect_position - OFFSET,rect_size),color_outline,true,12,true)
+
+func create_point(color : Color, color_outline : Color, position : Vector2, size : Vector2, value : Array, function : String):
+
+ self.color = color
+ self.color_outline = color_outline
+ self.point_position = position
+ self.rect_position = point_position - OFFSET
+ self.rect_size = size
+ self.point_value = value
+ self.function = function
+
+func format_value(v : Array, format_x : bool, format_y : bool):
+ var x : String = str(v[0])
+ var y : String = str(v[1])
+
+ if format_x:
+ x = format(v[1])
+ if format_y:
+ y = format(v[1])
+
+ return [x,y]
+
+func format(n):
+ n = str(n)
+ var size = n.length()
+ var s
+ for i in range(size):
+ if((size - i) % 3 == 0 and i > 0):
+ s = str(s,",", n[i])
+ else:
+ s = str(s,n[i])
+
+ return s.replace("Null","")
+
+func _on_Rect_mouse_entered():
+ mouse_entered = true
+ emit_signal("_mouse_entered")
+ update()
+
+func _on_Rect_mouse_exited():
+ mouse_entered = false
+ emit_signal("_mouse_exited")
+ update()
diff --git a/game/addons/easy_charts/utilities/components/rect/rect.tscn b/game/addons/easy_charts/utilities/components/rect/rect.tscn
new file mode 100644
index 0000000..60509e0
--- /dev/null
+++ b/game/addons/easy_charts/utilities/components/rect/rect.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/easy_charts/utilities/components/rect/rect.gd" type="Script" id=1]
+
+[node name="Rect" type="Control"]
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[connection signal="mouse_entered" from="." to="." method="_on_Rect_mouse_entered"]
+[connection signal="mouse_exited" from="." to="." method="_on_Rect_mouse_exited"]
diff --git a/game/addons/easy_charts/utilities/components/slice/slice.gd b/game/addons/easy_charts/utilities/components/slice/slice.gd
new file mode 100644
index 0000000..6dab277
--- /dev/null
+++ b/game/addons/easy_charts/utilities/components/slice/slice.gd
@@ -0,0 +1,17 @@
+extends Reference
+class_name Slice
+
+var x_value : String
+var y_value : String
+var from_angle : float
+var to_angle : float
+var function : String
+var color : Color
+
+func _init(x : String, y : String, from : float, to : float, fun : String, col : Color):
+ x_value = x
+ y_value = y
+ from_angle = from
+ to_angle = to
+ function = fun
+ color = col
diff --git a/game/addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.gd b/game/addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.gd
new file mode 100644
index 0000000..9b5b858
--- /dev/null
+++ b/game/addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.gd
@@ -0,0 +1,36 @@
+tool
+extends PanelContainer
+class_name DataTooltip
+
+var value : String = ""
+var position : Vector2 = Vector2()
+
+onready var x_lbl : Label = $PointData/x
+onready var y_lbl : Label = $PointData/Value/y
+onready var func_lbl : Label = $PointData/Value/Function
+onready var color_rect: Panel = $PointData/Value/Color
+
+func _ready():
+ hide()
+ update_size()
+
+func _process(delta):
+ if Engine.editor_hint:
+ return
+ rect_position = get_global_mouse_position() + Vector2(15, - (get_rect().size.y / 2))
+
+func update_values(x: String, y: String, function: String, color: Color):
+ x_lbl.set_text(x)
+ y_lbl.set_text(y)
+ func_lbl.set_text(function)
+ color_rect.get("custom_styles/panel").set("bg_color", color)
+
+func update_size():
+ x_lbl.set_text("")
+ y_lbl.set_text("")
+ func_lbl.set_text("")
+ rect_size = Vector2.ZERO
+
+func _on_DataTooltip_visibility_changed():
+ if not visible:
+ update_size()
diff --git a/game/addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.tscn b/game/addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.tscn
new file mode 100644
index 0000000..d1862fe
--- /dev/null
+++ b/game/addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.tscn
@@ -0,0 +1,116 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.gd" type="Script" id=1]
+
+[sub_resource type="StyleBoxFlat" id=1]
+content_margin_left = 10.0
+content_margin_right = 10.0
+content_margin_top = 8.0
+content_margin_bottom = 8.0
+bg_color = Color( 0.101961, 0.101961, 0.101961, 0.784314 )
+border_color = Color( 1, 1, 1, 1 )
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+corner_detail = 20
+anti_aliasing_size = 0.9
+
+[sub_resource type="StyleBoxFlat" id=2]
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+corner_detail = 20
+anti_aliasing_size = 0.7
+
+[sub_resource type="StyleBoxEmpty" id=3]
+
+[node name="DataTooltip" type="PanelContainer"]
+margin_right = 169.0
+margin_bottom = 49.0
+mouse_filter = 2
+custom_styles/panel = SubResource( 1 )
+script = ExtResource( 1 )
+
+[node name="PointData" type="VBoxContainer" parent="."]
+margin_left = 10.0
+margin_top = 8.0
+margin_right = 159.0
+margin_bottom = 42.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+custom_constants/separation = 6
+alignment = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="x" type="Label" parent="PointData"]
+margin_right = 55.0
+margin_bottom = 14.0
+size_flags_horizontal = 0
+custom_colors/font_color = Color( 1, 1, 1, 1 )
+text = "{x value}"
+valign = 1
+
+[node name="Value" type="HBoxContainer" parent="PointData"]
+margin_top = 20.0
+margin_right = 149.0
+margin_bottom = 34.0
+grow_horizontal = 2
+size_flags_horizontal = 7
+custom_constants/separation = 1
+
+[node name="Color" type="Panel" parent="PointData/Value"]
+margin_top = 2.0
+margin_right = 10.0
+margin_bottom = 12.0
+rect_min_size = Vector2( 10, 10 )
+size_flags_horizontal = 4
+size_flags_vertical = 4
+custom_styles/panel = SubResource( 2 )
+
+[node name="VSeparator" type="VSeparator" parent="PointData/Value"]
+margin_left = 11.0
+margin_right = 15.0
+margin_bottom = 14.0
+custom_constants/separation = 4
+custom_styles/separator = SubResource( 3 )
+
+[node name="Function" type="Label" parent="PointData/Value"]
+margin_left = 16.0
+margin_right = 78.0
+margin_bottom = 14.0
+size_flags_horizontal = 0
+size_flags_vertical = 5
+text = "{function}"
+valign = 1
+
+[node name="sep" type="Label" parent="PointData/Value"]
+margin_left = 79.0
+margin_right = 83.0
+margin_bottom = 14.0
+size_flags_horizontal = 0
+size_flags_vertical = 5
+text = ":"
+valign = 1
+
+[node name="VSeparator2" type="VSeparator" parent="PointData/Value"]
+margin_left = 84.0
+margin_right = 88.0
+margin_bottom = 14.0
+custom_constants/separation = 4
+custom_styles/separator = SubResource( 3 )
+
+[node name="y" type="Label" parent="PointData/Value"]
+margin_left = 89.0
+margin_right = 144.0
+margin_bottom = 14.0
+size_flags_horizontal = 0
+size_flags_vertical = 5
+custom_colors/font_color = Color( 1, 1, 1, 1 )
+text = "{y value}"
+valign = 1
+
+[connection signal="visibility_changed" from="." to="." method="_on_DataTooltip_visibility_changed"]
diff --git a/game/addons/easy_charts/utilities/containers/legend/function_legend.gd b/game/addons/easy_charts/utilities/containers/legend/function_legend.gd
new file mode 100644
index 0000000..24596d5
--- /dev/null
+++ b/game/addons/easy_charts/utilities/containers/legend/function_legend.gd
@@ -0,0 +1,41 @@
+tool
+extends VBoxContainer
+class_name LegendElement
+
+onready var Function : Label = $Function
+onready var FunctionColor : ColorRect = $Color
+
+var function : String setget set_function, get_function
+var color : Color setget set_function_color, get_function_color
+var font_color : Color
+var font : Font
+
+func _ready():
+ Function.set("custom_fonts/font",font)
+ Function.set("custom_colors/font_color",font_color)
+ Function.set_text(function)
+ FunctionColor.set_frame_color(color)
+
+func create_legend(text : String, color : Color, font : Font, font_color : Color):
+ self.function = text
+ self.color = color
+ self.font_color = font_color
+ self.font = font
+
+func set_function( t : String ):
+ function = t
+
+func get_function() -> String:
+ return function
+
+func set_function_color( c : Color ):
+ color = c
+
+func get_function_color() -> Color:
+ return color
+
+func get_class() -> String:
+ return "Legend Element"
+
+func _to_string() -> String:
+ return "%s (%s, %s) " % [get_class(), get_function(), get_function_color().to_html(true)]
diff --git a/game/addons/easy_charts/utilities/containers/legend/function_legend.tscn b/game/addons/easy_charts/utilities/containers/legend/function_legend.tscn
new file mode 100644
index 0000000..8ee6c53
--- /dev/null
+++ b/game/addons/easy_charts/utilities/containers/legend/function_legend.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/easy_charts/utilities/containers/legend/function_legend.gd" type="Script" id=1]
+
+[node name="FunctionLegend" type="VBoxContainer"]
+margin_right = 80.0
+margin_bottom = 26.0
+alignment = 1
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Function" type="Label" parent="."]
+margin_top = 2.0
+margin_right = 80.0
+margin_bottom = 16.0
+custom_colors/font_color = Color( 0, 0, 0, 1 )
+align = 1
+valign = 1
+
+[node name="Color" type="ColorRect" parent="."]
+margin_top = 20.0
+margin_right = 80.0
+margin_bottom = 23.0
+rect_min_size = Vector2( 15, 3 )
+color = Color( 0, 0, 0, 1 )
diff --git a/game/addons/easy_charts/utilities/icons/linechart.svg b/game/addons/easy_charts/utilities/icons/linechart.svg
new file mode 100644
index 0000000..7d168d9
--- /dev/null
+++ b/game/addons/easy_charts/utilities/icons/linechart.svg
@@ -0,0 +1,155 @@
+
+
+
+
diff --git a/game/addons/easy_charts/utilities/icons/linechart.svg.import b/game/addons/easy_charts/utilities/icons/linechart.svg.import
new file mode 100644
index 0000000..4452b09
--- /dev/null
+++ b/game/addons/easy_charts/utilities/icons/linechart.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/linechart.svg-922834f0462a2c88be644081c47c63ad.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/easy_charts/utilities/icons/linechart.svg"
+dest_files=[ "res://.import/linechart.svg-922834f0462a2c88be644081c47c63ad.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/game/addons/easy_charts/utilities/icons/linechart2d.svg b/game/addons/easy_charts/utilities/icons/linechart2d.svg
new file mode 100644
index 0000000..46729be
--- /dev/null
+++ b/game/addons/easy_charts/utilities/icons/linechart2d.svg
@@ -0,0 +1,155 @@
+
+
+
+
diff --git a/game/addons/easy_charts/utilities/icons/linechart2d.svg.import b/game/addons/easy_charts/utilities/icons/linechart2d.svg.import
new file mode 100644
index 0000000..01a536c
--- /dev/null
+++ b/game/addons/easy_charts/utilities/icons/linechart2d.svg.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/linechart2d.svg-1067b05eddcc451c2fc80a8734aa8056.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/easy_charts/utilities/icons/linechart2d.svg"
+dest_files=[ "res://.import/linechart2d.svg-1067b05eddcc451c2fc80a8734aa8056.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/game/addons/easy_charts/utilities/scripts/ec_utilities.gd b/game/addons/easy_charts/utilities/scripts/ec_utilities.gd
new file mode 100644
index 0000000..8a44c69
--- /dev/null
+++ b/game/addons/easy_charts/utilities/scripts/ec_utilities.gd
@@ -0,0 +1,46 @@
+tool
+extends Node
+
+var alphabet : String = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
+
+func _ready():
+ pass
+# templates = _load_templates()
+
+func _print_message(message : String, type : int = 0):
+ pass
+# match type:
+# 0:
+# print("[%s] => %s" % [plugin_name, message])
+# 1:
+# printerr("ERROR: [%s] => %s" % [plugin_name, message])
+
+func _load_templates() -> Dictionary:
+ pass
+ return {}
+# var template_file : File = File.new()
+# template_file.open("res://addons/easy_charts/templates.json",File.READ)
+# var templates = JSON.parse(template_file.get_as_text()).get_result()
+# template_file.close()
+# return templates
+
+func get_template(template_index : int):
+ pass
+# return templates.get(templates.keys()[template_index])
+
+func get_chart_type(chart_type : int) -> Array:
+ pass
+ return []
+# return chart_types.get(chart_types.keys()[chart_type])
+
+func get_letter_index(index : int) -> String:
+ return alphabet.split(" ")[index]
+
+func save_dataframe_to_file(dataframe: DataFrame, path: String, delimiter: String = ";") -> void:
+ pass
+# var file = File.new()
+# file.open(path, File.WRITE)
+# for row in dataframe.get_dataset():
+# file.store_line(PoolStringArray(row).join(delimiter))
+# file.close()
+
diff --git a/game/project.pandemonium b/game/project.pandemonium
index 802b4d5..91c362a 100644
--- a/game/project.pandemonium
+++ b/game/project.pandemonium
@@ -8,12 +8,120 @@
config_version=4
+_global_script_classes=[ {
+"base": "Reference",
+"class": @"ArrayOperations",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/structures/array_operations.gd"
+}, {
+"base": "Reference",
+"class": @"Bar",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/plotting/bar.gd"
+}, {
+"base": "Chart",
+"class": @"BarChart",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/control_charts/BarChart/bar_chart.gd"
+}, {
+"base": "Control",
+"class": @"Chart",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/control_charts/chart.gd"
+}, {
+"base": "Reference",
+"class": @"ChartProperties",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/plotting/chart_properties.gd"
+}, {
+"base": "Resource",
+"class": @"DataFrame",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/structures/data_frame.gd"
+}, {
+"base": "PanelContainer",
+"class": @"DataTooltip",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.gd"
+}, {
+"base": "VBoxContainer",
+"class": @"LegendElement",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/containers/legend/function_legend.gd"
+}, {
+"base": "ScatterChart",
+"class": @"LineChart",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/control_charts/LineChart/line_chart.gd"
+}, {
+"base": "Resource",
+"class": @"Matrix",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/structures/matrix.gd"
+}, {
+"base": "Reference",
+"class": @"MatrixGenerator",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/structures/matrix_generator.gd"
+}, {
+"base": "Reference",
+"class": @"Pair",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/structures/pair.gd"
+}, {
+"base": "Reference",
+"class": @"Point",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/plotting/point.gd"
+}, {
+"base": "Reference",
+"class": @"SampledAxis",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/classes/plotting/sampled_axis.gd"
+}, {
+"base": "Chart",
+"class": @"ScatterChart",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/control_charts/ScatterChart/scatter_chart.gd"
+}, {
+"base": "Reference",
+"class": @"Slice",
+"language": @"GDScript",
+"path": "res://addons/easy_charts/utilities/components/slice/slice.gd"
+} ]
+_global_script_class_icons={
+@"Chart": "",
+@"BarChart": "",
+@"LineChart": "",
+@"ScatterChart": "",
+@"Bar": "",
+@"ChartProperties": "",
+@"Point": "",
+@"SampledAxis": "",
+@"ArrayOperations": "",
+@"DataFrame": "",
+@"Matrix": "",
+@"MatrixGenerator": "",
+@"Pair": "",
+@"Slice": "",
+@"DataTooltip": "",
+@"LegendElement": ""
+}
+
[application]
config/name="PMLPP Sample"
run/main_scene="res://Main.tscn"
config/icon="res://icon.png"
+[autoload]
+
+ECUtilities="*res://addons/easy_charts/utilities/scripts/ec_utilities.gd"
+
+[editor_plugins]
+
+enabled=PoolStringArray( "res://addons/easy_charts/plugin.cfg" )
+
[physics]
common/enable_pause_aware_picking=true