Blame view

flaskr/static/js/plots/emissions-per-distance.js 11.6 KB
8daea46b   Antoine Goutenoir   fix: interactive ...
1
function draw_emissions_per_distance(divSelector, csvUrl) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
2
    // set the dimensions and margins of the graph
8daea46b   Antoine Goutenoir   fix: interactive ...
3
    let margin = {top: 42, right: 88, bottom: 62, 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
11
12
13
    function getTicks(maxValue, interval, startValue = 0) {
        let range = [];
        for (let i = startValue; i <= maxValue; i += interval) {
            range.push(i);
910464a0   Antoine Goutenoir   feat: switch to d...
14
        }
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
15
16
        return range;
    }
910464a0   Antoine Goutenoir   feat: switch to d...
17

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
18
19
20
21
22
    function getBottomTicks(maxDistance) {
        let range = getTicks(maxDistance, 2500, 2500);
        range.push(500);
        return range;
    }
910464a0   Antoine Goutenoir   feat: switch to d...
23

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
24
25
26
    function getLeftTicks(maxemissions) {
        return getTicks(maxemissions, Math.floor((maxemissions / 8) / 1000) * 1000);
    }
910464a0   Antoine Goutenoir   feat: switch to d...
27

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
28
29
30
31
    function getRightTicks(maxemissionsPercent) {
        return getTicks(maxemissionsPercent, 2);
    }

8daea46b   Antoine Goutenoir   fix: interactive ...
32
33
    function getAttendeesAmountOnRight(sliceId, attendeeNumberPerGroup) {
        let attendeesAmount = 0;
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
34
35
36
        let sliceInt = Math.floor(sliceId);
        let sliceFloat = sliceId - sliceInt;
        for (let i = sliceInt; i < attendeeNumberPerGroup.length; i++) {
8daea46b   Antoine Goutenoir   fix: interactive ...
37
            attendeesAmount += attendeeNumberPerGroup[i] * (1 - sliceFloat);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
38
            sliceFloat = 0;
910464a0   Antoine Goutenoir   feat: switch to d...
39
        }
8daea46b   Antoine Goutenoir   fix: interactive ...
40
        return attendeesAmount;
910464a0   Antoine Goutenoir   feat: switch to d...
41

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
42
    }
910464a0   Antoine Goutenoir   feat: switch to d...
43

8daea46b   Antoine Goutenoir   fix: interactive ...
44
    function setupCursorBoxes(event, attendeeSum, attendeeNumberPerGroup, xScale, tooltip, vertical, rightArea) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
45
46
47
48
        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
        // x += 5;
8daea46b   Antoine Goutenoir   fix: interactive ...
49
50
51

        // console.log("Mouse move", x, y);

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
52
53
        vertical.style("left", x + "px");
        rightArea.style("left", x + "px");
8daea46b   Antoine Goutenoir   fix: interactive ...
54
55
56
57
58
59
60
61
62
        tooltip.style("left", (x + 10) + "px");
        tooltip.style("top", (y - 20) + "px");

        // let sliceId = xScale.invert(x - d3.selectAll("g.yl.axis")._groups[0][0].getBoundingClientRect().right) / 500;

        let sliceId = xScale.invert(x - margin.left) / sliceThickness;

        console.log(`Slice ${sliceId} under the mouse.`);

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
63
        // 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 ...
64
65
66
67
68
69
        let attendeePercent = (getAttendeesAmountOnRight(sliceId, attendeeNumberPerGroup) / attendeeSum * 100.0).toFixed(1);
        if (
            x < margin.left
            ||
            x > width + margin.left
        ) {
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
70
71
            rightArea.style("width", 0);
            vertical.style("width", 0);
8daea46b   Antoine Goutenoir   fix: interactive ...
72
            tooltip.style("display", "none");
910464a0   Antoine Goutenoir   feat: switch to d...
73
        }
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
74
75
76
        else {
            rightArea.style("width", d3.selectAll("g.yr.axis")._groups[0][0].getBoundingClientRect().x - x);
            vertical.style("width", "2px");
8daea46b   Antoine Goutenoir   fix: interactive ...
77
            tooltip.style("display", "inherit");
910464a0   Antoine Goutenoir   feat: switch to d...
78
        }
8daea46b   Antoine Goutenoir   fix: interactive ...
79
        tooltip.text(attendeePercent + " % of attendees");
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
80
81
82
        // console.log(d3.selectAll("g.x.axis")._groups[0][0]);
        // console.log(d3.selectAll("g.x.axis")._groups[0][0].getBoundingClientRect().x);
    }
910464a0   Antoine Goutenoir   feat: switch to d...
83

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
84
    function addVerticalLineAndListenCursor(xScale, attendeeNumberPerGroup, attendeeSum) {
8daea46b   Antoine Goutenoir   fix: interactive ...
85
        let verticalRuler = d3.select(divSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
86
            .append("div")
8daea46b   Antoine Goutenoir   fix: interactive ...
87
            // .attr("class", "remove")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
88
89
90
91
92
93
            .style("position", "absolute")
            .style("z-index", "19")
            .style("width", "2px")
            .style("height", (height) + "px")
            .style("top", (10 + margin.top) + "px")
            .style("bottom", "30px")
8daea46b   Antoine Goutenoir   fix: interactive ...
94
            .style("left", "-10px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
95
            .style("background", "#000");
910464a0   Antoine Goutenoir   feat: switch to d...
96

8daea46b   Antoine Goutenoir   fix: interactive ...
97
        let rightArea = d3.select(divSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
98
            .append("div")
8daea46b   Antoine Goutenoir   fix: interactive ...
99
            // .attr("class", "remove")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
100
101
            .style("position", "absolute")
            .style("z-index", "-50")
8daea46b   Antoine Goutenoir   fix: interactive ...
102
103
            .style("width", "0px")
            // .style("width", "2000px")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
104
105
106
107
108
            .style("height", (height) + "px")
            .style("top", (10 + margin.top) + "px")
            .style("bottom", "30px")
            .style("left", "0px")
            .style("background", "rgba(60, 200, 60, 0.3)");
910464a0   Antoine Goutenoir   feat: switch to d...
109

8daea46b   Antoine Goutenoir   fix: interactive ...
110
        let tooltip = d3.select(divSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
111
            .append("div")
8daea46b   Antoine Goutenoir   fix: interactive ...
112
113
            .attr("class", "no-pointer-events")
            .style("display", "none")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
114
115
116
117
118
119
120
121
122
            .style("position", "absolute")
            .style("z-index", "20")
            .style("width", "150px")
            .style("height", "22px")
            .style("top", "10px")
            .style("bottom", "30px")
            .style("left", "0px")
            .style("border", "1px solid grey")
            .style("background", "rgba(255, 255, 255, 0.7)");
561e9392   Antoine Goutenoir   chore: lint
123

8daea46b   Antoine Goutenoir   fix: interactive ...
124
        d3.select(divSelector + " svg")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
125
            .on("mousemove", function (event) {
8daea46b   Antoine Goutenoir   fix: interactive ...
126
                setupCursorBoxes(event, attendeeSum, attendeeNumberPerGroup, xScale, tooltip, verticalRuler, rightArea);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
127
128
            })
            .on("mouseover", function (event) {
8daea46b   Antoine Goutenoir   fix: interactive ...
129
                setupCursorBoxes(event, attendeeSum, attendeeNumberPerGroup, xScale, tooltip, verticalRuler, rightArea);
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
130
131
            });
    }
910464a0   Antoine Goutenoir   feat: switch to d...
132

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
133
134
135
136
    document.addEventListener("DOMContentLoaded", () => {
        let maxemissions = 0;
        let maxemissionsPercent = 0;
        let maxDistance = 0;
8daea46b   Antoine Goutenoir   fix: interactive ...
137
        let svg = d3.select(divSelector)
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
138
            .append("svg")
8daea46b   Antoine Goutenoir   fix: interactive ...
139
            // .attr("id", divSelector + "-svg")
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
140
141
142
143
144
            .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...
145

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
146
147
148
149
150
        let emissionsPerGroup = [];
        let attendeeNumberPerGroup = [];
        let emissionsSum = 0;
        let attendeeSum = 0;
        let rows = [];
910464a0   Antoine Goutenoir   feat: switch to d...
151

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
        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);
            if (co2_kg === "NaN" || distance_km / 500 > 37 || distance_km === "NaN") {
                return;
            }
            rows.push(datum);
            maxDistance = Math.max(maxDistance, distance_km);
            emissionsSum += co2_kg;
910464a0   Antoine Goutenoir   feat: switch to d...
167

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

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
        const on_csv_ready = function () {
            for (let i = 0; i <= maxDistance / 500; i++) {
                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);
                emissionsPerGroup[Math.floor(distance_km / 500)] += parseFloat(co2_kg);
                attendeeNumberPerGroup[Math.floor(distance_km / 500)] += attendeeNumber;
                attendeeSum += attendeeNumber;
            });
            emissionsPerGroup.forEach((element, index) => {
                maxemissions = Math.max(maxemissions, element);
                maxemissionsPercent = Math.max(maxemissionsPercent, element / emissionsSum * 100.0)
            });
            maxDistance += 2000;
            // console.log(maxDistance);
910464a0   Antoine Goutenoir   feat: switch to d...
191

8daea46b   Antoine Goutenoir   fix: interactive ...
192
            // Title
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
193
194
195
196
197
198
199
            svg.append("text")
                .attr("transform",
                    "translate(" + (70 + margin.left) + ", -12)")
                .style("text-anchor", "middle")
                .style("font-weight", "bold")
                .style("font-size", "130%")
                .text("Emissions per distance");
910464a0   Antoine Goutenoir   feat: switch to d...
200

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
            // X axis: scale and draw:
            let x = d3.scaleLinear()
                .domain([0, maxDistance])
                .range([0, width]);
            let xAxis = d3.axisBottom(x)
                .tickValues(getBottomTicks(maxDistance));
            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...
217

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
218
219
220
221
222
223
224
225
226
            // Y axis Left
            let yl = d3.scaleLinear()
                .domain([0, maxemissions])
                .range([height, 0]);
            let ylAxis = d3.axisLeft(yl)
                .tickValues(getLeftTicks(maxemissions));
            svg.append("g")
                .attr("class", "yl axis")
                .call(ylAxis);
910464a0   Antoine Goutenoir   feat: switch to d...
227

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
228
229
230
231
232
233
234
235
236
237
            // Y axis Right
            let yr = d3.scaleLinear()
                .domain([0, maxemissionsPercent])
                .range([height, 0]);
            let yrAxis = d3.axisRight(yr)
                .tickValues(getRightTicks(maxemissionsPercent));
            svg.append("g")
                .attr("transform", "translate(" + width + ", 0)")
                .attr("class", "yr axis")
                .call(yrAxis);
910464a0   Antoine Goutenoir   feat: switch to d...
238

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
239
240
241
242
243
244
            svg.append("text")
                .attr("transform",
                    "translate(" + (width / 2) + " ," +
                    (height + margin.top + 12) + ")")
                .style("text-anchor", "middle")
                .text("Distance travelled (km)");
910464a0   Antoine Goutenoir   feat: switch to d...
245

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
246
247
248
249
250
251
252
            svg.append("text")
                .attr("transform",
                    "translate(" + (width) + ", 0), rotate(-90)")
                .attr("x", 0 - (height / 2))
                .attr("y", 42)
                .style("text-anchor", "middle")
                .text("Share of emission [%]");
910464a0   Antoine Goutenoir   feat: switch to d...
253

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
254
255
256
257
258
259
260
261
            svg.append("text")
                .attr("transform", "rotate(-90)")
                .attr("y", 0 - margin.left)
                .attr("x", 0 - (height / 2))
                .attr("dy", "1em")
                .style("text-anchor", "middle")
                .text("Emission (tCO2e)");
            // set the parameters for the histogram
8daea46b   Antoine Goutenoir   fix: interactive ...
262
            const histogram = d3.histogram()
ad528ad0   Antoine Goutenoir   fix: use DOMConte...
263
264
                .domain(x.domain())  // then the domain of the graphic
                .thresholds(x.ticks(Math.floor(maxDistance / 500))); // then the numbers of bins
910464a0   Antoine Goutenoir   feat: switch to d...
265

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
            let histolol = histogram(0);
            // console.log(histolol);
            let barSettings = [];
            emissionsPerGroup.forEach((element, index) => {
                barSettings[index] =
                    {
                        height: element,
                        leftBorder: histolol[index].x0,
                        rightBorder: histolol[index].x1,
                    };
                // 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);
910464a0   Antoine Goutenoir   feat: switch to d...
296
297
        };

ad528ad0   Antoine Goutenoir   fix: use DOMConte...
298
299
300
        d3.csv(csvUrl, on_csv_datum)
            .then(on_csv_ready);
    });
11ab5596   Adrenesis   Add emission-per-...
301

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