Blame view

flaskr/static/js/plots/emissions-per-distance.js 13.8 KB
701dbf83   Antoine Goutenoir   feat: integrate t...
1
function draw_emissions_per_distance(containerSelector, csvUrl) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
2
    // set the dimensions and margins of the graph
79eadc9f   Antoine Goutenoir   feat: polish
3
    let margin = {top: 48, right: 88, bottom: 68, left: 98},
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
4
5
        width = 960 - margin.left - margin.right,
        height = 540 - margin.top - margin.bottom;
910464a0   Antoine Goutenoir   feat: switch to d...
6

8daea46b   Antoine Goutenoir   fix: interactive ...
7
8
9
    const sliceThickness = 500.0;


ad528ad0   Antoine Goutenoir   fix: use DOMConte...
10
    function getTicks(maxValue, interval, startValue = 0) {
955be6d0   Antoine Goutenoir   fix: infinite loop
11
12
13
14
15
16
17
18
19
        // console.log("getTicks", maxValue, interval, startValue);
        if (0 === interval) {
            console.error("No interval for ticks.");
            return [0];
        }
        if (Math.sign(maxValue-startValue) !== Math.sign(interval)) {
            console.warn("Wrong interval sign for ticks.  Corrected.");
            interval *= -1;
        }
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
20
21
22
        let range = [];
        for (let i = startValue; i <= maxValue; i += interval) {
            range.push(i);
910464a0   Antoine Goutenoir   feat: switch to d...
23
        }
955be6d0   Antoine Goutenoir   fix: infinite loop
24
        // console.log("range", range);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
25
26
        return range;
    }
910464a0   Antoine Goutenoir   feat: switch to d...
27

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
28
29
    function getBottomTicks(maxDistance) {
        let range = getTicks(maxDistance, 2500, 2500);
e86f2462   Antoine Goutenoir   design: refactor ...
30
        range.push(sliceThickness);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
31
32
        return range;
    }
910464a0   Antoine Goutenoir   feat: switch to d...
33

955be6d0   Antoine Goutenoir   fix: infinite loop
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    function getLeftTicks(maximumValue) {
        maximumValue = 3200+1;
        if (0 > maximumValue) {
            console.error("Only positive values are supported on left axis.");
        }
        if (0 === maximumValue) {
            return [0];
        }
        let ticksAmount = 8.0; // MUST BE > 2
        let magnitude = 100000.0;
        let interval = 0;
        while (interval * (ticksAmount) < maximumValue) {
            interval = (
                Math.floor(
                    (maximumValue / (ticksAmount-1)) / magnitude
                )
                *
                magnitude
            );
            //console.log("interval + magnitude", interval, magnitude);
            magnitude *= 0.1;
        }

        return getTicks(maximumValue, interval);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
58
    }
910464a0   Antoine Goutenoir   feat: switch to d...
59

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
60
61
62
63
    function getRightTicks(maxemissionsPercent) {
        return getTicks(maxemissionsPercent, 2);
    }

8daea46b   Antoine Goutenoir   fix: interactive ...
64
65
    function getAttendeesAmountOnRight(sliceId, attendeeNumberPerGroup) {
        let attendeesAmount = 0;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
66
67
68
        let sliceInt = Math.floor(sliceId);
        let sliceFloat = sliceId - sliceInt;
        for (let i = sliceInt; i < attendeeNumberPerGroup.length; i++) {
8daea46b   Antoine Goutenoir   fix: interactive ...
69
            attendeesAmount += attendeeNumberPerGroup[i] * (1 - sliceFloat);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
70
            sliceFloat = 0;
910464a0   Antoine Goutenoir   feat: switch to d...
71
        }
8daea46b   Antoine Goutenoir   fix: interactive ...
72
        return attendeesAmount;
910464a0   Antoine Goutenoir   feat: switch to d...
73

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
74
    }
910464a0   Antoine Goutenoir   feat: switch to d...
75

8daea46b   Antoine Goutenoir   fix: interactive ...
76
    function setupCursorBoxes(event, attendeeSum, attendeeNumberPerGroup, xScale, tooltip, vertical, rightArea) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
77
78
79
        const x = d3.pointer(event)[0];
        const y = d3.pointer(event)[1];
        // var y = d3.event.pageY - document.getElementById(<id-of-your-svg>).getBoundingClientRect().y + 10
8daea46b   Antoine Goutenoir   fix: interactive ...
80
81
        // console.log("Mouse move", x, y);

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
82
83
        vertical.style("left", x + "px");
        rightArea.style("left", x + "px");
990a9752   Antoine Goutenoir   feat: clean the h...
84
        rightArea.style("width", (width + margin.left - x) + "px");
8daea46b   Antoine Goutenoir   fix: interactive ...
85
86
87
        tooltip.style("left", (x + 10) + "px");
        tooltip.style("top", (y - 20) + "px");

8daea46b   Antoine Goutenoir   fix: interactive ...
88
        let sliceId = xScale.invert(x - margin.left) / sliceThickness;
79eadc9f   Antoine Goutenoir   feat: polish
89
        // console.log(`Slice ${sliceId} under the mouse.`);
8daea46b   Antoine Goutenoir   fix: interactive ...
90

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
91
        // let sliceId = xScale.invert(x * 1.025 - d3.selectAll("g.yl.axis")._groups[0][0].getBoundingClientRect().x- margin.left)/500 +1;
8daea46b   Antoine Goutenoir   fix: interactive ...
92
93
94
95
96
        let attendeePercent = (getAttendeesAmountOnRight(sliceId, attendeeNumberPerGroup) / attendeeSum * 100.0).toFixed(1);
        if (
            x < margin.left
            ||
            x > width + margin.left
79eadc9f   Antoine Goutenoir   feat: polish
97
98
99
100
            ||
            y < margin.top
            ||
            y > height + margin.top
8daea46b   Antoine Goutenoir   fix: interactive ...
101
        ) {
e86f2462   Antoine Goutenoir   design: refactor ...
102
103
            rightArea.style("display", "none");
            vertical.style("display", "none");
8daea46b   Antoine Goutenoir   fix: interactive ...
104
            tooltip.style("display", "none");
e86f2462   Antoine Goutenoir   design: refactor ...
105
106
107
        } else {
            rightArea.style("display", "inherit");
            vertical.style("display", "inherit");
8daea46b   Antoine Goutenoir   fix: interactive ...
108
            tooltip.style("display", "inherit");
910464a0   Antoine Goutenoir   feat: switch to d...
109
        }
701dbf83   Antoine Goutenoir   feat: integrate t...
110

990a9752   Antoine Goutenoir   feat: clean the h...
111
        tooltip.text((((attendeePercent < 10) ? '0' : '') + attendeePercent) + "% of attendees");
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
112
    }
910464a0   Antoine Goutenoir   feat: switch to d...
113

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
114
    function addVerticalLineAndListenCursor(xScale, attendeeNumberPerGroup, attendeeSum) {
701dbf83   Antoine Goutenoir   feat: integrate t...
115
        let verticalRuler = d3.select(containerSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
116
            .append("div")
701dbf83   Antoine Goutenoir   feat: integrate t...
117
            .attr("class", "no-pointer-events")
e86f2462   Antoine Goutenoir   design: refactor ...
118
            .style("display", "none")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
119
120
121
122
            .style("position", "absolute")
            .style("z-index", "19")
            .style("width", "2px")
            .style("height", (height) + "px")
79eadc9f   Antoine Goutenoir   feat: polish
123
            .style("top", (margin.top) + "px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
124
            .style("bottom", "30px")
8daea46b   Antoine Goutenoir   fix: interactive ...
125
            .style("left", "-10px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
126
            .style("background", "#000");
910464a0   Antoine Goutenoir   feat: switch to d...
127

701dbf83   Antoine Goutenoir   feat: integrate t...
128
        let rightArea = d3.select(containerSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
129
            .append("div")
701dbf83   Antoine Goutenoir   feat: integrate t...
130
            .attr("class", "no-pointer-events")
e86f2462   Antoine Goutenoir   design: refactor ...
131
            .style("display", "none")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
132
133
            .style("position", "absolute")
            .style("z-index", "-50")
8daea46b   Antoine Goutenoir   fix: interactive ...
134
            .style("width", "0px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
135
            .style("height", (height) + "px")
79eadc9f   Antoine Goutenoir   feat: polish
136
            .style("top", (margin.top) + "px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
137
138
139
            .style("bottom", "30px")
            .style("left", "0px")
            .style("background", "rgba(60, 200, 60, 0.3)");
910464a0   Antoine Goutenoir   feat: switch to d...
140

701dbf83   Antoine Goutenoir   feat: integrate t...
141
        let tooltip = d3.select(containerSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
142
            .append("div")
8daea46b   Antoine Goutenoir   fix: interactive ...
143
144
            .attr("class", "no-pointer-events")
            .style("display", "none")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
145
146
            .style("position", "absolute")
            .style("z-index", "20")
701dbf83   Antoine Goutenoir   feat: integrate t...
147
            .style("width", "161px")
e86f2462   Antoine Goutenoir   design: refactor ...
148
            .style("height", "35px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
149
150
151
            .style("top", "10px")
            .style("bottom", "30px")
            .style("left", "0px")
e86f2462   Antoine Goutenoir   design: refactor ...
152
153
154
155
            .style("padding-left", "10px")
            .style("padding-right", "0px")
            .style("padding-top", "6px")
            .style("padding-bottom", "6px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
156
157
            .style("border", "1px solid grey")
            .style("background", "rgba(255, 255, 255, 0.7)");
561e9392   Antoine Goutenoir   chore: lint
158

701dbf83   Antoine Goutenoir   feat: integrate t...
159
        d3.select(containerSelector + " svg")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
160
            .on("mousemove", function (event) {
8daea46b   Antoine Goutenoir   fix: interactive ...
161
                setupCursorBoxes(event, attendeeSum, attendeeNumberPerGroup, xScale, tooltip, verticalRuler, rightArea);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
162
163
            })
            .on("mouseover", function (event) {
8daea46b   Antoine Goutenoir   fix: interactive ...
164
                setupCursorBoxes(event, attendeeSum, attendeeNumberPerGroup, xScale, tooltip, verticalRuler, rightArea);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
165
166
            });
    }
910464a0   Antoine Goutenoir   feat: switch to d...
167

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
168
    document.addEventListener("DOMContentLoaded", () => {
955be6d0   Antoine Goutenoir   fix: infinite loop
169
        console.info("[Emissions Per Distance] Starting…");
701dbf83   Antoine Goutenoir   feat: integrate t...
170
171
        width = Math.max(880, $(containerSelector).parent().width());
        width = width - margin.left - margin.right;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
172
173
174
        let maxemissions = 0;
        let maxemissionsPercent = 0;
        let maxDistance = 0;
701dbf83   Antoine Goutenoir   feat: integrate t...
175
        let svg = d3.select(containerSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
176
            .append("svg")
701dbf83   Antoine Goutenoir   feat: integrate t...
177
            // .attr("id", containerSelector + "-svg")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
178
179
180
181
182
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform",
                "translate(" + margin.left + "," + margin.top + ")");
910464a0   Antoine Goutenoir   feat: switch to d...
183

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
184
185
186
187
188
        let emissionsPerGroup = [];
        let attendeeNumberPerGroup = [];
        let emissionsSum = 0;
        let attendeeSum = 0;
        let rows = [];
910464a0   Antoine Goutenoir   feat: switch to d...
189

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
190
191
192
193
194
195
196
197
198
        const on_csv_datum = function (datum) {
            let trainAttendee = parseInt(datum["train trips_amount"]);
            let planeAttendee = parseInt(datum["plane trips_amount"]);
            if (trainAttendee === 0 && planeAttendee === 0) {
                return;
            }
            let attendeeNumber = trainAttendee + planeAttendee;
            let distance_km = datum.distance_km / attendeeNumber;
            let co2_kg = parseFloat(datum.co2_kg);
955be6d0   Antoine Goutenoir   fix: infinite loop
199
200
201
202
            if (
                (co2_kg === "NaN")
                ||
                (distance_km === "NaN")
771b70f5   Antoine Goutenoir   chore: clean up
203
204
                // ||
                // (distance_km / sliceThickness > 37)
955be6d0   Antoine Goutenoir   fix: infinite loop
205
            ) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
206
207
208
209
210
                return;
            }
            rows.push(datum);
            maxDistance = Math.max(maxDistance, distance_km);
            emissionsSum += co2_kg;
910464a0   Antoine Goutenoir   feat: switch to d...
211

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
212
        };
910464a0   Antoine Goutenoir   feat: switch to d...
213

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
214
        const on_csv_ready = function () {
955be6d0   Antoine Goutenoir   fix: infinite loop
215
            console.info("[Emissions Per Distance] Generating…");
e86f2462   Antoine Goutenoir   design: refactor ...
216
            for (let i = 0; i <= maxDistance / sliceThickness; i++) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
217
218
219
220
221
222
223
224
225
                emissionsPerGroup[i] = 0;
                attendeeNumberPerGroup[i] = 0;
            }
            rows.forEach((element, index) => {
                let trainAttendee = parseInt(element["train trips_amount"]);
                let planeAttendee = parseInt(element["plane trips_amount"]);
                let attendeeNumber = trainAttendee + planeAttendee;
                let distance_km = element.distance_km / attendeeNumber;
                let co2_kg = parseFloat(element.co2_kg);
e86f2462   Antoine Goutenoir   design: refactor ...
226
227
                emissionsPerGroup[Math.floor(distance_km / sliceThickness)] += parseFloat(co2_kg);
                attendeeNumberPerGroup[Math.floor(distance_km / sliceThickness)] += attendeeNumber;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
228
229
230
231
232
233
234
                attendeeSum += attendeeNumber;
            });
            emissionsPerGroup.forEach((element, index) => {
                maxemissions = Math.max(maxemissions, element);
                maxemissionsPercent = Math.max(maxemissionsPercent, element / emissionsSum * 100.0)
            });
            maxDistance += 2000;
910464a0   Antoine Goutenoir   feat: switch to d...
235

8daea46b   Antoine Goutenoir   fix: interactive ...
236
            // Title
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
237
238
            svg.append("text")
                .attr("transform",
990a9752   Antoine Goutenoir   feat: clean the h...
239
                    "translate(" + (0) + ", " + (-18) + ")")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
240
241
242
                .style("font-weight", "bold")
                .style("font-size", "130%")
                .text("Emissions per distance");
910464a0   Antoine Goutenoir   feat: switch to d...
243

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
244
245
246
247
248
            // X axis: scale and draw:
            let x = d3.scaleLinear()
                .domain([0, maxDistance])
                .range([0, width]);
            let xAxis = d3.axisBottom(x)
955be6d0   Antoine Goutenoir   fix: infinite loop
249
250
251
                .ticks(11)
                // .tickValues(getBottomTicks(maxDistance))
            ;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
252
253
254
255
256
257
258
259
260
261
            svg.append("g")
                .attr("transform", "translate(0," + height + ")")
                .attr("class", "x axis")
                .call(xAxis);
            d3.selectAll("g.x.axis")
                .selectAll(".tick")
                .filter(function (d) {
                    return d === 0;
                })
                .remove();
910464a0   Antoine Goutenoir   feat: switch to d...
262

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
263
264
265
266
267
            // Y axis Left
            let yl = d3.scaleLinear()
                .domain([0, maxemissions])
                .range([height, 0]);
            let ylAxis = d3.axisLeft(yl)
955be6d0   Antoine Goutenoir   fix: infinite loop
268
269
270
                    .ticks(13)
                // .tickValues(getLeftTicks(maxemissions))
            ;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
271
272
273
            svg.append("g")
                .attr("class", "yl axis")
                .call(ylAxis);
910464a0   Antoine Goutenoir   feat: switch to d...
274

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
275
276
277
278
279
            // Y axis Right
            let yr = d3.scaleLinear()
                .domain([0, maxemissionsPercent])
                .range([height, 0]);
            let yrAxis = d3.axisRight(yr)
955be6d0   Antoine Goutenoir   fix: infinite loop
280
281
282
                    .ticks(20)
                // .tickValues(getRightTicks(maxemissionsPercent))
            ;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
283
284
285
286
            svg.append("g")
                .attr("transform", "translate(" + width + ", 0)")
                .attr("class", "yr axis")
                .call(yrAxis);
910464a0   Antoine Goutenoir   feat: switch to d...
287

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
288
289
290
            svg.append("text")
                .attr("transform",
                    "translate(" + (width / 2) + " ," +
79eadc9f   Antoine Goutenoir   feat: polish
291
                    (height + margin.top + 2) + ")")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
292
293
                .style("text-anchor", "middle")
                .text("Distance travelled (km)");
910464a0   Antoine Goutenoir   feat: switch to d...
294

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
295
296
297
298
            svg.append("text")
                .attr("transform",
                    "translate(" + (width) + ", 0), rotate(-90)")
                .attr("x", 0 - (height / 2))
79eadc9f   Antoine Goutenoir   feat: polish
299
                .attr("y", 13 + margin.right / 2.0)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
300
                .style("text-anchor", "middle")
79eadc9f   Antoine Goutenoir   feat: polish
301
                .text("Share of emission (%)");
910464a0   Antoine Goutenoir   feat: switch to d...
302

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
303
304
            svg.append("text")
                .attr("transform", "rotate(-90)")
79eadc9f   Antoine Goutenoir   feat: polish
305
                .attr("y", 0 - (5 * margin.left / 6.0))
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
306
307
308
                .attr("x", 0 - (height / 2))
                .attr("dy", "1em")
                .style("text-anchor", "middle")
79eadc9f   Antoine Goutenoir   feat: polish
309
                .text("Emission (kgCO2e)");
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
310
            // set the parameters for the histogram
8daea46b   Antoine Goutenoir   fix: interactive ...
311
            const histogram = d3.histogram()
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
312
                .domain(x.domain())  // then the domain of the graphic
e86f2462   Antoine Goutenoir   design: refactor ...
313
                .thresholds(x.ticks(Math.floor(maxDistance / sliceThickness))); // then the numbers of bins
910464a0   Antoine Goutenoir   feat: switch to d...
314

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
315
            let histolol = histogram(0);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
316
317
            let barSettings = [];
            emissionsPerGroup.forEach((element, index) => {
990a9752   Antoine Goutenoir   feat: clean the h...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
                barSettings[index] = {
                    height: element,
                    leftBorder: histolol[index].x0,
                    rightBorder: histolol[index].x1,
                };
                if (attendeeNumberPerGroup[index] > 0) {
                    svg.append("text")
                        .attr("transform", "translate(" + ((x(histolol[index].x0) + x(histolol[index].x1)) / 2) + margin.left + ", " + (yl(element) - 15) + ")")
                        .attr("y", 0)
                        .attr("x", 0)
                        .attr("dy", "1em")
                        .style("text-anchor", "middle")
                        .style("font-size", "0.618em")
                        .text(attendeeNumberPerGroup[index] + "👱");
                }
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
                // console.log(index);
                // console.log(barSettings[index]);
            });
            svg.selectAll("rect")
                .data(barSettings)
                .enter()
                .append("rect")
                .attr("x", 1)
                .attr("transform", function (d) {
                    return "translate(" + x(d.leftBorder) + "," + yl(d.height) + ")";
                })
                .attr("width", function (d) {
                    return x(d.rightBorder) - x(d.leftBorder) - 1;
                })
                .attr("height", function (d) {
                    return (height - yl(d.height));
                })
                .style("z-index", "500")
                .style("fill", "#4444E5");
            addVerticalLineAndListenCursor(x, attendeeNumberPerGroup, attendeeSum);
955be6d0   Antoine Goutenoir   fix: infinite loop
353
            console.info("[Emissions Per Distance] Done.");
910464a0   Antoine Goutenoir   feat: switch to d...
354
355
        };

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
356
357
358
        d3.csv(csvUrl, on_csv_datum)
            .then(on_csv_ready);
    });
11ab5596   Adrenesis   Add emission-per-...
359

910464a0   Antoine Goutenoir   feat: switch to d...
360
}