crime <- read.csv("nzpolice-proceedings.csv")Lab 9 - SVG graphics
The data and questions of interest
Crime data
Data shows rows of incidents handled by the Police.
Generate Month and Year columns.
crime$Month <- as.Date(crime$Date)
crime$Year <- as.POSIXlt(crime$Date)$year + 1900Exclude year 2014 for which we only have partial data.
crime <- subset(crime, Year >= 2015)Create data with proportion of men and women in total and stratified by police district.
sexProp <- count(crime, SEX) %>%
mutate(prop = n/sum(n))
sexProp SEX n prop
1 Female 216350 0.2853506
2 Male 541840 0.7146494
sexRegionProps <- crime %>%
group_by(Police.District, SEX) %>%
summarise(n = n(), .groups = "drop_last") %>%
mutate(prop = n/sum(n), .keep = "unused") %>%
ungroup()
sexRegionProps$Police.District <- reorder(sexRegionProps$Police.District,
sexRegionProps$prop,
function(x) x[1])
head(sexRegionProps)# A tibble: 6 × 3
Police.District SEX prop
<fct> <chr> <dbl>
1 Auckland City Female 0.260
2 Auckland City Male 0.740
3 Bay Of Plenty Female 0.291
4 Bay Of Plenty Male 0.709
5 Canterbury Female 0.291
6 Canterbury Male 0.709
Define colors
cols <- c(female = "#E46C0A", male = "#0070C0")Questions of interest
In this lab we are only looking at the proportion of male versus female offenders, either for all of New Zealand or broken into different Police Districts. The data covers offending from 2015 to 2022.
Data visualisations and questions
Question 1
In the SVG code, we create 2 separate <svg> elements to write the text and draw the boxes. The reason for doing so, is to be able to use a viewBox with a specification of 0 0 100 100 and making sure to specify preserveAspectRatio="none". In this way, we create a viewport such that we can specify width of rectangles inside the viewBox as the percentage of the total width of the image. For the text, we use <tspan> to color some words.
Adding hyperlinks
We use the href attribute within an <a> element wrapped around the text to create a hyperlink.
Adding tooltips
We add a tooltip by simply adding a <title> elements as a child to whatever element we want to add the tooltip to, as described in the documentation. In this case, we add it as a child to the <rect> elements.
Adding animation
We can add animations by using the <animate> element. We do so by adding them inside the <rect> elements. For the left bar, we can simply animate “along” the width of the bar. Since rectangles are always left-aligned in SVG, for the right bar that needs to “grow” to the left, we add an animation both for the width and the x position.
We use fill="freeze" to ensure the rectangles stay in their end position.
<svg version="1.1"
width="500" height="100"
xmlns="http://www.w3.org/2000/svg">
<a href="https://www.police.govt.nz/about-us/publications-statistics/data-and-statistics/policedatanz/proceedings-offender-demographics">
<text x="0" y="45"
font-size="28"
font-family="sans-serif"
text-anchor="start">
Youth Crime:
<tspan fill="#E46C0A">Female</tspan>
versus
<tspan fill="#0070C0">Male</tspan>
</text>
</a>
<svg y="50" width="500" height = "50" viewBox="0 0 100 100" preserveAspectRatio="none">
<rect width="0" height="100" fill="#E46C0A">
<animate
attributeName="width"
from="0"
to="28.53506"
dur="1s"
fill="freeze" />
<title> 28,5% of crimes were committed by Females </title>
</rect>
<rect x="100" width="100" height="100" fill="#0070C0">
<animate
attributeName="width"
from="0"
to="71.46494"
dur="1s"
fill="freeze" />
<animate
attributeName="x"
from="100"
to="28.53506"
dur="1s"
fill="freeze" />
<title> 72,5% of crimes were committed by Males </title>
</rect>
</svg>
</svg>
Question 2
We create helper functions
add_text: adds left-aligned text as a child withfont-family“sans-serif”.add_barRatioSVG: Creates an<svg>object of specified size with aviewBoxenabling us to plot the rate in data within the new native user coordinate system. It then summarises the rate of observations within groups ofcolvarand creates rectangles using those rates and specified colors.add_text_and_bar: A function to combineadd_textandadd_barRatioSVG, allocating the top half to the text and the bottom half to the bar of ratios.add_to_facets: A function that splits data by afacetvar, allocates space to the different facets accordingly and then creates an<svg>for each facet and uses afacet_funinside each facet. As a default it usesadd_text_and_bar.svg_facet: A wrapper function that initialises an svg document by callingxml_new_root, and it then usesadd_to_facetsto create the visualisation.
library(xml2)
add_text <- function(.x, label = "Some label",
x = "0", y = "45",
text.size = "18") {
xml_add_child(.x, "text", label,
x=x, y=y,
"font-size"=text.size, "font-family"="sans-serif", "text-anchor"="start")
return(invisible())
}
add_barRatioSVG <- function(
.x,
data = crime,
label = "Some label",
ratevar = "prop",
colvar = "SEX",
cols = c("#E46C0A", "#0070C0"),
x = "0",
y = "50",
width = "500",
height = "50",
animate = FALSE) {
# Create rectangle inside svg object with viewBox specification
svg_rect <- xml_add_child(
.x, "svg",
x = x, y=y, width=width, height = height, viewBox="0 0 1 1", preserveAspectRatio="none"
)
# Find rates from data
# data_list <- data %>%
# group_split({{colvar}} := get(colvar))
# rates <- sapply(data_list, function(x) x[[ratevar]])
data_sum <- data %>%
group_by(across(all_of(colvar))) %>%
summarise(n = n(), .groups = "drop_last") %>%
mutate(prop = n/sum(n), .keep = "unused")
# Use the rates to determine box length and position
rect1 <- xml_add_child(svg_rect, "rect",
width=as.character(data_sum$prop[1]),
height="1",
fill=cols[1])
rect2 <- xml_add_child(svg_rect, "rect",
x=as.character(data_sum$prop[1]),
width=as.character(data_sum$prop[2]),
height="1",
fill=cols[2])
if (animate) {
xml_add_child(rect1, "animate",
attributeName="width",
from="0",
to=as.character(data_sum$prop[1]),
dur="1s",
fill="freeze")
xml_add_child(rect2, "animate",
attributeName="width",
from="0",
to=as.character(data_sum$prop[2]),
dur="1s",
fill="freeze")
xml_add_child(rect2, "animate",
attributeName="x",
from="1",
to=as.character(data_sum$prop[1]),
dur="1s",
fill="freeze")
}
# Add tooltips
if (TRUE) {
xml_add_child(
rect1, "title",
paste0(format(round(data_sum$prop[1]*100, 1), nsmall = 1),
"% of crimes were commited by ",
data_sum[[colvar]][1],
"s in ",
unique(data$Police.District))
)
xml_add_child(
rect2, "title",
paste0(format(round(data_sum$prop[2]*100, 1), nsmall = 1),
"% of crimes were commited by ",
data_sum[[colvar]][2],
"s in ",
unique(data$Police.District))
)
}
return(invisible())
}
add_text_and_bar <- function(
.x,
data = crime,
label = "Some label",
ratevar = "prop",
colvar = "SEX",
cols = c("#E46C0A", "#0070C0"),
x = "0",
y = "0",
text.size = "18",
text.y.offset = "5",
width = "500",
height = "100",
animate = FALSE
) {
args <- as.list(environment())
y_mid <- as.character(as.numeric(y) + as.numeric(height) / 2)
y_text <- as.character(as.numeric(y_mid) - as.numeric(text.y.offset))
add_text(.x, label, x = x, y = y_text)
args$.x <- .x
args$y <- y_mid
args$height <- as.character(as.numeric(args$height) / 2)
args_to_bar <- args[names(formals(add_barRatioSVG))]
do.call(add_barRatioSVG, args_to_bar)
return(invisible())
}
facetlevels_by_ratevar <- function(
data = crime,
ratevar = "prop",
colvar = "SEX",
facetvar = "Police.District"
) {
data_sum <- data %>%
group_by(across(all_of(c(facetvar, colvar)))) %>%
summarise(n = n(), .groups = "drop_last") %>%
mutate(ratevar := n/sum(n), .keep = "unused") %>%
ungroup()
levels(reorder(data_sum[[facetvar]], data_sum$ratevar, function(x) x[1]))
}
add_to_facets <- function(
.x,
facet_fun = add_text_and_bar,
...,
data = crime,
ratevar = "prop",
colvar = "SEX",
facetvar = "Police.District",
x = "0",
width = "500",
height = "600") {
args <- c(as.list(environment()), list(...))
data[[facetvar]] <- factor(
data[[facetvar]],
levels = facetlevels_by_ratevar(
data = data, ratevar = ratevar, colvar = colvar, facetvar = facetvar
)
)
facet_data <- data %>%
group_split({{facetvar}} := get(facetvar))
n_facets <- length(facet_data)
height_facet <- as.character(as.numeric(height) / n_facets)
ys <- as.numeric(height_facet) * 0:(n_facets-1)
args_to_facet <- args
args_to_facet$height <- height_facet
map2(1:n_facets, ys, function(i, y) {
svg_facet <- xml_add_child(
.x, "svg",
x = x, y=y, width=width, height = height_facet
)
args_to_facet$.x <- svg_facet
args_to_facet$data <- facet_data[[i]]
args_to_facet$label <- as.character(unique(args_to_facet$data[[facetvar]]))
args_to_facet$y <- 0
args_to_facet <- compact(args_to_facet[names(formals(facet_fun))])
do.call(facet_fun, args_to_facet)
})
}
svg_facet <- function(
facet_fun = add_text_and_bar,
...,
data = crime,
ratevar = "prop",
colvar = "SEX",
facetvar = "Police.District",
x = "0",
width = "500",
height = "600") {
args <- c(as.list(environment()), list(...))
svg <- xml_new_root("svg",
version="1.1",
width=width, height=height,
xmlns="http://www.w3.org/2000/svg")
do.call(add_to_facets, c(list(.x = svg), args))
return(svg)
}Run the function to save an svg file.
# svg <- svg_facet(facet_fun = add_barRatioSVG)
# svg <- svg_facet(facet_fun = add_text)
svg <- svg_facet(animate = TRUE)
write_xml(svg, "q2_program.svg")Challenge
The svg file produces by the function above is pasted at the end of the document. We use the following code to embed a selection menu, which determines which animation to run when the button is pressed:
<script type="text/javascript">
function runAnimSelected() {
const div = document.getElementById("c");
const menu = document.getElementById("region");
const region = menu.selectedOptions[0].value;
const allRects = div.querySelectorAll("svg svg rect");
const rect1 = +region * 2 - 1
const rect2 = rect1 - 1;
const anim1 = allRects[rect1].querySelectorAll("animate");
for (const anim of anim1) {
anim.beginElement();
}
const anim2 = allRects[rect2].querySelectorAll("animate");
for (const anim of anim2) {
anim.beginElement();
}
}
</script>
<select id="region">
<option value="1">Auckland City</option>
<option value="2">Tasman</option>
<option value="3">Waitemata</option>
<option value="4">Wellington</option>
<option value="5">Southern</option>
<option value="6">Bay Of Plenty</option>
<option value="7">Canterbury</option>
<option value="8">Northland</option>
<option value="9">Waikato</option>
<option value="10">Counties/Manukau</option>
<option value="11">Central</option>
<option value="12">Eastern</option>
</select>
<button id="button" onclick="runAnimSelected()">Play</button>Note that since we have nested
<svg>s, we use the lineconst allRects = div.querySelectorAll("svg svg rect");to fetch all<rect>elements in the svg file.
Afterwards we find the indices of the relevant <rect>s corresponding to the selection of region using const rect1 = +region * 2 - 1; const rect2 = rect1 - 1;. Then, we can fetch all the <animation> elements within a corresponding <rect> element with allRects[rectXX].querySelectorAll("animate") and then run the elements with the beginElement method.
Overall summary
To create a bar of ratios between men and women with a colored title, we write svg code using <text> and <rect> elements, making use of <tspan> to colors words in the title, and using viewBox and preserveAspectRatio attributes to easily use data values inside a coordinate system where we have specified a native scale.
We add hyperlinks using the href attribute inside an <a> element as parent to the <text> element. We add tooltips using … . Lastly, we add animation using an <animation> element as a child of the <rect> element we want to display as an animation. We can even embed a button using javascript that when pressed runs the animation.
In question 2, we do the same again, but here we stratify our data by police district, find values programmatically and create bars with labels inside sub-<svg> elements.
In question 3 we expand on the use of javascript by adding a selection menu of region to run the animation for.
svg file produced by R function
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="600">
<svg x="0" y="0" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Auckland City</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.260044660285139" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.260044660285139" dur="1s" fill="freeze"/>
<title>26.0% of crimes were commited by Females in Auckland City</title>
</rect>
<rect x="0.260044660285139" width="0.739955339714861" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.739955339714861" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.260044660285139" dur="1s" fill="freeze"/>
<title>74.0% of crimes were commited by Males in Auckland City</title>
</rect>
</svg>
</svg>
<svg x="0" y="50" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Tasman</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.267037948452273" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.267037948452273" dur="1s" fill="freeze"/>
<title>26.7% of crimes were commited by Females in Tasman</title>
</rect>
<rect x="0.267037948452273" width="0.732962051547727" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.732962051547727" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.267037948452273" dur="1s" fill="freeze"/>
<title>73.3% of crimes were commited by Males in Tasman</title>
</rect>
</svg>
</svg>
<svg x="0" y="100" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Waitemata</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.270004455519068" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.270004455519068" dur="1s" fill="freeze"/>
<title>27.0% of crimes were commited by Females in Waitemata</title>
</rect>
<rect x="0.270004455519068" width="0.729995544480932" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.729995544480932" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.270004455519068" dur="1s" fill="freeze"/>
<title>73.0% of crimes were commited by Males in Waitemata</title>
</rect>
</svg>
</svg>
<svg x="0" y="150" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Wellington</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.271174129064125" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.271174129064125" dur="1s" fill="freeze"/>
<title>27.1% of crimes were commited by Females in Wellington</title>
</rect>
<rect x="0.271174129064125" width="0.728825870935875" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.728825870935875" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.271174129064125" dur="1s" fill="freeze"/>
<title>72.9% of crimes were commited by Males in Wellington</title>
</rect>
</svg>
</svg>
<svg x="0" y="200" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Southern</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.271348261076703" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.271348261076703" dur="1s" fill="freeze"/>
<title>27.1% of crimes were commited by Females in Southern</title>
</rect>
<rect x="0.271348261076703" width="0.728651738923297" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.728651738923297" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.271348261076703" dur="1s" fill="freeze"/>
<title>72.9% of crimes were commited by Males in Southern</title>
</rect>
</svg>
</svg>
<svg x="0" y="250" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Bay Of Plenty</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.290506150148452" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.290506150148452" dur="1s" fill="freeze"/>
<title>29.1% of crimes were commited by Females in Bay Of Plenty</title>
</rect>
<rect x="0.290506150148452" width="0.709493849851548" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.709493849851548" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.290506150148452" dur="1s" fill="freeze"/>
<title>70.9% of crimes were commited by Males in Bay Of Plenty</title>
</rect>
</svg>
</svg>
<svg x="0" y="300" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Canterbury</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.290930312598201" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.290930312598201" dur="1s" fill="freeze"/>
<title>29.1% of crimes were commited by Females in Canterbury</title>
</rect>
<rect x="0.290930312598201" width="0.709069687401799" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.709069687401799" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.290930312598201" dur="1s" fill="freeze"/>
<title>70.9% of crimes were commited by Males in Canterbury</title>
</rect>
</svg>
</svg>
<svg x="0" y="350" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Northland</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.291545880988774" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.291545880988774" dur="1s" fill="freeze"/>
<title>29.2% of crimes were commited by Females in Northland</title>
</rect>
<rect x="0.291545880988774" width="0.708454119011226" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.708454119011226" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.291545880988774" dur="1s" fill="freeze"/>
<title>70.8% of crimes were commited by Males in Northland</title>
</rect>
</svg>
</svg>
<svg x="0" y="400" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Waikato</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.292708784399218" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.292708784399218" dur="1s" fill="freeze"/>
<title>29.3% of crimes were commited by Females in Waikato</title>
</rect>
<rect x="0.292708784399218" width="0.707291215600782" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.707291215600782" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.292708784399218" dur="1s" fill="freeze"/>
<title>70.7% of crimes were commited by Males in Waikato</title>
</rect>
</svg>
</svg>
<svg x="0" y="450" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Counties/Manukau</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.293579482258522" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.293579482258522" dur="1s" fill="freeze"/>
<title>29.4% of crimes were commited by Females in Counties/Manukau</title>
</rect>
<rect x="0.293579482258522" width="0.706420517741478" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.706420517741478" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.293579482258522" dur="1s" fill="freeze"/>
<title>70.6% of crimes were commited by Males in Counties/Manukau</title>
</rect>
</svg>
</svg>
<svg x="0" y="500" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Central</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.302394487048855" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.302394487048855" dur="1s" fill="freeze"/>
<title>30.2% of crimes were commited by Females in Central</title>
</rect>
<rect x="0.302394487048855" width="0.697605512951145" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.697605512951145" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.302394487048855" dur="1s" fill="freeze"/>
<title>69.8% of crimes were commited by Males in Central</title>
</rect>
</svg>
</svg>
<svg x="0" y="550" width="500" height="50">
<text x="0" y="20" font-size="18" font-family="sans-serif" text-anchor="start">Eastern</text>
<svg x="0" y="25" width="500" height="25" viewBox="0 0 1 1" preserveAspectRatio="none">
<rect width="0.313027363772573" height="1" fill="#E46C0A">
<animate attributeName="width" from="0" to="0.313027363772573" dur="1s" fill="freeze"/>
<title>31.3% of crimes were commited by Females in Eastern</title>
</rect>
<rect x="0.313027363772573" width="0.686972636227427" height="1" fill="#0070C0">
<animate attributeName="width" from="0" to="0.686972636227427" dur="1s" fill="freeze"/>
<animate attributeName="x" from="1" to="0.313027363772573" dur="1s" fill="freeze"/>
<title>68.7% of crimes were commited by Males in Eastern</title>
</rect>
</svg>
</svg>
</svg>