Published
Edited
May 12, 2020
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
sensitivies = {
//general setup
const config = {
width: 960,
cell: {height: 250, padding:40},
margin: {top: 60, right:10, bottom: 30, left: 35},
cols: 3,
format: {
"Window U": {
newName: "Window U",
convert: d => 1/d,
ticks: d => d3.format(".2f")(1/d)},
"WWR": {
newName: "Window-Wall Ratio",
ticks: d3.format(",.0%")},
"WWR wShades": {
newName: "Window-Wall Ratio w/ Shading",
ticks: d3.format(",.0%")},
"TEDI": {
newName: "TEDI + CEDI",
},
"Window SHGC wShades": {
newName: "Window SHGC w/ Shading",
}},

units: unitSelection.unit,
y: euiSwitch.y
}
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("display", "none")
.style("position", "absolute")
//.style("width", "80px")
//.style("height", "30px")
.style("z-index", "1000")
.style("padding", "12px")
.style("font-weight", "bold")
.style("font-family", "Open Sans")
.style("font-size", "12px")
.style("background", "#ffffff")
.style("opacity", 0.9)
.style("color", "#333333")
.style("pointer-events", "none");

//handles conversion for each variable in config.format
const data = sensitivity_data.map(d => {
let dd = d.data;
for (const key in config.format) {
console.log(key);
if ("convert" in config.format[key]) {
dd = dd.map(p => ({...p, [key]: config.format[key].convert(p[key])}));
}
}
return {variable: d.variable, data: dd};
});
//find the width of cells by dividing the total width by the number of cols
config.cell.width = _.round(
(config.width - config.margin.left - config.margin.right - config.cell.padding * (config.cols-1)) / config.cols
);
//csize the svg based on the number of rows
const svg = d3.select(DOM.svg(config.width,
(config.cell.height + config.margin.bottom + config.cell.padding)
* _.ceil(data.length / 3) + config.margin.top - config.margin.bottom
));
//all the y values are the same - could use a map here to get different scales for each small multiple
const y = d3.scaleLinear()
.domain(d3.extent(data.map(d => d.data.map(p => eui(p[config.y]))).flat()))
.range([config.cell.height, 0]).nice();
//create a map for all the x scales
const x = new Map(
Array.from(
data.map(d => d.variable),
key => [
key,
d3.scaleLinear(d3.extent(data.filter(d => d.variable === key)[0].data.map(d => d[key])), key === "Infiltration" ? [config.cell.width, 0] : [0, config.cell.width])
]
)
);
//create a group for each chart
const chart = svg.selectAll("g")
.data(data)
.join("g")
.attr("transform", (d, i) => {
//position the group in its appropriate row and col
const h = (i % config.cols) * (config.cell.width + config.cell.padding) + config.margin.left;
const v = _.floor(i / config.cols) * (config.cell.height + config.margin.bottom + config.cell.padding) + config.margin.top;
return `translate(${h},${v})`
});

//run through each group
chart.each(function(d, i) {
//rename the variable if needed
let name = d.variable;
if (name in config.format) {
if ("newName" in config.format[name]){
name = config.format[name].newName;
}
}
//if there are format specifiers, use them, otherwise just use the default
let xa = d3.axisBottom(x.get(d.variable)).ticks(6);
if (d.variable in config.format) {
if ("ticks" in config.format[d.variable]){
xa.tickFormat(config.format[d.variable].ticks);
}
}
const xAxis = g => g
.attr("transform", `translate(0,${config.cell.height})`)
.call(xa)
.call(g => g.select(".domain").remove());
const yAxis = g => {
if (i % config.cols === 0) {
g.call(d3.axisLeft(y).ticks(6).tickFormat(d3.format(".1f")))
.call(g => g.select(".domain").remove());
}
}
//put the y axis label in the top left corner of the first row/col.
if (i === 0) {
d3.select(this).append("g")
.attr("transform", "translate(15, 20)")
.append("text")
.attr("font-size", 10)
.attr("text-anchor", "start")
.attr("opacity",1)
.html(config.y === "TEDI" ?
(config.units === "IP" ? `TEDI + CEDI kBTU/ft²/yr`: `TEDI + CEDI kWh/m²/yr`) : `${config.y}`
);
}
//general pattern for generating the interpolation line
const line = d3.line()
.x(dd => x.get(d.variable)(dd[d.variable]))
.y(dd => y(eui(dd[config.y])))
.curve(d3.curveCardinal.tension(0));
//horizontal grid lines
d3.select(this).selectAll(".grid .horizontal")
.data(_.range(8).slice(1).map(d => d * config.cell.height / 8))
.join("line")
.attr("class", "grid")
.attr("x1", (i % config.cols != 0) ? -config.cell.padding : 0)
.attr("y1", d => d)
.attr("x2", config.cell.width)
.attr("y2", d => d);
//vertical grid lines
d3.select(this).selectAll(".grid .vertical")
.data(_.range(8).slice(1).map(d => d * config.cell.width / 8))
.join("line")
.attr("class", "grid")
.attr("x1", d => d)
.attr("y1", 0)
.attr("x2", d => d)
.attr("y2", config.cell.height);
d3.select(this).append("g").call(xAxis);
d3.select(this).append("g").call(yAxis);
d3.select(this).append("rect")
.attr("class", "frame")
.classed("filled", (d.variable === "Infiltration"))
.attr("x", 0)
.attr("y", 0)
.attr("width", config.cell.width)
.attr("height", config.cell.height);
//add the variable labels
d3.select(this).append("text")
.attr("x", 0)
.attr("y", -15)
.attr("text-anchor", "start")
.attr("font-size", 12)
.style("font-family", "Open Sans")
.html(name);
d3.select(this).selectAll(".recommended")
.data(d.data.filter(dd => dd.Recommended === "yes"))
.join("circle")
.attr("class", "recommended")
.attr("cx", function(dd) { return x.get(d.variable)(dd[d.variable]); })
.attr("cy", function(dd) { return y(eui(dd[config.y])); })
.attr("r",10)
.attr("fill", "#b4d6e8")
.attr("opacity", 0.8);
d3.select(this).append("path")
.datum(d.data)
.attr("class", "interpolation")
.attr("fill", "none")
.attr("stroke", d.variable === "Infiltration" ? "#kjahsd": "#1f77b4")
.attr("stroke-width", 2)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", line);

d3.select(this).selectAll("points")
.data(d.data)
.join("circle")
.attr("class", "points")
.attr("cx", function(dd) { return x.get(d.variable)(dd[d.variable]); })
.attr("cy", function(dd) { return y(eui(dd[config.y])); })
.attr("r",3)
.attr("display", dd => {
if (d.variable === "WWR" && dd[d.variable] === 0.35) {
return "none";
}
else if (d.variable === "Window U" && dd[d.variable] === 1/0.35) {
return "none";
}
else {
return null;
}
})
.attr("stroke", "#1f77b4")
.style("fill", dd => dd.Baseline === "yes" ? "#1f77b4": "#b4d6e8" );

d3.select(this).selectAll("overlay")
.data(d.data)
.join("circle")
.attr("class", "overlay")
.attr("r",18)
.attr("stroke", "none")
.style("fill", "#fff")
.style("opacity", 0)
.attr("cx", function(dd) { return x.get(d.variable)(dd[d.variable]); })
.attr("cy", function(dd) { return y(eui(dd[config.y])); })
.on("mouseover", dd => {
tooltip.style("display", null);
let data = dd[d.variable];
if (d.variable in config.format) {
if ("ticks" in config.format[d.variable]){
data = config.format[d.variable].ticks(data);
}
}
tooltip.html(`${data} | ${config.y} ${_.round(eui(dd[config.y]))} `)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");;
})
.on("mousemove", dd => {

tooltip
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", d => {
tooltip.style("display", "none");
});

});
return svg.node();
}


Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// md`
// </br></br></br>
// Detailed model input assumptions are documented [here.](https://www.elementa.nyc/projects/ccabe/assets/191101_CCABE_EM_Inputs.pdf)</br></br></br>`
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
Insert cell
Insert cell
Insert cell
Insert cell
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