aboutsummaryrefslogtreecommitdiff
path: root/game/src/Game
diff options
context:
space:
mode:
author Nemrav <>2023-07-29 03:07:02 +0200
committer Hop311 <hop3114@gmail.com>2023-08-09 20:02:01 +0200
commit505176d9cabe76cff7cdac6b4d4ef1c77ccb00d9 (patch)
treeeed289104a5fc476e9033d88fc74865d109e66bb /game/src/Game
parentff0d38b5d53fa95609f2587a2be5205f0c0d3118 (diff)
add piecharts
Diffstat (limited to 'game/src/Game')
-rw-r--r--game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd6
-rw-r--r--game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn129
-rw-r--r--game/src/Game/Theme/PieChart/LayeredChart.gd28
-rw-r--r--game/src/Game/Theme/PieChart/LayeredChart.tscn249
-rw-r--r--game/src/Game/Theme/PieChart/PieChart.gd235
-rw-r--r--game/src/Game/Theme/PieChart/PieChart.gdshader87
-rw-r--r--game/src/Game/Theme/PieChart/PieChart.tscn147
-rw-r--r--game/src/Game/Theme/PieChart/PieChartMat.tres21
-rw-r--r--game/src/Game/Theme/PieChart/chart_test.gd32
9 files changed, 931 insertions, 3 deletions
diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd
index 9a3690e..04a035c 100644
--- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd
+++ b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd
@@ -7,6 +7,7 @@ extends PanelContainer
@export var _rgo_icon_texture_rect : TextureRect
@export var _rgo_name_label : Label
@export var _buildings_container : Container
+@export var _pop_culture_chart : PieChart
const _missing_suffix : String = "_MISSING"
@@ -121,6 +122,11 @@ func _update_info() -> void:
for i in max(buildings.size(), _building_rows.size()):
_set_building_row(i, buildings[i] if i < buildings.size() else {})
+ #PLACEHOLDER for updating piechart
+ _pop_culture_chart.addOrReplaceLabel("NORTH_GERMAN",50,"North German Culture",Color.DIM_GRAY)
+ _pop_culture_chart.addOrReplaceLabel("FRENCH",25,"French Culture",Color.BLUE)
+ _pop_culture_chart.addOrReplaceLabel("SOUTH_GERMAN",7,"South German Culture",Color.FIREBRICK)
+
show()
else:
hide()
diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn
index 7c82f10..9b4c45b 100644
--- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn
+++ b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn
@@ -1,8 +1,118 @@
-[gd_scene load_steps=2 format=3 uid="uid://byq323jbel48u"]
+[gd_scene load_steps=6 format=3 uid="uid://byq323jbel48u"]
[ext_resource type="Script" path="res://src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd" id="1_3n8k5"]
-
-[node name="ProvinceOverviewPanel" type="PanelContainer" node_paths=PackedStringArray("_province_name_label", "_region_name_label", "_life_rating_bar", "_total_population_label", "_rgo_icon_texture_rect", "_rgo_name_label", "_buildings_container")]
+[ext_resource type="PackedScene" uid="uid://cr7p1k2xm7mum" path="res://src/Game/Theme/PieChart/PieChart.tscn" id="2_3oytt"]
+
+[sub_resource type="Shader" id="Shader_2k3yf"]
+code = "shader_type canvas_item;
+
+// The center in UV coordinates, which will always
+//be 0.5, the actual radius will be controlled by the control node
+//const vec2 center = vec2(0.5,0.5);
+uniform float radius = 0.4;
+
+//shadow
+uniform vec2 shadow_displacement = vec2(0.75,0.75);
+uniform float shadow_tightness = 10;
+uniform float shadow_radius = 0.7;
+uniform float shadow_thickness = 1.0;
+
+// Control of the slices
+uniform float stopAngles[5];
+uniform vec3 colours[5];
+
+// Trim
+uniform vec3 trim_colour;
+uniform float trim_size = 0.05;
+
+// The center is spotlighted by the gradient,
+//control its size and falloff with these
+uniform float gradient_falloff = 3.6;
+uniform float gradient_base = 3.1;
+
+// control whether this is a donut instead of a pie chart
+uniform bool donut = false;
+uniform bool donut_inner_trim = false;
+uniform float donut_inner_radius = 0.15;
+
+// get the polar coordinates of a pixel relative to the center
+vec2 getPolar(vec2 UVin, vec2 center){
+ vec2 relcoord = (UVin-center);
+ float dist = length(relcoord);
+ float theta = PI/2.0 + atan((relcoord.y)/(relcoord.x));
+ if(UVin.x < 0.5){
+ theta += PI;
+ }
+ return vec2(dist,theta);
+}
+
+// from thebookofshaders, returns a gradient falloff
+float parabola( float base, float x, float k ){
+ return pow( base*x*(1.0-x), k );
+}
+
+float parabola_shadow(float base, float x){
+ return base*x*x;
+}
+
+void fragment() {
+ vec2 coords = getPolar(UV,vec2(0.5,0.5));
+ float dist = coords.x;
+ float theta = coords.y;
+
+ vec2 shadow_polar = getPolar(UV,vec2(0.0+shadow_displacement.x,0.0+shadow_displacement.y));
+ float shadow_peak = radius+(radius-donut_inner_radius)/2.0;
+ float shadow_gradient = shadow_thickness+parabola_shadow(shadow_tightness*-10.0,shadow_polar.x+shadow_peak-shadow_radius);
+
+ // inner hole of the donut => make it transparent
+ if(donut && dist <= donut_inner_radius){
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+ // inner trim
+ else if(donut && donut_inner_trim && dist <= donut_inner_radius + trim_size){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ // interior
+ else if(dist <= radius-trim_size){
+ for(int i=0;i<stopAngles.length();i++){
+ if(theta <= stopAngles[i]){
+ float gradient = parabola(gradient_base,dist,gradient_falloff);
+ COLOR = vec4(colours[i]*(1.0-gradient),1.0);
+ break;
+ }
+ }
+ }
+ // outer trim
+ else if(dist <= radius){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ //outside the circle
+ else{
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+}"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_3ep5m"]
+resource_name = "Piechart_shader"
+shader = SubResource("Shader_2k3yf")
+shader_parameter/radius = 0.4
+shader_parameter/shadow_displacement = Vector2(0.6, 0.6)
+shader_parameter/shadow_tightness = 1.0
+shader_parameter/shadow_radius = 0.6
+shader_parameter/shadow_thickness = 1.0
+shader_parameter/stopAngles = []
+shader_parameter/colours = []
+shader_parameter/trim_colour = Vector3(0, 0, 0)
+shader_parameter/trim_size = 0.02
+shader_parameter/gradient_falloff = 3.6
+shader_parameter/gradient_base = 3.1
+shader_parameter/donut = false
+shader_parameter/donut_inner_trim = true
+shader_parameter/donut_inner_radius = 0.25
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_udhyk"]
+
+[node name="ProvinceOverviewPanel" type="PanelContainer" node_paths=PackedStringArray("_province_name_label", "_region_name_label", "_life_rating_bar", "_total_population_label", "_rgo_icon_texture_rect", "_rgo_name_label", "_buildings_container", "_pop_culture_chart")]
editor_description = "UI-56"
anchors_preset = 2
anchor_top = 1.0
@@ -19,6 +129,7 @@ _total_population_label = NodePath("PanelList/InteractList/TotalPopulation")
_rgo_icon_texture_rect = NodePath("PanelList/InteractList/RGOInfo/RGOIcon")
_rgo_name_label = NodePath("PanelList/InteractList/RGOInfo/RGOName")
_buildings_container = NodePath("PanelList/InteractList/BuildingsContainer")
+_pop_culture_chart = NodePath("PanelList/InteractList/PopStats/PieChart")
[node name="PanelList" type="VBoxContainer" parent="."]
layout_mode = 2
@@ -88,6 +199,18 @@ vertical_alignment = 1
layout_mode = 2
mouse_filter = 1
+[node name="PopStats" type="HBoxContainer" parent="PanelList/InteractList"]
+layout_mode = 2
+
+[node name="PieChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")]
+material = SubResource("ShaderMaterial_3ep5m")
+layout_mode = 2
+texture = SubResource("CanvasTexture_udhyk")
+
+[node name="HSeparator3" type="HSeparator" parent="PanelList/InteractList"]
+layout_mode = 2
+mouse_filter = 1
+
[node name="BuildingsContainer" type="GridContainer" parent="PanelList/InteractList"]
layout_mode = 2
columns = 3
diff --git a/game/src/Game/Theme/PieChart/LayeredChart.gd b/game/src/Game/Theme/PieChart/LayeredChart.gd
new file mode 100644
index 0000000..7bc69c8
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/LayeredChart.gd
@@ -0,0 +1,28 @@
+extends CenterContainer
+
+var overlapping_charts:Array = []
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ for child in get_children():
+ if child is PieChart:
+ overlapping_charts.push_back(child)
+
+#Process mouse to select the appropriate tooltip for the slice
+func _gui_input(event:InputEvent):
+ if event is InputEventMouse:
+ var pos = event.position
+ var handled:bool = false
+ var x = overlapping_charts.size()
+ #process the charts in reverse order (overlying charts first)
+ #as you can't actually make the inner chart(s) smaller with a centerContainer
+ for i in range(x):
+ var chart = overlapping_charts[x-(i+1)]
+ if not handled:
+ handled = chart.handleTooltip(pos)
+ else:
+ chart.RichTooltip.visible = false
+
+func _on_mouse_exited():
+ for chart in overlapping_charts:
+ chart.RichTooltip.visible = false
diff --git a/game/src/Game/Theme/PieChart/LayeredChart.tscn b/game/src/Game/Theme/PieChart/LayeredChart.tscn
new file mode 100644
index 0000000..9382483
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/LayeredChart.tscn
@@ -0,0 +1,249 @@
+[gd_scene load_steps=9 format=3 uid="uid://ct48qux7spi6u"]
+
+[ext_resource type="PackedScene" uid="uid://cr7p1k2xm7mum" path="res://src/Game/Theme/PieChart/PieChart.tscn" id="1_cdu1k"]
+[ext_resource type="Script" path="res://src/Game/Theme/PieChart/LayeredChart.gd" id="1_wxoci"]
+
+[sub_resource type="Shader" id="Shader_ojesc"]
+code = "shader_type canvas_item;
+
+// The center in UV coordinates, which will always
+//be 0.5, the actual radius will be controlled by the control node
+//const vec2 center = vec2(0.5,0.5);
+uniform float radius = 0.4;
+
+//shadow
+uniform vec2 shadow_displacement = vec2(0.75,0.75);
+uniform float shadow_tightness = 10;
+uniform float shadow_radius = 0.7;
+uniform float shadow_thickness = 1.0;
+
+// Control of the slices
+uniform float stopAngles[5];
+uniform vec3 colours[5];
+
+// Trim
+uniform vec3 trim_colour;
+uniform float trim_size = 0.05;
+
+// The center is spotlighted by the gradient,
+//control its size and falloff with these
+uniform float gradient_falloff = 3.6;
+uniform float gradient_base = 3.1;
+
+// control whether this is a donut instead of a pie chart
+uniform bool donut = false;
+uniform bool donut_inner_trim = false;
+uniform float donut_inner_radius = 0.15;
+
+// get the polar coordinates of a pixel relative to the center
+vec2 getPolar(vec2 UVin, vec2 center){
+ vec2 relcoord = (UVin-center);
+ float dist = length(relcoord);
+ float theta = PI/2.0 + atan((relcoord.y)/(relcoord.x));
+ if(UVin.x < 0.5){
+ theta += PI;
+ }
+ return vec2(dist,theta);
+}
+
+// from thebookofshaders, returns a gradient falloff
+float parabola( float base, float x, float k ){
+ return pow( base*x*(1.0-x), k );
+}
+
+float parabola_shadow(float base, float x){
+ return base*x*x;
+}
+
+void fragment() {
+ vec2 coords = getPolar(UV,vec2(0.5,0.5));
+ float dist = coords.x;
+ float theta = coords.y;
+
+ vec2 shadow_polar = getPolar(UV,vec2(0.0+shadow_displacement.x,0.0+shadow_displacement.y));
+ float shadow_peak = radius+(radius-donut_inner_radius)/2.0;
+ float shadow_gradient = shadow_thickness+parabola_shadow(shadow_tightness*-10.0,shadow_polar.x+shadow_peak-shadow_radius);
+
+ // inner hole of the donut => make it transparent
+ if(donut && dist <= donut_inner_radius){
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+ // inner trim
+ else if(donut && donut_inner_trim && dist <= donut_inner_radius + trim_size){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ // interior
+ else if(dist <= radius-trim_size){
+ for(int i=0;i<stopAngles.length();i++){
+ if(theta <= stopAngles[i]){
+ float gradient = parabola(gradient_base,dist,gradient_falloff);
+ COLOR = vec4(colours[i]*(1.0-gradient),1.0);
+ break;
+ }
+ }
+ }
+ // outer trim
+ else if(dist <= radius){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ //outside the circle
+ else{
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+}"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_6ril8"]
+resource_name = "Piechart_shader"
+shader = SubResource("Shader_ojesc")
+shader_parameter/radius = 0.4
+shader_parameter/shadow_displacement = Vector2(0.6, 0.6)
+shader_parameter/shadow_tightness = 2.0
+shader_parameter/shadow_radius = 0.75
+shader_parameter/shadow_thickness = 1.0
+shader_parameter/stopAngles = [3.14159, 5.02655, 6.28319]
+shader_parameter/colours = [Vector3(0, 0, 1), Vector3(1, 1, 0), Vector3(0.4, 0, 0.6)]
+shader_parameter/trim_colour = Vector3(0, 0, 0)
+shader_parameter/trim_size = 0.02
+shader_parameter/gradient_falloff = 3.6
+shader_parameter/gradient_base = 3.1
+shader_parameter/donut = false
+shader_parameter/donut_inner_trim = true
+shader_parameter/donut_inner_radius = 0.051
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_pwdgy"]
+
+[sub_resource type="Shader" id="Shader_vq8ad"]
+code = "shader_type canvas_item;
+
+// The center in UV coordinates, which will always
+//be 0.5, the actual radius will be controlled by the control node
+//const vec2 center = vec2(0.5,0.5);
+uniform float radius = 0.4;
+
+//shadow
+uniform vec2 shadow_displacement = vec2(0.75,0.75);
+uniform float shadow_tightness = 10;
+uniform float shadow_radius = 0.7;
+uniform float shadow_thickness = 1.0;
+
+// Control of the slices
+uniform float stopAngles[5];
+uniform vec3 colours[5];
+
+// Trim
+uniform vec3 trim_colour;
+uniform float trim_size = 0.05;
+
+// The center is spotlighted by the gradient,
+//control its size and falloff with these
+uniform float gradient_falloff = 3.6;
+uniform float gradient_base = 3.1;
+
+// control whether this is a donut instead of a pie chart
+uniform bool donut = false;
+uniform bool donut_inner_trim = false;
+uniform float donut_inner_radius = 0.15;
+
+// get the polar coordinates of a pixel relative to the center
+vec2 getPolar(vec2 UVin, vec2 center){
+ vec2 relcoord = (UVin-center);
+ float dist = length(relcoord);
+ float theta = PI/2.0 + atan((relcoord.y)/(relcoord.x));
+ if(UVin.x < 0.5){
+ theta += PI;
+ }
+ return vec2(dist,theta);
+}
+
+// from thebookofshaders, returns a gradient falloff
+float parabola( float base, float x, float k ){
+ return pow( base*x*(1.0-x), k );
+}
+
+float parabola_shadow(float base, float x){
+ return base*x*x;
+}
+
+void fragment() {
+ vec2 coords = getPolar(UV,vec2(0.5,0.5));
+ float dist = coords.x;
+ float theta = coords.y;
+
+ vec2 shadow_polar = getPolar(UV,vec2(0.0+shadow_displacement.x,0.0+shadow_displacement.y));
+ float shadow_peak = radius+(radius-donut_inner_radius)/2.0;
+ float shadow_gradient = shadow_thickness+parabola_shadow(shadow_tightness*-10.0,shadow_polar.x+shadow_peak-shadow_radius);
+
+ // inner hole of the donut => make it transparent
+ if(donut && dist <= donut_inner_radius){
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+ // inner trim
+ else if(donut && donut_inner_trim && dist <= donut_inner_radius + trim_size){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ // interior
+ else if(dist <= radius-trim_size){
+ for(int i=0;i<stopAngles.length();i++){
+ if(theta <= stopAngles[i]){
+ float gradient = parabola(gradient_base,dist,gradient_falloff);
+ COLOR = vec4(colours[i]*(1.0-gradient),1.0);
+ break;
+ }
+ }
+ }
+ // outer trim
+ else if(dist <= radius){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ //outside the circle
+ else{
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+}"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_5ilmo"]
+resource_name = "Piechart_shader"
+shader = SubResource("Shader_vq8ad")
+shader_parameter/radius = 0.4
+shader_parameter/shadow_displacement = Vector2(0.75, 0.75)
+shader_parameter/shadow_tightness = 10.0
+shader_parameter/shadow_radius = 0.0
+shader_parameter/shadow_thickness = 1.0
+shader_parameter/stopAngles = [3.14159, 5.02655, 6.28319]
+shader_parameter/colours = [Vector3(0, 0, 1), Vector3(1, 1, 0), Vector3(0.4, 0, 0.6)]
+shader_parameter/trim_colour = Vector3(0, 0, 0)
+shader_parameter/trim_size = 0.02
+shader_parameter/gradient_falloff = 3.6
+shader_parameter/gradient_base = 3.1
+shader_parameter/donut = true
+shader_parameter/donut_inner_trim = true
+shader_parameter/donut_inner_radius = 0.2
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_x3pc8"]
+
+[node name="LayeredChart" type="CenterContainer"]
+offset_right = 50.0
+offset_bottom = 50.0
+script = ExtResource("1_wxoci")
+
+[node name="PieChart" parent="." instance=ExtResource("1_cdu1k")]
+material = SubResource("ShaderMaterial_6ril8")
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+texture = SubResource("CanvasTexture_pwdgy")
+donut_inner_radius = 0.102
+shadow_focus = 2.0
+shadow_radius = 0.75
+
+[node name="PieChart2" parent="." instance=ExtResource("1_cdu1k")]
+material = SubResource("ShaderMaterial_5ilmo")
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+texture = SubResource("CanvasTexture_x3pc8")
+donut = true
+donut_inner_radius = 0.4
+shadow_displacement = Vector2(0.75, 0.75)
+shadow_focus = 10.0
+shadow_radius = 0.0
diff --git a/game/src/Game/Theme/PieChart/PieChart.gd b/game/src/Game/Theme/PieChart/PieChart.gd
new file mode 100644
index 0000000..aba1f81
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/PieChart.gd
@@ -0,0 +1,235 @@
+@tool
+extends TextureRect
+
+class_name PieChart
+
+
+@export var donut : bool = false
+@export_range(0.0,1.0) var donut_inner_radius : float = 0.5
+@export_range(0.0,0.5) var radius : float = 0.4
+@export var shadow_displacement : Vector2 = Vector2(0.6,0.6)
+@export var shadow_focus : float = 1.0
+@export var shadow_radius : float = 0.6
+@export var shadow_thickness : float = 1.0
+
+@export var trim_colour : Color = Color(0.0,0.0,0.0)
+@export_range(0.0,1.0) var trim_size : float = 0.02
+@export var donut_inner_trim : bool = true
+@export var slice_gradient_falloff : float = 3.6
+@export var slice_gradient_base : float = 3.1
+
+#@onready
+@export var RichTooltip : RichTextLabel# = $RichToolTip
+
+#a data class for the pie chart
+class SliceData:
+ #primary properties, change these to change
+ #the displayed piechart
+ var colour:Color = Color(1.0,0.0,0.0):
+ get:
+ return colour
+ set(value):
+ colour = value
+ var tooltip:String = "DEFAULT":
+ get:
+ return tooltip
+ set(value):
+ tooltip = value
+ var quantity:float = -1:
+ get:
+ return quantity
+ set(value):
+ quantity = value
+ #derived properties, don't set from an external script
+ var final_angle:float = -1:
+ get:
+ return final_angle
+ set(value):
+ final_angle = value
+ var percentage:float = 0:
+ get:
+ return percentage
+ set(value):
+ percentage = clampf(value,0,1)
+
+ func _init(quantityIn:float,tooltipIn:String,colourIn:Color):
+ colour = colourIn
+ tooltip = tooltipIn
+ quantity = quantityIn
+
+#The key of an entry of this dictionnary should be an easy to reference constant
+#The tooltip label is what the user will actually read
+var slices: Dictionary = {
+
+}
+#example slices:
+"""
+ "label1":SliceData.new(5,"Conservative",Color(0.0,0.0,1.0)),
+ "label2":SliceData.new(3,"Liberal",Color(1.0,1.0,0.0)),
+ "label3":SliceData.new(2,"Reactionary",Color(0.4,0.0,0.6))
+"""
+
+#These functions are the interface a developer will use to update the piechart
+#The piechart will only redraw once one of these has been triggered
+func addOrReplaceLabel(labelName:String,quantity:float,tooltip:String,colour:Color=Color(0.0,0.0,0.0)) -> void:
+ slices[labelName] = SliceData.new(quantity,tooltip,colour)
+ _recalculate()
+
+func updateLabelQuantity(labelName:String,quantity:float) -> void:
+ if slices.has(labelName):
+ slices[labelName].quantity = quantity
+ _recalculate()
+
+func updateLabelColour(labelName:String,colour:Color) -> void:
+ if slices.has(labelName):
+ slices[labelName].colour = colour
+ _recalculate()
+
+func updateLabelTooltip(labelName:String,tooltip:String) -> void:
+ if slices.has(labelName):
+ slices[labelName].tooltip = tooltip
+
+func RemoveLabel(labelName:String) -> bool:
+ var out = slices.erase(labelName)
+ _recalculate()
+ return out
+
+#Perhaps in the future, a method to reorder the labels?
+
+
+#In editor only, force the shader parameters to update whenever _draw
+#is called so developers can see their changes
+#otherwise, for performance, reduce the number of material resets
+func _draw():
+ if Engine.is_editor_hint():
+ if not material:
+ _reset_material()
+ _setShaderParams()
+ _recalculate()
+
+func _ready():
+ _reset_material()
+ _setShaderParams()
+
+func _reset_material():
+ texture = CanvasTexture.new()
+ var mat_res = load("res://src/Game/Theme/PieChart/PieChartMat.tres")
+ material = mat_res.duplicate(true)
+ custom_minimum_size = Vector2(50.0,50.0)
+ size_flags_horizontal = Control.SIZE_SHRINK_CENTER
+ size_flags_vertical = Control.SIZE_SHRINK_CENTER
+ _recalculate()
+
+func _setShaderParams():
+ material.set_shader_parameter("donut",donut)
+ material.set_shader_parameter("donut_inner_trim",donut_inner_trim)
+ material.set_shader_parameter("radius",radius)
+ material.set_shader_parameter("donut_inner_radius",donut_inner_radius/2.0)
+
+ material.set_shader_parameter("trim_colour",Vector3(trim_colour.r,trim_colour.g,trim_colour.b))
+ material.set_shader_parameter("trim_size",trim_size)
+ material.set_shader_parameter("gradient_falloff",slice_gradient_falloff)
+ material.set_shader_parameter("gradient_base",slice_gradient_base)
+
+ material.set_shader_parameter("shadow_displacement",shadow_displacement)
+ material.set_shader_parameter("shadow_tightness",shadow_focus)
+ material.set_shader_parameter("shadow_radius",shadow_radius)
+ material.set_shader_parameter("shadow_thickness",shadow_thickness)
+
+
+#Update the slice angles based on the new slice data
+func _recalculate() -> void:
+ #where the slices are the public interface, these are the actual paramters
+ #which will be sent to the shader
+ var angles: Array = []
+ var colours: Array = []
+
+ var total:float = 0
+ for slice in slices.values():
+ total += slice.quantity
+
+ var current_arc_start:float = 0
+ var current_arc_finish:float = 0
+
+ for slice in slices.values():
+ slice.percentage = slice.quantity / total
+ var rads_to_cover:float = slice.percentage * 2.0*PI
+ current_arc_finish = current_arc_start + rads_to_cover
+ slice.final_angle = current_arc_finish
+ current_arc_start = current_arc_finish
+ angles.push_back(current_arc_finish)
+ colours.push_back(Vector3(slice.colour.r,slice.colour.g,slice.colour.b) )
+ material.set_shader_parameter("stopAngles",angles)
+ material.set_shader_parameter("colours",colours)
+
+
+#Process mouse to select the appropriate tooltip for the slice
+func _gui_input(event:InputEvent):
+ if event is InputEventMouse:
+ var pos = event.position
+ var _handled:bool = _handleTooltip(pos)
+
+func _on_mouse_exited():
+ RichTooltip.visible = false
+
+#takes a mouse position, and sets an appropriate tooltip for the slice the mouse
+#is hovered over. Returns a boolean on whether the tooltip was handled.
+func _handleTooltip(pos:Vector2) -> bool:
+ #is it within the circle?
+ var center = Vector2(size.x/2.0, size.x/2.0)
+ var radius = size.x/2.0
+ var distance = center.distance_to(pos)
+ #print(distance >= donut_inner_radius/2.0)
+ var real_donut_inner_radius:float = radius * donut_inner_radius
+ if distance <= radius and (not donut or distance >= real_donut_inner_radius):
+ var angle = _convertAngle(center.angle_to_point(pos))
+ for label in slices.keys():
+ var slice = slices.get(label)
+ if angle <= slice.final_angle:
+ RichTooltip.visible = true
+ RichTooltip.text = _createTooltip(label)
+ RichTooltip.position = pos + Vector2(5,5) + get_global_rect().position #get_global_rect().position +
+ RichTooltip.reset_size()
+
+ return true
+ else:
+ #Technically the corners of the bounding box
+ #are part of the chart, but we don't want a tooltip there
+ RichTooltip.visible = false
+ return false
+
+#create a list of all the values and percentages
+# but with the hovered one on top and highlighted
+func _createTooltip(labelHovered:String) -> String:
+ var tooltip:String = ""
+ var hoveredSlice = slices.get(labelHovered)
+ var formatted_percent = _formatpercent(hoveredSlice.percentage)
+ #TOOD: perhaps this is a bit much, but final feedback should determine this
+ tooltip += "[font_size=10][i][u][b]>> {name} {percentage}% <<[/b][/u][/i]".format(
+ {"name":hoveredSlice.tooltip,"percentage":formatted_percent})
+
+ for label in slices.keys():
+ if label == labelHovered: continue
+ var slice = slices.get(label)
+ var percent = _formatpercent(slice.percentage)
+ tooltip += "\n{name} {percentage}%".format(
+ {"name":slice.tooltip,"percentage":percent})
+ tooltip += "[/font_size]"
+ return tooltip
+
+#angle from center.angle_to_point is measured from the +x axis
+#, but the chart starts from +y
+#the input angle is also -180 to 180, where we want 0 to 360
+func _convertAngle(angleIn:float) -> float:
+ #make the angle start from +y, range is now -90 to 270
+ var angle = angleIn + PI/2.0
+ #adjust range to be 0 to 360
+ if angle < 0:
+ angle = 2.0*PI + angle
+ return angle
+
+func _formatpercent(percentIn:float) -> float:
+ return snappedf((percentIn * 100),0.1)
+
+
+
diff --git a/game/src/Game/Theme/PieChart/PieChart.gdshader b/game/src/Game/Theme/PieChart/PieChart.gdshader
new file mode 100644
index 0000000..1707b3d
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/PieChart.gdshader
@@ -0,0 +1,87 @@
+shader_type canvas_item;
+
+// The center in UV coordinates, which will always
+//be 0.5, the actual radius will be controlled by the control node
+//const vec2 center = vec2(0.5,0.5);
+uniform float radius = 0.4;
+
+//shadow
+uniform vec2 shadow_displacement = vec2(0.75,0.75);
+uniform float shadow_tightness = 10;
+uniform float shadow_radius = 0.7;
+uniform float shadow_thickness = 1.0;
+
+// Control of the slices
+uniform float stopAngles[5];
+uniform vec3 colours[5];
+
+// Trim
+uniform vec3 trim_colour;
+uniform float trim_size = 0.05;
+
+// The center is spotlighted by the gradient,
+//control its size and falloff with these
+uniform float gradient_falloff = 3.6;
+uniform float gradient_base = 3.1;
+
+// control whether this is a donut instead of a pie chart
+uniform bool donut = false;
+uniform bool donut_inner_trim = false;
+uniform float donut_inner_radius = 0.15;
+
+// get the polar coordinates of a pixel relative to the center
+vec2 getPolar(vec2 UVin, vec2 center){
+ vec2 relcoord = (UVin-center);
+ float dist = length(relcoord);
+ float theta = PI/2.0 + atan((relcoord.y)/(relcoord.x));
+ if(UVin.x < 0.5){
+ theta += PI;
+ }
+ return vec2(dist,theta);
+}
+
+// from thebookofshaders, returns a gradient falloff
+float parabola( float base, float x, float k ){
+ return pow( base*x*(1.0-x), k );
+}
+
+float parabola_shadow(float base, float x){
+ return base*x*x;
+}
+
+void fragment() {
+ vec2 coords = getPolar(UV,vec2(0.5,0.5));
+ float dist = coords.x;
+ float theta = coords.y;
+
+ vec2 shadow_polar = getPolar(UV,vec2(0.0+shadow_displacement.x,0.0+shadow_displacement.y));
+ float shadow_peak = radius+(radius-donut_inner_radius)/2.0;
+ float shadow_gradient = shadow_thickness+parabola_shadow(shadow_tightness*-10.0,shadow_polar.x+shadow_peak-shadow_radius);
+
+ // inner hole of the donut => make it transparent
+ if(donut && dist <= donut_inner_radius){
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+ // inner trim
+ else if(donut && donut_inner_trim && dist <= donut_inner_radius + trim_size){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ // interior
+ else if(dist <= radius-trim_size){
+ for(int i=0;i<stopAngles.length();i++){
+ if(theta <= stopAngles[i]){
+ float gradient = parabola(gradient_base,dist,gradient_falloff);
+ COLOR = vec4(colours[i]*(1.0-gradient),1.0);
+ break;
+ }
+ }
+ }
+ // outer trim
+ else if(dist <= radius){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ //outside the circle
+ else{
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+} \ No newline at end of file
diff --git a/game/src/Game/Theme/PieChart/PieChart.tscn b/game/src/Game/Theme/PieChart/PieChart.tscn
new file mode 100644
index 0000000..a0d136e
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/PieChart.tscn
@@ -0,0 +1,147 @@
+[gd_scene load_steps=5 format=3 uid="uid://cr7p1k2xm7mum"]
+
+[ext_resource type="Script" path="res://src/Game/Theme/PieChart/PieChart.gd" id="2_ub6u3"]
+
+[sub_resource type="Shader" id="Shader_0kffx"]
+code = "shader_type canvas_item;
+
+// The center in UV coordinates, which will always
+//be 0.5, the actual radius will be controlled by the control node
+//const vec2 center = vec2(0.5,0.5);
+uniform float radius = 0.4;
+
+//shadow
+uniform vec2 shadow_displacement = vec2(0.75,0.75);
+uniform float shadow_tightness = 10;
+uniform float shadow_radius = 0.7;
+uniform float shadow_thickness = 1.0;
+
+// Control of the slices
+uniform float stopAngles[5];
+uniform vec3 colours[5];
+
+// Trim
+uniform vec3 trim_colour;
+uniform float trim_size = 0.05;
+
+// The center is spotlighted by the gradient,
+//control its size and falloff with these
+uniform float gradient_falloff = 3.6;
+uniform float gradient_base = 3.1;
+
+// control whether this is a donut instead of a pie chart
+uniform bool donut = false;
+uniform bool donut_inner_trim = false;
+uniform float donut_inner_radius = 0.15;
+
+// get the polar coordinates of a pixel relative to the center
+vec2 getPolar(vec2 UVin, vec2 center){
+ vec2 relcoord = (UVin-center);
+ float dist = length(relcoord);
+ float theta = PI/2.0 + atan((relcoord.y)/(relcoord.x));
+ if(UVin.x < 0.5){
+ theta += PI;
+ }
+ return vec2(dist,theta);
+}
+
+// from thebookofshaders, returns a gradient falloff
+float parabola( float base, float x, float k ){
+ return pow( base*x*(1.0-x), k );
+}
+
+float parabola_shadow(float base, float x){
+ return base*x*x;
+}
+
+void fragment() {
+ vec2 coords = getPolar(UV,vec2(0.5,0.5));
+ float dist = coords.x;
+ float theta = coords.y;
+
+ vec2 shadow_polar = getPolar(UV,vec2(0.0+shadow_displacement.x,0.0+shadow_displacement.y));
+ float shadow_peak = radius+(radius-donut_inner_radius)/2.0;
+ float shadow_gradient = shadow_thickness+parabola_shadow(shadow_tightness*-10.0,shadow_polar.x+shadow_peak-shadow_radius);
+
+ // inner hole of the donut => make it transparent
+ if(donut && dist <= donut_inner_radius){
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+ // inner trim
+ else if(donut && donut_inner_trim && dist <= donut_inner_radius + trim_size){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ // interior
+ else if(dist <= radius-trim_size){
+ for(int i=0;i<stopAngles.length();i++){
+ if(theta <= stopAngles[i]){
+ float gradient = parabola(gradient_base,dist,gradient_falloff);
+ COLOR = vec4(colours[i]*(1.0-gradient),1.0);
+ break;
+ }
+ }
+ }
+ // outer trim
+ else if(dist <= radius){
+ COLOR = vec4(trim_colour,1.0);
+ }
+ //outside the circle
+ else{
+ COLOR = vec4(0.1,0.1,0.1,shadow_gradient);
+ }
+}"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_aadl6"]
+resource_name = "Piechart_shader"
+shader = SubResource("Shader_0kffx")
+shader_parameter/radius = 0.4
+shader_parameter/shadow_displacement = Vector2(0.6, 0.6)
+shader_parameter/shadow_tightness = 1.0
+shader_parameter/shadow_radius = 0.6
+shader_parameter/shadow_thickness = 1.0
+shader_parameter/stopAngles = [3.14159, 5.02655, 6.28319]
+shader_parameter/colours = [Vector3(0, 0, 1), Vector3(1, 1, 0), Vector3(0.4, 0, 0.6)]
+shader_parameter/trim_colour = Vector3(0, 0, 0)
+shader_parameter/trim_size = 0.02
+shader_parameter/gradient_falloff = 3.6
+shader_parameter/gradient_base = 3.1
+shader_parameter/donut = false
+shader_parameter/donut_inner_trim = true
+shader_parameter/donut_inner_radius = 0.25
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_xntmh"]
+
+[node name="PieChart" type="TextureRect" node_paths=PackedStringArray("RichTooltip")]
+material = SubResource("ShaderMaterial_aadl6")
+custom_minimum_size = Vector2(50, 50)
+anchors_preset = -1
+anchor_right = 0.039
+anchor_bottom = 0.069
+offset_right = -32.92
+offset_bottom = -34.68
+size_flags_horizontal = 4
+size_flags_vertical = 4
+texture = SubResource("CanvasTexture_xntmh")
+expand_mode = 3
+script = ExtResource("2_ub6u3")
+RichTooltip = NodePath("RichToolTip")
+
+[node name="RichToolTip" type="RichTextLabel" parent="."]
+visible = false
+top_level = true
+layout_mode = 2
+offset_right = 50.0
+offset_bottom = 50.0
+mouse_filter = 2
+bbcode_enabled = true
+fit_content = true
+autowrap_mode = 0
+
+[node name="Panel" type="Panel" parent="RichToolTip"]
+show_behind_parent = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
diff --git a/game/src/Game/Theme/PieChart/PieChartMat.tres b/game/src/Game/Theme/PieChart/PieChartMat.tres
new file mode 100644
index 0000000..449c7ba
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/PieChartMat.tres
@@ -0,0 +1,21 @@
+[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://bl2arkbisbk8f"]
+
+[ext_resource type="Shader" path="res://src/Game/Theme/PieChart/PieChart.gdshader" id="1_j23w0"]
+
+[resource]
+resource_name = "Piechart_shader"
+shader = ExtResource("1_j23w0")
+shader_parameter/radius = 0.5
+shader_parameter/shadow_displacement = Vector2(0.75, 0.75)
+shader_parameter/shadow_tightness = 10.0
+shader_parameter/shadow_radius = 0.8
+shader_parameter/shadow_thickness = 1.0
+shader_parameter/stopAngles = [3.14159, 5.02655, 6.28319]
+shader_parameter/colours = [Vector3(0, 0, 1), Vector3(1, 1, 0), Vector3(0.4, 0, 0.6)]
+shader_parameter/trim_colour = Vector3(0, 0, 0)
+shader_parameter/trim_size = 0.02
+shader_parameter/gradient_falloff = 3.6
+shader_parameter/gradient_base = 3.1
+shader_parameter/donut = false
+shader_parameter/donut_inner_trim = true
+shader_parameter/donut_inner_radius = 0.25
diff --git a/game/src/Game/Theme/PieChart/chart_test.gd b/game/src/Game/Theme/PieChart/chart_test.gd
new file mode 100644
index 0000000..8a3b865
--- /dev/null
+++ b/game/src/Game/Theme/PieChart/chart_test.gd
@@ -0,0 +1,32 @@
+extends Control
+
+
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ var chart:PieChart = $LayeredChart/OuterPieChart
+ chart.addOrReplaceLabel("label4",3,"Socialist",Color(1.0,0.0,0.0))
+
+ var chart2:PieChart = $PieChart3
+ chart2.RemoveLabel("label1")
+ chart2.RemoveLabel("label2")
+ chart2.RemoveLabel("label3")
+ chart2.RemoveLabel("label4")
+
+ #$PieChart
+ #$PieChart2
+ #$PieChart/PieChart2
+ var chart3:PieChart = $LayeredChart/InnerPieChart
+ chart3.RemoveLabel("label1")
+ chart3.RemoveLabel("label2")
+ chart3.RemoveLabel("label3")
+ chart3.addOrReplaceLabel("a",3,"",Color(1.0,0.0,0.0))
+ chart3.addOrReplaceLabel("b",3,"Y",Color(0.0,1.0,0.0))
+ chart3.addOrReplaceLabel("c",3,"Z",Color(0.0,0.0,1.0))
+
+
+ var chart4:PieChart = $PieChart4
+ chart4.addOrReplaceLabel("hi",3,"antidisenstablishmentarianist antidisenstablishmentarianism",Color(0.0,0.5,0.5))
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta):
+ pass