Created
August 14, 2024 17:53
-
-
Save jefftrull/eece0fd0bf85f2846bc6a58bf0f9de75 to your computer and use it in GitHub Desktop.
Presentation on custom plot types for org-mode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#+TITLE: Creating a Custom Plot Type for org-mode | |
#+AUTHOR: Jeff Trull | |
* Introduction | |
- Motivated by last month's discussion of PLOT | |
- I wanted to understand the semantic meaning of PLOT for my Keynote backend | |
- Ihor pointed out the newly released "sectors" feature in gnuplot | |
- makes it theoretically possible to implement things like pie charts that Keynote has | |
* Basic PLOT usage | |
Let's grab some data from NOAA (US weather and climate agency): | |
#+PLOT: title:"Contiguous U.S. June Average Temperature" ind:1 deps:(3) set:"ytics 0.5" | |
#+PLOT: set:"xtics 5" set:"grid" with:"linespoints pointtype 19" file:"temps.png" | |
| Year | Fahrenheit | Celsius | | |
|------+------------+---------| | |
| 1970 | 68.72 | 20.4 | | |
| 1971 | 69.42 | 20.8 | | |
| 1972 | 68.18 | 20.1 | | |
| 1973 | 68.94 | 20.5 | | |
| 1974 | 68.47 | 20.3 | | |
| 1975 | 67.50 | 19.7 | | |
| 1976 | 67.78 | 19.9 | | |
| 1977 | 70.32 | 21.3 | | |
| 1978 | 68.92 | 20.5 | | |
| 1979 | 67.80 | 19.9 | | |
| 1980 | 68.72 | 20.4 | | |
| 1981 | 69.80 | 21.0 | | |
| 1982 | 66.31 | 19.1 | | |
| 1983 | 67.06 | 19.5 | | |
| 1984 | 68.59 | 20.3 | | |
| 1985 | 68.09 | 20.1 | | |
| 1986 | 70.39 | 21.3 | | |
| 1987 | 70.23 | 21.2 | | |
| 1988 | 70.84 | 21.6 | | |
| 1989 | 68.11 | 20.1 | | |
| 1990 | 70.21 | 21.2 | | |
| 1991 | 69.04 | 20.6 | | |
| 1992 | 67.41 | 19.7 | | |
| 1993 | 67.19 | 19.6 | | |
| 1994 | 70.93 | 21.6 | | |
| 1995 | 67.53 | 19.7 | | |
| 1996 | 69.84 | 21.0 | | |
| 1997 | 68.29 | 20.2 | | |
| 1998 | 68.11 | 20.1 | | |
| 1999 | 68.29 | 20.2 | | |
| 2000 | 69.01 | 20.6 | | |
| 2001 | 69.21 | 20.7 | | |
| 2002 | 70.84 | 21.6 | | |
| 2003 | 68.02 | 20.0 | | |
| 2004 | 68.05 | 20.0 | | |
| 2005 | 69.19 | 20.7 | | |
| 2006 | 70.74 | 21.5 | | |
| 2007 | 69.85 | 21.0 | | |
| 2008 | 69.58 | 20.9 | | |
| 2009 | 68.50 | 20.3 | | |
| 2010 | 70.47 | 21.4 | | |
| 2011 | 69.80 | 21.0 | | |
| 2012 | 70.52 | 21.4 | | |
| 2013 | 70.38 | 21.3 | | |
| 2014 | 69.57 | 20.9 | | |
| 2015 | 71.33 | 21.9 | | |
| 2016 | 71.74 | 22.1 | | |
| 2017 | 70.32 | 21.3 | | |
| 2018 | 71.53 | 22.0 | | |
| 2019 | 68.70 | 20.4 | | |
| 2020 | 70.27 | 21.3 | | |
| 2021 | 72.57 | 22.5 | | |
| 2022 | 70.79 | 21.6 | | |
| 2023 | 68.99 | 20.6 | | |
| 2024 | 71.82 | 22.1 | | |
#+TBLFM:$3=(($2-32)*5/9);%.1f | |
[[./temps.png]] | |
* Sector Support in gnuplot | |
The introduction of "sectors" in gnuplot 6 has made a rich variety of plot types possible. See [[http://www.gnuplot.info/demo/sectors.html]] for details. | |
The feature is still on a relatively low level, however. Users must supply for a sector (analogous to a rectangle in Cartesian coordinates) the initial and final radius, the initial angle, and the angular extent. | |
* Solution: Custom Plot Type | |
The "type" of the above plot is the default - /2d/. Special ELisp code associated with the plot's type is run when the plot is generated. We want to make our own type and code for pie charts, using sectors. | |
We can take values from a column in a table, and apportion fractions of a full circle to each. | |
** The gnuplot "stats" command | |
gnuplot can perform useful computations on a column of data. We are interested in the sum, for calculating fractions of a circle: | |
#+begin_src gnuplot | |
stats "data" using 2 nooutput; # calculate statistics on column 2 of the input | |
#+end_src | |
#+RESULTS: | |
This sets various global variables, including ~STATS_sum~ | |
** The gnuplot "using" clause | |
gnuplot processes its input data through a ~using~ clause that allows for: | |
- arbitrary calculations | |
- variables that can be updated on each row of data | |
For example, we can calculate angular extents in a pie chart as a fraction of a column total, multiplied by 2*pi. We can sum each extent as it is produced into a variable, so we know where to start the next sector: | |
#+begin_src gnuplot | |
# for sectors "using" supplies the initial angle, the start radius, the angular extent, | |
# the final radius, and the color. | |
# annular_extent and last_angle are variables | |
# $0 gives the current line number and is used to produce different colors | |
using (last_angle):(0):(annular_extent=(2*pi*$2/STATS_sum),last_angle=last_angle+annular_extent,annular_extent):(1):($0+1) | |
#+end_src | |
* Putting it all Together | |
Custom plot types require adding a special structure to ~org-plot/preset-plot-types~. My pie chart implementation required creating two functions: one, ~jet/pie-chart-pre~, for general setup, and one to make the plot itself | |
** Plot Code | |
#+begin_src elisp | |
(defun jet/pie-chart-func (table data-file num-cols params plot-str) | |
(let ((ind (plist-get params :ind)) | |
(deps (plist-get params :deps)) | |
(labels (plist-get params :labels))) | |
(list | |
(concat | |
(format "\"%s\" " data-file) | |
(format "using (last_angle):(0):(annular_extent=(2*pi*$%d/STATS_sum),last_angle=last_angle+annular_extent,annular_extent):(1):($0+1) \\\n" | |
(car deps)) | |
"with sectors units xx lc variable fill solid, \\\n" | |
"last_angle=0 " | |
(format "\"%s\" " data-file) | |
(format "using (annular_extent=(2*pi*$%d/STATS_sum),last_angle+annular_extent/2):(last_angle=last_angle+annular_extent, 1.1):%d \\\n" | |
(car deps) ind) | |
"with labels")))) | |
#+end_src | |
** Plot Setup | |
#+begin_src elisp | |
(defun jet/pie-chart-pre (table data-file num-cols params plot-str) | |
(let ((amounts-col (car (plist-get params :deps)))) | |
(concat | |
(format "stats \"%s\" using %s nooutput;" data-file amounts-col) | |
"set yrange [-1.2 : 1.2];set polar;" | |
"unset raxis;set size ratio -1 1,1;unset rtics;unset border;unset xtics;unset ytics;unset key;" | |
"set theta clockwise top;" | |
"last_angle=0"))) | |
#+end_src | |
** Creating the Plot Type | |
#+begin_src elisp :results none | |
(setq jet/pie-chart-plot-type | |
'(pie :plot-cmd "plot" | |
:plot-func (lambda (table data-file num-cols params plot-str) | |
(jet/pie-chart-func table data-file num-cols params plot-str)) | |
:plot-pre (lambda (table data-file num-cols params plot-str) | |
(jet/pie-chart-pre table data-file num-cols params plot-str)))) | |
(add-to-list 'org-plot/preset-plot-types jet/pie-chart-plot-type) | |
#+end_src | |
* Usage | |
#+PLOT: title:"Someone who is good at the economy help me budget this" | |
#+PLOT: type:pie ind:1 deps:(2) file:"dril.png" | |
| Category | Cost | | |
|----------+------| | |
| Food | 200 | | |
| Data | 150 | | |
| Rent | 800 | | |
| Candles | 3600 | | |
| Utility | 150 | | |
[[./dril.png]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment