diff --git a/README.md b/README.md index 6c25d0b..1212bbd 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A library of Charts plotted in Control, 2D and 3D nodes to visualize general purpose datasets. Author: *"Nicolo (fenix) Santilio"* -Version: *0.3.1* +Version: *0.4.1* Wiki: *[wip]* Godot Version: *3.2stable* diff --git a/addons/easy_charts/BarChart/BarChart.gd b/addons/easy_charts/BarChart/BarChart.gd index d68fd62..ec90b49 100644 --- a/addons/easy_charts/BarChart/BarChart.gd +++ b/addons/easy_charts/BarChart/BarChart.gd @@ -19,372 +19,372 @@ values of more than one measured variable. # --------------------- func _get_property_list(): - return [ - # Chart Properties - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/are_values_columns", - "type": TYPE_BOOL - }, - { - "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 - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/show_x_values_as_labels", - "type": TYPE_BOOL - }, - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "1,20,0.5", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/column_width", - "type": TYPE_REAL - }, - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "0,10,0.5", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/column_gap", - "type": TYPE_REAL - }, - - # Chart Display - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "0.1,10", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Display/x_decim", - "type": TYPE_REAL - }, - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "0.1,10", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Display/y_decim", - "type": TYPE_REAL - }, - - # Chart Style - { - "hint": 24, - "hint_string": "%d/%d:%s"%[TYPE_INT, PROPERTY_HINT_ENUM, - PoolStringArray(PointShapes.keys()).join(",")], - "name": "Chart_Style/points_shape", - "type": TYPE_ARRAY, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/function_colors", - "type": TYPE_COLOR_ARRAY - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/box_color", - "type": TYPE_COLOR - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/v_lines_color", - "type": TYPE_COLOR - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/h_lines_color", - "type": TYPE_COLOR - }, - { - "class_name": "Font", - "hint": PROPERTY_HINT_RESOURCE_TYPE, - "hint_string": "Font", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/font", - "type": TYPE_OBJECT - }, - { - "class_name": "Font", - "hint": PROPERTY_HINT_RESOURCE_TYPE, - "hint_string": "Font", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/bold_font", - "type": TYPE_OBJECT - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/font_color", - "type": TYPE_COLOR - }, - { - "hint": PROPERTY_HINT_ENUM, - "hint_string": PoolStringArray(TemplatesNames.keys()).join(","), - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/template", - "type": TYPE_INT - }, - - # Chart Modifiers - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Modifiers/invert_chart", - "type": TYPE_BOOL - }, - ] + return [ + # Chart Properties + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/are_values_columns", + "type": TYPE_BOOL + }, + { + "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 + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/show_x_values_as_labels", + "type": TYPE_BOOL + }, + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "1,20,0.5", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/column_width", + "type": TYPE_REAL + }, + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,10,0.5", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/column_gap", + "type": TYPE_REAL + }, + + # Chart Display + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0.1,10", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Display/x_decim", + "type": TYPE_REAL + }, + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0.1,10", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Display/y_decim", + "type": TYPE_REAL + }, + + # Chart Style + { + "hint": 24, + "hint_string": "%d/%d:%s"%[TYPE_INT, PROPERTY_HINT_ENUM, + PoolStringArray(PointShapes.keys()).join(",")], + "name": "Chart_Style/points_shape", + "type": TYPE_ARRAY, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/function_colors", + "type": TYPE_COLOR_ARRAY + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/box_color", + "type": TYPE_COLOR + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/v_lines_color", + "type": TYPE_COLOR + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/h_lines_color", + "type": TYPE_COLOR + }, + { + "class_name": "Font", + "hint": PROPERTY_HINT_RESOURCE_TYPE, + "hint_string": "Font", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/font", + "type": TYPE_OBJECT + }, + { + "class_name": "Font", + "hint": PROPERTY_HINT_RESOURCE_TYPE, + "hint_string": "Font", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/bold_font", + "type": TYPE_OBJECT + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/font_color", + "type": TYPE_COLOR + }, + { + "hint": PROPERTY_HINT_ENUM, + "hint_string": PoolStringArray(TemplatesNames.keys()).join(","), + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/template", + "type": TYPE_INT + }, + + # Chart Modifiers + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Modifiers/invert_chart", + "type": TYPE_BOOL + }, + ] func build_chart(): - SIZE = get_size() - origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) + SIZE = get_size() + origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) func structure_datas(database : Array, are_values_columns : bool, x_values_index : int): - # @x_values_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 - self.are_values_columns = are_values_columns - match are_values_columns: - true: - for row in database.size(): - var t_vals : Array - for column in database[row].size(): - if column == x_values_index: - var x_data = database[row][column] - if x_data.is_valid_float() or x_data.is_valid_integer(): - x_datas.append(x_data as float) - else: - x_datas.append(x_data.replace(",",".") as float) - else: - if row != 0: - var y_data = database[row][column] - if y_data.is_valid_float() or y_data.is_valid_integer(): - t_vals.append(y_data as float) - else: - t_vals.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()) - false: - for row in database.size(): - if row == x_values_index: - x_datas = (database[row]) - x_label = x_datas.pop_front() as String - 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 - - # 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 ordered_cluster = y_datas[cluster] as Array - ordered_cluster.sort() - ordered_cluster = PoolIntArray(ordered_cluster) - var margin_max = ordered_cluster[ordered_cluster.size()-1] - var margin_min = ordered_cluster[0] - to_order.append(margin_max) - to_order_min.append(margin_min) - - to_order.sort() - to_order_min.sort() - var margin = to_order.pop_back() - if not origin_at_zero: - y_margin_min = to_order_min.pop_front() - v_dist = y_decim * pow(10.0,str(margin).length()-2) - var multi = 0 - var p = (v_dist*multi) + ((y_margin_min) if not origin_at_zero else 0) - y_chors.append(p as String) - while p < margin: - multi+=1 - p = (v_dist*multi) + ((y_margin_min) if not origin_at_zero else 0) - y_chors.append(p as String) - - # draw x_labels - if not show_x_values_as_labels: - to_order.clear() - to_order = x_datas as PoolIntArray - - to_order.sort() - margin = to_order.pop_back() - if not origin_at_zero: - x_margin_min = to_order.pop_front() - h_dist = x_decim * pow(10.0,str(margin).length()-2) - multi = 0 - p = (h_dist*multi) + ((x_margin_min) if not origin_at_zero else 0) - x_labels.append(p as String) - while p < margin: - multi+=1 - p = (h_dist*multi) + ((x_margin_min) if not origin_at_zero else 0) - x_labels.append(p as String) + # @x_values_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 + self.are_values_columns = are_values_columns + match are_values_columns: + true: + for row in database.size(): + var t_vals : Array + for column in database[row].size(): + if column == x_values_index: + var x_data = database[row][column] + if x_data.is_valid_float() or x_data.is_valid_integer(): + x_datas.append(x_data as float) + else: + x_datas.append(x_data.replace(",",".") as float) + else: + if row != 0: + var y_data = database[row][column] + if y_data.is_valid_float() or y_data.is_valid_integer(): + t_vals.append(y_data as float) + else: + t_vals.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()) + false: + for row in database.size(): + if row == x_values_index: + x_datas = (database[row]) + x_label = x_datas.pop_front() as String + 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 + + # 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 ordered_cluster = y_datas[cluster] as Array + ordered_cluster.sort() + ordered_cluster = PoolIntArray(ordered_cluster) + var margin_max = ordered_cluster[ordered_cluster.size()-1] + var margin_min = ordered_cluster[0] + to_order.append(margin_max) + to_order_min.append(margin_min) + + to_order.sort() + to_order_min.sort() + var margin = to_order.pop_back() + if not origin_at_zero: + y_margin_min = to_order_min.pop_front() + v_dist = y_decim * pow(10.0,str(margin).length()-2) + var multi = 0 + var p = (v_dist*multi) + ((y_margin_min) if not origin_at_zero else 0) + y_chors.append(p as String) + while p < margin: + multi+=1 + p = (v_dist*multi) + ((y_margin_min) if not origin_at_zero else 0) + y_chors.append(p as String) + + # draw x_labels + if not show_x_values_as_labels: + to_order.clear() + to_order = x_datas as PoolIntArray + + to_order.sort() + margin = to_order.pop_back() + if not origin_at_zero: + x_margin_min = to_order.pop_front() + h_dist = x_decim * pow(10.0,str(margin).length()-2) + multi = 0 + p = (h_dist*multi) + ((x_margin_min) if not origin_at_zero else 0) + x_labels.append(p as String) + while p < margin: + multi+=1 + p = (h_dist*multi) + ((x_margin_min) if not origin_at_zero else 0) + x_labels.append(p as String) func calculate_pass(): - if invert_chart: - x_chors = y_labels as PoolStringArray - else: - if show_x_values_as_labels: - x_chors = x_datas as PoolStringArray - else: - x_chors = x_labels - - # calculate distance in pixel between 2 consecutive values/datas - if not are_values_columns: - x_pass = (SIZE.x - OFFSET.x*2 - (column_width) * ( y_datas.size() if not invert_chart else y_datas[0].size()+1 ) - column_gap - column_width/2) / ((x_chors.size()-1) if x_chors.size()!=1 else 1) - else: - x_pass = (SIZE.x - OFFSET.x*2 - (column_width) * ( y_datas.size() if invert_chart else y_datas[0].size()+1 ) - column_gap - column_width/2) / (x_chors.size()-1) - y_pass = origin.y / (y_chors.size()-1) + if invert_chart: + x_chors = y_labels as PoolStringArray + else: + if show_x_values_as_labels: + x_chors = x_datas as PoolStringArray + else: + x_chors = x_labels + + # calculate distance in pixel between 2 consecutive values/datas + if not are_values_columns: + x_pass = (SIZE.x - OFFSET.x*2 - (column_width) * ( y_datas.size() if not invert_chart else y_datas[0].size()+1 ) - column_gap - column_width/2) / ((x_chors.size()-1) if x_chors.size()!=1 else 1) + else: + x_pass = (SIZE.x - OFFSET.x*2 - (column_width) * ( y_datas.size() if invert_chart 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(): - x_coordinates.clear() - y_coordinates.clear() - point_values.clear() - point_positions.clear() - - if invert_chart: - for column in y_datas[0].size(): - var single_coordinates : Array - for row in y_datas: - if origin_at_zero: - single_coordinates.append((row[column]*y_pass)/v_dist) - else: - single_coordinates.append((row[column] - y_margin_min)*y_pass/v_dist) - y_coordinates.append(single_coordinates) - else: - for cluster in y_datas: - var single_coordinates : Array - for value in cluster.size(): - if origin_at_zero: - single_coordinates.append((cluster[value]*y_pass)/v_dist) - else: - 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(): - if origin_at_zero: - if not invert_chart: - x_coordinates.append(x_pass*x) - else: - x_coordinates.append(x*x_pass/h_dist) - else: - x_coordinates.append((x_datas[x] - x_margin_min)*x_pass/h_dist) - - for f in functions: - point_values.append([]) - point_positions.append([]) - - if invert_chart: - for function in y_coordinates.size(): - for function_value in y_coordinates[function].size(): - if are_values_columns: - point_values[function].append([x_datas[function_value],y_datas[function_value][function]]) - 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])) - else: - 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_datas[function_value],y_datas[function_value][function]]) - else: - for cluster in y_coordinates.size(): - for y in y_coordinates[cluster].size(): - if are_values_columns: - point_positions[y].append(Vector2(OFFSET.x/2 + column_width/2 + (column_width + column_gap)*y + x_coordinates[cluster] + origin.x, origin.y-y_coordinates[cluster][y])) - point_values[y].append([x_datas[cluster],y_datas[cluster][y]]) - else: - point_values[cluster].append([x_datas[y],y_datas[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])) + x_coordinates.clear() + y_coordinates.clear() + point_values.clear() + point_positions.clear() + + if invert_chart: + for column in y_datas[0].size(): + var single_coordinates : Array + for row in y_datas: + if origin_at_zero: + single_coordinates.append((row[column]*y_pass)/v_dist) + else: + single_coordinates.append((row[column] - y_margin_min)*y_pass/v_dist) + y_coordinates.append(single_coordinates) + else: + for cluster in y_datas: + var single_coordinates : Array + for value in cluster.size(): + if origin_at_zero: + single_coordinates.append((cluster[value]*y_pass)/v_dist) + else: + 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(): + if origin_at_zero: + if not invert_chart: + x_coordinates.append(x_pass*x) + else: + x_coordinates.append(x*x_pass/h_dist) + else: + x_coordinates.append((x_datas[x] - x_margin_min)*x_pass/h_dist) + + for f in functions: + point_values.append([]) + point_positions.append([]) + + if invert_chart: + for function in y_coordinates.size(): + for function_value in y_coordinates[function].size(): + if are_values_columns: + point_values[function].append([x_datas[function_value],y_datas[function_value][function]]) + 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])) + else: + 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_datas[function_value],y_datas[function_value][function]]) + else: + for cluster in y_coordinates.size(): + for y in y_coordinates[cluster].size(): + if are_values_columns: + point_positions[y].append(Vector2(OFFSET.x/2 + column_width/2 + (column_width + column_gap)*y + x_coordinates[cluster] + origin.x, origin.y-y_coordinates[cluster][y])) + point_values[y].append([x_datas[cluster],y_datas[cluster][y]]) + else: + point_values[cluster].append([x_datas[y],y_datas[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 _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) - - 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_point if invert_chart else _function], - Color.white, point_positions[_function][function_point], - point.format_value(point_values[_function][function_point], false, false), - y_labels[function_point if invert_chart else _function] as String) - PointContainer.add_child(point) - point.rect_size.y = origin.y - point_positions[_function][function_point].y - draw_line( Vector2(point_positions[_function][function_point].x, origin.y), - point_positions[_function][function_point], function_colors[_function], column_width, true) + 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) + + 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_point if invert_chart else _function], + Color.white, point_positions[_function][function_point], + point.format_value(point_values[_function][function_point], false, false), + y_labels[function_point if invert_chart else _function] as String) + PointContainer.add_child(point) + point.rect_size.y = origin.y - point_positions[_function][function_point].y + draw_line( Vector2(point_positions[_function][function_point].x, origin.y), + point_positions[_function][function_point], function_colors[_function], column_width, true) # draw_string(font, Vector2(point_positions[_function][function_point].x, origin.y+10), y_labels[function_point], font_color) 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) - var calculated_gap : float - if not are_values_columns: - calculated_gap = ( y_datas.size() if not invert_chart else y_datas[0].size()+1 ) - else: - calculated_gap = ( y_datas.size() if invert_chart else y_datas[0].size()+1 ) - draw_string(font,point+Vector2(-const_width/2*x_chors[p].length() + (column_width/2) * calculated_gap + column_gap,font_size),x_chors[p],font_color) + # 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) + var calculated_gap : float + if not are_values_columns: + calculated_gap = ( y_datas.size() if not invert_chart else y_datas[0].size()+1 ) + else: + calculated_gap = ( y_datas.size() if invert_chart else y_datas[0].size()+1 ) + draw_string(font,point+Vector2(-const_width/2*x_chors[p].length() + (column_width/2) * calculated_gap + column_gap,font_size),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,font_size/2),y_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,font_size/2),y_chors[p],font_color) func draw_chart_outlines(): # if boxed: - draw_line(Vector2(origin.x,0),Vector2(SIZE.x,0),box_color,1,true) - draw_line(Vector2(SIZE.x,0),Vector2(SIZE.x,origin.y),box_color,1,true) - draw_line(Vector2(SIZE.x,origin.y),origin,box_color,1,true) - draw_line(origin,Vector2(origin.x,0),box_color,1,true) + draw_line(Vector2(origin.x,0),Vector2(SIZE.x,0),box_color,1,true) + draw_line(Vector2(SIZE.x,0),Vector2(SIZE.x,origin.y),box_color,1,true) + draw_line(Vector2(SIZE.x,origin.y),origin,box_color,1,true) + draw_line(origin,Vector2(origin.x,0),box_color,1,true) func create_legend(): - legend.clear() - for function in functions: - var function_legend = FunctionLegend.instance() - var f_name : String - if invert_chart: - f_name = x_datas[function] as String - else: - f_name = y_labels[function] - var legend_font : Font - if font != null: - legend_font = font - if bold_font != null: - legend_font = bold_font - function_legend.create_legend(f_name,function_colors[function],bold_font,font_color) - legend.append(function_legend) - + legend.clear() + for function in functions: + var function_legend = FunctionLegend.instance() + var f_name : String + if invert_chart: + f_name = x_datas[function] as String + else: + f_name = y_labels[function] + var legend_font : Font + if font != null: + legend_font = font + if bold_font != null: + legend_font = bold_font + function_legend.create_legend(f_name,function_colors[function],bold_font,font_color) + legend.append(function_legend) + diff --git a/addons/easy_charts/BarChart/BarChart.tscn b/addons/easy_charts/BarChart/BarChart.tscn index 918599e..d809f70 100644 --- a/addons/easy_charts/BarChart/BarChart.tscn +++ b/addons/easy_charts/BarChart/BarChart.tscn @@ -82,10 +82,10 @@ __meta__ = { [node name="PointData" parent="PointData" index="0"] visible = false -margin_left = -154.32 -margin_top = -115.171 -margin_right = -154.456 -margin_bottom = -114.371 +margin_left = -223.769 +margin_top = -45.5654 +margin_right = -223.906 +margin_bottom = -44.7655 theme = SubResource( 1 ) [editable path="PointData"] diff --git a/addons/easy_charts/PieChart/PieChart.tscn b/addons/easy_charts/PieChart/PieChart.tscn new file mode 100644 index 0000000..42591fa --- /dev/null +++ b/addons/easy_charts/PieChart/PieChart.tscn @@ -0,0 +1,62 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/easy_charts/PieChart/pie_chart.gd" type="Script" id=1] +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.tscn" type="PackedScene" id=2] + +[node name="PieChart" type="Control"] +anchor_right = 1.0 +anchor_bottom = 0.993333 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": true +} +Chart_Properties/are_values_columns = false +Chart_Properties/labels_index = 0 +Chart_Properties/show_x_values_as_labels = true +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/font = null +Chart_Style/bold_font = null +Chart_Style/font_color = Color( 0.117647, 0.117647, 0.117647, 1 ) +Chart_Style/template = 0 +Chart_Modifiers/rotation = 0.0 + +[node name="Background" type="ColorRect" parent="."] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="Points" type="Control" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Legend" type="HBoxContainer" parent="."] +visible = false +anchor_right = 1.0 +anchor_bottom = 0.106667 +__meta__ = { +"_edit_use_anchors_": true +} + +[node name="ChartName" type="Label" parent="."] +use_parent_material = true +anchor_right = 1.0 +anchor_bottom = 0.0233333 +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": true +} + +[node name="PointData" parent="." instance=ExtResource( 2 )] + +[node name="PointData" parent="PointData" index="0"] +visible = false +margin_left = -148.407 +margin_top = -156.174 +margin_right = -148.543 +margin_bottom = -155.375 + +[editable path="PointData"] diff --git a/addons/easy_charts/PieChart/pie_chart.gd b/addons/easy_charts/PieChart/pie_chart.gd new file mode 100644 index 0000000..addb8d4 --- /dev/null +++ b/addons/easy_charts/PieChart/pie_chart.gd @@ -0,0 +1,219 @@ +tool +extends Chart + +var should_draw : bool = false +var area_angles : Array +var slices : Array +var areas : Array +var areas_interacted : Array + +class CustomSorter: + static func sort_ascending(a,b): + if a[1] < b[1]: + return true + return false + +func _get_property_list(): + return [ + # Chart Properties + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/are_values_columns", + "type": TYPE_BOOL + }, + { + "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 + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/show_x_values_as_labels", + "type": TYPE_BOOL + }, + + # Chart Style + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/function_colors", + "type": TYPE_COLOR_ARRAY + }, + { + "class_name": "Font", + "hint": PROPERTY_HINT_RESOURCE_TYPE, + "hint_string": "Font", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/font", + "type": TYPE_OBJECT + }, + { + "class_name": "Font", + "hint": PROPERTY_HINT_RESOURCE_TYPE, + "hint_string": "Font", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/bold_font", + "type": TYPE_OBJECT + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/font_color", + "type": TYPE_COLOR + }, + { + "hint": PROPERTY_HINT_ENUM, + "hint_string": PoolStringArray(TemplatesNames.keys()).join(","), + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/template", + "type": TYPE_INT + }, + + # Chart Modifiers + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,360", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Modifiers/rotation", + "type": TYPE_REAL + }, + ] + + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass + +func plot_placeholder() -> void: + datas = [ + ["United States",46], + ["Great Britain",27], + ["China",26], + ["Russia",19], + ["Germany",17] + ] + + function_colors = [ + Color.red, + Color.white, + Color.yellow, + Color.green, + Color.blue + ] + plot_from_array(datas) + +func structure_datas(database: Array, are_values_columns: bool, x_values_index: int): + # @x_values_index can be either a column or a row relative to x values + clean_variables() + self.are_values_columns = are_values_columns + if are_values_columns: + for row in database.size(): + var t_vals: Array + for column in database[row].size(): + if column == x_values_index: + var x_data = database[row][column] + if x_data.is_valid_float() or x_data.is_valid_integer(): + x_datas.append(x_data as float) + else: + x_datas.append(x_data.replace(",", ".") as float) + else: + if row != 0: + var y_data = database[row][column] + if y_data.is_valid_float() or y_data.is_valid_integer(): + t_vals.append(y_data as float) + else: + t_vals.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()) + else: + for row in database.size(): + if row == x_values_index: + x_datas = (database[row]) + x_label = x_datas.pop_front() as String + 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 + + +func build_chart(): + SIZE = get_size() + origin = SIZE/2 + radius = (SIZE.y/2 - 20) if SIZE.y < SIZE.x else (SIZE.x/2 - 20) + +func calculate_pass(): + var tot : float + for y_data in y_datas: tot+=y_data[0] + x_pass = 360/tot + +func calculate_coordinates(): + area_angles.clear() + slices.clear() + areas.clear() + var from : float = 0.0 + var to : float = y_datas[0][0]*x_pass + area_angles.append([from,to]) + for info in range(y_datas.size()): + slices.append(Slice.new(y_labels[info], str(y_datas[info][0]), from, to, x_label+" : "+x_datas[0], function_colors[info])) + areas.append(calculate_circle_arc_polygon(origin, radius, from + rotation, to + rotation, function_colors[info])) + from = to + to = (y_datas[info+1][0]*x_pass + from) if info < y_datas.size()-1 else (360) + area_angles.append([from, to]) + + create_legend() + +func calculate_circle_arc_polygon(center : Vector2, radius : float, angle_from : float, angle_to : float, color : Color) -> PoolVector2Array: + var nb_points : int = 32 + var points_arc : PoolVector2Array = PoolVector2Array() + points_arc.push_back(center) + var colors : PoolColorArray = PoolColorArray([color]) + + for i in range(nb_points + 1): + var angle_point = deg2rad(angle_from + i * (angle_to - angle_from) / nb_points - 90) + points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) + return points_arc + +func _draw(): + _draw_areas() + + if mouse_on_slice: + _draw_arc(area_angles[mouse_on_area], mouse_on_area) + mouse_on_slice = false + +func _draw_arc(arc : Array, index : int): + var temp_color : Color = function_colors[index] + temp_color.a = 0.7 + draw_arc(origin, radius + 6, deg2rad(arc[0]-90 + rotation), deg2rad(arc[1]-90 + rotation), 32, temp_color, 4) + +func _draw_areas(): + for area_idx in range(areas.size()): + draw_polygon(areas[area_idx], [function_colors[area_idx]]) + +var mouse_on_area : int +var mouse_on_slice : bool = false + +func _input(event): + if event is InputEventMouseMotion: + for area_idx in range(areas.size()): + if Geometry.is_point_in_polygon(event.global_position, areas[area_idx]): + mouse_on_slice = true + mouse_on_area = area_idx + show_slice_data(slices[area_idx]) + update() + + if not mouse_on_slice: + mouse_on_area = -1 + mouse_on_slice = false + hide_data() + update() diff --git a/addons/easy_charts/RadarChart/RadarChart.gd b/addons/easy_charts/RadarChart/RadarChart.gd index d6b5490..9434358 100644 --- a/addons/easy_charts/RadarChart/RadarChart.gd +++ b/addons/easy_charts/RadarChart/RadarChart.gd @@ -14,254 +14,254 @@ distinct correlations, trade-offs, and a multitude of other comparative measures """ func _get_property_list(): - return [ - # Chart Properties - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/are_values_columns", - "type": TYPE_BOOL - }, - { - "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 - }, - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "-1,100,1", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/function_names_index", - "type": TYPE_INT - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/use_height_as_radius", - "type": TYPE_BOOL - }, - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "0,2000", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Properties/radius", - "type": TYPE_REAL - }, - - # Chart Display - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "0.1,100", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Display/full_scale", - "type": TYPE_REAL - }, - - # Chart Style - { - "hint": 24, - "hint_string": "%d/%d:%s"%[TYPE_INT, PROPERTY_HINT_ENUM, - PoolStringArray(PointShapes.keys()).join(",")], - "name": "Chart_Style/points_shape", - "type": TYPE_ARRAY, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/function_colors", - "type": TYPE_COLOR_ARRAY - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/outline_color", - "type": TYPE_COLOR - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/grid_color", - "type": TYPE_COLOR - }, - { - "class_name": "Font", - "hint": PROPERTY_HINT_RESOURCE_TYPE, - "hint_string": "Font", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/font", - "type": TYPE_OBJECT - }, - { - "class_name": "Font", - "hint": PROPERTY_HINT_RESOURCE_TYPE, - "hint_string": "Font", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/bold_font", - "type": TYPE_OBJECT - }, - { - "hint": PROPERTY_HINT_NONE, - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/font_color", - "type": TYPE_COLOR - }, - { - "hint": PROPERTY_HINT_ENUM, - "hint_string": PoolStringArray(TemplatesNames.keys()).join(","), - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Style/template", - "type": TYPE_INT - }, - { - "hint": PROPERTY_HINT_RANGE, - "hint_string": "0,360", - "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, - "name": "Chart_Modifiers/rotation", - "type": TYPE_REAL - }, - ] + return [ + # Chart Properties + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/are_values_columns", + "type": TYPE_BOOL + }, + { + "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 + }, + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "-1,100,1", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/function_names_index", + "type": TYPE_INT + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/use_height_as_radius", + "type": TYPE_BOOL + }, + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,2000", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Properties/radius", + "type": TYPE_REAL + }, + + # Chart Display + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0.1,100", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Display/full_scale", + "type": TYPE_REAL + }, + + # Chart Style + { + "hint": 24, + "hint_string": "%d/%d:%s"%[TYPE_INT, PROPERTY_HINT_ENUM, + PoolStringArray(PointShapes.keys()).join(",")], + "name": "Chart_Style/points_shape", + "type": TYPE_ARRAY, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/function_colors", + "type": TYPE_COLOR_ARRAY + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/outline_color", + "type": TYPE_COLOR + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/grid_color", + "type": TYPE_COLOR + }, + { + "class_name": "Font", + "hint": PROPERTY_HINT_RESOURCE_TYPE, + "hint_string": "Font", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/font", + "type": TYPE_OBJECT + }, + { + "class_name": "Font", + "hint": PROPERTY_HINT_RESOURCE_TYPE, + "hint_string": "Font", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/bold_font", + "type": TYPE_OBJECT + }, + { + "hint": PROPERTY_HINT_NONE, + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/font_color", + "type": TYPE_COLOR + }, + { + "hint": PROPERTY_HINT_ENUM, + "hint_string": PoolStringArray(TemplatesNames.keys()).join(","), + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Style/template", + "type": TYPE_INT + }, + { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,360", + "usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, + "name": "Chart_Modifiers/rotation", + "type": TYPE_REAL + }, + ] func structure_datas(database : Array, are_values_columns : bool, labels_index : int): - # @x_values_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 - self.labels_index = labels_index - self.are_values_columns = are_values_columns - match are_values_columns: - true: - for row in database.size(): - if row == labels_index: - x_labels = database[row] as PoolStringArray - else: - if database[row].empty() or database[row].size() < 2: - continue - x_datas.append(PoolRealArray(database[row] as Array)) - - for column in database[row].size(): - if column == function_names_index: - y_labels.append(database[row][column]) - false: - for row in database.size(): - if row == function_names_index: - y_labels = database[row] as PoolStringArray - - var x_temp_datas : PoolRealArray = [] - for column in database[row].size(): - if column == labels_index: - x_labels.append(database[row][column] as String) - else: - x_temp_datas.append(database[row][column] as float) - x_datas.append(x_temp_datas) - - if labels_index == -1 : - for data in x_datas[0].size(): - x_labels.append("Element %s" % data) - - if function_names_index == -1 : - for data in x_datas.size(): - y_labels.append("Function %s" % data) + # @x_values_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 + self.labels_index = labels_index + self.are_values_columns = are_values_columns + match are_values_columns: + true: + for row in database.size(): + if row == labels_index: + x_labels = database[row] as PoolStringArray + else: + if database[row].empty() or database[row].size() < 2: + continue + x_datas.append(PoolRealArray(database[row] as Array)) + + for column in database[row].size(): + if column == function_names_index: + y_labels.append(database[row][column]) + false: + for row in database.size(): + if row == function_names_index: + y_labels = database[row] as PoolStringArray + + var x_temp_datas : PoolRealArray = [] + for column in database[row].size(): + if column == labels_index: + x_labels.append(database[row][column] as String) + else: + x_temp_datas.append(database[row][column] as float) + x_datas.append(x_temp_datas) + + if labels_index == -1 : + for data in x_datas[0].size(): + x_labels.append("Element %s" % data) + + if function_names_index == -1 : + for data in x_datas.size(): + y_labels.append("Function %s" % data) func build_chart(): - SIZE = get_size() - origin = OFFSET + SIZE/2 + SIZE = get_size() + origin = OFFSET + SIZE/2 var radar_polygon : Array func calculate_pass() : - var ordered_max : Array - for data in x_datas : - var ordered_data : Array = (data as Array) - ordered_data.sort() - ordered_max.append(ordered_data.pop_back()) - ordered_max.sort() - var max_value : float = ordered_max.pop_back() - var dist = full_scale * pow(10.0,str(max_value).length()-2) - var multi = 0 - var value = dist * multi - x_chors.append(value as String) - while value < max_value: - multi+=1 - value = dist * multi - x_chors.append(value as String) + var ordered_max : Array + for data in x_datas : + var ordered_data : Array = (data as Array) + ordered_data.sort() + ordered_max.append(ordered_data.pop_back()) + ordered_max.sort() + var max_value : float = ordered_max.pop_back() + var dist = full_scale * pow(10.0,str(max_value).length()-2) + var multi = 0 + var value = dist * multi + x_chors.append(value as String) + while value < max_value: + multi+=1 + value = dist * multi + x_chors.append(value as String) func calculate_coordinates(): - for chor in x_chors.size(): - var inner_polyline : PoolVector2Array - var scalar_factor : float = (x_chors[chor] as float/x_chors.back() as float) - for function in functions: - var angle : float = ((2 * PI * function) / functions) - PI /2 + deg2rad(rotation) - var x_coordinate : float = (radius if (not use_height_as_radius and radius 0): - s = str(s,",", n[i]) - else: - s = str(s,n[i]) - - return s.replace("Null","") + 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 set_value( v : Array = [] ) : - point_value = v + point_value = v func set_color_point( c : Color ): - color = c + color = c func set_function( f : String ): - function = f + function = f func set_shape(s : int): - shape = s + shape = s # Public Getters func get_value() -> Array: - return point_value + return point_value func get_color_point() -> Color: - return color + return color func get_function() -> String: - return function + return function func get_shape() -> int: - return shape + return shape diff --git a/addons/easy_charts/Utilities/Point/PointData.gd b/addons/easy_charts/Utilities/Point/PointData.gd index f4f703e..180be55 100644 --- a/addons/easy_charts/Utilities/Point/PointData.gd +++ b/addons/easy_charts/Utilities/Point/PointData.gd @@ -13,36 +13,57 @@ onready var Value : Label = $PointData/Value/y onready var Function : Label = $PointData/Function func _ready(): - pass + hide() func _process(delta): - if get_global_mouse_position().y > OFFSET.y + GAP.y: - rect_position = get_global_mouse_position() - OFFSET - GAP - else: - rect_position = get_global_mouse_position() + GAP*5 - OFFSET + if get_global_mouse_position().y > OFFSET.y + GAP.y: + rect_position = get_global_mouse_position() - OFFSET - GAP + else: + rect_position = get_global_mouse_position() + GAP*5 - OFFSET func update_datas(point : Control): - update_size() - - get("custom_styles/panel").set("bg_color",point.color) - - var font_color : Color - if point.color.g < 0.75: - font_color = Color(1,1,1,1) - else: - font_color = Color(0,0,0,1) - Data.set("custom_colors/font_color",font_color) - Value.set("custom_colors/font_color",font_color) - Function.set("custom_colors/font_color",font_color) - get("custom_styles/panel").set("border_color",font_color) - - Data.set_text(point.point_value[0]+":") - Value.set_text(point.point_value[1]) - Function.set_text(point.function) - update() - show() + update_size() + + get("custom_styles/panel").set("bg_color",point.color) + + var font_color : Color + if point.color.g < 0.75: + font_color = Color(1,1,1,1) + else: + font_color = Color(0,0,0,1) + Data.set("custom_colors/font_color",font_color) + Value.set("custom_colors/font_color",font_color) + Function.set("custom_colors/font_color",font_color) + get("custom_styles/panel").set("border_color",font_color) + + Data.set_text(point.point_value[0]+":") + Value.set_text(point.point_value[1]) + Function.set_text(point.function) + update() + show() + +func update_slice_datas(slice : Slice): + update_size() + + get("custom_styles/panel").set("bg_color",slice.color) + + var font_color : Color + if slice.color.g < 0.75: + font_color = Color(1,1,1,1) + else: + font_color = Color(0,0,0,1) + Data.set("custom_colors/font_color",font_color) + Value.set("custom_colors/font_color",font_color) + Function.set("custom_colors/font_color",font_color) + get("custom_styles/panel").set("border_color",font_color) + + Data.set_text(slice.x_value+":") + Value.set_text(slice.y_value) + Function.set_text(slice.function) + update() + show() func update_size(): - OFFSET.x = get_size().x/2 - OFFSET.y = get_size().y - GAP.y = OFFSET.y/3 + OFFSET.x = get_size().x/2 + OFFSET.y = get_size().y + GAP.y = OFFSET.y/3 diff --git a/addons/easy_charts/Utilities/Point/PointData.tscn b/addons/easy_charts/Utilities/Point/PointData.tscn index bdbd6c8..520bda4 100644 --- a/addons/easy_charts/Utilities/Point/PointData.tscn +++ b/addons/easy_charts/Utilities/Point/PointData.tscn @@ -24,8 +24,10 @@ corner_detail = 20 [node name="PointData" type="PanelContainer" parent="."] anchor_right = 0.0694688 anchor_bottom = 0.067 -margin_right = -0.136002 -margin_bottom = 0.799999 +margin_left = -256.805 +margin_top = -36.1267 +margin_right = -256.941 +margin_bottom = -35.3267 grow_horizontal = 2 mouse_filter = 2 custom_styles/panel = SubResource( 1 ) diff --git a/addons/easy_charts/Utilities/Scripts/Chart.gd b/addons/easy_charts/Utilities/Scripts/Chart.gd index 681cdee..3af5a09 100644 --- a/addons/easy_charts/Utilities/Scripts/Chart.gd +++ b/addons/easy_charts/Utilities/Scripts/Chart.gd @@ -9,6 +9,7 @@ signal point_pressed(point) onready var PointData = $PointData/PointData onready var Points = $Points onready var Legend = $Legend +onready var ChartName : Label = $ChartName # Scenes and Reosurces ...................... var point_node : PackedScene = preload("../Point/Point.tscn") @@ -256,25 +257,6 @@ func _ready(): templates = Utilities._load_templates() # .......................... Shared Functions and virtuals ........................ - -#func _plot(source_ : String, delimiter_ : String, are_values_columns_ : bool, x_values_index_ : int, invert_chart_ : bool = false): -# randomize() -# -# load_font() -# PointData.hide() -# -# datas = read_datas(source_,delimiter_) -# structure_datas(datas,are_values_columns_,x_values_index_) -# build_chart() -# count_functions() -# calculate_pass() -# calculate_coordinates() -# calculate_colors() -# set_shapes() -# create_legend() -# emit_signal("chart_plotted") - - func plot(): load_font() PointData.hide() @@ -284,7 +266,27 @@ func plot(): return datas = read_datas(source) - structure_datas(datas,are_values_columns,labels_index) + structure_datas(datas.duplicate(true),are_values_columns,labels_index) + build_chart() + count_functions() + calculate_pass() + calculate_coordinates() + calculate_colors() + set_shapes() + create_legend() + emit_signal("chart_plotted",self) + connect("item_rect_changed", self, "redraw") + +func plot_from_csv(csv_file : String, _delimiter : String = delimiter): + load_font() + PointData.hide() + + if csv_file == "" or csv_file == 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(csv_file, _delimiter) + structure_datas(datas.duplicate(true),are_values_columns,labels_index) build_chart() count_functions() calculate_pass() @@ -294,12 +296,51 @@ func plot(): create_legend() emit_signal("chart_plotted",self) +func plot_from_array(array : Array) -> void: + load_font() + PointData.hide() + + if array.empty(): + Utilities._print_message("Can't plot a chart without an empty Array.",1) + return + + datas = array + structure_datas(datas.duplicate(true),are_values_columns,labels_index) + build_chart() + count_functions() + calculate_pass() + calculate_coordinates() + calculate_colors() + set_shapes() + create_legend() + emit_signal("chart_plotted",self) + +func update_plot_data(array : Array) -> void: + if array.empty(): + Utilities._print_message("Can't plot a chart without an empty Array.",1) + return + + datas.append(array) + structure_datas(datas.duplicate(true),are_values_columns,labels_index) + redraw() + count_functions() + calculate_colors() + set_shapes() + create_legend() + emit_signal("chart_plotted",self) + + update() + +func plot_placeholder() -> void: + pass + func load_font(): if font != null: font_size = font.get_height() var theme : Theme = Theme.new() theme.set_default_font(font) - PointData.set_theme(theme) + set_theme(theme) + else: var lbl = Label.new() font = lbl.get_font("") @@ -308,21 +349,20 @@ func load_font(): PointData.Data.set("custom_fonts/font",bold_font) func calculate_colors(): - if function_colors.empty() or function_colors.size() < functions: - for function in functions: - function_colors.append(Color("#1e1e1e")) + if function_colors.size() <= functions: + for function in range(functions - function_colors.size() + 1): function_colors.append(Color(randf(),randf(), randf())) func set_shapes(): if points_shape.empty() or points_shape.size() < functions: for function in functions: points_shape.append(PointShapes.Dot) -func read_datas(source : String): +func read_datas(source : String, _delimiter : String = delimiter): var file : File = File.new() file.open(source,File.READ) var content : Array while not file.eof_reached(): - var line : PoolStringArray = file.get_csv_line(delimiter) + var line : PoolStringArray = file.get_csv_line(_delimiter) content.append(line) file.close() for data in content: @@ -355,24 +395,41 @@ func redraw(): calculate_coordinates() update() +func clean_variables(): + x_datas.clear() + y_datas.clear() + x_label = "" + x_labels.clear() + y_labels.clear() + # .................. VIRTUAL FUNCTIONS ......................... -func calculate_pass(): - pass - -func calculate_coordinates(): - pass - func structure_datas(database : Array, are_values_columns : bool, labels_index : int): pass func build_chart(): pass +func calculate_pass(): + pass + +func calculate_coordinates(): + pass + func function_colors(): pass func create_legend(): - pass + legend.clear() + for function in functions: + var function_legend = FunctionLegend.instance() + var f_name : String = y_labels[function] + var legend_font : Font + if font != null: + legend_font = font + if bold_font != null: + legend_font = bold_font + function_legend.create_legend(f_name,function_colors[function],bold_font,font_color) + legend.append(function_legend) # ........................... Shared Setters & Getters .............................. func apply_template(template_name : int): @@ -409,7 +466,7 @@ func set_chart_colors(f_colors : Array, o_color : Color, b_color : Color, g_colo v_lines_color = v_lines # !!! API v2 -func set_chart_fonts(normal_font : Font, bold_font : Font, f_color : Color): +func set_chart_fonts(normal_font : Font, bold_font : Font, f_color : Color = Color.white): font = normal_font self.bold_font = bold_font font_color = f_color @@ -542,3 +599,7 @@ func show_data(point : Point): func hide_data(): PointData.hide() + +func show_slice_data(slice : Slice): + PointData.update_slice_datas(slice) + PointData.show() diff --git a/addons/easy_charts/Utilities/Slice/slice.gd b/addons/easy_charts/Utilities/Slice/slice.gd new file mode 100644 index 0000000..6dab277 --- /dev/null +++ b/addons/easy_charts/Utilities/Slice/slice.gd @@ -0,0 +1,17 @@ +extends Reference +class_name Slice + +var x_value : String +var y_value : String +var from_angle : float +var to_angle : float +var function : String +var color : Color + +func _init(x : String, y : String, from : float, to : float, fun : String, col : Color): + x_value = x + y_value = y + from_angle = from + to_angle = to + function = fun + color = col diff --git a/addons/easy_charts/file.samples/2_columns.csv.import b/addons/easy_charts/file.samples/2_columns.csv.import new file mode 100644 index 0000000..a005d65 --- /dev/null +++ b/addons/easy_charts/file.samples/2_columns.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/2_columns.csv" +[params] + diff --git a/addons/easy_charts/file.samples/datas_on_columns.csv.import b/addons/easy_charts/file.samples/datas_on_columns.csv.import new file mode 100644 index 0000000..22f1cc0 --- /dev/null +++ b/addons/easy_charts/file.samples/datas_on_columns.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/datas_on_columns.csv" +[params] + diff --git a/addons/easy_charts/file.samples/datas_on_rows.csv.import b/addons/easy_charts/file.samples/datas_on_rows.csv.import new file mode 100644 index 0000000..865b2ef --- /dev/null +++ b/addons/easy_charts/file.samples/datas_on_rows.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/datas_on_rows.csv" +[params] + diff --git a/addons/easy_charts/file.samples/gei_jam#2columns.csv.import b/addons/easy_charts/file.samples/gei_jam#2columns.csv.import new file mode 100644 index 0000000..b661865 --- /dev/null +++ b/addons/easy_charts/file.samples/gei_jam#2columns.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/gei_jam#2columns.csv" +[params] + diff --git a/addons/easy_charts/file.samples/godot_engine_italia_jam#2.csv.import b/addons/easy_charts/file.samples/godot_engine_italia_jam#2.csv.import new file mode 100644 index 0000000..be821ee --- /dev/null +++ b/addons/easy_charts/file.samples/godot_engine_italia_jam#2.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/godot_engine_italia_jam#2.csv" +[params] + diff --git a/addons/easy_charts/file.samples/pie.csv b/addons/easy_charts/file.samples/pie.csv new file mode 100644 index 0000000..d22a361 --- /dev/null +++ b/addons/easy_charts/file.samples/pie.csv @@ -0,0 +1,8 @@ +Country,Votes +United States,46 +Great Britain,27 +China,26 +Russia,19 +Germany,17 +Italy,34 +Sweden,83 diff --git a/addons/easy_charts/file.samples/pie.csv.import b/addons/easy_charts/file.samples/pie.csv.import new file mode 100644 index 0000000..5d82515 --- /dev/null +++ b/addons/easy_charts/file.samples/pie.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/pie.csv" +[params] + diff --git a/addons/easy_charts/file.samples/radar.csv.import b/addons/easy_charts/file.samples/radar.csv.import new file mode 100644 index 0000000..467fd17 --- /dev/null +++ b/addons/easy_charts/file.samples/radar.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/radar.csv" +[params] + diff --git a/addons/easy_charts/file.samples/scatter.csv.import b/addons/easy_charts/file.samples/scatter.csv.import new file mode 100644 index 0000000..cc304f9 --- /dev/null +++ b/addons/easy_charts/file.samples/scatter.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/scatter.csv" +[params] + diff --git a/addons/easy_charts/plugin.cfg b/addons/easy_charts/plugin.cfg index bdd0c15..71d820b 100644 --- a/addons/easy_charts/plugin.cfg +++ b/addons/easy_charts/plugin.cfg @@ -3,5 +3,5 @@ name="EasyCharts" description="" author="Nicolò \"fenix\" Santilio" -version="0.3.1" +version="0.4.1" script="plugin.gd"