data1 = transpose(data1_);
data1_prop = transpose(data1_prop_);
data_tot = transpose(data_tot_);
data_tot_sex = transpose(data_tot_sex_);
data_indigenous = transpose(data_indigenous_)
data_indigenous_prop = transpose(data_indigenous_prop_)
data_cob = transpose(data_cob_)
data_pref_lan = transpose(data_pref_lan_)
years=Array.from({ length: 2022-2008+1 }, (value, index) => 2008+index);
years_transition=Array.from({ length: 2021-2008+1 }, (value, index) => 2008+index);
data_filtered = data1.filter(x=>x.year==year_selected1 & x.care_type==care_type_selected);
max_y = d3.max(data_tot.filter(x=>x.care_type==care_type_selected), x=>x.value);
In this article I share some interactive graphs that give insight into the people using aged care services in Australia. My aim was to highlight trends, age structure, and groups of interest. I extracted the data from the GEN website (Australian Institute of Health and Welfare 2023a) and cleaned it to facilitate longitudinal comparison. For additional context and commentary, readers may find the article on the GEN website (Australian Institute of Health and Welfare 2023b) useful as well.
Note that the fee structure was changed in 2014, which may explain the lower use of respite residential care that year.
Begin by selecting the care you would like to visualise below. The care type chosen applies to all graphs in this article.
{
const p = document.createElement("h3");
p.appendChild(document.createTextNode("People receiving "+care_type_selected.toLowerCase()+" visualised"));
return p;
}
care_type = function(x="",y="",padding=true) {
const p = document.createElement("div");
if(padding) {p.className = "my-title";}
p.appendChild(document.createTextNode(x+care_type_selected.toLowerCase()+y));
return p;
}
Plot.plot({
color: {domain: ages_in_order, legend: true},
y: {
label: "Number of people"
},
x: {label: "Year",
tickFormat: ".0f"},
marks: [
Plot.areaY(data_tot.filter(x=>x.care_type==care_type_selected), {x: "year", y: "value", fill: "age"}),
Plot.ruleY([0])
],
margin: 45
})
Plot.plot({
color: {legend: true},
y: {
label: "Number of people"
},
x: {label: "Year",
tickFormat: ".0f"},
marks: [
Plot.areaY(data_tot_sex.filter(x=>x.care_type==care_type_selected), {x: "year", y: "value", fill: "sex"}),
Plot.ruleY([0])
],
margin: 45
})
Plot.plot({
color: {legend: true},
y: {
label: "Number of indigenous Australians"
},
x: {label: "Year",
tickFormat: ".0f"},
marks: [
Plot.areaY(data_indigenous.filter(x=>x.care_type==care_type_selected), {x: "year", y: "value", fill: "age"}),
Plot.ruleY([0])
],
margin: 45
})
Plot.plot({
color: {legend: true},
y: {
label: "Number of people"
},
x: {label: "Year",
tickFormat: ".0f"},
marks: [
Plot.areaY(data_cob.filter(x=>x.care_type==care_type_selected), {x: "year", y: "value", fill: "country_of_birth"}),
Plot.ruleY([0])
],
margin: 45
})
Plot.plot({
color: {legend: true},
y: {
label: "Number of people"
},
x: {label: "Year",
tickFormat: ".0f"},
marks: [
Plot.areaY(data_pref_lan.filter(x=>x.care_type==care_type_selected), {x: "year", y: "value", fill: "preferred_language"}),
Plot.ruleY([0])
],
margin: 45
})
viewof year_selected1= Scrubber(care_type_selected=="Transition care" ? years_transition : years, {step: 1, delay: 250, loop: false, autoplay: false, label: "Year: "});
ages_in_order = ['0-49', '50-54', '55-59', '60-64', '65-69', '70-74', '75-79',
'80-84', '85-89', '90-94', '95-99', '100+']
Plot.plot({
marks: [
Plot.barY(data_filtered,
Plot.groupX({ y: "sum" },
{ x: "age",
y: 'value',
fill: 'sex',
tip: true})),
Plot.ruleY([0])
],
x: {domain: ages_in_order, label: 'Age group'},
y: {label: 'Number of people', domain: [0,max_y]},
color: {domain: ['M','F'], range: ['#FC3E3E', '#009486'],
legend: true}
})
pyramid_chart = function() {
function me(selection) {
const data = selection.datum().data;
const group_by = selection.datum().group_by
const group1 = selection.datum().group1;
const group1_label = selection.datum().group1_label;
const group2_label = selection.datum().group2_label;
const max_x = selection.datum().max_x;
const margin = ({top: 10, right: 0, bottom: 50, left: 0});
const height = data.length / 2 * 25 + margin.top + margin.bottom;
const x_label = selection.datum().x_label
const svg = selection.append("svg")
.attr("viewBox", [0, 0, width, height])
.attr("font-family", "sans-serif")
.attr("font-size", 15);
const xM = d3.scaleLinear()
.domain([0, max_x])
.rangeRound([width / 2, margin.left]);
const xF = d3.scaleLinear()
.domain(xM.domain())
.rangeRound([width / 2, width - margin.right])
const y = d3.scaleBand()
.domain(data.map(d => d.age))
.rangeRound([height - margin.bottom, margin.top])
.padding(0.1)
const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(g => g.append("g").call(d3.axisBottom(xM).ticks(width / 80, "s")))
.call(g => g.append("g").call(d3.axisBottom(xF).ticks(width / 80, "s")))
.call(g => g.selectAll(".domain").remove())
.call(g => g.selectAll(".tick:first-of-type").remove())
const yAxis = g => g
.attr("transform", `translate(${xM(0)},0)`)
.call(d3.axisRight(y).tickSizeOuter(0))
.call(g => g.selectAll(".tick text").attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", 15))
svg.append("g")
.selectAll("rect")
.data(data)
.join("rect")
.attr("fill", d => [selection.datum().color2, selection.datum().color1][d[group_by] === group1 ? 1 : 0])
.attr("x", d => d[group_by] === group1 ? xM(d.value) : xF(0))
.attr("y", d => y(d.age))
.attr("width", d => d[group_by] === group1 ? xM(0) - xM(d.value) : xF(d.value) - xF(0))
.attr("height", y.bandwidth());
svg.append("text")
.attr("text-anchor", "end")
.attr("fill", "black")
.attr("dy", "0.35em")
.attr("x", xM(0) - 30)
.attr("y", y(data[0].age) + y.bandwidth() / 2)
.text(group1_label);
svg.append("text")
.attr("text-anchor", "start")
.attr("fill", "black")
.attr("dy", "0.35em")
.attr("x", xF(0) + 60)
.attr("y", y(data[0].age) + y.bandwidth() / 2)
.text(group2_label);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
svg.append("text")
.attr('x', (xF(0) + xM(0))/2)
.attr('y', height-10)
.attr("text-anchor", "middle")
.text(x_label);
return me;
};
return me;
}
viewof year_selected2 = Scrubber(care_type_selected=="Transition care" ? years_transition : years, {step: 1, delay: 250, loop: false, autoplay: false, label: "Year: "});
{
const div = d3.create("div");
const pyramid = pyramid_chart();
div.datum({color1: '#FC3E3E',
color2: '#009486',
group_by: 'sex',
group1: 'M',
group1_label: 'Male',
group2_label: 'Female',
data: data1.filter(x=>x.care_type==care_type_selected&x.year==year_selected2),
max_x: d3.max(data1.filter(x=>x.care_type==care_type_selected),
x=>x.value),
x_label: "← number of people →"}).call(pyramid);
return div.node();
}
viewof year_selected3 = Scrubber(care_type_selected=="Transition care" ? years_transition : years, {step: 1, delay: 250, loop: false, autoplay: false, label: "Year: "});
{
const div = d3.create("div");
const pyramid = pyramid_chart();
div.datum({color1: '#FC3E3E',
color2: '#009486',
group_by: 'sex',
group1: 'M',
group1_label: 'Male',
group2_label: 'Female',
data: data1_prop.filter(x=>x.care_type==care_type_selected&x.year==year_selected3),
max_x: d3.max(data1_prop.filter(x=>x.care_type==care_type_selected), x=>x.value),
x_label: "← proportion of people (%) →"}).call(pyramid);
return div.node();
}
care_type("Number of individuals in chosen age group among people receiving ", " stratified by sex")
viewof age_group_selected1 = Scrubber([
'0-49', '50-54', '55-59', '60-64', '65-69', '70-74', '75-79', '80-84', '85-89', '90-94', '95-99', '100+'], {step: 1, delay: 250, loop: false, autoplay: false, label: "Age: "});
Plot.plot({
color: {legend: true, domain: ['M','F'], range: ['#FC3E3E', '#009486']},
y: {
label: "Number of people"
},
x: {label: "Year",
tickFormat: ".0f"},
marks: [
Plot.areaY(data1.filter(x=>x.care_type==care_type_selected &
x.age==age_group_selected1), {x: "year", y: "value", fill: "sex"}),
Plot.ruleY([0])
],
margin: 45
})
viewof age_group_selected2 = Scrubber([
'0-49', '50-54', '55-59', '60-64', '65-69', '70-74', '75-79', '80-84', '85-89', '90-94', '95-99', '100+'], {step: 1, delay: 250, loop: false, autoplay: false, label: "Age: "});
Plot.line(
data_sex.filter(x=>x.care_type==care_type_selected &
x.age_group==age_group_selected2),
{x: "year", y: "PercentFemale"}
).plot({
x: {
label: "Year",
tickFormat: ".0f"
},
y: {
label: "Female (%)",
domain: [0,100]
}})
care_type("Proportion of individuals of each age among people receiving ", " stratified by indigenous status");
viewof year_selected4 = Scrubber(care_type_selected=="Transition care" ? years_transition : years, {step: 1, delay: 250, loop: false, autoplay: false, label: "Year: "});
{
const div = d3.create("div");
const pyramid = pyramid_chart();
div.datum({color1: '#0380B8',
color2: '#C6D0CF',
group_by: 'indigenous_status',
group1: 'Indigenous',
group1_label: 'Indigenous',
group2_label: 'Non-indigenous',
data: data_indigenous_prop.filter(x=>x.care_type==care_type_selected&x.year==year_selected4),
max_x: d3.max(data_indigenous_prop.filter(x=>x.care_type==care_type_selected), x=>x.value),
x_label: "← proportion of people (%) →"}).call(pyramid);
return div.node();
}
function Scrubber(values, {
format = value => value,
initial = 0,
delay = null,
autoplay = true,
loop = true,
loopDelay = null,
alternate = false,
label = "Age: "
} = {}) {
values = Array.from(values);
const form = html`<form style="font: 8px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 50px; align-items: center; vertical-align: top;">
<label style="display: flex; align-items: center;">
${label}
<output name=o style="margin-left: 0.5em;margin-right: 1em;width: 3em"></output>
<input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 180px;">
</label>
<button name=b type=button style="margin-left: 1em;"></button>
</form>`;
let frame = null;
let timer = null;
let interval = null;
let direction = 1;
function start() {
form.b.textContent = "Pause";
if (delay === null) frame = requestAnimationFrame(tick);
else interval = setInterval(tick, delay);
}
function stop() {
form.b.textContent = "Play";
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (timer !== null) clearTimeout(timer), timer = null;
if (interval !== null) clearInterval(interval), interval = null;
}
function running() {
return frame !== null || timer !== null || interval !== null;
}
function tick() {
if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
if (!loop) return stop();
if (alternate) direction = -direction;
if (loopDelay !== null) {
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (interval !== null) clearInterval(interval), interval = null;
timer = setTimeout(() => (step(), start()), loopDelay);
return;
}
}
if (delay === null) frame = requestAnimationFrame(tick);
step();
}
function step() {
form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
}
form.i.oninput = event => {
if (event && event.isTrusted && running()) stop();
form.value = values[form.i.valueAsNumber];
form.o.value = format(form.value, form.i.valueAsNumber, values);
};
form.b.onclick = () => {
if (running()) return stop();
direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
start();
};
form.i.oninput();
if (autoplay) start();
else stop();
Inputs.disposal(form).then(stop);
return form;
}
References
Australian Institute of Health and Welfare. 2023a. “GEN Data: People Using Aged Care.” https://www.gen-agedcaredata.gov.au/Resources/Access-data/2023/April/GEN-data-People-using-aged-care.
———. 2023b. “People Using Aged Care.” https://www.gen-agedcaredata.gov.au/Topics/People-using-aged-care.