diff options
author | Nemrav <> | 2023-07-29 03:07:02 +0200 |
---|---|---|
committer | Hop311 <hop3114@gmail.com> | 2023-08-09 20:02:01 +0200 |
commit | 505176d9cabe76cff7cdac6b4d4ef1c77ccb00d9 (patch) | |
tree | eed289104a5fc476e9033d88fc74865d109e66bb /game/src | |
parent | ff0d38b5d53fa95609f2587a2be5205f0c0d3118 (diff) |
add piecharts
Diffstat (limited to 'game/src')
-rw-r--r-- | game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd | 6 | ||||
-rw-r--r-- | game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn | 129 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/LayeredChart.gd | 28 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/LayeredChart.tscn | 249 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/PieChart.gd | 235 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/PieChart.gdshader | 87 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/PieChart.tscn | 147 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/PieChartMat.tres | 21 | ||||
-rw-r--r-- | game/src/Game/Theme/PieChart/chart_test.gd | 32 |
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 |