Skip to content

Instantly share code, notes, and snippets.

@sastoudt
Created October 29, 2019 03:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sastoudt/fc4580ceb903add59df0b670a8451954 to your computer and use it in GitHub Desktop.
Save sastoudt/fc4580ceb903add59df0b670a8451954 to your computer and use it in GitHub Desktop.
adapt ggradar code for music radar charts
# Original:
# https://github.com/ricardo-bion/ggradar/blob/master/R/ggradar.R
ggradar2 <- function(plot.data,
base.size = 15,
font.radar = "sans",
values.radar = c("0%", "50%", "100%"),
axis.labels = colnames(plot.data)[-1],
grid.min = 0, # 10,
grid.mid = 0.5, # 50,
grid.max = 1, # 100,
centre.y = grid.min - ((1 / 9) * (grid.max - grid.min)),
plot.extent.x.sf = 1,
plot.extent.y.sf = 1.2,
x.centre.range = 0.02 * (grid.max - centre.y),
label.centre.y = FALSE,
grid.line.width = 0.5,
gridline.min.linetype = "longdash",
gridline.mid.linetype = "longdash",
gridline.max.linetype = "longdash",
gridline.min.colour = "grey",
gridline.mid.colour = "#007A87",
gridline.max.colour = "grey",
grid.label.size = 6,
gridline.label.offset = -0.1 * (grid.max - centre.y),
label.gridline.min = TRUE,
label.gridline.mid = TRUE,
label.gridline.max = TRUE,
axis.label.offset = 1.15,
axis.label.size = 5,
axis.line.colour = "grey",
group.line.width = 1.5,
group.point.size = 6,
group.colours = NULL,
background.circle.colour = "#D7D6D1",
background.circle.transparency = 0.2,
plot.legend = F,
legend.title = "",
plot.title = "",
legend.text.size = 14,
legend.position = "left") {
plot.data <- as.data.frame(plot.data)
if (!is.factor(plot.data[, 1])) {
plot.data[, 1] <- as.factor(as.character(plot.data[, 1]))
}
names(plot.data)[1] <- "group"
var.names <- colnames(plot.data)[-1] # Short version of variable names
# axis.labels [if supplied] is designed to hold 'long version' of variable names
# with line-breaks indicated using \n
# calculate total plot extent as radius of outer circle x a user-specifiable scaling factor
plot.extent.x <- (grid.max + abs(centre.y)) * plot.extent.x.sf
plot.extent.y <- (grid.max + abs(centre.y)) * plot.extent.y.sf
# Check supplied data makes sense
if (length(axis.labels) != ncol(plot.data) - 1) {
stop("'axis.labels' contains the wrong number of axis labels", call. = FALSE)
}
if (min(plot.data[, -1]) < centre.y) {
stop("plot.data' contains value(s) < centre.y", call. = FALSE)
}
if (max(plot.data[, -1]) > grid.max) {
stop("'plot.data' contains value(s) > grid.max", call. = FALSE)
}
### Convert supplied data into plottable format
# (a) add abs(centre.y) to supplied plot data
# [creates plot centroid of 0,0 for internal use, regardless of min. value of y
# in user-supplied data]
plot.data.offset <- plot.data
plot.data.offset[, 2:ncol(plot.data)] <- plot.data[, 2:ncol(plot.data)] + abs(centre.y)
# print(plot.data.offset)
# (b) convert into radial coords
group <- NULL
group$path <- CalculateGroupPath(plot.data.offset)
# print(group$path)
# (c) Calculate coordinates required to plot radial variable axes
axis <- NULL
axis$path <- CalculateAxisPath(var.names, grid.min + abs(centre.y), grid.max + abs(centre.y))
# print(axis$path)
# (d) Create file containing axis labels + associated plotting coordinates
# Labels
axis$label <- data.frame(
text = axis.labels,
x = NA,
y = NA
)
# print(axis$label)
# axis label coordinates
n.vars <- length(var.names)
angles <- seq(from = 0, to = 2 * pi, by = (2 * pi) / n.vars)
axis$label$x <- sapply(1:n.vars, function(i, x) {
((grid.max + abs(centre.y)) * axis.label.offset) * sin(angles[i])
})
axis$label$y <- sapply(1:n.vars, function(i, x) {
((grid.max + abs(centre.y)) * axis.label.offset) * cos(angles[i])
})
# print(axis$label)
# (e) Create Circular grid-lines + labels
# caclulate the cooridinates required to plot circular grid-lines for three user-specified
# y-axis values: min, mid and max [grid.min; grid.mid; grid.max]
gridline <- NULL
gridline$min$path <- funcCircleCoords(c(0, 0), grid.min + abs(centre.y), npoints = 360)
gridline$mid$path <- funcCircleCoords(c(0, 0), grid.mid + abs(centre.y), npoints = 360)
gridline$max$path <- funcCircleCoords(c(0, 0), grid.max + abs(centre.y), npoints = 360)
# print(head(gridline$max$path))
# gridline labels
gridline$min$label <- data.frame(
x = gridline.label.offset, y = grid.min + abs(centre.y),
text = as.character(grid.min)
)
gridline$max$label <- data.frame(
x = gridline.label.offset, y = grid.max + abs(centre.y),
text = as.character(grid.max)
)
gridline$mid$label <- data.frame(
x = gridline.label.offset, y = grid.mid + abs(centre.y),
text = as.character(grid.mid)
)
# print(gridline$min$label)
# print(gridline$max$label)
# print(gridline$mid$label)
### Start building up the radar plot
theme_clear <- theme_bw(base_size = base.size) +
theme(
axis.text.y = element_blank(),
axis.text.x = element_blank(),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
legend.key = element_rect(linetype = "blank")
)
# Declare 'theme_clear', with or without a plot legend as required by user
# [default = no legend if only 1 group [path] being plotted]
theme_clear <- theme_clear + theme(legend.position = "none")
## force there to be no legend, plot.legend=F didn't work for me for some reason
# Base-layer = axis labels + plot extent
# [need to declare plot extent as well, since the axis labels don't always
# fit within the plot area automatically calculated by ggplot, even if all
# included in first plot; and in any case the strategy followed here is to first
# plot right-justified labels for axis labels to left of Y axis for x< (-x.centre.range)],
# then centred labels for axis labels almost immediately above/below x= 0
# [abs(x) < x.centre.range]; then left-justified axis labels to right of Y axis [x>0].
# This building up the plot in layers doesn't allow ggplot to correctly
# identify plot extent when plotting first (base) layer]
# base layer = axis labels for axes to left of central y-axis [x< -(x.centre.range)]
base <- ggplot(axis$label) + xlab(NULL) + ylab(NULL) + coord_equal() +
geom_text(
data = subset(axis$label, axis$label$x < (-x.centre.range)),
aes(x = x, y = y, label = text), size = axis.label.size, hjust = 1, family = font.radar
) +
scale_x_continuous(limits = c(-1.5 * plot.extent.x, 1.5 * plot.extent.x)) +
scale_y_continuous(limits = c(-plot.extent.y, plot.extent.y))
# ... + circular grid-lines at 'min', 'mid' and 'max' y-axis values
base <- base + geom_path(
data = gridline$min$path, aes(x = x, y = y),
lty = gridline.min.linetype, colour = gridline.min.colour, size = grid.line.width
)
base <- base + geom_path(
data = gridline$mid$path, aes(x = x, y = y),
lty = gridline.mid.linetype, colour = gridline.mid.colour, size = grid.line.width
)
base <- base + geom_path(
data = gridline$max$path, aes(x = x, y = y),
lty = gridline.max.linetype, colour = gridline.max.colour, size = grid.line.width
)
# + axis labels for any vertical axes [abs(x)<=x.centre.range]
base <- base + geom_text(
data = subset(axis$label, abs(axis$label$x) <= x.centre.range),
aes(x = x, y = y, label = text), size = axis.label.size, hjust = 0.5, family = font.radar
)
# + axis labels for any vertical axes [x>x.centre.range]
base <- base + geom_text(
data = subset(axis$label, axis$label$x > x.centre.range),
aes(x = x, y = y, label = text), size = axis.label.size, hjust = 0, family = font.radar
)
# + theme_clear [to remove grey plot background, grid lines, axis tick marks and axis text]
base <- base + theme_clear
# + background circle against which to plot radar data
base <- base + geom_polygon(
data = gridline$max$path, aes(x, y),
fill = background.circle.colour,
alpha = background.circle.transparency
)
# + radial axes
base <- base + geom_path(
data = axis$path, aes(x = x, y = y, group = axis.no),
colour = axis.line.colour
)
# ... + group (cluster) 'paths'
base <- base + geom_path(
data = group$path, aes(x = x, y = y, group = group, colour = group),
size = group.line.width, lty = 2
)
# ... + group points (cluster data)
base <- base + geom_point(data = group$path, aes(x = x, y = y, group = group, colour = group), size = group.point.size, alpha = 0.5) ## I hard coded alpha
# ... + amend Legend title
# ... + grid-line labels (max; mid; min)
if (label.gridline.min == TRUE) {
base <- base + geom_text(aes(x = x, y = y, label = values.radar[1]), data = gridline$min$label, size = grid.label.size * 0.8, hjust = 1, family = font.radar)
}
if (label.gridline.mid == TRUE) {
base <- base + geom_text(aes(x = x, y = y, label = values.radar[2]), data = gridline$mid$label, size = grid.label.size * 0.8, hjust = 1, family = font.radar)
}
if (label.gridline.max == TRUE) {
base <- base + geom_text(aes(x = x, y = y, label = values.radar[3]), data = gridline$max$label, size = grid.label.size * 0.8, hjust = 1, family = font.radar)
}
# ... + centre.y label if required [i.e. value of y at centre of plot circle]
if (label.centre.y == TRUE) {
centre.y.label <- data.frame(x = 0, y = 0, text = as.character(centre.y))
base <- base + geom_text(aes(x = x, y = y, label = text), data = centre.y.label, size = grid.label.size, hjust = 0.5, family = font.radar)
}
if (!is.null(group.colours)) {
colour_values <- rep(group.colours, 100)
} else {
colour_values <- rep(c(
"#FF5A5F", "#FFB400", "#007A87", "#8CE071", "#7B0051",
"#00D1C1", "#FFAA91", "#B4A76C", "#9CA299", "#565A5C", "#00A04B", "#E54C20"
), 100)
}
if (plot.title != "") {
base <- base + ggtitle(plot.title)
}
return(base)
}
CalculateAxisPath <- function(var.names, min, max) {
# var.names <- c("v1","v2","v3","v4","v5")
n.vars <- length(var.names) # number of vars (axes) required
# Cacluate required number of angles (in radians)
angles <- seq(from = 0, to = 2 * pi, by = (2 * pi) / n.vars)
# calculate vectors of min and max x+y coords
min.x <- min * sin(angles)
min.y <- min * cos(angles)
max.x <- max * sin(angles)
max.y <- max * cos(angles)
# Combine into a set of uniquely numbered paths (one per variable)
axisData <- NULL
for (i in 1:n.vars) {
a <- c(i, min.x[i], min.y[i])
b <- c(i, max.x[i], max.y[i])
axisData <- rbind(axisData, a, b)
}
# Add column names + set row names = row no. to allow conversion into a data frame
colnames(axisData) <- c("axis.no", "x", "y")
rownames(axisData) <- seq(1:nrow(axisData))
# Return calculated axis paths
as.data.frame(axisData)
}
CalculateGroupPath <- function(df) {
# browser()
path <- df[, 1]
## find increment
angles <- seq(from = 0, to = 2 * pi, by = (2 * pi) / (ncol(df) - 1))
## create graph data frame
graphData <- data.frame(seg = "", x = 0, y = 0)
graphData <- graphData[-1, ]
for (i in levels(path)) {
pathData <- subset(df, df[, 1] == i)
for (j in c(2:ncol(df))) {
# pathData[,j]= pathData[,j]
graphData <- rbind(graphData, data.frame(
group = i,
x = pathData[, j] * sin(angles[j - 1]),
y = pathData[, j] * cos(angles[j - 1])
))
}
## complete the path by repeating first pair of coords in the path
graphData <- rbind(graphData, data.frame(
group = i,
x = pathData[, 2] * sin(angles[1]),
y = pathData[, 2] * cos(angles[1])
))
}
# Make sure that name of first column matches that of input data (in case !="group")
colnames(graphData)[1] <- colnames(df)[1]
graphData # data frame returned by function
}
funcCircleCoords <- function(center = c(0, 0), r = 1, npoints = 100) {
tt <- seq(0, 2 * pi, length.out = npoints)
xx <- center[1] + r * cos(tt)
yy <- center[2] + r * sin(tt)
return(data.frame(x = xx, y = yy))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment