Public
Edited
Mar 25
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, chart_size]);
var chart = { node:svg.node() };
chart.defs = svg.append("defs");
var g = svg.append("g").attr('transform', `translate(${chart_size / 2 + chart_left_pad}, ${chart_size / 2})`);
chart.pie_g = g.append("g");
chart.text_g = g.append("g")
.attr("font-size", 14)
.attr("font-weight", "bold")
.attr("text-anchor", "middle");
return chart;
}
Insert cell
bar_chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, 60]);
var bar_chart = {node:svg.node()};
var bar_chart = { node:svg.node() };
bar_chart.defs = svg.append("defs");

// group for stacked bar
bar_chart.g_bars = svg.append("g");

var unit = 'kWh';
// Add scales to axis
var x_axis = d3.axisBottom()
.scale(bar_scale)
.tickFormat(x => (x == 0) ? `${x}\u00a0${unit}` : x);

//Append group and insert axis
svg.append("g").attr("transform", `translate(${bar_margin}, 40)`)
.call(x_axis);
return bar_chart;
}
Insert cell
chart_update = {
// Pie chart
chart.defs.selectAll("*").remove();
for (const d of data) {
if (d.calorific)
{
chart.defs.append(x => mk_fill("hatch-"+d.calorific, d.color, use_heat_for_electric));
}
}

chart.pie_g
.selectAll("path")
.data(pie)
.join("path")
.attr("fill", d => d.data.calorific ? "url(#hatch-" + d.data.calorific + ")" : d.data.color)
.attr("stroke", "#fff")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 1.2)
.attr("d", d => inflate(d.data) ? arc_2(d) : arc(d))
.call(s => s.select("title").remove())
.append("title")
.text(d => `${d.data.name}: ${d.data.kWh} kWh`);
chart.text_g
.selectAll("text")
.data(pie)
.join("text")
.attr("style", "text-shadow: rgba(255, 255, 255, .5) 1px 1px 1px")
.attr("transform", d => `translate(${txt_arc.centroid(d)})`)
.attr("fill", "black")
.attr("y", d => d.data.txt_y || "-0.4em")
.text(d => d.data.name);
}
Insert cell
bar_chart_update = {
bar_chart.defs.selectAll("*").remove();
for (const d of data) {
if (d.calorific)
{
bar_chart.defs.append(x => mk_fill("hatch-"+d.calorific, d.color, use_heat_for_electric));
}
}


bar_chart.g_bars.selectAll("rect")
.data(stacked)
.join("rect")
.attr("fill", d => d.data.calorific ? "url(#hatch-" + d.data.calorific + ")" : d.data.color)
.attr("stroke", "#fff")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 1.2)
.attr("x", d => d.x + bar_margin)
.attr("y", d => inflate(d.data) ? 7.5 : 0.5)
.attr("width", d => d.w)
.attr("height", d => inflate(d.data) ? 32 : 39)
.call(s => s.select("title").remove())
.append("title")
.text(d => `${d.data.name}: ${d.data.kWh} kWh`);
}
Insert cell
inflate = d => use_heat_for_electric && !d.calorific
Insert cell
Insert cell
use_heat_for_electric = use_heat_for_electric_l.length > 0
Insert cell
average_calorific_correction =
data.map(to_kWh).reduce((a, b) => a + b, 0) /
data.map(d => d.kWh).reduce((a, b) => a + b, 0)
Insert cell
data = [
electrify.includes("Heating") ?
{name:"Heating (heat pump)", kWh: gas_kWh_per_m3 * gas_m3 / heat_pump_cof, color:"#fd0"} :
{name:"Heating (gas)", kWh: gas_kWh_per_m3 * gas_m3, color:"#fd0", calorific: "heat"},
electrify.includes("Cars") ?
{name:"Driving (electric)", kWh: 10 * car_kWh_100km * car_km_1000, color:"#f3a"} :
{name:"Driving (gasoline)", kWh: 10 * car_l_100km * petrol_kWh_per_litre * car_km_1000, color:"#f45", calorific: "drive"},
{name:"Hot water", kWh: hot_water_1000l * water_kWh_per_1000l, color:"#88f", txt_y:".1em"},
{name:"Cooking", kWh: cooking, color:"#3dd", txt_y:".1em"},
{name:"Appliances", kWh: appliances, color:"#3f9"},
{name:"Other", kWh: misc, color:"#7b7", is_other:1, txt_y:"-1.1em"},
];
Insert cell
stacked = {
var l = [];
var x = 0;
for (var d of data)
{
var w = bar_scale(to_kWh(d));
l.push({data:d, x:x, w:w});
x += w;
}
return l;
}
Insert cell
Insert cell
pie = d3.pie().value(to_kWh)
.sort(null)(data)
Insert cell
arc = d3.arc()
.innerRadius(chart_size * .25)
.outerRadius(chart_size * .5)
Insert cell
arc_2 = d3.arc()
.innerRadius(chart_size * .28)
.outerRadius(chart_size * .5)
Insert cell
txt_arc = d3.arc()
.innerRadius(chart_size * .35)
.outerRadius(chart_size * .4);
Insert cell
Insert cell
// Heat from combustion is not directly equivalent to electricity.
// The efficiency of fuel power plants is about 50% so sometimes
// a kWh of electricity actually comes from about 2 kWh worth of
// fossil fuel.
calorific_correction_factor = 2
Insert cell
// according to Wikipedia
petrol_kWh_per_litre = 9.3
Insert cell
// approximately, depends on gas composition and on season
gas_kWh_per_m3 = 10
Insert cell
// We’re assuming we’re heating up by 45°, eg. 15 to 60 degrees.
water_kWh_per_1000l = 4184 / 3.6e6 * 45 * 1000
Insert cell
to_kWh = d => (inflate(d) ? calorific_correction_factor : 1) * d.kWh
Insert cell
md`### Chart`
Insert cell
chart_size = Math.min(width - chart_left_pad, 400)
Insert cell
chart_left_pad = 15
Insert cell
bar_margin = 20
Insert cell
bar_scale = d3.scaleLinear()
.domain([0, 40e3])
.range([0, width - bar_margin*2]);
Insert cell
function mk_fill(id, color, use_heat)
{
var fill = use_heat ? .6 : .4;
var t = HatchTile45({size: 5, fill: fill, id:id, fg:color, bg:d3.interpolate(color, "white")(.85)});
return svg`${t.svg()}`;
}
Insert cell
Insert cell
md`### Import`
Insert cell
d3 = require("d3")
Insert cell
import { Range, Toggle, Checkbox } from "@observablehq/inputs"
Insert cell
import { HatchTile45 } from "@roelandschoukens/svg-tiled-pattern-fills"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more