* Refactor so it works with the new data format.

Restructured some functions in smaller pieces and changed it so they can work with data in the new format.

Main way to use it will be with the new plot_function but old functions support is requiered through structure_datas (WIP)

* structure_datas rework

structure_datas structure the data to match the new format.

* Add new plotting methods

Also renamed identifiers array to the already existing y_labels and a fix to correctly calculate tics.

* Add autoscale or user-defined range

* Add color definition from plot_function call

Introduction of a param_dic parameter on plot_function that allows for specific parameters definitions for that curve only without changing the full Chart.

Also moved the label identier string to a parameter on this dicionary so it can be called without specifying a label name.

* Fix representation of negative values

Using negative numbers should now work on both axis. For this I created two new methods calcualte_interval_tics and calculate_number_integer_digits to avoid code repetition.

I'd like to work more on this, since now the representation is correct but can look very weird for some values with a lot of empty space on the chart.

* Correctly calculates tics for numbers between 0 and 1

Also some small fixes to update_function and delete_function. Changed the tic little line to point outside the chart instead of inside so it doesn't overlap with the grid line.

* Correctly (almost) center axis labels

Needs a little of research for the vertical centering since get_string_size doesn't behave as expected.

* Add some style representation customization

Width of the grid and function lines.

Made drawing points for LineChart optional (especially useful when using a huge number of points to avoid overclustering). For this I had to rewrite the draw_lines function to work similar to draw_points using point_positions instead of information of the point nodes.

The string position of the labels is correctly calculated now. It uses a new variable: label_displacement so it don't look too close to the axis and the border.

Set some default values  that make more sense to the instanciable scenes.

* Fix clearing of data structures with multiple calls to plot_from_x

* Fix show_x_values_as_labels

Correctly sets the x position when show_x_values_as_labels is active.

* Fix show_x_values_as_labels when the label is a String

Co-authored-by: Jorge <63685920+JFerrerBeired@users.noreply.github.com>
This commit is contained in:
Nicolò Santilio 2021-08-09 20:09:49 +02:00 committed by GitHub
parent 02621e8437
commit 1e388f49a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 581 additions and 188 deletions

View File

@ -14,29 +14,77 @@ class_name LineChart
# In these cases they are known as run charts.
# Source: Wikipedia
var show_points := true
var function_line_width : int = 2
func build_property_list():
.build_property_list()
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/show_points",
"type": TYPE_BOOL
})
#Find first element of Chart Display
var position
for i in property_list.size():
if property_list[i]["name"].find("Chart_Style") != -1: #Found
position = i
break
property_list.insert(position + 2, #I want it to be below point shape and function colors
{
"hint": PROPERTY_HINT_RANGE,
"hint_string": "1, 100, 1",
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/function_line_width",
"type": TYPE_INT
})
func _get_property_list():
property_list[0].name = "LineChart"
return property_list
func _get(property):
match property:
"Chart_Display/show_points":
return show_points
"Chart_Style/function_line_width":
return function_line_width
func _set(property, value):
match property:
"Chart_Display/show_points":
show_points = value
return true
"Chart_Style/function_line_width":
function_line_width = value
return true
func _draw():
clear_points()
draw_grid()
draw_chart_outlines()
if show_points:
draw_points()
draw_lines()
draw_treshold()
func draw_lines():
var _function = 0
for PointContainer in Points.get_children(): #Each function is stored in a different PointContainer
for function_point in range(1, PointContainer.get_children().size()):
for function in point_values.size():
for function_point in range(1, point_values[function].size()):
draw_line(
point_positions[_function][function_point - 1],
point_positions[_function][function_point],
function_colors[_function],
2,
point_positions[function][function_point - 1],
point_positions[function][function_point],
function_colors[function],
function_line_width,
false)
_function += 1

View File

@ -3,7 +3,6 @@
[ext_resource path="res://addons/easy_charts/Utilities/Point/point_data.tscn" type="PackedScene" id=1]
[ext_resource path="res://addons/easy_charts/LineChart/line_chart.gd" type="Script" id=4]
[sub_resource type="Theme" id=1]
[node name="LineChart" type="Control"]
@ -27,12 +26,16 @@ In these cases they are known as run charts."
}
Chart_Properties/are_values_columns = false
Chart_Properties/labels_index = 0
Chart_Properties/show_x_values_as_labels = true
Chart_Display/x_decim = 5.0
Chart_Properties/show_x_values_as_labels = false
Chart_Display/autoscale_x = true
Chart_Display/x_decim = 1.0
Chart_Display/autoscale_y = true
Chart_Display/y_decim = 1.0
Chart_Style/points_shape = [ 0 ]
Chart_Style/function_colors = [ Color( 0.117647, 0.117647, 0.117647, 1 ) ]
Chart_Style/points_shape = [ ]
Chart_Style/function_colors = PoolColorArray( 0.117647, 0.117647, 0.117647, 1, 0.117647, 0.117647, 0.117647, 1, 0.117647, 0.117647, 0.117647, 1, 0.117647, 0.117647, 0.117647, 1 )
Chart_Style/function_line_width = 2
Chart_Style/box_color = Color( 0.117647, 0.117647, 0.117647, 1 )
Chart_Style/grid_lines_width = 1
Chart_Style/v_lines_color = Color( 0.792157, 0.792157, 0.792157, 1 )
Chart_Style/h_lines_color = Color( 0.792157, 0.792157, 0.792157, 1 )
Chart_Style/font = null
@ -43,6 +46,7 @@ Chart_Style/template = 0
Chart_Modifiers/treshold = Vector2( 0, 0 )
Chart_Modifiers/only_disp_values = Vector2( 0, 0 )
Chart_Modifiers/invert_chart = false
Chart_Display/show_points = true
[node name="Background" type="ColorRect" parent="."]
visible = false
@ -82,10 +86,10 @@ __meta__ = {
[node name="PointData" parent="." instance=ExtResource( 1 )]
[node name="PointData" parent="PointData" index="0"]
margin_left = -257.531
margin_top = -244.08
margin_right = -257.667
margin_bottom = -243.28
margin_left = -458.75
margin_top = -164.504
margin_right = -458.886
margin_bottom = -163.704
theme = SubResource( 1 )
[editable path="PointData"]

View File

@ -2,19 +2,17 @@ tool
extends ScatterChartBase
class_name ScatterChart
"""
[ScatterChart] - General purpose node for Scatter Charts
A scatter plot (also called a scatterplot, scatter graph, scatter chart, scattergram, or scatter diagram)
is a type of plot or mathematical diagram using Cartesian coordinates to display values for typically two variables
for a set of data. If the points are coded (color/shape/size), one additional variable can be displayed.
The data are displayed as a collection of points, each having the value of one variable determining the position on
the horizontal axis and the value of the other variable determining the position on the vertical axis.
/ source : Wikipedia /
"""
# ---------------------
# [ScatterChart] - General purpose node for Scatter Charts
# A scatter plot (also called a scatterplot, scatter graph, scatter chart,
# scattergram, or scatter diagram) is a type of plot or mathematical diagram
# using Cartesian coordinates to display values for typically two variables
# for a set of data. If the points are coded (color/shape/size), one additional
# variable can be displayed. The data are displayed as a collection of points,
# each having the value of one variable determining the position on the
# horizontal axis and the value of the other variable determining the position
# on the vertical axis.
# Source: Wikipedia
func _get_property_list():

View File

@ -3,7 +3,6 @@
[ext_resource path="res://addons/easy_charts/Utilities/Point/point_data.tscn" type="PackedScene" id=1]
[ext_resource path="res://addons/easy_charts/ScatterChart/scatter_chart.gd" type="Script" id=2]
[sub_resource type="Theme" id=1]
[node name="ScatterChart" type="Control"]
@ -24,18 +23,24 @@ the horizontal axis and the value of the other variable determining the position
}
Chart_Properties/are_values_columns = false
Chart_Properties/labels_index = 0
Chart_Properties/show_x_values_as_labels = true
Chart_Display/x_decim = 5.0
Chart_Display/y_decim = 5.0
Chart_Style/points_shape = [ 0 ]
Chart_Style/function_colors = PoolColorArray( 0.117647, 0.117647, 0.117647, 1, 0.117647, 0.117647, 0.117647, 1, 0.117647, 0.117647, 0.117647, 1, 0.117647, 0.117647, 0.117647, 1 )
Chart_Properties/show_x_values_as_labels = false
Chart_Display/autoscale_x = true
Chart_Display/x_decim = 1.0
Chart_Display/autoscale_y = true
Chart_Display/y_decim = 1.0
Chart_Style/points_shape = [ ]
Chart_Style/function_colors = PoolColorArray( )
Chart_Style/box_color = Color( 0.117647, 0.117647, 0.117647, 1 )
Chart_Style/grid_lines_width = 1
Chart_Style/v_lines_color = Color( 0.792157, 0.792157, 0.792157, 1 )
Chart_Style/h_lines_color = Color( 0.792157, 0.792157, 0.792157, 1 )
Chart_Style/font = null
Chart_Style/bold_font = null
Chart_Style/font_color = Color( 0.117647, 0.117647, 0.117647, 1 )
Chart_Style/use_template = true
Chart_Style/template = 0
Chart_Modifiers/treshold = Vector2( 0, 0 )
Chart_Modifiers/only_disp_values = Vector2( 0, 0 )
Chart_Modifiers/invert_chart = false
[node name="Background" type="ColorRect" parent="."]
@ -76,10 +81,10 @@ __meta__ = {
[node name="PointData" parent="." instance=ExtResource( 1 )]
[node name="PointData" parent="PointData" index="0"]
margin_left = -311.73
margin_top = -167.672
margin_right = -311.866
margin_bottom = -166.872
margin_left = -510.384
margin_top = -148.79
margin_right = -510.52
margin_bottom = -147.99
theme = SubResource( 1 )
[editable path="PointData"]

View File

@ -62,8 +62,8 @@ var x_label : String
var x_labels : Array
var y_labels : Array
var x_margin_min : int = 0
var y_margin_min : int = 0
var x_margin_min : float = 0
var y_margin_min : float = 0
# actual values of point, from the database
var point_values : Array
@ -78,9 +78,9 @@ export (String) var chart_name : String = "" setget set_chart_name
export (String, FILE, "*.txt, *.csv") var source : String = "" setget set_source
export (String) var delimiter : String = ";" setget set_delimiter
var origin_at_zero : bool = true setget set_origin_at_zero#, get_origin_at_zero
var origin_at_zero : bool = false setget set_origin_at_zero#, get_origin_at_zero
var are_values_columns : bool = false setget set_are_values_columns#, get_are_values_columns
var show_x_values_as_labels : bool = true setget set_show_x_values_as_labels#, get_show_x_values_as_labels
var show_x_values_as_labels : bool = false setget set_show_x_values_as_labels#, get_show_x_values_as_labels
var labels_index : int = 0 setget set_labels_index#, get_labels_index
var function_names_index : int = 0 setget set_function_names_index#, get_function_names_index
@ -94,13 +94,14 @@ var column_gap : float = 2 setget set_column_gap
# Calculations of decim and its relation with number of tics: https://www.desmos.com/calculator/jeiceaswiy
var full_scale : float = 1.0 setget set_full_scale
var x_decim : float = 5.0 setget set_x_decim
var x_decim : float = 1.0 setget set_x_decim
var y_decim : float = 1.0 setget set_y_decim
var points_shape : Array = [PointShapes.Dot] setget set_points_shape
var function_colors = [Color("#1e1e1e")] setget set_function_colors
var outline_color : Color = Color("#1e1e1e") setget set_outline_color
var box_color : Color = Color("#1e1e1e") setget set_box_color
var grid_lines_width : int = 1 setget set_grid_lines_width
var v_lines_color : Color = Color("#cacaca") setget set_v_lines_color
var h_lines_color : Color = Color("#cacaca") setget set_h_lines_color
var grid_color : Color = Color("#1e1e1e") setget set_grid_color
@ -131,6 +132,10 @@ var treshold : Vector2 setget set_treshold
# only used to draw treshold values
var treshold_draw : Vector2
# Custom parameters for plot display
var tic_length : int = 5 setget set_tic_length # Length of the bar indicating a tic
var label_displacement : int = 4 setget set_label_displacement # Separation between the label and both the axis and the edge border
# !! API v2
static func instance(chart_type : int):
var chart_t : String = Utilities.get_chart_type(chart_type)
@ -180,6 +185,8 @@ func _get(property):
return grid_color
"Chart_Style/box_color":
return box_color
"Chart_Style/grid_lines_width":
return grid_lines_width
"Chart_Style/v_lines_color":
return v_lines_color
"Chart_Style/h_lines_color":
@ -262,6 +269,9 @@ func _set(property, value):
"Chart_Style/box_color":
box_color = value
return true
"Chart_Style/grid_lines_width":
grid_lines_width = value
return true
"Chart_Style/v_lines_color":
v_lines_color = value
return true
@ -332,6 +342,8 @@ func plot():
emit_signal("chart_plotted",self)
func plot_from_csv(csv_file : String, _delimiter : String = delimiter):
clean_variables()
clear_points()
load_font()
PointData.hide()
@ -377,7 +389,6 @@ func plot_from_dataframe(dataframe : DataFrame) -> void:
clean_variables()
clear_points()
load_font()
load_font()
PointData.hide()
data = dataframe.get_dataframe().duplicate(true)
@ -433,7 +444,6 @@ func load_font():
var theme : Theme = Theme.new()
theme.set_default_font(font)
set_theme(theme)
else:
var lbl = Label.new()
font = lbl.get_font("")
@ -443,7 +453,7 @@ func load_font():
func calculate_colors():
if function_colors.size() < functions:
for function in range(functions - function_colors.size() + 1): function_colors.append(Color(randf(),randf(), randf()))
for function in range(functions - function_colors.size()): function_colors.append(Color(randf(),randf(), randf()))
func set_shapes():
if points_shape.empty() or points_shape.size() < functions:
@ -629,10 +639,22 @@ func set_outline_color(c : Color):
func set_box_color(c : Color):
box_color = c
# ! API
func set_tic_length(i: int):
tic_length = i
# ! API
func set_label_displacement(i:int):
label_displacement = i
# ! API
func set_grid_color(c : Color):
grid_color = c
# ! API
func set_grid_lines_width(i : int):
grid_lines_width = i
# ! API
func set_v_lines_color(c : Color):
v_lines_color = c

View File

@ -1,4 +1,3 @@
tool
extends Chart
class_name ScatterChartBase
@ -6,51 +5,122 @@ class_name ScatterChartBase
# of points in a two-variable space. It handles basic data structure and grid
# layout and leaves to child classes the more specific behaviour.
var property_list = [
#Stored in the form of [[min_func1, min_func2, min_func3, ...], [max_func1, max_func2, ...]]
var x_domain := [[], []]
var y_domain := [[], []]
var x_range := [0, 0]
var y_range := [0, 0]
var autoscale_x = true
var autoscale_y = true
var property_list = []
func _init():
build_property_list()
func build_property_list():
property_list.clear()
# Chart Properties
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_CATEGORY,
"name": "ScatterChartBase", #TODO Changue this in the child classes
"name": "ScatterChartBase",
"type": TYPE_STRING
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Properties/are_values_columns",
"type": TYPE_BOOL
},
})
property_list.append(
{
"hint": PROPERTY_HINT_RANGE,
"hint_string": "-1,100,1",
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Properties/labels_index",
"type": TYPE_INT
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Properties/show_x_values_as_labels",
"type": TYPE_BOOL
},
})
# Chart Display
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/autoscale_x",
"type": TYPE_BOOL
})
if not autoscale_x:
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/min_x_range",
"type": TYPE_REAL
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/max_x_range",
"type": TYPE_REAL
})
property_list.append(
{
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0.001, 10",
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/x_decim",
"type": TYPE_REAL
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/autoscale_y",
"type": TYPE_BOOL
})
if not autoscale_y:
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/min_y_range",
"type": TYPE_REAL
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/max_y_range",
"type": TYPE_REAL
})
property_list.append(
{
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0.001, 10",
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Display/y_decim",
"type": TYPE_REAL
},
})
# Chart Style
property_list.append(
{
"hint": 24,
"hint_string": ("%d/%d:%s"
@ -59,31 +129,44 @@ var property_list = [
"name": "Chart_Style/points_shape",
"type": TYPE_ARRAY,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/function_colors",
"type": TYPE_COLOR_ARRAY
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/box_color",
"type": TYPE_COLOR
},
})
property_list.append(
{
"hint": PROPERTY_HINT_RANGE,
"hint_string": "1, 100, 1",
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/grid_lines_width",
"type": TYPE_INT
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/v_lines_color",
"type": TYPE_COLOR
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/h_lines_color",
"type": TYPE_COLOR
},
})
property_list.append(
{
"class_name": "Font",
"hint": PROPERTY_HINT_RESOURCE_TYPE,
@ -91,7 +174,8 @@ var property_list = [
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/font",
"type": TYPE_OBJECT
},
})
property_list.append(
{
"class_name": "Font",
"hint": PROPERTY_HINT_RESOURCE_TYPE,
@ -99,225 +183,435 @@ var property_list = [
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/bold_font",
"type": TYPE_OBJECT
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/font_color",
"type": TYPE_COLOR
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/use_template",
"type": TYPE_BOOL
},
})
property_list.append(
{
"hint": PROPERTY_HINT_ENUM,
"hint_string": PoolStringArray(Utilities.templates.keys()).join(","),
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Style/template",
"type": TYPE_INT
},
})
# Chart Modifiers
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Modifiers/treshold",
"type": TYPE_VECTOR2
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Modifiers/only_disp_values",
"type": TYPE_VECTOR2
},
})
property_list.append(
{
"hint": PROPERTY_HINT_NONE,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE,
"name": "Chart_Modifiers/invert_chart",
"type": TYPE_BOOL
},
]
})
func _set(property, value):
match property:
"Chart_Display/autoscale_x":
autoscale_x = value
build_property_list()
property_list_changed_notify()
return true
"Chart_Display/autoscale_y":
autoscale_y = value
build_property_list()
property_list_changed_notify()
return true
"Chart_Display/min_x_range":
x_range[0] = value
return true
"Chart_Display/max_x_range":
x_range[1] = value
return true
"Chart_Display/min_y_range":
y_range[0] = value
return true
"Chart_Display/max_y_range":
y_range[1] = value
return true
func _get(property):
match property:
"Chart_Display/autoscale_x":
return autoscale_x
"Chart_Display/autoscale_y":
return autoscale_y
"Chart_Display/min_x_range":
return x_range[0]
"Chart_Display/max_x_range":
return x_range[1]
"Chart_Display/min_y_range":
return y_range[0]
"Chart_Display/max_y_range":
return y_range[1]
func plot():
# Overwrites the method on Chart to make a reusable piece to be used internally
# to do all calculations needed to replot.
calculate_tics()
build_chart()
count_functions()
calculate_pass()
calculate_colors()
calculate_coordinates()
set_shapes()
create_legend()
emit_signal("chart_plotted",self)
if not is_connected("item_rect_changed",self, "redraw"): connect("item_rect_changed", self, "redraw")
func plot_function(x:Array, y:Array, param_dic := {}):
# Add a function to the chart. If no identifier (label) is given a generic one
# is generated.
# param_dic is a dictionary with specific parameters to this curve
load_font()
PointData.hide()
var id := ""
if x.empty() or y.empty():
Utilities._print_message("Can't plot a chart with an empty Array.",1)
return
elif x.size() != y.size():
Utilities._print_message("Can't plot a chart with x and y having different number of elements.",1)
return
for param in param_dic.keys():
match param:
"label":
id = param_dic[param]
"color":
if function_colors.size() < functions + 1: #There is going to be a new function
function_colors.append(param_dic[param])
else:
function_colors[functions] = param_dic[param]
id = generate_identifier() if id.empty() else id
if y_labels.has(id):
Utilities._print_message("The identifier %s is already used. Please use a different one." % id,1)
return
y_domain[0].append(null)
y_domain[1].append(null)
x_domain[0].append(null)
x_domain[1].append(null)
x_datas.append(x)
y_datas.append(y)
y_labels.append(id)
calculate_range(id)
plot()
func update_function(id:String, x:Array, y:Array, param_dic := {}):
var function = y_labels.find(id)
if function == -1: #Not found
Utilities._print_message("The identifier %s does not exist." % id,1)
return
for param in param_dic.keys():
match param:
"label":
y_labels[function] = param_dic[param]
"color":
function_colors[function] = param_dic[param]
x_datas[function] = x
y_datas[function] = y
calculate_range(id)
plot()
update()
func delete_function(id:String):
var function = y_labels.find(id)
if function == -1: #Not found
Utilities._print_message("The identifier %s does not exist." % id,1)
return
y_labels.remove(function)
x_datas.remove(function)
y_datas.remove(function)
y_domain[0].remove(function)
y_domain[1].remove(function)
x_domain[0].remove(function)
x_domain[1].remove(function)
function_colors.remove(function)
plot()
update()
func generate_identifier():
#TODO: Check if the identifier generated already exist (given by the user)
return "f%d" % (y_labels.size() + 1)
func structure_datas(database : Array):
# @labels_index can be either a column or a row relative to x values
# @y_values can be either a column or a row relative to y values
#This is done to make sure this arrays are empty on subsecuent calls of this function.
#This function is called from the "old" methods such as plot_from_array and
#for the moment it doesn't clean this variables on clean_variable.
x_domain = [[], []]
y_domain = [[], []]
are_values_columns = invert_chart != are_values_columns
var x_values := []
if are_values_columns:
var y_values := []
var y_columns = database[0].size()
if range(database.size()).has(labels_index): # x column is present
y_columns -= 1
else:
x_values = range(database.size()) #If no x column is given, a generic one is generated
x_values.push_front("")
for _i in y_columns: #Resize to number of y columns
y_values.append([])
for row in database.size():
var t_vals : Array
var y_column = 0
for column in database[row].size():
if column == labels_index:
var x_data = database[row][column]
if typeof(x_data) == TYPE_INT or typeof(x_data) == TYPE_REAL:
x_datas.append(x_data as float)
x_values.append(x_data as float)
else:
x_datas.append(x_data.replace(",", ".") as float)
x_values.append(x_data.replace(",", ".") as float)
else:
if row != 0:
var y_data = database[row][column]
if typeof(y_data) == TYPE_INT or typeof(y_data) == TYPE_REAL:
t_vals.append(y_data as float)
y_values[y_column].append(y_data as float)
else:
t_vals.append(y_data.replace(",",".") as float)
y_values[y_column].append(y_data.replace(",",".") as float)
else:
y_labels.append(str(database[row][column]))
if not t_vals.empty():
y_datas.append(t_vals)
x_label = str(x_datas.pop_front())
y_column += 1
x_label = str(x_values.pop_front())
for function in y_values.size():
y_datas.append(y_values[function])
x_datas.append(x_values)
else:
for row in database.size():
if row == labels_index:
x_datas = (database[row])
x_label = x_datas.pop_front() as String
var database_size = range(database.size())
if database_size.has(labels_index):
x_values = database[labels_index]
x_label = x_values.pop_front() as String
database_size.erase(labels_index) #Remove x row from the iterator
for row in database_size:
var y_values = database[row] as Array
y_labels.append(y_values.pop_front() as String)
for val in y_values.size():
y_values[val] = y_values[val] as float
y_datas.append(y_values)
x_datas.append(x_values if not x_values.empty() else range(y_values.size()))
for function in y_labels:
y_domain[0].append(null)
y_domain[1].append(null)
x_domain[0].append(null)
x_domain[1].append(null)
calculate_range(function)
calculate_tics()
func calculate_range(id):
# Calculate the domain of the given function in the x and y axis
# and updates the range value
var function = y_labels.find(id)
y_domain[0][function] = y_datas[function].min()
y_domain[1][function] = y_datas[function].max()
x_domain[0][function] = x_datas[function].min()
x_domain[1][function] = x_datas[function].max()
func calculate_tics():
y_chors.clear()
x_chors.clear()
# Chose the min/max from all functions
if autoscale_x:
x_range = [x_domain[0].min() if not origin_at_zero else 0, x_domain[1].max()]
if autoscale_y:
y_range = [y_domain[0].min() if not origin_at_zero else 0, y_domain[1].max()]
y_margin_min = y_range[0]
var y_margin_max = y_range[1]
v_dist = y_decim * pow(10.0, calculate_position_significant_figure(y_margin_max - y_margin_min) - 1)
# There are three cases of min/max:
# For +/+ and -/- we just do the usual and draw tics from min to max
# But for the -/+ we do in two times to force the 0 to appear so it is
# easier to read. Then we draw the negative from 0 to min and the positives
# from 0 to max without drawing the 0 again
if y_margin_min < 0 and y_margin_max >= 0:
calculate_interval_tics(0, y_margin_min, -v_dist, y_chors) #Negative tics
calculate_interval_tics(0, y_margin_max, v_dist, y_chors, false) #Positive tics
y_chors.sort()
y_margin_min = min(y_margin_min, y_chors[0])
else:
var values = database[row] as Array
y_labels.append(values.pop_front() as String)
y_datas.append(values)
for data in y_datas:
for value in data.size():
data[value] = data[value] as float
calculate_interval_tics(y_margin_min, y_margin_max, v_dist, y_chors)
for i in y_chors.size():
y_chors[i] = String(y_chors[i]) #Can't cast directly on calculate_interval_tics because it mess up with the sorting
# draw y labels
var to_order : Array
var to_order_min : Array
for cluster in y_datas.size():
# define x_chors and y_chors
var margin_max = y_datas[cluster].max()
var margin_min = y_datas[cluster].min()
to_order.append(margin_max)
to_order_min.append(margin_min)
var y_margin_max = to_order.max()
y_margin_min = to_order_min.min() if not origin_at_zero else 0
v_dist = y_decim * pow(10.0, str(y_margin_max).split(".")[0].length() - 1)
var multi = 0
var p = (v_dist * multi) + y_margin_min
y_chors.append(p as String)
while p < y_margin_max:
multi += 1
p = (v_dist * multi) + y_margin_min
y_chors.append(p as String)
# draw x_labels
to_order.clear()
to_order = x_datas
var x_margin_max = to_order.max()
x_margin_min = to_order.min() if not origin_at_zero else 0
if not show_x_values_as_labels:
h_dist = x_decim * pow(10.0, str(x_margin_max).split(".")[0].length() - 1)
multi = 0
p = (h_dist * multi) + x_margin_min
x_labels.append(p as String)
while p < x_margin_max:
multi += 1
p = (h_dist * multi) + x_margin_min
x_labels.append(p as String)
x_margin_min = x_range[0]
var x_margin_max = x_range[1]
h_dist = x_decim * pow(10.0, calculate_position_significant_figure(x_margin_max - x_margin_min) - 1)
OFFSET.x = str(y_margin_max).length() * font_size
OFFSET.y = font_size * 2
if x_margin_min < 0 and x_margin_max >= 0:
calculate_interval_tics(0, x_margin_min, -h_dist, x_labels) #Negative tics
calculate_interval_tics(0, x_margin_max, h_dist, x_labels, false) #Positive tics
x_labels.sort()
x_margin_min = min(x_margin_min, x_labels[0])
else:
calculate_interval_tics(x_margin_min, x_margin_max, h_dist, x_labels)
for i in x_labels.size():
x_labels[i] = String(x_labels[i])
x_chors = x_labels
else:
for function in y_labels.size():
for value in x_datas[function]:
if not x_chors.has(value as String): #Don't append repeated values
x_chors.append(value as String)
func build_chart():
var longest_y_tic = 0
for y_tic in y_chors:
var length = font.get_string_size(y_tic).x
if length > longest_y_tic:
longest_y_tic = length
OFFSET.x = longest_y_tic + tic_length + 2 * label_displacement
OFFSET.y = font.get_height() + tic_length + label_displacement
SIZE = get_size() - Vector2(OFFSET.x, 0)
origin = Vector2(OFFSET.x, SIZE.y - OFFSET.y)
func calculate_pass():
if show_x_values_as_labels:
x_chors = x_datas.duplicate(true) as PoolStringArray
else:
x_chors = x_labels
func count_functions():
functions = y_labels.size()
# calculate distance in pixel between 2 consecutive values/datas
func calculate_pass():
# Calculate distance in pixel between 2 consecutive values/datas
x_pass = (SIZE.x - OFFSET.x) / (x_chors.size() - 1 if x_chors.size() > 1 else x_chors.size())
y_pass = (origin.y - ChartName.get_rect().size.y * 2) / (y_chors.size() - 1)
y_pass = (origin.y - ChartName.get_rect().size.y * 2) / (y_chors.size() - 1 if y_chors.size() > 1 else y_chors.size())
func calculate_coordinates():
x_coordinates.clear()
y_coordinates.clear()
point_values.clear()
point_positions.clear()
for cluster in y_datas:
var single_coordinates : Array
for value in cluster.size():
single_coordinates.append((cluster[value] - y_margin_min) * y_pass / v_dist)
y_coordinates.append(single_coordinates)
if show_x_values_as_labels:
for x in x_datas.size():
x_coordinates.append(x_pass * x)
else:
for x in x_datas.size():
x_coordinates.append((x_datas[x] - x_margin_min) * x_pass / h_dist)
for f in functions:
for _i in y_labels.size():
point_values.append([])
point_positions.append([])
for cluster in y_coordinates.size():
for y in y_coordinates[cluster].size():
if are_values_columns:
point_values[y].append([x_datas[cluster], y_datas[cluster][y]])
point_positions[y].append(Vector2(x_coordinates[cluster] + origin.x,
origin.y - y_coordinates[cluster][y]))
else:
point_values[cluster].append([x_datas[y], y_datas[cluster][y]])
point_positions[cluster].append(Vector2(x_coordinates[y] + origin.x,
origin.y - y_coordinates[cluster][y]))
for function in y_labels.size():
for val in x_datas[function].size():
var value_x = (x_datas[function][val] - x_margin_min) * x_pass / h_dist if h_dist else 0 \
if not show_x_values_as_labels else x_chors.find(String(x_datas[function][val])) * x_pass
var value_y = (y_datas[function][val] - y_margin_min) * y_pass / v_dist if v_dist else 0
point_values[function].append([x_datas[function][val], y_datas[function][val]])
point_positions[function].append(Vector2(value_x + origin.x, origin.y - value_y))
func draw_grid():
# ascisse
for p in x_chors.size():
var point : Vector2 = origin+Vector2((p)*x_pass,0)
var point : Vector2 = origin + Vector2(p * x_pass, 0)
var size_text : Vector2 = font.get_string_size(x_chors[p])
# v grid
draw_line(point,point-Vector2(0,SIZE.y-OFFSET.y),v_lines_color,0.2,true)
draw_line(point, point - Vector2(0, SIZE.y - OFFSET.y), v_lines_color, grid_lines_width, true)
# ascisse
draw_line(point-Vector2(0,5),point,v_lines_color,1,true)
draw_string(font,point+Vector2(-const_width/2*x_chors[p].length(),font_size+const_height),x_chors[p],font_color)
draw_line(point + Vector2(0, tic_length), point, v_lines_color, grid_lines_width, true)
draw_string(font, point + Vector2(-size_text.x / 2, size_text.y + tic_length),
x_chors[p], font_color)
# ordinate
for p in y_chors.size():
var point : Vector2 = origin-Vector2(0,(p)*y_pass)
var point : Vector2 = origin - Vector2(0, p * y_pass)
var size_text := Vector2(font.get_string_size(y_chors[p]).x, font.get_ascent()) #The y should be ascent instead full height to get correctly centered
# h grid
draw_line(point,point+Vector2(SIZE.x-OFFSET.x,0),h_lines_color,0.2,true)
draw_line(point, point + Vector2(SIZE.x - OFFSET.x, 0), h_lines_color, grid_lines_width, true)
# ordinate
draw_line(point,point+Vector2(5,0),h_lines_color,1,true)
draw_string(font,point-Vector2(y_chors[p].length()*const_width+font_size,-const_height),y_chors[p],font_color)
draw_line(point, point + Vector2(-tic_length, 0), h_lines_color, grid_lines_width, true)
draw_string(font, point + Vector2(-size_text.x - tic_length - label_displacement,
size_text.y / 2), y_chors[p], font_color)
func draw_chart_outlines():
draw_line(origin,SIZE-Vector2(0,OFFSET.y),box_color,1,true)
draw_line(origin,Vector2(OFFSET.x,0),box_color,1,true)
draw_line(Vector2(OFFSET.x,0),Vector2(SIZE.x,0),box_color,1,true)
draw_line(Vector2(SIZE.x,0),SIZE-Vector2(0,OFFSET.y),box_color,1,true)
draw_line(origin, SIZE-Vector2(0, OFFSET.y), box_color, 1, true)
draw_line(origin, Vector2(OFFSET.x, 0), box_color, 1, true)
draw_line(Vector2(OFFSET.x, 0), Vector2(SIZE.x, 0), box_color, 1, true)
draw_line(Vector2(SIZE.x, 0), SIZE - Vector2(0, OFFSET.y), box_color, 1, true)
func draw_points():
var defined_colors : bool = false
if function_colors.size():
defined_colors = true
for _function in point_values.size():
for function in point_values.size():
var PointContainer : Control = Control.new()
Points.add_child(PointContainer)
for function_point in point_values[_function].size():
for function_point in point_values[function].size():
var point : Point = point_node.instance()
point.connect("_point_pressed",self,"point_pressed")
point.connect("_mouse_entered",self,"show_data")
point.connect("_mouse_exited",self,"hide_data")
point.create_point(points_shape[_function], function_colors[_function],
Color.white, point_positions[_function][function_point],
point.format_value(point_values[_function][function_point], false, false),
y_labels[_function] as String)
point.create_point(points_shape[function], function_colors[function],
Color.white, point_positions[function][function_point],
point.format_value(point_values[function][function_point], false, false),
y_labels[function])
PointContainer.add_child(point)
@ -329,3 +623,25 @@ func draw_treshold():
draw_line(Vector2(origin.x, treshold_draw.y), Vector2(SIZE.x, treshold_draw.y), Color.red, 0.4, true)
if treshold.x != 0:
draw_line(Vector2(treshold_draw.x, 0), Vector2(treshold_draw.x, SIZE.y - OFFSET.y), Color.red, 0.4, true)
func calculate_position_significant_figure(number):
return floor(log(abs(number))/log(10) + 1) #If number = 0 Godot returns -#INF and it behaves correctly on the pow call on calculate_tics
func calculate_interval_tics(v_from:float, v_to:float, dist:float, chords:Array, include_first := true):
# Appends to array chords the tics calculated between v_from and v_to with
# a given distance between tics.
#include_first is used to tell if v_from should be appended or ignored
var multi = 0
var p = (dist * multi) + v_from
var missing_tics = p < v_to if dist > 0 else p > v_to
if include_first:
chords.append(p)
while missing_tics:
multi += 1
p = (dist * multi) + v_from
missing_tics = p < v_to if dist > 0 else p > v_to
chords.append(p)