moment = require("[email protected]")
moment = require("[email protected]")
viewof storyStep = Inputs.button(
[
["→",
value => {
const maxVal = comments.length;
if (value == maxVal - 2) {
document
.querySelector("#advance-buttons button:first-child")
.disabled = true
} else {
document
.querySelector("#advance-buttons button:first-child")
.disabled = false
}
return value < maxVal - 1 ? value + 1 : value
}
],
["↩",
() => {
document
.querySelector("#advance-buttons button:first-child")
.disabled = false
return 0
}
],
], {
value: 0,
id: "storyStepsBtns"
})
as_sec = (durStr) =>
moment.duration("PT" + durStr.toUpperCase()).as("seconds")
// calculate midpoint on log-transformed axis using duration strings
log_size_midpoint = (minNum, maxNum) =>
Math.exp(Math.log(minNum) +
((Math.log(maxNum) - Math.log(minNum)) / 2))
log_time_midpoint = (minStr, maxStr) =>
Math.exp(Math.log(as_sec(minStr)) +
((Math.log(as_sec(maxStr)) - Math.log(as_sec(minStr))) / 2))
si_watts = x => d3.format(".0s") + "W"
storageData = FileAttachment("storage-types.json").json()
sizeLabels = [
{ size: 5000, time: "20h", steps: [1, 5], label: "RESERVE & RESPONSE" },
{ size: 2000000, time: "20h", steps: [2, 5], label: "GRID SUPPORT" },
{ size: 250000000, time: "20h", steps: [3, 5], label: "BULK POWER" },
]
timeLabels = [
{ size: 500, time: "1s", label: "SECONDS" },
{ size: 500, time: "1m0s", label: "MINUTES" },
{ size: 500, time: "1h", label: "HOURS" },
]
rectTemplate = ({
x1: "size_min",
x2: "size_max",
y1: d => as_sec(d.time_min),
y2: d => as_sec(d.time_max),
fill: "category",
})
textTemplate = ({
text: "name",
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
fill: "category",
stroke: "white",
lineWidth: 15,
fontWeight: "bold",
})
batteryPlot = Plot.plot({
marks: [
// these layers show when showing data
Plot.rect(storageData, {
...rectTemplate,
filter: d => d.size_classes.includes("reserve"),
fillOpacity: d => steps.smallSystems.includes(storyStep) ? 0.25 : 0,
ariaHidden: !steps.smallSystems.includes(storyStep),
}),
Plot.text(storageData, {
...textTemplate,
filter: d => d.size_classes.includes("reserve"),
opacity: d => steps.smallSystems.includes(storyStep) ? 0.75 : 0,
ariaHidden: !steps.smallSystems.includes(storyStep),
}),
Plot.rect(storageData, {
...rectTemplate,
filter: d => d.size_classes.includes("distribution"),
fillOpacity: d => steps.mediumSystems.includes(storyStep) ? 0.25 : 0,
ariaHidden: !steps.mediumSystems.includes(storyStep),
}),
Plot.text(storageData, {
...textTemplate,
filter: d => d.size_classes.includes("distribution"),
opacity: d => steps.mediumSystems.includes(storyStep) ? 0.75 : 0,
ariaHidden: !steps.mediumSystems.includes(storyStep),
}),
Plot.rect(storageData, {
...rectTemplate,
filter: d => d.size_classes.includes("bulk"),
fillOpacity: d => steps.largeSystems.includes(storyStep) ? 0.25 : 0,
ariaHidden: !steps.largeSystems.includes(storyStep),
}),
Plot.text(storageData, {
...textTemplate,
filter: d => d.size_classes.includes("bulk"),
opacity: d => steps.largeSystems.includes(storyStep) ? 0.75 : 0,
ariaHidden: !steps.largeSystems.includes(storyStep),
}),
// then show everything but lithium-ion
Plot.rect(storageData, {
...rectTemplate,
filter: d => d.name != "Lithium-ion Batteries",
fillOpacity: d => steps.allButLithium.includes(storyStep) ? 0.25 : 0,
ariaHidden: !steps.allButLithium.includes(storyStep),
}),
Plot.text(storageData, {
...textTemplate,
filter: d => d.name != "Lithium-ion Batteries",
opacity: d => steps.allButLithium.includes(storyStep) ? 0.75 : 0,
ariaHidden: !steps.allButLithium.includes(storyStep),
}),
// do lithium-ion separately
Plot.rect(storageData, {
...rectTemplate,
filter: d => d.name == "Lithium-ion Batteries",
fillOpacity: d => steps.lithiumion.includes(storyStep) ? 0.25 : 0,
ariaHidden: !steps.lithiumion.includes(storyStep),
}),
Plot.text(storageData, {
...textTemplate,
filter: d => d.name == "Lithium-ion Batteries",
opacity: d => steps.lithiumion.includes(storyStep) ? 0.75 : 0,
ariaHidden: !steps.lithiumion.includes(storyStep),
}),
// at the end, show all systems
Plot.rect(storageData, {
...rectTemplate,
fillOpacity: d => steps.fullDataRects.includes(storyStep) ? 0.25 : 0,
ariaHidden: !steps.fullDataRects.includes(storyStep),
}),
Plot.text(storageData, {
...textTemplate,
opacity: d => steps.fullDataText.includes(storyStep) ? 0.75 : 0,
ariaHidden: !steps.fullDataText.includes(storyStep),
}),
// labels for size and time classes
Plot.text(sizeLabels, {
x: "size",
y: d => as_sec(d.time),
text: "label",
fill: "black",
fontWeight: "bold",
fillOpacity: d =>
steps.sizeLabels.includes(storyStep) &&
d.steps.includes(storyStep) ?
0.5 : 0,
ariaHidden: !steps.sizeLabels.includes(storyStep),
}),
Plot.ruleX([100000, 50000000], {
stroke: "grey",
strokeWidth: 1.5,
strokeDasharray: [1, 4],
strokeLinecap: "round",
strokeOpacity: steps.sizeLabels.includes(storyStep) ? 0.5 : 0,
ariaHidden: !steps.sizeLabels.includes(storyStep),
}),
Plot.text(timeLabels, {
x: "size",
y: d => as_sec(d.time),
text: "label",
fill: "black",
rotate: 90,
fontWeight: "bold",
fillOpacity: steps.timeLabels.includes(storyStep) ? 0.5 : 0,
ariaHidden: !steps.timeLabels.includes(storyStep),
}),
// axes
Plot.axisX({ color: "lightgrey", tickSize: 0, tickPadding: 20, anchor: "top" }),
Plot.axisY({ color: "#ffffff00", tickSize: 0 }),
// tips at each step
steps.fullDataText.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
steps.smallSystems.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
filter: d => d.size_classes.includes("reserve") &&
d.name != "Lithium-ion Batteries",
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
steps.mediumSystems.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
filter: d => d.size_classes.includes("distribution") &&
d.name != "Lithium-ion Batteries",
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
steps.largeSystems.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
filter: d => d.size_classes.includes("bulk") &&
d.name != "Lithium-ion Batteries",
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
steps.allButLithium.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
filter: d => d.name != "Lithium-ion Batteries",
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
steps.lithiumion.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
filter: d => d.name == "Lithium-ion Batteries",
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
steps.fullDataText.includes(storyStep) ?
Plot.tip(storageData, Plot.pointer({
x: d => log_size_midpoint(d.size_min, d.size_max) + (d.nudge_x || 0),
y: d => log_time_midpoint(d.time_min, d.time_max) + (d.nudge_y || 0),
lineHeight: 1.2,
title: d => d.name + "\n\n" + d.notes,
})) :
null,
],
x: {
type: "log",
label: "Larger system size (W)",
},
y: {
type: "log",
label: "Longer discharge time (s)",
},
color: {
legend: true,
opacity: 0.25,
},
style: {
fontSize: 14,
fontFamily: "Roboto Condensed"
},
marginTop: 60,
marginBottom: 0,
marginLeft: 10,
height: 600
})
micro = require("[email protected]")
micro.init({
awaitOpenAnimation: true,
awaitCloseAnimation: true
});
This chart, as well as the analysis that underpins it, is available under a Creative Commons Attribution 4.0 licence.
Please acknowledge 360info and our data sources when you use them.
Copy and paste the following code:
<div style="aspect-ratio: 11 / 20; width: 100%; min-height: 610px; max-height: 885px">
<iframe
allow="fullscreen; clipboard-write self https://energystorage.360visuals.org"
allowfullscreen="true"
src="https://energystorage.360visuals.org/storage-size-discharge/"
title="Interactive: energy storage"
style="width:100%; height:100%; position: relative; top: 0; left: 0; border:none; background-color: white;" scrolling="no"></iframe>
</div>
This content is subject to 360info’s Terms of Use.
Visit the GitHub repository to:
This interactive is based on a 2013 graphic by Richard Andrew Williams. We’ve updated it with the help of Roger Dargaville at the Monash Energy Institute.