-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from Datavisualiatie-UGent/develop
Develop
- Loading branch information
Showing
18 changed files
with
1,452 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -263,3 +263,4 @@ dist | |
# Eigen toevoegingen | ||
|
||
*csv | ||
/fietstellingen/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import * as Plot from "npm:@observablehq/plot"; | ||
|
||
function calculateLabel(timeframe) { | ||
let hour = timeframe.getUTCHours().toString().padStart(2, "0") | ||
let quarter = ["00", "15", "30", "45"][Math.floor(timeframe.getUTCMinutes() / 15)] | ||
return `${hour}:${quarter}` | ||
} | ||
|
||
export function doubleBarHorizontal(data, {width}) { | ||
return Plot.plot({ | ||
width: width, | ||
y: {grid: true}, | ||
x: { | ||
label: null, | ||
tickFormat: (x) => { | ||
let minutes = new Date(x).getUTCMinutes() | ||
if(minutes === 0){ | ||
return `${new Date(x).getUTCHours().toString().padStart(2, "0")}:00` | ||
} | ||
return "" | ||
}, | ||
type:"band" | ||
}, | ||
marginRight: 20, | ||
marginBottom: 20, | ||
marginLeft: 50, | ||
color: { | ||
scheme: "PiYg", | ||
type: "ordinal" | ||
}, | ||
marks: [ | ||
Plot.axisY({anchor: "left", label: "aantal fietsers"}), | ||
Plot.rectY( | ||
data, | ||
{y: "in", x: "timeframe", fill: (d) => Math.sign(d.in)}, | ||
), | ||
Plot.rectY( | ||
data, | ||
{y: "out", x: "timeframe", fill: (d) => Math.sign(d.out)} | ||
), | ||
Plot.ruleY([0]), | ||
Plot.tip(data, Plot.pointer({ | ||
y: "out", | ||
x: "timeframe", | ||
title: (d) => [`Hour: ${calculateLabel(new Date(d.timeframe))}`, `arrived: ${d.in}`, `departed: ${d.out}`].join("\n\n"), | ||
fill: (d) => Math.sign(d.in) | ||
})), | ||
Plot.tip(data, Plot.pointer({ | ||
y: "in", | ||
x: "timeframe", | ||
title: (d) => [`Hour: ${calculateLabel(new Date(d.timeframe))}`, `arrived: ${d.in}`, `departed: ${d.out}`].join("\n\n"), | ||
fill: (d) => Math.sign(d.in) | ||
})), | ||
] | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import * as Plot from "npm:@observablehq/plot"; | ||
import * as d3 from "npm:d3"; | ||
|
||
/** | ||
* This function calculates the month when adding an index to a start date. | ||
* @param startDate | ||
* @param index | ||
* @param withYear | ||
* @returns {string} | ||
*/ | ||
function getMonth(startDate, index, withYear = true) { | ||
const monthNames = Array.from({length: 12}, (_, i) => new Date(0, i + 1, 0).toLocaleString('default', {month: 'short'})); | ||
|
||
let newDate = new Date(startDate); | ||
|
||
newDate.setMonth(newDate.getMonth() + index); | ||
if (!withYear) { | ||
return monthNames[newDate.getMonth()]; | ||
} | ||
return monthNames[newDate.getMonth()] + ' ' + newDate.getFullYear(); | ||
} | ||
|
||
/** | ||
* This function plots the normalized data. | ||
* @param normalizedSiteCumulativeCountsGemeente object containing per gemeente the normalized the cumulative counts normalized by month 0 | ||
* @param startDate the start date of the data | ||
* @param gemeentenActiveSince | ||
* @param gemeenteActiveSince the date when the gemeente started | ||
* @param totalMothsCount the total number of months | ||
* @param width the width of the plot | ||
* @returns {*} | ||
*/ | ||
export function plotNormalizedData(normalizedSiteCumulativeCountsGemeente, startDate, gemeentenActiveSince, totalMothsCount, {width} = {}, gemeenteActiveSince = undefined) { | ||
|
||
const lines = Array.from(Object.entries(normalizedSiteCumulativeCountsGemeente)).map(([gemeente, counts], i) => { | ||
let data; | ||
if (gemeenteActiveSince === undefined) { | ||
data = counts.map((value, timeslot) => ({ | ||
timeslot, | ||
value, | ||
gemeente | ||
})).filter((value, timeslot) => timeslot >= gemeentenActiveSince[gemeente]) | ||
} else { | ||
data = counts.map((value, timeslot) => ({ | ||
timeslot, | ||
value, | ||
gemeente | ||
})).filter((value, timeslot) => timeslot >= gemeenteActiveSince) | ||
} | ||
|
||
return Plot.lineY(data, { | ||
x: "timeslot", | ||
y: "value", | ||
stroke: "gemeente", | ||
}); | ||
}); | ||
|
||
const minY = d3.min(lines, line => d3.min(line.data, d => d.value)); | ||
const maxY = d3.max(lines, line => d3.max(line.data, d => d.value)); | ||
|
||
const minX = d3.min(lines, line => d3.min(line.data, d => d.timeslot)); | ||
const maxX = d3.max(lines, line => d3.max(line.data, d => d.timeslot)); | ||
|
||
const stepSize = 0.1; // Pas dit aan aan uw behoeften | ||
const ticks = Math.ceil((maxY - minY) / stepSize); | ||
|
||
return Plot.plot({ | ||
width: width, | ||
color: {legend: true}, | ||
y: { | ||
percent: true, | ||
grid: true, | ||
ticks: ticks, | ||
label: "Percentage (%)", | ||
}, | ||
x: { | ||
ticks: Math.ceil(maxX) - Math.floor(minX), | ||
label: "Maanden", | ||
tickFormat: (d => { | ||
if (gemeenteActiveSince === undefined) return getMonth(startDate, d); | ||
else return getMonth(startDate, d, false); | ||
}), | ||
grid: true, | ||
}, | ||
marks: [ | ||
...lines, | ||
], | ||
title: "Gemiddelde Cumulatieve Procentuele Verandering in Tellingen per Gemeente vanaf Maand Eén" | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* this function gets the trend compare data. | ||
* @param cumulatieveCounts | ||
* @param year | ||
* @param firstTrend | ||
* @param secondTrend | ||
* @returns {{filteredObj: {[p: string]: *}, totalMothsCount: (*|number), gemeenteActiveSince: (*|{[p: string]: any}), startDate: *}} | ||
*/ | ||
export function getTrendCompareData(cumulatieveCounts, year, firstTrend, secondTrend) { | ||
const startDate = cumulatieveCounts.resultJSON[year].startDate; | ||
const gemeenteActiveSince = cumulatieveCounts.resultJSON[year].gemeenteActiveSince; | ||
const totalMothsCount = cumulatieveCounts.resultJSON[year].totalMothsCount; | ||
const compare = cumulatieveCounts.resultJSON[year].normalizedSiteCumulativeCountsGemeente | ||
|
||
const filteredObj = Object.fromEntries( | ||
Object.entries(compare).filter(([key, value]) => (key === firstTrend || key === secondTrend)) | ||
); | ||
|
||
return { | ||
filteredObj, | ||
startDate, | ||
gemeenteActiveSince, | ||
totalMothsCount | ||
} | ||
} | ||
|
||
|
||
/** | ||
* This function gets the first and second trend years. | ||
* @param cumulatieveCounts | ||
* @param year | ||
* @param firstTrend | ||
* @param secondTrend | ||
* @returns {{secondTrendActiveSince: *, firstTrendsYears: {}, firstTrendActiveSince: *, secondTrendsYears: {}}} | ||
*/ | ||
export function getFistAndSecondTrendYears(cumulatieveCounts, year, firstTrend, secondTrend) { | ||
const firstTrendsYears = {} | ||
const secondTrendsYears = {} | ||
const indexYear = Object.keys(cumulatieveCounts.resultJSON).indexOf(year) | ||
for (let i = indexYear; i < Object.keys(cumulatieveCounts.resultJSON).length; i++) { | ||
firstTrendsYears[firstTrend + " " + Object.keys(cumulatieveCounts.resultJSON)[i]] = cumulatieveCounts.resultJSON[Object.keys(cumulatieveCounts.resultJSON)[i]].normalizedSiteCumulativeCountsGemeente[firstTrend] | ||
secondTrendsYears[secondTrend + " " + Object.keys(cumulatieveCounts.resultJSON)[i]] = cumulatieveCounts.resultJSON[Object.keys(cumulatieveCounts.resultJSON)[i]].normalizedSiteCumulativeCountsGemeente[secondTrend] | ||
} | ||
const firstTrendActiveSince = cumulatieveCounts.resultJSON[year].gemeenteActiveSince[firstTrend] | ||
const secondTrendActiveSince = cumulatieveCounts.resultJSON[year].gemeenteActiveSince[secondTrend] | ||
|
||
return { | ||
firstTrendsYears, | ||
secondTrendsYears, | ||
firstTrendActiveSince, | ||
secondTrendActiveSince | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import L from 'npm:leaflet'; | ||
|
||
|
||
/** | ||
* Extracts the installation date from a site | ||
* @param d the site | ||
* @returns {string} the installation date | ||
*/ | ||
function extractInstallationDate(d) { | ||
const date = new Date(d.datum_van); | ||
return 'Datum eerste telling: ' + String(date.getDate()).padStart(2, '0') + '/' + String((date.getMonth() + 1)).padStart(2, '0') + '/' + date.getFullYear(); | ||
} | ||
|
||
/** | ||
* Creates a popup for a site | ||
* @param d the site | ||
* @returns {string} the popup HTML String | ||
*/ | ||
function createPopUp(d) { | ||
return 'Naam site: ' + d.naam + '<br>' + | ||
'Naam gemeente: ' + d.gemeente + '<br>' + | ||
'Interval tellingen: ' + d.interval + "minuten" + '<br>' + | ||
extractInstallationDate(d) | ||
} | ||
|
||
/** | ||
* Returns a smaller bounds | ||
* @param bounds the bounds to make smaller | ||
* @param shrinkAmount the amount to shrink the bounds | ||
* @returns the smaller bounds | ||
*/ | ||
function makeSmallerBounds(bounds, shrinkAmount) { | ||
const northEast = bounds.getNorthEast(); | ||
const southWest = bounds.getSouthWest(); | ||
|
||
const smallerNorthEast = L.latLng(northEast.lat - shrinkAmount, northEast.lng - shrinkAmount); | ||
const smallerSouthWest = L.latLng(southWest.lat + shrinkAmount, southWest.lng + shrinkAmount); | ||
|
||
return L.latLngBounds(smallerSouthWest, smallerNorthEast); | ||
} | ||
|
||
|
||
/** | ||
* Limits a point to a bounds | ||
* @param point the point to limit | ||
* @param bounds the bounds to limit to | ||
* @returns the point limited to the bounds | ||
*/ | ||
function limitToBounds(point, bounds) { | ||
const lat = Math.max(Math.min(point.lat, bounds.getNorth()), bounds.getSouth()); | ||
const lng = Math.max(Math.min(point.lng, bounds.getEast()), bounds.getWest()); | ||
return L.latLng(lat, lng); | ||
} | ||
|
||
|
||
/** | ||
* Checks if the map is within the bounds and moves it back if it is not | ||
* @param map the map to check | ||
* @param bounds the bounds to check | ||
*/ | ||
function checkBounds(map, bounds) { | ||
let newCenter = map.getCenter(); | ||
if (!bounds.contains(newCenter)) { | ||
newCenter = limitToBounds(newCenter, makeSmallerBounds(bounds, 0.5)); | ||
map.panTo(newCenter, {animate: true, duration: 1}); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Creates a map with the given sites | ||
* @param sites the sites to create the map with | ||
*/ | ||
export function createMap(sites) { | ||
// create map | ||
const northEast = L.latLng(51.6, 6.05), | ||
southWest = L.latLng(50.4, 2.5), | ||
bounds = L.latLngBounds(southWest, northEast); | ||
|
||
const centerLat = (southWest.lat + northEast.lat) / 2; | ||
const centerLng = (southWest.lng + northEast.lng) / 2; | ||
|
||
|
||
const map = L.map('map', { | ||
center: [centerLat, centerLng], | ||
bounds: bounds, | ||
maxBoundsViscosity: 0.9, | ||
zoomControl: false, | ||
}).setView([centerLat, centerLng], 9); | ||
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png', { | ||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>', | ||
subdomains: 'abcd', | ||
minZoom: 9 | ||
}).addTo(map); | ||
|
||
const markers = []; | ||
|
||
// add markers | ||
sites.forEach((d) => { | ||
const marker = L.marker([d.lat, d.long]).addTo(map) | ||
.bindPopup(createPopUp(d)) | ||
.bindTooltip(d.naam); | ||
markers.push(marker); | ||
}); | ||
|
||
|
||
// check bounds | ||
map.on('moveend', () => { | ||
checkBounds(map, bounds); | ||
}); | ||
} |
Oops, something went wrong.