Skip to content

Commit

Permalink
Updates (#66)
Browse files Browse the repository at this point in the history
* further optimizations

* abstract `Chart` class

* (add) `PieChart` + example
  • Loading branch information
fenix-hub authored Jan 28, 2023
1 parent a54196e commit dbdf303
Show file tree
Hide file tree
Showing 16 changed files with 591 additions and 284 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ scn/
.import/
export.cfg
export_presets.cfg
*.import

# Mono-specific ignores
.mono/
data_*/
data_*/
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<img src="easy_charts.svg" align="center">
<img src="easy_charts.svg" align="middle">

Charts for Godot Engine, made easy.
> Charts for Godot Engine, made easy.
## 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.
Expand All @@ -19,7 +19,7 @@ This library offers a set of charts for each main Godot Node:
| LineChart ||||
| BarChart ||||
| AreaChart ||||
| PieChart | |||
| PieChart | |||
| RadarChart ||||
| BubbleChart ||||
| DonutChart ||||
Expand Down
77 changes: 53 additions & 24 deletions addons/easy_charts/control_charts/BarChart/bar_chart.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ var bars: Array = []

# List of all bars, grouped by function
var function_bars: Array = []
var function_bars_pos: Array = []

# Currently focused bar
var focused_bar: Bar = null

func _clear_bars() -> void:
bars.clear()
function_bars.clear()

func _clear() -> void:
_clear_bars()
func _draw() -> void:
# Draw Bars
_calculate_bars()
_draw_bars()

func _calc_x_domain() -> void:
pass
Expand Down Expand Up @@ -73,11 +73,14 @@ func _get_vertical_tick_label_pos(base_position: Vector2, text: String) -> Vecto

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 vertical_grid: Array = []
var vertical_ticks: Array = []
for _x in x.size():
var top: Vector2 = Vector2(
(_x * x_sector_size) + plot_box.position.x,
Expand All @@ -87,43 +90,69 @@ func _draw_vertical_grid() -> void:
(_x * x_sector_size) + plot_box.position.x,
bounding_box.end.y
)
vertical_grid.append(top)
vertical_grid.append(bottom)

vertical_ticks.append(bottom)
vertical_ticks.append(bottom + Vector2(0, _x_tick_size))

_draw_vertical_gridline_component(top, bottom, _x, x_sector_size)
# Draw V Tick Labels
if chart_properties.labels:
var tick_lbl: String = x[_x]
draw_string(
chart_properties.font,
_get_vertical_tick_label_pos(bottom, tick_lbl),
tick_lbl,
chart_properties.colors.bounding_box
)

### Draw last gridline
var p1: Vector2 = Vector2(
var top: Vector2 = Vector2(
(x.size() * x_sector_size) + plot_box.position.x,
bounding_box.position.y
)
vertical_grid.append(top)

var p2: Vector2 = Vector2(
var bottom: Vector2 = Vector2(
(x.size() * x_sector_size) + plot_box.position.x,
bounding_box.end.y
)

# Draw V Ticks
if chart_properties.ticks:
_draw_tick(p2, p2 + Vector2(0, _x_tick_size), chart_properties.colors.bounding_box)
vertical_grid.append(bottom)
vertical_ticks.append(bottom)
vertical_ticks.append(bottom + Vector2(0, _x_tick_size))

# Draw V Grid Lines
if chart_properties.grid:
draw_line(p1, p2, chart_properties.colors.grid, 1, true)
draw_multiline(vertical_grid, chart_properties.colors.grid, 1, true)

# Draw V Ticks
if chart_properties.ticks:
draw_multiline(vertical_ticks, chart_properties.colors.bounding_box, 1, true)


func _calculate_bars() -> void:
var validation: int = _validate_sampled_axis(x_sampled, y_sampled)
if not validation == OK:
printerr("Cannot plot bars for invalid dataset! Error: %s" % validation)
return

bars.clear()
function_bars.clear()
function_bars_pos.clear()

if y_sampled.values[0] is Array:
for yxi in y_sampled.values.size():
var _function_bars: Array = []
for i in y_sampled.values[yxi].size():
var real_bar_value: Pair = Pair.new(x[i], y[yxi][i])
var sampled_bar_pos: Vector2 = Vector2(
(x_sector_size * i) + x_sampled_domain.left + (x_sector_size / 2) - (chart_properties.bar_width / 2),
var center_bar_pos: Vector2 = Vector2(
(x_sector_size * i) + (x_sector_size / 2) + x_sampled_domain.left,
y_sampled.values[yxi][i]
)
var sampled_bar_pos: Vector2 = center_bar_pos - Vector2(
chart_properties.bar_width / 2,
0
)
var sampled_bar_size: Vector2 = Vector2(
chart_properties.bar_width,
y_sampled_domain.left - y_sampled.values[yxi][i]
Expand All @@ -135,10 +164,14 @@ func _calculate_bars() -> void:
else:
for i in y_sampled.values.size():
var real_bar_value: Pair = Pair.new(x[i], y[i])
var sampled_bar_pos: Vector2 = Vector2(
(x_sector_size * i) + x_sampled_domain.left + (x_sector_size / 2) - chart_properties.bar_width,
var center_bar_pos: Vector2 = Vector2(
(x_sector_size * i) + (x_sector_size / 2) + x_sampled_domain.left,
y_sampled.values[i]
)
var sampled_bar_pos: Vector2 = center_bar_pos - Vector2(
chart_properties.bar_width / 2,
0
)
var sampled_bar_size: Vector2 = Vector2(
chart_properties.bar_width,
y_sampled_domain.left - y_sampled.values[i]
Expand All @@ -147,10 +180,6 @@ func _calculate_bars() -> void:
bars.append(bar)
function_bars.append(bars)

func _draw() -> void:
_calculate_bars()

_draw_bars()

func _get_function_bar(bar: Bar) -> int:
var bar_f_index: int = -1
Expand All @@ -173,8 +202,8 @@ func _input(event: InputEvent) -> void:
$Tooltip.update_values(
str(focused_bar.value.left),
str(focused_bar.value.right),
_get_function_name(func_index),
_get_function_color(func_index)
chart_properties.get_function_name(func_index),
chart_properties.get_function_color(func_index)
)
$Tooltip.show()
emit_signal("bar_entered", bar)
Expand Down
22 changes: 15 additions & 7 deletions addons/easy_charts/control_charts/LineChart/line_chart.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func _draw_line(from: Point, to: Point, function_index: int) -> void:
true
)

func _draw_spline(points: Array, function: int, density: float = 10.0, tension: float = 1) -> void:
func _get_spline_points(points: Array, density: float = 10.0, tension: float = 1) -> Array:
var spline_points: Array = []

var augmented: Array = points.duplicate(true)
Expand All @@ -31,16 +31,24 @@ func _draw_spline(points: Array, function: int, density: float = 10.0, tension:
f / density)
)

for i in range(1, spline_points.size()):
draw_line(spline_points[i-1], spline_points[i], chart_properties.get_function_color(function), chart_properties.line_width, true)
return spline_points

func _draw_lines() -> void:
for function in function_points.size():
for function_i in function_points_pos.size():
if chart_properties.use_splines:
_draw_spline(function_points[function], function)
draw_polyline(
_get_spline_points(function_points[function_i]),
chart_properties.get_function_color(function_i),
chart_properties.line_width,
true
)
else:
for i in range(1, function_points[function].size()):
_draw_line(function_points[function][i - 1], function_points[function][i], function)
draw_polyline(
function_points_pos[function_i],
chart_properties.get_function_color(function_i),
chart_properties.line_width,
true
)

func _draw() -> void:
_draw_lines()
124 changes: 124 additions & 0 deletions addons/easy_charts/control_charts/PieChart/pie_chart.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
extends AbstractChart
class_name PieChart

signal slice_entered(slice)

var values: Array = []

var radius_multiplayer: float = 1.0

#### INTERNAL
var ratios: Array = []
var total: float = 0.0

var _slices_polygons: Array = []
var _slices_dirs: Array = []

var _radius: float

var focused_slice: PoolVector2Array

func _ready() -> void:
set_process_input(false)
set_process(false)

func plot(values: Array, properties: ChartProperties = self.chart_properties) -> void:
self.values = values

if properties != null:
self.chart_properties = properties
self.chart_properties.bounding_box = false

set_process_input(chart_properties.interactive)

func _draw() -> void:
_calc_slices()
_draw_pie()

func _calc_slices() -> void:
_radius = min(plot_box.size.x, plot_box.size.y) * 0.5 * radius_multiplayer
# Calculate total and ratios
var total: float = 0.0
for value in values:
total += float(value)

ratios.clear()
for value in values:
ratios.append(value / total * 100)

# Calculate directions
_slices_polygons.clear()
_slices_dirs.clear()
var center: Vector2 = plot_box.get_center()
var start_angle: float = 0.0
for i in ratios.size():
var end_angle: float = start_angle + (2 * PI * float(ratios[i]) * 0.01)
_slices_polygons.append(
_calc_circle_arc_poly(
plot_box.get_center(),
_radius,
start_angle,
end_angle
)
)
var mid_point: Vector2 = ((center + (Vector2.RIGHT.rotated(start_angle).normalized() * _radius)) + (center + (Vector2.RIGHT.rotated(end_angle).normalized() * _radius))) / 2
_slices_dirs.append(center.direction_to(mid_point) * (-1 if (end_angle - start_angle) > PI else 1))
start_angle = end_angle

func _calc_circle_arc_poly(center: Vector2, radius: float, angle_from: float, angle_to: float) -> PoolVector2Array:
var nb_points: int = 64
var points_arc: PoolVector2Array = PoolVector2Array()
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_polygons.size():
draw_polygon(_slices_polygons[i], [chart_properties.get_function_color(i)])
draw_polyline(_slices_polygons[i] + PoolVector2Array([_slices_polygons[i][0]]), Color.white, 2.0, true)

func _draw_labels() -> void:
for i in _slices_dirs.size():
var ratio_lbl: String = "%.1f%%" % ratios[i]
var value_lbl: String = "(%s)" % values[i]
var position: Vector2 = plot_box.get_center() + _slices_dirs[i] * _radius * 0.5
var ratio_lbl_size: Vector2 = chart_properties.get_string_size(ratio_lbl)
var value_lbl_size: Vector2 = chart_properties.get_string_size(value_lbl)
draw_string(
chart_properties.font,
position - Vector2(ratio_lbl_size.x / 2, 0),
ratio_lbl,
Color.white
)
draw_string(
chart_properties.font,
position - Vector2(value_lbl_size.x / 2, - value_lbl_size.y),
value_lbl,
Color.white
)

func _input(event: InputEvent) -> void:
if event is InputEventMouse:
for slice_i in _slices_polygons.size():
var slice: PoolVector2Array = _slices_polygons[slice_i]
if Geometry.is_point_in_polygon(event.position, slice):
if focused_slice == slice:
return
else:
focused_slice = slice
$Tooltip.update_values(
"%.1f%%" % ratios[slice_i],
"%s" % values[slice_i],
chart_properties.get_function_name(slice_i),
chart_properties.get_function_color(slice_i)
)
$Tooltip.show()
emit_signal("slice_entered", slice)
return
# Mouse is not in any slice's box
focused_slice = PoolVector2Array()
$Tooltip.hide()
7 changes: 7 additions & 0 deletions addons/easy_charts/control_charts/PieChart/pie_chart.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[gd_scene load_steps=3 format=2]

[ext_resource path="res://addons/easy_charts/control_charts/chart.tscn" type="PackedScene" id=1]
[ext_resource path="res://addons/easy_charts/control_charts/PieChart/pie_chart.gd" type="Script" id=2]

[node name="PieChart" instance=ExtResource( 1 )]
script = ExtResource( 2 )
Loading

0 comments on commit dbdf303

Please sign in to comment.