Plotting a Pie Chart with Gnuplot and Org Mode
Recent versions of GnuPlot support drawing arcs and the feature can be used to plot pie charts.
The process can be greatly simplified by using Org Mode and a source block to transform the values to be plotted into percentages and arcs (or radians).
Here we show how.
Input Data: World Greenhouse Gas Emissions
We take the Greenhouse Gas Emissions by Sector from Our World in Data. The table shows World emission from 2020 and is expressed in tons.
Entity | World |
---|---|
Greenhouse gas emissions from agriculture | 5865470500 |
Greenhouse gas emissions from land use change and forestry | 1392230000 |
Greenhouse gas emissions from waste | 1652870000 |
Greenhouse gas emissions from buildings | 2980520000 |
Greenhouse gas emissions from industry | 3126930000 |
Greenhouse gas emissions from manufacturing and construction | 6223020000 |
Greenhouse gas emissions from transport | 7288009700 |
Greenhouse gas emissions from electricity and heat | 15181350000 |
Fugitive emissions of greenhouse gases from energy production | 3223690000 |
Greenhouse gas emissions from other fuel combustion | 579050000 |
Greenhouse gas emissions from bunker fuels | 938140000 |
Preprocessing
We use a source block in Ruby which preprocess the table and adds
information useful for plotting in GnuPlot. Here we export both code and
result; in general you might want to set :exports none
We change the label and add various columns:
- Label: we add a
\n
and the percentage - Percentage (not really needed for the plot)
- Start angle
- End angle
- Coordinates for label: we take the angle between start and end and then use trigonometric functions to compute the Cartesian coordinates
- Color (taken from a colors map, if you want to use your own)
… and we make it very simple. More complex elaborations could, for instance, aggregate entries with low percentages, to make the chart easier to read.
# we repeat as necessary colors = [0x8FCA74, 0x5470C6, 0xFAC858, 0xEE6666, 0x73C0DE, 0x3BA272, 0xFC8452, 0x9A60B4] total = world_emissions.map { |x| x[1] }.sum # Note that computations are not rounded, but data is presented rounded start_angle = 0 # start from top and move clockwise radius = 100 # how big of a circly do you want? enh_table = world_emissions.each_with_index.map do |data, index| perc = data[1].to_f / total perc_100 = perc * 100 fraction = 360 * perc end_angle = start_angle + fraction mid_angle = (end_angle - start_angle) / 2.0 color = colors[index % colors.size] # we take the mid angle between arc_end and arc_begin which is: (arc_end + arc_begin) / 2 # then we convert from polar to rectangular: 2 * PI / 360. In the formulas, we simplify the "2" cartesian_x = (1.3 * radius * Math.cos(((end_angle + start_angle) * Math::PI) / 360)).round(0) cartesian_y = (1.3 * radius * Math.sin(((end_angle + start_angle) * Math::PI) / 360)).round(0) entry = [ data[0] + "\n(#{perc_100.round(0)}%)", data[1], perc.round(2), perc_100.round(2), 0, 0, radius, start_angle.round(0), end_angle.round(0), color, cartesian_x, cartesian_y ] start_angle = end_angle entry end # Add header (nil separates header from data) # Nice thing about header is that it is removed from data when assigning vars in Source Blocks [["Label", "Emissions (t)", "Perc [0..1]", "Perc %", "Center X", "Center Y", "Radius", "Start Angle", "End Angle", "Color", "x", "y"]] + [nil] + enh_table
Pie Chart
We are now ready to plot the table. The syntax is described at page 76 of the GnuPlot manual (version 6.0). The Pie Chart starts from 0 degrees and develops counterclockwise
Pie Chart, Starting from 90 degrees
If you prefer to start from the Y-axis, you need to take the modulus, so that degrees never exceed 360.
# we repeat as necessary colors = [0x8FCA74, 0x5470C6, 0xFAC858, 0xEE6666, 0x73C0DE, 0x3BA272, 0xFC8452, 0x9A60B4] total = world_emissions.map { |x| x[1] }.sum # Note that computations are not rounded, but data is presented rounded start_angle = 90 # start from top and move clockwise radius = 100 # how big of a circly do you want? enh_table = world_emissions.each_with_index.map do |data, index| perc = data[1].to_f / total perc_100 = perc * 100 fraction = 360 * perc end_angle = (start_angle + fraction) % 360 mid_angle = ((start_angle + fraction + start_angle) / 2.0) % 360 color = colors[index % colors.size] # we take the mid angle between arc_end and arc_begin which is: (arc_end + arc_begin) / 2 # then we convert from polar to rectangular: 2 * PI / 360. In the formulas, we simplify the "2" cartesian_x = (1.3 * radius * Math.cos((mid_angle * 2 * Math::PI) / 360)).round(0) cartesian_y = (1.3 * radius * Math.sin((mid_angle * 2 * Math::PI) / 360)).round(0) entry = [ data[0] + "\n(#{perc_100.round(0)}%)", data[1], perc.round(2), perc_100.round(2), 0, 0, radius, start_angle.round(0), end_angle.round(0), color, cartesian_x, cartesian_y ] start_angle = end_angle entry end # Add header (nil separates header from data) # Nice thing about header is that it is removed from data when assigning vars in Source Blocks [["Label", "Emissions (t)", "Perc [0..1]", "Perc %", "Center X", "Center Y", "Radius", "Start Angle", "End Angle", "Color", "x", "y"]] + [nil] + enh_table