Published
Edited
Oct 26, 2020
3 forks
Insert cell
md`# MIT Graph Output`
Insert cell
data = FileAttachment("TEST.csv").csv({typed: true})
Insert cell
height = 425
Insert cell
margin = ({top: 20, right: 20, bottom: 30, left: 50})
Insert cell
c_max = {
// const vals = ['E', 'C', 'S'].map(d => d3.range(1, 13).map(dd => d + dd)).flat();
// let filtered = data.filter(d => d.Run === "Baseline").map(d => vals.map(dd => d[dd]));
// return d3.max(filtered.flat()) * 1.1;
return 80;
}
Insert cell
Insert cell
Insert cell
Insert cell
v_margin = ({top: 80, right: 100, bottom: 50, left: 325})
Insert cell
v_height = 1375
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@^6.1")
Insert cell
_ = require('lodash@4.17.15/lodash.js').catch(() => window["_"])
Insert cell
c_x = d3.scaleLinear()
.domain([0, 11])
.range([margin.left, width - margin.right])
Insert cell
c_y = d3.scaleLinear()
.domain([0, c_max]).nice()
.range([height - margin.bottom, margin.top])
Insert cell
c_xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(c_x).tickFormat(d => dates[d]).tickSizeOuter(0).tickPadding(12))
Insert cell
c_yAxis = g => g
.attr("transform", `translate(${margin.left},0)`).attr("class", "axis")
.call(d3.axisLeft(c_y).tickPadding(8).ticks(5))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.html("Monthly Energy Use kbtu/ft²"))
Insert cell
dates = d3.timeMonth.range(new Date(2019, 0, 1), new Date(2019, 11, 30)).map(d3.timeFormat("%b"));
Insert cell
line = d3.line()
.defined(d => !isNaN(d))
.x((d, i) => c_x(i))
.y(d => c_y(d))
.curve(d3.curveCardinal)
Insert cell
colors = ({"S": "#f79320", "E": "#fed202", "C":"#00a1b0"})
Insert cell
chroma = require('chroma-js')
Insert cell
v_lookup = ({
"WWR": "Window-Wall Ratio (%)",
"WINU": "Window U-Value (BTU/h-ft2-°F)",
"WINSHGC": "Window SHGC",
"WALLR": "Wall R (h-ft2-°F/BTU)",
"ROOFR": "Roof R (h-ft2-°F/BTU)",
"EQUIPUSAGE": "Equipment Usage Schedule",
"LIGHTUSAGE": "Lighting Usage Schedule",
"EPD": "Equipment Power Density (W/ft2)",
"LPD": "Lighting Power Density (W/ft2)",
"HRV": "Sensible ERV Effectiveness",
"ACH": "Air Changes per Hour",
"VENTSCHED": "Ventilation Schedule",
"HUMID": "Humidity Control Max RH",
"SUPPTEMPRESET": "Max Supply Air Temperature",
"FANPOWER": "Fan Pressure Drop (in. H2O)"
})
Insert cell
v_data = {
let filtered = data.filter(d => d.Run === "Baseline");

const count = (d, i) => {
const arr = filtered.map(dd => dd[d]);
return arr.filter(dd => dd === i).length;
}
const keymap = Object.keys(v_lookup).map(d => ({variable: d, "0": count(d, 0), "1": count(d, 1), "2": count(d, 2), "3": count(d, 3)}));
return keymap;

}
Insert cell
energy = d => {
let r = "Low";
switch(d) {
case 0:
r= "High";
break;
case 1:
r= "Mid-High";
break;
case 2:
r= "Low-High";
break;
default:
r="Low";
}
return r;
}
Insert cell
v_y = d3.scaleBand()
.domain(Object.keys(v_lookup).reverse())
.range([v_height - v_margin.bottom, v_margin.top])
.padding(.1)
.paddingOuter(.5)
Insert cell
v_x = d3.scaleLinear()
.domain([0, 3]).nice()
.range([v_margin.left, width - v_margin.right])
Insert cell
v_xAxis = g => g
.attr("transform", `translate(0,${v_margin.top})`)
.call(d3.axisTop(v_x).tickValues([0, 1, 2, 3]).tickFormat((d, i) => energy(i)).tickPadding(10))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:first-of-type text").clone()
.attr("y", -50)
.attr("x", -18)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.html("Energy Use Impact"))
Insert cell
v_yAxis = g => g
.attr("transform", `translate(${5},0)`)
.call(d3.axisRight(v_y).tickFormat(d => v_lookup[d]))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").remove())
Insert cell
v_yAxis_ticks = g => g
.attr("transform", `translate(${v_margin.left},0)`)
.call(d3.axisRight(v_y).tickFormat(d => v_lookup[d]))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll("text").remove())
.call(g => g.selectAll(".tick line")
.attr("stroke-opacity", 0.1)
.attr("x2", width - v_margin.left - v_margin.right))
Insert cell
v_scale = d3.scaleLinear()
.domain([0, 100])
.range([0, v_y.bandwidth()/1.5])
Insert cell
p_lookup = ({
"WWR": ["60%", "50%", "40%", "30%"],
"WINU": ["1.0", "0.8", "0.6", "0.4"],
"WINSHGC": ["0.2", "0.3", "0.4", "0.5"],
"WALLR": ["5", "10", "20", "30"],
"ROOFR": ["10", "20", "30", "40"],
"EQUIPUSAGE": ["High Intensity", "Uniform Academic", "Seasonal Academic", "Standard"],
"LIGHTUSAGE": ["High Intensity", "Uniform Academic", "Seasonal Academic", "Standard"],
"EPD": ["8", "6", "4", "2"],
"LPD": ["1.6", "1.3", "1", "0.7"],
"HRV": ["0.0", "0.2", "0.4", "0.6"],
"ACH": ["15", "12", "9", "6"],
"VENTSCHED": ["0", "1", "2", "3"],
"HUMID": ["50", "60", "70", "80"],
"SUPPTEMPRESET": ["56", "60", "64", "68"],
"FANPOWER": ["10", "8", "6", "4"]
})
Insert cell
highlight = (d, i) => {
const objs = Object.entries(d).filter(dd => dd[0] != "variable").sort((a, b) => b[1] - a[1]);
const index = +objs[0][0];
return index === i ? "#00a1b0": "#dfdfdf";

}
Insert cell
ecm_data = {
const filtered = data.filter(d => d.Run != "Metered" && d.Run != "Baseline");
const grouped = _.groupBy(filtered, d=> d.Run);
const map = new Map();
for (const [key, arr] of Object.entries(grouped)) {
let f = arr.map(d => d["GHG Savings MT"]);
map.set(key, quartiles(f));
}

return map;
}
Insert cell
quartiles = (data) => {

const values = data.sort((a, b) => a - b);
const min = values[0];
const max = values[values.length - 1];
const q1 = d3.quantile(values, 0.25);
const q2 = d3.quantile(values, 0.50);
const q3 = d3.quantile(values, 0.75);
const iqr = q3 - q1; // interquartile range
const r0 = Math.max(min, q1 - iqr * 1.5);
const r1 = Math.min(max, q3 + iqr * 1.5);
values.quartiles = [q1, q2, q3];
values.range = [r0, r1];
values.outliers = values.filter(v => v < r0 || v > r1); // TODO
return values;
}
Insert cell
b_lookup = ({
"Envelope": "Envelope Upgrades",
"HRV": "Heat Recovery Ventilation",
"STR60": "Supply Air Temperature Reset",
"WAC": "Enhanced SAT Reset",
"VENT2": "Ventilation Measures",
"VENT3": "Enhanced Ventilation Measures",
"FanPower": "Fan Energy Reductions",
"LPD": "LPD Reductions",
"Combined": "Combined ECMs"
})
Insert cell
b_y = d3.scaleBand()
.domain(Object.keys(b_lookup).reverse())
.range([b_height - b_margin.bottom, b_margin.top])
.padding(.1)
.paddingOuter(.5)
Insert cell
b_x = d3.scaleLinear()
.domain([0, d3.max(Array.from(ecm_data.values()).flat())]).nice()
.range([b_margin.left, width - b_margin.right])
Insert cell
b_xAxis = g => g
.attr("transform", `translate(0,${v_margin.top})`)
.call(d3.axisTop(b_x).tickPadding(10).tickFormat(d3.format("~s")))
// .call(g => g.select(".domain").remove())
.call(g => g.select(".tick:first-of-type text").clone()
.attr("y", -50)
.attr("x", -3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.html("Annual GHG Reductions (MT CO2e/yr)"))
.call(g => g.selectAll(".tick line").clone()
.attr("stroke-opacity", 0.1)
.attr("y2", b_height - b_margin.bottom - 80))
Insert cell
b_yAxis = g => g
.attr("transform", `translate(${5},0)`)
.call(d3.axisRight(b_y).tickFormat(d => b_lookup[d]))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").remove())
Insert cell
b_margin = ({top: 80, right: 100, bottom: 50, left: 325})
Insert cell
b_height = 860
Insert cell
showtick = d => {
if (ecm_data.get(d).quartiles[1] < 10) {
return 0;
}
else if (ecm_data.get(d).quartiles[2] - ecm_data.get(d).quartiles[0] < b_x.domain()*.05) {
return 0;
}
else { return 1;}
}
Insert cell
//reset to filter ecms
ecm_highlight = d => {
if (ecm_data.get(d).quartiles[1] < b_x.domain()[1]*.10) {
return "#00a1b0";
//return "#bfbfbf"
}
else { return "#00a1b0";}
}
Insert cell
//reset to turn on labels
text_highlight = d => {
if (ecm_data.get(d).quartiles[1] < b_x.domain()[1]*.10) {
return 0;
}
else { return 0;}
//else { return 1;}
}
Insert cell
percentage_savings = d => {
const reduction = ecm_data.get(d).quartiles[1] / (data.filter(v => v.Run === "Metered")[0]["GHG Total lbs"]/2204);
return d3.format(".0%")(reduction)

}
Insert cell
t_x = d3.scaleBand()
.domain([0, 1])
.range([margin.left, width - margin.right])
.padding(0.1)
.paddingOuter(1)
Insert cell
t_y = d3.scaleLinear()
.domain([0, data.filter(d => d.Run === "Metered")[0]["GHG Total lbs"]/2204 * 1.3]).nice()
.range([height - margin.bottom, margin.top])
Insert cell
runs = ["Baseline (Current)", "With Combined ECMs"]
Insert cell
t_xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(t_x).tickFormat(i => runs[i]).tickSizeOuter(.5).tickPadding(12))
Insert cell
t_yAxis = g => g
.attr("transform", `translate(${margin.left},0)`).attr("class", "axis")
.call(d3.axisLeft(t_y).tickPadding(8).ticks(5).tickFormat(d3.format("~s")))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.html("Annual GHG Emissions MT CO2e"))
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