Title: | Create Colour Scales and Legend from Continuous or Categorical Vectors |
---|---|
Description: | Streamlines the steps for adding colour scales and associated legends when working with base R graphics, especially for interactive use. Popular palettes are included and pretty legends produced when mapping a large variety of vector classes to a colour scale. An additional helper for adding axes and grid lines complements the base::plot() work flow. |
Authors: | John Hobbs [aut, cre] |
Maintainer: | John Hobbs <[email protected]> |
License: | MIT + file LICENSE |
Version: | 0.4.2 |
Built: | 2024-10-14 02:45:16 UTC |
Source: | https://github.com/johnxhobbs/paletteknife |
Overlay base plot with a new axis and optional gridlines. The axis spacing can
be manually specified or automatically generated, including for date and time
axis. A default grid is drawn if called with just the side
specified.
autoaxis( side, major = NA, major_grid = FALSE, minor = NA, minor_grid = FALSE, format = "auto", spacing = TRUE, tck = -0.03, ... )
autoaxis( side, major = NA, major_grid = FALSE, minor = NA, minor_grid = FALSE, format = "auto", spacing = TRUE, tck = -0.03, ... )
side |
Side to add axis, 1 = bottom, 2 = left, 3 = top, 4 = right. If
only this argument is given, a default dense grid is drawn. If
this argument is given as a character, a date-time grid will be
attempted, for example |
major |
Spacing of major axis ticks and labels (or approx. number of
intervals if |
major_grid |
Add grid lines corresponding to major axis ticks, |
minor |
Spacing (or number) of minor ticks (note, no label for minor).
If given as a character string, it will pass to |
minor_grid |
Add gridlines for minor ticks, |
format |
Date or time format for major axis for example |
spacing |
Should |
tck |
Size of axis tick: minor axis will always take half this value |
... |
Additional arguments passed to |
Major and minor tick marks can be specified in a number of ways:
As a character string if the axis is datetime, such as 'year' or 'hour'
which are passed as by
to seq()
. These can be prefixed with an integer multiplier,
for example '6 hour' or '10 year', as per seq.POSIXt
As a tick interval using the default spacing = TRUE
As an approximate number of tick marks to include, using pretty()
to find
the best interval, using spacing = FALSE
. Use a character number if this
is a Date or Time axis, such as major = '100'
and spacing
will be set
FALSE automatically.
Major adds labels and ticks, minor is just half-sized ticks marks. Both
tick sizes can be changed (or direction changed) using tck
.
Three different datetime axis are possible: year, day-offset, seconds-offset. Use
format
to specify how the label should appear, such as '%b %Y' (see ?strptime
)
Year should be treated as a conventional numeric axis, use major=1/12
not major='month'
day-offset is an axis of class(x)=='Date'
and is identified if the axis range exists
within +/-9e4, meaning within dates 1723 - 2216, and minimum interval is 'day'
second-offset is an axis of class(x)=='POSIXct'
and is identified by a range outside
of +/-9e4. This will give very strange results if your entire POSIXct axis is within
24 hours of 1970-01-01
A grid can be added at the same time by setting major_grid
or minor_grid
to TRUE
or a colour string. If TRUE
, a transparent black is used by default.
Any other options can be passed through to axis()
directly (see ?axis
), most
notably las = 2
to rotate the labels, and cex.axis
for label size.
The function will exit with a warning if more than 1000 ticks or gridlines were generated, as this is most likely a mistake with autogenerated date / time intervals and can lead to very slow behaviour.
This does NOT work well for barplot()
categorical axis, for this continue to use
the basic axis()
function with custom labels, see examples.
No return value (NULL
)
plot(sunspots) # This time series is actually given in decimal years autoaxis(side=3, major=50, major_grid='coral', minor=10, minor_grid=TRUE, spacing=TRUE) autoaxis(side=4, major=11, minor=25, spacing=FALSE, las=2, cex.axis=0.5, tck=0.02) plot(seq(as.POSIXct('2020-01-01'),as.POSIXct('2020-01-03'),length.out=1e3), rnorm(1e3), xlab='POSIXct', xaxt='n') autoaxis(side=1, major='day', minor='3 hour', format='%x') # Shortcut method to make a default dense grid autoaxis(side='3') autoaxis(side=2) # You can always request a datetime axis (side='4' not 4L) but it will be nonsense autoaxis(side='4', col='red') plot(seq(as.Date('2013-02-01'),as.Date('2020-01-03'),length.out=1e3), rnorm(1e3), xlab='Date', xaxt='n') autoaxis(side=1, major='10', minor='50', format='%Y') autoaxis(side=3, minor='3 month', minor_grid=TRUE) # Guessing is ambiguous with small values, depends on smallest interval plot(1:500,runif(500), type='l', xaxt='n', xlab='Time or Date?', main= 'For small values (<1e5), use interval to guess format\n') autoaxis(1, major='min', minor='10 sec', format='%M:%S') autoaxis(3, major='quarter', minor='month', format='%b %Y') # For barplot() use base functions - remember to set width=1, space=0 # otherwise bars will not be plotted on integer x-coordinates barplot(mtcars$mpg, width=1, space=0, ylab='mpg') # Adjust the x-axis down by 0.5 so that the tick is in centre of each bar axis(side=1, at=-0.5+1:length(mtcars$mpg), labels=rownames(mtcars), las=2 ) # Often prettier, label each bar inside the bar itself using text() text(x=-1+1:length(mtcars$mpg), y=1, pos=4, labels=rownames(mtcars), srt=90, cex=0.7) # autoaxis can still be used for adjusting the numeric scale autoaxis(side=2, major=5, major_grid=TRUE, minor=1, minor_grid=TRUE)
plot(sunspots) # This time series is actually given in decimal years autoaxis(side=3, major=50, major_grid='coral', minor=10, minor_grid=TRUE, spacing=TRUE) autoaxis(side=4, major=11, minor=25, spacing=FALSE, las=2, cex.axis=0.5, tck=0.02) plot(seq(as.POSIXct('2020-01-01'),as.POSIXct('2020-01-03'),length.out=1e3), rnorm(1e3), xlab='POSIXct', xaxt='n') autoaxis(side=1, major='day', minor='3 hour', format='%x') # Shortcut method to make a default dense grid autoaxis(side='3') autoaxis(side=2) # You can always request a datetime axis (side='4' not 4L) but it will be nonsense autoaxis(side='4', col='red') plot(seq(as.Date('2013-02-01'),as.Date('2020-01-03'),length.out=1e3), rnorm(1e3), xlab='Date', xaxt='n') autoaxis(side=1, major='10', minor='50', format='%Y') autoaxis(side=3, minor='3 month', minor_grid=TRUE) # Guessing is ambiguous with small values, depends on smallest interval plot(1:500,runif(500), type='l', xaxt='n', xlab='Time or Date?', main= 'For small values (<1e5), use interval to guess format\n') autoaxis(1, major='min', minor='10 sec', format='%M:%S') autoaxis(3, major='quarter', minor='month', format='%b %Y') # For barplot() use base functions - remember to set width=1, space=0 # otherwise bars will not be plotted on integer x-coordinates barplot(mtcars$mpg, width=1, space=0, ylab='mpg') # Adjust the x-axis down by 0.5 so that the tick is in centre of each bar axis(side=1, at=-0.5+1:length(mtcars$mpg), labels=rownames(mtcars), las=2 ) # Often prettier, label each bar inside the bar itself using text() text(x=-1+1:length(mtcars$mpg), y=1, pos=4, labels=rownames(mtcars), srt=90, cex=0.7) # autoaxis can still be used for adjusting the numeric scale autoaxis(side=2, major=5, major_grid=TRUE, minor=1, minor_grid=TRUE)
Create a vector of colours and associated legend for easier base plots
autocol( x, set = "", alpha = NA, limits = NA, na_colour = NA, bias = 1, legend_len = 6 ) palette.misc palette.viridis palette.colorbrewer
autocol( x, set = "", alpha = NA, limits = NA, na_colour = NA, bias = 1, legend_len = 6 ) palette.misc palette.viridis palette.colorbrewer
x |
Vector to be mapped to colours |
set |
Colour set to use – see Details for full list. A default |
alpha |
Transparency as a single value or as another vector (recycled to fill).
If it is a vector, all values are scaled from 0:max(alpha) meaning transparent:opaque.
Single values must be in range 0-1. If |
limits |
Colour scale limits as absolute range |
na_colour |
Colour to represent NA-values, default |
bias |
Skew to apply to colour-ramp (>1 increases resolution at low end, <1 at the high end) |
legend_len |
Continuous legend target size |
An object of class list
of length 1.
An object of class list
of length 8.
An object of class list
of length 35.
Helper function for using colours in R's default plot()
and legend()
. Colours
from built-in palettes are automatically scaled to return a vector of colours
and create options('autolegend')
which contains the correct legend mapping for
autolegend()
.
A discrete palette is used for factor and character inputs whilst a continuous palette is used for integer and numeric.
Colour sets built-in so far are held in lists starting pals.
and can be
visualized most easily with pals_display()
. The set
argument can be
any of the colour set names listed here (such as 'magma'), or from palette.pals()
,
or finally as a custom-defined vector, such as set = rainbow(5)
.
The current lists of palettes included with paletteknife all being with pal.
pals.viridis
All of the continuous palette forked from the viridisLite
package maintained by Simon Garnier.
Contains: cividis
inferno
magma
mako
plasma
rocket
turbo
viridis
pals.rcolorbrewer
All of the palettes included in RColorBrewer
Categorical:
Accent
Set1
Set2
Set3
Paired
Pastel1
Pastel2
Dark2
Continuous:
Greys
Blues
BuGn
BuPu
Greens
GnBu
PuBu
Purples
PuBuGn
YlGnBu
YlGn
YlOrBr
YlOrRd
Oranges
OrRd
Reds
RdPu
PuRd
Divergent:
Spectral
RdYlBu
RdYlGn
BrBG
RdBu
RdGy
PiYG
PRGn
PuOr
pals.misc
Sasha Trubetskoy (2017): List of 20 Simple, Distinct Colors: sasha
Custom limits can be specified using c(0,10)
. This is useful if multiple
plots using the same range are required for cross-comparison. Default
behaviour (limits = NA
) sets the range to exactly fit.
The skew of the colourscale can be adjusted with bias
, for example if x
has an exponential distribution, a bias value > 1 will bring out contrast at
the low end.
A character vector of colours of equal length to input x
, sampled from the chosen set
.
This allows it to be used for plotting directly. Information for a legend (containing every
level for categorical data, or approximately length legend_len
for continuous) is stored in
options('autolegend')
and not returned explicitly.
plot(iris$Sepal.Length, iris$Petal.Length, cex=3, pch=16, col=autocol(iris$Petal.Width, set='PuBuGn', alpha=0.8, legend_len=12) ) autolegend('topleft', title='Petal.Width', ncol=3) # Also try simplest "autolegend()" for click-to-draw # Try scales which include NA in both colour and alpha channel with(airquality, plot(Temp, col=autocol(x=Solar.R, set='YlOrRd', alpha=Ozone, na_colour='cyan'), pch=16, cex=sqrt(Wind) )) # Note inset=1 draws on opposite side ie above not below plot area autolegend('bottom', inset=1, bty='n', horiz=TRUE) # Here we want a summary plot ordered by level, so need to create a colour vector to match # 'Alphabet' is a built-in colour set, see "palette.pals()" mixedbag = as.factor(sample(letters,1000,replace=TRUE)) plot(x=mixedbag, y=rnorm(1000), col=autocol(levels(mixedbag), set='Alphabet')) autolegend('bottom', ncol=9, cex=0.7) # Maintain the order of strings barplot(1:8, col=autocol(LETTERS[8:1])) autolegend('topleft') # Any unusual formats are coerced to numeric and the legend converted back mydates = as.Date('2000-01-01')+0:100 plot(mydates, pch=16, col=autocol(mydates, set=rainbow(10), bias=2) ) autolegend(x=0, y=mydates[100], title='My Dates') # Timeseries objects plot as a line, but can overlay with points() plot(airmiles) points(airmiles, pch=15, col=autocol(airmiles, set='Reds')) # Use the limits to clip or augment the colour-scale layout(matrix(1:2)) plot(runif(10), col=autocol(1:10, limits=c(0,20)), pch=16, main='Data split over two plots with same scale') plot(runif(10), col=autocol(c(100,20:12), limits=c(0,20)), pch=16) text(1, 0.5, pos=4, xpd=NA, 'This point has a value of 100 but clipped to max colour == 20') autolegend('bottom', inset=1, horiz=TRUE) # Draws above! layout(1)
plot(iris$Sepal.Length, iris$Petal.Length, cex=3, pch=16, col=autocol(iris$Petal.Width, set='PuBuGn', alpha=0.8, legend_len=12) ) autolegend('topleft', title='Petal.Width', ncol=3) # Also try simplest "autolegend()" for click-to-draw # Try scales which include NA in both colour and alpha channel with(airquality, plot(Temp, col=autocol(x=Solar.R, set='YlOrRd', alpha=Ozone, na_colour='cyan'), pch=16, cex=sqrt(Wind) )) # Note inset=1 draws on opposite side ie above not below plot area autolegend('bottom', inset=1, bty='n', horiz=TRUE) # Here we want a summary plot ordered by level, so need to create a colour vector to match # 'Alphabet' is a built-in colour set, see "palette.pals()" mixedbag = as.factor(sample(letters,1000,replace=TRUE)) plot(x=mixedbag, y=rnorm(1000), col=autocol(levels(mixedbag), set='Alphabet')) autolegend('bottom', ncol=9, cex=0.7) # Maintain the order of strings barplot(1:8, col=autocol(LETTERS[8:1])) autolegend('topleft') # Any unusual formats are coerced to numeric and the legend converted back mydates = as.Date('2000-01-01')+0:100 plot(mydates, pch=16, col=autocol(mydates, set=rainbow(10), bias=2) ) autolegend(x=0, y=mydates[100], title='My Dates') # Timeseries objects plot as a line, but can overlay with points() plot(airmiles) points(airmiles, pch=15, col=autocol(airmiles, set='Reds')) # Use the limits to clip or augment the colour-scale layout(matrix(1:2)) plot(runif(10), col=autocol(1:10, limits=c(0,20)), pch=16, main='Data split over two plots with same scale') plot(runif(10), col=autocol(c(100,20:12), limits=c(0,20)), pch=16) text(1, 0.5, pos=4, xpd=NA, 'This point has a value of 100 but clipped to max colour == 20') autolegend('bottom', inset=1, horiz=TRUE) # Draws above! layout(1)
Add a legend for the last autocol()
set generated
autolegend(...)
autolegend(...)
... |
Arguments passed directly to |
If no location (such as 'top', 'above', or an x,y coordinate) is given, then it
calls the locator()
crosshairs so the position of the legend can be picked
interactively. All arguments are passed to legend()
, see ?legend
for a full
list.
Positions 'above' and 'below' are allowed which shorthand for inset and horizontal (see example).
Legend labels and fill are generated by either autopal()
or autocol()
and
stored in the global options('autolegend')
where they can be manipulated
if needs be.
See more examples in ?autocol for a plot()
and autolegend()
work flow.
No return value (NULL
)
# Simplest version: click-to-draw with locator() plot(1:10, pch=16, col=autocol(1:10, 'Blues', legend_len=5)) # autolegend() # Try me! And click on plot to add legend # Other neat versions -- note ?legend autolegend('above', title='Above plot') # Exactly equivalent to... autolegend('bottom', inset=1, horiz=TRUE, bty='n') autolegend(x=6, y=4, ncol=2, title='Draw at (6,4)') autolegend('topleft', title='"topleft"', ncol=2, bty='n') # Use pch (and optionally pt.cex) in legend -- these get recycled autolegend('bottom', horiz=TRUE, pch=16, pt.cex=3, title='pch=16, pt.cex=3') autolegend('right', pch=1:10, pt.cex=2, title='pch=1:10') # Manipulate the legend text, for example with format(), this is a bit long-winded! heatmap(as.matrix(eurodist), col=autopal('turbo', limits=range(eurodist)) ) current_legend = options('autolegend')[[1]] options(autolegend = list(format(current_legend[[1]], big.mark=','), current_legend[[2]])) autolegend('bottom', inset=1, horiz=TRUE, title='Misleading miles between cities') # No helper exists yet for creating size or shape legends -- follow this idea... with(airquality, plot(Temp, pch=16, cex=Solar.R/100, col=autocol(Ozone, set='Reds'))) cex_legend = pretty(airquality$Solar.R) legend('bottom', pt.cex=cex_legend/100, legend=cex_legend, pch=1, horiz=TRUE, title='Solar.R', bty='n' ) autolegend('above', title='Ozone')
# Simplest version: click-to-draw with locator() plot(1:10, pch=16, col=autocol(1:10, 'Blues', legend_len=5)) # autolegend() # Try me! And click on plot to add legend # Other neat versions -- note ?legend autolegend('above', title='Above plot') # Exactly equivalent to... autolegend('bottom', inset=1, horiz=TRUE, bty='n') autolegend(x=6, y=4, ncol=2, title='Draw at (6,4)') autolegend('topleft', title='"topleft"', ncol=2, bty='n') # Use pch (and optionally pt.cex) in legend -- these get recycled autolegend('bottom', horiz=TRUE, pch=16, pt.cex=3, title='pch=16, pt.cex=3') autolegend('right', pch=1:10, pt.cex=2, title='pch=1:10') # Manipulate the legend text, for example with format(), this is a bit long-winded! heatmap(as.matrix(eurodist), col=autopal('turbo', limits=range(eurodist)) ) current_legend = options('autolegend')[[1]] options(autolegend = list(format(current_legend[[1]], big.mark=','), current_legend[[2]])) autolegend('bottom', inset=1, horiz=TRUE, title='Misleading miles between cities') # No helper exists yet for creating size or shape legends -- follow this idea... with(airquality, plot(Temp, pch=16, cex=Solar.R/100, col=autocol(Ozone, set='Reds'))) cex_legend = pretty(airquality$Solar.R) legend('bottom', pt.cex=cex_legend/100, legend=cex_legend, pch=1, horiz=TRUE, title='Solar.R', bty='n' ) autolegend('above', title='Ozone')
Return a palette vector from one of the built-in sets
autopal(set = "", n = 30, limits = NA, bias = 1, legend_len = 6)
autopal(set = "", n = 30, limits = NA, bias = 1, legend_len = 6)
set |
Colour set to use – see ?autocol for full list. A default |
n |
Length of colour vector to return, must be at least 2 |
limits |
Colour scale limits to pass to legend eg |
bias |
Skew to apply to colour-ramp (>1 increases resolution at low end, <1 at the high end) |
legend_len |
Continuous legend target size |
This can be used where a palette is provided rather than a mapped colour
vector, for example image()
. The limits must be specified for autolegend()
information to be updated.
Custom colour limits can be set using breaks
or levels
(see examples) if
the same colour range is needed across several plots.
See ?autocol for list of all available colour sets.
A character vector of colours of length n
giving a continuous colour palette sampled from set
.
If limits
are specified, information for a colour legend is produced of approximate length
legend_len
. This is stored in options('autolegend')
and not returned explicitly.
image(volcano, col=autopal('RdYlGn', n=100, limits=c(50,200), bias=1.5), breaks=seq(50,200,length.out=101) ) autolegend('bottom', inset=1, ncol=5) # Or using the slightly smarter filled.contour filled.contour(volcano, col=autopal('RdYlGn', n=20, limits=c(100,150)), levels=seq(50,200,length.out=21) )
image(volcano, col=autopal('RdYlGn', n=100, limits=c(50,200), bias=1.5), breaks=seq(50,200,length.out=101) ) autolegend('bottom', inset=1, ncol=5) # Or using the slightly smarter filled.contour filled.contour(volcano, col=autopal('RdYlGn', n=20, limits=c(100,150)), levels=seq(50,200,length.out=21) )
Replaces plot()
with an interactive loop which allows user to click twice
on the plot window to redraw with new limits. Press ESCAPE to finish.
autozoom(x, ..., after = NULL)
autozoom(x, ..., after = NULL)
x |
Passed directly to |
... |
Passed directly to |
after |
An expression to be executed after each plot is drawn |
Click twice to set the new plot extents. If both clicks are on one of the axis (outside of the plot area) then only this axis is zoomed. Click twice on the same spot to reset the zoom to the entire plot.
Extras such as axes or legends are added using the after
argument.
## Not run: autozoom(airmiles) autozoom(faithful, cex=runif(272), after={autoaxis(3); autoaxis(4)}) autozoom(faithful, col=autocol(runif(272)), pch=16, after=autolegend('above') ) ## End(Not run)
## Not run: autozoom(airmiles) autozoom(faithful, cex=runif(272), after={autoaxis(3); autoaxis(4)}) autozoom(faithful, col=autocol(runif(272)), pch=16, after=autolegend('above') ) ## End(Not run)
Plot a list of palettes for comparison
palette.display(palette = palette.colorbrewer)
palette.display(palette = palette.colorbrewer)
palette |
Character vector of palette names or a named list of colour vectors |
No return value (NULL
)
palette.display(c(palette.misc,palette.colorbrewer,palette.viridis)) palette.display(c(list(rainbow=rainbow(10), default=palette()), palette.misc, palette.colorbrewer[c('Paired','Set1','Set2')] )) palette.display(list(rainbow=rainbow(45)[30:1], turbo=palette.viridis$turbo )) # Call by vector of names - here it gets 'Paired' from palette.colorbrewer palette.display(palette.pals() ) # Bit of fun ordering a list of palettes (MUST be same palette size) mat_cols = do.call(rbind, lapply(palette.colorbrewer[9:26], function(hex) as.vector(rgb2hsv(col2rgb(hex))))) palette.display(palette.colorbrewer[9:26][hclust(dist(mat_cols))$order])
palette.display(c(palette.misc,palette.colorbrewer,palette.viridis)) palette.display(c(list(rainbow=rainbow(10), default=palette()), palette.misc, palette.colorbrewer[c('Paired','Set1','Set2')] )) palette.display(list(rainbow=rainbow(45)[30:1], turbo=palette.viridis$turbo )) # Call by vector of names - here it gets 'Paired' from palette.colorbrewer palette.display(palette.pals() ) # Bit of fun ordering a list of palettes (MUST be same palette size) mat_cols = do.call(rbind, lapply(palette.colorbrewer[9:26], function(hex) as.vector(rgb2hsv(col2rgb(hex))))) palette.display(palette.colorbrewer[9:26][hclust(dist(mat_cols))$order])