Radar Graph - Flaxbeard/hoi4-scripted-graphs GitHub Wiki
This section assumes general familiarity with scripted GUIs. Refer to the introduction page for resources.
A radar graph is a visually intriguing way to display several different qualities or affinities.
You might use these to show a country's foreign policy leaning, military doctrine, or a party's appeal to different demographics.
For example, 538 used them to describe the 2020 Democratic presidential candidates:
Using the radar graph code from this repo, you can implement your own radar graphs. Code is included for 5-pointed, 6-pointed, 7-pointed graphs, however the code is easy enough to modify to allow for more or fewer. Graphs can be any size or any color.
Here you can see a radar graph in action, with randomized values.
In order to add a radar graph to your custom GUI, you will need five components:
- Shaderfiles (provided by this repo)
- Graphical assets in a
.gfx
file (one per shaderfile, this means 5 on a 5-pointed graph or 7 on a 7-pointed graph) - Progressbars in a
.gui
file (one per shaderfile) - Corresponding scripted GUI properties, to set progressbar frames
- Variables holding radar graph values, and code to compute the frame number (in scripted GUI or scripted effect file)
If you learn best by example, take a look at the code samples in this repo. Otherwise, we will examine each section below.
You will need to copy the relevant shaderfiles to your mod's /gfx/FX/
directory. Depending on how many vertices you want in your graph, you will need a separate set of files. See sample code for reference. If you're curious how the shaderfile works, see below.
You need to create a progressbartype
asset for each shaderfile. These look like the following:
progressbartype = {
name = "<<YOUR NAME HERE>>" # Needs a name, as you would with any other asset
textureFile1 = "gfx/interface/<<FULL FILE>>.dds" # Square texture file, the color you want your graph to be filled
# Can use a more complex texture too
textureFile2 = "gfx/interface/<<EMPTY FILE>>.dds" # Completely empty .dds file, same size as the full one
size = {
x = 400 # Dimensions of your texture file
y = 400
}
effectFile = "gfx/FX/radar_<NUMBER OF VERTICES HERE>>/<<N>>.lua" # For example, "gfx/FX/radar_6/0.lua"
horizontal = yes
}
Create one for every vertex. For a five-pointed diagram, you'd need six assets with distinct names with effectfiles "gfx/FX/radar_5/0.lua"
,
"gfx/FX/radar_5/1.lua"
, "gfx/FX/radar_5/2.lua"
, "gfx/FX/radar_5/3.lua"
, and "gfx/FX/radar_5/4.lua"
. Again, see code samples for reference.
Adding the radar graph to your GUI is fairly straightforward. Simply position one progressbar for each shaderfile in the same spot.
iconType = {
name = "radar_0"
spriteType = "GFX_Radar_5_0" # Same name as assets above
position = { x = 325 y = 185 } # Any position will do, make sure they're all the same
}
iconType = {
name = "radar_1"
spriteType = "GFX_Radar_5_1"
position = { x = 325 y = 185 }
}
...
iconType = {
name = "radar_4"
spriteType = "GFX_Radar_5_2"
position = { x = 325 y = 185 }
}
As you would a conventional progressbar, you'll need to specify the frame
property of each of these bars in the corresponding scripted GUI file.
properties = {
radar_0 = { # Use the names of the iconTypes from your .gui file
frame = MYMOD_radar_display_0 # You can use either variables or an array here. See next section.
}
radar_1 = {
frame = MYMOD_radar_display_1
}
...
radar_5 = {
frame = MMYMOD_radar_display_5
}
}
Finally, you must assign values to the variables or array that you use to adjust your progress bars. Each axis can take a value 0-9. Do not input these values directly as frame numbers. Instead, you must pass each progress bar the concatenation of its axis and the next axis' values. This is gross, yes, but it is needed. See the technical explanation for details.
For a clearer explanation, consider a radar graph with 5 vertices, and values 0, 2, 5, 1, 7
.
The first progress bar should be passed 02
, the second 25
, the third 51
, the fourth 17
, and the fifth 70
(as it wraps around to the start).
The most simple code to do this is as follows. More elegant methods involving arrays or loops are certainly possible.
# Here, MYMOD_radar_value_x holds a value from 0-9
# These values MUST be integers, consider rounding using round_variable!
set_variable = { MYMOD_radar_display_0 = MYMOD_radar_value_0 }
multiply_variable = { MYMOD_radar_display_0 = 10 }
add_to_variable = { MYMOD_radar_display_0 = MYMOD_radar_value_1 }
set_variable = { MYMOD_radar_display_1 = MYMOD_radar_value_1 }
multiply_variable = { MYMOD_radar_display_1 = 10 }
add_to_variable = { MYMOD_radar_display_1 = MYMOD_radar_value_2 }
...
set_variable = { MYMOD_radar_display_5 = MYMOD_radar_value_5 }
multiply_variable = { MYMOD_radar_display_5 = 10 }
add_to_variable = { MYMOD_radar_display_5 = MYMOD_radar_value_0 } # Remember, the values wrap around
Refer to the introduction page for an overview of what shaderfiles are and some high level strategies that these graphs employ.
The radar graph itself is made up of a number of triangles equal to the number of vertices. Each triangle consists of two adjacent vertices and the origin, as can be seen in this picture.
Each of the shaderfiles is nearly identical but is tasked with drawing a different triangle. Each shaderfile, progressbartype
, and iconType
is associated with one such triangle.
The input passed to the shaderfile in the form of the frame
property is broken apart into its component digits. For example, when 6
and 7
are concatenated to 67
, they are broken back into 6
and 7
by the shaderfile.
The angle that the left and right vertices of the triangle this shaderfile is supposed to draw are fixed based on the number of vertices. The input numbers determine how far from the origin to position the two other vertices. Once the three points are determined, a well-known algorithm is used to check if the pixel being drawn falls in this triangle. If so, the point is shaded.