├── addons └── graph_2d │ ├── custom_nodes │ ├── axis.gd │ ├── coordinate.gd │ ├── grid.gd │ ├── legend.gd │ └── plot_2d.gd │ ├── custom_refcounted │ └── plot_item.gd │ ├── graph_2d.gd │ ├── graph_2d.svg │ ├── plugin.cfg │ └── plugin.gd └── examples ├── plot_sint.gd ├── plot_sint.tscn ├── single_plot.gd └── single_plot.tscn /addons/graph_2d/custom_nodes/axis.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | var default_font: Font 5 | 6 | enum { 7 | POINT = 0, 8 | LABEL, 9 | } 10 | 11 | var vert_grad: Array # [Vector2, String] 12 | var hor_grad: Array 13 | var x_label: String 14 | var y_label: String 15 | var show_x_ticks: bool 16 | var show_y_ticks: bool 17 | var show_x_numbers: bool 18 | var show_y_numbers: bool 19 | var show_vertical_line: bool 20 | var show_horizontal_line: bool 21 | 22 | 23 | func _ready(): 24 | name = "Axis" 25 | default_font = ThemeDB.fallback_font 26 | var x_label_node = Label.new() 27 | x_label_node.name = "XLabel" 28 | add_child(x_label_node) 29 | var y_label_node = Label.new() 30 | y_label_node.name = "YLabel" 31 | y_label_node.rotation = -PI/2 32 | add_child(y_label_node) 33 | 34 | 35 | func _draw() -> void: 36 | if vert_grad.is_empty() or hor_grad.is_empty(): return 37 | 38 | var topleft: Vector2 = vert_grad.front()[POINT] 39 | var topright: Vector2 = Vector2(hor_grad.back()[POINT].x, vert_grad.front()[POINT].y) 40 | var bottomright: Vector2 = hor_grad.back()[POINT] 41 | 42 | if show_x_ticks: 43 | for grad in hor_grad: 44 | draw_line(grad[POINT], grad[POINT] + Vector2(0, 10), Color.WHITE) 45 | 46 | if show_x_numbers: 47 | for grad in hor_grad: 48 | draw_string(default_font, grad[POINT] + Vector2(0, 20), grad[LABEL]) 49 | 50 | if show_horizontal_line == true: 51 | draw_line(hor_grad.front()[POINT], hor_grad.back()[POINT], Color.WHITE) 52 | 53 | if show_y_ticks == true: 54 | for grad in vert_grad: 55 | draw_line(grad[POINT], grad[POINT] - Vector2(10, 0), Color.WHITE) 56 | 57 | if show_y_numbers == true: 58 | for grad in vert_grad: 59 | draw_string(default_font, grad[0] + Vector2(-35, -5), grad[1]) 60 | 61 | if show_vertical_line == true: 62 | draw_line(topleft, vert_grad.back()[POINT], Color.WHITE) 63 | 64 | get_node("XLabel").text = x_label 65 | get_node("YLabel").text = y_label 66 | get_node("XLabel").position = Vector2((bottomright.x + topleft.x)/2, bottomright.y + 20) 67 | get_node("YLabel").position = Vector2(5, (bottomright.y + topleft.y)/2) 68 | -------------------------------------------------------------------------------- /addons/graph_2d/custom_nodes/coordinate.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Label 3 | 4 | func _ready(): 5 | name = "Coordinate" 6 | anchor_right = 1 7 | anchor_left = 1 8 | offset_right = -20 9 | grow_horizontal = Control.GROW_DIRECTION_BEGIN 10 | 11 | text = "(0.0, 0.0)" 12 | -------------------------------------------------------------------------------- /addons/graph_2d/custom_nodes/grid.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | var vert_grid: Array 5 | var hor_grid: Array 6 | var grid_vertical_color: Color = Color(1,1,1,0.3) 7 | var grid_horizontal_color: Color = Color(1,1,1,0.3) 8 | 9 | func _ready(): 10 | name = "Grid" 11 | 12 | func _draw() -> void: 13 | for line in hor_grid: 14 | draw_line(line[0], line[1], grid_horizontal_color) 15 | 16 | for line in vert_grid: 17 | draw_line(line[0], line[1], grid_vertical_color) 18 | 19 | -------------------------------------------------------------------------------- /addons/graph_2d/custom_nodes/legend.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | var layout := VBoxContainer.new() 5 | 6 | func _ready(): 7 | name = "Legend" 8 | layout.position.x = 10 9 | layout.position.y = 20 10 | add_child(layout) 11 | 12 | func update(labels): 13 | for child in layout.get_children(): 14 | layout.remove_child(child) 15 | child.queue_free() 16 | 17 | for label in labels: 18 | var l = Label.new() 19 | l.text = label.name 20 | l.add_theme_color_override("font_color", label.color) 21 | layout.add_child(l) 22 | -------------------------------------------------------------------------------- /addons/graph_2d/custom_nodes/plot_2d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | var points_px := PackedVector2Array([]) 5 | var color: Color = Color.WHITE 6 | var width: float = 1.0 7 | 8 | 9 | func _draw() -> void: 10 | if points_px.size() > 1: 11 | draw_polyline(points_px, color, width, true) 12 | -------------------------------------------------------------------------------- /addons/graph_2d/custom_refcounted/plot_item.gd: -------------------------------------------------------------------------------- 1 | class_name PlotItem 2 | extends RefCounted 3 | 4 | ## PlotItem offers properties and some methods on the plot. 5 | ## 6 | ## You do not directly create a [b]PlotItem[/b] type variable. This is created with the method [method Graph2D.add_plot_item]. 7 | 8 | ## Plot label 9 | var label: String: 10 | set(value): 11 | label = value 12 | if not label.is_empty(): 13 | _curve.name = label 14 | get: 15 | return label 16 | 17 | ## Line color 18 | var color: Color = Color.WHITE: 19 | set(value): 20 | color = value 21 | _curve.color = color 22 | 23 | ## Line thickness 24 | var thickness: float = 1.0: 25 | set(value): 26 | if value > 0.0: 27 | thickness = value 28 | _curve.width = thickness 29 | 30 | var _curve 31 | var _LineCurve = preload("res://addons/graph_2d/custom_nodes/plot_2d.gd") 32 | var _points: PackedVector2Array 33 | var _graph 34 | 35 | 36 | func _init(obj, l, c, w): 37 | _curve = _LineCurve.new() 38 | _graph = obj 39 | label = l 40 | _curve.name = l 41 | _curve.color = c 42 | _curve.width = w 43 | _graph.get_node("PlotArea").add_child(_curve) 44 | 45 | 46 | ## Add point to plot 47 | func add_point(pt: Vector2): 48 | _points.append(pt) 49 | var point = pt.clamp(Vector2(_graph.x_min, _graph.y_min), Vector2(_graph.x_max, _graph.y_max)) 50 | var pt_px: Vector2 51 | pt_px.x = remap(point.x, _graph.x_min, _graph.x_max, 0, _graph.get_node("PlotArea").size.x) 52 | pt_px.y = remap(point.y, _graph.y_min, _graph.y_max, _graph.get_node("PlotArea").size.y, 0) 53 | _curve.points_px.append(pt_px) 54 | _curve.queue_redraw() 55 | 56 | 57 | ## Remove point from plot 58 | func remove_point(pt: Vector2): 59 | if _points.find(pt) == -1: 60 | printerr("No point found with the coordinates of %s" % str(pt)) 61 | _points.remove_at(_points.find(pt)) 62 | var point = pt.clamp(Vector2(_graph.x_min, _graph.y_min), Vector2(_graph.x_max, _graph.y_max)) 63 | var pt_px: Vector2 64 | pt_px.x = remap(point.x, _graph.x_min, _graph.x_max, 0, _graph.get_node("PlotArea").size.x) 65 | pt_px.y = remap(point.y, _graph.y_min, _graph.y_max, _graph.get_node("PlotArea").size.y, 0) 66 | _curve.points_px.remove_at(_curve.points_px.find(pt_px)) 67 | _curve.queue_redraw() 68 | 69 | 70 | ## Remove all points from plot 71 | func remove_all(): 72 | _points.clear() 73 | _curve.points_px.clear() 74 | _curve.queue_redraw() 75 | 76 | 77 | ## Delete instance 78 | func delete(): 79 | _graph.get_node("PlotArea").remove_child(_curve) 80 | _curve.queue_free() 81 | call_deferred("unreference") 82 | 83 | 84 | func _redraw(): 85 | _curve.points_px.clear() 86 | for pt in _points: 87 | # print_debug("Plot redraw %s" % pt) 88 | if pt.x > _graph.x_max or pt.x < _graph.x_min: continue 89 | var point = pt.clamp(Vector2(_graph.x_min, _graph.y_min), Vector2(_graph.x_max, _graph.y_max)) 90 | var pt_px: Vector2 91 | pt_px.x = remap(point.x, _graph.x_min, _graph.x_max, 0, _graph.get_node("PlotArea").size.x) 92 | pt_px.y = remap(point.y, _graph.y_min, _graph.y_max, _graph.get_node("PlotArea").size.y, 0) 93 | _curve.points_px.append(pt_px) 94 | _curve.queue_redraw() 95 | -------------------------------------------------------------------------------- /addons/graph_2d/graph_2d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name Graph2D 3 | extends Control 4 | ## A graph for representing data on a two-dimensional axis system as plots 5 | ## 6 | ## Graph2D allows you to create and display several plots simultaneously.[br][br] 7 | ## To add a new plot, call the [method add_plot_item]. It returns an object of type [PlotItem].[br] 8 | ## To add points to the plot, call the [method PlotItem.add_point]. 9 | 10 | #region Export variables 11 | 12 | @export_group("X Axis") 13 | ## Minimun value on X-axis 14 | @export var x_min: float = 0.0: 15 | set(value): 16 | if value < x_max: 17 | _x_step = _get_min_step(value, x_max) 18 | x_min = _get_min_value(value, x_max, _x_step) 19 | _update_graph() 20 | _update_plots() 21 | ## Maximum value on X-axis 22 | @export var x_max: float = 10.0: 23 | set(value): 24 | if value > x_min: 25 | _x_step = _get_min_step(x_min, value) 26 | x_max = _get_max_value(x_min, value, _x_step) 27 | _update_graph() 28 | _update_plots() 29 | ## Label of X-axis 30 | @export var x_label: String = "": 31 | set(value): 32 | x_label = value 33 | _update_graph() 34 | ## Shows ticks on the X-axis 35 | @export var show_x_ticks: bool = true: 36 | set(value): 37 | show_x_ticks = value 38 | _update_graph() 39 | ## Shows numbers on the X-axis 40 | @export var show_x_numbers: bool = true: 41 | set(value): 42 | show_x_numbers = value 43 | _update_graph() 44 | ## Shows line on the X-axis 45 | @export var show_horizontal_line: bool = true: 46 | set(value): 47 | show_horizontal_line = value 48 | _update_graph() 49 | 50 | @export_group("Y Axis") 51 | ## Minimun value on Y-axis 52 | @export var y_min = 0.0: 53 | set(value): 54 | if value < y_max: 55 | _y_step = _get_min_step(value, y_max) 56 | # print_debug("y step: ", y_step) 57 | y_min = _get_min_value(value, y_max, _y_step) 58 | # print_debug("y_min: ", y_min) 59 | _update_graph() 60 | _update_plots() 61 | ## Maximum value on Y-axis 62 | @export var y_max = 1.0: 63 | set(value): 64 | if value > y_min: 65 | _y_step = _get_min_step(y_min, value) 66 | y_max = _get_max_value(y_min, value, _y_step) 67 | # print_debug("y_max: ", y_max) 68 | _update_graph() 69 | _update_plots() 70 | 71 | ## Lable of Y-axis 72 | @export var y_label: String = "": 73 | set(value): 74 | y_label = value 75 | _update_graph() 76 | ## Shows ticks on the Y-axis 77 | @export var show_y_ticks: bool = true: 78 | set(value): 79 | show_y_ticks = value 80 | _update_graph() 81 | ## Shows numbers on the Y-axis 82 | @export var show_y_numbers: bool = true: 83 | set(value): 84 | show_y_numbers = value 85 | _update_graph() 86 | ## Shows line of the Y-axis 87 | @export var show_vertical_line: bool = true: 88 | set(value): 89 | show_vertical_line = value 90 | _update_graph() 91 | 92 | @export_group("Background") 93 | ## Background color of graph 94 | @export var background_color = Color.BLACK: 95 | set(value): 96 | background_color = value 97 | if get_node_or_null("Background"): 98 | get_node("Background").color = background_color 99 | ## Shows horizontal grid 100 | @export var grid_horizontal_visible = false: 101 | set(value): 102 | grid_horizontal_visible = value 103 | _update_graph() 104 | ## Horizontal grid color 105 | @export var grid_horizontal_color: Color = Color(1,1,1,0.3): 106 | set(value): 107 | grid_horizontal_color = value 108 | _update_graph() 109 | ## Shows vertical grid 110 | @export var grid_vertical_visible = false: 111 | set(value): 112 | grid_vertical_visible = value 113 | _update_graph() 114 | ## Vertical grid color 115 | @export var grid_vertical_color: Color = Color(1,1,1,0.3): 116 | set(value): 117 | grid_vertical_color = value 118 | _update_graph() 119 | 120 | #endregion 121 | 122 | #region Private variables 123 | 124 | const _MARGIN_TOP = 30 125 | const _MARGIN_BOTTOM = 30 126 | const _MARGIN_LEFT = 45 127 | const _MARGIN_RIGHT = 30 128 | 129 | const _Graph2DAxis = preload("res://addons/graph_2d/custom_nodes/axis.gd") 130 | const _Graph2DCoord = preload("res://addons/graph_2d/custom_nodes/coordinate.gd") 131 | const _Graph2DGrid = preload("res://addons/graph_2d/custom_nodes/grid.gd") 132 | const _Graph2DLegend = preload("res://addons/graph_2d/custom_nodes/legend.gd") 133 | 134 | var _plots: Array 135 | 136 | var _x_step: float 137 | var _y_step: float 138 | 139 | #endregion 140 | 141 | func _ready(): 142 | var background = ColorRect.new() 143 | background.name = "Background" 144 | background.color = background_color 145 | background.anchor_right = 1.0 146 | background.anchor_bottom = 1.0 147 | add_child(background) 148 | 149 | var plot_area = Control.new() 150 | 151 | plot_area.name = "PlotArea" 152 | plot_area.anchor_right = 1.0 153 | plot_area.anchor_bottom = 1.0 154 | plot_area.offset_left = _MARGIN_LEFT 155 | plot_area.offset_top = _MARGIN_TOP 156 | plot_area.offset_right = -_MARGIN_RIGHT 157 | plot_area.offset_bottom = -_MARGIN_BOTTOM 158 | add_child(plot_area) 159 | 160 | var axis = _Graph2DAxis.new() 161 | add_child(axis) 162 | 163 | var grid = _Graph2DGrid.new() 164 | add_child(grid) 165 | 166 | var legend = _Graph2DLegend.new() 167 | plot_area.add_child(legend) 168 | 169 | var coordinate = _Graph2DCoord.new() 170 | plot_area.add_child(coordinate) 171 | 172 | resized.connect(_on_Graph_resized) 173 | plot_area.resized.connect(_on_plot_area_resized) 174 | 175 | move_child(grid, 0) 176 | move_child(axis, 0) 177 | move_child(plot_area, 0) 178 | move_child(background, 0) 179 | 180 | _update_graph() 181 | 182 | func _input(event: InputEvent) -> void: 183 | 184 | if event is InputEventMouseMotion: 185 | var plot_rect: Rect2 = Rect2(Vector2.ZERO, get_node("PlotArea").size) 186 | 187 | if plot_rect.has_point(get_node("PlotArea").get_local_mouse_position()): 188 | var pos: Vector2i = get_node("PlotArea").get_local_mouse_position() 189 | var point = _pixel_to_coordinate(pos) 190 | get_node("PlotArea/Coordinate").text = "(%.3f, %.3f)" % [point.x, point.y] 191 | 192 | ## Add plot to the graph and return an instance of plot. 193 | func add_plot_item(label = "", color = Color.WHITE, width = 1.0) -> PlotItem: 194 | var plot = PlotItem.new(self, label, color, width) 195 | _plots.append(plot) 196 | _update_legend() 197 | return plot 198 | 199 | ## Remove plot from the graph. 200 | func remove_plot_item(plot: PlotItem): 201 | # remove from plot_list 202 | var new_plot_list = _plots.filter(func(p): return p!=plot) 203 | _plots = new_plot_list 204 | 205 | plot.delete() 206 | _update_legend() 207 | 208 | ## Remove all plots inside graph. 209 | func remove_all() -> void: 210 | for p:PlotItem in _plots: 211 | remove_plot_item(p) 212 | 213 | ## Return number of plots 214 | func count() -> int: 215 | return _plots.size() 216 | 217 | 218 | func _pixel_to_coordinate(px: Vector2i) -> Vector2: 219 | var point: Vector2 220 | point.x = remap(px.x, 0, get_node("PlotArea").size.x, x_min, x_max) 221 | point.y = remap(px.y, 0, get_node("PlotArea").size.y, y_max, y_min) 222 | return point 223 | 224 | func _update_graph() -> void: 225 | if get_node_or_null("Axis") == null: return 226 | if get_node_or_null("Grid") == null: return 227 | if get_node_or_null("PlotArea") == null: return 228 | 229 | # Update margins depend of axis labels 230 | get_node("Axis").x_label = x_label 231 | get_node("Axis").y_label = y_label 232 | get_node("Axis").show_x_ticks = show_x_ticks 233 | get_node("Axis").show_x_numbers = show_x_numbers 234 | get_node("Axis").show_horizontal_line = show_horizontal_line 235 | get_node("Axis").show_y_ticks = show_y_ticks 236 | get_node("Axis").show_y_numbers = show_y_numbers 237 | get_node("Axis").show_vertical_line = show_vertical_line 238 | get_node("Grid").grid_horizontal_color = grid_horizontal_color 239 | get_node("Grid").grid_vertical_color= grid_vertical_color 240 | var margin_left: float = _MARGIN_LEFT if get_node("Axis").y_label == "" else _MARGIN_LEFT + 20 241 | var margin_bottom: float = _MARGIN_BOTTOM if get_node("Axis").x_label == "" else _MARGIN_BOTTOM + 20 242 | 243 | get_node("PlotArea").offset_left = margin_left 244 | get_node("PlotArea").offset_bottom = -margin_bottom 245 | 246 | # Vertical Graduation 247 | var y_step = _get_min_step(y_min, y_max) 248 | assert(not is_inf(y_step), "y_step is infinite!") 249 | 250 | var y_axis_range: float = y_max - y_min 251 | var vert_grad_number = _get_graduation_num(y_min, y_max, y_step, "vert") 252 | 253 | # Horizontal Graduation 254 | var x_step = _get_min_step(x_min, x_max) 255 | assert(not is_inf(x_step), "y_step is infinite!") 256 | 257 | var x_axis_range: float = x_max - x_min 258 | var hor_grad_number = _get_graduation_num(x_min, x_max, x_step, "hor") 259 | 260 | # Plot area height in pixel 261 | var area_height = size.y - _MARGIN_TOP - margin_bottom 262 | var vert_grad_step_px = area_height / (vert_grad_number - 1) 263 | # Plot area width in pixel 264 | var area_width = size.x - margin_left - _MARGIN_RIGHT 265 | var hor_grad_step_px = area_width / (hor_grad_number -1) 266 | 267 | var vert_grad: Array 268 | var hor_grid: Array 269 | var grad_px: Vector2 270 | grad_px.x = margin_left 271 | # Draw grid number 272 | for n in range(vert_grad_number): 273 | var grad: Array = [] 274 | grad_px.y = _MARGIN_TOP + n * vert_grad_step_px 275 | grad.append(grad_px) 276 | var grad_text = "%0.1f" % (float(y_max) - n * float(y_axis_range)/(vert_grad_number-1)) 277 | grad.append(grad_text) 278 | vert_grad.append(grad) 279 | 280 | # Horizontal grid 281 | if grid_horizontal_visible: 282 | var grid_px: PackedVector2Array 283 | grid_px.append(grad_px) 284 | grid_px.append(Vector2(grad_px.x + area_width, grad_px.y)) 285 | hor_grid.append(grid_px) 286 | 287 | get_node("Axis").vert_grad = vert_grad 288 | 289 | if grid_horizontal_visible: 290 | get_node("Grid").hor_grid = hor_grid 291 | else: 292 | get_node("Grid").hor_grid = [] 293 | 294 | var hor_grad: Array 295 | var vert_grid: Array 296 | grad_px = Vector2() 297 | grad_px.y = _MARGIN_TOP + area_height 298 | 299 | for n in range(hor_grad_number): 300 | var grad: Array = [] 301 | grad_px.x = margin_left + n * hor_grad_step_px 302 | grad.append(grad_px) 303 | var grad_text = "%0.1f" % (float(x_min) + n * float(x_axis_range)/(hor_grad_number-1)) 304 | grad.append(grad_text) 305 | hor_grad.append(grad) 306 | 307 | # Vertical grid 308 | if grid_vertical_visible: 309 | var grid_px: PackedVector2Array 310 | grid_px.append(grad_px) 311 | grid_px.append(Vector2(grad_px.x, grad_px.y - area_height)) 312 | vert_grid.append(grid_px) 313 | 314 | get_node("Axis").hor_grad = hor_grad 315 | 316 | if grid_vertical_visible: 317 | get_node("Grid").vert_grid = vert_grid 318 | else: 319 | get_node("Grid").vert_grid = [] 320 | 321 | get_node("Axis").queue_redraw() 322 | get_node("Grid").queue_redraw() 323 | 324 | func _update_plots(): 325 | for plot in _plots: 326 | plot._redraw() 327 | 328 | func _update_legend() -> void: 329 | # Add labels to the legend 330 | var labels = Array() 331 | for p in _plots: 332 | labels.append({ 333 | name = p.label, 334 | color = p.color, 335 | }) 336 | get_node("PlotArea/Legend").update(labels) 337 | 338 | func _on_Graph_resized() -> void: 339 | _update_graph() 340 | 341 | func _on_plot_area_resized() -> void: 342 | _update_plots() 343 | 344 | # This function return the minimal step 345 | func _get_min_step(value_min, value_max): 346 | var range_log: int = int(_log10(value_max - value_min)) 347 | var step: float = 10.0**(range_log-1) 348 | # print("min step: %f " % [step]) 349 | return step 350 | 351 | func _get_graduation_num(value_min, value_max, step, orientation) -> int: 352 | var diff = value_max - value_min 353 | var nb_grad: int = roundi(diff/step) 354 | var max_grad_num: int 355 | match orientation: 356 | "vert": 357 | if size.y < 250: max_grad_num = 5 358 | else: max_grad_num = 10 359 | "hor": 360 | if size.x < 450: max_grad_num = 5 361 | else: max_grad_num = 10 362 | 363 | while nb_grad > max_grad_num: 364 | # print("->", nb_grad) 365 | if not nb_grad % 2: 366 | nb_grad /= 2 367 | continue 368 | elif not nb_grad % 3: 369 | nb_grad /= 3 370 | continue 371 | elif not nb_grad % 5: 372 | nb_grad /= 5 373 | continue 374 | elif not nb_grad % 7: 375 | nb_grad /= 7 376 | continue 377 | elif not nb_grad % 9: 378 | nb_grad /= 9 379 | continue 380 | else: 381 | # not divided 382 | break 383 | # return nb_grad + 1 384 | 385 | # print("diff: %f , nb_grad: %d" % [diff, nb_grad]) 386 | return nb_grad + 1 387 | 388 | func _get_min_value(min_value, max_value, step): 389 | var min_token = roundf(min_value/step) * step 390 | 391 | while true: 392 | var diff = max_value - min_token 393 | var nb_grad: int = roundi(diff/step) 394 | 395 | while nb_grad > 10: 396 | # print("->", nb_grad) 397 | if not nb_grad % 2: 398 | nb_grad /= 2 399 | continue 400 | elif not nb_grad % 3: 401 | nb_grad /= 3 402 | continue 403 | elif not nb_grad % 5: 404 | nb_grad /= 5 405 | continue 406 | elif not nb_grad % 7: 407 | nb_grad /= 7 408 | continue 409 | elif not nb_grad % 9: 410 | nb_grad /= 9 411 | continue 412 | else: 413 | # not divided 414 | break 415 | if nb_grad <= 10: 416 | return min_token 417 | min_token -= step 418 | 419 | func _get_max_value(min_value, max_value, step): 420 | var max_token = roundf(max_value/step) * step 421 | 422 | while true: 423 | var diff = max_token - min_value 424 | var nb_grad: int = roundi(diff/step) 425 | 426 | while nb_grad > 10: 427 | # print("->", nb_grad) 428 | if not nb_grad % 2: 429 | nb_grad /= 2 430 | continue 431 | elif not nb_grad % 3: 432 | nb_grad /= 3 433 | continue 434 | elif not nb_grad % 5: 435 | nb_grad /= 5 436 | continue 437 | elif not nb_grad % 7: 438 | nb_grad /= 7 439 | continue 440 | elif not nb_grad % 9: 441 | nb_grad /= 9 442 | continue 443 | else: 444 | # not divided 445 | break 446 | if nb_grad <= 10: 447 | return max_token 448 | max_token += step 449 | 450 | func _log10(value: float) -> float: 451 | return log(value)/log(10) 452 | -------------------------------------------------------------------------------- /addons/graph_2d/graph_2d.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 53 | 58 | 63 | 64 | -------------------------------------------------------------------------------- /addons/graph_2d/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Graph2D" 4 | description="2D graph to display multiple plots in real time" 5 | author="LD2Studio" 6 | version="1.1.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/graph_2d/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | 5 | func _enter_tree() -> void: 6 | # Initialization of the plugin goes here. 7 | add_custom_type("Graph2D", "Control", 8 | preload("res://addons/graph_2d/graph_2d.gd"), 9 | preload("res://addons/graph_2d/graph_2d.svg")) 10 | 11 | 12 | func _exit_tree() -> void: 13 | # Clean-up of the plugin goes here. 14 | remove_custom_type("Graph2D") 15 | -------------------------------------------------------------------------------- /examples/plot_sint.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | var plot_sin 5 | var x = 0.0 6 | var draw_enabled = false: 7 | set(value): 8 | draw_enabled = value 9 | # if is_instance_valid($Graph2D): 10 | # $Graph2D.background_color = Color.SLATE_GRAY if draw_enabled else Color.BLACK 11 | 12 | func _ready(): 13 | plot_sin = $Graph2D.add_plot_item("Sin(x)", Color.RED, 0.5) 14 | 15 | func _process(_delta): 16 | if draw_enabled: 17 | var y: float = sin(x) 18 | plot_sin.add_point(Vector2(x,y)) 19 | x += 0.1 20 | 21 | if draw_enabled and x > $Graph2D.x_max: 22 | draw_enabled = false 23 | 24 | func _on_draw_button_pressed() -> void: 25 | draw_enabled = true 26 | plot_sin.remove_all() 27 | x = 0.0 28 | 29 | 30 | func _on_clear_button_pressed() -> void: 31 | draw_enabled = false 32 | plot_sin.remove_all() 33 | x = 0.0 34 | -------------------------------------------------------------------------------- /examples/plot_sint.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dwuptr3eos1kt"] 2 | 3 | [ext_resource type="Script" path="res://examples/plot_sint.gd" id="1_nl7wu"] 4 | [ext_resource type="Script" path="res://addons/graph_2d/graph_2d.gd" id="2_o0pag"] 5 | 6 | [node name="PlotSint" type="Control"] 7 | layout_mode = 3 8 | anchors_preset = 15 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | grow_horizontal = 2 12 | grow_vertical = 2 13 | script = ExtResource("1_nl7wu") 14 | 15 | [node name="Graph2D" type="Control" parent="."] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | script = ExtResource("2_o0pag") 23 | x_label = "t[s]" 24 | y_min = -2.0 25 | y_max = 2.0 26 | y_label = "y" 27 | grid_horizontal_visible = true 28 | grid_vertical_visible = true 29 | metadata/_edit_layout_mode = 0 30 | metadata/_edit_use_custom_anchors = false 31 | 32 | [node name="DrawButton" type="Button" parent="Graph2D"] 33 | layout_mode = 1 34 | anchors_preset = 5 35 | anchor_left = 0.5 36 | anchor_right = 0.5 37 | offset_left = -48.0 38 | offset_right = 48.0 39 | offset_bottom = 31.0 40 | grow_horizontal = 2 41 | text = "Draw Sin(x)" 42 | metadata/_edit_layout_mode = 0 43 | metadata/_edit_use_custom_anchors = false 44 | 45 | [node name="ClearButton" type="Button" parent="Graph2D"] 46 | layout_mode = 1 47 | anchors_preset = 5 48 | anchor_left = 0.5 49 | anchor_right = 0.5 50 | offset_left = 58.0 51 | offset_right = 107.0 52 | offset_bottom = 31.0 53 | grow_horizontal = 2 54 | text = "Clear" 55 | metadata/_edit_layout_mode = 0 56 | metadata/_edit_use_custom_anchors = false 57 | 58 | [connection signal="pressed" from="Graph2D/DrawButton" to="." method="_on_draw_button_pressed"] 59 | [connection signal="pressed" from="Graph2D/ClearButton" to="." method="_on_clear_button_pressed"] 60 | -------------------------------------------------------------------------------- /examples/single_plot.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | func _on_add_plot_pressed() -> void: 4 | var my_plot = $Graph2D.add_plot_item( 5 | "Plot %d" % [$Graph2D.count()], 6 | [Color.RED, Color.GREEN, Color.BLUE][$Graph2D.count() % 3], 7 | [1.0, 3.0, 5.0].pick_random() 8 | ) 9 | 10 | for x in range(0, 11, 1): 11 | var y = randf_range(-50, 50) 12 | my_plot.add_point(Vector2(x, y)) 13 | 14 | 15 | func _on_remove_all_plots_pressed() -> void: 16 | $Graph2D.remove_all() 17 | -------------------------------------------------------------------------------- /examples/single_plot.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://br4viqfrbdlpe"] 2 | 3 | [ext_resource type="Script" path="res://examples/single_plot.gd" id="1_qmytq"] 4 | [ext_resource type="Script" path="res://addons/graph_2d/graph_2d.gd" id="2_2ul57"] 5 | 6 | [node name="SinglePlot" type="Control"] 7 | layout_mode = 3 8 | anchors_preset = 15 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | grow_horizontal = 2 12 | grow_vertical = 2 13 | script = ExtResource("1_qmytq") 14 | 15 | [node name="Graph2D" type="Control" parent="."] 16 | layout_mode = 1 17 | anchors_preset = 15 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | grow_horizontal = 2 21 | grow_vertical = 2 22 | script = ExtResource("2_2ul57") 23 | x_label = "Time(s)" 24 | y_min = -53.0 25 | y_max = 50.0 26 | y_label = "Y" 27 | background_color = Color(0.0941176, 0.227451, 0.4, 1) 28 | grid_horizontal_visible = true 29 | grid_vertical_visible = true 30 | 31 | [node name="AddPlot" type="Button" parent="."] 32 | layout_mode = 0 33 | offset_left = 280.0 34 | offset_top = 32.0 35 | offset_right = 393.0 36 | offset_bottom = 63.0 37 | text = "Add New Plot" 38 | 39 | [node name="RemoveAllPlots" type="Button" parent="."] 40 | layout_mode = 0 41 | offset_left = 400.0 42 | offset_top = 32.0 43 | offset_right = 537.0 44 | offset_bottom = 63.0 45 | text = "Remove All Plots" 46 | 47 | [connection signal="pressed" from="AddPlot" to="." method="_on_add_plot_pressed"] 48 | [connection signal="pressed" from="RemoveAllPlots" to="." method="_on_remove_all_plots_pressed"] 49 | --------------------------------------------------------------------------------