diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4fdbdb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.import diff --git a/addons/easy_charts/BarChart/BarChart.gd b/addons/easy_charts/BarChart/BarChart.gd index 07e7ae6..eab8eef 100644 --- a/addons/easy_charts/BarChart/BarChart.gd +++ b/addons/easy_charts/BarChart/BarChart.gd @@ -1,6 +1,22 @@ tool extends Control + +""" +[BarChart] - General purpose node for Bar Charts + +A bar chart or bar graph is a chart or graph that presents categorical data with +rectangular bars with heights or lengths proportional to the values that they represent. +The bars can be plotted vertically or horizontally. A vertical bar chart is sometimes +called a column chart. +A bar graph shows comparisons among discrete categories. One axis of the chart shows +the specific categories being compared, and the other axis represents a measured value. +Some bar graphs present bars clustered in groups of more than one, showing the +values of more than one measured variable. + +/ source : Wikipedia / +""" + onready var PointData = $PointData/PointData onready var Points = $Points onready var Legend = $Legend @@ -12,10 +28,7 @@ var font_size : float = 16 var const_height : float = font_size/2*font_size/20 var const_width : float = font_size/2 -var source : String -var delimiter : String - -var OFFSET : Vector2 = Vector2(50,30) +var OFFSET : Vector2 = Vector2(0,0) #-------------------------------------------------------------------------# var origin : Vector2 @@ -29,7 +42,6 @@ var v_dist : float # quantization, representing the interval in which values will be displayed var x_decim : float = 1.0 -export (float) var y_decim : float = 5.0 # define values on x an y axis var x_chors : Array @@ -61,45 +73,48 @@ var point_values : Array var point_positions : Array var legend : Array setget set_legend,get_legend -var are_values_columns : bool # --------------------- -export (bool) var invert_xy : bool +#export (bool) var SIZE : Vector2 = Vector2() +export (String, FILE) var source : String = "" +export (String) var delimiter : String = ";" + +export (bool) var are_values_columns : bool = false +export (bool) var invert_xy : bool = false + +export (int,0,100) var x_values : int = 0 + +export (float,1,20,0.5) var column_width : float = 10 +export (float,0,10,0.5) var column_gap : float = 2 + +export (float,0,10) var y_decim : float = 5.0 export (PoolColorArray) var function_colors = [Color("#1e1e1e")] + +export (bool) var boxed : bool = true export (Color) var v_lines_color : Color = Color("#cacaca") export (Color) var h_lines_color : Color = Color("#cacaca") export (Color) var outline_color : Color = Color("#1e1e1e") export (Font) var font : Font export (Font) var bold_font : Font export (Color) var font_color : Color = Color("#1e1e1e") -export (String,"Default","Clean","Minimal","Invert") var template : String = "Default" setget apply_template +export (String,"Default","Clean","Gradient","Minimal","Invert") var template : String = "Default" setget apply_template -signal linechart_plotted() +var templates : Dictionary = {} -#func _ready(): -# plot_line_chart("res://ChartNode/datas.csv",";",true,0) +signal chart_plotted(chart) +signal point_pressed(point) -func plot_line_chart(source_ : String, delimiter_ : String, are_values_columns_ : bool, x_values_ : int, invert_xy_ : bool = false): - randomize() - - load_font() - PointData.hide() - - datas = read_datas(source_,delimiter_) - count_functions() - structure_datas(datas,are_values_columns_,x_values_) - build_chart() - calculate_pass() - calculate_coordinates() - calculate_colors() - create_legend() - emit_signal("linechart_plotted") +func _point_drawn(): + pass + +func _ready(): + pass + +func _script_changed(): + _enter_tree() + _ready() -func calculate_colors(): - if function_colors.empty() or function_colors.size() < functions: - for function in functions: - function_colors.append(Color("#1e1e1e")) func load_font(): if font != null: @@ -114,6 +129,101 @@ func load_font(): if bold_font != null: PointData.Data.set("custom_fonts/font",bold_font) +func _plot(source_ : String, delimiter_ : String, are_values_columns_ : bool, x_values_ : int): + randomize() + + load_font() + PointData.hide() + + datas = read_datas(source_,delimiter_) + count_functions() + structure_datas(datas,are_values_columns_,x_values_) + build_chart() + calculate_pass() + calculate_coordinates() + calculate_colors() + create_legend() + emit_signal("chart_plotted", self) + + +func plot(): + randomize() + + load_font() + PointData.hide() + + if source == "" or source == null: + Utilities._print_message("Can't plot a chart without a Source file. Please, choose it in editor, or use the custom function _plot().",1) + return + datas = read_datas(source,delimiter) + count_functions() + structure_datas(datas,are_values_columns,x_values) + build_chart() + calculate_pass() + calculate_coordinates() + calculate_colors() + + create_legend() + emit_signal("chart_plotted", self) + +func _draw(): + draw_chart() + +func calculate_colors(): + if function_colors.empty() or function_colors.size() < functions: + for function in functions: + function_colors.append(Color("#1e1e1e")) + +func draw_chart(): + clear_points() + draw_outlines() + draw_v_grid() + draw_h_grid() + draw_functions() + +func draw_outlines(): + if boxed: + draw_line(Vector2(origin.x,0),Vector2(SIZE.x,0),outline_color,1,true) + draw_line(Vector2(SIZE.x,0),Vector2(SIZE.x,origin.y),outline_color,1,true) + draw_line(Vector2(SIZE.x,origin.y),origin,outline_color,1,true) + draw_line(origin,Vector2(origin.x,0),outline_color,1,true) + +func draw_v_grid(): + for p in x_chors.size(): + var point : Vector2 = origin+Vector2((p)*x_pass + OFFSET.x/2,0) + # v grid + draw_line(Vector2(point.x,origin.y),Vector2(point.x,origin.y-5),v_lines_color,0.2,true) + draw_string(font,point+Vector2(-const_width/2*x_chors[p].length() + (column_width/2) * ( y_datas.size() if not invert_xy else y_datas[0].size()+1 ) + column_gap,font_size),x_chors[p],font_color) + +func draw_h_grid(): + for p in y_chors.size(): + var point : Vector2 = origin-Vector2(0,(p)*y_pass) + # h grid + draw_line(point,Vector2(SIZE.x,point.y),h_lines_color,0.2,true) + draw_string(font,point-Vector2(y_chors[p].length()*const_width+font_size,font_size/2),y_chors[p],font_color) + +func draw_functions(): + for function in point_positions.size(): + draw_function(function,point_positions[function]) + +func draw_function(f_index : int, function : Array): + for point in function.size(): + var pointv : Point + pointv = point_node.instance() + pointv.connect("_mouse_entered",self,"show_data",[pointv]) + pointv.connect("_mouse_exited",self,"hide_data") + pointv.connect("_point_pressed",self,"point_pressed") + pointv.create_point(function_colors[f_index], Color.white, function[point]+Vector2(0,5), + pointv.format_value(point_values[f_index][point],false,true),(x_datas[f_index] if invert_xy else y_labels[f_index])) + add_child(pointv) + pointv.rect_size.y = origin.y - function[point].y + draw_line( + Vector2(function[point].x, origin.y), + function[point], function_colors[f_index], column_width, true) + +func construct_column(f_index : int, function : Array): + draw_line(Vector2(function[f_index].x,origin.y), function[f_index], function_colors[f_index], column_width, true) + func read_datas(source : String, delimiter : String): var file : File = File.new() file.open(source,File.READ) @@ -177,7 +287,7 @@ func structure_datas(database : Array, are_values_columns : bool, x_values : int y_chors.append(p as String) func build_chart(): - SIZE = get_parent().get_size() + SIZE = get_size() origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) func calculate_pass(): @@ -186,7 +296,7 @@ func calculate_pass(): else: x_chors = x_datas as PoolStringArray # calculate distance in pixel between 2 consecutive values/datas - x_pass = (SIZE.x - OFFSET.x) / (x_chors.size()-1) + x_pass = (SIZE.x - OFFSET.x*2 - (column_width) * ( y_datas.size() if not invert_xy else y_datas[0].size()+1 ) - column_gap - column_width/2) / (x_chors.size()-1) y_pass = origin.y / (y_chors.size()-1) func calculate_coordinates(): @@ -216,54 +326,51 @@ func calculate_coordinates(): point_positions.append([]) if invert_xy: - for function in y_coordinates.size()-1: + for function in y_coordinates.size(): for function_value in y_coordinates[function].size(): - point_positions[function].append(Vector2(x_coordinates[function_value]+origin.x,origin.y-y_coordinates[function][function_value])) + point_positions[function].append(Vector2(OFFSET.x/2 + column_width/2 + (column_width + column_gap)*function + x_coordinates[function_value]+origin.x, origin.y-y_coordinates[function][function_value])) point_values[function].append([x_chors[function_value],y_datas[function_value][function]]) else: for cluster in y_coordinates.size(): for y in y_coordinates[cluster].size(): point_values[cluster].append([x_chors[y],y_datas[cluster][y]]) - point_positions[cluster].append(Vector2(x_coordinates[y]+origin.x,origin.y-y_coordinates[cluster][y])) + point_positions[cluster].append(Vector2(OFFSET.x/2 + column_width/2 + (column_width + column_gap)*cluster + x_coordinates[y]+origin.x, origin.y-y_coordinates[cluster][y])) func redraw(): - build_chart() - calculate_pass() - calculate_coordinates() - update() + pass -func _draw(): - clear_points() - - draw_grid() - draw_chart_outlines() - - var defined_colors : bool = false - if function_colors.size(): - defined_colors = true - - for _function in point_values.size(): - var PointContainer : Control = Control.new() - Points.add_child(PointContainer) - - if invert_xy: - for function_point in point_values[_function].size(): - var point : Control = point_node.instance() - point.connect("_mouse_entered",self,"show_data",[point]) - point.connect("_mouse_exited",self,"hide_data") - point.create_point(function_colors[_function], Color.white, point_positions[_function][function_point],point.format_value(point_values[_function][function_point],false,true),x_datas[_function]) - PointContainer.add_child(point) - if function_point > 0: - draw_line(point_positions[_function][function_point-1],point_positions[_function][function_point],function_colors[_function],2,true) +func invert_chart(): + invert_xy = !invert_xy + count_functions() + redraw() + create_legend() + + +func count_functions(): + if are_values_columns: + if not invert_xy: + functions = datas[0].size()-1 else: - for function_point in point_values[_function].size(): - var point : Control = point_node.instance() - point.connect("_mouse_entered",self,"show_data",[point]) - point.connect("_mouse_exited",self,"hide_data") - point.create_point(function_colors[_function], Color.white, point_positions[_function][function_point],point.format_value(point_values[_function][function_point],false,true),y_labels[_function]) - PointContainer.add_child(point) - if function_point > 0: - draw_line(point_positions[_function][function_point-1],point_positions[_function][function_point],function_colors[_function],2,true) + functions = datas.size()-1 + else: + if invert_xy: + functions = datas[0].size()-1 + else: + functions = datas.size()-1 + +func show_data(point): + PointData.update_datas(point) + PointData.show() + +func hide_data(): + PointData.hide() + +func clear_points(): + if Points.get_children(): + for function in Points.get_children(): + function.queue_free() + for legend in Legend.get_children(): + legend.queue_free() func create_legend(): legend.clear() @@ -282,116 +389,26 @@ func create_legend(): function_legend.create_legend(f_name,function_colors[function],bold_font,font_color) legend.append(function_legend) -func draw_grid(): - # ascisse - for p in x_chors.size(): - var point : Vector2 = origin+Vector2((p)*x_pass,0) - # v grid - draw_line(point,point-Vector2(0,SIZE.y-OFFSET.y),v_lines_color,0.2,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) - - # ordinate - for p in y_chors.size(): - var point : Vector2 = origin-Vector2(0,(p)*y_pass) - # h grid - draw_line(point,point+Vector2(SIZE.x-OFFSET.x,0),h_lines_color,0.2,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) - -func draw_chart_outlines(): - draw_line(origin,SIZE-Vector2(0,OFFSET.y),outline_color,1,true) - draw_line(origin,Vector2(OFFSET.x,0),outline_color,1,true) - draw_line(Vector2(OFFSET.x,0),Vector2(SIZE.x,0),outline_color,1,true) - draw_line(Vector2(SIZE.x,0),SIZE-Vector2(0,OFFSET.y),outline_color,1,true) - -var can_grab_x : bool = false -var can_grab_y : bool = false -var can_move : bool = false -var range_mouse : float = 7 - -#func _input(event): -# if not can_grab_x and (event.position.x > (SIZE.x-range_mouse + rect_position.x) and event.position.x < (SIZE.x+range_mouse + rect_position.x)) : -# set_default_cursor_shape(Control.CURSOR_HSIZE) -# if Input.is_action_pressed("mouse_left"): -# can_grab_x = true -# -# if Input.is_action_just_released("mouse_left") and can_grab_x: -# can_grab_x = false -# -# if not can_grab_y and (event.position.y > ( rect_position.y + origin.y-range_mouse) and event.position.y < (rect_position.y+ origin.y+range_mouse)) : -# set_default_cursor_shape(Control.CURSOR_VSIZE) -# if Input.is_action_pressed("mouse_left"): -# can_grab_y = true -# -# if Input.is_action_just_released("mouse_left") and can_grab_y: -# can_grab_y = false -# -# if (event.position.x > SIZE.x-range_mouse+rect_position.x and event.position.x < SIZE.x+range_mouse + rect_position.x) and (event.position.y > rect_position.y+origin.y-range_mouse and event.position.y < rect_position.y+origin.y+range_mouse): -# set_default_cursor_shape(Control.CURSOR_FDIAGSIZE) -# if not (event.position.x > SIZE.x-range_mouse+rect_position.x and event.position.x < SIZE.x+range_mouse + rect_position.x) and not (event.position.y > rect_position.y+ origin.y-range_mouse and event.position.y < rect_position.y+origin.y+range_mouse ): -# set_default_cursor_shape(Control.CURSOR_ARROW) - - -func _process(delta): - if can_grab_x: - PointData.hide() - get_parent().rect_size.x = get_global_mouse_position().x - rect_position.x - redraw() - - if can_grab_y: - PointData.hide() - get_parent().rect_size.y = get_global_mouse_position().y - rect_position.y + OFFSET.y - redraw() - -func show_data(point): - PointData.update_datas(point) - PointData.show() - -func hide_data(): - PointData.hide() - -func clear_points(): - if Points.get_children(): - for function in Points.get_children(): - function.queue_free() - for legend in Legend.get_children(): - legend.queue_free() - func set_legend(l : Array): legend = l -func get_legend(): +func get_legend() -> Array: return legend -func invert_chart(): - invert_xy = !invert_xy - count_functions() - redraw() - create_legend() - -func count_functions(): - if are_values_columns: - if not invert_xy: - functions = datas[0].size()-1 - else: - functions = datas.size()-1 - else: - if invert_xy: - functions = datas[0].size()-1 - else: - functions = datas.size()-1 - func apply_template(template_name : String): if Engine.editor_hint: if template_name!=null and template_name!="": template = template_name - var custom_template = get_parent().templates[template_name.to_lower()] - function_colors[0] = Color(custom_template.function_color) + var custom_template = templates[template_name.to_lower()] + function_colors = custom_template.function_colors v_lines_color = Color(custom_template.v_lines_color) h_lines_color = Color(custom_template.h_lines_color) outline_color = Color(custom_template.outline_color) font_color = Color(custom_template.font_color) property_list_changed_notify() + +func point_pressed(point : Point): + emit_signal("point_pressed",point) + +func _enter_tree(): + templates = Utilities._load_templates() diff --git a/addons/easy_charts/BarChart/BarChart.tscn b/addons/easy_charts/BarChart/BarChart.tscn index 7e3abed..15b6da5 100644 --- a/addons/easy_charts/BarChart/BarChart.tscn +++ b/addons/easy_charts/BarChart/BarChart.tscn @@ -49,6 +49,8 @@ __meta__ = { [node name="PointData" parent="." instance=ExtResource( 1 )] [node name="PointData" parent="PointData" index="0"] +visible = false theme = SubResource( 1 ) +[connection signal="script_changed" from="." to="." method="_script_changed"] [editable path="PointData"] diff --git a/addons/easy_charts/BarChart2D/BarChart2D.gd b/addons/easy_charts/BarChart2D/BarChart2D.gd index 972098d..f47d6c4 100644 --- a/addons/easy_charts/BarChart2D/BarChart2D.gd +++ b/addons/easy_charts/BarChart2D/BarChart2D.gd @@ -285,7 +285,7 @@ func draw_function(f_index : int, function : Array): pointv.connect("_mouse_entered",self,"show_data",[pointv]) pointv.connect("_mouse_exited",self,"hide_data") pointv.connect("_point_pressed",self,"point_pressed") - pointv.create_point(function_colors[f_index], Color.white, function[point]+Vector2(0,10), pointv.format_value(point_values[f_index][point],false,true),(x_datas[f_index] if invert_xy else y_labels[f_index])) + pointv.create_point(function_colors[f_index], Color.white, function[point]+Vector2(0,5), pointv.format_value(point_values[f_index][point],false,true),(x_datas[f_index] if invert_xy else y_labels[f_index])) pointv.rect_size.y = origin.y - function[point].y construct_column(line,f_index,function) FunctionsTween.interpolate_method(line,"add_point",Vector2(function[point].x,origin.y),function[point],drawing_duration/function.size(),Tween.TRANS_QUINT,Tween.EASE_OUT) diff --git a/addons/easy_charts/LineChart/LineChart.gd b/addons/easy_charts/LineChart/LineChart.gd index c9e00d4..d25d183 100644 --- a/addons/easy_charts/LineChart/LineChart.gd +++ b/addons/easy_charts/LineChart/LineChart.gd @@ -78,7 +78,6 @@ export (bool) var boxed : bool = true export (Color) var v_lines_color : Color = Color("#cacaca") export (Color) var h_lines_color : Color = Color("#cacaca") export (Color) var outline_color : Color = Color("#1e1e1e") -export (float,0.01,1) var drawing_duration : float = 0.3 export (Font) var font : Font export (Font) var bold_font : Font export (Color) var font_color : Color = Color("#1e1e1e") diff --git a/addons/easy_charts/file.samples/datas2.csv b/addons/easy_charts/file.samples/datas2.csv index b3bd3a9..f8939cb 100644 --- a/addons/easy_charts/file.samples/datas2.csv +++ b/addons/easy_charts/file.samples/datas2.csv @@ -1,5 +1,5 @@ -Year;2009;2010;2011;2012;2013;2014;2015;2016;2017 -Column 1;36200;36600;37500;38700;39600;40500;41200;41803;42600 -Column 2;27200;27800;28500;29400;30200;30900;31500;31931;32600 -Column 3;26200;26600;27500;28700;29600;20500;21200;21803;22600 -Column 4;17200;17800;18500;19400;10200;10900;11500;11931;12600 +Year;2009;2010;2011;2012;2013;2014;2015;2016;2017 +Column 1;36200;36600;37500;38700;39600;40500;41200;41803;42600 +Column 2;27200;27800;28500;29400;30200;30900;31500;31931;32600 +Column 3;26200;26600;27500;28700;29600;20500;21200;21803;22600 +Column 4;17200;17800;18500;19400;10200;10900;11500;11931;12600 diff --git a/addons/easy_charts/plugin.gd b/addons/easy_charts/plugin.gd index ac288f3..c37c821 100644 --- a/addons/easy_charts/plugin.gd +++ b/addons/easy_charts/plugin.gd @@ -3,10 +3,11 @@ extends EditorPlugin func _enter_tree(): add_autoload_singleton("Utilities","res://addons/easy_charts/Utilities/utilities.gd") - add_custom_type("ChartContainer", "Container", load("Utilities/ChartContainer.gd"), load("Utilities/icons/linechart.svg")) - add_custom_type("ChartContainer2D", "Node2D", load("Utilities/ChartContainer2D.gd"), load("Utilities/icons/linechart2d.svg")) +# Containers are not really that useful +# add_custom_type("ChartContainer", "Container", load("Utilities/ChartContainer.gd"), load("Utilities/icons/linechart.svg")) +# add_custom_type("ChartContainer2D", "Node2D", load("Utilities/ChartContainer2D.gd"), load("Utilities/icons/linechart2d.svg")) func _exit_tree(): - remove_custom_type("ChartContainer") - remove_custom_type("ChartContainer2D") +# remove_custom_type("ChartContainer") +# remove_custom_type("ChartContainer2D") remove_autoload_singleton("Utilities")