PageSourceSearch

https://global-plastics-tool.org/js/report_stage.js

js global-plastics-tool.org collected 2026-09-26 01:14:24 UTC 9,576 bytes, 268 lines download raw bytes

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.