Skip to content

Commit

Permalink
3d calendar (#283)
Browse files Browse the repository at this point in the history
* wip cool 3d calender

* wip cool 3d calendar

* better labels

* looks good so far

* minor

* linting
  • Loading branch information
csae8092 authored Jan 2, 2025
1 parent 61b98b0 commit 631b69f
Show file tree
Hide file tree
Showing 11 changed files with 437 additions and 103 deletions.
70 changes: 70 additions & 0 deletions network/static/network/calendar-to-coords.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
function dateToCoordinates(isoDate, minYear = 1500, maxYear = 1800) {
// Parse the ISO date
const date = new Date(isoDate);
if (isNaN(date)) {
throw new Error("Invalid date format");
}

// Extract year, month, and day
const year = date.getUTCFullYear();
const month = date.getUTCMonth() + 1; // Months are 0-based
const day = date.getUTCDate();

// Map year to latitude (-90 to 90) with larger gaps between years
const yearFactor = (year - minYear) / (maxYear - minYear);
const latitude = yearFactor * 180 - 90;

// Map month and day to longitude (-180 to 180)
const maxMonth = 12;
const maxDay = 31; // Approximation for simplicity
const monthFactor = (month - 1) / (maxMonth - 1);
const dayFactor = (day - 1) / (maxDay - 1);
const longitude = ((monthFactor + dayFactor) / 2) * 360 - 180;

return { lat: latitude, lng: longitude };
}

function scaleToRange(
x,
originalMin = 1,
originalMax = 10,
targetMin = 0,
targetMax = 250
) {
return (
Math.round(
((x - originalMin) * (targetMax - targetMin)) /
(originalMax - originalMin)
) + targetMin
);
}

function mapValueToColor(value) {
if (value < 1 || value > 10) {
return [0, 0, 0];
}

// Normalize the value to a range of 0 to 1
const normalized = (value - 1) / 9;

// Define the gradient colors (red -> yellow -> green)
const startColor = [255, 0, 0]; // Red
const midColor = [255, 255, 0]; // Yellow
const endColor = [0, 255, 0]; // Green

let color;
if (normalized <= 0.5) {
// Interpolate between startColor and midColor
const t = normalized * 2; // Scale to [0, 1]
color = startColor.map((start, i) =>
Math.round(start + t * (midColor[i] - start))
);
} else {
// Interpolate between midColor and endColor
const t = (normalized - 0.5) * 2; // Scale to [0, 1]
color = midColor.map((mid, i) => Math.round(mid + t * (endColor[i] - mid)));
}

// Convert to RGB format
return [color[0], color[1], color[2]];
}
102 changes: 102 additions & 0 deletions network/static/network/calender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const url = document.getElementById("url").textContent;
console.log("fetching data");
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("Calender data response was not ok");
}
return response.json();
})
.then((data) => {
const legendDiv = document.getElementById("legend");
const dl = document.createElement("dl"); // Create the <dl> element

data.metadata.query_params.forEach((param) => {
for (const [key, value] of Object.entries(param)) {
const dt = document.createElement("dt"); // Create the <dt> element
dt.textContent = key;
const dd = document.createElement("dd"); // Create the <dd> element
dd.textContent = value;

dl.appendChild(dt);
dl.appendChild(dd);
}
});

// Ensure each event has a label property
const validEvents = data.events
.filter((event) => event.latitude && event.longitude)
.map((event) => ({
...event,
label: event.label || "Unknown Event", // Default to 'Unknown Event' if label is missing
}));

console.log(validEvents);
const deckgl = new deck.DeckGL({
container: "map",
initialViewState: {
altitude: 1.5,
height: 700,
longitude: 80,
latitude: 35,
zoom: 2,
pitch: 60,
// bearing: -1.7
},
controller: true,
// onViewStateChange: ({ viewState }) => {
// console.log("Current view state:", viewState);
// },
layers: [
new deck.HexagonLayer({
data: validEvents,
getPosition: (d) => [d.longitude, d.latitude],
radius: 50000,
elevationScale: 4000,
elevationRange: [0, 50],
extruded: true,
pickable: true,
onHover: ({ object, x, y }) => {
const tooltip = document.getElementById("tooltip");
if (object) {
const eventLabels = object.points.map((p) => p.source.label);
const curDate = object.points[0].source.date;
const listItems = eventLabels
.map((label) => `<li>${label}</li>`)
.join("");
tooltip.style.display = "block";
tooltip.style.left = `${x}px`;
tooltip.style.top = `${y}px`;
tooltip.innerHTML = `<strong>${curDate}</strong><ul>${listItems}</ul>`;
} else {
tooltip.style.display = "none";
}
},
}),
],
});

legendDiv.appendChild(dl);
})
.catch((error) => {
console.error("Something went wrong:", error);
});

// Add this CSS for the tooltip
const style = document.createElement("style");
style.innerHTML = `
#tooltip {
position: absolute;
background: white;
padding: 5px;
border: 1px solid black;
display: none;
pointer-events: none;
}
`;
document.head.appendChild(style);

// Add this HTML for the tooltip
const tooltip = document.createElement("div");
tooltip.id = "tooltip";
document.body.appendChild(tooltip);
105 changes: 105 additions & 0 deletions network/static/network/map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const url = document.getElementById("url").textContent;
console.log("fetching data");
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("Geojson response was not ok");
}
return response.json();
})
.then((data) => {
var map = L.map("map");
const legendDiv = document.getElementById("legend");
const dl = document.createElement("dl"); // Create the <dl> element

data.metadata.query_params.forEach((param) => {
for (const [key, value] of Object.entries(param)) {
const dt = document.createElement("dt"); // Create the <dt> element
dt.textContent = key;
const dd = document.createElement("dd"); // Create the <dd> element
dd.textContent = value;

dl.appendChild(dt); // Append <dt> to <dl>
dl.appendChild(dd); // Append <dd> to <dl>
}
});

legendDiv.appendChild(dl);
var OSMBaseLayer = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
maxZoom: 19,
attribution:
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}
).addTo(map);

var CartoDB_PositronNoLabels = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: "abcd",
maxZoom: 20,
}
);

var CartoDB_DarkMatterNoLabels = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: "abcd",
maxZoom: 20,
}
);

const markers = L.markerClusterGroup();
const geojsonLayer = L.geoJSON(data, {
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.label);
},
pointToLayer: function (feature, latlng) {
return L.marker(latlng);
},
});
markers.addTo(map);
geojsonLayer.eachLayer((layer) => markers.addLayer(layer));

var heatData = [];
L.geoJSON(data, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Point") {
var lat = feature.geometry.coordinates[1];
var lng = feature.geometry.coordinates[0];
heatData.push([lat, lng]);
}
},
});

// Create the heatmap layer
var heatmapLayer = L.heatLayer(heatData, {
radius: 25,
blur: 10,
maxZoom: 17,
max: 0.7,
gradient: { 0: "white", 0.5: "lime", 1: "red" },
});

var baseMaps = {
"Base Layer": OSMBaseLayer,
"CartoDB hell": CartoDB_PositronNoLabels,
"CartoDB dunkel": CartoDB_DarkMatterNoLabels,
};

const overlayMaps = {
"Marker Cluster": markers,
Heatmap: heatmapLayer,
};

L.control.layers(baseMaps, overlayMaps, { collapsed: false }).addTo(map);
map.fitBounds(geojsonLayer.getBounds());
})
.catch((error) => {
console.error("Something went wrong:", error);
});
26 changes: 26 additions & 0 deletions network/templates/network/calender.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Map{% endblock %}
{% block scriptHeader %}
{% endblock %}
{% block content %}
<style>
#map {
width: 100%;
height: 700px;
}
</style>
<script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
<div class="container-fluid pt-3">
<h1 class="display-3 text-center">Kalender</h1>
<div id="mapcontainer" class="p-4">
<div id="map"></div>
<div id="legend" class="text-center pt-3">
<h2>gewählte Filterparameter</h2>
</div>
</div>
</div>
<span id="url" class="visually-hidden" aria-hidden="true">{% url 'network:calender_data' %}{% querystring %}</span>

<script src="{% static 'network/calender.js' %}"></script>
{% endblock %}
1 change: 1 addition & 0 deletions network/templates/network/list_view.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ <h1 class="display-1 text-center">
<a type="button" class="btn btn-outline-primary" href="{% url 'network:data' %}{% querystring %}&format=csv">CSV</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:data' %}{% querystring %}&format=cosmograph">JSON</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:network' %}{% querystring %}&format=cosmograph">Als Netzwerk</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:calender' %}{% querystring %}">Als Kalender</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:geojson' %}{% querystring %}">GeoJson</a>
<a type="button" class="btn btn-outline-primary" href="{% url 'network:map' %}{% querystring %}">Karte</a>
</div>
Expand Down
Loading

0 comments on commit 631b69f

Please sign in to comment.