Sverigekarta över skolor med spetsutbildningar
Klickbara markörer visar orter med skolor och deras spetsutbildningar.
#spets-karta-app{
width:200%;
max-width:none;
position:relative;
left:50%;
transform:translateX(-50%);
margin:0;
}
.spets-map-wrap{
position:relative;
width:100%;
}
#spetsSwedenMap{
display:block;
width:100%;
height:auto;
}
#spetsSwedenMap .sweden{
fill:#2f4f70;
stroke:#2f4f70;
stroke-width:2;
}
#spetsSwedenMap .marker{
cursor:pointer;
outline:none;
}
#spetsSwedenMap .marker circle{
fill:#ea5545;
stroke:#ffffff;
stroke-width:2.5;
transition:fill .18s ease,r .18s ease;
}
#spetsSwedenMap .marker:hover circle,
#spetsSwedenMap .marker:focus circle,
#spetsSwedenMap .marker.active circle{
fill:#eea996;
r:9;
}
#spetsSwedenMap .marker text{
fill:#ffffff;
font-family:inherit;
font-size:13px;
font-weight:600;
paint-order:stroke;
stroke:#2f4f70;
stroke-width:3.5px;
stroke-linejoin:round;
pointer-events:auto;
}
.spets-info-box{
position:absolute;
right:18px;
top:18px;
width:min(360px,calc(100% - 36px));
max-height:calc(100% - 36px);
overflow:auto;
background:#ffffff;
color:#2f4f70;
border-radius:12px;
padding:16px 17px;
box-shadow:0 8px 28px rgba(0,0,0,.16);
z-index:5;
}
.spets-info-box.is-hidden{display:none}
.spets-info-head{
display:flex;
justify-content:space-between;
gap:14px;
align-items:flex-start;
}
.spets-info-head h3{
margin:-7px 0 0;
color:#2f4f70;
font-size:20px;
line-height:1;
}
.spets-kicker{
margin:0;
font-size:9px;
line-height:1.2;
letter-spacing:.12em;
font-weight:700;
color:#ea5545;
}
.spets-close{
border:0;
background:transparent;
color:#2f4f70;
font-size:26px;
line-height:1;
cursor:pointer;
padding:0 3px;
}
.spets-school{
padding-top:15px;
margin-top:15px;
border-top:1px solid #d9dee4;
}
.spets-school:first-child{
padding-top:0;
margin-top:16px;
border-top:0;
}
.spets-school-name{
margin:0 0 9px;
color:#2f4f70;
font-size:12.5px;
line-height:1.3;
font-weight:700;
}
.spets-programs{
margin:6px 0 0;
padding:0;
list-style:none;
color:#2f4f70;
font-size:12.5px;
line-height:1.3;
}
.spets-programs li{
margin:0 0 7px;
}
.spets-category{
display:block;
color:#ea5545;
font-size:9px;
line-height:1.15;
font-weight:700;
text-transform:uppercase;
letter-spacing:.055em;
margin-bottom:1px;
}
.spets-program-link{
color:#2f4f70!important;
font-weight:600;
text-decoration:underline;
text-decoration-thickness:1px;
text-underline-offset:2px;
}
.spets-program-link:hover,
.spets-program-link:focus{
color:#ea5545!important;
}
.spets-school-link{
display:inline-block;
margin-top:2px;
color:#ea5545!important;
font-size:11.5px;
line-height:1.25;
font-weight:600;
text-decoration:none;
}
.spets-school-link:hover,
.spets-school-link:focus{
color:#eea996!important;
text-decoration:underline;
text-underline-offset:2px;
}
@media(max-width:767px){
#spets-karta-app{
width:100%;
left:auto;
transform:none;
}
.spets-info-box{
position:relative;
right:auto;
top:auto;
width:100%;
max-height:none;
margin-top:14px;
box-sizing:border-box;
box-shadow:none;
border:1px solid #d9dee4;
}
#spetsSwedenMap .marker text{
font-size:14px;
}
}
/* Slutlig mindre typografi i informationsrutorna */
#spets-karta-app .spets-school-name {
font-size:14px !important;
line-height:1.3 !important;
}
#spets-karta-app .spets-programs,
#spets-karta-app .spets-programs li,
#spets-karta-app .spets-program-link {
font-size:15px !important;
line-height:1.3 !important;
}
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7.9.0/+esm";
import {feature} from "https://cdn.jsdelivr.net/npm/topojson-client@3.1.0/+esm";
import world from "https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json/+esm";
const schools = [
{
city:"Botkyrka", name:"S:t Botvids gymnasium", lat:59.24, lon:17.83,
schoolUrl:"https://stbotvidsgymnasium.se/",
programs:[
{cat:"Estetiska", name:"Estetiska programmet – Nycirkus (ESC)", url:"https://cirkor.se/cirkusgymnasiet/"}
]
},
{
city:"Båstad", name:"Apelrydsskolan", lat:56.43, lon:12.85,
schoolUrl:"https://apelryd.se/",
programs:[
{cat:"Estetiska", name:"Etisk modedesign", url:"https://apelryd.se/etisk-modedesign/"}
]
},
{
city:"Borås", name:"Sven Eriksonsgymnasiet", lat:57.72, lon:12.94,
schoolUrl:"https://borasgymnasier.se/gymnasieskolor/sven-eriksonsgymnasiet",
programs:[
{cat:"Ekonomi", name:"Entreprenörsprogrammet", url:"https://borasgymnasier.se/program/entreprenorsprogrammet"},
{cat:"Teknikvetenskap", name:"Teknik – spets", url:"https://borasgymnasier.se/program/teknik-spets"}
]
},
{
city:"Danderyd", name:"Danderyds gymnasium", lat:59.41, lon:18.04,
schoolUrl:"https://www.danderyd.se/danderyds-gymnasium/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning i matematik", url:"https://www.danderyd.se/skolor/danderyds-gymnasium/program/spetsutbildning-i-matematik/"}
]
},
{
city:"Eskilstuna", name:"JENSEN gymnasium Eskilstuna", lat:59.37, lon:16.51,
schoolUrl:"https://www.jensengymnasium.se/eskilstuna",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning Matematik", url:"https://www.jensengymnasium.se/eskilstuna/program/naturvetenskap"},
{cat:"Samhällsvetenskap", name:"Spetsutbildning i Engelska – Samhällsvetenskapsprogrammet", url:"https://www.jensengymnasium.se/eskilstuna/program/samhallsvetenskap"},
{cat:"Ekonomi", name:"Spetsutbildning i Engelska – Ekonomiprogrammet", url:"https://www.jensengymnasium.se/eskilstuna/program/ekonomi"}
]
},
{
city:"Falun", name:"Falu Frigymnasium", lat:60.61, lon:15.63,
schoolUrl:"https://falufri.se/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Naturvetenskaplig spetsutbildning – Forskare utan gränser", url:"https://falufri.se/na-spets/"},
{cat:"Samhällsvetenskap", name:"Samhällsvetenskaplig spetsutbildning – Kriminologi och brottsprevention", url:"https://falufri.se/sa-spets/"}
]
},
{
city:"Göteborg", name:"Hvitfeldtska gymnasiet", lat:57.70, lon:11.97,
schoolUrl:"https://goteborg.se/wps/portal/enheter/hvitfeldtska-gymnasiet",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Naturvetenskapsprogrammet med matematikspets", url:"https://goteborg.se/wps/portal/enheter/hvitfeldtska-gymnasiet/vara-program/naturvetenskapsprogrammet/matematik-spets"},
{cat:"Samhällsvetenskap", name:"Samhällsvetenskaplig spets – Jan Eliassons internationella profil", url:"https://goteborg.se/wps/portal/enheter/hvitfeldtska-gymnasiet/vara-program/samhallsvetenskapsprogrammet/europainriktning-spets-jeip"},
{cat:"Estetiska", name:"Estetiska programmet med musikspets", url:"https://goteborg.se/wps/portal/enheter/hvitfeldtska-gymnasiet/vara-program/estetiska-programmet/estetiska-programmet-musik-spets"}
]
},
{
city:"Göteborg", name:"The International High School", lat:57.70, lon:11.97,
schoolUrl:"https://goteborg.se/wps/portal/enheter/the-international-high-school",
programs:[
{cat:"Samhällsvetenskap", name:"The Social Science Programme with Advanced Courses in English (PACE)", url:"https://goteborg.se/wps/portal/enheter/the-international-high-school/vara-program/sa-global-studies-programme-enriched--pace-"}
]
},
{
city:"Göteborg", name:"GTG, Göteborgsregionens tekniska gymnasium", lat:57.70, lon:11.97,
schoolUrl:"https://www.gtg.se/",
programs:[
{cat:"Teknikvetenskap", name:"Teknikspets mot elektronik och programmering", url:"https://www.gtg.se/gymnasieprogram/teknikprogrammet-spets-programmering"}
]
},
{
city:"Göteborg", name:"Schillerska gymnasiet", lat:57.70, lon:11.97,
schoolUrl:"https://goteborg.se/wps/portal/enheter/schillerska-gymnasiet",
programs:[
{cat:"Estetiska", name:"Spetsutbildning i bild och form", url:"https://goteborg.se/wps/portal/enheter/schillerska-gymnasiet/vara-program/estetiska-programmet/bild-och-form-spets"}
]
},
{
city:"Göteborg", name:"Rytmus Musikgymnasium Göteborg", lat:57.70, lon:11.97,
schoolUrl:"https://rytmus.se/goteborg/",
programs:[
{cat:"Estetiska", name:"Estetisk spetsutbildning – Musik", url:"https://rytmus.se/"}
]
},
{
city:"Halmstad", name:"Sturegymnasiet", lat:56.67, lon:12.86,
schoolUrl:"https://www.halmstad.se/barnochutbildning/gymnasieskola/gymnasieskolorihalmstad/sturegymnasiet",
programs:[
{cat:"Estetiska", name:"Estetiska programmet – Dans", url:"https://www.halmstad.se/barnochutbildning/gymnasieskola/gymnasieskolorihalmstad/sturegymnasiet/utbildningar/estetiskaprogrammetdansspetsutbildningmedriksintag.11635.html"},
{cat:"Estetiska", name:"Estetiska programmet – Film", url:"https://www.halmstad.se/barnochutbildning/gymnasieskola/gymnasieskolorihalmstad/sturegymnasiet/utbildningar/estetiskaprogrammetfilmspetsutbildningmedriksintag.11636.html"},
{cat:"Estetiska", name:"Estetiska programmet – Musik", url:"https://www.halmstad.se/barnochutbildning/gymnasieskola/gymnasieskolorihalmstad/sturegymnasiet/utbildningar/estetiskaprogrammetmusikspetsutbildningmedriksintag.11879.html"},
{cat:"Estetiska", name:"Estetiska programmet – Teater", url:"https://www.halmstad.se/barnochutbildning/gymnasieskola/gymnasieskolorihalmstad/sturegymnasiet/utbildningar/estetiskaprogrammetteaterspetsutbildningmedriksintag.11637.html"}
]
},
{
city:"Helsingborg", name:"NTI Gymnasiet Helsingborg", lat:56.05, lon:12.69,
schoolUrl:"https://ntigymnasiet.se/helsingborg/",
programs:[
{cat:"Teknikvetenskap", name:"Spetsutbildning: Innovativ teknisk design", url:"https://ntigymnasiet.se/helsingborg/program/spets-innovativ-teknisk-design/"}
]
},
{
city:"Härnösand", name:"Härnösands gymnasieskola", lat:62.63, lon:17.94,
schoolUrl:"https://harnosand.se/barn--utbildning/gymnasieskola.html",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning i biomedicin", url:"https://harnosand.se/barn--utbildning/gymnasieskola/program-pa-gymnasiet/spetsutbildning-i-biomedicin.html"}
]
},
{
city:"Jönköping", name:"Per Brahegymnasiet", lat:57.78, lon:14.16,
schoolUrl:"https://www.perbrahe.se/",
programs:[
{cat:"Estetiska", name:"Estetiska programmet – Konst och design", url:"https://www.perbrahe.se/program/estetiska-programmet.html"},
{cat:"Estetiska", name:"Estetiska programmet – Digital design", url:"https://www.perbrahe.se/program/estetiska-programmet.html"},
{cat:"Estetiska", name:"Estetiska programmet – Scenkonst teater", url:"https://www.perbrahe.se/program/estetiska-programmet.html"},
{cat:"Estetiska", name:"Estetiska programmet – Musiker", url:"https://www.perbrahe.se/program/estetiska-programmet.html"}
]
},
{
city:"Karlskrona", name:"Ehrensvärdska gymnasiet", lat:56.16, lon:15.59,
schoolUrl:"https://www.karlskrona.se/skola-och-forskola/gymnasieskola-och-gymnasiesarskola/ehrensvardska-gymnasiet/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning i matematik – Leonardo", url:"https://www.karlskrona.se/skola-och-forskola/gymnasieskola-och-gymnasiesarskola/gymnasieprogram/naturvetenskapsprogrammet-inriktning-naturvetenskap-spetsutbildning-i-matematik-leonardo---nanatos/"}
]
},
{
city:"Linköping", name:"Berzeliusskolans gymnasium", lat:58.41, lon:15.62,
schoolUrl:"https://www.linkoping.se/forskola-och-utbildning/gymnasieskola/alla-gymnasieskolor-i-linkoping/berzeliusskolans-gymnasium/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Matematisk spetsutbildning", url:"https://www.linkoping.se/forskola-och-utbildning/gymnasieskola/alla-gymnasieskolor-i-linkoping/berzeliusskolans-gymnasium/program-och-inriktningar/naturvetenskapsprogrammet-pa-berzeliusskolan"}
]
},
{
city:"Luleå", name:"Luleå gymnasieskola", lat:65.58, lon:22.15,
schoolUrl:"https://www.lulea.se/utbildning--forskola/gymnasieskola/lulea-gymnasieskola.html",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Matematik spets", url:"https://www.lulea.se/utbildning--forskola/gymnasieskola/lulea-gymnasieskola/gymnasieprogram/naturvetenskapsprogrammet.html"}
]
},
{
city:"Lund", name:"Polhemskolan", lat:55.70, lon:13.19,
schoolUrl:"https://lund.se/gymnasiewebbar/polhemskolan",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Fysikprogrammet", url:"https://lund.se/gymnasiewebbar/polhemskolan/utbildningar/naturvetenskapsprogrammet-fysik-spets"},
{cat:"Naturvetenskap/Matematik", name:"Matematikprogrammet", url:"https://lund.se/gymnasiewebbar/polhemskolan/utbildningar/naturvetenskapsprogrammet-matematik-spets"}
]
},
{
city:"Lund", name:"Katedralskolan", lat:55.70, lon:13.19,
schoolUrl:"https://lund.se/gymnasiewebbar/katedralskolan",
programs:[
{cat:"Samhällsvetenskap", name:"Samhällsvetenskapsprogrammet med spets i historia", url:"https://lund.se/gymnasiewebbar/katedralskolan/utbildningar/spetsutbildning-i-historia"}
]
},
{
city:"Lund", name:"Lunds dans- och musikalgymnasium", lat:55.70, lon:13.19,
schoolUrl:"https://www.dansomusikal.se/",
programs:[
{cat:"Estetiska", name:"Estetisk spetsutbildning dans", url:"https://www.dansomusikal.se/estetisk-spetsutbildning-dans"},
{cat:"Estetiska", name:"Estetisk spetsutbildning musikal", url:"https://www.dansomusikal.se/estetisk-spetsutbildning-musikal"}
]
},
{
city:"Lund", name:"Spyken", lat:55.70, lon:13.19,
schoolUrl:"https://lund.se/gymnasiewebbar/spyken",
programs:[
{cat:"Estetiska", name:"Spetsutbildning i musik", url:"https://lund.se/gymnasiewebbar/spyken/utbildningar/riksrekryterande-spetsutbildning-i-musik-es-spets"}
]
},
{
city:"Lysekil", name:"Gullmarsgymnasiet", lat:58.27, lon:11.44,
schoolUrl:"https://www.lysekil.se/forskola-skola-och-utbildning/gymnasium/gullmarsgymnasiet",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning marinbiologi", url:"https://www.lysekil.se/forskola-skola-och-utbildning/gymnasium/gullmarsgymnasiet/marina-utbildningar/naturvetenskapsprogrammet-marinbiologisk-spetsutbildning"}
]
},
{
city:"Malmö", name:"Malmö latinskola", lat:55.60, lon:13.00,
schoolUrl:"https://malmo.se/Ditt-gymnasieval/Malmo-latinskola.html",
programs:[
{cat:"Estetiska", name:"Spetsutbildning Musik", url:"https://malmo.se/Ditt-gymnasieval/Malmo-latinskola/Spetsutbildning---musik.html"},
{cat:"Estetiska", name:"Spetsutbildning Teater", url:"https://malmo.se/Ditt-gymnasieval/Malmo-latinskola/Spetsutbildning---teater.html"}
]
},
{
city:"Malmö", name:"Rytmus Musikgymnasium Malmö", lat:55.60, lon:13.00,
schoolUrl:"https://rytmus.se/malmo/",
programs:[
{cat:"Estetiska", name:"Estetisk spetsutbildning – Musik", url:"https://rytmus.se/"}
]
},
{
city:"Nacka", name:"Nacka gymnasium", lat:59.31, lon:18.16,
schoolUrl:"https://www.nacka.se/valfard-skola/nacka-gymnasium/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning matematik", url:"https://www.nacka.se/valfard-skola/nacka-gymnasium/vara-utbildningar/naturvetenskapsprogrammet/matematik/"}
]
},
{
city:"Norrköping", name:"De Geergymnasiet", lat:58.59, lon:16.19,
schoolUrl:"https://degeer.norrkoping.se/de-geergymnasiet",
programs:[
{cat:"Estetiska", name:"Musik spets – Musikestetiska programmet", url:"https://degeer.norrkoping.se/de-geergymnasiet/utbildningar/estetiska-programmet/musik"}
]
},
{
city:"Stockholm", name:"NTI Gymnasiet Stockholm Odenplan", lat:59.33, lon:18.07,
schoolUrl:"https://ntigymnasiet.se/stockholm-odenplan/",
programs:[
{cat:"Teknikvetenskap", name:"Spetsutbildning: Spelutveckling", url:"https://ntigymnasiet.se/stockholm-odenplan/program/spets-spelutveckling/"}
]
},
{
city:"Stockholm", name:"Södra Latins gymnasium", lat:59.33, lon:18.07,
schoolUrl:"https://sodralatinsgymnasium.stockholm/",
programs:[
{cat:"Estetiska", name:"Inriktning musik – spets", url:"https://www.skolverket.se/styrning-och-ansvar/anordna-utbildning/anordna-gymnasieutbildning/anordna-riksrekryterande-gymnasieutbildning/riksrekryterande-spetsutbildning-i-gymnasieskolan"}
]
},
{
city:"Stockholm", name:"Rytmus Musikgymnasium Stockholm", lat:59.33, lon:18.07,
schoolUrl:"https://rytmus.se/stockholm/",
programs:[
{cat:"Estetiska", name:"Estetisk spetsutbildning – Musik", url:"https://rytmus.se/"}
]
},
{
city:"Strängnäs", name:"Europaskolan Strängnäs", lat:59.38, lon:17.03,
schoolUrl:"https://www.europaskolan.se/strangnas/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Scienceprogrammet – spetsutbildning i naturvetenskap", url:"https://www.europaskolan.se/strangnas/gymnasieprogram/scienceprogrammet-spetsutbildning/"},
{cat:"Samhällsvetenskap", name:"Modern Humanities – spetsutbildning i samhällskunskap och historia", url:"https://www.europaskolan.se/strangnas/gymnasieprogram/modern-humanities-spetsutbildning/"}
]
},
{
city:"Tumba", name:"Tumba gymnasium", lat:59.20, lon:17.83,
schoolUrl:"https://www.tumbagymnasium.se/",
programs:[
{cat:"Ekonomi", name:"Aktier och finans spets – spetsutbildning i finansekonomi", url:"https://www.tumbagymnasium.se/program/finansekonomi/"},
{cat:"Estetiska", name:"Film och tv", url:"https://www.tumbagymnasium.se/program/estetiska-spetsprogram/estetik-media/film-tv-ljudproduktion/"},
{cat:"Estetiska", name:"Mediedesign och fotografi", url:"https://www.tumbagymnasium.se/program/estetiska-spetsprogram/"},
{cat:"Estetiska", name:"Bild och form – animation", url:"https://www.tumbagymnasium.se/program/estetiska-spetsprogram/bild-animation/"}
]
},
{
city:"Uppsala", name:"Rosendalsgymnasiet", lat:59.86, lon:17.64,
schoolUrl:"https://rosendalsgymnasiet.uppsala.se/",
programs:[
{cat:"Naturvetenskap/Matematik", name:"Spetsutbildning i matematik", url:"https://rosendalsgymnasiet.uppsala.se/vara-utbildningar/spetsmatte/"}
]
},
{
city:"Uppsala", name:"NTI Gymnasiet Uppsala", lat:59.86, lon:17.64,
schoolUrl:"https://ntigymnasiet.se/uppsala/",
programs:[
{cat:"Teknikvetenskap", name:"Spetsutbildning: Matematik och AI", url:"https://ntigymnasiet.se/uppsala/program/spets-matematik/"}
]
},
{
city:"Vänersborg", name:"Birger Sjöberggymnasiet", lat:58.38, lon:12.32,
schoolUrl:"https://kunskapsforbundet.se/birger-sjoberggymnasiet/",
programs:[
{cat:"Estetiska", name:"Estetiska programmet – Spetsutbildning MUV", url:"https://kunskapsforbundet.se/birger-sjoberggymnasiet/program/estetiska-programmet-spetsutbildning-muv/"}
]
},
{
city:"Västerås", name:"Hitachigymnasiet", lat:59.61, lon:16.55,
schoolUrl:"https://vasteras.hitachigymnasiet.se/",
programs:[
{cat:"Teknikvetenskap", name:"Teknikspets mot smarta system och produkter", url:"https://vasteras.hitachigymnasiet.se/utbildning-teknikprogrammet/teknikspets/"}
]
},
{
city:"Västerås", name:"Carlforsska gymnasiet", lat:59.61, lon:16.55,
schoolUrl:"https://skola.vasteras.se/gymnasieskolor/carlforsska",
programs:[
{cat:"Ekonomi", name:"Ekonomiprogrammet mot internationellt företagande", url:"https://skola.vasteras.se/gymnasieskolor/carlforsska/vara-program.html"}
]
},
{
city:"Växjö", name:"Teknikum", lat:56.88, lon:14.81,
schoolUrl:"https://teknikum.se/",
programs:[
{cat:"Teknikvetenskap", name:"Spetsutbildning inom teknikvetenskap", url:"https://teknikum.se/sidor/forskola-och-skola/gymnasieskola/teknikum/program-pa-teknikum/spetsutbildning-inom-teknikvetenskap.html"}
]
},
{
city:"Växjö", name:"Katedralskolan", lat:56.88, lon:14.81,
schoolUrl:"https://gymnasieval.vaxjo.se/vara-gymnasieskolor/katedralskolan",
programs:[
{cat:"Estetiska", name:"Spetsutbildning i musik", url:"https://gymnasieval.vaxjo.se/vara-gymnasieskolor/katedralskolan/spetsutbildning-i-musik"}
]
}
];
const svg = d3.select("#spetsSwedenMap");
const box = document.getElementById("spetsInfoBox");
const cityTitle = document.getElementById("spetsInfoCity");
const holder = document.getElementById("spetsInfoSchools");
const countries = feature(world, world.objects.countries);
const sweden = countries.features.find(d => String(d.id) === "752");
if (sweden) {
// Mindre marginaler än i förra versionen gör själva Sverigeformen ca 20–25 % större.
const projection = d3.geoMercator().fitExtent([[18,12],[632,838]], sweden);
const path = d3.geoPath(projection);
svg.append("path")
.datum(sweden)
.attr("class","sweden")
.attr("d",path);
const cities = d3.groups(schools,d=>d.city).map(([city,items])=>({
city, items, lat:items[0].lat, lon:items[0].lon
}));
// Etiketterna i Stockholm/Mälardalen är medvetet utspridda för läsbarhet.
// De behöver inte ligga exakt intill sin geografiska punkt – tydlighet prioriteras.
const labelOffsets = {
/* Stockholm/Mälardalen – extra luft mellan orterna */
"Botkyrka":[-46,39],
"Tumba":[28,32],
"Stockholm":[27,-15],
"Nacka":[30,14],
"Uppsala":[34,-35],
"Västerås":[-37,-29],
"Strängnäs":[-38,20],
"Eskilstuna":[-42,-28],
"Danderyd":[29,-18],
/* Västkusten och Skåne */
"Lysekil":[-36,-8],
"Göteborg":[-37,13],
"Helsingborg":[18,34],
"Båstad":[-18,20],
"Lund":[-18,-14],
"Malmö":[18,17],
"Borås":[17,18],
"Halmstad":[-20,20],
"Växjö":[17,17]
};
const markers = svg.append("g")
.selectAll("g")
.data(cities)
.join("g")
.attr("class","marker")
.attr("role","button")
.attr("tabindex",0)
.attr("aria-label",d=>`${d.city}: ${d.items.length} ${d.items.length===1?"skola":"skolor"}`)
.attr("transform",d=>{
const p = projection([d.lon,d.lat]);
return `translate(${p[0]},${p[1]})`;
});
const leaderCities = new Set([
"Botkyrka","Tumba","Stockholm","Nacka","Uppsala",
"Västerås","Strängnäs","Eskilstuna","Danderyd",
"Lysekil","Göteborg","Helsingborg"
]);
const leaderLines = markers.filter(d=>leaderCities.has(d.city))
.append("line")
.attr("class","spets-label-leader")
.attr("x1",0)
.attr("y1",0)
.attr("x2",d=>{
const o=labelOffsets[d.city]||[12,5];
return o[0] {
const o=labelOffsets[d.city]||[12,5];
return o[1] > 0 ? o[1]-5 : o[1]+5;
})
.attr("stroke","#ffffff")
.attr("stroke-width",1)
.attr("stroke-opacity",.78)
.attr("pointer-events","none");
markers.append("circle").attr("r",7);
const cityLabels = markers.append("text")
.attr("x",d=>(labelOffsets[d.city]||[12,5])[0])
.attr("y",d=>(labelOffsets[d.city]||[12,5])[1])
.attr("text-anchor",d=>(labelOffsets[d.city]||[12,5])[0] d.city);
function resolveLabelCollisions(){
const nodes = cityLabels.nodes();
// Större säkerhetsmarginal än tidigare.
for(let pass=0; pass<12; pass++){
let moved = false;
for(let i=0; i<nodes.length; i++){
const a = nodes[i];
for(let j=i+1; j<nodes.length; j++){
const b = nodes[j];
const ra = a.getBoundingClientRect();
const rb = b.getBoundingClientRect();
const overlapX = ra.left
rb.left;
const overlapY = ra.top rb.top;
if(overlapX && overlapY){
const ax = parseFloat(a.getAttribute("x") || 0);
const ay = parseFloat(a.getAttribute("y") || 0);
const bx = parseFloat(b.getAttribute("x") || 0);
const by = parseFloat(b.getAttribute("y") || 0);
const aLeft = (a.getAttribute("text-anchor") === "end");
const bLeft = (b.getAttribute("text-anchor") === "end");
// Separera först vertikalt och därefter lite från kartpunkten.
a.setAttribute("y", ay - 5);
b.setAttribute("y", by + 5);
a.setAttribute("x", ax + (aLeft ? -2 : 2));
b.setAttribute("x", bx + (bLeft ? -2 : 2));
moved = true;
}
}
}
if(!moved) break;
}
// Låt ledlinjerna följa etikettens slutliga placering.
leaderLines
.attr("x2",function(d){
const label = this.parentNode.querySelector("text");
if(!label) return 0;
const x = parseFloat(label.getAttribute("x") || 0);
return x 0 ? y - 5 : y + 5;
});
}
// Kör när SVG-texten är renderad.
requestAnimationFrame(()=>requestAnimationFrame(resolveLabelCollisions));
function openCity(d){
markers.classed("active",m=>m.city===d.city);
cityTitle.textContent = d.city;
holder.innerHTML = "";
d.items.forEach(s=>{
const sec = document.createElement("section");
sec.className = "spets-school";
const h = document.createElement("h4");
h.className = "spets-school-name";
h.textContent = s.name;
sec.appendChild(h);
const ul = document.createElement("ul");
ul.className = "spets-programs";
s.programs.forEach(p=>{
const li = document.createElement("li");
const c = document.createElement("span");
c.className = "spets-category";
c.textContent = p.cat;
li.appendChild(c);
const a = document.createElement("a");
a.href = p.url;
a.target = "_blank";
a.rel = "noopener noreferrer";
a.className = "spets-program-link";
a.textContent = p.name + " →";
li.appendChild(a);
ul.appendChild(li);
});
sec.appendChild(ul);
const schoolLink = document.createElement("a");
schoolLink.href = s.schoolUrl;
schoolLink.target = "_blank";
schoolLink.rel = "noopener noreferrer";
schoolLink.className = "spets-school-link";
schoolLink.textContent = "Besök skolans webbplats →";
sec.appendChild(schoolLink);
holder.appendChild(sec);
});
box.classList.remove("is-hidden");
}
markers.on("click",(e,d)=>openCity(d));
markers.on("keydown",(e,d)=>{
if(e.key==="Enter" || e.key===" "){
e.preventDefault();
openCity(d);
}
});
cityLabels.on("click",(e,d)=>{
e.stopPropagation();
openCity(d);
});
cityLabels.on("keydown",(e,d)=>{
if(e.key==="Enter" || e.key===" "){
e.preventDefault();
e.stopPropagation();
openCity(d);
}
});
document.getElementById("spetsCloseInfo").addEventListener("click",()=>{
box.classList.add("is-hidden");
markers.classed("active",false);
});
}