1/** 2 * Logic for the combination menu / bar graph overview of different stages. 3 * 4 * @license BSD, see LICENSE.md 5 */ 6 7import { 8 CONSUMPTION_ATTRS, 9 DISPLAY_STAGES, 10 DISPLAY_TYPES, 11 EOL_ATTRS, 12 PRODUCTION_ATTRS, 13 STANDARD_ATTR_NAMES, 14 getGlobalColors, 15} from "const"; 16import {STRINGS, UNITS} from "strings"; 17 18 19/** 20 * Presenter for the stage presenter which typically appears on the left side of the details tab. 21 * 22 * Presenter which summaries different stages for the user and allows for selection from among those 23 * stages where stages include production, consumption, and waste / end of life. 24 */ 25class StagePresenter { 26 /** 27 * Create a new stage presenter to manage the stage summary / selection widget. 28 * 29 * @param targetDiv The div where the stage presenter / selector is rendered. 30 * @param stage The stage currently selected like production. 31 * @param onStageChange Callback to invoke if the user changes the highlighted stage. 32 * @param requestRender Callback to invoke if the visualiztation needs to be redrawn. 33 */ 34 constructor(targetDiv, stage, onStageChange, requestRender) { 35 const self = this; 36 37 self._targetDiv = targetDiv; 38 self._stage = stage; 39 self._onStageChange = onStageChange; 40 self._requestRender = requestRender; 41 42 self._d3Selection = self._getD3().select("#" + targetDiv.id); 43 44 // Setup radio 45 const radio = self._targetDiv.querySelector(".stage-radio"); 46 radio.addEventListener("change", () => { 47 self._onStageChange(self._stage); 48 }); 49 50 self._targetDiv.addEventListener("click", () => { 51 self._onStageChange(self._stage); 52 radio.checked = true; 53 }); 54 55 // Color scales 56 const colorScaleConstructors = new Map(); 57 58 const globalColors = getGlobalColors(); 59 const makeColorsEol = () => { 60 const colorScalesEol = new Map(); 61 EOL_ATTRS.forEach((attr, i) => { 62 const color = globalColors[i]; 63 colorScalesEol.set(attr, color); 64 }); 65 return colorScalesEol; 66 }; 67 colorScaleConstructors.set(DISPLAY_STAGES.eol, makeColorsEol); 68 69 const makeColorsConsumption = () => { 70 const colorScalesConsumption = new Map(); 71 CONSUMPTION_ATTRS.forEach((attr, i) => { 72 const color = globalColors[i]; 73 colorScalesConsumption.set(attr, color); 74 }); 75 return colorScalesConsumption; 76 }; 77 colorScaleConstructors.set(DISPLAY_STAGES.consumption, makeColorsConsumption); 78 79 const makeColorsProduction = () => { 80 const colorScalesProduction = new Map(); 81 PRODUCTION_ATTRS.forEach((attr, i) => { 82 const color = globalColors[i]; 83 colorScalesProduction.set(attr, color); 84 }); 85 return colorScalesProduction; 86 }; 87 colorScaleConstructors.set(DISPLAY_STAGES.production, makeColorsProduction); 88 89 self._colorScale = colorScaleConstructors.get(self._stage)(); 90 } 91 92 /** 93 * Update the display. 94 * 95 * @param stateSet The set of state Maps having gone through the policy simulation. 96 * @param selection Structure describing the selections made by the user like year and region. 97 */ 98 update(stateSet, selection) { 99 const self = this; 100 101 const smallDisplay = window.innerWidth < 1500; 102 103 const selected = selection.getDisplayStage() == self._stage; 104 const attrs = STANDARD_ATTR_NAMES.get(self._stage); 105 106 const unitsStrRaw = UNITS.get(selection.getDisplayType()); 107 const isPercent = selection.getDisplayType() == DISPLAY_TYPES.percent; 108 const unitsStrLong = (isPercent ? "" : " ") + unitsStrRaw; 109 const isMetricTons = unitsStrRaw.includes("Tons"); 110 const unitsStr = (smallDisplay && isMetricTons) ? " Mt" : unitsStrLong; 111 112 const state = stateSet.getWithIntervention(); 113 const regionData = state.get("out").get(selection.getRegion()); 114 115 const maxValue = CONSUMPTION_ATTRS.concat(EOL_ATTRS).concat(PRODUCTION_ATTRS) 116 .map((attr) => Math.abs(regionData.get(attr))) 117 .reduce((a, b) => a > b ? a : b); 118 119 const minValue = selection.getShowBauDelta() ? -maxValue : 0; 120 121 const horizontalScale = self._getD3().scaleLinear() 122 .domain([minValue, maxValue]) 123 .range([0, self._targetDiv.getBoundingClientRect().width - 7]
123); 124 125 const updateSelected = () => { 126 if (selected) { 127 self._targetDiv.classList.add("selected"); 128 self._targetDiv.classList.remove("unselected"); 129 } else { 130 self._targetDiv.classList.remove("selected"); 131 self._targetDiv.classList.add("unselected"); 132 } 133 const radio = self._targetDiv.querySelector(".stage-radio"); 134 radio.checked = selected; 135 }; 136 137 const updateTitle = () => { 138 const title = self._targetDiv.querySelector(".description"); 139 const subtitle = self._targetDiv.querySelector(".subtitle"); 140 141 const regionName = selection.getRegion(); 142 const regionStr = regionName === "row" ? "RoW" : STRINGS.get(regionName); 143 144 const titleText = STRINGS.get(self._stage); 145 const subtitleText = [ 146 selection.getYear() + "", 147 regionStr, 148 ].join(" "); 149 150 title.innerHTML = titleText; 151 subtitle.innerHTML = subtitleText; 152 }; 153 154 const updateBars = () => { 155 const bound = self._d3Selection.select(".bars") 156 .selectAll(".bar") 157 .data(attrs); 158 159 bound.exit().remove(); 160 161 const newBars = bound.enter() 162 .append("div") 163 .classed("bar", true); 164 165 const newText = newBars.append("div") 166 .classed("text", true); 167 168 newText.append("div") 169 .classed("label", true); 170 171 newText.append("div") 172 .classed("value", true); 173 174 const newSvgs = newBars.append("svg") 175 .classed("glyph-holder", true); 176 177 newSvgs.append("rect") 178 .classed("glyph", true); 179 180 newSvgs.append("rect") 181 .classed("zero", true) 182 .style("fill", "#C0C0C0") 183 .attr("width", 1); 184 185 const boundUpdated = self._d3Selection.select(".bars") 186 .selectAll(".bar"); 187 188 const isGlobal = selection.getRegion() === "global"; 189 boundUpdated.select(".label") 190 .html((attr) => { 191 const labelRaw = STRINGS.get(attr); 192 if (smallDisplay && labelRaw === "House, Leis, Sport") { 193 return "Household"; 194 } else if (labelRaw === "Net Export (Good/Mat)" && isGlobal) { 195 return "Goods in Trade"; 196 } else if (labelRaw === "Net Export (Waste)" && isGlobal) { 197 return "Waste Trade"; 198 } else if (isGlobal) { 199 return labelRaw.replace(" (Dom ", " ("); 200 } else { 201 return labelRaw; 202 } 203 }); 204 205 boundUpdated.select(".value") 206 .html((attr) => { 207 const value = Math.round(regionData.get(attr)); 208 const valueStr = value + unitsStr; 209 if (selection.getShowBauDelta() && value >= 0) { 210 return "+" + valueStr; 211 } else { 212 return valueStr; 213 } 214 }); 215 216 boundUpdated.select(".glyph") 217 .transition() 218 .attr("width", (attr) => { 219 const value = regionData.get(attr); 220 const width = horizontalScale(value) - horizontalScale(0); 221 return Math.abs(width); 222 }) 223 .attr("x", (attr) => { 224 const value = regionData.get(attr); 225 const width = horizontalScale(value) - horizontalScale(0); 226 if (width < 0) { 227 return horizontalScale(0) + width; 228 } else { 229 return horizontalScale(0); 230 } 231 }) 232 .style("fill", (attr) => { 233 if (selected) { 234 return self._colorScale.get(attr); 235 } else { 236 return "#C0C0C0"; 237 } 238 }) 239 .attr("height", selection.getShowBauDelta() ? 4 : 7); 240 241 boundUpdated.select(".zero") 242 .transition() 243 .attr("x", horizontalScale(0)) 244 .attr("height", selection.getShowBauDelta() ? 7 : 0); 245 246 const hideZeros = !selection.getShowBauDelta(); 247 if (hideZeros) {
248 boundUpdated.style("display", (attr) => { 249 const isZero = regionData.get(attr) == 0; 250 return isZero ? "none" : "block"; 251 }); 252 } 253 }; 254 255 updateSelected(); 256 updateTitle(); 257 updateBars(); 258 } 259 260 _getD3() { 261 const self = this; 262 // eslint-disable-next-line no-undef 263 return d3; 264 } 265} 266 267 268export {StagePresenter};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.