From 33973845828c6ebd792afcb0aa69dd5197da3499 Mon Sep 17 00:00:00 2001 From: fenix-hub Date: Thu, 14 May 2020 02:36:45 +0200 Subject: [PATCH] Add files via upload --- addons/easy_charts/BarChart/BarChart.gd | 397 ++++++++++++++ addons/easy_charts/BarChart/BarChart.tscn | 54 ++ addons/easy_charts/BarChart2D/BarChart2D.gd | 486 +++++++++++++++++ addons/easy_charts/BarChart2D/BarChart2D.tscn | 48 ++ .../BarChart2D/LineChart2D.tscn.depren | 108 ++++ addons/easy_charts/LineChart/LineChart.gd | 397 ++++++++++++++ addons/easy_charts/LineChart/LineChart.tscn | 49 ++ addons/easy_charts/LineChart2D/LineChart2D.gd | 494 ++++++++++++++++++ .../easy_charts/LineChart2D/LineChart2D.tscn | 49 ++ .../LineChart2D/LineChart2D.tscn.depren | 108 ++++ .../ScatterChart3D/ScatterChart3D.gd | 10 + .../ScatterChart3D/ScatterChart3D.tscn | 41 ++ .../easy_charts/Utilities/ChartContainer.gd | 40 ++ .../easy_charts/Utilities/ChartContainer2D.gd | 40 ++ .../Utilities/Legend/ColorLabel.tscn | 12 + .../Utilities/Legend/FunctionLegend.gd | 21 + .../Utilities/Legend/FunctionLegend.tscn | 28 + addons/easy_charts/Utilities/Point/Point.gd | 80 +++ addons/easy_charts/Utilities/Point/Point.tscn | 17 + .../easy_charts/Utilities/Point/PointData.gd | 46 ++ .../Utilities/Point/PointData.tscn | 68 +++ .../Utilities/Rect/Line-Graph1.png | Bin 0 -> 71512 bytes .../Utilities/Rect/Line-Graph1.png.import | 34 ++ addons/easy_charts/Utilities/Rect/Rect.gd | 66 +++ addons/easy_charts/Utilities/Rect/Rect.tscn | 11 + .../easy_charts/Utilities/icons/linechart.svg | 155 ++++++ .../Utilities/icons/linechart.svg.import | 34 ++ .../Utilities/icons/linechart2d.svg | 155 ++++++ .../Utilities/icons/linechart2d.svg.import | 34 ++ addons/easy_charts/Utilities/utilities.gd | 21 + addons/easy_charts/file.samples/datas.csv | 10 + .../easy_charts/file.samples/datas.csv.import | 10 + addons/easy_charts/file.samples/datas2.csv | 5 + .../file.samples/datas2.csv.import | 10 + addons/easy_charts/icon.png | Bin 0 -> 69813 bytes addons/easy_charts/icon.png.import | 34 ++ addons/easy_charts/plugin.cfg | 7 + addons/easy_charts/plugin.gd | 12 + addons/easy_charts/templates.json | 44 ++ 39 files changed, 3235 insertions(+) create mode 100644 addons/easy_charts/BarChart/BarChart.gd create mode 100644 addons/easy_charts/BarChart/BarChart.tscn create mode 100644 addons/easy_charts/BarChart2D/BarChart2D.gd create mode 100644 addons/easy_charts/BarChart2D/BarChart2D.tscn create mode 100644 addons/easy_charts/BarChart2D/LineChart2D.tscn.depren create mode 100644 addons/easy_charts/LineChart/LineChart.gd create mode 100644 addons/easy_charts/LineChart/LineChart.tscn create mode 100644 addons/easy_charts/LineChart2D/LineChart2D.gd create mode 100644 addons/easy_charts/LineChart2D/LineChart2D.tscn create mode 100644 addons/easy_charts/LineChart2D/LineChart2D.tscn.depren create mode 100644 addons/easy_charts/ScatterChart3D/ScatterChart3D.gd create mode 100644 addons/easy_charts/ScatterChart3D/ScatterChart3D.tscn create mode 100644 addons/easy_charts/Utilities/ChartContainer.gd create mode 100644 addons/easy_charts/Utilities/ChartContainer2D.gd create mode 100644 addons/easy_charts/Utilities/Legend/ColorLabel.tscn create mode 100644 addons/easy_charts/Utilities/Legend/FunctionLegend.gd create mode 100644 addons/easy_charts/Utilities/Legend/FunctionLegend.tscn create mode 100644 addons/easy_charts/Utilities/Point/Point.gd create mode 100644 addons/easy_charts/Utilities/Point/Point.tscn create mode 100644 addons/easy_charts/Utilities/Point/PointData.gd create mode 100644 addons/easy_charts/Utilities/Point/PointData.tscn create mode 100644 addons/easy_charts/Utilities/Rect/Line-Graph1.png create mode 100644 addons/easy_charts/Utilities/Rect/Line-Graph1.png.import create mode 100644 addons/easy_charts/Utilities/Rect/Rect.gd create mode 100644 addons/easy_charts/Utilities/Rect/Rect.tscn create mode 100644 addons/easy_charts/Utilities/icons/linechart.svg create mode 100644 addons/easy_charts/Utilities/icons/linechart.svg.import create mode 100644 addons/easy_charts/Utilities/icons/linechart2d.svg create mode 100644 addons/easy_charts/Utilities/icons/linechart2d.svg.import create mode 100644 addons/easy_charts/Utilities/utilities.gd create mode 100644 addons/easy_charts/file.samples/datas.csv create mode 100644 addons/easy_charts/file.samples/datas.csv.import create mode 100644 addons/easy_charts/file.samples/datas2.csv create mode 100644 addons/easy_charts/file.samples/datas2.csv.import create mode 100644 addons/easy_charts/icon.png create mode 100644 addons/easy_charts/icon.png.import create mode 100644 addons/easy_charts/plugin.cfg create mode 100644 addons/easy_charts/plugin.gd create mode 100644 addons/easy_charts/templates.json diff --git a/addons/easy_charts/BarChart/BarChart.gd b/addons/easy_charts/BarChart/BarChart.gd new file mode 100644 index 0000000..07e7ae6 --- /dev/null +++ b/addons/easy_charts/BarChart/BarChart.gd @@ -0,0 +1,397 @@ +tool +extends Control + +onready var PointData = $PointData/PointData +onready var Points = $Points +onready var Legend = $Legend + +var point_node : PackedScene = preload("../Utilities/Point/Point.tscn") +var FunctionLegend : PackedScene = preload("../Utilities/Legend/FunctionLegend.tscn") + +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 origin : Vector2 + +# actual distance between x and y values +var x_pass : float +var y_pass : float + +# vertical distance between y consecutive points used for intervals +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 +var y_chors : Array + +# actual coordinates of points (in pixel) +var x_coordinates : Array +var y_coordinates : Array + +# datas contained in file +var datas : Array + +# amount of functions to represent +var functions : int = 0 + + +# database values +var x_datas : Array +var y_datas : Array + +# labels displayed on chart +var x_label : String +var y_labels : Array + +# actual values of point, from the database +var point_values : Array + +# actual position of points in pixel +var point_positions : Array + +var legend : Array setget set_legend,get_legend +var are_values_columns : bool + +# --------------------- +export (bool) var invert_xy : bool +var SIZE : Vector2 = Vector2() +export (PoolColorArray) var function_colors = [Color("#1e1e1e")] +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 + +signal linechart_plotted() + +#func _ready(): +# plot_line_chart("res://ChartNode/datas.csv",";",true,0) + +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 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: + font_size = font.get_height() + var theme : Theme = Theme.new() + theme.set_default_font(font) + PointData.set_theme(theme) + else: + var lbl = Label.new() + font = lbl.get_font("") + lbl.free() + if bold_font != null: + PointData.Data.set("custom_fonts/font",bold_font) + +func read_datas(source : String, delimiter : String): + 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) + content.append(line) + file.close() + for data in content: + if data.size() < 2: + content.erase(data) + return content + +func structure_datas(database : Array, are_values_columns : bool, x_values : int): + # @x_values 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: + x_datas.append(database[row][column]) + else: + if row != 0: + t_vals.append(float(database[row][column])) + 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: + 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 + + var to_order : Array + for cluster in y_datas.size(): + # define x_chors and y_chors + var margin = y_datas[cluster][y_datas[cluster].size()-1] + to_order.append(margin) + + to_order.sort() + var margin = to_order.pop_back() + v_dist = y_decim * pow(10.0,str(margin).length()-2) + var multi = 0 + var p = v_dist*multi + y_chors.append(p as String) + while p < margin: + multi+=1 + p = v_dist*multi + y_chors.append(p as String) + +func build_chart(): + SIZE = get_parent().get_size() + origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) + +func calculate_pass(): + if invert_xy: + x_chors = y_labels as PoolStringArray + 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) + 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_xy: + for column in y_datas[0].size(): + var single_coordinates : Array + for row in y_datas: + single_coordinates.append((row[column]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + else: + for cluster in y_datas: + var single_coordinates : Array + for value in cluster.size(): + single_coordinates.append((cluster[value]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + + for x in x_chors.size(): + x_coordinates.append(x_pass*x) + + for f in functions: + point_values.append([]) + point_positions.append([]) + + if invert_xy: + for function in y_coordinates.size()-1: + 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_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])) + +func redraw(): + build_chart() + calculate_pass() + calculate_coordinates() + update() + +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) + 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) + +func create_legend(): + legend.clear() + for function in functions: + var function_legend = FunctionLegend.instance() + var f_name : String + if invert_xy: + f_name = x_datas[function] + 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) + +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(): + 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) + 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() diff --git a/addons/easy_charts/BarChart/BarChart.tscn b/addons/easy_charts/BarChart/BarChart.tscn new file mode 100644 index 0000000..7e3abed --- /dev/null +++ b/addons/easy_charts/BarChart/BarChart.tscn @@ -0,0 +1,54 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.tscn" type="PackedScene" id=1] +[ext_resource path="res://addons/easy_charts/BarChart/BarChart.gd" type="Script" id=2] + +[sub_resource type="Theme" id=1] + +[node name="BarChart" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 70, 50 ) +mouse_filter = 2 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} +function_colors = PoolColorArray( 0.117647, 0.117647, 0.117647, 1 ) +v_lines_color = Color( 0.792157, 0.792157, 0.792157, 1 ) +h_lines_color = Color( 0.792157, 0.792157, 0.792157, 1 ) +outline_color = Color( 0.117647, 0.117647, 0.117647, 1 ) +font_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="Background" type="ColorRect" parent="."] +visible = false +show_behind_parent = true +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color( 0.882353, 0.882353, 0.882353, 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Points" type="Control" parent="."] +margin_right = 40.0 +margin_bottom = 40.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Legend" type="HBoxContainer" parent="."] +visible = false +margin_right = 1024.0 +margin_bottom = 64.0 +alignment = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PointData" parent="." instance=ExtResource( 1 )] + +[node name="PointData" parent="PointData" index="0"] +theme = SubResource( 1 ) + +[editable path="PointData"] diff --git a/addons/easy_charts/BarChart2D/BarChart2D.gd b/addons/easy_charts/BarChart2D/BarChart2D.gd new file mode 100644 index 0000000..972098d --- /dev/null +++ b/addons/easy_charts/BarChart2D/BarChart2D.gd @@ -0,0 +1,486 @@ +tool +extends Node2D + + +""" +[BarChart2D] - 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 FunctionsTween : Tween = $FunctionsTween +onready var OutlinesTween : Tween = $OutlinesTween +onready var GridTween : Tween = $GridTween +onready var Functions : Node2D = $Functions +onready var PointData = $PointData/PointData +onready var Outlines : Line2D = $Outlines +onready var Grid : Node2D = $Grid + +var point_node : PackedScene = preload("../Utilities/Point/Point.tscn") +var FunctionLegend : PackedScene = preload("../Utilities/Legend/FunctionLegend.tscn") + +var font_size : float = 16 +var const_height : float = font_size/2*font_size/20 +var const_width : float = font_size/2 + +var OFFSET : Vector2 = Vector2(0,0) + +#-------------------------------------------------------------------------# +var origin : Vector2 + +# actual distance between x and y values +var x_pass : float +var y_pass : float + +# vertical distance between y consecutive points used for intervals +var v_dist : float + +# quantization, representing the interval in which values will be displayed +var x_decim : float = 1.0 + +# define values on x an y axis +var x_chors : Array +var y_chors : Array + +# actual coordinates of points (in pixel) +var x_coordinates : Array +var y_coordinates : Array + +# datas contained in file +var datas : Array + +# amount of functions to represent +var functions : int = 0 + + +# database values +var x_datas : Array +var y_datas : Array + +# labels displayed on chart +var x_label : String +var y_labels : Array + +# actual values of point, from the database +var point_values : Array + +# actual position of points in pixel +var point_positions : Array + +var legend : Array setget set_legend,get_legend + +# --------------------- +#export (bool) +export (Vector2) 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 (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") +export (String,"Default","Clean","Gradient","Minimal","Invert") var template : String = "Default" setget apply_template + +var templates : Dictionary = {} + +signal chart_plotted(chart) +signal point_pressed(point) + +func _point_drawn(): + pass + +func _ready(): + pass + +func _script_changed(): + _ready() + +func _process(delta): + if Engine.editor_hint: + Outlines.set_default_color(outline_color) + Grid.get_node("VLine").set_default_color(v_lines_color) + Grid.get_node("HLine").set_default_color(h_lines_color) + if function_colors.size(): + Functions.get_node("Function").set_default_color(function_colors[0]) + else: + Functions.get_node("Function").set_default_color(Color("#1e1e1e")) + + if SIZE!=Vector2(): + build_chart() + Outlines.set_point_position(0,Vector2(origin.x,0)) + Outlines.set_point_position(1,Vector2(SIZE.x,0)) + Outlines.set_point_position(2,Vector2(SIZE.x,origin.y)) + Outlines.set_point_position(3,origin) + Outlines.set_point_position(4,Vector2(origin.x,0)) + + Grid.get_node("VLine").set_point_position(0,Vector2((OFFSET.x+SIZE.x)/2,0)) + Grid.get_node("VLine").set_point_position(1,Vector2((OFFSET.x+SIZE.x)/2,origin.y)) + Grid.get_node("HLine").set_point_position(0,Vector2(origin.x,origin.y/2)) + Grid.get_node("HLine").set_point_position(1,Vector2(SIZE.x,origin.y/2)) + + if function_colors.size(): + Functions.get_node("Function").set_point_position(0,Vector2((OFFSET.x+SIZE.x)/2,origin.y)) + Functions.get_node("Function").set_point_position(1,Vector2((OFFSET.x+SIZE.x)/2,0)) + +#func _ready(): +# plot_line_chart("res://ChartNode/datas2.csv",";",false,0,invert_xy,function_colors,drawing_duration,SIZE) + +func clear(): + Outlines.points = [] + Grid.get_node("HLine").queue_free() + Grid.get_node("VLine").queue_free() + Functions.get_node("Function").queue_free() + +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) + 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() + + clear() + + 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() + draw_chart() + + create_legend() + emit_signal("chart_plotted", self) + +func plot(): + randomize() + + clear() + + 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() + draw_chart() + + create_legend() + emit_signal("chart_plotted", self) + +func calculate_colors(): + if function_colors.empty() or function_colors.size() < functions: + for function in functions: + function_colors.append(Color("#1e1e1e")) + +func draw_chart(): + draw_outlines() + draw_v_grid() + draw_h_grid() + draw_functions() + +func draw_outlines(): + if boxed: + Outlines.set_default_color(outline_color) + OutlinesTween.interpolate_method(Outlines,"add_point", + Vector2(origin.x,0),Vector2(SIZE.x,0),drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + OutlinesTween.interpolate_method(Outlines,"add_point", + Vector2(SIZE.x,0),Vector2(SIZE.x,origin.y),drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + OutlinesTween.interpolate_method(Outlines,"add_point", + Vector2(SIZE.x,origin.y),origin,drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + OutlinesTween.interpolate_method(Outlines,"add_point", + origin,Vector2(origin.x,0),drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + +func draw_v_grid(): + for p in x_chors.size(): + var point : Vector2 = origin+Vector2((p)*x_pass + OFFSET.x/2,0) + var v_grid : Line2D = Line2D.new() + Grid.add_child(v_grid) + v_grid.set_width(1) + v_grid.set_default_color(v_lines_color) + add_label(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/2), x_chors[p]) + GridTween.interpolate_method(v_grid,"add_point",Vector2(point.x,origin.y),Vector2(point.x,origin.y-5),drawing_duration/(x_chors.size()),Tween.TRANS_EXPO,Tween.EASE_OUT) + GridTween.start() + yield(GridTween,"tween_all_completed") + +func draw_h_grid(): + for p in y_chors.size(): + var point : Vector2 = origin-Vector2(0,(p)*y_pass) + var h_grid : Line2D = Line2D.new() + Grid.add_child(h_grid) + h_grid.set_width(1) + h_grid.set_default_color(h_lines_color) + add_label(point-Vector2(y_chors[p].length()*const_width+font_size,font_size/2), y_chors[p]) + GridTween.interpolate_method(h_grid,"add_point",point,Vector2(SIZE.x,point.y),drawing_duration/(y_chors.size()),Tween.TRANS_EXPO,Tween.EASE_OUT) + GridTween.start() + yield(GridTween,"tween_all_completed") + + +func add_label(point : Vector2, text : String): + var lbl : Label = Label.new() + if font != null: + lbl.set("custom_fonts/font",font) + lbl.set("custom_colors/font_color",font_color) + Grid.add_child(lbl) + lbl.rect_position = point + lbl.set_text(text) + +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 line : Line2D = Line2D.new() + var pointv : Control + pointv = point_node.instance() + line.add_child(pointv) + 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.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) + FunctionsTween.start() + yield(FunctionsTween,"tween_all_completed") + +func construct_column(line : Line2D, f_index : int, function : Array): + line.set_width(column_width) + line.set_default_color(function_colors[f_index]) + line.antialiased = true + Functions.add_child(line) + +func read_datas(source : String, delimiter : String): + 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) + content.append(line) + file.close() + for data in content: + if data.size() < 2: + content.erase(data) + return content + +func structure_datas(database : Array, are_values_columns : bool, x_values : int): + # @x_values 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: + x_datas.append(database[row][column]) + else: + if row != 0: + t_vals.append(float(database[row][column])) + 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: + 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 + + var to_order : Array + for cluster in y_datas.size(): + # define x_chors and y_chors + var margin = y_datas[cluster][y_datas[cluster].size()-1] + to_order.append(margin) + + to_order.sort() + var margin = to_order.pop_back() + v_dist = y_decim * pow(10.0,str(margin).length()-2) + var multi = 0 + var p = v_dist*multi + y_chors.append(p as String) + while p < margin: + multi+=1 + p = v_dist*multi + y_chors.append(p as String) + +func build_chart(): + origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) + +func calculate_pass(): + if invert_xy: + x_chors = y_labels as PoolStringArray + else: + x_chors = x_datas as PoolStringArray + # calculate distance in pixel between 2 consecutive values/datas + 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(): + x_coordinates.clear() + y_coordinates.clear() + point_values.clear() + point_positions.clear() + + if invert_xy: + for column in y_datas[0].size(): + var single_coordinates : Array + for row in y_datas: + single_coordinates.append((row[column]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + else: + for cluster in y_datas: + var single_coordinates : Array + for value in cluster.size(): + single_coordinates.append((cluster[value]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + + for x in x_chors.size(): + x_coordinates.append(x_pass*x) + + for f in functions: + point_values.append([]) + point_positions.append([]) + + if invert_xy: + for function in y_coordinates.size(): + for function_value in y_coordinates[function].size(): + 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(OFFSET.x/2 + column_width/2 + (column_width + column_gap)*cluster + x_coordinates[y]+origin.x, origin.y-y_coordinates[cluster][y])) + +func redraw(): + pass + +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 show_data(point): + PointData.update_datas(point) + PointData.show() + +func hide_data(): + PointData.hide() + +func clear_points(): + function_colors.clear() + if Functions.get_children(): + for function in Functions.get_children(): + function.queue_free() + +func create_legend(): + legend.clear() + for function in functions: + var function_legend = FunctionLegend.instance() + var f_name : String + if invert_xy: + f_name = x_datas[function] + 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) + +func set_legend(l : Array): + legend = l + +func get_legend() -> Array: + return legend + +func apply_template(template_name : String): + if Engine.editor_hint: + if template_name!=null and template_name!="": + template = template_name + 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/BarChart2D/BarChart2D.tscn b/addons/easy_charts/BarChart2D/BarChart2D.tscn new file mode 100644 index 0000000..cc95814 --- /dev/null +++ b/addons/easy_charts/BarChart2D/BarChart2D.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/easy_charts/BarChart2D/BarChart2D.gd" type="Script" id=1] +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.tscn" type="PackedScene" id=2] + +[node name="BarChart2D" type="Node2D"] +script = ExtResource( 1 ) +__meta__ = { +"_edit_group_": true +} + +[node name="Grid" type="Node2D" parent="."] + +[node name="VLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="HLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="Outlines" type="Line2D" parent="."] +points = PoolVector2Array( 0, 0, 2, 0, 2, 2, 0, 2, 0, 0 ) +width = 2.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="Functions" type="Node2D" parent="."] + +[node name="Function" type="Line2D" parent="Functions"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 5.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="FunctionsTween" type="Tween" parent="."] + +[node name="OutlinesTween" type="Tween" parent="."] + +[node name="GridTween" type="Tween" parent="."] + +[node name="PointData" parent="." instance=ExtResource( 2 )] + +[node name="PointData" parent="PointData" index="0"] +visible = false +[connection signal="script_changed" from="." to="." method="_script_changed"] + +[editable path="PointData"] diff --git a/addons/easy_charts/BarChart2D/LineChart2D.tscn.depren b/addons/easy_charts/BarChart2D/LineChart2D.tscn.depren new file mode 100644 index 0000000..aa35599 --- /dev/null +++ b/addons/easy_charts/BarChart2D/LineChart2D.tscn.depren @@ -0,0 +1,108 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/easy_charts/LineChart2D/LineChart2D.gd" type="Script" id=1] +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.gd" type="Script" id=3] + + + + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 8.0 +content_margin_right = 8.0 +content_margin_top = 5.0 +content_margin_bottom = 5.0 +bg_color = Color( 1, 1, 1, 0 ) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color( 1, 1, 1, 1 ) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 +corner_detail = 20 + +[node name="LineChart2D" type="Node2D"] +script = ExtResource( 1 ) +drawing_duration = 0.3 +font_color = Color( 0.137255, 0.137255, 0.137255, 1 ) + +[node name="Grid" type="Node2D" parent="."] + +[node name="VLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="HLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="Outlines" type="Line2D" parent="."] +points = PoolVector2Array( 0, 0, 2, 0, 2, 2, 0, 2, 0, 0 ) +width = 2.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="Functions" type="Node2D" parent="."] + +[node name="Function" type="Line2D" parent="Functions"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 2.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="FunctionsTween" type="Tween" parent="."] + +[node name="OutlinesTween" type="Tween" parent="."] + +[node name="GridTween" type="Tween" parent="."] + +[node name="UI" type="CanvasLayer" parent="."] + +[node name="PointData" type="PanelContainer" parent="UI"] +margin_right = 67.0 +margin_bottom = 38.0 +mouse_filter = 2 +custom_styles/panel = SubResource( 1 ) +script = ExtResource( 3 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PointData" type="VBoxContainer" parent="UI/PointData"] +margin_left = 8.0 +margin_top = 5.0 +margin_right = 59.0 +margin_bottom = 36.0 +custom_constants/separation = 3 +alignment = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Function" type="Label" parent="UI/PointData/PointData"] +margin_right = 51.0 +margin_bottom = 14.0 +align = 1 +valign = 1 + +[node name="Value" type="HBoxContainer" parent="UI/PointData/PointData"] +margin_top = 17.0 +margin_right = 51.0 +margin_bottom = 31.0 + +[node name="x" type="Label" parent="UI/PointData/PointData/Value"] +margin_right = 39.0 +margin_bottom = 14.0 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +text = "Value:" +valign = 1 + +[node name="y" type="Label" parent="UI/PointData/PointData/Value"] +margin_left = 43.0 +margin_right = 51.0 +margin_bottom = 14.0 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +text = "0" +valign = 1 diff --git a/addons/easy_charts/LineChart/LineChart.gd b/addons/easy_charts/LineChart/LineChart.gd new file mode 100644 index 0000000..07e7ae6 --- /dev/null +++ b/addons/easy_charts/LineChart/LineChart.gd @@ -0,0 +1,397 @@ +tool +extends Control + +onready var PointData = $PointData/PointData +onready var Points = $Points +onready var Legend = $Legend + +var point_node : PackedScene = preload("../Utilities/Point/Point.tscn") +var FunctionLegend : PackedScene = preload("../Utilities/Legend/FunctionLegend.tscn") + +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 origin : Vector2 + +# actual distance between x and y values +var x_pass : float +var y_pass : float + +# vertical distance between y consecutive points used for intervals +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 +var y_chors : Array + +# actual coordinates of points (in pixel) +var x_coordinates : Array +var y_coordinates : Array + +# datas contained in file +var datas : Array + +# amount of functions to represent +var functions : int = 0 + + +# database values +var x_datas : Array +var y_datas : Array + +# labels displayed on chart +var x_label : String +var y_labels : Array + +# actual values of point, from the database +var point_values : Array + +# actual position of points in pixel +var point_positions : Array + +var legend : Array setget set_legend,get_legend +var are_values_columns : bool + +# --------------------- +export (bool) var invert_xy : bool +var SIZE : Vector2 = Vector2() +export (PoolColorArray) var function_colors = [Color("#1e1e1e")] +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 + +signal linechart_plotted() + +#func _ready(): +# plot_line_chart("res://ChartNode/datas.csv",";",true,0) + +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 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: + font_size = font.get_height() + var theme : Theme = Theme.new() + theme.set_default_font(font) + PointData.set_theme(theme) + else: + var lbl = Label.new() + font = lbl.get_font("") + lbl.free() + if bold_font != null: + PointData.Data.set("custom_fonts/font",bold_font) + +func read_datas(source : String, delimiter : String): + 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) + content.append(line) + file.close() + for data in content: + if data.size() < 2: + content.erase(data) + return content + +func structure_datas(database : Array, are_values_columns : bool, x_values : int): + # @x_values 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: + x_datas.append(database[row][column]) + else: + if row != 0: + t_vals.append(float(database[row][column])) + 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: + 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 + + var to_order : Array + for cluster in y_datas.size(): + # define x_chors and y_chors + var margin = y_datas[cluster][y_datas[cluster].size()-1] + to_order.append(margin) + + to_order.sort() + var margin = to_order.pop_back() + v_dist = y_decim * pow(10.0,str(margin).length()-2) + var multi = 0 + var p = v_dist*multi + y_chors.append(p as String) + while p < margin: + multi+=1 + p = v_dist*multi + y_chors.append(p as String) + +func build_chart(): + SIZE = get_parent().get_size() + origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) + +func calculate_pass(): + if invert_xy: + x_chors = y_labels as PoolStringArray + 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) + 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_xy: + for column in y_datas[0].size(): + var single_coordinates : Array + for row in y_datas: + single_coordinates.append((row[column]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + else: + for cluster in y_datas: + var single_coordinates : Array + for value in cluster.size(): + single_coordinates.append((cluster[value]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + + for x in x_chors.size(): + x_coordinates.append(x_pass*x) + + for f in functions: + point_values.append([]) + point_positions.append([]) + + if invert_xy: + for function in y_coordinates.size()-1: + 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_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])) + +func redraw(): + build_chart() + calculate_pass() + calculate_coordinates() + update() + +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) + 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) + +func create_legend(): + legend.clear() + for function in functions: + var function_legend = FunctionLegend.instance() + var f_name : String + if invert_xy: + f_name = x_datas[function] + 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) + +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(): + 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) + 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() diff --git a/addons/easy_charts/LineChart/LineChart.tscn b/addons/easy_charts/LineChart/LineChart.tscn new file mode 100644 index 0000000..1af5faf --- /dev/null +++ b/addons/easy_charts/LineChart/LineChart.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.tscn" type="PackedScene" id=1] +[ext_resource path="res://addons/easy_charts/LineChart/LineChart.gd" type="Script" id=4] + +[sub_resource type="Theme" id=1] + +[node name="LineChart" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 70, 50 ) +mouse_filter = 2 +script = ExtResource( 4 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Background" type="ColorRect" parent="."] +visible = false +show_behind_parent = true +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color( 0.882353, 0.882353, 0.882353, 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Points" type="Control" parent="."] +margin_right = 40.0 +margin_bottom = 40.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Legend" type="HBoxContainer" parent="."] +visible = false +margin_right = 1024.0 +margin_bottom = 64.0 +alignment = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PointData" parent="." instance=ExtResource( 1 )] + +[node name="PointData" parent="PointData" index="0"] +theme = SubResource( 1 ) + +[editable path="PointData"] diff --git a/addons/easy_charts/LineChart2D/LineChart2D.gd b/addons/easy_charts/LineChart2D/LineChart2D.gd new file mode 100644 index 0000000..670ea15 --- /dev/null +++ b/addons/easy_charts/LineChart2D/LineChart2D.gd @@ -0,0 +1,494 @@ +tool +extends Node2D + +""" +[Linechart2D] - General purpose node for Line Charts + +A line chart or line plot or line graph or curve chart is a type of chart which +displays information as a series of data points called 'markers' +connected by straight line segments. +It is a basic type of chart common in many fields. It is similar to a scatter plot +except that the measurement points are ordered (typically by their x-axis value) +and joined with straight line segments. +A line chart is often used to visualize a trend in data over intervals of time – +a time series – thus the line is often drawn chronologically. +In these cases they are known as run charts. + +/ source : Wikipedia / +""" + +onready var FunctionsTween : Tween = $FunctionsTween +onready var OutlinesTween : Tween = $OutlinesTween +onready var GridTween : Tween = $GridTween +onready var Functions : Node2D = $Functions +onready var PointData = $PointData/PointData +onready var Outlines : Line2D = $Outlines +onready var Grid : Node2D = $Grid + +var point_node : PackedScene = preload("../Utilities/Point/Point.tscn") +var FunctionLegend : PackedScene = preload("../Utilities/Legend/FunctionLegend.tscn") + +var font_size : float = 16 +var const_height : float = font_size/2*font_size/20 +var const_width : float = font_size/2 + +var OFFSET : Vector2 = Vector2(0,0) + +#-------------------------------------------------------------------------# +var origin : Vector2 + +# actual distance between x and y values +var x_pass : float +var y_pass : float + +# vertical distance between y consecutive points used for intervals +var v_dist : float + +# quantization, representing the interval in which values will be displayed +var x_decim : float = 1.0 + +# define values on x an y axis +var x_chors : Array +var y_chors : Array + +# actual coordinates of points (in pixel) +var x_coordinates : Array +var y_coordinates : Array + +# datas contained in file +var datas : Array + +# amount of functions to represent +var functions : int = 0 + + +# database values +var x_datas : Array +var y_datas : Array + +# labels displayed on chart +var x_label : String +var y_labels : Array + +# actual values of point, from the database +var point_values : Array + +# actual position of points in pixel +var point_positions : Array + +var legend : Array setget set_legend,get_legend + +# --------------------- +export (Vector2) 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 (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") +export (String,"Default","Clean","Gradient","Minimal","Invert") var template : String = "Default" setget apply_template + +var templates : Dictionary = {} + +signal chart_plotted(chart) +signal point_pressed(point) + +func _point_plotted(): + pass + +func _ready(): + pass + +func _script_changed(): + _ready() + +func _process(delta): + if Engine.editor_hint: + Outlines.set_default_color(outline_color) + Grid.get_node("VLine").set_default_color(v_lines_color) + Grid.get_node("HLine").set_default_color(h_lines_color) + if function_colors.size(): + Functions.get_node("Function").set_default_color(function_colors[0]) + else: + Functions.get_node("Function").set_default_color(Color("#1e1e1e")) + + if SIZE!=Vector2(): + build_chart() + Outlines.set_point_position(0,Vector2(origin.x,0)) + Outlines.set_point_position(1,Vector2(SIZE.x,0)) + Outlines.set_point_position(2,Vector2(SIZE.x,origin.y)) + Outlines.set_point_position(3,origin) + Outlines.set_point_position(4,Vector2(origin.x,0)) + + Grid.get_node("VLine").set_point_position(0,Vector2((OFFSET.x+SIZE.x)/2,0)) + Grid.get_node("VLine").set_point_position(1,Vector2((OFFSET.x+SIZE.x)/2,origin.y)) + Grid.get_node("HLine").set_point_position(0,Vector2(origin.x,origin.y/2)) + Grid.get_node("HLine").set_point_position(1,Vector2(SIZE.x,origin.y/2)) + + if function_colors.size(): + Functions.get_node("Function").set_point_position(0,origin) + Functions.get_node("Function").set_point_position(1,Vector2(SIZE.x,0)) + +#func _ready(): +# plot_line_chart("res://ChartNode/datas2.csv",";",false,0,invert_xy,function_colors,drawing_duration,SIZE) + +func clear(): + Outlines.points = [] + Grid.get_node("HLine").queue_free() + Grid.get_node("VLine").queue_free() + Functions.get_node("Function").queue_free() + +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) + 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() + + clear() + + 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() + draw_chart() + + create_legend() + emit_signal("chart_plotted", self) + +func plot(): + randomize() + + clear() + + 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() + draw_chart() + + create_legend() + emit_signal("chart_plotted", self) + +func calculate_colors(): + if function_colors.empty() or function_colors.size() < functions: + for function in functions: + function_colors.append(Color("#1e1e1e")) + +func draw_chart(): + draw_outlines() + draw_v_grid() + draw_h_grid() + draw_functions() + +func draw_outlines(): + if boxed: + Outlines.set_default_color(outline_color) + OutlinesTween.interpolate_method(Outlines,"add_point", + Vector2(origin.x,0),Vector2(SIZE.x,0),drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + OutlinesTween.interpolate_method(Outlines,"add_point", + Vector2(SIZE.x,0),Vector2(SIZE.x,origin.y),drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + OutlinesTween.interpolate_method(Outlines,"add_point", + Vector2(SIZE.x,origin.y),origin,drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + OutlinesTween.interpolate_method(Outlines,"add_point", + origin,Vector2(origin.x,0),drawing_duration*0.5,Tween.TRANS_QUINT,Tween.EASE_OUT) + OutlinesTween.start() + yield(OutlinesTween,"tween_all_completed") + +func draw_v_grid(): + for p in x_chors.size(): + var point : Vector2 = origin+Vector2((p)*x_pass,0) + var v_grid : Line2D = Line2D.new() + Grid.add_child(v_grid) + v_grid.set_width(1) + v_grid.set_default_color(v_lines_color) + add_label(point+Vector2(-const_width/2*x_chors[p].length(),font_size/2), x_chors[p]) + GridTween.interpolate_method(v_grid,"add_point",point,point-Vector2(0,SIZE.y-OFFSET.y),drawing_duration/(x_chors.size()),Tween.TRANS_EXPO,Tween.EASE_OUT) + GridTween.start() + yield(GridTween,"tween_all_completed") + +func draw_h_grid(): + for p in y_chors.size(): + var point : Vector2 = origin-Vector2(0,(p)*y_pass) + var h_grid : Line2D = Line2D.new() + Grid.add_child(h_grid) + h_grid.set_width(1) + h_grid.set_default_color(h_lines_color) + add_label(point-Vector2(y_chors[p].length()*const_width+font_size,font_size/2), y_chors[p]) + GridTween.interpolate_method(h_grid,"add_point",point,Vector2(SIZE.x,point.y),drawing_duration/(y_chors.size()),Tween.TRANS_EXPO,Tween.EASE_OUT) + GridTween.start() + yield(GridTween,"tween_all_completed") + + +func add_label(point : Vector2, text : String): + var lbl : Label = Label.new() + if font != null: + lbl.set("custom_fonts/font",font) + lbl.set("custom_colors/font_color",font_color) + Grid.add_child(lbl) + lbl.rect_position = point + lbl.set_text(text) + +func draw_functions(): + for function in point_positions.size(): + draw_function(function,point_positions[function]) + +func draw_function(f_index : int, function : Array): + var line : Line2D = Line2D.new() + var backline : Line2D = Line2D.new() + construct_line(line,backline,f_index,function) + var pointv : Control + for point in function.size(): + pointv = point_node.instance() + line.add_child(pointv) + 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],pointv.format_value(point_values[f_index][point],false,true),(x_datas[f_index] if invert_xy else y_labels[f_index])) + if point < function.size()-1: + FunctionsTween.interpolate_method(line,"add_point",function[point],function[point+1],drawing_duration/function.size(),Tween.TRANS_QUINT,Tween.EASE_OUT) +# FunctionsTween.interpolate_method(backline,"add_point",function[point],function[point+1],drawing_duration/function.size(),Tween.TRANS_QUINT,Tween.EASE_OUT) + FunctionsTween.start() + yield(FunctionsTween,"tween_all_completed") + +func construct_line(line : Line2D, backline : Line2D, f_index : int, function : Array): + var midtone = Color(Color(function_colors[f_index]).r,Color(function_colors[f_index]).g,Color(function_colors[f_index]).b,Color(function_colors[f_index]).a/2) + backline.set_width(3) + backline.set_default_color(midtone) + backline.antialiased = true + Functions.add_child(backline) + line.set_width(4) + line.set_default_color(function_colors[f_index]) + line.antialiased = true + Functions.add_child(line) + +func read_datas(source : String, delimiter : String): + 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) + content.append(line) + file.close() + for data in content: + if data.size() < 2: + content.erase(data) + return content + +func structure_datas(database : Array, are_values_columns : bool, x_values : int): + # @x_values 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: + x_datas.append(database[row][column]) + else: + if row != 0: + t_vals.append(float(database[row][column])) + 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: + 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 + + var to_order : Array + for cluster in y_datas.size(): + # define x_chors and y_chors + var margin = y_datas[cluster][y_datas[cluster].size()-1] + to_order.append(margin) + + to_order.sort() + var margin = to_order.pop_back() + v_dist = y_decim * pow(10.0,str(margin).length()-2) + var multi = 0 + var p = v_dist*multi + y_chors.append(p as String) + while p < margin: + multi+=1 + p = v_dist*multi + y_chors.append(p as String) + +func build_chart(): + origin = Vector2(OFFSET.x,SIZE.y-OFFSET.y) + +func calculate_pass(): + if invert_xy: + x_chors = y_labels as PoolStringArray + 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) + 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_xy: + for column in y_datas[0].size(): + var single_coordinates : Array + for row in y_datas: + single_coordinates.append((row[column]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + else: + for cluster in y_datas: + var single_coordinates : Array + for value in cluster.size(): + single_coordinates.append((cluster[value]*y_pass)/v_dist) + y_coordinates.append(single_coordinates) + + for x in x_chors.size(): + x_coordinates.append(x_pass*x) + + for f in functions: + point_values.append([]) + point_positions.append([]) + + if invert_xy: + for function in y_coordinates.size()-1: + 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_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])) + +func redraw(): + pass + +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 show_data(point): + PointData.update_datas(point) + PointData.show() + +func hide_data(): + PointData.hide() + +func clear_points(): + function_colors.clear() + if Functions.get_children(): + for function in Functions.get_children(): + function.queue_free() + +func create_legend(): + legend.clear() + for function in functions: + var function_legend = FunctionLegend.instance() + var f_name : String + if invert_xy: + f_name = x_datas[function] + 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) + +func set_legend(l : Array): + legend = l + +func get_legend() -> Array: + return legend + +func apply_template(template_name : String): + if Engine.editor_hint: + if template_name!=null and template_name!="": + template = template_name + var custom_template = templates[template_name.to_lower()] + function_colors.resize(0) + for color in custom_template.function_colors: + function_colors.append(Color(color)) + 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/LineChart2D/LineChart2D.tscn b/addons/easy_charts/LineChart2D/LineChart2D.tscn new file mode 100644 index 0000000..3fdb23d --- /dev/null +++ b/addons/easy_charts/LineChart2D/LineChart2D.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/easy_charts/LineChart2D/LineChart2D.gd" type="Script" id=1] +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.tscn" type="PackedScene" id=2] + +[node name="LineChart2D" type="Node2D"] +script = ExtResource( 1 ) +__meta__ = { +"_edit_group_": true +} +font_color = Color( 0.137255, 0.137255, 0.137255, 1 ) + +[node name="Grid" type="Node2D" parent="."] + +[node name="VLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="HLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="Outlines" type="Line2D" parent="."] +points = PoolVector2Array( 0, 0, 2, 0, 2, 2, 0, 2, 0, 0 ) +width = 2.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="Functions" type="Node2D" parent="."] + +[node name="Function" type="Line2D" parent="Functions"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 5.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="FunctionsTween" type="Tween" parent="."] + +[node name="OutlinesTween" type="Tween" parent="."] + +[node name="GridTween" type="Tween" parent="."] + +[node name="PointData" parent="." instance=ExtResource( 2 )] + +[node name="PointData" parent="PointData" index="0"] +visible = false +[connection signal="script_changed" from="." to="." method="_script_changed"] + +[editable path="PointData"] diff --git a/addons/easy_charts/LineChart2D/LineChart2D.tscn.depren b/addons/easy_charts/LineChart2D/LineChart2D.tscn.depren new file mode 100644 index 0000000..aa35599 --- /dev/null +++ b/addons/easy_charts/LineChart2D/LineChart2D.tscn.depren @@ -0,0 +1,108 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/easy_charts/LineChart2D/LineChart2D.gd" type="Script" id=1] +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.gd" type="Script" id=3] + + + + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 8.0 +content_margin_right = 8.0 +content_margin_top = 5.0 +content_margin_bottom = 5.0 +bg_color = Color( 1, 1, 1, 0 ) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color( 1, 1, 1, 1 ) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 +corner_detail = 20 + +[node name="LineChart2D" type="Node2D"] +script = ExtResource( 1 ) +drawing_duration = 0.3 +font_color = Color( 0.137255, 0.137255, 0.137255, 1 ) + +[node name="Grid" type="Node2D" parent="."] + +[node name="VLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="HLine" type="Line2D" parent="Grid"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 1.0 +default_color = Color( 0.792157, 0.792157, 0.792157, 1 ) + +[node name="Outlines" type="Line2D" parent="."] +points = PoolVector2Array( 0, 0, 2, 0, 2, 2, 0, 2, 0, 0 ) +width = 2.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="Functions" type="Node2D" parent="."] + +[node name="Function" type="Line2D" parent="Functions"] +points = PoolVector2Array( 0, 0, 0, 0 ) +width = 2.0 +default_color = Color( 0.117647, 0.117647, 0.117647, 1 ) + +[node name="FunctionsTween" type="Tween" parent="."] + +[node name="OutlinesTween" type="Tween" parent="."] + +[node name="GridTween" type="Tween" parent="."] + +[node name="UI" type="CanvasLayer" parent="."] + +[node name="PointData" type="PanelContainer" parent="UI"] +margin_right = 67.0 +margin_bottom = 38.0 +mouse_filter = 2 +custom_styles/panel = SubResource( 1 ) +script = ExtResource( 3 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PointData" type="VBoxContainer" parent="UI/PointData"] +margin_left = 8.0 +margin_top = 5.0 +margin_right = 59.0 +margin_bottom = 36.0 +custom_constants/separation = 3 +alignment = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Function" type="Label" parent="UI/PointData/PointData"] +margin_right = 51.0 +margin_bottom = 14.0 +align = 1 +valign = 1 + +[node name="Value" type="HBoxContainer" parent="UI/PointData/PointData"] +margin_top = 17.0 +margin_right = 51.0 +margin_bottom = 31.0 + +[node name="x" type="Label" parent="UI/PointData/PointData/Value"] +margin_right = 39.0 +margin_bottom = 14.0 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +text = "Value:" +valign = 1 + +[node name="y" type="Label" parent="UI/PointData/PointData/Value"] +margin_left = 43.0 +margin_right = 51.0 +margin_bottom = 14.0 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +text = "0" +valign = 1 diff --git a/addons/easy_charts/ScatterChart3D/ScatterChart3D.gd b/addons/easy_charts/ScatterChart3D/ScatterChart3D.gd new file mode 100644 index 0000000..e09715a --- /dev/null +++ b/addons/easy_charts/ScatterChart3D/ScatterChart3D.gd @@ -0,0 +1,10 @@ +extends Spatial + +onready var Point = $Chart/Point + + +func _ready(): +# var p : MeshInstance = Point.duplicate() +# p.transform.origin = Vector3(1,0,1) +# add_child(p) + pass diff --git a/addons/easy_charts/ScatterChart3D/ScatterChart3D.tscn b/addons/easy_charts/ScatterChart3D/ScatterChart3D.tscn new file mode 100644 index 0000000..76f3f26 --- /dev/null +++ b/addons/easy_charts/ScatterChart3D/ScatterChart3D.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=7 format=2] + +[ext_resource path="res://addons/easy_charts/ScatterChart3D/ScatterChart3D.gd" type="Script" id=1] +[ext_resource path="res://d4hj068-433f5832-3c04-42db-9c2e-173a26a6970a.png" type="Texture" id=2] + +[sub_resource type="SpatialMaterial" id=1] +flags_unshaded = true +albedo_texture = ExtResource( 2 ) + +[sub_resource type="PlaneMesh" id=2] +material = SubResource( 1 ) +size = Vector2( 20, 20 ) + +[sub_resource type="SphereMesh" id=3] + +[sub_resource type="SpatialMaterial" id=4] +albedo_color = Color( 0, 1, 0.156863, 1 ) + +[node name="ScatterChart3D" type="Spatial"] +script = ExtResource( 1 ) + +[node name="MeshInstance" type="MeshInstance" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 ) +mesh = SubResource( 2 ) +material/0 = null + +[node name="Chart" type="Spatial" parent="."] + +[node name="Point" type="MeshInstance" parent="Chart"] +mesh = SubResource( 3 ) +material/0 = SubResource( 4 ) + +[node name="Camera" type="Camera" parent="."] +transform = Transform( 0.707107, -0.40558, 0.579228, 0, 0.819152, 0.573577, -0.707107, -0.40558, 0.579228, 10, 15, 10 ) +projection = 1 +current = true +size = 30.0 +near = 0.01 + +[node name="OmniLight" type="OmniLight" parent="."] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 9, 0 ) diff --git a/addons/easy_charts/Utilities/ChartContainer.gd b/addons/easy_charts/Utilities/ChartContainer.gd new file mode 100644 index 0000000..53f283e --- /dev/null +++ b/addons/easy_charts/Utilities/ChartContainer.gd @@ -0,0 +1,40 @@ +tool +extends Container + +var LineChart = preload("LineChart/LineChart.tscn") + +export (String,"None","LineChart","BoxChart") var chart_type : String setget set_type,get_type +var chart : Control setget set_chart,get_chart +var templates : Dictionary + +# Called when the node enters the scene tree for the first time. +func _ready(): + set_chart(get_child(0)) + var template_file : File = File.new() + template_file.open("res://addons/easy_charts/templates.json",File.READ) + templates = JSON.parse(template_file.get_as_text()).get_result() + template_file.close() + +func set_type(type : String): + chart_type = type + var new_node + if get_children().size(): + for child in get_children(): + child.queue_free() + if Engine.editor_hint: + match type: + "LineChart": + new_node = LineChart.instance() + add_child(new_node) + new_node.set_owner(owner) + "None": + set_chart(null) + +func get_type(): + return chart_type + +func set_chart(ch : Control): + chart = ch + +func get_chart(): + return chart diff --git a/addons/easy_charts/Utilities/ChartContainer2D.gd b/addons/easy_charts/Utilities/ChartContainer2D.gd new file mode 100644 index 0000000..5fec906 --- /dev/null +++ b/addons/easy_charts/Utilities/ChartContainer2D.gd @@ -0,0 +1,40 @@ +tool +extends Node2D + +var LineChart = preload("LineChart2D/LineChart2D.tscn") +var ColumnChart = preload("BarChart2D/BarChart2D.tscn") + +export (String,"None","LineChart2D","BarChart2D") var chart_type : String setget set_type,get_type +var chart : Node2D setget set_chart,get_chart + +# Called when the node enters the scene tree for the first time. +func _ready(): + set_chart(get_child(0)) + +func set_type(type : String): + chart_type = type + var new_node + if get_children().size(): + for child in get_children(): + child.queue_free() + if Engine.editor_hint: + match type: + "LineChart2D": + new_node = LineChart.instance() + add_child(new_node) + new_node.set_owner(owner) + "ColumnChart2D": + new_node = ColumnChart.instance() + add_child(new_node) + new_node.set_owner(owner) + "None": + set_chart(null) + +func get_type(): + return chart_type + +func set_chart(ch : Node2D): + chart = ch + +func get_chart(): + return chart diff --git a/addons/easy_charts/Utilities/Legend/ColorLabel.tscn b/addons/easy_charts/Utilities/Legend/ColorLabel.tscn new file mode 100644 index 0000000..12f3ccb --- /dev/null +++ b/addons/easy_charts/Utilities/Legend/ColorLabel.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/easy_charts/ChartContainer.gd" type="Script" id=1] + +[node name="Control" type="ColorRect"] +margin_right = 15.0 +margin_bottom = 3.0 +rect_min_size = Vector2( 15, 3 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/easy_charts/Utilities/Legend/FunctionLegend.gd b/addons/easy_charts/Utilities/Legend/FunctionLegend.gd new file mode 100644 index 0000000..9153681 --- /dev/null +++ b/addons/easy_charts/Utilities/Legend/FunctionLegend.gd @@ -0,0 +1,21 @@ +extends VBoxContainer + +onready var Function : Label = $Function +onready var FunctionColor : ColorRect = $Color + +var text : String +var color : Color +var font_color : Color +var font : Font + +func _ready(): + Function.set("custom_fonts/font",font) + Function.set("custom_colors/font_color",font_color) + Function.set_text(text) + FunctionColor.set_frame_color(color) + +func create_legend(text : String, color : Color, font : Font, font_color : Color): + self.text = text + self.color = color + self.font_color = font_color + self.font = font diff --git a/addons/easy_charts/Utilities/Legend/FunctionLegend.tscn b/addons/easy_charts/Utilities/Legend/FunctionLegend.tscn new file mode 100644 index 0000000..ad5cb57 --- /dev/null +++ b/addons/easy_charts/Utilities/Legend/FunctionLegend.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/easy_charts/Utilities/Legend/FunctionLegend.gd" type="Script" id=2] + +[node name="FunctionLegend" type="VBoxContainer"] +margin_right = 80.0 +margin_bottom = 26.0 +alignment = 1 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Function" type="Label" parent="."] +margin_top = 2.0 +margin_right = 80.0 +margin_bottom = 16.0 +custom_colors/font_color = Color( 0.184314, 0.192157, 0.262745, 1 ) +text = "Function" +align = 1 +valign = 1 + +[node name="Color" type="ColorRect" parent="."] +margin_top = 20.0 +margin_right = 80.0 +margin_bottom = 23.0 +rect_min_size = Vector2( 15, 3 ) +color = Color( 0.184314, 0.192157, 0.262745, 1 ) diff --git a/addons/easy_charts/Utilities/Point/Point.gd b/addons/easy_charts/Utilities/Point/Point.gd new file mode 100644 index 0000000..7d8280b --- /dev/null +++ b/addons/easy_charts/Utilities/Point/Point.gd @@ -0,0 +1,80 @@ +extends Control +class_name Point + +const OFFSET : Vector2 = Vector2(13,13) +var point_value : Array setget set_value,get_value +var point_position : Vector2 +var color : Color +var color_outline : Color +var function : String + +var mouse_entered : bool = false + + +signal _mouse_entered() +signal _mouse_exited() +signal _point_pressed(point) + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +func _draw(): + if mouse_entered: + draw_circle(OFFSET,7,color_outline) + draw_circle(OFFSET,5,color) + +func create_point(color : Color, color_outline : Color, position : Vector2, value : Array, function : String): + self.color = color + self.color_outline = color_outline + self.point_position = position + self.rect_position = point_position - OFFSET + self.point_value = value + self.function = function + +func _on_Point_mouse_entered(): + mouse_entered = true + emit_signal("_mouse_entered") + update() + +func _on_Point_mouse_exited(): + mouse_entered = false + emit_signal("_mouse_exited") + update() + +func format_value(v : Array, format_x : bool, format_y : bool): + var x : String = str(v[0]) + var y : String = str(v[1]) + + if format_x: + x = format(v[1]) + if format_y: + y = format(v[1]) + + return [x,y] + +func format(n): + n = str(n) + var size = n.length() + var s + for i in range(size): + if((size - i) % 3 == 0 and i > 0): + s = str(s,",", n[i]) + else: + s = str(s,n[i]) + + return s.replace("Null","") + + +func _on_Point_gui_input(event): + if event is InputEventMouseButton: + if event.is_pressed(): + if event.button_index == 1: + emit_signal("_point_pressed",self) + +func set_value( v : Array = [] ) : + point_value = v + +func get_value() -> Array: + return point_value diff --git a/addons/easy_charts/Utilities/Point/Point.tscn b/addons/easy_charts/Utilities/Point/Point.tscn new file mode 100644 index 0000000..5224f7a --- /dev/null +++ b/addons/easy_charts/Utilities/Point/Point.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/easy_charts/Utilities/Point/Point.gd" type="Script" id=1] + +[node name="Point" type="Control"] +margin_left = -13.0 +margin_top = -13.0 +margin_right = 13.0 +margin_bottom = 13.0 +rect_min_size = Vector2( 16, 16 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} +[connection signal="gui_input" from="." to="." method="_on_Point_gui_input"] +[connection signal="mouse_entered" from="." to="." method="_on_Point_mouse_entered"] +[connection signal="mouse_exited" from="." to="." method="_on_Point_mouse_exited"] diff --git a/addons/easy_charts/Utilities/Point/PointData.gd b/addons/easy_charts/Utilities/Point/PointData.gd new file mode 100644 index 0000000..fb94dac --- /dev/null +++ b/addons/easy_charts/Utilities/Point/PointData.gd @@ -0,0 +1,46 @@ +extends PanelContainer + +var value : String = "" +var position : Vector2 = Vector2() + +var OFFSET : Vector2 = Vector2(15,35) +var GAP : Vector2 = Vector2(0,15) + +onready var Data : Label = $PointData/Value/x +onready var Value : Label = $PointData/Value/y +onready var Function : Label = $PointData/Function + +func _ready(): + pass + +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 + +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() + +func update_size(): + 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 new file mode 100644 index 0000000..bfb0e52 --- /dev/null +++ b/addons/easy_charts/Utilities/Point/PointData.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/easy_charts/Utilities/Point/PointData.gd" type="Script" id=1] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 8.0 +content_margin_right = 8.0 +content_margin_top = 5.0 +content_margin_bottom = 5.0 +bg_color = Color( 1, 1, 1, 0 ) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color( 1, 1, 1, 1 ) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 +corner_detail = 20 + +[node name="PointData" type="CanvasLayer"] + +[node name="PointData" type="PanelContainer" parent="."] +margin_right = 67.0 +margin_bottom = 41.0 +mouse_filter = 2 +custom_styles/panel = SubResource( 1 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="PointData" type="VBoxContainer" parent="PointData"] +margin_left = 8.0 +margin_top = 5.0 +margin_right = 59.0 +margin_bottom = 36.0 +custom_constants/separation = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Function" type="Label" parent="PointData/PointData"] +margin_right = 51.0 +margin_bottom = 14.0 +align = 1 +valign = 1 + +[node name="Value" type="HBoxContainer" parent="PointData/PointData"] +margin_top = 17.0 +margin_right = 51.0 +margin_bottom = 31.0 + +[node name="x" type="Label" parent="PointData/PointData/Value"] +margin_right = 39.0 +margin_bottom = 14.0 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +text = "Value:" +valign = 1 + +[node name="y" type="Label" parent="PointData/PointData/Value"] +margin_left = 43.0 +margin_right = 51.0 +margin_bottom = 14.0 +custom_colors/font_color = Color( 1, 1, 1, 1 ) +text = "0" +valign = 1 diff --git a/addons/easy_charts/Utilities/Rect/Line-Graph1.png b/addons/easy_charts/Utilities/Rect/Line-Graph1.png new file mode 100644 index 0000000000000000000000000000000000000000..e6af59b38c05929e3961e815c6509f5931bae196 GIT binary patch literal 71512 zcmcG$c{E$=<2Rbn>gkYbzuIb}=s6vmTHYLhK2||J>{5Nb4{V< zF*Oq;T2qO!W2n4S6TWJEWz+O+Zeg~EITwVk&j=X-L_W%Sci$2MHarn{W z?thqjfk3C54}ZYzZuu`kpo1LkhYw8N*evgbQ>^U+<_`|`FaJ8L{vB-FbmsRfS2WeH z7&?eYnqJ{KA=8%BW~&u>e%9#Edq4UMj)_-(i=Z$R#vNBS=ijlbbM%X6`-er5E_njE zC3kO?IK&QnZ}o2DnN#d#`hIahEhE4NM_+$)fA}YUxaze3XkKAs!z z19rd zm?s8$@nNq(pICZuUZB_d@tewEg_8P=&S>Uao1GJF4_Qc_)u~36G}zxlZVfVN(XWIo zPVS^J&|+zB(u5t`_R1%zHl;=*(ftebBPJWi8eGZMP8=`n?!;%$ToZkNHc6U@HoBDa z3~M~;EpNaQRq-jlN};Al?Ve%YQR2l0iS9HVvw(4b@(ad?iWWZUG7clP-!%`=_eh@~ z(JR8>vbvbwb&#ZnQnBZ{>&?cx`R7P!N?y5YjlPVj;r=YGqIKT}s&I**&6~`vBPL-i zdyRlboOwkqbi2X2-sh=?ULwuTyw~_t=W)e=;0wGwkNcTNtm=JS+tD8 z3KX;#k|$jiRUW{jyeMJnI8^v_#Zza0329D)ZBKZe5748F9u0U@e=6DaCj6^1xPq-N z-SThCo)f!rqmUus-kYvgqL(6{gIb_GRT-(7VtsV}(SUK8#8**88_h?ykJkns><>o? zs}!y$!Z9eL`+mE#3<}{7bIr!%c?Q_hHU-4Qv;NJGSG5W=^_x6DnOA4yp6fS#W&dTo zxTW8Kp~Z z&x$S_nP@?kyAxY8Y(=ndcT%v3BiX&S#`!W z4)5a>ZX`aX3%LCUwKev-2yN{ZtwEdgXEYA=b&y^%O-yfNL(*}qR?p-MlM;q=EN5G> zX+cV2@4>V+n|tHOv6OD?1?9zZ_PeC-$45(YcDwwU>?8x;)*$>zb1F#*jKfP}MH@H9 z(D&zh)y1YWH9`v4X{kYh`!DaqIMntHb`s2P?}W%KHX_Yzjwu6g^W#C-V0(MlL_O|B ztQ?vB3wjz!Iz=V+I`(fL&nK6u65<5hyF1Ys1*Xpr1p6Ds=MA+T<7RY8?Oql^Ve)7e zruLY~n~dZ+L=twK5a2I&dGmqFue9Nl!EGCilWVIjB!0~ZNDz?{v5E( zzyEV$vF3v!xZRy+YangLet-3cbI8_P>BNIO!6=?r<>iyB_d~s%2@KnH*bRJ^1yv-0 z^w2}NP(lLd`f+C|K7T~nZ*lj?AoI$*DM&@?FufyOp$f)jsKqBN4w$S zFuco8Z~@KGWt=Xt6}5Xhm#Otg3>y>784nlBip^uO1l`)7jD?6|YLQ#ZWAcHI@aeS; zd1

n#F9oYWEA$LgSFlk~p(9wN^gO9P%PVUwTb&U)HZ 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%0A_(z4o4NR>?wK|U% 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 literal 0 HcmV?d00001 diff --git a/addons/easy_charts/Utilities/Rect/Line-Graph1.png.import b/addons/easy_charts/Utilities/Rect/Line-Graph1.png.import new file mode 100644 index 0000000..680e1ba --- /dev/null +++ b/addons/easy_charts/Utilities/Rect/Line-Graph1.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/Line-Graph1.png-cf75f72c04876dd893d0cb31b406bc73.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/easy_charts/Utilities/Rect/Line-Graph1.png" +dest_files=[ "res://.import/Line-Graph1.png-cf75f72c04876dd893d0cb31b406bc73.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/easy_charts/Utilities/Rect/Rect.gd b/addons/easy_charts/Utilities/Rect/Rect.gd new file mode 100644 index 0000000..e04c27c --- /dev/null +++ b/addons/easy_charts/Utilities/Rect/Rect.gd @@ -0,0 +1,66 @@ +extends Control + +var OFFSET : Vector2 = Vector2() +var point_value : Array +var point_position : Vector2 +var color : Color +var color_outline : Color +var function : String + +var mouse_entered : bool = false + + +signal _mouse_entered() +signal _mouse_exited() + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +func _draw(): + if mouse_entered: + draw_rect(Rect2(rect_position - OFFSET,rect_size),color_outline,true,12,true) + +func create_point(color : Color, color_outline : Color, position : Vector2, size : Vector2, value : Array, function : String): + + self.color = color + self.color_outline = color_outline + self.point_position = position + self.rect_position = point_position - OFFSET + self.rect_size = size + self.point_value = value + self.function = function + +func format_value(v : Array, format_x : bool, format_y : bool): + var x : String = str(v[0]) + var y : String = str(v[1]) + + if format_x: + x = format(v[1]) + if format_y: + y = format(v[1]) + + return [x,y] + +func format(n): + n = str(n) + var size = n.length() + var s + for i in range(size): + if((size - i) % 3 == 0 and i > 0): + s = str(s,",", n[i]) + else: + s = str(s,n[i]) + + return s.replace("Null","") + +func _on_Rect_mouse_entered(): + mouse_entered = true + emit_signal("_mouse_entered") + update() + +func _on_Rect_mouse_exited(): + mouse_entered = false + emit_signal("_mouse_exited") + update() diff --git a/addons/easy_charts/Utilities/Rect/Rect.tscn b/addons/easy_charts/Utilities/Rect/Rect.tscn new file mode 100644 index 0000000..466bbb9 --- /dev/null +++ b/addons/easy_charts/Utilities/Rect/Rect.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/easy_charts/Utilities/Rect/Rect.gd" type="Script" id=1] + +[node name="Rect" type="Control"] +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} +[connection signal="mouse_entered" from="." to="." method="_on_Rect_mouse_entered"] +[connection signal="mouse_exited" from="." to="." method="_on_Rect_mouse_exited"] diff --git a/addons/easy_charts/Utilities/icons/linechart.svg b/addons/easy_charts/Utilities/icons/linechart.svg new file mode 100644 index 0000000..7d168d9 --- /dev/null +++ b/addons/easy_charts/Utilities/icons/linechart.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/addons/easy_charts/Utilities/icons/linechart.svg.import b/addons/easy_charts/Utilities/icons/linechart.svg.import new file mode 100644 index 0000000..de6ccb1 --- /dev/null +++ b/addons/easy_charts/Utilities/icons/linechart.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/linechart.svg-1b22ec0a8537b638d716ea5c462d4141.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/easy_charts/Utilities/icons/linechart.svg" +dest_files=[ "res://.import/linechart.svg-1b22ec0a8537b638d716ea5c462d4141.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/addons/easy_charts/Utilities/icons/linechart2d.svg b/addons/easy_charts/Utilities/icons/linechart2d.svg new file mode 100644 index 0000000..46729be --- /dev/null +++ b/addons/easy_charts/Utilities/icons/linechart2d.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/addons/easy_charts/Utilities/icons/linechart2d.svg.import b/addons/easy_charts/Utilities/icons/linechart2d.svg.import new file mode 100644 index 0000000..d04431c --- /dev/null +++ b/addons/easy_charts/Utilities/icons/linechart2d.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/linechart2d.svg-af92a4d6767d218934fb7a6905401722.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/easy_charts/Utilities/icons/linechart2d.svg" +dest_files=[ "res://.import/linechart2d.svg-af92a4d6767d218934fb7a6905401722.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/easy_charts/Utilities/utilities.gd b/addons/easy_charts/Utilities/utilities.gd new file mode 100644 index 0000000..e558571 --- /dev/null +++ b/addons/easy_charts/Utilities/utilities.gd @@ -0,0 +1,21 @@ +tool +extends Node + +var plugin_name : String = "Easy Charts" + +func _ready(): + pass + +func _print_message(message : String, type : int = 0): + match type: + 0: + print("[%s] => %s" % [plugin_name, message]) + 1: + printerr("[%s] => %s" % [plugin_name, message]) + +func _load_templates() -> Dictionary: + var template_file : File = File.new() + template_file.open("res://addons/easy_charts/templates.json",File.READ) + var templates = JSON.parse(template_file.get_as_text()).get_result() + template_file.close() + return templates diff --git a/addons/easy_charts/file.samples/datas.csv b/addons/easy_charts/file.samples/datas.csv new file mode 100644 index 0000000..57b4e53 --- /dev/null +++ b/addons/easy_charts/file.samples/datas.csv @@ -0,0 +1,10 @@ +Year;Column 1;Column 2;Column 3;Column 4 +2009;36200;27200;26200;17200 +2010;36600;27800;26600;17800 +2011;37500;28500;27500;18500 +2012;38700;29400;28700;19400 +2013;39600;30200;29600;10200 +2014;40500;30900;20500;10900 +2015;41200;31500;21200;11500 +2016;41803;31931;21803;11931 +2017;42600;32600;22600;12600 diff --git a/addons/easy_charts/file.samples/datas.csv.import b/addons/easy_charts/file.samples/datas.csv.import new file mode 100644 index 0000000..aeeda06 --- /dev/null +++ b/addons/easy_charts/file.samples/datas.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/datas.csv" +[params] + diff --git a/addons/easy_charts/file.samples/datas2.csv b/addons/easy_charts/file.samples/datas2.csv new file mode 100644 index 0000000..b3bd3a9 --- /dev/null +++ b/addons/easy_charts/file.samples/datas2.csv @@ -0,0 +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 diff --git a/addons/easy_charts/file.samples/datas2.csv.import b/addons/easy_charts/file.samples/datas2.csv.import new file mode 100644 index 0000000..cc1d366 --- /dev/null +++ b/addons/easy_charts/file.samples/datas2.csv.import @@ -0,0 +1,10 @@ +[remap] + +importer="csv" +type="TextFile" + +[deps] + +source_file="res://addons/easy_charts/file.samples/datas2.csv" +[params] + diff --git a/addons/easy_charts/icon.png b/addons/easy_charts/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1c687201f814a62ff4d2d7165ce595bdc772a4c GIT binary patch literal 69813 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

Xe>N z?A=r2@zQN#q*tq%O2IJO`R;#|3i}HL;3RVi0G0Nv&g~nB+j{-S4>;f^>%V(k8y)Qu zx)39a!;M1O7^@j~1iACg>V#%is$0i{)!B9u_~ixlP%U^W=TRP_cG&T=K+W*!r=BuU z=LdvxX?)}`dWrZ&R5Up|P^wJ+OxSJC&o6K6+iyT7FNQ2CMej9WWkNkN$&>Rr9z8Ed zfhJk8qEgJ#LbYZwEMhV_*}`0G<@^k)<-{kK&)-<9EWxoZXGhCZtTr+AW$47AZg;^6 zZuSd0WvW7`R81WF4MgoSAPacY8eaxIuBDDrCJ>s7qBx4IfUDzDj@$VbtuXmMb-nrI zaLxp&W`=%DtDyV%x4k;H3b#zC;yBm)Et~IX`@<+_dG0t~I_ra8(E3qZzP)|QI@d&b z(X(7XZSC~BWQ_v2vbl8Xm{W%6!BWwly5r*M zFp+Hq1%&LmfwiaPcXaDFz>T2ym8bLv7klCc?o+4wg@kIh2fh;rnQkd+5z(>8%4pC& zo_At67^_%bqFHD( zA0GAd!Q{_v_KJN)@ux7Jm`KnNj1E_e;(46MblI^`JI{|aKu-rgx~@*`sS@3?sj1qq z7+C$7P?61?`;~9}*>A&x+51}>009I;d)z4Ukr2rKovscYRGK7OcaxpY;xR-q?>Ds~ zR2vldy+)?%C--=Wl#~dE2Q*hxI@bX0SY9To*gReBlLEw|U4_49{_fu5B=ZWgTeDuQ z`W*4vVr(!9#7r!s$x;?~!^s)@uzj&25@xJ?urgUHPE@_#U#7HD4#kF$fnH0G+?!WL zNi}O@l8KKoU}h><>~u3%?Wl2z_@GCG5CRoU;A3AFwgm`}ggny2? z4Ifa*uI+d1g^WwuhKvIt$BzIaM073|QJW4YN=}57S;g$A6t@Se_5!=sDn@)>`V+Tc z;1Ei(M-Sa=LJZSwSamJG9P0;5vh8A)@cI~IQ+^IUREjn@{{7R)(82>EP$P?W!)3K> zr*&bKJVfvaQ4L?J#GSBcJFH`ySlP?RQyv91Y6*#t=xco1B9Sm!RL5y2#>KN zG-b1RT)7xgUjcIr>T^D6z&!4P(W8nxsv*t3Kp9YL6OFZ1ivs)b6Ds%MtH%8*q|Ty5 zPHcu2p&F>HXgpZ>C~lOC<47zDm*%6OGXtA^1e*v_ZhdKV+L09I1zjpY;NvsBz-`Bu z6EX`7T?rCGD6obqS68D3uw*MJ3jA*1jq=EJW3_7asY#vLM+Jbd!0ID*i)`+5BHYUE*dJlK6>~#$&s7hpXscqb(cJ4f{E&JF%-53EE1=-g0slRdbQiO48) z=sE64%&gL@7{5_m1WVhivuF!@s_8JWL~ahj8)Mfg;f!z`_wk1Sd zxwr%S0B4z6jFX%gxEKXtHddftNYznQ5{_$h8)oaw(V0%-uxI^-LOnUY1=-zNJ_#A7$71})9T9><{O0+7n* zt|k|h)|0Pp5g^dTwVL%PZI=X_yADDCKB4D4kmqlHq(OIL=x(IcI&!MX6HM(1SMn;?& z>_4zTOqlZoJYC}9(8#;szd*Ecxt!oJI#kP!rI31Ip)IrNs5y4m%S#h)F&^+ClBsMr zlrip-8Zha!z#FaK17fa^>OJ}Y(8JNyefU0x$p8RxIEbutv3aKDV&adgw$k9@&ieyv z8PzDu;r#r>P=CZ-V21-(nT08qv*-4rH@_9ie-zi|Mtzwn?57VwOZp~4%f%}p+1bix z4PTYRYmMP_sjw3DbqI-jR8hT7+Jz+wM~dndMfX-8EXMc2S?CT23Dx9XJ-~fbuL$6> zke4Vp{Mt)0zN8M+aG^d?Q%hWT<= z&|#bNK?HuKW1!j2fe{58xr`^v`HWfq-g^9Rp5RhYUe5O748X}dixmK&fS?cNn>nPe z(7&yw>)-p90kC1a$Kid^SiYI(q|$XrJ2qf5Sj4O~+SA?snoyHakTI2O_#-CK zIQb2Sr0`i^Uyv$PD4`GMtCtvTo>WG)pzln_>+rvP<#h&ud<+JLD+6GFs=jla`{y-l z7Sc<-ZI958VdbxPcWS#TJdJ>7A~K3K9ljphFI1pf3+-3Q&!;95VNjBzE#Bwa!)We( z!u`~B*B~EzJb7;UPC8lEC~9&m14?M3eFy3y6AAQUqZ)*+_^V~Y2qxsBJ_|d5uAi20 z>*A%H!=SPV=TufB_?oe-4XFfYg~K&cH66*DkeONFhUf91xF)$yGNB+&hH*5}E9hf6d{xw8i7@~5SRi%I4t!5W=UgZuNvG}a*YHB*e;X!kX`uIuXeAhL%j zaa?&uDpR~W?kI69PVCDdqQPC!#>rM8o2!w7r$z?HT7iN+tkmaXaC+z)A;jCi2dFrP zeFGC{HZ7(}Ci9QPACxwaT!%b-3995hfHF>#_Hh@(N%1RsZ7v4hhj4P&hAwodtrb{- z0BXheSm+x3N3WYCex$uGs;+xNu;{8=S;t(nkWIOza}TsBx*$Z#W>B>H+W(hUgK?{R z9iiH8dUovG&sAt%0E@>#R-7%kVg;})5TW=0y9&cn+*aT#+Xvouxy}w9NOKz~9oKkF z=5RDQmxNF!MsaB`PotzYe3y+vC7#{IiK8tWLZv}%)?U7ziwUh*OQ^r`%&=)83}442 z`LhBlyIAO3oC1#f5J=HA|5FYIC<3_|U;`>HBS z(jEJT5>sik1DgQnA}~Gx7Y)cwj_Wu$pwR=v7Nr<)V@HlSaM*~f93Rv1dSvBwOLa~TNy_qX4yF^a8p#DDG+XIj^()e z7PR6htz39(RC_Rk6TzLByDtkq=j&=M9wp8Ei3!w*Wh~J^t*N}lG3bK|A`yoDSi3;6 z^=!`#)e3?_E6j`JHwQlWNxev33dkl97rub+P=v9*{@E3bCm=6B0fE;Sf=SamsDmp& z)Ahn>i;7RBZp<;L|1And+#RClA0jbc=4CfJcf?&Dw8Ejy^2pPgpKL=*n82MzbAob? zi>hSLaV;0yocfKsD_!6=guMe=(6ym<7}cc8h7 z-a?fdFlHDFdBEMsi0UN(*9Tz)Lh3A4HX_s9E^oS5y5WYcD!)SEn)9_c5M6Eu1>CpL2aYm zcYA{GF5pkC!g$O$EbiGi=tu#QM zs?r|@gC+|#A&5}~qyC?1_W`PO&238oTeiPHh;dJq^rpt?{GBXvaL4iC>$i#QH43P* zRK?v7UO@O5N_EXRb5;~%$fOOs8r8%%SrreO(N#@^$k~sW7sEXm3rbK51NI-!eWZV( z@ckU1S%9|;dI~qwD2x?DJT19(cLirL-iqFx|57RozVR)%!)|asLm60IVXEHXxdRiQ zU=A3W-6(?7b@?PEIMo!@9ZFWxEwEbx2YGmD60c(akXb~?&2PN{ts@kSmgyeZ+|b-9 z6z;mAiG2zG%AB)va)9HFJ7WB0Oz#w5F(bEDDaXsQ!Js8N`Ul(Es=@{M3(pi+w#2k- zeaCndj8gbC@yjOyLMi!E*&IjT6Oia zOt<7bOnb-aU(mzuQd&Cyi+1t#deTq2sE(U%WWP?rr*QWUV-zDo`18__ofxp~d%$gT zd*DJo#QWqMqQ3b_P~KAU^wetK3pblF&mSYD+!_Y)3YmA>6%q;+IAW2WUPPa54?-wdaJO1;04^GcHrh@^~zO{yR; z?WHb>2|Acr1d}M~u?cd|uAql9Dr+jhOtIXR!M}>ASG_FJNQV_P^YaS~0V@4EI9b_= zmdw1NS*|IjI2f`)vKasI=_4D5shYF*#mH@*z)B;=*?9;gMVJ7C_(%JbQ>BQ{sy)`{ zbomv|KBt4(qfHkCj&)zgB83;Wjt$x_fy1@2iBxzwlk3b5MP0s%9}K8_b1-zHkRiGS zalSdHJ$={xudd8_REH_B>YpFL=*y2|wJ-B$OT+FNFsqJJ@=nfI$0p@#5CNNZk&eaM z5KBI@65i^XMm|2MT`dFQ6vd{46MT3jx`_enf|NakBdP?5hIg^g$o)pfC!$~tGW5UD zX0D^CjoBKO8{^OXCGC2~V(_{|?am3&PNmICZ!?@PPa&>|GgoG2K6S-J^A@lL2!cT1*jU;Hn zz%Zwh-4r6)8@tO0u6B^qe-bw|l^>i)4S~QS#oN-Da_PcV^|+;}z>=P&e`q+J%$usl zAx#`sxtHSR(Fm?@BP#d6&;d-Fhy{2b^&BOiGh5ZZ9EEZ$Aj#j>5k*3g9peQ_U$}6H zw80;(q}EKn*x|(_32O*6QwMzN=#YGvBfG515A#1i`vHzV0`j&m1qCS``lx$Mpg52W znj~WHg<39z-Sh*`2+~J8@O4;GtG@I>#TFa5R}5(T@n6UQrBQOkFrVkyDof$$gQD;E z7eZIR7AJANNsdecFI=2)A`R%Z%b73cNZ-Mv9`y-BxH=S$SZG6Ep6cg@ahC|l2Dgv3q#GhQ1329Ow9atZrkGrmH@97B1;(<%>i1_15h>QrGX2=Xl@!tYh^? z4z5)~m$OKl`nIIb@z#A|8zljp`?x|6pM40!5vTd;_W-uiUGJ3|ct{(uRmh#3!SL;b1hwWEJ(8(4(LR|=WJ-^{)2i$=r~TH&OjaRnOfp!bpz>8M5-dw8nY1LwPF{nYR<%duTq7<DMv%g5-H_g>(6AY&?Fhgp6rcq}tDpNnD$AgkG^%&g2x*)P@|=Omw9~$OFO$ z8C%uCva&kfr37Pj_x6s{dMt;j+CDAsmygpvZI)RudNB-N*h~{wJN82LmMajNOU>YlXmK z==u72N|#w=)Ci}zNpSjZ@pNH@gO`7d6<1EMi|}}&N^b9~zOGb|GbuT-I1zqURorN$#~>hOz3yMS?x`5b_zO6_N}`^9D?0zY=~#@9 z#-ouYCz9}0_#3p&>>Dg*nFzxwf z!?S6)a6D$~7xZlRVYLzZt;1ZY^xI|8x&@K#8{=z0MH-~J ze3d<+HJC8#$KhK&)@=zowym84LXzRlgiSIpKF+6_@6Q^K9_;&b@Q-{TRdI90gm~j^ z_H1OJ)_R}UE`MMhZcxZ8J;TbD_|MQ_Nk_~e*vulrl0U!jLHg?g?SjY7N2h&zh$9K! z1~f>sgf*bN;B#0KwEOUMGnwsi%0!LlVLqboA^l zgk`yVF|?qf{`JAj)2f&vt&Jrm2t@1irQO~8rDCz=7aad2Lkn#=_ZGV)abuK*Yeozs z*B^)F)oha8NEp_vtvgt1%tnl4O-^0xoeB1Kt_*|lN2_p6x)K65+pL~6UfA#ni@Rqr zNTqQsBiJo)GLOX3_MX9u&`RfYsKxXF-QK7fXl{!+uJ$|_^w zPjPIZ5QqZ-|1DhDt^7PNT=L-x zPb-6F^gSjKvzCrLx9{V&r|US@4@?rn=bFKoy9xDl>#uZ+1T}rJady1 zHl|&wG08u?FqDm7Me|vPK6UHD;@jRvj#uVWL;NKU6@S4VRCT~z$4r@)-MZ*$=RO{uY z%AJL?9((D1+QuT@1_=To6~f0j#Mj3B;3Y@~Dzdv=SB41m{8%EW_t1Th!6SNpI=dHI zZJKJw+Raz0fmhHznE`)!@|wT6YwKdU2w0Azjs17In6Y)(<2n!D*B`D2_l(vTzb*Ex z>nI#=9e4tD6UeLIi<%i;sa_mjva57o6Okzfx5&CoGjz8%c&jeos7z&)vY9ef|ojm(6 z!!L@GQ$O|5Xm3b0+n#v@L1_Z3H(z6*vCBWpYAhVi)2*qLaOocpo_OIjVB4d;U5*UA+flvWd(h4Cv9KG#(4dPvG3a1sT#^i@D1`%}GR_f1sR+}O#B>Gj?{-dAHapclu zuo$nB2vpHG?M<8SoNDMBq`L;OmIS`QqqzO@<%KIIgnU0PmrjV%Ff~s~Gy-5|bJ==S zZJNMwdoYJ}{B6D5+9=enIHqta2iJ_lM0kV>ck?C_B=zwV47B=(udI7;Zi5PErCUC> zyV(<)Z}Xi~j}ti%Qi;dx3@;mGNH7<*=BsO-XzV=C?G%cV-40kYpHk;Z?NFJH zRfLm&d;Rb+VcKR%n4VUa%9?dfLH%pw&b>+t_{;hA-oYwOmvQLM=J}&Xv4j)grNKdv zc8o$etBb3B@1)#7xBEoO!%8DH(SmzP??j{ z2hFI2Va8&;;MVJmC{!!mOI5EFguYNpalG@$%HFqY9+rOYqFr||PrB2l#6#T4_*~gj zx3p9fEDc;u)`ECD-2PBF&?J~9k~E;qo{-IP4FcixES0IJsA53 zsx(n0mr=Ad1^XQwvfrD1uflpvEu9sm0_wQ0aLnmZ$CXVPwO~I*#(WJ3^=Dz%)0X>4 z)!iF07#u3|5glo^oBHC22qRBL-x6j<+RHWq7`v4KK8EwP4?3)3XvB{1WDkAHePl%&4VOG%;i`)*?p`IsWl%hM%;1K*o$6KM57sWEg16 zAXCPLQJJz#=*7<;++;%-<}UnBd) zu?6ulh6Hc6B)_^?9Wn6BnMFF??+}*6aY->|Gb9 z<|zFbuo?SOl1zo+cV(2bH$_mpzQWO{zQ`$=+Zda0z3Ncew{J>H3Dgf_7i*8!HQPs+ zSl&AA$wYTFk)wdYup2o&14?i`9xyjH&P}+~h00Y^PCde=gZ2KUX9h}G!70(X0kopQ zW}pRg)v<<>3tq=CcYM3P?h(Ja?ALmH12VG)wB`;0T2r93t~7XqSSPd}_ZKmeJSJD4 zz>a|`xlJuSGmXU2-4YR1R8%(E&2UnbSGK%}k`NFj-*10dK}We-M@`%|ZJpR9d4M3L zX!7d@`>I|3{!kSrTaJQWAP4vOY_V5H@Ps45g`YtKxlHq6=J=xksC-{g9qOIxP(^Fm z)KY?#gNMt(!Uo&yAB8j&Go^Z=7P9JC0^q)V%;k586WqW#|hs1hbj` zX3L)XIhKC}ss>YHAu zbX5`C=R4}OwJyhww4)NIR)g@M6MekYsi_pav3^MSO=pHtQH96UgX&eu0h7o3&Xlbm z8{j`N(Gk&i&S^!kzrJ>M)gdaxnmgoI8MxOJ@lUO0k7e~}y|YKD^a;Fq+9azb zx3q-oP53N7XLA?4gV}uDF+nPHnp+Zd8LftMvC5{u7gTMb8f-uh&Nnu%g{Ec-u-R^(4ian&BC1TLif-3Q# zyQQ9`+VgmiR;IHN%D5@6*CEZezAeq#qviYvl$Xmr*67qc`TPTbjoQDZA)@OU5-wWzN^$i!QWE5zCWJ}h%!D(4{Q0sIAh-wbe z?=*@RmP6KKYk#z|=%?HTNEs*Al`Tx)Wr!rhE?~7)bZ2*V+594O41g-k1qkRMP^Hb_ zpwWH09vtcM&QAFXD222P+}}on{$Fh*;<y6CJ0D(3{<4MVWc9WD4-x9Aft1PQDbx%ARxWbDH7WlAwB9jPQ1R? zeO>qOem#Hrmz?K$#OL#_<5&gK^niqbq2x9|r73b%n}6K!6-gD@j;Kp&vQom@Ar`u# z1=xX%s-qb^0y019EkmbRB{H^>ldfj>?AKXP?jmfy4~Fmmnbu7J>TcH%csby4^iqWX z6HIbkqRVFUo`-`GW=RBXg$`__h$j|$5n;+L0GKc0Vz)m}5gEV>vYeeXUjYEO)l%4N z`wMQshYBDQ&Q_YMX|_8$3e+!a!AD8Cx&w*fP)H>d3~-YM#o2nP<&iq(z$NYJEECFT z+yYS)08`o{G#hrmlJt3Q$>96nC2eLR9Cr~oTbBEWZGNVR4A9aL-o|1GTW%Ma$RJu1 z0Z`X=c)Xe5>dJ}wn{C@vMRg2#!m#s9%nUEqBC2S2N|$bDtK?CPu9U3_*$HQ}GGG&S z>bOwhz>}f%IZ%#kx#C)rctJf5k_OeOt_TooW3Tzu73TV6(7XHM#`fm^zSsXBn(R6g zqf4mVi*E-1MO+LNitRu!`D0+uA!ed6&{$<>b-**CZkgUc06MIQ-LQsvK~4YcyAJ(@ zZhb@F;XKM{-t+c0YgD%k>zCNkXMJ95{rDyicEJrqm8lQ>G#~%gLLAhuNx%byffvD>u>7AVBQCx2svK@ zGm1oz>!4-bg6f&cOZia~833?nNBN8yW8Q-vg+?l)z7DyoIEl{`=N0F|!!2`hEa~G=ycCPHi8;1d6CnnkBU} zeG#&y4nIKr&a`orst}@s1yQex@!XA!VCIyUcN478o0_(xcAMaAhs470qBX`w)m>eq zo9@pq2pHQzgP6mFJVmpc+Hqk!C&`z7_JeVOM)0SHfUyZ`8R@0Iar!x43~C68sj4k2 zaO{@O(oM}q%Q@b>P9X0%`rC5L^qZw0&1@cftO1E$7=MXA1JD}A*}xy=D+Jt$wq{F- z-=wUu2UV|E;@o1XKS z%(z8pcyU#BV;HQm4V`lUd(x`Y9JJnhn~XBk$j>dQ>uNF!z;xeQcRX|ybeQwZmof@FPltpYzHv?B;gvhjPaIp+ zH}nZnR&LeR(kmzqQ;vzzw+#$L3# zthW%^hKA45o5suO%cBP#LzCgm{zT~UZC&iWWd04!l3M7bkhW-fhZ4!>Zo-R$hh%D) zNvA`<=1#@4h>{aCscYGydHH#3E6O=sMj{Rwy=`X+`7SRKL>+T}H!dlwz0cuY7!lb?DN0 zeqj5|%5}O>?L=2EUbh}G^v}Tzc0IH{*cjP;cV$nrpUaHM<#&G?Dkio0(o^~M+p-Ky zaxz>goS8dU^U$iy6-waV=!CQFWLXl`rj2H2rIiNVGWtm36u4^&zj(!VtTMi6H-?}F z0otgrgh1J{A^XbpInOZKlOmvP_^!>6C*rc2ze80OZ*xuaHvbJw4&ICR*6K*uBc-S| zOYPvCrLu*|;0`=g*0lkNVaNyM9xweB)Oc zF5zO8!bJ+b^pjlv99Mo9i6#sL1bAkaJ<_smy++I4t)Dr?VDx8Ms?D&}L_XiBX~7(} z5gZ^ZMgFy0^RC_7rGaMZH$#=K^q-FxNS`m6L+BaEH$X|!yrcOBf_wGMfRBfQeNJf= zlpwi=|8Z>(Tcwf54CTXpj)ML~N_}0Zu}bbzI;2j?Qgbl&MkMVWr=`=XV@o1n6s;I8 z#Mw4PqC|mE0ETLgNPyK(MxCAX*jtUe2%>$JHB?s$8_rhUo+4DRrn#)X>*SG++Fnq4U0)uCCA)SI7`d-Yb{Xsy z^Mp2bwCFsDNpHj+2=J5@l7N4>GA$s~K2oC|S+kZ3VX%4xq?Y50!92OQGR(~WZl^pG z_D1k$ zzR3vV>lRZ`6^D_bx7Sd45qwb8-kCjI29tbno4n?7dfs|iNNumyBB!0w2mJD-BGoL9KNDCyjqbIWIJpkCAhHz4Bo&@3}sq$jwe z4z;rxf;2r2v(5j%36&H_%?B`r6Rs623vt;t0GF8qVjbiolLlB=2#}gqhdk6q+& zT5lV(FCs=tP^jK)QjxZ2)9N$0WDqv)1oxkaCv^=BC;9=Ee$vW{^j|9dy?nDd{mqc? zmFq;Dt}aCg0=;;)FJOFkQ$5j9E4}4Ic#+iSK+_eU^Q@Y$n!Y+B^mHzQ|}<_5g^NU@+-+2HyKxVol%QZp|4p)uCcm90*}VJ^YcqU*1U~SE;dCh zDgdbKgNQ-cY`m{^t@zdyc>piDRHT{UJai0jGGwx0H0?`x+Yes8 zz8y1qaM9iL~hDj>rs0voN?orjr79FDr4W$Rz7Sttx zHP188W_R8WJaV+3fsrjEt0+}Dj8^xlPT}^B%-(1rz^9+c0$scJLX0XAHQ9X!#=ojj zdQJaD`Re}h4+hgKZY3mc?P^n;XLqf#x$6r1As{#|xnJ~xMjZRTNn>2@!^w9Q>$WlA6$J8e)tVI)B zk@wiW($pO}8JknCI&(Wyb-MVm4y@J=e^L!^80c zhnvffe+MyY+|rZjLB|&6dwFaa+wWr`jBgdTaO0qM;#rzJH4j-KL$~p zH&ZKfjH9TT*YPCHv;5J_!~#@%U-_P&hMn+%@pj%0ebCFzcUn=G5YH^LT6kDR3s!MM zkUf%i7gEal*JQU=7vC;4TToeO0TAMbzCPlfz0ONuvH0gQ_BDUR24-%htp$b#c5$y}QFu5-3>3M)61MasR_xXJ(u<=AWO%*drd{$+!s>fWZ07Gg zV4!x0CyKZc)JFQVv0ZQ91}q>0;W?r#WjtQ%wa_!gv5#ys{_2qfC@&(}!*}E_!EOCZ za7pMr9^eK+D#`7U@lEnqpwUdyB=xGohj3zYP1kLvJET2Ujpc3bupWOA=wuZTKfkxZ zD7)(uJH*&axc^WiWqz|Sq;Uqe2(L@hW;=V{+P<-|<^h5-r0Pfzi=gWQyDJ|Z&4G#Ehp+wi-fn%3WhjEL#hj+=7X8EHnK36Z8S;(4XTgB#U>zkm%kvN7)p){~GN z`5T>sdt)=##pFZv;v>tLlY}%I4)dM2ym_Ke^kR8XNkGe5!Px;xBIHQ?mXKBZ+6eO= z$=w{pId6{uX&5|e*nM*#?9B;rDf1oV-4P*>nj6vxjJVftrjzO`XObBiJH?y~wF%%% z*aIJb%(crf+xqcB0q_~FLEhqD%50ttD6^24jHZoGh=O`T{+}>qsNMYQA!gRVoS9*+ z)tH@e*sNSgC$#Y92ga>Qd={vwur5LFUNxr6uDHh!Sj7tFEtQ2#U%#G#RqoB1xc9F8 zQxTs&5Xl$xC`8IxJa7;|Q*-!57B2P3_MN#1dKvXCC~iS@&g;62KOBX8U(0coPbYHZ=H2H9rX$P@a43yvMkg0k znNP6Lh^GPzq|Sz#`DIs(@r&=*WL2ofY`=+?D%0KSZF{$xoLr%(-foy}8?27+&0-g$ z^rZ{;F>fs_EtlARtJrzBLNhLH58UTb?_8idg||uQ80ET@+!~=GE}osuKH9FNuMw2| zyp684rJ1n2(7wNs@eY3<@?j?aBwL)LT#8kliQvNP=aB|wQz_6W3B<+k6(6V(en%q# zs~%gt;_q&Yqx)<4+_XxfJFj1^8>mtzX3NZCyQHTG^ab0W!rH*m!3pvJQ+66Mnir2! zjK#GT5Mdui4XaLw1n6aT346*C6Fq!ar_?h=ffdbhXKeO(j#FWfU?10fzFVCNi^2b%UmiceuqRGGyXKFg_~i7 z-EMS^%W>V1<7|281RLf34{9C=s5v<}v%-;3=5RjOPR+prl9y(Bju@5Oqak@IWgZGw zjC&=B+vG&6TU+^z1yZ{vsM8^vETxl=bEo_d9oqJ2i03jMWei#`9-;sq=bY|zjq;bB zyH>h9Qu8jXQ=nIlu4_m^JecN$EZO}%umfTK`h`=oBdTJFZGURn`G*P^jOmR=aRodm z*V&?ul6NND;wfI@1GL-B2Nu=(;J51gyX2#EDDF1EJ^8mc&f2dQp7sLL)B#lw_YcUs zM7OZ312kp{x&0|^u`J2my8-80NYpndSPvMs^ckxZ;KCRr7xX{z8T+(gstJfWs$pUi#60KDS`ZYlKU_k!w@ z*{aMl{u)#9W?5V_maR!&ou7U^C`z3ZNx!qY&RZffRf5^qmsZa~t3}H1reSill;LO} zV`S(?`gfK!o7UQ=D!#8m4wF9$*u<_K9+y?1Wl38@Co+-E3@P}R1MxiQ`~nyBh-HuL zp`ja`e4RkJ0;((~)YMO1KwO{{;HObwUBX~W#2anrna@76>Bvl^x9%Ces^Z(LD4@i? z{n5^6?3{ENwwf!5iZP39uk^kxm@aH;Qg}zwXTGLc^uO4t9pvp8nAq+QaUC?o@9|iF zyH?+T!jIW60+&aq{)6#O4nj|U^_{xC6Z`oh=4e43e)gSU-+b3!gG&n-T<%Yc!HiKh zy{ve6o2cUB0Ec@4of~#rt8eW_>{@FxZ*L`henem){IfvZ^ltG`HxkkTt_TcvBVi;A zpqR1&GcmD&EtBhI^MYL}W=`&?vM0|9=jW1n+yL@hj@!)Y`6xFN5Fo|pRae*wl(&~Ae|$oR65gNL^H$urKCE2?5o7TH1O3g0pGho7Q!@zm`o z8gSnCPIi*Ddt-)RF?J`#w`s)03lTPW?Y;p(%s?sKcZvRGlr7Rh8zZ@|?18q*7Rbmv z+`JwWyS8%SZSO)!shO>cT_6R9mq#ax_KxT$Ik`7x^LhbPHSfP8#g9#;@vyarYQ%Op zQ_FxOMH5iSdg;HPUD8O)Pr>o5zt7yMZtke)m1Fvh=Qj0BY(6J(0B2eP&6zOo6jCZ{c2f4e9j`EtCddEXn8(H-{wm`pRV+0(0S<=d-|^sPtcamUrdu9 zEOm$jwDBBNcY@YIWB|4y!<&3c0Of8yIeN8~>XS~mATR0Q@rgbqpxpkk)2d`{bkVLr z9JtxkFuNvXKIyA3RN5~vT7uI4F;>@lML7B&%WZML$|PT4IFprgjBJ|kG-7r&w8z3w z-sxkszP*h+DsmHW6c6hj6LB+_h%TlD!8SrM|Iv{=*8ZX*YSwjVe!!FH>sBCHOc-KG z-dxa}tCvtGzt4|~EU0+?Hr{f>Kr2IRT~UP>gK*Y`zu_!9 zMr!c7yjhM@ppiMG#=JRY;1x$x9R9_A4T_Av*}H1%b?gPZTp6{xA^{hojgugTsk&?uNNLMjix)V zn*(wuaW{8j3g16&MI6W(DySTPoVX7k(}9A?phNa5bV z4Uo*+?_T>gW4z}JA6@X<78E~aCfp=SnJ0hC=LB}6@AT~vhz$n9y|cQQoCHVnvC=1o z^J<_ZpxR9@`|y7`bJd4>O}~JvyZlaq+E$+(^2&haTCDD$(%vO*?BdKDaGl!%BRfmx zaa{QO5y$zdRuJo2St_PD60$fIY=7V2hh+W#b9W^Ct7K)_9m15;?C9{4u}@F0|i)X+fuvo5^eZM!)bj#!=)+%LGb5Ct6XIk=6k z39oH=GGbUhnh#X0Y_O1_0rPdN^m{Lbv$M$$i~&bEtSs{i^Ya?9Vu|hx^`2oT#n-cR zMxWnlu1@^gI zP-Uvk-9xf|%NoGeHW#jKYw!7N=2@XTBq^y7=iy`t6#|z*KA`y7?=Lt0{pF_jbV)n& z%JY{EO^mKq3?BvutjndR~){4Js!!os?GhRZ7Q zI~1{SrjJ=U?_q}$SY#4hvH!igKq(ju0h1rwhT_{Ijrl^x;wtA;EKIY%*E}Z zz!eSq`lla=O@L)f8Ll!@%kC)-XfBdr+V)B?+ID+}8xmdl+_N$o?FCWX?opVnI+Po$ zGtantsR3+OF#9^d!*?4W`wX}e4F_}@NX<#PKTx7C)T zs-O8QNcV)Dh2R7f>nb)CQ({d}Fz)Rb+8A(9c6vIWubSAFwM<>@Tv1xj1&gvs5|-~V zVJPdN*C7Cr7}bP0^vDqlN5U9B8>@BpYCfFgrQFIRg6Nj!LFI@*#qc%=0m5zrbIVCPfAGFnNbe&u_F#~{emwJU!u}hsX zHX8@BXsl-;llk1(r&P2wddC!W>qV>m1>w{hQ4_l+U1agOOZYmu z`Il6-t}BFcV~gV5T*y^D+AO`)pR=!pWL;Ys5Yx4Gc%cXmnV`xqpo>i;3hF?)~&3&bHmm8ktkLQQmC6jfNRlg_282UZE>1;7G9(h)>9 zL0{r65i=ZI2DurA*$&*m7@5F1+QZAucghHd1YHJ2t$hMRTejq>YROd zL#51is>}~(8~VBP+iW5NYHl?fNY4dnYY55JCr{{tEM=75Gb0un#aD-Cf37IRuH`R` z!i#l5Q0Gb&8D6#}H9H$YN-Y97`n& zGJ(ei0&CM7OBR^NfVi^x4vKZ(N%}|lt2s23tEp262&%Mm&O?ytGNcRb>4(;pIRstC z_09ZnU;ZHV*KBhi2a9+0=`-lk)f5RS;vT_MRejMl)g7!Lx?Kq()S|0zs_?0*ZqVa0 zkzA!cua~JzNlPGb-!clH)uUu`iD~05xa=ofi1+jdO9eL-~19iy0=MOduw^j76z~ZyA`+{w8|BFdt4cqC*`a zuW`6b61Wc8|C6v=wRfc44O~p7pS~y=bqsJzPG|#e5Rf~YMp(y^DSyGagbdD`S7O&! zF91cEefJ|OaST4OcFtd~c)(r?V@6-IhnJPgH8%zyg!cumAtaQCMI00U8z_#)jiLuZ zZa-X3y3JZ+XQzcvb#u-R0)4OusKCPN%zx#)hRIjWvs$m+z3Nm*x&6j5FfbFDTz3b= z)4-AdVfC8D`qlk`7psUrGR=w^`XgHtaV;0r8R|IE z*Mq9W3IgdG3*gQ;L&o(53-`viC&jqAF#YJk580&}GrunaRU{1r_S3m)((nC^_`&dp z_diM-c;u1O{9@f(FC3K=1iAij`krX<*n3AnOt-G`6y$971n9j5o&XSN@fp5*nH&Aw z(D^o_J znAj!9Tx2H`J%Xf&NR*%k@Wb|Fh|n~)*n&hJfD0pDo(Wf9ptqz}L8fQgW*gPBkJy~| zmrLN&!8s@3T)4e4)Oi@xm^5~0>C7Dks_zScoPmBw$cDc_VBhqiII4(v{Hxe@1K=$b zBv+f5SyZfla7HEJbJ3%LR5rLwO5B`SbO+^u-v zFZXt;W;>xpuS%ahWa=`}8!`%VhX`B8GPaEkMsPI67z|2k!<|ZU%kt_V2QJ4a`emIU ztlETZ8Sb&JJHwK+L~LQDI)0S{{;Y~aj+>M?M8}~14!p#DTcjQiwVUmbKDp1RL!n{s zRwE;ib!VukYapVjzE=$II|ntIb!z$#?)aP@tkFGGBUYUF%H4*CmmLYOT}>(Qv5J@5 zzM-!c*IL==Na`;O$&z!4E0Ebc1z3y^g0-FlK#$c8km%y>99}y4Ux`cO4(oki(Y@i{ ziA&h-ae%&HyLVk|DonkdV!N0o8g>!eX=6#X?LjOYL8a`6IAMh#Jq4TqA;Y2g12B3PHTABY;2s9HX978eVXEEr5#g&}I^QveR8bNYh?%^%+yfM%GOgw;DLkER z3j%#BnH8=QvU?w$!BQQ)B(Cgb>VpKQ13+$HgI7R$OXt6NsUsV`8$?Sj7Y&fW#|#g! zEau@s?Nh3Mz05c;AXkR=V!noRb=i_W_AViOy!DfY2Zy6Q_H>@QzhaySf#@^zR-D|st_M}3;@bH?Aeb0+pk{UcoEPz>Ct9Y4 z?y*j&SewUk=bir@DNVGTf8-R`ni*<_P#^M~VuI3vj~Vvot+COeYNcgG5_<~;bd*Lq zM~n-jyH50{_eSwPGItN|xEropA}qZ1!MUj2rkz0$83IQKX2YVvg>W5}brHXRGeEC2 zOZlFtYv>?XzbD=NW(hGhkthOnp$li{Ko@F#@G8`HIA=nkqs25P5tFM@2D0toI75NF z>~|3IempoA0tHw0U2206NW-37oVPL~HxUM~Vi>z+gBqzn_eVr8>o#9NmKbXT%byk; zwQ_=cL@BJ;r>4Nj2id{-t%BJu6G7Tm4Uq1%@Ml$v%L3HiBRURTPjfGJ)dN^qJw7{# z*>o#KGTxCD8Z0Qlr0B#7&P16fv+Sz>j?*ClQ+l|P`xbCw=|4vo+zxaT^yzgR&WamL zc7PB@5Mle5v8Q)`kBY$+p=*!wy!01fiYg#gvuDbLm0JF4QQ`56D5?F6GSE(|{7o%YMZ;+c4@RQg zXM%K+Vh20X99JUr-K|sF?Z+NIxwC6jRnl4P7WlRmg}rPSc|sY5PqisM$me%(fE65D zkSduu4jr7(vz!9XS$FytY!6>2AfM7nkHsNkFmu*){x!orCx>TvsNh1LuQ^UG` z3^)nlv0T2FlhVJ;XaKnEa$ZZ#q;J4TT_z1INa|``p#i`W~y#i<>6DlDk0~DND8RA0brP z`Fbd+bODP6Z)LW}9T%dGIK;UDOb!6C8O|{{+BYI!xXOYHwTCx*PBWZMxd|a`aWB8& zZnIu;8`Clrj)w`offQx&M~E5i2QOb)kLQla)U;@VH^N^XHF>C=2!?%Sv4XN{-hQBF zkwC-BT~L_e6kwAH&ZI!OSr0o4CrI~k)KbEyujui)&Z7#t%Y?1#q({D_$8>8_`S~;eA&W2?4SoK=WMJC#=7ed7HRsO%iVzu z)u-s$^PP*cwdIj5CE_4tSAwRFQ}f%Fmc4GM%{iJ2vZWYUpgicH?2MqP9`|~uasN|= z3a8g4K7!kFUzP6A<*N^&q^9GooL!r6a7w@$s&Tg+_Gt}!UQRx^2%rufZ?-o})b-o% zM^$`L*@AxjL5F5795K_lPnkWkQ{MtJgBw`*6h6zKi}g ztC1R~FS+vL=YOZVPwc0BI0V%vUnl~dnE~1og+X5@@a6hh&~Zi(nMwWtc8?$} zIgb}15?41;$#u}}PnamFh5SN#%?_1V?2F1WE{832){u|l_j`5Xu0W2~CKn{d?W>(3m>&R+j;DxWPNG+a$k_5YC z!JAU%Yb3bQdu$(ia30M(JABGbolm7(+i6FCz}OQy&;-*+-2dGEnUirg8#U^7lVa-v zcki}g-_iF8$9Z+>))?`0j#bu;+K-r}4P~M6%gf?9`FYdqPjE)jFC}VL#%U27)2U%$ z$Ckl!p8`w?F{Km7Pl$+_FV{Y;S)*`*poC)R?EHb;TVBeABL88p!)*6WN*ep2m~7O1 zpkA`ygZCQ7+jC-Yzo*5fOkJw1(P>~{U6sJj$zFFmRX!Z&m7a|t*$>C?MILVLi ze8}!wVgf;Uk)>blm8P8?q;$zK2kDLlT&NWC4No%V;P6_i{#PG$8`e=O&)+})d+ z6QEI;{{B*eAmtMCl_<@7tbUJIO{Vz(l9(~%j z<5&S1$1vn?#-g?L%HgoSt|^Jgzy};`JUCL^%W+Bp9#i6v+m#M{Q!MVJ|J3i@SrW15Gsxs3_Lf6{ zZv2ejxa1Cju4!+#?pOD%Hh8H?qSu@*ZVkHKViDt#yvZsWav<*uFQc~AzI)*m#b z77yk^_9=*lV4codtZ1Po&zv5|AmQEaPn*Q37GE#0@)QefzglN@aWsQ__ulv$MgZr0 z64yb}H&Ag;$3snQ-J>`sLq}YmdLqyitPiuRrwYuE1y9f(3q~tmd{-&pwJz7TZJ7&D z9F)^kJ@@Nb31Mf!%22WbvM*L=14sLQ?U^sgeNE?EUW-f?j$e6y`fkwqyT`7aM(Ari zJEQUKML}2&x8AqPr}yw4F7OKvZd|p~KYWCNm-mGT%jKH&&O_I(UAdNli{GqU^x2iG zEQp$@b?bOmn>@ML&*-{WAUx$9>Q+!upjxG)yEEoRWtH;0`&Q3h!!P?LoaEa^E(!PWz{3AYJ(yc;0RXYn!m1wxA@k5l}^4t=v+uoTTaO?wCi>dwC z(CY{XRWd3gTzcJZvVAA)-LamYw#Ko4vH(GD(smE)kfe{X_CBc_fgloPH$dX2+7o#w zvhLM-9Y{Xz?*3+B0?yYLn8w&Jhw~O%EyCx{%yk|L?#9d4yvgDpKusQLtbVSoNx@6= z(%DiP{A9f~Swx-ATmCRnKXOFMeStQP_-nb6H~=cu{2@jDwnGa&+yKw+w5pysCbYow zp-u6`N>}BhNTfp#pO)8(orQCnS;FdfSM+ds4<6)UVbw>Z!D-zu?T;smgfG@+0nx~p zj;$wdtbowXE>3oLi3b^Ch904AJluD$ZDLZ=sM5=~;T7@vx) zE3@f4u(Ly;@0SE6bN4iOdst9z#Iq?YD4Gxujk>T?Zr1A7@TK zo`sav>{usSpH^qjG)ZIvOON$fxT3OL zkMN>p$)Bfat0tOFx{@m}p1C>ImAxH<*JS-%j78s`xGC4a_X8aM&=zcq?P*Tr02?h( z0}zDqLoGZ!tCQ0sRF`E(C9T^*@Fmr!AI~$QE-a}+|9rCyXJBH>TLecrULEEYx%O}( zRi5DnaGHRTr(xrhsodHE&U2)G`K84w6`OvqNp03yui#dRYvB5vSvq>4Qn62dyL!H2 zR|_t@;n(Wbk=3L~ib?dHEumEu-D>)pcQfa*)F=J4qce`(1la^~mR$1*k)_EEtE-Z! zZ%p^LcBs#mlxuFS^8)l?lyJ0H0%KRHU@F>)7aiT+4f14@r*xHk?XXE5%?+njTM-6q zKHD?_9ew0M78>l5@o;W1QHyufvQ-pImfc09)lp&;>WI8u$w2iQN2dit48mpF%7{8? zDJzL6k2;rNkkIr5XJdf5wyON5>*IIdk1k(6ot*wGfSX3Q;#=OyKpVlscC_<-1+sX^ zo>{CM1CG9?fSh_y0@|0aEx3|_kp;c9K4MK`5#qh|MC!XHq)9fb5B=X?;q zu`Fk*QDCF8hFGz8H+bJjrzDQeF~$1CS^5_Zmu`Z`kG_L`f6F_BIa|AAMzHTk6PI1z z^GB#j?s~_bn31!qq#tagsAt0!jgCquaTYc!`KyE88y}yzbu1K=ZgOvx?syySIj*SH+@^cXwA77iwr&nL7K3t1F$8~b$=er8m&HBV=;MtZb zK)r{VS_E2_IR=uhtNN_F#pvr9vMRc0rY3khSjh@;t3J8Ym{b^}t&_?nKazFaY<^|- z1%J9|m~G0^`e@1x^97CQi*}JkL6S6U^J!0lw-PEF-%T+|y*K{VRf79G*4-&?8h~yj z;-h;kQHA}VRIY-N_B#Xp&e)<9SP$$1+jqp}{=5*I>$mc_G;ocUcpiguBxlOenAu{l za;SUNRGESqJe5SwU9Q6Qg+@1#@}AdNlz<%e#cUswd?bjH=GyU%BGK2S)x1~iF&{6& zhj^RE@7IZ}jtG{@P8R0cfe-bT20sr48^=aWea6>x)aHSuq75>T1Q$HF%tLj0N(z^t z6DFtg_SfkPA9ZcM#7VE>I&i#o8Qp~yNRaOB0{PXI!3|%6j6g3*LPF)vb2`;a!derl zSF+G98_#l0H+xG?4kX-vHT$8#!D8B&zE#hutLd$NLOIYaf}H-2avQ~yyljCIookp` z*`ZSOnm$}xReeh^#;tb0S6h134)+x@gFY9`ax;sX2qj@u+0D#bN1UGZa#d1Xcr9s3 z8l!9E`odx-RZ89^sQ_jS-I}<`|_x~f-cY@ ztA9m<03*1A0b0=;AE!{b*~(C4&lh$v1J(yo_!5hErYr;6_o}Y9{NV7!vGd|(7aTgP zJ>p#}&|_S~sU9cWd?wC}cuiWIa6fFoderJN_CYmU?G%r8--h4h&B%_b`M#_-0_3No z5f(~_`L@UymyPoM>D@THKfP`z)Z(nS-$^nr4=1)X3zkYnd3(|e`eQjUK6^lPVayzyc~MGIiPs&VJv6I_TRcu8@4~nrBTHvRFRt%3X#dF zKkYl@3LG;ohr)5Is$H!dmAqJ+Ltm$R%ZC$i0@~{}b(&0EVYG?l8$et{(X90jGqP)U zL`=^w-lv!Z+Q`Gb)vS7^tH7P`dj!X+^FE$*3pIy~P!ArJH{=HB>6zxbXj|nL=M9!Z z(7}%Z+QT`FWpi>lEbRVwK<$caojOuwm3ZHJkaon~c%h>qw zp9zXGh%yWNmkR6Zq7q$#ZScjKTEi;BK3MW&+W%s&H8!aNQD1T=C=3hS!Mb@U(ytp(C&ies?ikJ#MlGO&JtsHu&gWj&unRK^Z z+n!?H+&E9^pBYe7-~eeGsAlRbuIcn$Xk_r*?-IlauU;a6Dzu5dVl*rBY3ioT*t5l48rlsMIss@GH*~%!}oqW~qPj82~3!daGHS8*ai^Y(i#Z(C3N|q#0 zz{cDDKHPKMx@_JXvIDJ?S5nj{5x z-1%-u`{gUJ;)PUMy|NQT#hh?F8EmXZOz$T7hQ0 zXTOPSUcI&xWqkK$5Txn?%1XoUCK10MM*u$1Ys&R{3J@n4@oQBh)p4tK^wZ=XT18nz znS)7&qhwtscrcNoe!;G-S2n8vG$;+T{as!60XPC%drJw8dv*ojo`U>{^x4JBvK|k7 z!|RIEK})4gG}h8g2lKqr*HOlc=?4V`^aR9gENW#?c>M;zP6QWHco3|LGos?bO+1c>*0?4FQNAf3^0_(=_IsC(m9UoSSQJQg7i!eKa%3AKt!8>G;`;yw~J@ zS=iW-Uf}g%*v5tZLLB$!;ewHPK=~kaMIHp^<(Aes}v^laehhn$wxYH5AzUbu-003>= zs<216e%%1ZU-CPftaO@JaV3}dEs3s<&uVc<1W^`{|4(JFFgE82w}ZDl_4nMQa_`xm zJo2fWgJo5_fp``@T5N2F8P4z+6826%0wd)H^?!_*MAJQanbPUJ<9?8RyejI~Q_*s# zbt$;pDD{yp0YAowD5AaV?$T5)1z-LeBA-)UTCLW1c)S9@Z0lWdI|imRSt}o>^_n#O zzG{HY$Q5Ti7&A2x0OjUY|hOnf@it*Wj)AUC`n{6v)&+t~@UBSt2wCN8LG8Gj?E8W(hH zr-!1{``ktCF7ZoC2TeQ&e-Eps)_I+A1@Nkl57)rN=!N&#xZHv*(h(SX*@dkRw>++Y zt3ksO34W6^J}0lBwlT<*nUT$Xl@H%769KELEPJijmtjq5=h4#ip~z?L83V{I zW_7{3?YSS;ruTOEqwQmR@r6SUq+ziK!R_R%9mV30N&1g%*vw9!pY3c_kI=i@(+D8Z zoKF1i+ILTagx#LYQX}mP(WSxm6SSv zJUB<{&&r&Fn$9b0=O&3G{SJdM9!3Je5w3iosz$ML>1a-Bg?{61?Kl%2cC06J2f>TdTk_W2RZ#JtBnAKC+jH1NL?4z zN%>xrfpTDc4kHnfLMtC<>)Eq*1 zbt!5$+SXSjp(CDF09gP6?5Uil7rlfq_nrI#>(i|!SzeJqOOv zh*M11Apl%hiA<{NNmoB{L~;)CtJw1dz2~R*b6iIwEXm0j|7U}KPK@BfIi|pxA^g~Z zl(Kyq@3!Ql*93^oKGq&TC6Of1hO9GzSiac&u+u+Os!w4}XZ^Rrt9H{5AF`_~(S0rc zKCjwW@tY$<m@blZOA2tc%wA_71~7 zP-x)=KmjO13C-lb(*?c`vR2@T!eJH@|BolLL|= z&1<{9D4N2F7Y^7XsK|*hr?cjR%;2IXYEzuF?xbrUb)d5zChOwkd*mpym+2~oBK1C( zxUe%+x>-98sH5m=8?2st2jQuaTtI}D&gUyouMR3H^;^9_8@S=)U*(xd(7dKJQx?_* z4G$otxBk2wp$%0hwu2wA8?Ag)F@hC56W*Jvs!}6QJTrnI+?G7}lb5qY!Q1-v)AaQ7 zW7iY_-fN$YPYgB936Paeh;62L+I4>!VPoHO*-H_*E-F=m`F;-EM8+&`5vy;~YaO#( znT7J*(gDL{HCGD`^Nw`1yB~#_eK&>9BoD&o*=$||@x_oC1YmG4PPVmOMOzFo^!T^^pBIUaxPg!}9~S&#q;C6W5_cmP#M z_LLv5g|LsT^L~HJvOP0)pZ{DUPmzr|xEKPcY6bxH)NPt58HOmgv;gFF(H_9GrM(2yT(x!hFLw+OH5heftf#m;5HL9~mr{0T+`HvXw=|4B*=0~Zo>lM}PsjA-Dy%+6 z%|U&dee48AlZ`kk zr~5|r|6}jHCn?v>{93p!h z*^zbZIQF<6ucP<%{eFMf_2>1+^}GE(=XUFs8|S$m&&Rw!9?$2KZuU{DbVdRS<#EIq ziM&4f98{ge(bGI{+($hPgCi(8^jNr{iuY=r)_{IIw)GcC8{hvCOIimJRw=zWcq zLk$y9r4Tga5%Be{x#xSGmEuQ3?z>4_ze7@ZEeBb&LA8qL{*5ahbXmyIzIF|hO~~Vu zrqCT!Yl-*R?mNNs3oeOE3wzo;lfSp#Amb3x(!COYD32go^q~ii+j|lg z{;!+?o=o-Nr~cE~`A@O)ztE+`u-vgl1Cu3n;!jv^&LN_}D9_+}uV_%{ufKQ_brmX> zzXST(FMx3DsWDs1{l&^ZsI`aDk_Zw0uYRgFN@En3tqf7;zh})`}2LfU|NUINVtB zUi753$U;Y6R5*XP+!Qk%-L$&Sw#j?M9N}R?b_1bmQm9s$?e25O?Wx!ISy8BQC1eM? z-~WL=vlJfmfQUkrg({O%caZJQU~)~BRnC86Hxvx&xs#q7XG-u`6cDl%Tjba>X&R^P zPRU4&X{D;1Q>a*7&bOiUTs&zu{JlI1qbB&jpsryTrjg;~mT<)qut)XtwuiG9KH!HQNdo=fV-aBpOPCD&8u3cQ;#CQqBc1Dko)}g z4yAM@{a6W`-SW9=S{vvNHcSqI3|(dmb?^pDn>hfG+T+S*3oLNQAo@*Lz^%II{w>@2 z-*H%FZB6PT%^jRsr2H;=%M}X0xGhiWp1~!)?>Xg_hh5}CIq@Ur0Cj@C`4-$J~?~dB1}>YD~2%+iZp0MHlCT=DXOq*a;fLJVucJ5C$Fe@uhct<3Tj6P!JV3{W3yNO9+}OPdCJ!E& z23UhxKJt3|CtquPaHa5SJG_*Yey2Fe`O@9GZ%* ziOW(v53__1%}pw0=MWXj%E2@!56+4~_gT_9nhL43Ifw*@WE{hIg3qS=v?yXfuEpA6 zY8uRvobHif%MS5fL+)k$#?lRKQj}BRz6|i2(`QLGK9fH2Hq)OJbeVyd;Aza8_b>r- zX>miRjSM|=b|1wpL|5Ed={2(T{d6b!`fx%*%55PZNBjMUNexo8k<-SiJ-jZq{T3l1 zb`PLZ-b+cA! z);y*E#>s1uExH9LYgS|9%JdHp%kb$h9LFHENSYenc?2R6Q3@Um%7YQ{@Vuxr3rxe1 zEXgFNE>jWJT=e`q>Lhh5d>r6!hWyVWmZIk_4GFovM6_;?eApAa-Fo;<#r)5PeaiUS z)v&jnh#3Npy8{x3pNT#1Q4@O(V}pAVU*SYM5tM%X)+WsRU2XQjR`T@3siBbm{O3>X z%(bjqG{e%iM@CXBuKEF{J91Y2q50Sg?eyhX2@2aj!mo+>Gv^G4RWBiuT>m-dGan;N z-T1)%N?G*fD%3K1=nw%*quqrcYI2u&=Dn>-v#kC8BJp>9>9thbi0BPD*4$I1DO-;pJ0}55rU^+nVwTH`8{SL=wi3UVkaO$LDA@9B)!p znuqHQ0-CNx*_I3)KXF{~QI7bj8uHgigg*AMJWz^z&XDy1xm@Uk!?%f=q!&MH$c5b{ zT@T;-K0x&abVkdkylP@DhZwReS5s8&RocR zdCj7NuzLuy7}Ph{4Q-OJp1IndW&;wAx_$lQmwN6uoMH=hIVo_K9L3jH>&q$=)r(Do1Q(^9`j6)fgMqvwf(Fb}>7_G817B z&35D5q?N%ayA)a^LTa^#jrrcGn*B{%_sOQ0{Wf#?QnR}yQg#GsOEvd&(*~ z7ewn8YmO{@>mecO>Mp*MbJ1=;qRA{JbnbQ>rpS0!%*xkv_TfVzBr^v_2Y`&(__dbR zDTbD_`=Ub5>WiXj3OGiBiNp3QmZ5(B`o`k@pYsZ#Cg7qGaaB~?fyL*xD$+brrHdg9 zaPzDMn%mzAB3F}AuHuvH2HlTwFVh{FYvr<7D1Es}OIUCj^0?#YZ66rclJiXzOsQ#2 z?{)Wb4wb{CXMYrEZ)aFO7Ubkr+wF=l!3S5Q@3Ph?KQxJrF3yZP?~qU>E~;5(i5cEA z%r#ws!2Q|U0Zr1%RZ<9ziAB7zj#R=913i9$emOh}h9KnX z2n#cshCKt_&nNRnWP9b=P;ls6V{c}Nk9~T+mF(z#<5;NLLUR|y)(pbddC~@)mMNc( zt~e-6QJph~bQFWO2|)N>r3S-izXJ0t9dA~*?m$xrr-0`FH!(I`S`g+;pIPTRP85)Su~jfj1ma<&9Ro3u$j!^`7F@J$$W;$-))-ri|Sm zl`H&=IBj_GYm(`(x6v16KnzNM4xWR^8&h&39|{|YI>L*KNV}cJ^p-N-owzB&Ot7l0 zYmL;F(B*qGJrS-Z5{ri7ZFwz?tiA4w)<~KSu9M!X6-Cm~04l8F`L`dct~7WKQ}I~+ zlLH?#-tw zO;e>DZ{>A3@SfKzijg3s4Q=QIr*31`8@rZJC9XOoT%^}j1zX>LomVZJ!M(S z$2kLNR+{b_pJx6bdh;`6oxqWS+23G5bPo!_!2;ae{B(Ao#Q{79PV{@=KG( zvbqY_ceEqRjTAuCi(01MA;VT*N+PI=tCXviUl> z1&owe36V#ZPQa#_ba!!@|Nh>ls`kYFNk7j`3Ciq+M4ux?suFK%@d6@0lagrqJ-$bF zuHoNq!7r#g*#01U z(-*bndb~Lu5p~u)Ox5aNKP;a{KL87k;wDmK)Ujm9ihaW>$~(YWek6P7Df+y0XGxa+ z>Q-YkGMBWlq|H*s^_cEpdTb2ic>q7629l{osGx(%cm%hsra^9h1^j}r@yjtp_mKtq zyyPo^)uqwGaC>bka3eM78_X*l`**4y3fDbBmP1t*Uvqg5Ujbt73PlFZLjxJm9gx-Hw{UPre0GhGx#T zz&#fQD^Bm@xXTJa4mMZn+CxnvB^1dm6V= znr~!`crF-X<%d_=&Ab&Jmz}EB9nBbcE#!-Q@1R?v7?#0X(KA!zS!cVm^m6WT2#N(M zVU)>(HZ$Ps^Yd3AdGU4YuQat7%eUK+kEe1;tA(ZK=^X$Fwst=+ClMTdkz$6 zK(B7{l3dpJqeyDdw>f!ZzFc13{52)gi;8w?ZR(g9#~3WKrsj z^xPY+-Q|xn4>pjT*x(Vqy2Cq+BaR>QXTmfU+{NYn#p4wCJX;dkm3Qwh)IJ6NuD(rN ze9l97qe4>OsGu2XZWLf+^vvW2Qcj3+J4Dn#*Hu+5=V+?#kn4;^`(BXpdXjf7Lm&mJ zm8+?iWRRle&}(cM9(x^=SQ}^i= zR3Fm(kM%WO^Jjla_v&w2K}@9|8SldOL$#V#J!t+f$!Xl{#O~Us#;xEJ02#|Q%Bd-X z*Pl^(f9RYJ4;-{RBV{eH6U_JMwuQk>{llq$=xj6*r3`7nzS{|JTeljg7nPnmME{Sy zYMQa1>GO4~ovi02Qv$u5<4jN**tjn~W%QKjscrmqu%{f{Lb#mAP(~YdnvRwaBZ40s z>{|?aFA8Te{^h+;A=|;en;p|%@_lD`=agMBqRRJGMK+C_f5A{4(E*$X?5y7Sy;Vic zsxppSMW3mgjTsBQHeQ+OkCygs(=M?|`AJm13`BA}1=O?g(}=80u4ruS{-}Yqr>lMd zGK7Yzn;b@1%S+*)Q{1DaT^F!Y;>Lw$r{kJS>*xf#!d{Uco?bhlOct}<5^f0>Zu7Zu zz{>t8FN_UTUzU-d=XG0PpDKFzKqBX-oqLU(d$8PQf0a&2$I@qnW(P*NyeXcMk6MKL zdShG}=L251>>}Bqpzii;U+=aSHi4E28~Cco8=@@g7=25ZOHa?{a$DHUGC(_r{arid zk^wk9mfSQ2Rkl3E%(>wZrNt5U)9%07m=GveMGP!!p44tyh;jtq4Wq&$=jpLRvvX(l zHwHnUw`{w-K4=1OfWvT3QI4Xz$+Rh5OC`?_IqVYr)(Cq$qo*o9^gvaC%2Ur=ivlP; zkoydWp-^^24z8Y!6nGrV%*OxuW|^tsn=4vF(x)zxt4wNI-cNA~nEqhVGzajb-8nb& zCJSiv3G(CcpU%t>XXv*Hhlra(B{s!aiuu&hY7{h@DDTZJwbnE5n&i|ek^bR)P@VI~ zVvmMEQmW?*W6>wRDslafS5D4mHnxj(^`^>_^z-%B{k2HsExMxoPv55mllmPGoR;ExfyjoEs@JT63p z8l_{|`og+ElzX6?7YPPUA5jHNM_s2BQWb|9^W-q-sD^(0fv!1zZZmnI9aOT|(1B@E zw-eM3UtPOu8)5%>aQ`^K+VKis_MFki_55VK=&ZaaaPVFde8$`HWg6IfpDKs9=4mu4 z`^)o?lf8#;HM&T{UBuOg?4HaD zepA!Z$UcT?<90`0>g+!0(*xb0yttRggksVlNtncZ^;2air0k(uwnwNS7pW0OGXL}T zpDPzUyKeH`t!ps_}3My1UX)aJ}#G$RkKX5J&W)&p92D9 zR~w&)TaQFXHtz3lG`FBjtw6f&?Xtqz4Y`i~mzx@uY*MonppcK1y{4plo#r`krSwKc z+Sql$vJEhq6|2K;vb~7qJMd>lRYEiSGXW6dv9B&cS32l0|l+>X&hLalTfNf`>Y&*U(9XXv+u0w~O|i zrG#XHGNb}SjgB58Qw;ib5C4Afe(*1cGN|=UGdOI7An0J2;jutfWhEx*l?j;h_Y|8zQD{4B$qFz2@nNX{=M7TI5B!$ElgXpiUX<-b9fqgoXqK+XMNY=(vF zo2K~K5hor>@IbiZp>PcU-U1!ok7O-ewzm7fY{&K}K^wi(L=@gj0Tfp+(}&^SID@O1 zfy$f@oYzmd=Q(8ba3Np1L{tVFnO*fsE_z>Qn_Ag&1pO3T3AL}o*9pgwoGYr@@IX78 z0{B_Fn`YYYc)1zRBGLc}$t~CY-tTRp8uUo9D*8YeL>3M2yoVaf8t0+{MS#F`Q>s#+ za;lfOqEw~>hl$Ehwi!JlsxvV-z^w>Au1}1oEHFW#Ae{3>;r}5TeS83@E#X_1g(4ex zS5VCv<4#GLgKhyXTSicZ-*C{M!9joOP_vZWx^)++&Lx(YjjL8_7HRVbM8*5pHe>|? zi=s{tEmEw`){|QD0H--g%K_I$JWD82ar@Cx6lcI$nJ}h&+!ns+3C}qr(mh-E?XRKT z?^UbARaNi1X1)jpGLzE_#UUk&6jXb9kQ+y#9({j;;8-95J-qT2J9}Y}!cM>r+XM&} znVO;#1(lQ@C#~=8WInRAr%f8$; zB4L*99&DNrq=>@>XHT^3Sfpjs531Mttg60Tx{+bvYys&jB!`nVm@jKp1lTGpVoCX+ zmF8<;hm~;*;pf*y^!#WgE#HP46sqf?Gr7mW`spxL2=X0FH)xT!8? zm-Q7Re20jt_!i+Il#Q`=p9p#~9`KGglaliGg)V*kKVAH-}$O<`NLgow$RuF7A@$s`pVQCx;Qv50^40Z zV{h74lhALsl=`$yHycV^l6b6>f4xeE#H#AsjVnlY+qZ=lXLe-y+lx!|-9rnhs0k;ThYe9exzaOQVQ$xHe;^&WLMSk8v z5k&?0apMqCl#w5o4lYMmS&xpAu;0KUr?&FY7^LhjKxhqInm>&mAQ08kwL^y-wB0=5 z(2`qae}T*wL2`T=!a?#T=|9Ce+I>G94zF?xm~^jD20%eg3XB{)Xo+k{`3oqFiWSjD zw-Lml?gL8HmY5(>Jev&{#LKB`rIzTrr##lZt`66P_2eS0s-GcD*38UjnVGV{kZm=u z@fd2pu3zVQ2$f3i*;x1nVKb9o7{la zPRfMUYB_b}R2LM8!RH1U619awQB3K0_*?W;0TtAeZBXz<$T4v&V)B3eEiB@kEYox? zGY~OJDC2;r8n`{e7+(L}h{7rVT|0}^PpI!XU}#_Yz>JXZw9*1SV_O4goRhL?`v&mT z)ikD1Cc!DpCG+HBjhYl9$6k5x5IHJUrIvKPXXzrCf9z0Ga*i=r$tk zsoYRsK^Sd}gCz6qPK@!9q1&r>i6#o=)qt=$`Vu6etA>TO;4A_ZHZaif&Ye!WQvaBq zNbTFiaVQZY1yhD=(31$TJr9I39~}BT&$R}Pz|v7*}YJ*wl^8N3l|sU3W6g6 z&JwbcYWt#H=XX#?1a(Q&PrGw!zT^oZKCV{~qQ^d^-*HOyUY#{XlT|CSk42<9h7}^| zaQM3h@$Y7hhlS5JW$Sf|dsa^0hNL-s#Ig_`c)*)OVVn`~A()~cE0V~o z=IMBU3jZn80(w0I{7hH~wn8JJKT3dT`?~C{27+Y^6)|Y5flfT6EdiRxBE(2WjsifI zSOu*=C4~H%5n2BYxwjk1kVAeDa%QCNe|%lqq_Yu!)GO`l-4|z(!moodi1xZmLeg84 z{?HBWhksX1AzitV5NAf*L!cPkI*carga8w<+8WOZr?%2os1_T0^O;@cv`H$nbR({~ z>~uOLXP^2tI)P|3)JXLR9F7%fp5L2c9Ig7fPF%7F7I|lZ`1wCnbN%nD#7FmY9Wl#l zIP~zos5#>;M#;Y={`u$cI_|WzM`q8%!521@E|y zakk0IH&q%*{Emo;OB{OtGE!lCr^=?qlOgN_i)$WkZ=s;FZA#91$yIdFJdYnk&>Uej z6hojb6spk+E=`2byF*-vB%&^m=p&duN{qBl5BcAV>GHUEJgYo)7%#Z}Hrl z=M^Ab9Om32EYKA2_N1jKk%mV^7z|wae0tMH<93igw}LQhX>V_0G>Nrs^{0yQepyjQ z1(7P2?8=$2T&J1Yw5x|uAItr^fnTScn7kKG@$24VYTf_NdiO?D|HW~s`tKPWZ%kEE zBwi=TqEHULi21&0_uF;kEL99w&-;Hq`lmNFed0sg`u}jf?>4{9Z7{o5+Wi5Ak_je$ z^`<5M;|Bh7%JG_yiPAm`R5)D^ht=63ZUR(i2VVv;F&z7@5J}yYEH5ui;_$7K$r~F~ zC8GTgDawQdr0oJpO0@k0w>8G$Fc;GNw@+R=1}*B`WH&QUS5Y{GVqrx{^f^M{iGAeu zH;UxwL-S^Hjd9B%oNl)X@PJbAB^cmiu84411Ap7D;X}2pf}r2r6Vu{jzt$_+h~N0B z3r>WQ1y*$TWu7Xl`PAkG2ekbXHl7o^svPrYrmNMKoDUb7Gwl@1>{fSKK_hnDeqAz2$Kj(NXGpW zv(=u9^lR^etbit5a3ZyvGFF`4apQSw15_IwAuep@M}_%$_ePI2V=#JpQs;B5@yFiP9Wq!8w9+CS(3ZkV0)F$_CJQ^KgRYy4#$6-E<_LfhY$Wk zga2dsAYGXPj%& z?q&`*9#5yN0AK234fI9DniN`L6oe1?St0H#QrO|(2hZf>1qVF^=`ly; zJmn#=j_aBwSFnNS!(B+vv~0;~>UV`&f;Tt!vB~f5L)B777FY1~52G%Ff2+vzHmow* z<*ze6&CTW5=W8A9eUDbn-F~&lEx#A9ke0RKt%04BnGci{=J6 zl474t%*+B^P6gb|$!g_hLJ4v^A|19>6ko>E(IzDK2hlZk4Q_HKMZc54g*Q#BjccCU7o?y}|WjNgomY{Sbdl z@k}0rYqI!Pb92i9oiLZbE*BKMsyd?MY^jPrmeX7wcu0_+3s&UvthM}6g@ZVrrhUO< zb6ZMT!AW+OZ_A0pc_7Es9DmGmEi-S8)n=C8(l-f%krpQLD!K@Zd>9}nx7;jx8z`Kg zQL>`zcbJo1PI#QXqOhsK?%TlckR-bJXpLe4!ry(&E9W=@LqkKslXap4w^?#MZ$#vb zb>_%NXdA&ev^>yF=`c;;oz*Y9TJj-RdGF0u=h+N{C+F{rylys&}(Z$e^C9YGaPC4$| z$n)@_D}7YH3T@UVDAZ$Hl3btii*dkI1SMo;WwmT-qjEhy_-HDHYiMYU>1J1P_gYQw zNJZcNWl(B}k86mQ6@C(y1wArEt~>w=Ut?!yx0%|dfcaO;0~RrTgG~+d7Ng}V%fri? z68%@!T9@wbMFy90o=dY|!@@##hTgZIo3O3>deU)d(U_TeLLo?)?gW)k{L$DmAUXD_ z4KeOqmCi%-9dpuMAK#6LtU3)o!+9n~cU-I5*<}Jw_0jm^X7oKumq9AAyi^}OxLZBf z&8?RLFa6e|MQ6(?)DDlfAj=tC+}n}Ra;|MlDEXnSVDGUmx+CGXQW_I7NJqUcvhm1b zNDbl+`%f{u3vC_B`XedUQ%8z+uc%e#iV(=0r1*D#ujd+Wro$fhNy8pXBLfL6>Uv&M z>qr}N-j5ld6)mo~JW|}3Z)85W7V#K~n4Bzo=xjzZtCFKTvI{K+81`-5XDUuf>O||V znhm^J~SX|FC-fBID->mnM#Z(h833jb&|^tt2I2k}5VVplns9Qt%DRkGzHBtzNw2j$xRUZiXTh`O z&W+zy!~bwsx*KFZs^B0*I;=DfZr(j@WIpdLXH$Q2V%J*=*vf6T!4c~jT0{5nqmLz; zz%`yuH(pXz5$tjtWvLkA0(qJJ(CfNq!#t)&3^v|9U+(xOh5f}u5jZSdmJ%&VV{T&M z=M&?`;j!EBV;M-07_GuHQn@_EWs4EkA8f2pR(S5v)z^Jye`hGdprte*E#-{1ot+(m z4UG5T)Ee^BKB%-^GynlTGv&E`HFq>ADJ=!neh+pcsx^3mZ5)vpxBXt%@ll(Eu_Y30 z-5mod?3*}O2}?ON-uYwBb+JOps}syl!n+t?ka81`H*R3SnWveEW|aaC`gYD zADOD-2JA*xc!iSd$?8(KU89)0xWUdgW}jMgm%tdGmUfG99_8>J_WPTOl~te-8?Nq4 zh&fOetn!|@iT(1 z_zF9$kW$QJl9HJBR{QdntBi-I967yiA_$40A#BrfeX}PumHPsd%@`LY%s8shVj{eF zu3yt&PZpCL-N~AtpI_&m$MAOFY2%x~lg_%0>0e9+BNgipgx`XF$`)aWL5&iUuh)3oR9d zQ@Tunv0ND=Dc3~=t|I8kuxsC*P2Zk@{$OfFe0ryi%v>bUy1}5Wk~%L&=O0g(ogaOr zSsaARC_f1RJsP_`_l&G=Z*{*A7*F^x&N#eoMPZWI3wz2o!^?=1?q#v!~{g8hf z-`e)M(=)Ca)U^xi_l*uE=q^Tg6sZTLW@g?OelM>-u(tIm&%+?o#lZc#3tAG{Qy57Q z!9L#Q%4rtXJ6cx?SygU7->CEbFqXLf!2giDE64|ymNsJSeaXsrAXp&i-}r12Ip-6h3iVKS31OrAwvgtbq68N9sq1wk1b& zgyW6Zvjacqh4Jtg)g@;Q=3bK7=wvU@JYJLaU6o#=2Uk+bWAM z11^WsqjG)$zRv$A05{^C!Dlq9=b^f!rJNj|rp%(baK2-3Fw%w85W&V!sQan$if_1> z*8GoclLgr@`C!}ckm$F2dE{!2=%l3-WF#j~J}Pi1H>jJ?}k^JSHyKP^Rnn`3Kj;k}D;hpL)dUF@M##TR! zP^QG}L^~~5W9q)U8rR1#xLyg@)KFG<`GpC#6NNhSSW;4Qr*=E=4Y!EkEdY%_yU3>$ zjEK~ZHPOWQ*30S!Ew+{OGq{`G!AmQufmMXe!KHr3y7uC^w~Rp@wLXc_$~W(k4KZa` ztgbA#kGIvU^6yBjdzG4=`9L`3GCj9Vm4Mzz z#ke2i2#u%9+>dU%*k7uNfV)BcmX%q0#u(j{$F<~`y+aqBs0cGZgy9{(n6ck8DN?xp zaQJQ_!b>RBe5zyH!tw{}NV-MQ-?7~)O-;TZ$O9rUw?)@z_yh=NSRNk`T4-h{&vj?o z+_=8)y|{`3t8A?gnSYGV^2El*u@CWzEC4k#OA+_g+oDh7 zv{p-|2QsAaw-l8RXrTQNb`GY~u%YCd6dfFpKu*J0r0xFXLF5fV7?y@OY&FLgB4@?5 z6!(Rhgvto>XGMW*T;q8u66la4gc_k!L^Q~uCiTh7@wqOMcP{q|0daTRc_nRA%ikIx zd}JZih1}lkU=4I3NU~>L0B$ zxPk=+RBqEd0_Z9L;3AOY{_UDTq*T<41GXb&jIaf#y8I>J_5$lFjQ)&TYxUIaMUMDe z@BVP6%=o>9vk5*F(!wvJcx=8-e{v=qw)s^v4i+KtfRJh~`$|VrqQ%$<^F2(JcFQz2 z!zrTYd3#jTro>JkOqPv)p1dm!b@^*PRx_+0f%dT>+#;01Wsb7KM+9FTEa$j^ z6q%gxa|ipmCr9eOXFRQY)lsXW@H`&8BH%QQnp3AHj1hsX#kKh&$py*RTK6`8;}K900oq;yMGT3Ds|^%YE>DS}%z2jMeDh7M){J=a>EP-vLwQdlP>XChGajxMldM}e#*o+ke#!%(TyX?6hjbAjsQ6~k}Y z`0J}g%LOz9SJUT>@5f{>mq;@un9DWnd74vPl?P0pg7I{i`(o&$@>N2&onF{kEWM{j z+Mf3BD?{7lZ{FO+(<~Jt`U64Tm$%8@a@G3Oy?efE01){-48zRVpHiM1VSVKlTt5hJ zEeA)3m!niDWQ0L?=!NYxIQlbz4N6bB1$Ur1LUAsdOzQMq9`Hpjr3;- zT~PxRdeNsa@LiV|)qiy6coJG%7knwLo0ewh3->>5^ad^Z2SgMlNB?aZFLUQT8Erei z$Yzbfc%n3Af~I-{QZ=4qE+c%DpSP&)`w!W%k_o!8uas*6d%`k{BTb9K7W>rvW|)os zbz`i`2^|^XBN|z@)o+f4r7(0bn3XzEG)T15tb*QY-Cp$>&oGXrg+xdYUf$#Cl6FuP zEg_&K&G<}(%Wn5%kW(9%d?&l zwoZ~N;;`xD^0@~p_jbiIoBWUZpHq5h3&R?QM)hyxCs5EkI%(N;(AbRjol<(N^xmaa zo~R&XWN-cbvlKh&Wb!k;?;(!*$U@K6Yiu$ZGiQ#!7H}F&{RF9`+slJ*bDd|`x<9ad zvGuJqs^$K3YK%>H!>QA!2H5zW4=W{Ueb_Mn6`8r|A%0WeH?ET$Z)b`` zXk>dWq=i%D$f@%jzn9~`3M{-K0p^byYt`$ML{e&T*#L5(C&;!;-A8V}O zYtFHywF+8^aNdcMi(11J(rPv>lExB690lO~BV}@IDHjI22TyYwbNb49h;a5=q{{*g z0|gD;1|0~mxj^A{tBh9p*0di#3u=8gvddt7&s`akfbdMciu@gcOJkptX2(zPi!xAD ze8sjRAf_&sd)-g!p3w)m@_M_Hm=Kh|$Ejz_q zdxw>9?H^s_Gr&9`)@;lhtf4FUAUomw&8wuf^0eR&JctlvhP)mWw@Xc&d&P8#;ZxPk z*3X-U*L5mx!KgH%W|&XTJ%??^62UlLG|;~>m$)KoC)52sU?HVswUXNc*=*WcgN-ETf+ zfArZ!CISf)DqX+j3oLdquAyB0?vZ&HahdgvQuD-GBMDbjyH(Szqt;p0{?_|eceLF1 zU-b3dh!miWj=JRjhZ^1Jp0XeT9c`-Z1cB=^cUC)1wE zZk2vR2m;2E*;?Oz7}?J5R$<|WB?W*6mlZ}R;TJ#na_LKf=6LzQ6jnQ^f|i{qb<}!LM}lTpw;c;5($0oyUFM)&^5Dy7;wpB7t4m`WO`I{l1)6vjpaqh~ zEuP1pIM4x#P({-B94ruZvE~6+#-=t2!yLc68)-+Pn<xEXPQ!9f}K z(G}(62)U{X@i4uO&CG7dXj zsMk%_tpbi6Ztsr64eE^K;$}@S<+;opyY=NnL6nhM4^QvY!hLeNr(~I7?U%8r`;VRO zW4f*&I(lvMYbpw6AyBoUa+UGykpOy{|Jw$CU^xT=CU&uAAhzwgHlt?Kj*q3(fHIC+ zq2db%QA!2Vj!a_wr$Q~?yj7s38z&8eH=q*MXwMXB-C_bP#~p#o5Ksg!ZLbeFFnlhU zv+-L6omg}ed@2&cnY;aOEkJ4FWdk!gv})x3XO*!5ZV|c)7kgP@ANdDFujqoSTnjsw z-CA(*Dpgw@UBoQH^y}s3cLzNPuWgo!->0RtOCLDeOS;h>=!2cb)mtZmIF_w1Qi^W% zKVIGplJjknnF-=w@k6HKwcp@vdD3PR6B97|l-bq*r%$ba*FM(;f)twhBiKbg!k@7~eb9Eo-)Zp)eDm&A09T>oYVz{m*ieD> z!mHy8CC$pfT+u^i*pMmDD3tV%3rl-n+H5uGj6@I7|}J1tRjCAXE~DT9?YEHVW} zmO2{7sPnop9;J4#xTi9RF`m%R_f=Vec~UObbQA5XHv#_R9U`6 zI%yY*5I#Ec%(O2rkhdQA&M3(oSh1GvYa`-oXaYe5`E{$K~xcYU*mt# zus`C~>75mckqN2f72+m~y{}l>!U}BnUNz`_5t5cto@AwMHa;zmg?`>2uwwh6!d`xC zk!R6`>`Ko&7Um{0!c&XqnMuQh4^}MLPO_qoy~Q2lAEst0FFYZK=uvQ$y17{{{rN`U z|5S`o<3DXHTpfah*ZbHBWPX66AyoOBHavVh*@vn@-DC4X=8$rXi@bz{Kiw@J!r%XX z<<(^67mn7_316q33GfL(A(JLXi=8@{=exeGEW&but}rF8MdM6>T+25H@KQN{6tIf? zna1)or~4cgF~z?UZE~d+Inq)jB#@-RcinuR{~|p3LnW3vQy+@AU4hi+&b~eq zL#j7Oa`Wse5Dhke;>$h)Qg^Y%BTWsUMBJui-z?1!&<=5-}{{8r-ZoA>$WOmJQ zoM&Vd$krc$cj(SAM@Nb}k03d`ZWV{7HjnjZ7C#Dw31otQN;KzxdBwSp#Zxxi+h+hS zn)|3~of$N+5CLhyeyz0BlojGA4^+~q zXJ4LfQSq<3TrM-cHW)TByYixk)3)`Pan&{`RQpQ@QG}^Qi02dUU##$v>w~scXIP%M6s&C*^(L;UGO`7)dAh zH~RZ8U%RPcn_!_2(qk=GmcK>OwA9rZ`5O#g$`}hb>?^$^aVkXe`2y)jIk7vmCesD! zt-psw-~D|iCoJ7%Hgd}&`l^E0LiUP#slhv4#$m)R!bjTuT32bo)&p(99RVEckbYLr zik)uPP>^VfMXO`f%hPoSJdN`D;@G<^g)ihhi905xS|UFanmnmCv|Rm=U8{Fg@G!GK z7q!5v3rF92v>01e6i?&I*F<%d%&)L)q2C(S`R=0Ei|w?Hkpaar4R;kqovo5Wt%v6Y zlScg$Ib--A6=~U)cIw<_JYAHyKtJR$V#v!kZxi+6bOSZn`MXyl?4ppSD6x)^u77UF zDkSYqoT#89@eX0 zQ9Nq85MQ*4p%6|A<+Cyr$004+d*63Nhw>2?aX2(`#14fztjZz2#45GkVW}R}{_c)c zG0bi)Fmu7>mubtRoI3PzeLtX*KQG-#*Wm=8(pF-lSiL5rqm>qQHd_t&IK1|ITbN%) zuVru0LR4ytCY^T51uXT;=d~t}%6t#_{oSL98-K6QhxCf7QS|gZX(*MSIcJ`AWp__f zSisVI=YoCI%TESC0|V(Hp{*@l^|FI;E-W@ZIm+Ph^?m8QER((4{&2QRsnOKJIk{rb zFFLxpSoHDJFOb<8ot*9NkM20-KDHTw7&8>=$cNR7ecboP_jeu!FxsWxrfh4dtH}Lr ziL`X!;VqND^8%sAsaJ72k2NqEB94`_#M20<-9posy6(MV-_PZ)J!RkARK$<>Ufk2- z#Z=A+9yS6(PQSY0zxuWz8vSp(%jlybU()Xu>2hm~l)9z5s3vMn**`ub2>>?j?$G?Y zsrfhu?iLGEBn_E;XMB;G`S5Hrdd7JF`yb|tVM!BTiivXjSh-&Rlh#Rs6QuQv`;^s? z$G#zZmhLPJ`v!)6;pu8pdXGX_4q{$Mx`dq-E)5 zVHsg{?sc{b3L=lv@sfT?T6=p>_mzG&?G$95s0*yl;?Go#WDsS5P#D?5AK~hR<`EGL zqK*E#uTMr=Iv$2YZ3Z^i%#YS zAND-H?z;T?e2c7x#dMH{?3Z*`wAya7cF=Tin}4V1kXwNKZNBKL=R_`)(N*>y%{R8+ zHCE_3X&@iMg+8*cfI_AYt-^F$qEuW{OYR3yZ7NW(pugWe-@ z_M=s9FaFzS&e^?hRqCx2T2m-(?CHa2q^0;$!p`AllImR9Jb%(GbjP${hb+2t>41C9&5Yf+wC0k=PV6TE)6^17Q{$nZ#I*S|gf z+~3hq!ceVW?7P49+xaEBnK^}RzJBGRjVyk+V}4yu&7g|`$0`;C%8~i9zeNXTGm6cJ z_1Uu_{TW=@#y&rlq*4b40VHCbI$1HPxPjr|o}Q`5#EiCEX{orDu<@<+-u6!-mpP7# zgJw^@vTGH6DPmAmD8Y@`W5fy=TQ1}CDy!yKwEs1cv0h(p=~lR^o||{M$~Xk^+E9uK zu2x5r&T*z;UB1(gmrg4Mht-RH#j{}M5aq?t-_<$!i`*I!fcmat^CEd_o@zAd@z@Hc4A(1_u~TML zz&yv}i+u)B0-4|+1X8~#$qFdbx{9=5$;k?k%cfLBA=XWyT@{S&2~dA zX>GPxvsK#VX8EiStbJ^Cuk|oq@Q|}%R*y=aPHzRJ=T*_8+Eb{*^)UTUTLlr+FAXxE z%wNGFG(;WGjP_hq@J|-b+L|FzS;a?Wz)B@+xK4opR)(j1#Qw02zKPu6b6ID%U+(zb zg$n^?udVr}w>(QeT%64ltaR}y+OB}UiI0uE|wSL-`SI_w1TOLm3$&JDNpPGQA_o(FPIUP zSwOUUC}ZUNJnID$W~Q^xqMma&O%1L0Z+|T_O zcgqXW(Z`w$?ZQQ7jc_z@IhTkij_B%mU2+|JU2^`c`WPN()j8s}PnGUWkOP*L2Bj&} z-A!|%d$`8&`nXQ@CPl@*0jt;0=r4>NL0AY765?0z2c}vnY~} zWWX;H^QNX<32nW_tzuUPSL#mq#Wxb$qK+&?t-2n5GA&55{i7g7a%8W_XLa-g_gU|q z$0z$34Q?oYP2O8~BWe$_^VqLOCtaLb6;2@%+|=XEKCt?WEO3+UEGK(*7jAD;zM|4C z0JO|yE)cNq6nA%jRZ9ORschEr)9CLBRortw zLjA_CMTsUk31#F^gzzy!;W%VwCvip`$~ZC;QFPWJ+3OI_;cRC_xw5yjDJw3NmF@dJ zclG`L2jAB(-tM0F>v>*#Jf4UAep1~Z&Io&eXQV4>R^+#x*05b0^%z|KZ zWd{wt&9_8G>8I1I();g^08@=4<1Ufn>U|q1Y`;)q(JB#S@u3sRu^`R+n;hk)y!jS> z&)IwWbA~o{mf|(Po+doLdc6_cGfI3%8aQ|sP zkQHUg>DP)cwCQcJyyZICsO@pE;dy7R@5a(evTVEf1t-QoR7lq4yzO91ZwW0WB@K=Y zSE^4`ty;AEc=Epx3cQGH;0+c5&3rb=C%!8jNMJN|YZy}0@6Yc+=vq3C4}W;ET`}=w zpH21#up3CDKqTjk{WaU~ASuqF;cU-i0E`L0dn|qDI#fzMN6rxW!7wOmVEdbe-yxgs zY7<8SKZB4ix6=;JS0i@K7Z!?BzX^SlZP|L6y=zg&;av`4A4(z}p6C{Nr{-C`5&CxB z!@O1vh$mGYAz^^#cL5bsq2ap$AQ>Jiv4`8cu}850s&Nk~JH_O?S}lm^uHt@}^VtLh zNvM4m&*AAMF>xr?NW){>oau>$xdR6uI5Nj~89Nn+yZbQE`WHG{oqozv@_5 zD5*E&PTynSnb5axTS0SqTKd97sp9U^9Q}jZ$Hgh}CEClrMZE|z_5R1%x(-g7xekGR zw}j>Lqr=&LNatAf)vjXwB$p9DCG!3}2w-OAG!RDU7JBhR@CZknv3Xr?;1Rh)YFWd^Td{fUQb-@ zFLH{0WIT`-7uN@$^5(46JbLCOWtP_-ZT{Sd0Xx2V^i)K5NMN{u{_>**VnyDK{oGR- zs*CK#;uYm^AOWuZ)1WDi8Ns|Ox4N=VrsXPYo|pat=2{@4<#(W4W3dY9$Nt`f{(N2L z`500uN9(kYHBL%gLL5%oFdHtiP6W)n<-XLLQ-nzr5aa*^WE=-5G1|D!3G)BFCxgEz zwb%fjt^FxQ74XmZT3Rpu!1YxQWvSL z@$Y#Y?FxS;B;rBKHwUNG&opUnJT}iYI+7cL#Sf1I1FxUqmGYofX*T|Gm8f!rcu!<`Ukmk({$`>?XcEI)u`evcS^U^%R|A|r2 zDx~l}VHp3$FB$?0*6!BsI#&PWEBEHp^kS(=_ss>TjYxpn4UM~8Xe{Ht4|#QobHv$+=1QC zBq4o@P#NH7lzI)oe+}hgSi}%2zaN4?vsPiz@^WUT4_Dw#fFPv&Ji)pt~K^&IXPD`w|Nm!lbHA;MzA3 zMw?L+z5-V3vOL_+01Cyht56x(kA8IkbFN$7BVDdDBVmmS1AnV!8XC(MlYFo$!MPMd zZz_$;N1y#N$?ST)9BMLGDxkIDmwusim`Wlz5BwFXMqtb_@Um^qm}l>)gK=%_e^>K> zZZ%c?wWw-AxuLEj< zq*IGb8X6H;rnb&|k~?%EKEZ0G24QoT#!|4CQC@-R+pv z7uOq58~%p=K!e&)rmj-^`O8%2jki?5uMRkTya|sl2_= zsQN(o$}QTb#CF<2$>a*S^4E#^ ze=0JtXmnR{jZFRngB8WjUgFD9#aII;jSu0O>Rlm0cmWAnBf272zUt?|;AFRMep@$V zDP%)z5lcQ2LWUTyd?IGAOO+jO>MHzVjVLd1xEyYj+(x+brJ$GhyZ|b%3AY>#gU9NKCeh$=0#3@?-*5a>`^nBs}19U=oP?W;p9L z4sL^;RAcu){-#meFMA9DX;9jT_88T=5=esQ)N#e@Z$Sla4wzUEC;YEg7CrQS%R`dR z)HWzgkwJ9x)#~vrF}%ZMeDNCBOUU?W7}*XauoTd~dbeEBBGNTk=a#{2z4WSI^5sCm znzg6Ayc;F4q$ki{#@8qV6}E6XXa^V0%Y4wGTW_u-X4z%LX$#+rIpdLgW}OLYab*TW z?%{8#Xi?Wk%2jY7cR215epFS=X^nNq|5@w<-GCAjUgJ|AY~Pd5x!ORdO4YI%LT9NU zgAbAQgmwScSa~n(neSqZ6lwsG55A6LcMNR=Dl7_Oe~!=qg{p7~@=!D*BTMp@rR_w0 zFCa0vwG85%vyZX$NP_i6pAo8Wk92z>eMw?SCRKb^Ufkkd4rlN{tca0c&>wTiaPK;1 z^G&0IRL5B=a!bLH@rP&lXl~m&VAWN2-0c`1$+)_>^4u9N%icWYV!V~aNzG9{ho{=< zy>G4V0RIR3Nfw01JYh^ItO12q{iCY3i%B6p(F09CJnWzY3;%)c>TK59lxpBy(W~$CswCvM5}^eVk5C=VQv^r|ZLP1^qj# zwbmscqYA=gzial5*#4R&ABRyTozg!t^fN2+Jd_!)+K4*htut|3Z2Ki7cy>*H^4XsQ zWN8hK;CG6hFVfcG9NTq`N3Ez4`tz$;(E>d4iI5kchf<&GRCo+IMKr9J>6wc_#K-nF zD^KlVL>=4Ps8+3TstAQ!Y$qFIOh$1J=jB-q?^-B)y zm^hBP8t0s>qcKo28=g|55J8x%POwu)wjon$Afv~tkhsO0Sn?l90pJguJb0=fc{Bf- z5n*_aG^hD!G@AIM8(6=Nm0yNvwTuxez=6*8Bvsp#%a@;=lpk4=&Kkt@yPzNc@0}aqCs8elY1~J9<DAv} z&X$?l>C1vF!9J7=fLR$Od>)P}uwIkK(X zr}j~jQhK4rs*th;u#-bsqbd^25NkJ700xhYEe1LSEwvEe&ddP8b+~G)eY9Ay!|B~x z+?H4Sf5ig=ZPqhcy(={}z9tlJ<&R_9ma;bSXdV8F2o)nFm=fFKcOi9;gr9oQs_X7G z*~(d>v$lMIcO`Vott)mlhVHae9pYCu`ChC8$tkxcwZ9Cr?`o^rHg|eYj{uiVYUmEsfn( zgd}styRPmHJ6nFzPEXYnFuOK2vrg+kq4lbB##QZd$B0HzU+vK&NY4*`OMT{ZJJzRh zx;9L^CFnn=e-Arf2zn`P;?7U(p8d-ru(4mc8T-xqp^Z4;z5Q~7ZRQH*??Ic@N?RqY&-t6mXrs?-@ zeBH5yaMrb49lUj0e^8C)=pa}(vQ^b!rHA_ARAYKUwc;hvxYo+H}D`_dK5U@Nh7sGPdK8I;HNo~9v*J@=eVyEvyt)-#W?(Df5d57&8WsFh5|09rKC90d?zWznz%Lf+TpW&^6UsMKRWY&(^t4Zyw!OF-Qy-6OJ`dUKYp_BzGn$fGPvDc56sN`I^TO z4;}TE5*AF77y=<8Y*GrO%R|LZO&c&d#D+A?&9S(U-F5&1xV!)ddg-Leu(`5dE}5dWru8+F1=w$o*4U=|0?~%sXE> zxwq`7mHfO8DYu+=Z7HM65c|mxo2j4c7SzLBJFUO*?u+6aWl>#<67Ume^q#r& ztV@)Q1sbroL?~0x%Z#fzSAR)urx%G{@8Mlv&93<-+PDSyb8cZy$qo}SW8R)O*1LR-wbniPpL zJ_t69*h!DtiYJA;*wn<4QiSME|deS2W|n4beC zw#;B4*!w^RhYPj(qz!N%lJ3AHbtAZJb|-&Uv#u5K`oTRQ6AAmkaR7(DSgxkV8yc?` z&>dZKv6)1y%m#n%Cs=+$_~tv2Jm)d0?a|zU*)Xn52wTLJtLMfl(tYfWd~P;|M#FX5QmpZ;>n-%1Q3 z&U*3T1>!X$lrHv|SB5i8(P^6hSgQT%HoM?_FV>q%Bu|8h7y1vDn;>~3X>h4Ji_g+q zuV-FB&5f{c5Et=qDt@*#Bf|2E4m535;@f+2o7$3D!z>aKvzE$6i^=o2OpKAd!jiUof#VZS&z6t;1&`_HXgVym+gJa- z%4nre_h%cTNKoWvl$+A}W<6@{6`I@ZuSmhr0?}=VVtdRyR*|0nH<^mfx6a(LJ-3^Y z|JIlG$_PIy)?HaW%x74o$zQ6(aq+iK7#r#GPRh#sm!3yP7U1CNudT$BF>$q)NPYFp9WQFFH{wZA2=EuvvFreCG#VUs8lJ#UFoDo6VNfmdn z6jU3)CzV`X|2W6J~(>JlE4+VBao~K3`B6rl`?ZWondf9j({u9<7HowwuWAnZ76Ok4X+4u7z(4BA651ZIDG;ajqXz2Bs z7gZJ=(duSjV(4i*13}J1VX4s}e!TOt|A?0ER8!a0SzE03?0tk7)3pZ$o^59+StsqgGTQqcEn}0Ms z9NvAt*w);Rg{iT^p7DiMufcOv_?IWF+YLX@Q3LX`_RI6RAJz9J$hP*3{URMQ!R+>K zXd)yYyc>dsH_cHC*=OkkknulMGf%o@qNPobA5tLZ4#`6m7n>m3`1ZI<;iEe99& zXdYz42~qTR^?t3s^V_KXideaszeB;+km1K+Tb&P0>WzUKv4oVuW62;?dg@|LR3WCS zt7X&*orrlbBYn8n3G|81OF@M}otl_s&bF?#`E*hm4~#)5!_{uC>C({`~z{cm0ogn?tlzqLRzK zmim;ju$J?%BKS^t)U8=*V>7<_<3uQhG=-F(w|f{o-M7<52jaxYt3Mc$QCbk7Oo;_mgDm$nDqpNhgMH^St-9PnONw+LEkJpShHX=h2bj!qN@4*&xc=fB7C_zeL zs9DX#5^45iy4`VI@RCE)!E*wFNe+;pMi{${L~X&!XL&IL7PkDi$lC*Ife$?>YJ1f= zfw%UWU3%;zJG)gDC!o#t3BgnyF7lsL+XwY}2_B8C&JCup@}4KguH{jh6}l~yOMQV$ z*%G7f2^K^b3u;&yD5d182Q`FNdum}_$AWhsGiX*ez_9t!3vc?qc6o6l*Z7T-ZNl!f z8@VPRY1P&6PLvuzq5C*)*fMPF31$eK88+~wR^0xH<8QXgDaj#FVC&#uF|5~AP)lw| z?>UYo0alO$!-UbQ*`)C?#@s8|& zD_o3<5f_!=#fzcb)!cJL#x-G?gs);@18*hRQ-{i}_jk8mesceB?chlKLrgGTJnP}+ O2UJ;8348Bx!2bZu)GsLj literal 0 HcmV?d00001 diff --git a/addons/easy_charts/icon.png.import b/addons/easy_charts/icon.png.import new file mode 100644 index 0000000..8143974 --- /dev/null +++ b/addons/easy_charts/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-85017c6eecaf83ace12c82c530e61e9d.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/easy_charts/icon.png" +dest_files=[ "res://.import/icon.png-85017c6eecaf83ace12c82c530e61e9d.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/easy_charts/plugin.cfg b/addons/easy_charts/plugin.cfg new file mode 100644 index 0000000..6fc27ec --- /dev/null +++ b/addons/easy_charts/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="EasyCharts" +description="" +author="Nicolò \"fenix\" Santilio" +version="1.0" +script="plugin.gd" diff --git a/addons/easy_charts/plugin.gd b/addons/easy_charts/plugin.gd new file mode 100644 index 0000000..ac288f3 --- /dev/null +++ b/addons/easy_charts/plugin.gd @@ -0,0 +1,12 @@ +tool +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")) + +func _exit_tree(): + remove_custom_type("ChartContainer") + remove_custom_type("ChartContainer2D") + remove_autoload_singleton("Utilities") diff --git a/addons/easy_charts/templates.json b/addons/easy_charts/templates.json new file mode 100644 index 0000000..cdf9d1f --- /dev/null +++ b/addons/easy_charts/templates.json @@ -0,0 +1,44 @@ +{ + "default": + { + "function_colors" : ["#1e1e1e","#1e1e1e","#1e1e1e","#1e1e1e"], + "v_lines_color" : "#cacaca", + "h_lines_color" : "#cacaca", + "outline_color" : "#1e1e1e", + "font_color" : "#1e1e1e" + }, + "clean": + { + "function_colors" : ["#f7aa29","#f4394a","#5a6b7b","#8fbf59","#504538","#B7A99A","#00D795","#FFECCC","#FF8981"], + "v_lines_color" : "#00000000", + "h_lines_color" : "#3cffffff", + "outline_color" : "#00000000", + "font_color" : "#3cffffff" + }, + "gradient": + { + "function_colors" : ["#F7AA29","#B8A806","#79A117","#2C9433","#00854C","#006571","#2F4858","#2a364f","#27294a"], + "v_lines_color" : "#64ffffff", + "h_lines_color" : "#64ffffff", + "outline_color" : "#64ffffff", + "font_color" : "#64ffffff", + }, + "minimal": + { + "function_colors" : ["#1e1e1e","#1e1e1e","#1e1e1e","#1e1e1e"], + "v_lines_color" : "#00000000", + "h_lines_color" : "#00000000", + "outline_color" : "#00000000", + "font_color" : "#00000000" + }, + "invert": + { + "function_colors" : ["#ffffff","#ffffff","#ffffff","#ffffff"], + "v_lines_color" : "#3b3b3b", + "h_lines_color" : "#3b3b3b", + "outline_color" : "#ffffff", + "font_color" : "#ffffff" + }, +} + +