Added basic coaster physics and graph

This commit is contained in:
ObeseTermite 2026-03-16 02:12:19 -07:00
parent f8ffcbb6be
commit c91cb455c2
106 changed files with 3654 additions and 17 deletions

3
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: fenix-hub

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: fenix-hub
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows]
- Godot Engine Version [e.g. 3.2.3rc6]
- Plugin Version [e.g. v1.7.0]
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees: fenix-hub
---
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe an implementation suggestion**
A clear and concise description of any implementation method you'd like to suggest. It may be technical or just theoretical.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -5,21 +5,86 @@ class_name Cart
var going = false
var progress = 0
var progress : float = 0
var velocity = 0
var initial_velocity = 10
var speed_multiplier = 0.1
var initial_height = 0
var last_velocity = 0
var time = 0
var function : Function
var first_tick = false
@export var chart : Chart
func _ready():
hide()
function = Function.new(
[0],
[0],
"Acceleration",
{
type = Function.Type.LINE,
marker = Function.Marker.SQUARE,
color = Color.WHITE,
interpolation = Function.Interpolation.SPLINE
}
)
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.draw_bounding_box = false
cp.title = "Roller Coaster Acceleration"
cp.x_label = "Time"
cp.y_label = "Acceleration (m/s^2)"
cp.x_scale = 5
cp.y_scale = 10
cp.interactive = true
cp.max_samples = 1000
chart.plot([function], cp)
func _process(delta: float) -> void:
if len(spline.curve_points) > 1:
position = spline.curve_points[progress]
rotation = (spline.curve_points[progress+1] - position).angle()
func start():
first_tick = true
for i in range(1000):
function.remove_point(0)
if not going:
return
initial_height = position.y - initial_velocity
last_velocity = sqrt(2*9.8*(position.y-initial_height))*speed_multiplier
velocity = 0
going = true
time = 0
show()
progress += 1
if progress >= len(spline.curve_points)-1:
func _physics_process(delta: float) -> void:
var new_point = spline.get_point_at(progress)
if new_point != null:
position = new_point
else:
going = false
hide()
progress = 0
if not going:
return
last_velocity = velocity
velocity = sqrt(2*9.8*(position.y-initial_height))*speed_multiplier
progress += velocity
time += delta
if not first_tick:
function.add_point(time, velocity-last_velocity)
chart.queue_redraw()
if first_tick:
first_tick = false

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 周公不解梦
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

74
README.md Normal file
View file

@ -0,0 +1,74 @@
<img src="easy_charts.svg" align="middle">
> Charts for Godot Engine, made easy.
> **Note**
Looking for 3.x version? 👉 [3.x](https://github.com/fenix-hub/godot-engine.easy-charts/tree/godot-3)
## How does it work?
There is a [WIKI](https://github.com/fenix-hub/godot-engine.easy-charts/wiki) with some tutorials, even if it is a work in progress.
You can also find practical examples in `addons/easy_charts/examples/`.
# Available Charts and when to use them
This library offers a set of charts for each main Godot Node:
- ![control](https://raw.githubusercontent.com/fenix-hub/godot-engine.easy-charts/036d7126a16547ff1f1199531160cd1e1b01dc72/addons/easy_charts/utilities/icons/linechart.svg) **Control Charts:** Fast Charts plotted in a Control Node. They offer basic Control properties, such as Margins, size inheritance and control. No animations, best suited for UIs that rely on Control Node structures.
- **2D Charts:** plotted in 2D Nodes. They offer additional tools, such as animations. They can be used to implement more aesthetic charts in 2D contexts. Compatibility not guaranteed in Canvas and Control nodes.
- **3D Charts:** Plotted using 3D nodes, but can be used both in 2D and 3D spaces. They offer the possibility to plot 3D datasets, which are common in machine learning contexts or just data analysis. A Camera Control will also be available, which can be used to move around the chart.
### Available Charts
| | Control | 2D | 3D |
|--------------|---------|----|----|
| ScatterChart | ✅ | ❌ | ❌ |
| LineChart | ✅ | ❌ | ❌ |
| BarChart | ✅ | ❌ | ❌ |
| AreaChart | ✅ | ❌ | ❌ |
| PieChart | ✅ | ❌ | ❌ |
| RadarChart | ❌ | ❌ | ❌ |
| BubbleChart | ❌ | ❌ | ❌ |
| DonutChart | ❌ | ❌ | ❌ |
| ParliamentChart | ❌ | ❌ | ❌ |
| SunburstChart | ❌ | ❌ | ❌ |
### Some Examples
<details>
<summary>Realtime LineChart</summary>
![example_LineChart_realtime](imgs/real_time_line.gif)
</details>
<details>
<summary>Realtime PieChart</summary>
![example_Piechart](imgs/pie_chart_realtime.gif)
</details>
<details>
<summary>RadarChart</summary>
![exampleradar](imgs/radar.png)
</details>
<details>
<summary>ScatterChart</summary>
![example01](imgs/scatter.gif)
</details>
<details>
<summary>Composite Chart</summary>
![example03](imgs/example03.gif)
</details>
<details>
<summary>Multiplot</summary>
![example03](imgs/multiplot.png)
</details>
##### Some references for charts and plots
[Flourish](https://app.flourish.studio/projects)
[Chart.js](https://www.chartjs.org/samples/latest/)
[Google Charts](https://developers.google.com/chart)
[plotly](https://plotly.com)
[matplotlib](https://matplotlib.org)
> **Warning**
This addon was built for a **personal use** intention. It was released as an open source plugin in the hope that it could be useful to the Godot Engine Community.
As a "work in progress" project, there is *no warranty* for any eventual issue and bug that may broke your project.
I don't assume any responsibility for possible corruptions of your project. It is always advisable to keep a copy of your project and check any changes you make in your Github repository.

View file

@ -5,3 +5,21 @@ class_name Spline
var points : PackedVector2Array
var curve_points : PackedVector2Array
func get_point_at(distance : float):
var current_point : int = 0
while true:
if current_point >= len(curve_points)-1:
return null
if distance > curve_points[current_point].distance_to(curve_points[current_point + 1]):
distance -= curve_points[current_point].distance_to(curve_points[current_point + 1])
else:
return lerp(curve_points[current_point],curve_points[current_point+1], distance/(curve_points[current_point].distance_to(curve_points[current_point + 1])))
current_point += 1
if distance < 0:
break
return curve_points[current_point]

View file

@ -4,7 +4,9 @@ extends Control
@export var cart : Cart
@export var control_point_scene : PackedScene
@export var steps : int = 10
@onready var check_button : CheckButton = $HBoxContainer/CheckButton
@export var steps : int = 100
var editing = true
@ -30,6 +32,7 @@ func _process(delta: float) -> void:
add_child(new_control_point)
update()
func update():
spline.points = []
for child in get_children():
@ -45,7 +48,7 @@ func update():
var b3 : Vector2 = spline.points[i+3]
var t : float = 0
while t < 0.99:
while t < 1:
# B-spline calculations
var calculated_position =\
((-b0+3*b1-3*b2+b3)*(t**3) +\
@ -53,19 +56,22 @@ func update():
(-3*b0+3*b2)*t +\
(b0+4*b1+b2))/6
spline.curve_points.append(calculated_position)
t += 0.1
t += 1.0/steps
queue_redraw()
func _on_check_button_toggled(toggled_on: bool) -> void:
func set_editing(toggled_on: bool):
editing = toggled_on
for child in get_children():
if not child is ControlPoint:
continue
child.visible = editing
check_button.button_pressed = toggled_on
update()
func _on_check_button_toggled(toggled_on: bool) -> void:
set_editing(toggled_on)
func _on_start_pressed() -> void:
cart.going = true
cart.show()
set_editing(false)
cart.start()

View file

@ -0,0 +1,181 @@
@icon("res://addons/easy_charts/utilities/icons/linechart.svg")
extends PanelContainer
class_name Chart
@onready var _canvas: Canvas = $Canvas
@onready var plot_box: PlotBox = $"%PlotBox"
@onready var grid_box: GridBox = $"%GridBox"
@onready var functions_box: Control = $"%FunctionsBox"
@onready var function_legend: FunctionLegend = $"%FunctionLegend"
var functions: Array = []
var x: Array = []
var y: Array = []
var x_labels_function: Callable = Callable()
var y_labels_function: Callable = Callable()
var x_domain: Dictionary = {}
var y_domain: Dictionary = {}
var chart_properties: ChartProperties = null
###########
func _ready() -> void:
if theme == null:
theme = Theme.new()
func plot(functions: Array[Function], properties: ChartProperties = ChartProperties.new()) -> void:
self.functions = functions
self.chart_properties = properties
theme.set("default_font", self.chart_properties.font)
_canvas.prepare_canvas(self.chart_properties)
plot_box.chart_properties = self.chart_properties
function_legend.chart_properties = self.chart_properties
load_functions(functions)
func get_function_plotter(function: Function) -> FunctionPlotter:
var plotter: FunctionPlotter
match function.get_type():
Function.Type.LINE:
plotter = LinePlotter.new(function)
Function.Type.AREA:
plotter = AreaPlotter.new(function)
Function.Type.PIE:
plotter = PiePlotter.new(function)
Function.Type.BAR:
plotter = BarPlotter.new(function)
Function.Type.SCATTER, _:
plotter = ScatterPlotter.new(function)
return plotter
func load_functions(functions: Array[Function]) -> void:
self.x = []
self.y = []
function_legend.clear()
# Remove existing function_plotters
for function_plotter in functions_box.get_children():
functions_box.remove_child(function_plotter)
function_plotter.queue_free()
for function in functions:
# Load x and y values
self.x.append(function.__x)
self.y.append(function.__y)
# Create FunctionPlotter
var function_plotter: FunctionPlotter = get_function_plotter(function)
function_plotter.connect("point_entered", Callable(plot_box, "_on_point_entered"))
function_plotter.connect("point_exited", Callable(plot_box, "_on_point_exited"))
functions_box.add_child(function_plotter)
# Create legend
match function.get_type():
Function.Type.PIE:
for i in function.__x.size():
var interp_color: Color = function.get_gradient().sample(float(i) / float(function.__x.size()))
function_legend.add_label(function.get_type(), interp_color, Function.Marker.NONE, function.__y[i])
_:
function_legend.add_function(function)
func _draw() -> void:
if (x.size() == 0) or (y.size() == 0) or (x.size() == 1 and x[0].is_empty()) or (y.size() == 1 and y[0].is_empty()):
printerr("Cannot plot an empty function!")
return
var is_x_fixed: bool = x_domain.get("fixed", false)
var is_y_fixed: bool = y_domain.get("fixed", false)
# GridBox
if not is_x_fixed or not is_y_fixed :
if chart_properties.max_samples > 0 :
var _x: Array = []
var _y: Array = []
_x.resize(x.size())
_y.resize(y.size())
for i in x.size():
if not is_x_fixed:
_x[i] = x[i].slice(max(0, x[i].size() - chart_properties.max_samples), x[i].size())
if not is_y_fixed:
_y[i] = y[i].slice(max(0, y[i].size() - chart_properties.max_samples), y[i].size())
if not is_x_fixed:
x_domain = calculate_domain(_x)
if not is_y_fixed:
y_domain = calculate_domain(_y)
else:
if not is_x_fixed:
x_domain = calculate_domain(x)
if not is_y_fixed:
y_domain = calculate_domain(y)
# Update values for the PlotBox in order to propagate them to the children
update_plotbox(x_domain, y_domain, x_labels_function, y_labels_function)
# Update GridBox
update_gridbox(x_domain, y_domain, x_labels_function, y_labels_function)
# Update each FunctionPlotter in FunctionsBox
for function_plotter in functions_box.get_children():
if function_plotter is FunctionPlotter:
function_plotter.visible = function_plotter.function.get_visibility()
if function_plotter.function.get_visibility():
function_plotter.update_values(x_domain, y_domain)
func calculate_domain(values: Array) -> Dictionary:
for value_array in values:
if ECUtilities._contains_string(value_array):
return { lb = 0.0, ub = (value_array.size() - 1), has_decimals = false , fixed = false }
var min_max: Dictionary = ECUtilities._find_min_max(values)
if not chart_properties.smooth_domain:
return { lb = min_max.min, ub = min_max.max, has_decimals = ECUtilities._has_decimals(values), fixed = false }
else:
return { lb = ECUtilities._round_min(min_max.min), ub = ECUtilities._round_max(min_max.max), has_decimals = ECUtilities._has_decimals(values) , fixed = false }
func set_x_domain(lb: Variant, ub: Variant) -> void:
x_domain = { lb = lb, ub = ub, has_decimals = ECUtilities._has_decimals([lb, ub]), fixed = true }
func set_y_domain(lb: Variant, ub: Variant) -> void:
y_domain = { lb = lb, ub = ub, has_decimals = ECUtilities._has_decimals([lb, ub]), fixed = true }
func update_plotbox(x_domain: Dictionary, y_domain: Dictionary, x_labels_function: Callable, y_labels_function: Callable) -> void:
plot_box.box_margins = calculate_plotbox_margins(x_domain, y_domain)
plot_box.set_labels_functions(x_labels_function, y_labels_function)
func update_gridbox(x_domain: Dictionary, y_domain: Dictionary, x_labels_function: Callable, y_labels_function: Callable) -> void:
grid_box.set_domains(x_domain, y_domain)
grid_box.set_labels_functions(x_labels_function, y_labels_function)
grid_box.queue_redraw()
func calculate_plotbox_margins(x_domain: Dictionary, y_domain: Dictionary) -> Vector2:
var plotbox_margins: Vector2 = Vector2(
chart_properties.x_tick_size,
chart_properties.y_tick_size
)
if chart_properties.show_tick_labels:
var x_ticklabel_size: Vector2
var y_ticklabel_size: Vector2
var y_max_formatted: String = ECUtilities._format_value(y_domain.ub, y_domain.has_decimals)
if y_domain.lb < 0: # negative number
var y_min_formatted: String = ECUtilities._format_value(y_domain.lb, y_domain.has_decimals)
if y_min_formatted.length() >= y_max_formatted.length():
y_ticklabel_size = chart_properties.get_string_size(y_min_formatted)
else:
y_ticklabel_size = chart_properties.get_string_size(y_max_formatted)
else:
y_ticklabel_size = chart_properties.get_string_size(y_max_formatted)
plotbox_margins.x += y_ticklabel_size.x + chart_properties.x_ticklabel_space
plotbox_margins.y += ThemeDB.fallback_font_size + chart_properties.y_ticklabel_space
return plotbox_margins

View file

@ -0,0 +1 @@
uid://0pw0m252abmq

View file

@ -0,0 +1,104 @@
[gd_scene format=3 uid="uid://dlwq4kmdb3bhs"]
[ext_resource type="Script" uid="uid://0pw0m252abmq" path="res://addons/easy_charts/control_charts/chart.gd" id="1"]
[ext_resource type="PackedScene" path="res://addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.tscn" id="2"]
[ext_resource type="Script" uid="uid://d0q44x2e5wxym" path="res://addons/easy_charts/utilities/containers/canvas/canvas.gd" id="3"]
[ext_resource type="Script" uid="uid://bt0h3q1ocyhu8" path="res://addons/easy_charts/utilities/containers/canvas/plot_box/plot_box.gd" id="4"]
[ext_resource type="Script" uid="uid://oql4qoedksyg" path="res://addons/easy_charts/utilities/containers/canvas/plot_box/grid_box.gd" id="5"]
[ext_resource type="PackedScene" path="res://addons/easy_charts/utilities/containers/legend/function_legend.tscn" id="6"]
[sub_resource type="Theme" id="4"]
[sub_resource type="StyleBoxEmpty" id="8"]
[sub_resource type="StyleBoxFlat" id="5"]
content_margin_left = 15.0
content_margin_top = 15.0
content_margin_right = 15.0
content_margin_bottom = 15.0
draw_center = false
[node name="Chart" type="PanelContainer" unique_id=1786449154]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 1
theme = SubResource("4")
theme_override_styles/panel = SubResource("8")
script = ExtResource("1")
[node name="Canvas" type="PanelContainer" parent="." unique_id=1915297635]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 1
theme_override_styles/panel = SubResource("5")
script = ExtResource("3")
[node name="CanvasContainer" type="VBoxContainer" parent="Canvas" unique_id=217645487]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 10
[node name="Title" type="Label" parent="Canvas/CanvasContainer" unique_id=2011788518]
layout_mode = 2
text = "{title}"
[node name="DataContainer" type="HBoxContainer" parent="Canvas/CanvasContainer" unique_id=1255003439]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 10
[node name="YLabel" type="Label" parent="Canvas/CanvasContainer/DataContainer" unique_id=1497055450]
layout_mode = 2
text = "{ylabel}"
[node name="PlotContainer" type="VBoxContainer" parent="Canvas/CanvasContainer/DataContainer" unique_id=462879481]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="PlotBox" type="Control" parent="Canvas/CanvasContainer/DataContainer/PlotContainer" unique_id=1084807178]
unique_name_in_owner = true
clip_contents = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("4")
[node name="GridBox" type="Control" parent="Canvas/CanvasContainer/DataContainer/PlotContainer/PlotBox" unique_id=956292477]
unique_name_in_owner = true
anchors_preset = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 2
script = ExtResource("5")
[node name="FunctionsBox" type="Control" parent="Canvas/CanvasContainer/DataContainer/PlotContainer/PlotBox" unique_id=248020886]
unique_name_in_owner = true
anchors_preset = 0
anchor_right = 1.0
anchor_bottom = 1.0
mouse_default_cursor_shape = 3
[node name="Tooltip" parent="Canvas/CanvasContainer/DataContainer/PlotContainer/PlotBox" unique_id=918153509 instance=ExtResource("2")]
layout_mode = 0
offset_left = -67.0
offset_top = -33.0
offset_right = -17.0
offset_bottom = 30.0
[node name="XLabel" type="Label" parent="Canvas/CanvasContainer/DataContainer/PlotContainer" unique_id=1121436047]
layout_mode = 2
text = "{xlabel}"
[node name="FunctionLegend" parent="Canvas/CanvasContainer/DataContainer" unique_id=1840811922 instance=ExtResource("6")]
unique_name_in_owner = true
use_parent_material = true
layout_mode = 2

View file

@ -0,0 +1,33 @@
extends LinePlotter
class_name AreaPlotter
func _init(function: Function) -> void:
super(function)
pass
func _draw_areas() -> void:
var box: Rect2 = get_box()
var fp_augmented: PackedVector2Array = []
match function.get_interpolation():
Function.Interpolation.LINEAR:
fp_augmented = points_positions
Function.Interpolation.STAIR:
fp_augmented = _get_stair_points()
Function.Interpolation.SPLINE:
fp_augmented = _get_spline_points()
Function.Interpolation.NONE, _:
return
fp_augmented.push_back(Vector2(fp_augmented[-1].x, box.end.y + 80))
fp_augmented.push_back(Vector2(fp_augmented[0].x, box.end.y + 80))
var base_color: Color = function.get_color()
var colors: PackedColorArray = []
for point in fp_augmented:
base_color.a = remap(point.y, box.end.y, box.position.y, 0.0, 0.5)
colors.push_back(base_color)
draw_polygon(fp_augmented, colors)
func _draw() -> void:
super._draw()
_draw_areas()

View file

@ -0,0 +1 @@
uid://bwpt6ptm4bf7k

View file

@ -0,0 +1,56 @@
extends FunctionPlotter
class_name BarPlotter
signal point_entered(point, function)
signal point_exited(point, function)
var bars: PackedVector2Array
var bars_rects: Array
var focused_bar_midpoint: Point
var bar_size: float
func _init(function: Function) -> void:
super(function)
self.bar_size = function.props.get("bar_size", 5.0)
func _draw() -> void:
super._draw()
var box: Rect2 = get_box()
var x_sampled_domain: Dictionary = { lb = box.position.x, ub = box.end.x }
var y_sampled_domain: Dictionary = { lb = box.end.y, ub = box.position.y }
sample(x_sampled_domain, y_sampled_domain)
_draw_bars()
func sample(x_sampled_domain: Dictionary, y_sampled_domain: Dictionary) -> void:
bars = []
bars_rects = []
for i in function.__x.size():
var top: Vector2 = Vector2(
ECUtilities._map_domain(i, x_domain, x_sampled_domain),
ECUtilities._map_domain(function.__y[i], y_domain, y_sampled_domain)
)
var base: Vector2 = Vector2(top.x, ECUtilities._map_domain(0.0, y_domain, y_sampled_domain))
bars.push_back(top)
bars.push_back(base)
bars_rects.append(Rect2(Vector2(top.x - bar_size, top.y), Vector2(bar_size * 2, base.y - top.y)))
func _draw_bars() -> void:
for bar in bars_rects:
draw_rect(bar, function.get_color())
func _input(event: InputEvent) -> void:
if event is InputEventMouse:
for i in bars_rects.size():
if bars_rects[i].grow(5).abs().has_point(get_relative_position(event.position)):
var point: Point = Point.new(bars_rects[i].get_center(), { x = function.__x[i], y = function.__y[i]})
if focused_bar_midpoint == point:
return
else:
focused_bar_midpoint = point
emit_signal("point_entered", point, function)
return
# Mouse is not in any point's box
emit_signal("point_exited", focused_bar_midpoint, function)
focused_bar_midpoint = null

View file

@ -0,0 +1 @@
uid://dgfyhejfleesp

View file

@ -0,0 +1,32 @@
extends Control
class_name FunctionPlotter
var function: Function
var x_domain: Dictionary
var y_domain: Dictionary
func _init(function: Function) -> void:
self.function = function
func _ready() -> void:
set_process_input(get_chart_properties().interactive)
func update_values(x_domain: Dictionary, y_domain: Dictionary) -> void:
self.visible = self.function.get_visibility()
if not self.function.get_visibility():
return
self.x_domain = x_domain
self.y_domain = y_domain
queue_redraw()
func _draw() -> void:
return
func get_box() -> Rect2:
return get_parent().get_parent().get_plot_box()
func get_chart_properties() -> ChartProperties:
return get_parent().get_parent().chart_properties
func get_relative_position(position: Vector2) -> Vector2:
return position - global_position

View file

@ -0,0 +1 @@
uid://dy7l5qjo078mc

View file

@ -0,0 +1,70 @@
extends ScatterPlotter
class_name LinePlotter
func _init(function: Function) -> void:
super(function)
func _get_spline_points(density: float = 10.0, tension: float = 1) -> PackedVector2Array:
var spline_points: PackedVector2Array = []
var augmented: PackedVector2Array = points_positions
var pi: Vector2 = augmented[0] - Vector2(10, -10)
var pf: Vector2 = augmented[augmented.size() - 1] + Vector2(10, 10)
augmented.insert(0, pi)
augmented.push_back(pf)
for p in range(1, augmented.size() - 2, 1) : #(inclusive)
for f in range(0, density + 1, 1):
spline_points.append(
augmented[p].cubic_interpolate(
augmented[p + 1],
augmented[p - 1],
augmented[p + 2],
f / density)
)
return spline_points
func _get_stair_points() -> PackedVector2Array:
var stair_points: PackedVector2Array = points_positions
for i in range(points_positions.size() - 1, 0, -1):
stair_points.insert(i, Vector2(points_positions[i].x, points_positions[i-1].y))
return stair_points
func _draw() -> void:
super._draw()
#prevent error when drawing with no data.
if points_positions.size() < 2:
printerr("Cannot plot a line with less than two points!")
return
match function.get_interpolation():
Function.Interpolation.LINEAR:
draw_polyline(
points_positions,
function.get_color(),
function.get_line_width(),
true
)
Function.Interpolation.STAIR:
draw_polyline(
_get_stair_points(),
function.get_color(),
function.get_line_width(),
true
)
Function.Interpolation.SPLINE:
draw_polyline(
_get_spline_points(),
function.get_color(),
function.get_line_width(),
true
)
Function.Interpolation.NONE, _:
pass

View file

@ -0,0 +1 @@
uid://bv0cpnwt5cn64

View file

@ -0,0 +1,127 @@
extends FunctionPlotter
class_name PiePlotter
signal point_entered(slice_mid_point, function, props)
signal point_exited(slice_mid_point, function)
var radius_multiplayer: float = 1.0
#### INTERNAL
var box: Rect2
var radius: float
var slices: Array = []
var slices_dirs: PackedVector2Array = []
var focused_point: Point
func _init(function: Function) -> void:
super(function)
pass
func _draw() -> void:
super._draw()
box = get_box()
radius = min(box.size.x, box.size.y) * 0.5 * radius_multiplayer
var total: float = get_total()
var ratios: PackedFloat32Array = get_ratios(total)
sample(radius, box.get_center(), total, ratios)
_draw_pie()
_draw_labels(radius, box.get_center(), ratios)
func get_total() -> float:
# Calculate total and ratios
var total: float = 0.0
for value in function.__x:
total += float(value)
return total
func get_ratios(total: float) -> PackedFloat32Array:
var ratios: PackedFloat32Array = []
for value in function.__x:
ratios.push_back(value / total * 100)
return ratios
func sample(radius: float, center: Vector2, total: float, ratios: PackedFloat32Array) -> void:
# Calculate directions
slices.clear()
slices_dirs = []
var start_angle: float = 0.0
for ratio in ratios:
var end_angle: float = start_angle + (2 * PI * float(ratio) * 0.01)
slices.append(
_calc_circle_arc_poly(
center,
radius,
start_angle,
end_angle
)
)
start_angle = end_angle
for slice in slices:
var mid_point: Vector2 = (slice[-1] + slice[1]) / 2
draw_circle(mid_point, 5, Color.WHITE)
slices_dirs.append(center.direction_to(mid_point))
func _calc_circle_arc_poly(center: Vector2, radius: float, angle_from: float, angle_to: float) -> PackedVector2Array:
var nb_points: int = 64
var points_arc: PackedVector2Array = PackedVector2Array()
points_arc.push_back(center)
for i in range(nb_points + 1):
var angle_point: float = - (PI / 2) + angle_from + i * (angle_to - angle_from) / nb_points
points_arc.push_back(center + (Vector2.RIGHT.rotated(angle_point).normalized() * radius))
return points_arc
func _draw_pie() -> void:
for i in slices.size():
draw_colored_polygon(slices[i], function.get_gradient().sample(float(i) / float(slices.size() - 1)))
draw_polyline(slices[i], Color.WHITE, 2.0, true)
func _draw_labels(radius: float, center: Vector2, ratios: PackedFloat32Array) -> void:
for i in slices_dirs.size():
var ratio_lbl: String = "%.1f%%" % ratios[i]
var value_lbl: String = "(%s)" % function.__x[i]
var position: Vector2 = center + slices_dirs[i] * radius * 0.5
var ratio_lbl_size: Vector2 = get_chart_properties().get_string_size(ratio_lbl)
var value_lbl_size: Vector2 = get_chart_properties().get_string_size(value_lbl)
draw_string(
get_chart_properties().font,
position - Vector2(ratio_lbl_size.x / 2, 0),
ratio_lbl,
HORIZONTAL_ALIGNMENT_CENTER,
ratio_lbl_size.x,
16,
Color.WHITE,
3,
TextServer.DIRECTION_AUTO,
TextServer.ORIENTATION_HORIZONTAL
)
draw_string(
get_chart_properties().font,
position - Vector2(value_lbl_size.x / 2, - value_lbl_size.y),
value_lbl,
HORIZONTAL_ALIGNMENT_CENTER,
value_lbl_size.x,
16,
Color.WHITE,
3,TextServer.DIRECTION_AUTO,TextServer.ORIENTATION_HORIZONTAL
)
func _input(event: InputEvent) -> void:
if event is InputEventMouse:
for i in slices.size():
if Geometry2D.is_point_in_polygon(get_relative_position(event.position), slices[i]):
var point: Point = Point.new(self.box.get_center() + slices_dirs[i] * self.radius * 0.5, { x = function.__x[i], y = function.__y[i] })
if focused_point == point:
return
else:
focused_point = point
emit_signal("point_entered", focused_point, function, { interpolation_index = float(i) / float(slices.size() - 1)})
return
# Mouse is not in any slice's box
emit_signal("point_exited", focused_point, function)
focused_point = null

View file

@ -0,0 +1 @@
uid://bkthewhd8mb

View file

@ -0,0 +1,87 @@
extends FunctionPlotter
class_name ScatterPlotter
signal point_entered(point, function)
signal point_exited(point, function)
var points: Array[Point]
var points_positions: PackedVector2Array
var focused_point: Point
var point_size: float
func _init(function: Function) -> void:
super(function)
self.point_size = function.props.get("point_size", 3.0)
func _draw() -> void:
super._draw()
var box: Rect2 = get_box()
var x_sampled_domain: Dictionary = { lb = box.position.x, ub = box.end.x }
var y_sampled_domain: Dictionary = { lb = box.end.y, ub = box.position.y }
sample(x_sampled_domain, y_sampled_domain)
if function.get_marker() != Function.Marker.NONE:
for point in points:
# Don't plot points outside domain upper and lower bounds!
if point.position.y <= y_sampled_domain.lb and point.position.y >= y_sampled_domain.ub:
draw_function_point(point.position)
func sample(x_sampled_domain: Dictionary, y_sampled_domain: Dictionary) -> void:
points = []
points_positions = []
var lower_bound: int = max(0, function.__x.size() - get_chart_properties().max_samples) \
#disable sample display limits
if get_chart_properties().max_samples > 0 \
else 0
for i in range(lower_bound, function.__x.size()):
var _position: Vector2 = Vector2(
ECUtilities._map_domain(float(function.__x[i]), x_domain, x_sampled_domain),
ECUtilities._map_domain(float(function.__y[i]), y_domain, y_sampled_domain)
)
points.push_back(Point.new(_position, { x = function.__x[i], y = function.__y[i] }))
points_positions.push_back(_position)
func draw_function_point(point_position: Vector2) -> void:
match function.get_marker():
Function.Marker.SQUARE:
draw_rect(
Rect2(point_position - (Vector2.ONE * point_size), (Vector2.ONE * point_size * 2)),
function.get_color(), true, 1.0
)
Function.Marker.TRIANGLE:
draw_colored_polygon(
PackedVector2Array([
point_position + (Vector2.UP * point_size * 1.3),
point_position + (Vector2.ONE * point_size * 1.3),
point_position - (Vector2(1, -1) * point_size * 1.3)
]), function.get_color(), [], null
)
Function.Marker.CROSS:
draw_line(
point_position - (Vector2.ONE * point_size),
point_position + (Vector2.ONE * point_size),
function.get_color(), point_size, true
)
draw_line(
point_position + (Vector2(1, -1) * point_size),
point_position + (Vector2(-1, 1) * point_size),
function.get_color(), point_size / 2, true
)
Function.Marker.CIRCLE, _:
draw_circle(point_position, point_size, function.get_color())
func _input(event: InputEvent) -> void:
if event is InputEventMouse:
for point in points:
if Geometry2D.is_point_in_circle(get_relative_position(event.position), point.position, self.point_size * 4):
if focused_point == point:
return
else:
focused_point = point
emit_signal("point_entered", point, function)
return
# Mouse is not in any point's box
emit_signal("point_exited", focused_point, function)
focused_point = null

View file

@ -0,0 +1 @@
uid://t4lofek667rq

View file

@ -0,0 +1,69 @@
extends Control
@onready var chart: Chart = $VBoxContainer/Chart
# This Chart will plot 3 different functions
var f1: Function
func _ready():
# Let's create our @x values
var x: Array = ArrayOperations.multiply_float(range(-10, 11, 1), 0.5)
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = ArrayOperations.multiply_int(ArrayOperations.cos(x), 20)
# Let's customize the chart properties, which specify how the chart
# should look, plus some additional elements like labels, the scale, etc...
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.draw_bounding_box = false
cp.title = "Air Quality Monitoring"
cp.x_label = "Time"
cp.y_label = "Sensor values"
cp.x_scale = 5
cp.y_scale = 10
cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
# and interecept clicks on the plot
# Let's add values to our functions
f1 = Function.new(
x, y, "Pressure", # This will create a function with x and y values taken by the Arrays
# we have created previously. This function will also be named "Pressure"
# as it contains 'pressure' values.
# If set, the name of a function will be used both in the Legend
# (if enabled thourgh ChartProperties) and on the Tooltip (if enabled).
# Let's also provide a dictionary of configuration parameters for this specific function.
{
color = Color("#36a2eb"), # The color associated to this function
marker = Function.Marker.NONE, # The marker that will be displayed for each drawn point (x,y)
# since it is `NONE`, no marker will be shown.
type = Function.Type.AREA, # This defines what kind of plotting will be used,
# in this case it will be an Area Chart.
}
)
# Now let's plot our data
chart.plot([f1], cp)
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://lyegfrrwjwij

View file

@ -0,0 +1,48 @@
[gd_scene load_steps=4 format=3 uid="uid://c2ymglyg812ss"]
[ext_resource type="Script" path="res://addons/easy_charts/examples/area_chart/Control.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dlwq4kmdb3bhs" path="res://addons/easy_charts/control_charts/chart.tscn" id="2"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Chart" parent="VBoxContainer" instance=ExtResource("2")]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1,63 @@
extends Control
@onready var chart: Chart = $VBoxContainer/Chart
# This Chart will plot 3 different functions
var f1: Function
func _ready():
# Let's create our @x values
var x: Array = ["Day 1", "Day 2", "Day 3", "Day 4"]
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = [20, 10, 50, 30]
# Let's customize the chart properties, which specify how the chart
# should look, plus some additional elements like labels, the scale, etc...
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.y_scale = 10
cp.draw_origin = true
cp.draw_bounding_box = false
cp.draw_vertical_grid = false
cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
# and interecept clicks on the plot
# Let's add values to our functions
f1 = Function.new(
x, y, "User", # This will create a function with x and y values taken by the Arrays
# we have created previously. This function will also be named "Pressure"
# as it contains 'pressure' values.
# If set, the name of a function will be used both in the Legend
# (if enabled thourgh ChartProperties) and on the Tooltip (if enabled).
{
type = Function.Type.BAR,
bar_size = 5
}
)
# Now let's plot our data
chart.plot([f1], cp)
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://xrknxgy8l3ev

View file

@ -0,0 +1,48 @@
[gd_scene load_steps=4 format=3 uid="uid://dbtgnlgjeoxj4"]
[ext_resource type="Script" path="res://addons/easy_charts/examples/bar_chart/Control.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dlwq4kmdb3bhs" path="res://addons/easy_charts/control_charts/chart.tscn" id="2"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control2" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Chart" parent="VBoxContainer" instance=ExtResource("2")]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1,72 @@
extends Control
@onready var chart: Chart = $VBoxContainer/Chart
# This Chart will plot 3 different functions
var f1: Function
func _ready():
# Let's create our @x values
var x: PackedFloat32Array = ArrayOperations.multiply_float(range(-10, 11, 1), 0.5)
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = ArrayOperations.multiply_int(ArrayOperations.cos(x), 20)
# Let's customize the chart properties, which specify how the chart
# should look, plus some additional elements like labels, the scale, etc...
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.draw_bounding_box = false
cp.title = "Air Quality Monitoring"
cp.x_label = "Time"
cp.y_label = "Sensor values"
cp.x_scale = 5
cp.y_scale = 10
cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
# and interecept clicks on the plot
# Let's add values to our functions
f1 = Function.new(
x, y, "Pressure", # This will create a function with x and y values taken by the Arrays
# we have created previously. This function will also be named "Pressure"
# as it contains 'pressure' values.
# If set, the name of a function will be used both in the Legend
# (if enabled thourgh ChartProperties) and on the Tooltip (if enabled).
# Let's also provide a dictionary of configuration parameters for this specific function.
{
color = Color("#36a2eb"), # The color associated to this function
marker = Function.Marker.CIRCLE, # The marker that will be displayed for each drawn point (x,y)
# since it is `NONE`, no marker will be shown.
type = Function.Type.LINE, # This defines what kind of plotting will be used,
# in this case it will be a Linear Chart.
interpolation = Function.Interpolation.STAIR # Interpolation mode, only used for
# Line Charts and Area Charts.
}
)
# Now let's plot our data
chart.plot([f1], cp)
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
f1.remove_point(0)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://b3f6ye7dq4g6m

View file

@ -0,0 +1,48 @@
[gd_scene format=3 uid="uid://chcj7up8k8pa8"]
[ext_resource type="Script" uid="uid://b3f6ye7dq4g6m" path="res://addons/easy_charts/examples/line_chart/Control.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dlwq4kmdb3bhs" path="res://addons/easy_charts/control_charts/chart.tscn" id="2"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control2" type="Control" unique_id=1432132673]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=420636511]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer" unique_id=1087670003]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Chart" parent="VBoxContainer" unique_id=1336462756 instance=ExtResource("2")]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer" unique_id=1068372980]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1,80 @@
extends Control
@onready var chart: Chart = $VBoxContainer/Chart
# This Chart will plot 3 different functions
var f1: Function
var f2: Function
var f3: Function
func _ready():
# Let's create our @x values
var x: Array = ArrayOperations.multiply_float(range(-10, 11, 1), 0.5)
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = ArrayOperations.multiply_int(ArrayOperations.cos(x), 20)
var y2: Array = ArrayOperations.add_float(ArrayOperations.multiply_int(ArrayOperations.sin(x), 20), 20)
var y3: Array = ArrayOperations.add_float(ArrayOperations.multiply_int(ArrayOperations.cos(x), -5), -3)
# Let's customize the chart properties, which specify how the chart
# should look, plus some additional elements like labels, the scale, etc...
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.draw_bounding_box = false
cp.show_legend = true
cp.title = "Air Quality Monitoring"
cp.x_label = "Time"
cp.y_label = "Sensor values"
cp.x_scale = 5
cp.y_scale = 10
cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
# and interecept clicks on the plot
# Let's add values to our functions
f1 = Function.new(
x, y, "Pressure", # This will create a function with x and y values taken by the Arrays
# we have created previously. This function will also be named "Pressure"
# as it contains 'pressure' values.
# If set, the name of a function will be used both in the Legend
# (if enabled thourgh ChartProperties) and on the Tooltip (if enabled).
# Let's also provide a dictionary of configuration parameters for this specific function.
{
color = Color("#36a2eb"), # The color associated to this function
marker = Function.Marker.NONE, # The marker that will be displayed for each drawn point (x,y)
# since it is `NONE`, no marker will be shown.
type = Function.Type.AREA, # This defines what kind of plotting will be used,
# in this case it will be an Area Chart.
interpolation = Function.Interpolation.STAIR # Interpolation mode, only used for
# Line Charts and Area Charts.
}
)
f2 = Function.new(x, y2, "Humidity", { color = Color("#ff6384"), marker = Function.Marker.CROSS })
f3 = Function.new(x, y3, "CO2", { color = Color.GREEN, marker = Function.Marker.TRIANGLE })
# Now let's plot our data
chart.plot([f1, f2, f3], cp)
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
f2.add_point(new_val, (sin(new_val) * 20) + 20)
f3.add_point(new_val, (cos(new_val) * -5) - 3)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://gjg5j18ou3f1

View file

@ -0,0 +1,48 @@
[gd_scene load_steps=4 format=3 uid="uid://7v0v5lsl0kqe"]
[ext_resource type="Script" path="res://addons/easy_charts/examples/multiplot/Control.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dlwq4kmdb3bhs" path="res://addons/easy_charts/control_charts/chart.tscn" id="2"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control2" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Chart" parent="VBoxContainer" instance=ExtResource("2")]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1,67 @@
extends Control
@onready var chart: Chart = $VBoxContainer/Chart
# This Chart will plot 3 different functions
var f1: Function
func _ready():
# Let's create our @x values
var x: Array = [10, 20, 30, 40]
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = ["Java", "JavaScript", "C++", "GDScript"]
# Let's customize the chart properties, which specify how the chart
# should look, plus some additional elements like labels, the scale, etc...
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.draw_bounding_box = false
cp.title = "Users preferences on programming languages"
cp.draw_grid_box = false
cp.show_legend = true
cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
# and interecept clicks on the plot
var gradient: Gradient = Gradient.new()
gradient.set_color(0, Color.AQUAMARINE)
gradient.set_color(1, Color.DEEP_PINK)
# Let's add values to our functions
f1 = Function.new(
x, y, "Language", # This will create a function with x and y values taken by the Arrays
# we have created previously. This function will also be named "Pressure"
# as it contains 'pressure' values.
# If set, the name of a function will be used both in the Legend
# (if enabled thourgh ChartProperties) and on the Tooltip (if enabled).
{
gradient = gradient,
type = Function.Type.PIE
}
)
# Now let's plot our data
chart.plot([f1], cp)
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://db3v84wxna0nn

View file

@ -0,0 +1,48 @@
[gd_scene load_steps=4 format=3 uid="uid://be3nkm3rmqe7m"]
[ext_resource type="Script" path="res://addons/easy_charts/examples/pie_chart/Control.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dlwq4kmdb3bhs" path="res://addons/easy_charts/control_charts/chart.tscn" id="2"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control2" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Chart" parent="VBoxContainer" instance=ExtResource("2")]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1,66 @@
extends Control
@onready var chart: Chart = $VBoxContainer/Chart
# This Chart will plot 3 different functions
var f1: Function
var f2: Function
func _ready():
# Let's create our @x values
var x: Array = ArrayOperations.multiply_float(range(-10, 11, 1), 0.5)
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = ArrayOperations.multiply_int(ArrayOperations.cos(x), 20)
var y2: Array = ArrayOperations.add_float(ArrayOperations.multiply_int(ArrayOperations.sin(x), 20), 20)
# Let's customize the chart properties, which specify how the chart
# should look, plus some additional elements like labels, the scale, etc...
var cp: ChartProperties = ChartProperties.new()
cp.colors.frame = Color("#161a1d")
cp.colors.background = Color.TRANSPARENT
cp.colors.grid = Color("#283442")
cp.colors.ticks = Color("#283442")
cp.colors.text = Color.WHITE_SMOKE
cp.draw_bounding_box = false
cp.title = "Air Quality Monitoring"
cp.x_label = "Time"
cp.y_label = "Sensor values"
cp.x_scale = 5
cp.y_scale = 10
cp.interactive = true # false by default, it allows the chart to create a tooltip to show point values
# and interecept clicks on the plot
# Let's add values to our functions
f1 = Function.new(
x, y, "Pressure", # This will create a function with x and y values taken by the Arrays
# we have created previously. This function will also be named "Pressure"
# as it contains 'pressure' values.
# If set, the name of a function will be used both in the Legend
# (if enabled thourgh ChartProperties) and on the Tooltip (if enabled).
{ color = Color.GREEN, marker = Function.Marker.CIRCLE }
)
f2 = Function.new(x, y2, "Humidity", { color = Color("#ff6384"), marker = Function.Marker.CROSS })
# Now let's plot our data
chart.plot([f1, f2], cp)
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
f2.add_point(new_val, (sin(new_val) * 20) + 20)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://nmgyex22cpp4

View file

@ -0,0 +1,48 @@
[gd_scene load_steps=4 format=3 uid="uid://cekkstadxpimf"]
[ext_resource type="Script" path="res://addons/easy_charts/examples/scatter_chart/Control.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dlwq4kmdb3bhs" path="res://addons/easy_charts/control_charts/chart.tscn" id="2"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control2" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Chart" parent="VBoxContainer" instance=ExtResource("2")]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1,47 @@
# This example shows how to instantiate a Chart node at runtime and plot a single function
extends Control
@onready var chart_scn: PackedScene = load("res://addons/easy_charts/control_charts/chart.tscn")
var chart: Chart
# This Chart will plot 1 function
var f1: Function
func _ready():
chart = chart_scn.instantiate()
$VBoxContainer.add_child(chart)
# Let's create our @x values
var x: Array = ArrayOperations.multiply_float(range(-10, 11, 1), 0.5)
# And our y values. It can be an n-size array of arrays.
# NOTE: `x.size() == y.size()` or `x.size() == y[n].size()`
var y: Array = ArrayOperations.multiply_int(ArrayOperations.cos(x), 20)
# Let's add values to our functions
f1 = Function.new(x, y, "Pressure", { marker = Function.Marker.CIRCLE })
# Set fixed Y domain
chart.set_y_domain(-50, 50)
# Now let's plot our data
chart.plot([f1])
# Uncommenting this line will show how real time data plotting works
set_process(false)
var new_val: float = 4.5
func _process(delta: float):
# This function updates the values of a function and then updates the plot
new_val += 5
# we can use the `Function.add_point(x, y)` method to update a function
f1.add_point(new_val, cos(new_val) * 20)
chart.queue_redraw() # This will force the Chart to be updated
func _on_CheckButton_pressed():
set_process(not is_processing())

View file

@ -0,0 +1 @@
uid://dtvd8j3myl3fh

View file

@ -0,0 +1,44 @@
[gd_scene load_steps=3 format=3 uid="uid://v4c2f17q8a1o"]
[ext_resource type="Script" path="res://addons/easy_charts/examples/simple_chart/Control.gd" id="1"]
[sub_resource type="StyleBoxFlat" id="1"]
content_margin_right = 5.0
content_margin_bottom = 5.0
draw_center = false
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[node name="Control2" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="CheckButton" type="CheckButton" parent="VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_pressed_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
theme_override_colors/font_disabled_color = Color(0, 0, 0, 1)
text = "Start Relatime Plotting"
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("1")
text = "Try to scale the window!"
[connection signal="pressed" from="VBoxContainer/CheckButton" to="." method="_on_CheckButton_pressed"]

View file

@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="282" xmlns="http://www.w3.org/2000/svg" height="283" id="screenshot-b8eadac8-0d0a-803f-8001-f21d3cdd64ab" viewBox="-0 -28.069 282 283" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g id="shape-b8eadac8-0d0a-803f-8001-f21d3cdd64ab" rx="0" ry="0"><g id="shape-b8eadac8-0d0a-803f-8001-f21d3cdd64ac"><g id="fills-b8eadac8-0d0a-803f-8001-f21d3cdd64ac"><path rx="0" ry="0" d="M109.571,9.931C137.517,9.931,100.264,111.526,122.198,132.482C145.546,154.789,244.294,116.502,244,143.181C243.256,210.759,189.641,254.931,122.198,254.931C54.755,254.931,0,200.064,0,132.482C0,64.9,42.128,9.931,109.571,9.931Z" style="fill: rgb(83, 126, 255); fill-opacity: 1;"/></g></g><g id="shape-b8eadac8-0d0a-803f-8001-f21d3cdd64ad"><g id="fills-b8eadac8-0d0a-803f-8001-f21d3cdd64ad"><path rx="0" ry="0" d="M175.542,0C149.482,1.117,140.591,15.797,137.561,37.981L175.542,0ZM191.08,0.936L136.216,55.801C136.025,61.771,136.001,68.07,136,74.618L205.696,4.922C201.025,3.12,196.136,1.774,191.08,0.936ZM216.799,10.293L136.158,90.935C136.386,98.109,136.94,103.445,138.281,107.414L227.652,18.042C224.271,15.153,220.64,12.557,216.799,10.293ZM235.913,26.256L146.049,116.12C149.806,117.738,154.968,118.423,162.059,118.712L243.735,37.035C241.43,33.211,238.81,29.605,235.913,26.256ZM249.229,48.016L178.324,118.921C178.488,118.921,178.653,118.921,178.818,118.921C185.068,118.921,191.144,118.96,196.959,118.887L253.502,62.344C252.524,57.381,251.084,52.589,249.229,48.016ZM254.999,77.322L214.206,118.114C224.287,117.188,233.037,115.226,239.821,111.101L249.685,101.237C253.115,95.481,255,87.858,255,77.764C255,77.617,255,77.469,254.999,77.322Z" style="fill: rgb(51, 51, 51); fill-opacity: 1;"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bxynlfljql6so"
path="res://.godot/imported/icon.svg-655bc7c28f15e7cab48b85d562ef0068.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/easy_charts/icon.svg"
dest_files=["res://.godot/imported/icon.svg-655bc7c28f15e7cab48b85d562ef0068.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,7 @@
[plugin]
name="EasyCharts"
description=""
author="Nicolò \"fenix\" Santilio"
version="14.08.2023"
script="plugin.gd"

View file

@ -0,0 +1,8 @@
@tool
extends EditorPlugin
func _enter_tree():
pass
func _exit_tree():
pass

View file

@ -0,0 +1 @@
uid://cu4dkywehlo0g

View file

@ -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"
},
}

View file

@ -0,0 +1,36 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://dv41l4rmsac4o"
path="res://.godot/imported/OpenSans-VariableFont_wdth,wght.ttf-d05790715e89c94bb2a3b1122054f8d1.fontdata"
[deps]
source_file="res://addons/easy_charts/utilities/assets/OpenSans-VariableFont_wdth,wght.ttf"
dest_files=["res://.godot/imported/OpenSans-VariableFont_wdth,wght.ttf-d05790715e89c94bb2a3b1122054f8d1.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=false
hinting=1
subpixel_positioning=4
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -0,0 +1,12 @@
extends RefCounted
class_name Bar
#var rect: Rect2
#var value: Pair
#
#func _init(rect: Rect2, value: Pair = Pair.new()) -> void:
# self.value = value
# self.rect = rect
func _to_string() -> String:
return "Value: %s\nRect: %s" % [self.value, self.rect]

View file

@ -0,0 +1 @@
uid://6465cllnm5b7

View file

@ -0,0 +1,75 @@
extends RefCounted
class_name ChartProperties
var title: String
var x_label: String
var y_label: String
## {n}_scale defines in how many sectors the grid will be divided.
var x_scale: float = 5.0
var y_scale: float = 2.0
var x_tick_size: float = 7
var x_ticklabel_space: float = 5
var y_tick_size: float = 7
var y_ticklabel_space: float = 5
## Scale type, 0 = linear | 1 = logarithmic
var x_scale_type: int = 0
var y_scale_type: int = 0
var draw_borders: bool = true
var draw_frame: bool = true
var draw_background: bool = true
var draw_bounding_box: bool = true
var draw_vertical_grid: bool = true
var draw_horizontal_grid: bool = true
var draw_ticks: bool = true
var draw_origin: bool = false
var draw_grid_box: bool = true
var show_tick_labels: bool = true
var show_x_label: bool = true
var show_y_label: bool = true
var show_title: bool = true
## If true will show the legend of your Chart on the right side of the frame.
var show_legend: bool = false
## If true will make the Chart interactive, i.e. the DataTooltip will be displayed when
## hovering points with your mouse, and mouse_entered and mouse_exited signal will be emitted.
var interactive: bool = false
## If true, will smooth the domain lower and upper bounds to the closest rounded value,
## instead of using precise values.
var smooth_domain: bool = false
## If > 0, will limit the amount of points plotted in a Chart, discarding older values.
## [b]Note:[/b] this parameter will not make the Chart remove points from your Function objects,
## instead older points will be just ignored. This will make your Function object x and y arrays
## grow linearly, but won't interfere with your own data.
## If you instead prefer to improve performances by completely remove older data from your Function
## object, consider calling the Function.remove_point(0) method before adding a new point and plotting
## again.
var max_samples: int = 100
## Dictionary of colors for all of the Chart elements.
var colors: Dictionary = {
frame = Color.WHITE_SMOKE,
background = Color.WHITE,
borders = Color.RED,
bounding_box = Color.BLACK,
grid = Color.GRAY,
ticks = Color.BLACK,
text = Color.BLACK,
origin = Color.DIM_GRAY
}
var font: FontFile = load("res://addons/easy_charts/utilities/assets/OpenSans-VariableFont_wdth,wght.ttf")
var font_size: int = 13
func _init() -> void:
ThemeDB.set_fallback_font(font)
ThemeDB.set_fallback_font_size(font_size)
func get_string_size(text: String) -> Vector2:
return font.get_string_size(text)

View file

@ -0,0 +1 @@
uid://dtlrom7yygrt8

View file

@ -0,0 +1,93 @@
extends RefCounted
class_name Function
enum Type {
SCATTER,
LINE,
AREA,
PIE,
BAR
}
enum Interpolation {
NONE,
LINEAR,
STAIR,
SPLINE
}
# TODO: add new markers, like an empty circle, an empty box, etc.
enum Marker {
NONE,
CIRCLE,
TRIANGLE,
SQUARE,
CROSS
}
var __x: Array
var __y: Array
var name: String
var props: Dictionary = {}
func _init(x: Array, y: Array, name: String = "", props: Dictionary = {}) -> void:
self.__x = x.duplicate()
self.__y = y.duplicate()
self.name = name
if not props.is_empty() and props != null:
self.props = props
func get_point(index: int) -> Array:
return [self.__x[index], self.__y[index]]
func add_point(x: float, y: float) -> void:
self.__x.append(x)
self.__y.append(y)
func set_point(index: int, x: float, y: float) -> void:
self.__x[index] = x
self.__y[index] = y
func remove_point(index: int) -> void:
self.__x.remove_at(index)
self.__y.remove_at(index)
func pop_back_point() -> void:
self.__x.pop_back()
self.__y.pop_back()
func pop_front_point() -> void:
self.__x.pop_front()
self.__y.pop_front()
func count_points() -> int:
return self.__x.size()
func get_color() -> Color:
return props.get("color", Color.DARK_SLATE_GRAY)
func get_gradient() -> Gradient:
return props.get("gradient", Gradient.new())
func get_marker() -> int:
return props.get("marker", Marker.NONE)
func get_type() -> int:
return props.get("type", Type.SCATTER)
func get_interpolation() -> int:
return props.get("interpolation", Interpolation.LINEAR)
func get_line_width() -> float:
return props.get("line_width", 2.0)
func get_visibility() -> bool:
return props.get("visible", true)
func copy() -> Function:
return Function.new(
self.__x.duplicate(),
self.__y.duplicate(),
self.name,
self.props.duplicate(true)
)

View file

@ -0,0 +1 @@
uid://bv8gpeqso3ojt

View file

@ -0,0 +1,12 @@
extends RefCounted
class_name Point
var position: Vector2
var value: Dictionary
func _init(position: Vector2, value: Dictionary) -> void:
self.position = position
self.value = value
func _to_string() -> String:
return "Value: %s\nPosition: %s" % [self.value, self.position]

View file

@ -0,0 +1 @@
uid://d2hychsr02l0c

View file

@ -0,0 +1,56 @@
extends RefCounted
class_name ArrayOperations
static func add_int(array: Array, _int: int) -> Array:
var t: Array = array.duplicate(true)
for ti in t.size():
t[ti] = int(t[ti] + _int)
return t
static func add_float(array: Array, _float: float) -> Array:
var t: Array = array.duplicate(true)
for ti in t.size():
t[ti] = float(t[ti] + _float)
return t
static func multiply_int(array: Array, _int: int) -> Array:
var t: Array = array.duplicate(true)
for ti in t.size():
t[ti] = int(t[ti] * _int)
return t
static func multiply_float(array: Array, _float: float) -> PackedFloat32Array:
var t: PackedFloat32Array = array.duplicate(true)
for ti in t.size():
t[ti] = float(t[ti] * _float)
return t
static func pow(array: Array, _int: int) -> Array:
var t: Array = array.duplicate(true)
for ti in t.size():
t[ti] = float(pow(t[ti], _int))
return t
static func cos(array: Array) -> Array:
var t: Array = array.duplicate(true)
for val in array.size():
t[val] = cos(t[val])
return t
static func sin(array: Array) -> Array:
var t: Array = array.duplicate(true)
for val in array.size():
t[val] = sin(t[val])
return t
static func affix(array: Array, _string: String) -> Array:
var t: Array = array.duplicate(true)
for val in array.size():
t[val] = str(t[val]) + _string
return t
static func suffix(array: Array, _string: String) -> Array:
var t: Array = array.duplicate(true)
for val in array.size():
t[val] = _string + str(t[val])
return t

View file

@ -0,0 +1 @@
uid://d36mcc8nlkx4x

View file

@ -0,0 +1,190 @@
@tool
extends Resource
class_name DataFrame
var table_name : String = ""
var labels : PackedStringArray = []
var headers : PackedStringArray = []
var datamatrix : Matrix = null
var dataset : Array = []
func _init(datamatrix : Matrix, headers : PackedStringArray = [], labels : PackedStringArray = [] , table_name : String = "") -> void:
if datamatrix.is_empty(): datamatrix.resize(labels.size(), headers.size())
if labels.is_empty() : for label in range(datamatrix.get_size().x) : labels.append(label as String)
if headers.is_empty() : for header in range(datamatrix.get_size().y) : headers.append(MatrixGenerator.get_letter_index(header))
build_dataframe(datamatrix, headers, labels, table_name)
func build_dataframe(datamatrix : Matrix, headers : PackedStringArray = [], labels : PackedStringArray = [] , table_name : String = "") -> void:
self.datamatrix = datamatrix
self.headers = headers
self.labels = labels
self.table_name = table_name
self.dataset = build_dataset_from_matrix(datamatrix, headers, labels)
func build_dataset_from_matrix(datamatrix : Matrix, headers : PackedStringArray, labels : PackedStringArray) -> Array:
var data : Array = datamatrix.to_array()
return build_dataset(data, headers, labels)
func build_dataset(data : Array, headers : PackedStringArray, labels : PackedStringArray) -> Array:
var dataset : Array = [Array([" "]) + Array(headers)]
for row_i in range(labels.size()): dataset.append(([labels[row_i]] + data[row_i]) if not data.is_empty() else [labels[row_i]])
return dataset
func insert_column(column : Array, header : String = "", index : int = dataset[0].size() - 1) -> void:
assert(column.size() == (datamatrix.rows() if not datamatrix.is_empty() else labels.size())) #,"error: the column size must match the dataset column size")
headers.insert(index, header if header != "" else MatrixGenerator.get_letter_index(index))
datamatrix.insert_column(column, index)
dataset = build_dataset_from_matrix(datamatrix, headers, labels)
func insert_row(row : Array, label : String = "", index : int = dataset.size() - 1) -> PackedStringArray:
assert(row.size() == (datamatrix.columns() if not datamatrix.is_empty() else headers.size())) #,"error: the row size must match the dataset row size")
labels.insert(index, label if label != "" else str(index))
datamatrix.insert_row(row, index)
dataset = build_dataset_from_matrix(datamatrix, headers, labels)
return PackedStringArray([label] + row)
func get_datamatrix() -> Matrix:
return datamatrix
func get_dataset() -> Array:
return dataset
func get_labels() -> PackedStringArray:
return labels
func transpose():
build_dataframe(MatrixGenerator.transpose(datamatrix), labels, headers, table_name)
func _to_string() -> String:
var last_string_len : int
for row in dataset:
for column in row:
var string_len : int = str(column).length()
last_string_len = string_len if string_len > last_string_len else last_string_len
var string : String = ""
for row_i in dataset.size():
for column_i in dataset[row_i].size():
string+="%*s" % [last_string_len+1, dataset[row_i][column_i]]
string+="\n"
string+="\n['{table_name}' : {rows} rows x {columns} columns]\n".format({
rows = datamatrix.rows(),
columns = datamatrix.columns(),
table_name = table_name})
return string
# ...............................................................................
# Return a list of headers corresponding to a list of indexes
func get_headers_names(indexes : PackedInt32Array) -> PackedStringArray:
var headers : PackedStringArray = []
for index in indexes:
headers.append(dataset[0][index])
return headers
# Returns the index of an header
func get_column_index(header : String) -> int:
for headers_ix in range(dataset[0].size()):
if dataset[0][headers_ix] == header:
return headers_ix
return -1
# Get a column by its header
func get_column(header : String) -> Array:
var headers_i : int = get_column_index(header)
if headers_i!=-1:
return datamatrix.get_column(headers_i)
else:
return []
# Get a list of columns by their headers
func columns(headers : PackedStringArray) -> Matrix:
var values : Array = []
for header in headers:
values.append(get_column(header))
return MatrixGenerator.transpose(Matrix.new(values))
# Get a column by its index
func get_icolumn(index : int) -> Array:
return datamatrix.get_column(index)
# Get a list of columns by their indexes
func get_icolumns(indexes : PackedInt32Array) -> Array:
var values : Array = []
for index in indexes:
values.append(datamatrix.get_column(index))
return values
# Returns the list of labels corresponding to the list of indexes
func get_labels_names(indexes : PackedInt32Array) -> PackedStringArray:
var headers : PackedStringArray = []
for index in indexes:
headers.append(dataset[index][0])
return headers
# Returns the index of a label
func get_row_index(label : String) -> int:
for row in dataset.size():
if dataset[row][0] == label:
return row
return -1
# Get a row by its label
func get_row(label : String) -> Array:
var index : int = get_row_index(label)
if index == -1 :
return []
else:
return datamatrix.get_row(index)
# Get a list of rows by their labels
func rows(labels : Array) -> Matrix:
var values : Array = []
for label in labels:
values.append(get_row(label))
return Matrix.new(values)
# Get a row by its index
func get_irow(index : int) -> Array:
return datamatrix.get_row(index)
# Get a list of rows by their indexes
func get_irows(indexes : PackedInt32Array) -> Array:
var values : Array = []
for index in indexes:
values.append(datamatrix.get_row(index))
return values
# Returns a a group of rows or a group of columns, using indexes or names
# dataset["0;5"] ---> Returns an array containing all rows from the 1st to the 4th
# dataset["0:5"] ---> Returns an array containing all columns from the 1st to the 4th
# dataset["label0;label5"] ---> Returns an array containing all row from the one with label == "label0" to the one with label == "label5"
# dataset["header0:header0"] ---> Returns an array containing all columns from the one with label == "label0" to the one with label == "label5"
func _get(_property : StringName):
# ":" --> Columns
if ":" in _property:
var property : PackedStringArray = _property.split(":")
if (property[0]).is_valid_int():
if property[1] == "*":
return get_icolumns(range(property[0] as int, headers.size()-1))
else:
return get_icolumns(range(property[0] as int, property[1] as int +1))
else:
if property[1] == "*":
return get_icolumns(range(get_column_index(property[0]), headers.size()-1))
else:
return get_icolumns(range(get_column_index(property[0]), get_column_index(property[1])))
# ";" --> Rows
elif ";" in _property:
var property : PackedStringArray = _property.split(";")
if (property[0]).is_valid_int():
return get_irows(range(property[0] as int, property[1] as int + 1 ))
else:
return get_irows(range(get_row_index(property[0]), get_row_index(property[1])))
elif "," in _property:
var property : PackedStringArray = _property.split(",")
else:
if (_property as String).is_valid_int():
return get_icolumn(int(_property))
else:
return get_column(_property)

View file

@ -0,0 +1 @@
uid://clmkup3i4vomr

View file

@ -0,0 +1,174 @@
@tool
extends Resource
class_name Matrix
var values : Array = []
func _init(matrix : Array = [], size : int = 0) -> void:
values = matrix
func insert_row(row : Array, index : int = values.size()) -> void:
if rows() != 0:
assert(row.size() == columns()) #,"the row size must match matrix row size")
values.insert(index, row)
func update_row(row : Array, index : int) -> void:
assert(rows() > index) #,"the row size must match matrix row size")
values[index] = row
func remove_row(index: int) -> void:
assert(rows() > index) #,"the row size must match matrix row size")
values.remove_at(index)
func insert_column(column : Array, index : int = values[0].size()) -> void:
if columns() != 0:
assert(column.size() == rows()) #,"the column size must match matrix column size")
for row_idx in column.size():
values[row_idx].insert(index, column[row_idx])
func update_column(column : Array, index : int) -> void:
assert(columns() > index) #,"the column size must match matrix column size")
for row_idx in column.size():
values[row_idx][index] = column[row_idx]
func remove_column(index: int) -> void:
assert(columns() > index) #,"the column index must be at least equals to the rows count")
for row in get_rows():
row.remove(index)
func resize(rows: int, columns: int) -> void:
for row in range(rows):
var row_column: Array = []
row_column.resize(columns)
values.append(row_column)
func to_array() -> Array:
return values.duplicate(true)
func get_size() -> Vector2:
return Vector2(rows(), columns())
func rows() -> int:
return values.size()
func columns() -> int:
return values[0].size() if rows() != 0 else 0
func value(row: int, column: int) -> float:
return values[row][column]
func set_value(value: float, row: int, column: int) -> void:
values[row][column] = value
func get_column(column : int) -> Array:
assert(column < columns()) #,"index of the column requested (%s) exceedes matrix columns (%s)"%[column, columns()])
var column_array : Array = []
for row in values:
column_array.append(row[column])
return column_array
func get_columns(from : int = 0, to : int = columns()-1) -> Array:
var values : Array = []
for column in range(from, to):
values.append(get_column(column))
return values
# return MatrixGenerator.from_array(values)
func get_row(row : int) -> Array:
assert(row < rows()) #,"index of the row requested (%s) exceedes matrix rows (%s)"%[row, rows()])
return values[row]
func get_rows(from : int = 0, to : int = rows()-1) -> Array:
return values.slice(from, to)
# return MatrixGenerator.from_array(values)
func is_empty() -> bool:
return rows() == 0 and columns() == 0
func is_square() -> bool:
return columns() == rows()
func is_diagonal() -> bool:
if not is_square():
return false
for i in rows():
for j in columns():
if i != j and values[i][j] != 0:
return false
return true
func is_upper_triangular() -> bool:
if not is_square():
return false
for i in rows():
for j in columns():
if i > j and values[i][j] != 0:
return false
return true
func is_lower_triangular() -> bool:
if not is_square():
return false
for i in rows():
for j in columns():
if i < j and values[i][j] != 0:
return false
return true
func is_triangular() -> bool:
return is_upper_triangular() or is_lower_triangular()
func is_identity() -> bool:
if not is_diagonal():
return false
for i in rows():
if values[i][i] != 1:
return false
return true
func _to_string() -> String:
var last_string_len : int
for row in values:
for column in row:
var string_len : int = str(column).length()
last_string_len = string_len if string_len > last_string_len else last_string_len
var string : String = "\n"
for row_i in values.size():
for column_i in values[row_i].size():
string+="%*s" % [last_string_len+1 if column_i!=0 else last_string_len, values[row_i][column_i]]
string+="\n"
return string
# ----
func set(position: StringName, value: Variant) -> void:
var t_pos: Array = position.split(",")
values[t_pos[0]][t_pos[1]] = value
# --------------
func _get(_property : StringName):
# ":" --> Columns
if ":" in _property:
var property : PackedStringArray = _property.split(":")
var from : PackedStringArray = property[0].split(",")
var to : PackedStringArray = property[1].split(",")
elif "," in _property:
var property : PackedStringArray = _property.split(",")
if property.size() == 2:
return get_row(property[0] as int)[property[1] as int]
else:
if (_property as String).is_valid_int():
return get_row(int(_property))

View file

@ -0,0 +1 @@
uid://c1k3thpb7ihaw

View file

@ -0,0 +1,160 @@
@tool
extends RefCounted
class_name MatrixGenerator
static func zeros(rows: int, columns: int) -> Matrix:
var zeros: Array = []
var t_rows: Array = []
t_rows.resize(columns)
t_rows.fill(0.0)
for row in rows:
zeros.append(t_rows.duplicate())
return Matrix.new(zeros)
# Generates a Matrix with random values between [from; to] with a given @size (rows, columns)
static func random_float_range(from : float, to : float, size : Vector2, _seed : int = 1234) -> Matrix:
seed(_seed)
randomize()
var array : Array = []
for row in range(size.x):
var matrix_row : Array = []
for column in range(size.y): matrix_row.append(randf_range(from,to))
array.append(matrix_row)
return Matrix.new(array)
# Generates a Matrix giving an Array (Array must by Array[Array])
static func from_array(array : Array = []) -> Matrix:
var matrix : Array = []
matrix.append(array)
return Matrix.new(matrix)
# Generates a sub-Matrix giving a Matrix, a @from Array [row_i, column_i] and a @to Array [row_j, column_j]
static func sub_matrix(_matrix : Matrix, from : PackedInt32Array, to : PackedInt32Array) -> Matrix:
assert( not (to[0] > _matrix.rows() or to[1] > _matrix.columns()),
"%s is not an acceptable size for the submatrix, giving a matrix of size %s"%[to, _matrix.get_size()])
var array : Array = []
var rows : Array = _matrix.get_rows(from[0], to[0])
for row in rows:
array.append(row.slice(from[1], to[1]))
return Matrix.new(array)
# Duplicates a given Matrix
static func duplicate(_matrix : Matrix) -> Matrix:
return Matrix.new(_matrix.to_array().duplicate())
# Calculate the determinant of a matrix
static func determinant(matrix: Matrix) -> float:
assert(matrix.is_square()) #,"Expected square matrix")
var determinant: float = 0.0
if matrix.rows() == 2 :
determinant = (matrix.value(0, 0) * matrix.value(1, 1)) - (matrix.value(0, 1) * matrix.value(1, 0))
elif matrix.is_diagonal() or matrix.is_triangular() :
for i in matrix.rows():
determinant *= matrix.value(i, i)
elif matrix.is_identity() :
determinant = 1.0
else:
# Laplace expansion
var multiplier: float = -1.0
var submatrix: Matrix = sub_matrix(matrix, [1, 0], [matrix.rows(), matrix.columns()])
for j in matrix.columns() :
var cofactor: Matrix = copy(submatrix)
cofactor.remove_column(j)
multiplier *= -1.0
determinant += multiplier * matrix.value(0, j) * determinant(cofactor)
return determinant
# Calculate the inverse of a Matrix
static func inverse(matrix: Matrix) -> Matrix:
var inverse: Matrix
# Minors and Cofactors
var minors_cofactors: Matrix = zeros(matrix.rows(), matrix.columns())
var multiplier: float = -1.0
for i in minors_cofactors.rows():
for j in minors_cofactors.columns():
var t_minor: Matrix = copy(matrix)
t_minor.remove_row(i)
t_minor.remove_column(j)
multiplier *= -1.0
minors_cofactors.set_value(multiplier * determinant(t_minor), i, j)
var transpose: Matrix = transpose(minors_cofactors)
var determinant: float = determinant(matrix)
inverse = multiply_float(transpose, 1 / determinant)
return inverse
# Transpose a given Matrix
static func transpose(_matrix : Matrix) -> Matrix:
var array : Array = []
array.resize(_matrix.get_size().y)
var row : Array = []
row.resize(_matrix.get_size().x)
for x in array.size():
array[x] = row.duplicate()
for i in range(_matrix.get_size().x):
for j in range(_matrix.get_size().y):
array[j][i] = (_matrix.to_array()[i][j])
return Matrix.new(array)
# Calculates the dot product (A*B) matrix between two Matrixes
static func dot(_matrix1 : Matrix, _matrix2 : Matrix) -> Matrix:
if _matrix1.get_size().y != _matrix2.get_size().x:
printerr("matrix1 number of columns: %s must be the same as matrix2 number of rows: %s"%[_matrix1.get_size().y, _matrix2.get_size().x])
return Matrix.new()
var array : Array = []
for x in range(_matrix1.get_size().x):
var row : Array = []
for y in range(_matrix2.get_size().y):
var sum : float
for k in range(_matrix1.get_size().y):
sum += (_matrix1.to_array()[x][k]*_matrix2.to_array()[k][y])
row.append(sum)
array.append(row)
return Matrix.new(array)
# Calculates the hadamard (element-wise product) between two Matrixes
static func hadamard(_matrix1 : Matrix, _matrix2 : Matrix) -> Matrix:
if _matrix1.get_size() != _matrix2.get_size():
printerr("matrix1 size: %s must be the same as matrix2 size: %s"%[_matrix1.get_size(), _matrix2.get_size()])
return Matrix.new()
var array : Array = []
for x in range(_matrix1.to_array().size()):
var row : Array = []
for y in range(_matrix1.to_array()[x].size()):
assert(typeof(_matrix1.to_array()[x][y]) != TYPE_STRING and typeof(_matrix2.to_array()[x][y]) != TYPE_STRING) #,"can't apply operations over a Matrix of Strings")
row.append(_matrix1.to_array()[x][y] * _matrix2.to_array()[x][y])
array.append(row)
return Matrix.new(array)
# Multiply a given Matrix for an int value
static func multiply_int(_matrix1 : Matrix, _int : int) -> Matrix:
var array : Array = _matrix1.to_array().duplicate()
for x in range(_matrix1.to_array().size()):
for y in range(_matrix1.to_array()[x].size()):
array[x][y]*=_int
array[x][y] = int(array[x][y])
return Matrix.new(array)
# Multiply a given Matrix for a float value
static func multiply_float(_matrix1 : Matrix, _float : float) -> Matrix:
var array : Array = _matrix1.to_array().duplicate()
for x in range(_matrix1.to_array().size()):
for y in range(_matrix1.to_array()[x].size()):
array[x][y]*=_float
return Matrix.new(array)
static func copy(matrix: Matrix) -> Matrix:
return Matrix.new(matrix.values.duplicate(true))
# ------------------------------------------------------------
static func get_letter_index(index : int) -> String:
return "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ")[index]

View file

@ -0,0 +1 @@
uid://ds4sjjjv10ywy

View file

@ -0,0 +1,64 @@
extends Control
class_name Canvas
@onready var _title_lbl: Label = $CanvasContainer/Title
@onready var _x_lbl: Label = $CanvasContainer/DataContainer/PlotContainer/XLabel
@onready var _y_lbl: Label = $CanvasContainer/DataContainer/YLabel
@onready var _legend: FunctionLegend = $CanvasContainer/DataContainer/FunctionLegend
func _ready():
pass # Replace with function body.
func prepare_canvas(chart_properties: ChartProperties) -> void:
if chart_properties.draw_frame:
set_color(chart_properties.colors.frame)
set_frame_visible(true)
else:
set_frame_visible(false)
if chart_properties.show_title:
update_title(chart_properties.title, chart_properties.colors.text)
else:
_title_lbl.hide()
if chart_properties.show_x_label:
update_x_label(chart_properties.x_label, chart_properties.colors.text)
else:
_x_lbl.hide()
if chart_properties.show_y_label:
update_y_label(chart_properties.y_label, chart_properties.colors.text, -90)
else:
_y_lbl.hide()
if chart_properties.show_legend:
_legend.show()
else:
hide_legend()
func update_title(text: String, color: Color, rotation: float = 0.0) -> void:
_title_lbl.show()
_update_canvas_label(_title_lbl, text, color, rotation)
func update_y_label(text: String, color: Color, rotation: float = 0.0) -> void:
_y_lbl.show()
_update_canvas_label(_y_lbl, text, color, rotation)
func update_x_label(text: String, color: Color, rotation: float = 0.0) -> void:
_x_lbl.show()
_update_canvas_label(_x_lbl, text, color, rotation)
func _update_canvas_label(canvas_label: Label, text: String, color: Color, rotation: float = 0.0) -> void:
canvas_label.set_text(text)
canvas_label.modulate = color
canvas_label.rotation = rotation
func hide_legend() -> void:
_legend.hide()
func set_color(color: Color) -> void:
get("theme_override_styles/panel").set("bg_color", color)
func set_frame_visible(visible: bool) -> void:
get("theme_override_styles/panel").set("draw_center", visible)

View file

@ -0,0 +1 @@
uid://d0q44x2e5wxym

View file

@ -0,0 +1,176 @@
extends Control
class_name GridBox
var x_domain: Dictionary = { lb = 0, ub = 0 }
var x_labels_function: Callable = Callable()
var y_domain: Dictionary = { lb = 0, ub = 0 }
var y_labels_function: Callable = Callable()
var box: Rect2
var plot_box: Rect2
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
func set_domains(x_domain: Dictionary, y_domain: Dictionary) -> void:
self.x_domain = x_domain
self.y_domain = y_domain
func set_labels_functions(x_labels_function: Callable, y_labels_function: Callable) -> void:
self.x_labels_function = x_labels_function
self.y_labels_function = y_labels_function
func _draw() -> void:
if get_parent().chart_properties == null:
printerr("Cannot draw GridBox without ChartProperties!")
return
self.box = get_parent().get_box()
self.plot_box = get_parent().get_plot_box()
if get_parent().chart_properties.draw_background:
_draw_background()
if get_parent().chart_properties.draw_grid_box:
_draw_vertical_grid()
_draw_horizontal_grid()
if get_parent().chart_properties.draw_origin:
_draw_origin()
if get_parent().chart_properties.draw_bounding_box:
_draw_bounding_box()
func _draw_background() -> void:
draw_rect(self.box, get_parent().chart_properties.colors.background, true)# false) TODOGODOT4 Antialiasing argument is missing
func _draw_bounding_box() -> void:
var box: Rect2 = self.box
box.position.y += 1
draw_rect(box, get_parent().chart_properties.colors.bounding_box, false, 1)# true) TODOGODOT4 Antialiasing argument is missing
func _draw_origin() -> void:
var xorigin: float = ECUtilities._map_domain(0.0, x_domain, { lb = self.plot_box.position.x, ub = self.plot_box.end.x })
var yorigin: float = ECUtilities._map_domain(0.0, y_domain, { lb = self.plot_box.end.y, ub = self.plot_box.position.y })
draw_line(Vector2(xorigin, self.plot_box.position.y), Vector2(xorigin, self.plot_box.position.y + self.plot_box.size.y), get_parent().chart_properties.colors.origin, 1)
draw_line(Vector2(self.plot_box.position.x, yorigin), Vector2(self.plot_box.position.x + self.plot_box.size.x, yorigin), get_parent().chart_properties.colors.origin, 1)
draw_string(
get_parent().chart_properties.font, Vector2(xorigin, yorigin) - Vector2(15, -15), "O", HORIZONTAL_ALIGNMENT_CENTER, -1,
ThemeDB.fallback_font_size, get_parent().chart_properties.colors.text, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL
)
func _draw_vertical_grid() -> void:
# draw vertical lines
# 1. the amount of lines is equals to the X_scale: it identifies in how many sectors the x domain
# should be devided
# 2. calculate the spacing between each line in pixel. It is equals to x_sampled_domain / x_scale
# 3. calculate the offset in the real x domain, which is x_domain / x_scale.
var scaler: int = get_parent().chart_properties.x_scale
var x_pixel_dist: float = self.plot_box.size.x / scaler
var vertical_grid: PackedVector2Array = []
var vertical_ticks: PackedVector2Array = []
for _x in (scaler + 1):
var x_sampled_val: float = (_x * x_pixel_dist) + self.plot_box.position.x
var x_val: float = ECUtilities._map_domain(x_sampled_val, { lb = self.plot_box.position.x, ub = self.plot_box.end.x }, x_domain)
var top: Vector2 = Vector2(x_sampled_val, self.box.position.y)
var bottom: Vector2 = Vector2(x_sampled_val, self.box.end.y)
vertical_grid.append(top)
vertical_grid.append(bottom)
vertical_ticks.append(bottom)
vertical_ticks.append(bottom + Vector2(0, get_parent().chart_properties.x_tick_size))
# Draw V Tick Labels
if get_parent().chart_properties.show_tick_labels:
var tick_lbl: String = _get_tick_label(_x, x_val, x_domain.has_decimals, self.x_labels_function)
draw_string(
get_parent().chart_properties.font,
_get_vertical_tick_label_pos(bottom, tick_lbl),
tick_lbl,HORIZONTAL_ALIGNMENT_CENTER, -1, ThemeDB.fallback_font_size,
get_parent().chart_properties.colors.text, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO,
TextServer.ORIENTATION_HORIZONTAL
)
# Draw V Grid
if get_parent().chart_properties.draw_vertical_grid:
draw_multiline(vertical_grid, get_parent().chart_properties.colors.grid, 1)
# Draw V Ticks
if get_parent().chart_properties.draw_ticks:
draw_multiline(vertical_ticks, get_parent().chart_properties.colors.ticks, 1)
func _draw_horizontal_grid() -> void:
# 1. the amount of lines is equals to the y_scale: it identifies in how many sectors the y domain
# should be devided
# 2. calculate the spacing between each line in pixel. It is equals to y_sampled_domain / y_scale
# 3. calculate the offset in the real y domain, which is y_domain / y_scale.
var scaler: int = get_parent().chart_properties.y_scale
var y_pixel_dist: float = self.plot_box.size.y / scaler
var horizontal_grid: PackedVector2Array = []
var horizontal_ticks: PackedVector2Array = []
for _y in (scaler + 1):
var y_sampled_val: float = (_y * y_pixel_dist) + self.plot_box.position.y
var y_val: float = ECUtilities._map_domain(y_sampled_val, { lb = self.plot_box.end.y, ub = self.plot_box.position.y }, y_domain)
var left: Vector2 = Vector2(self.box.position.x, y_sampled_val)
var right: Vector2 = Vector2(self.box.end.x, y_sampled_val)
horizontal_grid.append(left)
horizontal_grid.append(right)
horizontal_ticks.append(left)
horizontal_ticks.append(left - Vector2(get_parent().chart_properties.y_tick_size, 0))
# Draw H Tick Labels
if get_parent().chart_properties.show_tick_labels:
var tick_lbl: String = _get_tick_label(_y, y_val, y_domain.has_decimals, y_labels_function)
draw_string(
get_parent().chart_properties.font,
_get_horizontal_tick_label_pos(left, tick_lbl),
tick_lbl,
HORIZONTAL_ALIGNMENT_CENTER,
-1, ThemeDB.fallback_font_size,
get_parent().chart_properties.colors.text,
TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL
)
# Draw H Grid
if get_parent().chart_properties.draw_horizontal_grid:
draw_multiline(horizontal_grid, get_parent().chart_properties.colors.grid, 1)
# Draw H Ticks
if get_parent().chart_properties.draw_ticks:
draw_multiline(horizontal_ticks, get_parent().chart_properties.colors.ticks, 1)
func _get_vertical_tick_label_pos(base_position: Vector2, text: String) -> Vector2:
return base_position + Vector2(
- get_parent().chart_properties.font.get_string_size(text).x / 2,
ThemeDB.fallback_font_size + get_parent().chart_properties.x_tick_size
)
func _get_horizontal_tick_label_pos(base_position: Vector2, text: String) -> Vector2:
return base_position - Vector2(
get_parent().chart_properties.font.get_string_size(text).x + get_parent().chart_properties.y_tick_size + get_parent().chart_properties.x_ticklabel_space,
- ThemeDB.fallback_font_size * 0.35
)
func _get_tick_label(line_index: int, line_value: float, axis_has_decimals: bool, labels_function: Callable) -> String:
var tick_lbl: String = ""
if labels_function.is_null():
tick_lbl = ECUtilities._format_value(line_value, axis_has_decimals)
else:
tick_lbl = labels_function.call(line_value)
return tick_lbl

View file

@ -0,0 +1 @@
uid://oql4qoedksyg

View file

@ -0,0 +1,56 @@
extends Control
class_name PlotBox
signal function_point_entered(point, function)
signal function_point_exited(point, function)
@onready var tooltip: DataTooltip = $Tooltip
var focused_point: Point
var focused_function: Function
var x_labels_function: Callable = Callable()
var y_labels_function: Callable = Callable()
var box_margins: Vector2 # Margins relative to this rect, in order to make space for ticks and tick_labels
var plot_inner_offset: Vector2 = Vector2(15, 15) # How many pixels from the broders should the plot be
var chart_properties: ChartProperties
func set_labels_functions(x_labels_function: Callable, y_labels_function: Callable) -> void:
self.x_labels_function = x_labels_function
self.y_labels_function = y_labels_function
func get_box() -> Rect2:
var box: Rect2 = get_rect()
box.position.x += box_margins.x
# box.position.y += box_margins.y
box.end.x -= box_margins.x
box.end.y -= box_margins.y
return box
func get_plot_box() -> Rect2:
var inner_box: Rect2 = get_box()
inner_box.position.x += plot_inner_offset.x
inner_box.position.y += plot_inner_offset.y
inner_box.end.x -= plot_inner_offset.x * 2
inner_box.end.y -= plot_inner_offset.y * 2
return inner_box
func _on_point_entered(point: Point, function: Function, props: Dictionary = {}) -> void:
self.focused_function = function
var x_value: String = x_labels_function.call(point.value.x) if not x_labels_function.is_null() else \
point.value.x if point.value.x is String else ECUtilities._format_value(point.value.x, ECUtilities._is_decimal(point.value.x))
var y_value: String = y_labels_function.call(point.value.y) if not y_labels_function.is_null() else \
point.value.y if point.value.y is String else ECUtilities._format_value(point.value.y, ECUtilities._is_decimal(point.value.y))
var color: Color = function.get_color() if function.get_type() != Function.Type.PIE \
else function.get_gradient().sample(props.interpolation_index)
tooltip.show()
tooltip.update_values(x_value, y_value, function.name, color)
tooltip.update_position(point.position)
emit_signal("function_point_entered", point, function)
func _on_point_exited(point: Point, function: Function) -> void:
if function != self.focused_function:
return
tooltip.hide()
emit_signal("function_point_exited", point, function)

View file

@ -0,0 +1 @@
uid://bt0h3q1ocyhu8

View file

@ -0,0 +1,44 @@
@tool
extends PanelContainer
class_name DataTooltip
var gap: float = 15
@onready var x_lbl : Label = $PointData/x
@onready var y_lbl : Label = $PointData/Value/y
@onready var func_lbl : Label = $PointData/Value/Function
@onready var color_rect: Panel = $PointData/Value/Color
func _ready():
hide()
update_size()
func update_position(position: Vector2) -> void:
if (position.x + gap + size.x) > get_parent().size.x:
self.position = position - Vector2(size.x + gap, (get_rect().size.y / 2))
else:
self.position = position + Vector2(15, - (get_rect().size.y / 2))
#func _process(delta):
# if Engine.editor_hint:
# return
# rect_position = get_global_mouse_position() + Vector2(15, - (get_rect().size.y / 2))
func set_font(font: FontFile) -> void:
theme.set("default_font", font)
func update_values(x: String, y: String, function_name: String, color: Color):
x_lbl.set_text(x)
y_lbl.set_text(y)
func_lbl.set_text(function_name)
color_rect.get("theme_override_styles/panel").set("bg_color", color)
func update_size():
x_lbl.set_text("")
y_lbl.set_text("")
func_lbl.set_text("")
size = Vector2.ZERO
func _on_DataTooltip_visibility_changed():
if not visible:
update_size()

View file

@ -0,0 +1 @@
uid://b13bsc7on5q6e

View file

@ -0,0 +1,114 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://addons/easy_charts/utilities/containers/data_tooltip/data_tooltip.gd" type="Script" id=1]
[sub_resource type="StyleBoxFlat" id=1]
content_margin_left = 10.0
content_margin_right = 10.0
content_margin_top = 8.0
content_margin_bottom = 8.0
bg_color = Color( 0.101961, 0.101961, 0.101961, 0.784314 )
border_color = Color( 1, 1, 1, 1 )
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
corner_detail = 20
anti_aliasing_size = 0.65
[sub_resource type="StyleBoxFlat" id=2]
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
corner_detail = 20
anti_aliasing_size = 0.7
[sub_resource type="StyleBoxEmpty" id=3]
[node name="DataTooltip" type="PanelContainer"]
visible = false
offset_right = 20.0
offset_bottom = 16.0
mouse_filter = 2
theme_override_styles/panel = SubResource( 1 )
script = ExtResource( 1 )
[node name="PointData" type="VBoxContainer" parent="."]
offset_left = 10.0
offset_top = 8.0
offset_right = 37.0
offset_bottom = 42.0
grow_horizontal = 2
size_flags_horizontal = 3
theme_override_constants/separation = 1
alignment = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="x" type="Label" parent="PointData"]
offset_top = 2.0
offset_bottom = 16.0
size_flags_horizontal = 0
theme_override_colors/font_color = Color( 1, 1, 1, 1 )
valign = 1
[node name="Value" type="HBoxContainer" parent="PointData"]
offset_top = 17.0
offset_right = 27.0
offset_bottom = 31.0
grow_horizontal = 2
size_flags_horizontal = 7
theme_override_constants/separation = 1
[node name="Color" type="Panel" parent="PointData/Value"]
offset_top = 2.0
offset_right = 10.0
offset_bottom = 12.0
custom_minimum_size = Vector2( 10, 10 )
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource( 2 )
[node name="VSeparator" type="VSeparator" parent="PointData/Value"]
offset_left = 11.0
offset_right = 15.0
offset_bottom = 14.0
theme_override_constants/separation = 4
theme_override_styles/separator = SubResource( 3 )
[node name="Function" type="Label" parent="PointData/Value"]
offset_left = 16.0
offset_right = 16.0
offset_bottom = 14.0
size_flags_horizontal = 0
size_flags_vertical = 5
valign = 1
[node name="sep" type="Label" parent="PointData/Value"]
offset_left = 17.0
offset_right = 21.0
offset_bottom = 14.0
size_flags_horizontal = 0
size_flags_vertical = 5
text = ":"
valign = 1
[node name="VSeparator2" type="VSeparator" parent="PointData/Value"]
offset_left = 22.0
offset_right = 26.0
offset_bottom = 14.0
theme_override_constants/separation = 4
theme_override_styles/separator = SubResource( 3 )
[node name="y" type="Label" parent="PointData/Value"]
offset_left = 27.0
offset_right = 27.0
offset_bottom = 14.0
size_flags_horizontal = 0
size_flags_vertical = 5
theme_override_colors/font_color = Color( 1, 1, 1, 1 )
valign = 1
[connection signal="visibility_changed" from="." to="." method="_on_DataTooltip_visibility_changed"]

View file

@ -0,0 +1,23 @@
extends HBoxContainer
class_name FunctionLabel
@onready var type_lbl: FunctionTypeLabel = $FunctionType
@onready var name_lbl: Label = $FunctionName
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
func init_label(function: Function) -> void:
type_lbl.type = function.get_type()
type_lbl.color = function.get_color()
type_lbl.marker = function.get_marker()
name_lbl.set_text(function.name)
name_lbl.set("theme_override_colors/font_color", get_parent().chart_properties.colors.text)
func init_clabel(type: int, color: Color, marker: int, name: String) -> void:
type_lbl.type = type
type_lbl.color = color
type_lbl.marker = marker
name_lbl.set_text(name)
name_lbl.set("theme_override_colors/font_color", get_parent().chart_properties.colors.text)

View file

@ -0,0 +1 @@
uid://bkdv3quptaxde

View file

@ -0,0 +1,24 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://addons/easy_charts/utilities/containers/legend/function_type.gd" type="Script" id=1]
[ext_resource path="res://addons/easy_charts/utilities/containers/legend/function_label.gd" type="Script" id=2]
[node name="FunctionLabel" type="HBoxContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
theme_override_constants/separation = 5
script = ExtResource( 2 )
[node name="FunctionType" type="Label" parent="."]
offset_top = 293.0
offset_right = 20.0
offset_bottom = 307.0
custom_minimum_size = Vector2( 20, 0 )
script = ExtResource( 1 )
[node name="FunctionName" type="Label" parent="."]
offset_left = 25.0
offset_top = 293.0
offset_right = 25.0
offset_bottom = 307.0
theme_override_colors/font_color = Color( 0, 0, 0, 1 )

View file

@ -0,0 +1,23 @@
extends VBoxContainer
class_name FunctionLegend
@onready var f_label_scn: PackedScene = preload("res://addons/easy_charts/utilities/containers/legend/function_label.tscn")
var chart_properties: ChartProperties
func _ready() -> void:
pass
func clear() -> void:
for label in get_children():
label.queue_free()
func add_function(function: Function) -> void:
var f_label: FunctionLabel = f_label_scn.instantiate()
add_child(f_label)
f_label.init_label(function)
func add_label(type: int, color: Color, marker: int, name: String) -> void:
var f_label: FunctionLabel = f_label_scn.instantiate()
add_child(f_label)
f_label.init_clabel(type, color, marker, name)

View file

@ -0,0 +1 @@
uid://b8cjm6kaq00kl

View file

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/easy_charts/utilities/containers/legend/function_legend.gd" type="Script" id=1]
[node name="FunctionLegend" type="VBoxContainer"]
offset_right = 80.0
offset_bottom = 26.0
script = ExtResource( 1 )

View file

@ -0,0 +1,69 @@
extends Label
class_name FunctionTypeLabel
var type: int
var marker: int
var color: Color
func _draw() -> void:
var center: Vector2 = get_rect().get_center()
match self.type:
Function.Type.LINE:
draw_line(
Vector2(get_rect().position.x, center.y),
Vector2(get_rect().end.x, center.y),
color, 3
)
Function.Type.AREA:
var c2: Color = color
c2.a = 0.3
draw_rect(
Rect2(
Vector2(get_rect().position.x, center.y),
Vector2(get_rect().end.x, get_rect().end.y / 2)
),
c2, 3
)
draw_line(
Vector2(get_rect().position.x, center.y),
Vector2(get_rect().end.x, center.y),
color, 3
)
Function.Type.PIE:
draw_rect(
Rect2(center - (Vector2.ONE * 3), (Vector2.ONE * 3 * 2)),
color, true, 1.0
)
Function.Type.SCATTER, _:
pass
match self.marker:
Function.Marker.NONE:
pass
Function.Marker.SQUARE:
draw_rect(
Rect2(center - (Vector2.ONE * 3), (Vector2.ONE * 3 * 2)),
color, true, 1.0
)
Function.Marker.TRIANGLE:
draw_colored_polygon(
PackedVector2Array([
center + (Vector2.UP * 3 * 1.3),
center + (Vector2.ONE * 3 * 1.3),
center - (Vector2(1, -1) * 3 * 1.3)
]), color, [], null
)
Function.Marker.CROSS:
draw_line(
center - (Vector2.ONE * 3),
center + (Vector2.ONE * 3),
color, 3, true
)
draw_line(
center + (Vector2(1, -1) * 3),
center + (Vector2(-1, 1) * 3),
color, 3 / 2, true
)
Function.Marker.CIRCLE, _:
draw_circle(center, 3, color)

View file

@ -0,0 +1 @@
uid://chxuvkwtm4y6u

View file

@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="24.276" xmlns="http://www.w3.org/2000/svg" height="24.276" id="screenshot-fea98781-87c2-80d4-8002-03a4453d007c" viewBox="-0.138 -0.138 24.276 24.276" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g id="shape-fea98781-87c2-80d4-8002-03a4453d007c" width="12" version="1.1" height="12" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-fea98781-87c2-80d4-8002-03a4453ee7a8"><defs id="defs2" rx="0" ry="0" style="fill: rgb(0, 0, 0);"/></g><g id="shape-fea98781-87c2-80d4-8002-03a4453f6292" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-fea98781-87c2-80d4-8002-03a44542b380"><g class="fills" id="fills-fea98781-87c2-80d4-8002-03a44542b380"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="3" height="23.999930224010313" style="stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: rgb(165, 239, 172); fill-opacity: 1;"/></g><g id="strokes-fea98781-87c2-80d4-8002-03a44542b380" class="strokes"><g class="stroke-shape"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="3" height="23.999930224010313" style="stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: none; stroke-width: 0.275386; stroke-opacity: 1;"/></g></g></g><g id="shape-fea98781-87c2-80d4-8002-03a44542dd7d"><g class="fills" id="fills-fea98781-87c2-80d4-8002-03a44542dd7d"><rect rx="0" ry="0" x="0" y="21" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="24" height="3" style="stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: rgb(165, 239, 172); fill-opacity: 1;"/></g><g id="strokes-fea98781-87c2-80d4-8002-03a44542dd7d" class="strokes"><g class="stroke-shape"><rect rx="0" ry="0" x="0" y="21" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="24" height="3" style="stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: none; stroke-width: 0.276348; stroke-opacity: 1;"/></g></g></g><g id="shape-fea98781-87c2-80d4-8002-03a44543293d"><g class="fills" id="fills-fea98781-87c2-80d4-8002-03a44543293d"><rect rx="1.5" ry="1.5" x="7.400090781751828" y="10.320378028651703" transform="matrix(-0.485732, -0.874108, 0.895244, -0.445577, 7.469654, 27.707754)" width="9.499999999999886" height="3" style="fill-rule: evenodd; stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: rgb(165, 239, 172); fill-opacity: 1;"/></g></g><g id="shape-fea98781-87c2-80d4-8002-03a44543a59d"><g class="fills" id="fills-fea98781-87c2-80d4-8002-03a44543a59d"><rect rx="1.5" ry="1.5" x="9.916580393732545" y="9.051625054708893" transform="matrix(0.493265, -0.869879, -0.891361, -0.453294, 17.850846, 29.832525)" width="13.5" height="3" style="fill-rule: evenodd; stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: rgb(165, 239, 172); fill-opacity: 1;"/></g></g><g id="shape-fea98781-87c2-80d4-8002-03a44544bca7"><g class="fills" id="fills-fea98781-87c2-80d4-8002-03a44544bca7"><rect rx="1.5" ry="1.5" x="2.4233153669828766" y="11.181745974957721" transform="matrix(0.493265, -0.869879, -0.891361, -0.453294, 15.445718, 25.540104)" width="11.499999999999943" height="3" style="fill-rule: evenodd; stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: rgb(165, 239, 172); fill-opacity: 1;"/></g></g><g id="shape-fea98781-87c2-80d4-8002-03a44546f189"><g class="fills" id="fills-fea98781-87c2-80d4-8002-03a44546f189"><path rx="0" ry="0" d="M20.752,0.000C19.889,-0.001,19.062,0.341,18.452,0.951C17.842,1.561,17.500,2.388,17.500,3.250C17.500,4.112,17.842,4.939,18.452,5.549C19.062,6.159,19.889,6.501,20.752,6.500C21.614,6.500,22.440,6.157,23.049,5.548C23.658,4.938,24.000,4.112,24.000,3.250C24.000,2.388,23.658,1.562,23.049,0.952C22.440,0.343,21.614,0.000,20.752,0.000ZZM20.752,1.680C21.160,1.680,21.551,1.845,21.839,2.140C22.127,2.434,22.289,2.834,22.289,3.250C22.289,3.666,22.127,4.066,21.839,4.360C21.551,4.655,21.160,4.820,20.752,4.820C20.344,4.820,19.953,4.655,19.665,4.360C19.377,4.066,19.215,3.666,19.215,3.250C19.215,2.834,19.377,2.434,19.665,2.140C19.953,1.845,20.344,1.680,20.752,1.680ZZ" style="fill-rule: evenodd; stroke: none; stroke-miterlimit: 4; stroke-dasharray: none; fill: rgb(165, 239, 172); fill-opacity: 1;"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cbpprqhqhq1xp"
path="res://.godot/imported/linechart.svg-922834f0462a2c88be644081c47c63ad.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/easy_charts/utilities/icons/linechart.svg"
dest_files=["res://.godot/imported/linechart.svg-922834f0462a2c88be644081c47c63ad.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,71 @@
extends RefCounted
class_name ECUtilities
var alphabet : String = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
func _ready():
pass
static func _map_domain(value: float, from_domain: Dictionary, to_domain: Dictionary) -> float:
return remap(value, from_domain.lb, from_domain.ub, to_domain.lb, to_domain.ub)
static func _format_value(value: float, is_decimal: bool) -> String:
return ("%.2f" if is_decimal else "%s") % snapped(value, 0.01)
### Utility Inner functions ###
static func _contains_string(array: Array) -> bool:
for value in array:
if value is String:
return true
return false
static func _is_decimal(value: float) -> bool:
return abs(fmod(value, 1)) > 0.0
static func _has_decimals(values: Array) -> bool:
var temp: Array = values.duplicate(true)
for dim in temp:
for val in dim:
if val is String:
return false
if abs(fmod(val, 1)) > 0.0:
return true
return false
static func _find_min_max(values: Array) -> Dictionary:
var temp: Array = values.duplicate(true)
var _min: float
var _max: float
var min_ts: Array
var max_ts: Array
for dim in temp:
min_ts.append(dim.min())
max_ts.append(dim.max())
_min = min_ts.min()
_max = max_ts.max()
return { min = _min, max = _max }
static func _sample_values(values: Array, from_domain: Dictionary, to_domain: Dictionary) -> PackedFloat32Array:
if values.is_empty():
printerr("Trying to plot an empty dataset!")
return PackedFloat32Array()
# We are not considering String values here!!!
var sampled: PackedFloat32Array = []
for value in values:
sampled.push_back(_map_domain(value, from_domain, to_domain))
return sampled
static func _round_min(val: float) -> float:
return round(val) if abs(val) < 10 else floor(val / 10.0) * 10.0
static func _round_max(val: float) -> float:
return round(val) if abs(val) < 10 else ceil(val / 10.0) * 10.0

View file

@ -0,0 +1 @@
uid://dc2s3hmwcx2p0

41
easy_charts.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 71 KiB

43
easy_charts.svg.import Normal file
View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://mxify2wd8r0m"
path="res://.godot/imported/easy_charts.svg-5963eccbc8d55a9c4bb3a7a394bde2c4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://easy_charts.svg"
dest_files=["res://.godot/imported/easy_charts.svg-5963eccbc8d55a9c4bb3a7a394bde2c4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

33
examples/plot_sint.gd Normal file
View file

@ -0,0 +1,33 @@
@tool
extends Control
var plot_sin
var x = 0.0
var draw_enabled = false:
set(value):
draw_enabled = value
# if is_instance_valid($Graph2D):
# $Graph2D.background_color = Color.SLATE_GRAY if draw_enabled else Color.BLACK
func _ready():
plot_sin = $Graph2D.add_plot_item("Sin(x)", Color.RED, 0.5)
func _process(_delta):
if draw_enabled:
var y: float = sin(x)
plot_sin.add_point(Vector2(x,y))
x += 0.1
if draw_enabled and x > $Graph2D.x_max:
draw_enabled = false
func _on_draw_button_pressed() -> void:
draw_enabled = true
plot_sin.remove_all()
x = 0.0
func _on_clear_button_pressed() -> void:
draw_enabled = false
plot_sin.remove_all()
x = 0.0

View file

@ -0,0 +1 @@
uid://dun708xbgrl0u

59
examples/plot_sint.tscn Normal file
View file

@ -0,0 +1,59 @@
[gd_scene load_steps=3 format=3 uid="uid://dwuptr3eos1kt"]
[ext_resource type="Script" path="res://examples/plot_sint.gd" id="1_nl7wu"]
[ext_resource type="Script" path="res://addons/graph_2d/graph_2d.gd" id="2_o0pag"]
[node name="PlotSint" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_nl7wu")
[node name="Graph2D" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2_o0pag")
x_label = "t[s]"
y_min = -2.0
y_max = 2.0
y_label = "y"
grid_horizontal_visible = true
grid_vertical_visible = true
metadata/_edit_layout_mode = 0
metadata/_edit_use_custom_anchors = false
[node name="DrawButton" type="Button" parent="Graph2D"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -48.0
offset_right = 48.0
offset_bottom = 31.0
grow_horizontal = 2
text = "Draw Sin(x)"
metadata/_edit_layout_mode = 0
metadata/_edit_use_custom_anchors = false
[node name="ClearButton" type="Button" parent="Graph2D"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = 58.0
offset_right = 107.0
offset_bottom = 31.0
grow_horizontal = 2
text = "Clear"
metadata/_edit_layout_mode = 0
metadata/_edit_use_custom_anchors = false
[connection signal="pressed" from="Graph2D/DrawButton" to="." method="_on_draw_button_pressed"]
[connection signal="pressed" from="Graph2D/ClearButton" to="." method="_on_clear_button_pressed"]

16
examples/single_plot.gd Normal file
View file

@ -0,0 +1,16 @@
extends Control
func _on_add_plot_pressed() -> void:
var my_plot = $Graph2D.add_plot_item(
"Plot %d" % [$Graph2D.count()],
[Color.RED, Color.GREEN, Color.BLUE][$Graph2D.count() % 3],
[1.0, 3.0, 5.0].pick_random()
)
for x in range(0, 11, 1):
var y = randf_range(-50, 50)
my_plot.add_point(Vector2(x, y))
func _on_remove_all_plots_pressed() -> void:
$Graph2D.remove_all()

View file

@ -0,0 +1 @@
uid://b4bpgh57roql0

48
examples/single_plot.tscn Normal file
View file

@ -0,0 +1,48 @@
[gd_scene load_steps=3 format=3 uid="uid://br4viqfrbdlpe"]
[ext_resource type="Script" path="res://examples/single_plot.gd" id="1_qmytq"]
[ext_resource type="Script" path="res://addons/graph_2d/graph_2d.gd" id="2_2ul57"]
[node name="SinglePlot" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_qmytq")
[node name="Graph2D" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2_2ul57")
x_label = "Time(s)"
y_min = -53.0
y_max = 50.0
y_label = "Y"
background_color = Color(0.0941176, 0.227451, 0.4, 1)
grid_horizontal_visible = true
grid_vertical_visible = true
[node name="AddPlot" type="Button" parent="."]
layout_mode = 0
offset_left = 280.0
offset_top = 32.0
offset_right = 393.0
offset_bottom = 63.0
text = "Add New Plot"
[node name="RemoveAllPlots" type="Button" parent="."]
layout_mode = 0
offset_left = 400.0
offset_top = 32.0
offset_right = 537.0
offset_bottom = 63.0
text = "Remove All Plots"
[connection signal="pressed" from="AddPlot" to="." method="_on_add_plot_pressed"]
[connection signal="pressed" from="RemoveAllPlots" to="." method="_on_remove_all_plots_pressed"]

0
imgs/.gdignore Normal file
View file

BIN
imgs/example03.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

BIN
imgs/multiplot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Some files were not shown because too many files have changed in this diff Show more