sensitivies = {
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();
}