Code flow - Jelmerovereem/frontend-data GitHub Wiki
First of all I need to fetch geoData for mapping the Netherlands.
const url = "https://cartomap.github.io/nl/wgs84/gemeente_2020.topojson"; // the geodata for the map
fetch(url)
.then(response => response.json())
.then((data) => {
var stadData = topojson.feature(data, data.objects.gemeente_2020); // Curran dataviz https://www.youtube.com/watch?v=Qw6uAg3EO64&list=PL9yYRbwpkykvOXrZumtZWbuaXWHvjD8gi&index=15
renderMap(stadData); // render the mapdata
});
After that I create the projection and pathGenerator:
const projection = d3.geoMercator()
.center([5.116667, 52.17]) // https://github.com/mbergevoet/frontend-data/blob/master/frontend-data/index.js#L61
.scale(6000)
.translate([width/2, height/2]); // center the map based on the width and height from the svg element
const pathGenerator = d3.geoPath().projection(projection);
Now I can render the map through my own function renderMap()
:
function renderMap(data) {
/* Create the base map */
group.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", pathGenerator)
/* Add the "gemeente" names */
group.selectAll("text") //https://stackoverflow.com/questions/13897534/add-names-of-the-states-to-a-map-in-d3-js
.data(data.features)
.enter()
.append("svg:text")
.text(obj => obj.properties.statnaam)
.attr("fill", "white")
.attr("x", (d) => {return pathGenerator.centroid(d)[0]})
.attr("y", (d) => {return pathGenerator.centroid(d)[1]})
.attr("text-anchor", "middle")
.attr("font-size", "1pt")
/* Add title tooltip */
group.selectAll("path")
.append("title")
.text(obj => obj.properties.statnaam)
}
Now the map is present!
Fetching parking data
Now I have to get the parking data from the RDW.
First I fetch the dataset with the areaId's and area descriptions(the same goes for the capacity dataset):
let garageData;
fetch('https://opendata.rdw.nl/resource/adw6-9hsg.json?$limit=8352&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
garageData = data;
});
//capacity data
let capacityData;
fetch('https://opendata.rdw.nl/resource/b3us-f26s.json?$limit=1567&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
capacityData = data;
})
Now it's getting interesting, I want to fetch the data for the parking locations, clean and convert this data.
Before cleaning: POINT (6.8635054 53.325557274)
After cleaning: long:6.8635054, lat: 53.325557274
I first fetch the dataset:
fetch('https://opendata.rdw.nl/resource/nsk3-v9n7.json?$limit=6101&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
//do something
});
After that I have to loop over the parking spots and clean this data, my classmate(Stan Brankras) has written functions for cleaning this data:
functions for cleaning data (Stan Brankras).
I myself have written code to clean up coordinates in the previous course. But the code from Stan was specifically written for this.
fetch('https://opendata.rdw.nl/resource/nsk3-v9n7.json?$limit=6101&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
data.forEach((garage) => {
let coordinateObj;
if (garage.areageometryastext != "" && garage.areageometryastext != undefined) {
if (!Number.isNaN(getCenterCoord(garage.areageometryastext)[0]) || !Number.isNaN(getCenterCoord(garage.areageometryastext)[1])) {
coordinateObj = { // create object with areaid and coordinates
areaId: garage.areaid,
long: getCenterCoord(garage.areageometryastext)[0],
lat: getCenterCoord(garage.areageometryastext)[1]
}
coordinatesArray.push(coordinateObj) // push every object into array
}
}
});
});
Now I have to combine the data from the 3 separate datasets:
function combineData(garageData, garageLocatieData, variableData) {
let outcomeData=[];
garageLocatieData.forEach((garage)=> {
var result=garageData.find(obj=> {
return obj.areaid===garage.areaId;
}
);
if (result===undefined) {
var garageObj= {
areaId: garage.areaId,
long: garage.long,
lat: garage.lat,
areaDesc: "onbekend"
}
}
else {
var garageObj= {
areaId: garage.areaId,
long: garage.long,
lat: garage.lat,
areaDesc: result.areadesc
}
}
outcomeData.push(garageObj);
}
);
//check option and edit data
outcomeData = checkOption(variableData, outcomeData);
renderPoints(outcomeData)
}
function checkOption(variableData, garagesData) {
if (variableData.length > 2000) {
//paid/free dropdown chosen
garagesData.forEach((garage) => {
var paidObj = variableData.find(obj => {
return obj.areaid === garage.areaId;
})
if (paidObj === undefined || paidObj.usageid === undefined) {
garage.paid = "onbekend";
} else {
garage.paid = paidObj.usageid;
}
})
} else {
//capacity dropdown chosen
garagesData.forEach((garage) => {
var capacityObj = variableData.find(obj => {
return obj.areaid === garage.areaId
})
if (capacityObj === undefined || capacityObj.capacity === undefined) {
garage.capacity = "onbekend";
} else {
garage.capacity = capacityObj.capacity;
}
})
}
return garagesData;
}
After I have combined the data I can show the parking spots on the map with renderPoints()
.
With .join() I can easily say what needs to happen when data gets updated through enter and exit:
function renderPoints(coordinates) {
let circle = group.selectAll("circle");
circle = circle
.data(coordinates, d => d.areaId)
.join(
enter => enter.append("circle")
.attr("r", 4)
.attr("transform", (obj) => {
return `translate(${projection([obj.long, obj.lat])})`
})
.attr("fill", (obj) => {
if (dropdown.value === "paid/free") {
if (/VERGUNNING|VERGUNP|VERGUN-ALG|VERGUN-MV/.test(obj.paid)) {
return "red"
} else if (/BETAALDP|GARAGEP/.test(obj.paid)) {
return "orange"
} else if (/onbekend/.test(obj.paid)) {
return "grey"
} else {
return "green"
}
} else if (dropdown.value === "capacity") {
if (obj.capacity <= 600) {
return "red"
} else if (obj.capacity > 600 && obj.capacity <= 1000) {
return "orange"
} else if (obj.capacity > 1000) {
return "green"
} else {
return "grey"
}
}
})
.attr("opacity", (d) => {
if (dropdown.value === "paid/free") {
if (/onbekend/.test(d.paid)) {
return .1
} else {
return 1
}
} else if (dropdown.value === "capacity") {
if (d.capacity === "onbekend") {
return .1
} else {
return 1
}
}
}),
exit => exit
.attr("fill", "white")
.call(exit => exit.transition().duration(500)
.attr("opacity", 0)
.remove())
)
}
After that I add a tooltip:
const tooltip = document.querySelector(".tooltip");
group.selectAll("circle").on("mouseover", (event, obj) => {
tooltip.innerHTML = obj.areaDesc + "<br>";
if (obj.paid === undefined) {
tooltip.innerHTML += "capacity: " + obj.capacity;
} else {
tooltip.innerHTML += "Paid/free: " + obj.paid;
}
tooltip.style.left = (event.pageX) + "px";
tooltip.style.top = (event.pageY + 10) + "px";
tooltip.classList.add("focus");
tooltip.style.opacity = "1";
})
.on("mouseout", () => {
tooltip.style.opacity = "0";
});
For the interactivity, I need to change the data on the change of a dropdown:
function updateData() {
const variable = this.value;
if (variable === "capacity") {
/* Set bar */
document.querySelector(".first").innerText = "0";
document.querySelector(".last").innerText = "10";
combineData(garageData, coordinatesArray, capacityData);
} else if (variable === "paid/free") {
document.querySelector(".first").innerText = "Betalen";
document.querySelector(".last").innerText = "Gratis";
fetch('https://opendata.rdw.nl/resource/adw6-9hsg.json?$limit=8352&$$app_token=zI34snM8XBhNRzxL50vrTeOLA') // fetch the paid/free data
.then(response => response.json())
.then((paidData) => {
combineData(garageData, coordinatesArray, paidData)
})
}
}
dropdown.addEventListener("change", updateData);