Added the easy charts addon.

This commit is contained in:
Relintai 2023-01-28 12:55:40 +01:00
parent 987f923346
commit 952078279a
41 changed files with 2856 additions and 0 deletions

View File

@ -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()

View File

@ -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
}

View File

@ -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()

View File

@ -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 )

View File

@ -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()

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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"]

View File

@ -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())

View File

@ -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"]

View File

@ -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())

View File

@ -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"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -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

View File

@ -0,0 +1,7 @@
[plugin]
name="EasyCharts"
description=""
author="Nicolò \"fenix\" Santilio"
version="2.0.0"
script="plugin.gd"

View File

@ -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")

View File

@ -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"
},
}

View File

@ -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]

View File

@ -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

View File

@ -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]

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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]

View File

@ -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)]

View File

@ -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()

View File

@ -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"]

View File

@ -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

View File

@ -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()

View File

@ -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"]

View File

@ -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)]

View File

@ -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 )

View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
viewBox="0 0 3.1749999 3.1750001"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="linechart.svg">
<defs
id="defs2">
<linearGradient
id="linearGradient5569"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5567" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="45.254834"
inkscape:cx="7.2321717"
inkscape:cy="4.3383636"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:snap-bbox="true"
inkscape:snap-others="true"
inkscape:snap-nodes="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-global="false"
inkscape:object-nodes="false"
inkscape:snap-grids="true"
inkscape:snap-to-guides="false"
inkscape:object-paths="false"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="2391"
inkscape:window-y="-9"
inkscape:window-maximized="1"
units="px"
scale-x="1e-05">
<inkscape:grid
type="xygrid"
id="grid1377"
empspacing="12" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Livello 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-293.82497)">
<rect
style="fill:#a5efac;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1486"
width="0.26458332"
height="0.26458332"
x="-1.0583333"
y="293.82498" />
<rect
style="fill:#a5b7f3;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1578"
width="0.26458332"
height="0.26458332"
x="-1.0583333"
y="294.08954" />
<rect
style="fill:#a5efac;fill-opacity:1;stroke:none;stroke-width:0.27538645;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5539"
width="0.52916664"
height="3.4395685"
x="-0.26458332"
y="293.82498" />
<rect
style="fill:#a5efac;fill-opacity:1;stroke:none;stroke-width:0.27634826;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5541"
width="3.175"
height="0.52916664"
x="1.3907751e-08"
y="296.73535" />
<rect
style="fill:#a5efac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347834;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010"
width="1.3280054"
height="0.39521819"
x="264.53415"
y="142.6459"
transform="matrix(0.48573212,0.87410772,-0.89524404,0.44557615,0,0)"
ry="0.1976091" />
<rect
style="fill:#a5efac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347831;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010-8"
width="1.3280054"
height="0.39521819"
x="262.32922"
y="146.91296"
transform="matrix(-0.49326555,0.86987878,0.89136123,0.45329367,0,0)"
ry="0.1976091" />
<rect
style="fill:#a5efac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347828;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010-8-9"
width="1.3280054"
height="0.39521819"
x="261.59872"
y="146.90884"
transform="matrix(-0.49326555,0.86987878,0.89136123,0.45329367,0,0)"
ry="0.1976091" />
<rect
style="fill:#a5efac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347825;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010-8-9-2"
width="1.3280054"
height="0.39521819"
x="263.42633"
y="143.44034"
transform="matrix(0.49326555,0.86987878,-0.89136123,0.45329367,0,0)"
ry="0.1976091" />
<path
style="fill:#a5efac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28821853;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 2.7247357,295.19899 a 0.45017607,0.44140657 0 0 0 -0.4505985,0.44164 0.45017607,0.44140657 0 0 0 0.4505985,0.44163 0.45017607,0.44140657 0 0 0 0.450061,-0.44163 0.45017607,0.44140657 0 0 0 -0.450061,-0.44164 z m 0,0.22824 a 0.2129211,0.21319639 0 0 1 0.2129321,0.2134 0.2129211,0.21319639 0 0 1 -0.2129321,0.21339 0.2129211,0.21319639 0 0 1 -0.2129319,-0.21339 0.2129211,0.21319639 0 0 1 0.2129319,-0.2134 z"
id="path7057"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -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

View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
viewBox="0 0 3.1749999 3.1750001"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="linechart2d.svg">
<defs
id="defs2">
<linearGradient
id="linearGradient5569"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop5567" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="45.254834"
inkscape:cx="7.2321717"
inkscape:cy="4.3383636"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:snap-bbox="true"
inkscape:snap-others="true"
inkscape:snap-nodes="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-global="false"
inkscape:object-nodes="false"
inkscape:snap-grids="true"
inkscape:snap-to-guides="false"
inkscape:object-paths="false"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="2391"
inkscape:window-y="-9"
inkscape:window-maximized="1"
units="px"
scale-x="1e-05">
<inkscape:grid
type="xygrid"
id="grid1377"
empspacing="12" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Livello 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-293.82497)">
<rect
style="fill:#a5efac;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1486"
width="0.26458332"
height="0.26458332"
x="-1.0583333"
y="293.82498" />
<rect
style="fill:#a5b7f3;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1578"
width="0.26458332"
height="0.26458332"
x="-1.0583333"
y="294.08954" />
<rect
style="fill:#a5b7f3;fill-opacity:1;stroke:none;stroke-width:0.27538645;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5539"
width="0.52916664"
height="3.4395685"
x="-0.26458332"
y="293.82498" />
<rect
style="fill:#a5b7f3;fill-opacity:1;stroke:none;stroke-width:0.27634826;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5541"
width="3.175"
height="0.52916664"
x="1.3907751e-08"
y="296.73535" />
<rect
style="fill:#a5b7f3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347834;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010"
width="1.3280054"
height="0.39521819"
x="264.53415"
y="142.6459"
transform="matrix(0.48573212,0.87410772,-0.89524404,0.44557615,0,0)"
ry="0.1976091" />
<rect
style="fill:#a5b7f3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347831;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010-8"
width="1.3280054"
height="0.39521819"
x="262.32922"
y="146.91296"
transform="matrix(-0.49326555,0.86987878,0.89136123,0.45329367,0,0)"
ry="0.1976091" />
<rect
style="fill:#a5b7f3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347828;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010-8-9"
width="1.3280054"
height="0.39521819"
x="261.59872"
y="146.90884"
transform="matrix(-0.49326555,0.86987878,0.89136123,0.45329367,0,0)"
ry="0.1976091" />
<rect
style="fill:#a5b7f3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29347825;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect7010-8-9-2"
width="1.3280054"
height="0.39521819"
x="263.42633"
y="143.44034"
transform="matrix(0.49326555,0.86987878,-0.89136123,0.45329367,0,0)"
ry="0.1976091" />
<path
style="fill:#a5b7f3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.28821853;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 2.7247357,295.19899 a 0.45017607,0.44140657 0 0 0 -0.4505985,0.44164 0.45017607,0.44140657 0 0 0 0.4505985,0.44163 0.45017607,0.44140657 0 0 0 0.450061,-0.44163 0.45017607,0.44140657 0 0 0 -0.450061,-0.44164 z m 0,0.22824 a 0.2129211,0.21319639 0 0 1 0.2129321,0.2134 0.2129211,0.21319639 0 0 1 -0.2129321,0.21339 0.2129211,0.21319639 0 0 1 -0.2129319,-0.21339 0.2129211,0.21319639 0 0 1 0.2129319,-0.2134 z"
id="path7057"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -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

View File

@ -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()

View File

@ -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