handleiding_nieuw:sub_kengetallenbeelden
Differences
This shows you the differences between two versions of the page.
| handleiding_nieuw:sub_kengetallenbeelden [2025/10/28 10:43] – support | handleiding_nieuw:sub_kengetallenbeelden [2026/02/12 15:36] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 66: | Line 66: | ||
| flex: 1; | flex: 1; | ||
| } | } | ||
| - | | + | |
| + | |||
| + | | ||
| background-color: | background-color: | ||
| border: 1px solid #ccc; | border: 1px solid #ccc; | ||
| Line 72: | Line 74: | ||
| margin: 2em 0; | margin: 2em 0; | ||
| } | } | ||
| + | |||
| + | /* 3-koloms lijst */ | ||
| .overzicht ul { | .overzicht ul { | ||
| - | padding-left: | ||
| list-style: none; | list-style: none; | ||
| + | padding-left: | ||
| + | margin: 0.6em 0 0; | ||
| + | display: grid; | ||
| + | grid-template-columns: | ||
| + | gap: 0.35em 1.4em; | ||
| } | } | ||
| + | |||
| .overzicht li { | .overzicht li { | ||
| - | | + | |
| - | | + | |
| - | gap: 10px; | + | |
| - | margin-bottom: 0.5em; | + | |
| } | } | ||
| - | | + | |
| - | | + | |
| - | | + | |
| - | border-radius: 4px; | + | |
| - | border: 1px solid #ccc; | + | |
| } | } | ||
| + | |||
| + | .overzicht a:hover { | ||
| + | text-decoration: | ||
| + | } | ||
| + | |||
| + | /* Responsive: 2 kolommen / 1 kolom */ | ||
| + | @media (max-width: 900px) { | ||
| + | .overzicht ul { grid-template-columns: | ||
| + | } | ||
| + | @media (max-width: 600px) { | ||
| + | .overzicht ul { grid-template-columns: | ||
| + | } | ||
| + | | ||
| + | | ||
| .projectblok { | .projectblok { | ||
| display: none; | display: none; | ||
| Line 194: | Line 214: | ||
| .project-dot: | .project-dot: | ||
| background-color: | background-color: | ||
| + | } | ||
| + | |||
| + | .project-dot.highlight { | ||
| + | transform: translate(-50%, | ||
| + | box-shadow: 0 0 0 2px rgba(0, | ||
| + | background-color: | ||
| + | z-index: 50; | ||
| + | } | ||
| + | |||
| + | /* COLLAGE | ||
| + | |||
| + | .collagebox{ | ||
| + | background:# | ||
| + | border:1px solid #ddd; | ||
| + | padding: | ||
| + | margin: 1.2em 0 1.8em; | ||
| + | } | ||
| + | .collage-controls{ | ||
| + | display: | ||
| + | align-items: | ||
| + | flex-wrap: | ||
| + | gap:8px; | ||
| + | margin-bottom: | ||
| + | } | ||
| + | .collage-grid{ | ||
| + | display: | ||
| + | grid-template-columns: | ||
| + | gap:10px; | ||
| + | } | ||
| + | .collage-card{ | ||
| + | border:1px solid #ddd; | ||
| + | border-radius: | ||
| + | overflow: | ||
| + | background: | ||
| + | cursor: | ||
| + | transition: transform 0.08s ease; | ||
| + | height: 210px; | ||
| + | display: flex; | ||
| + | flex-direction: | ||
| + | } | ||
| + | .collage-card: | ||
| + | .collage-card img{ | ||
| + | width:100%; | ||
| + | height: | ||
| + | object-fit: | ||
| + | flex-shrink: | ||
| + | display: | ||
| + | } | ||
| + | .collage-meta{ | ||
| + | padding:4px 6px; | ||
| + | font-size: | ||
| + | line-height: | ||
| + | height: | ||
| + | overflow: | ||
| + | } | ||
| + | .collage-meta .title{ | ||
| + | font-weight: | ||
| + | font-size: | ||
| + | margin-bottom: | ||
| + | display: | ||
| + | white-space: | ||
| + | overflow: hidden; | ||
| + | text-overflow: | ||
| + | } | ||
| + | .collage-meta .kpi{ | ||
| + | color:#444; | ||
| + | font-family: | ||
| + | } | ||
| + | |||
| + | @media (max-width: | ||
| + | @media (max-width: | ||
| + | @media (max-width: | ||
| + | |||
| + | /* Export header (alleen zichtbaar bij export) */ | ||
| + | # | ||
| + | background: #fff; /* export graag wit */ | ||
| + | padding: 0; /* header regelt padding */ | ||
| + | } | ||
| + | |||
| + | .collage-export-header{ | ||
| + | border: 1px solid #ddd; | ||
| + | border-radius: | ||
| + | padding: 10px 12px; | ||
| + | margin-bottom: | ||
| + | background: #ffffff; | ||
| + | font-family: | ||
| + | } | ||
| + | |||
| + | .collage-export-title{ | ||
| + | font-size: 14px; | ||
| + | font-weight: | ||
| + | margin: 0 0 4px 0; | ||
| + | } | ||
| + | |||
| + | .collage-export-sub{ | ||
| + | font-size: 11px; | ||
| + | color: #333; | ||
| + | margin: 0; | ||
| + | line-height: | ||
| + | } | ||
| + | |||
| + | .collage-export-sub .muted{ | ||
| + | color: #666; | ||
| + | } | ||
| + | |||
| + | button.generate-collage { | ||
| + | background-color: | ||
| + | color: #0b2a55 !important; | ||
| + | border: 1px solid #1f6fd6; | ||
| + | border-radius: | ||
| + | padding: 0.45em 1.2em; | ||
| + | font-weight: | ||
| + | cursor: pointer; | ||
| + | transition: background-color 0.15s ease, box-shadow 0.15s ease; | ||
| + | } | ||
| + | |||
| + | button.generate-collage: | ||
| + | background-color: | ||
| + | color: #0b2a55 !important; | ||
| + | box-shadow: 0 2px 6px rgba(0, | ||
| + | } | ||
| + | |||
| + | button.generate-collage: | ||
| + | background-color: | ||
| } | } | ||
| Line 201: | Line 345: | ||
| Beeldenbank Stedelijke Dichtheden | Beeldenbank Stedelijke Dichtheden | ||
| </h2> | </h2> | ||
| + | |||
| + | <!-- RESPONSIVE VOOR MOBIELE TELEFOONS --> | ||
| + | <div class=" | ||
| + | < | ||
| + | Deze pagina bevat interactieve grafieken en tabellen die zijn ontworpen voor gebruik op een groter scherm. | ||
| + | Voor een beter overzicht kun je je telefoon liggend houden of deze pagina openen op een tablet of laptop. | ||
| + | </ | ||
| + | |||
| <div class=" | <div class=" | ||
| <button onclick=" | <button onclick=" | ||
| Line 256: | Line 408: | ||
| </ | </ | ||
| </ | </ | ||
| + | |||
| <div class=" | <div class=" | ||
| Line 264: | Line 417: | ||
| + | <div class=" | ||
| + | <div class=" | ||
| + | < | ||
| + | <label style=" | ||
| + | Sorteer op: | ||
| + | <select id=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | </ | ||
| + | </ | ||
| - | <!--------------- PROJECTEN -----------------> | + | |
| + | Richting: | ||
| + | <select id=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | </ | ||
| + | </label> | ||
| + | <label style=" | ||
| + | Max: | ||
| + | <select id=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | </ | ||
| + | </ | ||
| + | <button type=" | ||
| + | Genereer collage | ||
| + | </ | ||
| + | |||
| + | <button type=" | ||
| + | Download PNG | ||
| + | </ | ||
| + | |||
| + | <button type=" | ||
| + | Download PDF (A4 liggend) | ||
| + | </ | ||
| + | |||
| + | <span id=" | ||
| + | |||
| + | </ | ||
| + | |||
| + | <div id=" | ||
| + | <div id=" | ||
| + | <div id=" | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | <h2 style=" | ||
| + | REFERENTIEPROJECTEN | ||
| + | </h2> | ||
| + | |||
| + | <!-- PROJECT_INSERT_START --> | ||
| < | < | ||
| Line 303: | Line 510: | ||
| </ | </ | ||
| < | < | ||
| - | |||
| - | |||
| Line 341: | Line 546: | ||
| </ | </ | ||
| < | < | ||
| - | |||
| - | |||
| Line 380: | Line 583: | ||
| </ | </ | ||
| < | < | ||
| - | |||
| Line 453: | Line 655: | ||
| </ | </ | ||
| < | < | ||
| - | |||
| Line 559: | Line 760: | ||
| </ | </ | ||
| < | < | ||
| + | |||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | De wijk is vormgegeven als een eigentijdse interpretatie van de Engelse tuinstad, met architectuur die refereert aan de Tudorstijl: puntgevels, metselwerk met ornamentiek en verticale geleding. Het stedenbouwkundig raster is organisch uitgewerkt met gebogen straten, kleinschalige woonhoven en visuele aslijnen naar het omliggende landschap. Door de lage dichtheid en de karakteristieke beeldtaal heeft Tudorpark een uitgesproken dorpse uitstraling binnen de Randstad.< | ||
| + | <br /> | ||
| + | De openbare ruimte is ruim bemeten en groen ingericht, met een sterke rol voor wadi’s, plantsoenen, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | Het gebied is ontwikkeld op de voormalige gemeentewerf van Heemstede, aan de voet van de karakteristieke watertoren. De stedenbouwkundige opzet bestaat uit middelhoge, gestapelde bouw rondom een mix van parkachtige verblijfsruimten, | ||
| + | <br /> | ||
| + | De architectuur is eigentijds, met variatie in metselwerk, balkonuitkragingen en grote gevelopeningen. Door de ligging aan de rand van het centrum en de nabijheid van voorzieningen, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | De buurt is opgebouwd in de typische jaren 30-verkavelingslogica met korte rijtjes eengezinswoningen, | ||
| + | <br /> | ||
| + | De stedenbouwkundige structuur bestaat uit een fijnmazig raster van woonstraten, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | De wijk is opgezet als een eigentijds landgoed en combineert duurzame woningbouw met een groen raamwerk dat visueel én functioneel de beleving van ruimte bepaalt. Centraal in de stedenbouw staat de relatie tussen natuur, collectief gebruik en individualiteit. Brede kavels, doorwaadbare padenstructuren en informele erfafscheidingen versterken dit beeld. De bebouwing volgt een organisch patroon met onderbroken rooilijnen, lage goothoogtes en natuurlijke materiaalkeuzes.< | ||
| + | <br /> | ||
| + | Oonderzoek naar stedenbouwkundige beleving laat zien dat wijken zoals Wickevoort — met lage GSI, hoge OSR en een gelaagde overgang van privaat naar publiek domein — consistent hoger scoren op waargenomen rust, comfort en ‘visual coherence’. Wickevoort laat zien dat dichtheid niet alleen getalsmatig, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | Kop van Zuid, Rotterdam | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | De OSR ligt op 0,17, wat wijst op een beperkte hoeveelheid publieke en semipublieke ruimte binnen het gebied. Het stedenbouwkundig patroon bestaat uit gesloten bouwblokken en hoogbouwaccenten, | ||
| + | <br /> | ||
| + | De analyse is gebaseerd op reproduceerbare uitgangspunten. Projectcontour, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | Stedenbouwkundig manifesteert Little C zich als een samenhangend ensemble van stevige bouwblokken, | ||
| + | <br /> | ||
| + | De kwantitatieve analyse van het gebied is gebaseerd op een handmatig vastgelegde projectbegrenzing, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | Duin Almere | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | Duin is een van de meest onconventionele gebiedsontwikkelingen in Nederland. De wijk is gebouwd op een kunstmatig duinlandschap dat letterlijk is opgeworpen bovenop een voormalige zandwinput aan het IJmeer. Door grootschalige zandophoging is een glooiend maaiveld ontstaan dat niet alleen de beleving van hoogteverschillen introduceert, | ||
| + | <br /> | ||
| + | De bebouwing varieert sterk in typologie en schaal — van geschakelde laagbouw tot woontorens aan het strand — maar is altijd ingepast in het duinprofiel. Openbare ruimte is opgezet als een sequentie van informele paden, zandige taluds, natuurlijke speelplekken en ruig groen. Duin is daarmee niet alleen een antwoord op het tekort aan woningbouwlocaties, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | Stedenbouwkundig wordt Cosunpark gekenmerkt door een losse verkavelingsstructuur waarin middelhoge woonvolumes zijn ingebed in een samenhangend groen raamwerk. In plaats van gesloten bouwblokken ontstaat een netwerk van open ruimten, paden en verblijfsplekken, | ||
| + | <br /> | ||
| + | De kwantitatieve analyse van Cosunpark is gebaseerd op een handmatig vastgelegde projectcontour in combinatie met gegevens uit OpenStreetMap. Aannames over bouwlagen en functies zijn expliciet vastgelegd en maken de berekeningen transparant en reproduceerbaar. Daarmee vormt het gebied niet alleen een voorbeeld van groen-stedelijk wonen, maar ook van de inzet van open databronnen als instrument voor stedenbouwkundige analyse.</ | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | |||
| + | < | ||
| + | <div class=" | ||
| + | <h2> | ||
| + | | ||
| + | <a href="#" | ||
| + | </h2> | ||
| + | <div class=" | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | <img src="/ | ||
| + | </ | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <br /> | ||
| + | Stedenbouwkundig ontvouwt Zuidkade zich als een samenhangend ensemble van middelhoge bouwblokken die de openbare ruimte scherp omlijsten. Langs straten en kades ontstaan stedelijke wanden, terwijl binnenhoven, | ||
| + | <br /> | ||
| + | De analyse van het gebied is gebaseerd op een handmatig vastgelegde projectcontour, | ||
| + | </ | ||
| + | <p style=" | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | |||
| - | < | ||
| + | <!-- PROJECT_INSERT_END --> | ||
| Line 572: | Line 1039: | ||
| </ | </ | ||
| + | <script src=" | ||
| + | <script src=" | ||
| < | < | ||
| Line 663: | Line 1132: | ||
| document.getElementById(' | document.getElementById(' | ||
| } | } | ||
| + | } | ||
| + | |||
| + | function getProjectTitle(blok) { | ||
| + | const h2 = blok.querySelector(' | ||
| + | if (!h2) return blok.id; | ||
| + | |||
| + | // h2 bevat ook de " | ||
| + | return (h2.textContent || '' | ||
| + | .replace(' | ||
| + | .replace(/ | ||
| + | .trim(); | ||
| } | } | ||
| Line 671: | Line 1151: | ||
| const gsiMax = parseFloat(document.getElementById(' | const gsiMax = parseFloat(document.getElementById(' | ||
| - | const projectBlokken = document.querySelectorAll(' | + | const projectBlokken = Array.from(document.querySelectorAll(' |
| const lijst = document.getElementById(' | const lijst = document.getElementById(' | ||
| lijst.innerHTML = ''; | lijst.innerHTML = ''; | ||
| const dotsLayer = document.getElementById(' | const dotsLayer = document.getElementById(' | ||
| - | dotsLayer.innerHTML = ''; | + | dotsLayer.innerHTML = ''; |
| + | // 1) bepaal zichtbaarheid + toon/ | ||
| + | const visible = []; | ||
| projectBlokken.forEach(blok => { | projectBlokken.forEach(blok => { | ||
| - | | + | |
| - | const gsi = parseFloat(blok.dataset.gsi); | + | const gsi = parseFloat(blok.dataset.gsi); |
| + | |||
| + | const zichtbaar = | ||
| + | fsi >= fsiMin && fsi <= fsiMax && | ||
| + | gsi >= gsiMin && gsi <= gsiMax; | ||
| + | |||
| + | blok.style.display = zichtbaar ? ' | ||
| + | if (zichtbaar) visible.push(blok); | ||
| + | }); | ||
| - | | + | |
| - | fsi >= fsiMin && fsi <= fsiMax && | + | |
| - | gsi >= gsiMin && gsi <= gsiMax; | + | |
| - | blok.style.display | + | |
| + | visible.forEach(blok => { | ||
| + | const fsi = parseFloat(blok.dataset.fsi); | ||
| + | const gsi = parseFloat(blok.dataset.gsi); | ||
| + | const title = getProjectTitle(blok); | ||
| - | if (zichtbaar) { | + | |
| - | | + | |
| const dot = document.createElement(' | const dot = document.createElement(' | ||
| dot.className = ' | dot.className = ' | ||
| + | dot.dataset.pid = blok.id; | ||
| const baseLeft = 60; | const baseLeft = 60; | ||
| - | const baseTop = 40; // gebruik jouw waarde! | + | const baseTop = 40; |
| const graphWidth = 680; | const graphWidth = 680; | ||
| const graphHeight = 400; | const graphHeight = 400; | ||
| Line 704: | Line 1196: | ||
| dot.style.left = x + ' | dot.style.left = x + ' | ||
| dot.style.top = y + ' | dot.style.top = y + ' | ||
| - | dot.title = blok.querySelector(' | + | dot.title = title; |
| dot.onclick = () => { | dot.onclick = () => { | ||
| Line 712: | Line 1204: | ||
| dotsLayer.appendChild(dot); | dotsLayer.appendChild(dot); | ||
| - | // ➤ Projectlink | + | // ➤ link in lijst (zonder thumbnail) |
| const li = document.createElement(' | const li = document.createElement(' | ||
| const link = document.createElement(' | const link = document.createElement(' | ||
| - | const img = blok.querySelector(' | ||
| link.href = '#' | link.href = '#' | ||
| - | link.textContent = blok.querySelector('h2').textContent; | + | link.textContent |
| - | if (img) { | + | link.dataset.pid |
| - | const mini = document.createElement('img'); | + | |
| - | mini.src = img.src; | + | link.addEventListener('mouseenter', () => { |
| - | li.appendChild(mini); | + | const d = dotsLayer.querySelector(`.project-dot[data-pid=" |
| - | } | + | if (d) d.classList.add('highlight'); |
| + | }); | ||
| + | |||
| + | link.addEventListener(' | ||
| + | const d = dotsLayer.querySelector(`.project-dot[data-pid=" | ||
| + | if (d) d.classList.remove(' | ||
| + | }); | ||
| + | |||
| li.appendChild(link); | li.appendChild(link); | ||
| lijst.appendChild(li); | lijst.appendChild(li); | ||
| - | } | ||
| }); | }); | ||
| } | } | ||
| Line 775: | Line 1273: | ||
| enableImageLightbox(); | enableImageLightbox(); | ||
| }); | }); | ||
| + | |||
| + | |||
| + | /* function getProjectTitle(blok) { | ||
| + | const h2 = blok.querySelector(' | ||
| + | if (!h2) return blok.id; | ||
| + | return (h2.textContent || '' | ||
| + | } */ | ||
| + | |||
| + | function getCoverImageSrc(blok) { | ||
| + | const imgs = blok.querySelectorAll(' | ||
| + | |||
| + | // voorkeur: 2e foto (index 1) | ||
| + | if (imgs.length >= 2) { | ||
| + | return imgs[1].getAttribute(' | ||
| + | } | ||
| + | |||
| + | // fallback: 1e foto als er maar één is | ||
| + | if (imgs.length === 1) { | ||
| + | return imgs[0].getAttribute(' | ||
| + | } | ||
| + | |||
| + | return null; | ||
| + | } | ||
| + | |||
| + | function renderCollage() { | ||
| + | const grid = document.getElementById(' | ||
| + | if (!grid) return; | ||
| + | |||
| + | const sortKey = document.getElementById(' | ||
| + | const dir = document.getElementById(' | ||
| + | const limit = parseInt(document.getElementById(' | ||
| + | |||
| + | const blocks = Array.from(document.querySelectorAll(' | ||
| + | .filter(b => b.style.display !== ' | ||
| + | |||
| + | const items = blocks.map(blok => { | ||
| + | const title = getProjectTitle(blok); | ||
| + | const img = getCoverImageSrc(blok); | ||
| + | const fsi = parseFloat(blok.dataset.fsi); | ||
| + | const gsi = parseFloat(blok.dataset.gsi); | ||
| + | const osr = parseFloat(blok.dataset.osr); | ||
| + | return { id: blok.id, title, img, fsi, gsi, osr }; | ||
| + | }).filter(x => x.img); | ||
| + | |||
| + | items.sort((a, | ||
| + | const av = a[sortKey], bv = b[sortKey]; | ||
| + | if (av === bv) return a.title.localeCompare(b.title, | ||
| + | return av - bv; | ||
| + | }); | ||
| + | if (dir === ' | ||
| + | |||
| + | const sliced = (limit >= 999) ? items : items.slice(0, | ||
| + | |||
| + | grid.innerHTML = ''; | ||
| + | sliced.forEach(p => { | ||
| + | const card = document.createElement(' | ||
| + | card.className = ' | ||
| + | card.onclick = () => location.href = '#' | ||
| + | |||
| + | const img = document.createElement(' | ||
| + | img.src = p.img; | ||
| + | img.loading = ' | ||
| + | img.alt = p.title; | ||
| + | |||
| + | const meta = document.createElement(' | ||
| + | meta.className = ' | ||
| + | meta.innerHTML = ` | ||
| + | <span class=" | ||
| + | <span class=" | ||
| + | `; | ||
| + | |||
| + | card.appendChild(img); | ||
| + | card.appendChild(meta); | ||
| + | grid.appendChild(card); | ||
| + | }); | ||
| + | |||
| + | grid.style.display = ' | ||
| + | updateExportHeader(); | ||
| + | } | ||
| + | |||
| + | | ||
| + | function fmt2(x) { | ||
| + | // nette afronding (zoals 0.6 of 0.62) | ||
| + | const n = Number(x); | ||
| + | if (Number.isNaN(n)) return ''; | ||
| + | return (Math.round(n * 100) / 100).toString(); | ||
| + | } | ||
| + | |||
| + | function currentDateNL() { | ||
| + | const d = new Date(); | ||
| + | const dd = String(d.getDate()).padStart(2,' | ||
| + | const mm = String(d.getMonth()+1).padStart(2,' | ||
| + | const yyyy = d.getFullYear(); | ||
| + | const hh = String(d.getHours()).padStart(2,' | ||
| + | const mi = String(d.getMinutes()).padStart(2,' | ||
| + | return `${dd}-${mm}-${yyyy} ${hh}: | ||
| + | } | ||
| + | |||
| + | function updateExportHeader() { | ||
| + | const header = document.getElementById(' | ||
| + | if (!header) return; | ||
| + | |||
| + | const sortKey = document.getElementById(' | ||
| + | const dir = document.getElementById(' | ||
| + | const limit = document.getElementById(' | ||
| + | |||
| + | const fsiMin = fmt2(document.getElementById(' | ||
| + | const fsiMax = fmt2(document.getElementById(' | ||
| + | const gsiMin = fmt2(document.getElementById(' | ||
| + | const gsiMax = fmt2(document.getElementById(' | ||
| + | |||
| + | const grid = document.getElementById(' | ||
| + | const nTiles = grid ? grid.children.length : 0; | ||
| + | |||
| + | const sortLabelMap = { fsi: ' | ||
| + | const sortLabel = sortLabelMap[sortKey] || sortKey; | ||
| + | const dirLabel = (dir === ' | ||
| + | |||
| + | header.innerHTML = ` | ||
| + | <div class=" | ||
| + | <p class=" | ||
| + | < | ||
| + | <span class=" | ||
| + | </p> | ||
| + | `; | ||
| + | } | ||
| + | |||
| + | function getCollageExportNode() { | ||
| + | const node = document.getElementById(' | ||
| + | if (!node) throw new Error(' | ||
| + | return node; | ||
| + | } | ||
| + | |||
| + | function makeFilenameBase() { | ||
| + | const sortKey = document.getElementById(' | ||
| + | const dir = document.getElementById(' | ||
| + | const limit = document.getElementById(' | ||
| + | const ts = new Date().toISOString().slice(0, | ||
| + | return `Sumsonite-collage_${sortKey}-${dir}_max${limit}_${ts}`; | ||
| + | } | ||
| + | |||
| + | async function renderNodeToCanvas(node) { | ||
| + | // header vullen + tijdelijk zichtbaar maken | ||
| + | updateExportHeader(); | ||
| + | const header = document.getElementById(' | ||
| + | const prevHeaderDisplay = header ? header.style.display : ''; | ||
| + | if (header) header.style.display = ' | ||
| + | |||
| + | // Zorg dat export area zichtbaar is (collageGrid zelf is al zichtbaar na renderCollage) | ||
| + | const canvas = await html2canvas(node, | ||
| + | backgroundColor: | ||
| + | scale: 2, | ||
| + | useCORS: true, | ||
| + | allowTaint: false, | ||
| + | logging: false | ||
| + | }); | ||
| + | |||
| + | // header weer verbergen | ||
| + | if (header) header.style.display = prevHeaderDisplay; | ||
| + | |||
| + | return canvas; | ||
| + | } | ||
| + | |||
| + | async function downloadCollagePNG() { | ||
| + | const grid = document.getElementById(' | ||
| + | if (!grid || grid.style.display === ' | ||
| + | alert(' | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | document.getElementById(' | ||
| + | 'Bezig met genereren… even geduld.'; | ||
| + | |||
| + | const node = getCollageExportNode(); | ||
| + | const canvas = await renderNodeToCanvas(node); | ||
| + | |||
| + | const a = document.createElement(' | ||
| + | a.href = canvas.toDataURL(' | ||
| + | a.download = makeFilenameBase() + ' | ||
| + | a.click(); | ||
| + | | ||
| + | document.getElementById(' | ||
| + | } | ||
| + | |||
| + | async function downloadCollagePDF() { | ||
| + | const grid = document.getElementById(' | ||
| + | if (!grid || grid.style.display === ' | ||
| + | alert(' | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | document.getElementById(' | ||
| + | 'Bezig met genereren… even geduld.'; | ||
| + | |||
| + | // 1) Zorg dat header tekst klopt + header even tonen (voor rendering) | ||
| + | updateExportHeader(); | ||
| + | const headerEl = document.getElementById(' | ||
| + | const prevHeaderDisplay = headerEl ? headerEl.style.display : ''; | ||
| + | if (headerEl) headerEl.style.display = ' | ||
| + | |||
| + | const exportNode = document.getElementById(' | ||
| + | |||
| + | // 2) Render volledige export area (header + grid) naar canvas | ||
| + | const fullCanvas = await html2canvas(exportNode, | ||
| + | backgroundColor: | ||
| + | scale: 2, | ||
| + | useCORS: true, | ||
| + | allowTaint: false, | ||
| + | logging: false | ||
| + | }); | ||
| + | |||
| + | // 3) Render header apart (voor herhalen per pagina) | ||
| + | const headerCanvas = await html2canvas(headerEl, | ||
| + | backgroundColor: | ||
| + | scale: 2, | ||
| + | useCORS: true, | ||
| + | allowTaint: false, | ||
| + | logging: false | ||
| + | }); | ||
| + | |||
| + | // header weer terug naar oude staat | ||
| + | if (headerEl) headerEl.style.display = prevHeaderDisplay; | ||
| + | |||
| + | const { jsPDF } = window.jspdf; | ||
| + | const pdf = new jsPDF({ orientation: | ||
| + | |||
| + | const pageWidth = pdf.internal.pageSize.getWidth(); | ||
| + | const pageHeight = pdf.internal.pageSize.getHeight(); | ||
| + | |||
| + | const margin = 24; | ||
| + | const usableWidth = pageWidth - 2 * margin; | ||
| + | const usableHeight = pageHeight - 2 * margin; | ||
| + | |||
| + | // Schaal op breedte (zelfde schaal voor alles) | ||
| + | const scale = usableWidth / fullCanvas.width; | ||
| + | |||
| + | // Header (PDF-hoogte) | ||
| + | const headerGapPt = 10; // ruimte tussen header en grid op PDF | ||
| + | const headerHeightPt = headerCanvas.height * scale; | ||
| + | |||
| + | // Beschikbare hoogte voor grid op PDF | ||
| + | const gridUsableHeightPt = usableHeight - headerHeightPt - headerGapPt; | ||
| + | if (gridUsableHeightPt <= 50) { | ||
| + | alert(' | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // 4) Bepaal " | ||
| + | // (Robuuster dan hard-coded 210/10) | ||
| + | const cardEl = document.querySelector(' | ||
| + | const gridEl = document.getElementById(' | ||
| + | const rowGapPx = parseFloat(getComputedStyle(gridEl).rowGap || ' | ||
| + | const cardHpx = cardEl ? cardEl.getBoundingClientRect().height : 210; | ||
| + | const rowHpx = Math.round((cardHpx + rowGapPx) * 2); | ||
| + | // *2 omdat html2canvas(scale: | ||
| + | |||
| + | // Startpositie van grid binnen de fullCanvas: | ||
| + | // We nemen headerCanvas hoogte + (margin-bottom van header in CSS) ~ 10px. | ||
| + | // In jouw CSS heeft header margin-bottom: | ||
| + | const headerBlockPx = headerCanvas.height + Math.round(10 * 2); | ||
| + | |||
| + | // Hoeveel rijen passen per pagina? | ||
| + | const gridPagePixelHeightRaw = gridUsableHeightPt / scale; // terug naar canvas-pixels | ||
| + | const rowsPerPage = Math.max(1, Math.floor(gridPagePixelHeightRaw / rowHpx)); | ||
| + | const sliceHeightPx = rowsPerPage * rowHpx; | ||
| + | |||
| + | // 5) Slicing: alleen het gridgedeelte (onder header), netjes op rijen | ||
| + | let yOffset = headerBlockPx; | ||
| + | const gridBottom = fullCanvas.height; | ||
| + | |||
| + | const sliceCanvas = document.createElement(' | ||
| + | const sliceCtx = sliceCanvas.getContext(' | ||
| + | sliceCanvas.width = fullCanvas.width; | ||
| + | |||
| + | let pageIndex = 0; | ||
| + | while (yOffset < gridBottom) { | ||
| + | if (pageIndex > 0) pdf.addPage(' | ||
| + | |||
| + | // 5a) Header op elke pagina | ||
| + | const headerImg = headerCanvas.toDataURL(' | ||
| + | pdf.addImage(headerImg, | ||
| + | |||
| + | // 5b) Grid-slice voor deze pagina | ||
| + | const remaining = gridBottom - yOffset; | ||
| + | |||
| + | // Normaal: volle pagina op rijen | ||
| + | let h = Math.min(sliceHeightPx, | ||
| + | |||
| + | // Als we bij het einde zijn en er resteert minder dan één rij: | ||
| + | // pak die rest alsnog (anders valt er content af). | ||
| + | if (remaining < rowHpx) { | ||
| + | h = remaining; | ||
| + | } | ||
| + | |||
| + | sliceCanvas.height = h; | ||
| + | sliceCtx.clearRect(0, | ||
| + | sliceCtx.drawImage(fullCanvas, | ||
| + | |||
| + | const sliceImg = sliceCanvas.toDataURL(' | ||
| + | const sliceHeightPt = h * scale; | ||
| + | |||
| + | pdf.addImage( | ||
| + | sliceImg, | ||
| + | ' | ||
| + | margin, | ||
| + | margin + headerHeightPt + headerGapPt, | ||
| + | usableWidth, | ||
| + | sliceHeightPt | ||
| + | ); | ||
| + | |||
| + | yOffset += h; | ||
| + | pageIndex++; | ||
| + | } | ||
| + | |||
| + | pdf.save(makeFilenameBase() + ' | ||
| + | | ||
| + | document.getElementById(' | ||
| + | } | ||
| + | | ||
| </ | </ | ||
| </ | </ | ||
handleiding_nieuw/sub_kengetallenbeelden.1761648206.txt.gz · Last modified: 2025/10/28 10:43 by support