<script src="https://cdn.lordicon.com/lordicon-1.1.0.js"></script>
<div class="site u-z-index-foreground" id="site">
<div class="site__header" id="siteHeader">
<div class="u-position-relative">
<header class="o-header">
<div class="wrapper">
<div class="row justify-content-between align-items-center flex-nowrap">
<div class="grid-auto">
<a href="/" class="o-header__logo-link">
<span class="o-header__logo">
<img class="logotype" src="https://static.internetstiftelsen.se/images/logotypes/internetkollen.svg" alt="logotyp">
</span>
<span class="u-visuallyhidden">Till startsidan</span>
</a>
</div>
<div class="grid u-hide-sm">
<h1 id="portal-title" data-default="Internetkollen" class="u-b-0 u-b-l-1 u-b-solid u-b-concrete display-flex u-p-y-1 u-p-x-2 u-m-y-1 u-font-size-medium u-weight-normal">Test för moderna internetstandarder</h1>
</div>
<div class="grid">
<nav class="a-main-menu" aria-label="Huvudmeny">
<ul class="a-main-menu__list">
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Testa själv</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link is-current">
<span class="a-main-menu__list__text">Hälsoläget</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Om</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Hjälp</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Kontakt</span>
</a>
</li>
<li class="u-hide-xl-up">
<button type="button" class="a-main-menu__list__link" aria-expanded="false" data-a11y-toggle="dropdown">
<span class="a-main-menu__list__text">Meny</span>
<div class="icon-arrow-down"></div>
</button>
<ul id="dropdown" class="a-main-menu__subnav a-main-menu__subnav--right-aligned u-box-shadow-card">
<li class="a-main-menu__subnav__item">
<a href="#">Testa själv</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Hälsoläget</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Om</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Hjälp</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Kontakt</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</div>
</header>
</div>
</div>
<div class="site__main" id="siteMain">
<div class="u-p-2 background-snow display-flex justify-content-end align-items-center u-b-solid u-b-b-1 u-b-x-0 u-b-t-0 u-b-concrete">
<label for="history" class="u-m-r-1">Historiska tester</label>
<select name="history" id="history" class="a-select">
<option value="0" disabled selected>Välj datum</option>
<option value="1">2025-10-01 (latest)</option>
<option value="1">2025-09-01</option>
<option value="1">2025-08-01</option>
</select>
</div>
<div class="wrapper u-p-t-2 u-p-b-8">
<div class="row">
<main class="grid-18">
<section class="row justify-content-center">
<div class="grid-18 grid-md-12 u-p-t-4 u-m-b-2">
<h1 class="supersize u-align-center">
Hälsoläget - 1200 samhällskritiska domäner
</h1>
<p class="preamble u-align-center">
Sveriges Hälsoläget övervakar webb- och e-postsäkerhetskonfigurationen för Sveriges främsta internettjänster. Tjänsten drivs av <a href="">Internetstiftelsen.</a>
</p>
</div>
</section>
<section>
<div class="row justify-content-center u-p-b-2">
<div class="grid-18 u-m-b-2">
<hr>
<span class="a-meta justify-content-center u-m-t-6">Data from: 2025-10-01</span>
<h2 class="alpha u-align-center">Average score over time</h2>
<p class="u-align-center">1200 samhällskritiska domäner.</p>
</div>
<div class="grid-18 grid-md-12">
<div class="chart-container chart-container--large" id="container"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Web Configuration</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites well-configured HTTPS",
value: 76
},
{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 24
},
];
const container = document.getElementById("container");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">76%</strong><strong style="font-size: ${18 * fontScale}px; margin: 0; line-height:2;">Well configured</strong><span style="font-size: ${18 * fontScale}px; margin: 0;">912 av 1200</span></div>`;
const svg = d3.select("#container")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#ffce2e", "#ff4069"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
</div>
<div class="row justify-content-center flex-wrap u-p-b-2">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-6">Adoption of standards</h2>
<p class="u-align-center">Key aspects of web application security of the hosts monitored by this dashboard.</p>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container3"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Modern address (IPv6)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites well-configured HTTPS",
value: 49
},
{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 40
},
{
name: "Sites well-configured HTTP",
value: 11
},
];
const container = document.getElementById("container3");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">49%</strong></div>`;
const svg = d3.select("#container3")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container4"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Signed domain name (DNSSEC)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 22
},
{
name: "Sites whose HTTPS configuration has errors or lacks critical features",
value: 33
},
{
name: "Sites whose HTTP configuration has errors or lacks critical features",
value: 55
},
];
const container = document.getElementById("container4");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">22%</strong></div>`;
const svg = d3.select("#container4")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container5"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Secure connection following NCSC requirements (HTTPS)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 92
},
{
name: "Sites whose HTTPS configuration has errors or lacks critical features",
value: 5
},
{
name: "Sites whose HTTP configuration has errors or lacks critical features",
value: 3
},
];
const container = document.getElementById("container5");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">92%</strong></div>`;
const svg = d3.select("#container5")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container6"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Security options</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 3
},
{
name: "Sites whose HTTPS configuration has errors or lacks critical features",
value: 70
},
{
name: "Sites whose HTTP configuration has errors or lacks critical features",
value: 27
},
];
const container = document.getElementById("container6");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">3%</strong></div>`;
const svg = d3.select("#container6")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container66"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Route authorisation (RPKI)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 3
},
{
name: "Sites whose HTTPS configuration has errors or lacks critical features",
value: 70
},
{
name: "Sites whose HTTP configuration has errors or lacks critical features",
value: 27
},
];
const container = document.getElementById("container66");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">3%</strong></div>`;
const svg = d3.select("#container66")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container666"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Extra Fields</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [{
name: "Sites whose HTTPS configuration has smaller problems or lacks important features",
value: 3
},
{
name: "Sites whose HTTPS configuration has errors or lacks critical features",
value: 70
},
{
name: "Sites whose HTTP configuration has errors or lacks critical features",
value: 27
},
];
const container = document.getElementById("container666");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">3%</strong></div>`;
const svg = d3.select("#container666")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
</div>
<div class="row justify-content-center u-p-b-2">
<div class="grid-18">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-6">Adoption of standards per category</h2>
<p class="u-align-center">This graph shows the average adoption per standard per report.</p>
</div>
<div class="chart-wrapper" id="chart-bar">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
</div>
</div>
<div class="row">
<div class="grid-18">
<div id="tableSmall" class="ag-grid js-ag-grid" style="min-height: 345px">
<div class="ag-grid__wrapper">
<div class="ag-grid__inner js-ag-grid" data-json="./table-small.json"></div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center u-p-b-2 u-m-t-4">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-4">Selective graphs</h2>
</div>
<div class="grid-18 u-p-b-4">
<div class="o-selectable o-selectable--padded o-selectable--shadow-small o-selectable--border-radius background-snow" data-selectable id="selectable">
<div class="form-control">
<fieldset>
<div class="row align-items-end">
<div class="grid u-m-b-1">
<legend>Välj</legend>
<label for="selactable" class="u-visuallyhidden">Valt</label>
<select name="selactable" id="selactable" class="a-select a-select--full-width" aria-controls="selectable-panel" data-selectable-select>
<option value="0">Välj</option>
<option value="1" selected>Modern address (IPv6)</option>
<option value="2">Signed domain name (DNSSEC)</option>
<option value="3">Secure connection following NCSC requirements (HTTPS)</option>
<option value="10">Security options</option>
<option value="13">Route authorization (RPKI)</option>
<option value="16">Extra <fields></fields>
</option>
</select>
</div>
<div class="grid-md-auto u-m-b-1">
<button class="a-button a-button--icon u-m-r-1" data-selectable-all>
<span class="a-button__text" data-label-unpressed="Visa alla" data-label-pressed="Visa en i taget">
Visa alla
</span>
<svg class="icon a-button__icon">
<use xlink:href="#icon-hamburger"></use>
</svg>
</button>
<button class="a-button a-button--ocean-light a-button--icon" data-selectable-copy>
<span class="a-button__text" data-copied="Kopierad!">
Kopiera länk
</span>
<svg class="icon a-button__icon">
<use xlink:href="#icon-link"></use>
</svg>
</button>
</div>
</div>
</fieldset>
</div>
<div id="selectable-panel" data-selectable-items>
<div class="o-selectable__item" data-selectable-item id="selectable-1">
<h2 class="u-align-center u-m-t-2">Modern address (IPv6)</h2>
<div class="chart-wrapper" id="chart-bar2">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar2");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Name servers of domain</h3>
<div class="chart-wrapper" id="chart-bar3">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar3");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Webserver</h3>
<div class="chart-wrapper" id="chart-bar4">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar4");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-2">
<h2 class="u-align-center u-m-t-2">Signed domain name (DNSSEC)</h2>
<div class="chart-wrapper" id="chart-bar0">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar0");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-3">
<h2 class="u-align-center u-m-t-2">HTTP</h2>
<div class="chart-wrapper" id="chart-bar5">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar5");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">TLS</h3>
<div class="chart-wrapper" id="chart-bar6">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar6");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Certificate</h3>
<div class="chart-wrapper" id="chart-bar7">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar7");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Dane</h3>
<div class="chart-wrapper" id="chart-bar8">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => {
void el.offsetWidth;
el.style.opacity = '1';
}, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [{
name: "Modern adress (IPv6)",
Passed: 62,
Info: 6,
Warning: 8,
Failed: 12,
"Not tested": 5,
"Not applicable": 5,
"Test error": 2
},
{
name: "Signed domain name (DNSSEC)",
Passed: 70,
Info: 4,
Warning: 7,
Failed: 10,
"Not tested": 4,
"Not applicable": 3,
"Test error": 2
},
{
name: "Secure connection following NCSC requirements (HTTPS)",
Passed: 58,
Info: 10,
Warning: 12,
Failed: 12,
"Not tested": 4,
"Not applicable": 2,
"Test error": 2
},
{
name: "Security options",
Passed: 54,
Info: 9,
Warning: 14,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
},
{
name: "Route authorization",
Passed: 48,
Info: 7,
Warning: 12,
Failed: 20,
"Not tested": 6,
"Not applicable": 5,
"Test error": 2
},
{
name: "Extra fields",
Passed: 60,
Info: 8,
Warning: 10,
Failed: 12,
"Not tested": 6,
"Not applicable": 2,
"Test error": 2
},
{
name: "Average",
Passed: 59,
Info: 7,
Warning: 11,
Failed: 13,
"Not tested": 5,
"Not applicable": 3,
"Test error": 2
}
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar8");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d) {
return colors[d];
});
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d) {
return d;
});
// ---- Dimensions in viewBox units (layout space) ----
var margin = {
top: 10,
right: 20,
bottom: 70,
left: 60
};
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d) {
return d + "%";
});
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var series = statuses.map(function(key) {
return {
key: key,
values: data.map(function(d) {
return {
x: d.name,
y: +d[key] || 0
};
})
};
});
var layers = stack(series).map(function(s) {
s.values.forEach(function(v) {
v.key = s.key;
});
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines() {
return d3.svg.axis().scale(y).orient("left").ticks(5);
}
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d) {
return colors[d.key];
})
.on("mouseover", function(d) {
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function() {
tooltip.transition().duration(150).style("opacity", 0);
})
.transition().duration(1200)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
// ===== Initial draw =====
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function() {
drawBarGraph(barData, {
heightPx: 600,
viewBoxW: 1200,
viewBoxH: 600
});
});
</script>
</div>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-10">
<h2 class="u-align-center u-m-t-2">Security options</h2>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-13">
<h2 class="u-align-center u-m-t-2">Route authorization (RPKI)</h2>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-16">
<h2 class="u-align-center u-m-t-2">Extra</h2>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-6">Metrics table</h2>
</div>
<div class="grid-18 u-m-b-8">
<div id="tableLarge" class="ag-grid js-ag-grid" style="min-height: 600px">
<button data-ag-grid-fullscreen class="a-button a-button--icon a-button--transparent u-display-inline-flex align-self-end" type="button" aria-controls="tableLarge" data-toggle-icon-target="toggleGridIcon-tableLarge" data-toggle-target="toggleGridText-tableLarge" data-toggle-icon="fullscreen|exit-fullscreen" data-toggle-text="Expandera tabell|Återgå">
<span class="a-button__text" id="toggleGridText-tableLarge">Expandera tabell</span>
<svg class="icon a-button__icon" id="toggleGridIcon-tableLarge">
<use xlink:href="#icon-fullscreen"></use>
</svg>
</button>
<div class="ag-grid__wrapper">
<div class="ag-grid__inner js-ag-grid" data-json="./table.json"></div>
</div>
</div>
</div>
</div>
</div>
<div class="background-snow u-border-radius u-p-2 u-m-b-2">
<span class="a-meta">Ikonförklaringar</span>
<h3 class="beta u-m-b-0">Ikoner per testkategori</h3>
<ul class="u-list-clean u-m-b-3">
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#25c279" role="image" aria-labelledby="test-result-definition-good">
<use xlink:href="#icon-check-variant"></use>
</svg>
<span id="test-result-definition-good">
<strong class="u-display-inline">Good</strong>: passed all subtests ⇒ full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#d9002f" role="image" aria-labelledby="test-result-definition-bad">
<use xlink:href="#icon-close-variant"></use>
</svg>
<span id="test-result-definition-bad">
<strong class="u-display-inline">Bad</strong>: failed at least one REQUIRED subtest ⇒ no full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#f99963" role="image" aria-labelledby="test-result-definition-warning">
<use xlink:href="#icon-info-variant"></use>
</svg>
<span id="test-result-definition-warning">
<strong class="u-display-inline">Warning</strong>: failed at least one RECOMMENDED subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#50b2fc" role="image" aria-labelledby="test-result-definition-informational">
<use xlink:href="#icon-info-variant"></use>
</svg>
<span id="test-result-definition-informational">
<strong class="u-display-inline">Informational</strong>: failed at least one OPTIONAL subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#8E9297" role="image" aria-labelledby="test-result-definition-error">
<use xlink:href="#icon-close-variant"></use>
</svg>
<span id="test-result-definition-error">
<strong class="u-display-inline">Test error</strong>: execution error for at least one subtest ⇒ no result in test category
</span>
</li>
</ul>
<h3 class="beta u-m-b-0">Ikoner per subtext</h3>
<ul class="u-list-clean u-m-b-3">
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#25c279" role="image" aria-labelledby="subtest-result-definition-good">
<use xlink:href="#icon-security-variant"></use>
</svg>
<span id="subtest-result-definition-good">
<strong class="u-display-inline">Good</strong>: pass on REQUIRED, RECOMMENDED or OPTIONAL subtest ⇒ first: full score / latter two: no score impact
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#d9002f" role="image" aria-labelledby="subtest-result-definition-bad">
<use xlink:href="#icon-unsecure-variant"></use>
</svg>
<span id="subtest-result-definition-bad">
<strong class="u-display-inline">Bad</strong>: failed at least one REQUIRED subtest ⇒ no full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#f99963" role="image" aria-labelledby="subtest-result-definition-warning">
<use xlink:href="#icon-warning-variant"></use>
</svg>
<span id="subtest-result-definition-warning">
<strong class="u-display-inline">Warning</strong>: failed at least one RECOMMENDED subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#50b2fc" role="image" aria-labelledby="subtest-result-definition-informational">
<use xlink:href="#icon-info-variant"></use>
</svg>
<span id="subtest-result-definition-informational">
<strong class="u-display-inline">Informational</strong>: failed at least one OPTIONAL subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#8E9297" role="image" aria-labelledby="subtest-result-definition-error">
<use xlink:href="#icon-unsecure-variant"></use>
</svg>
<span id="subtest-result-definition-error">
<strong class="u-display-inline">Test error</strong>: execution error for at least one subtest ⇒ no result in test category
</span>
</li>
<li class="display-flex">
<svg class="icon u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#d8d8d8" role="image" aria-labelledby="subtest-result-definition-untested">
<use xlink:href="#icon-unsecure-variant"></use>
</svg>
<span id="subtest-result-definition-untested">
<strong class="u-display-inline">Not tested</strong>, because already failed related parent subtest ⇒ null score
</span>
</li>
</ul>
</div>
<div class="row">
<div class="grid-18">
<hr>
</div>
</div>
</section>
</main>
</div>
</div>
</div>
<div class="o-pre-footer">
<div class="wrapper">
<div class="row justify-content-between">
<div class="grid-lg-5">
<dl class="u-list-clean u-font-size-medium">
<dt class="u-weight-bold u-m-b-1">Powered by</dt>
<dd class="u-m-l-0">
<img src="/assets/images/internet-nl_logo_en.svg" alt="Internet NL logotype">
</dd>
<dd class="u-m-l-0 u-m-t-1">
<a href="https://internet.nl" target="_blank" class="u-link-normal color-cyberspace">Internet.nl</a> is an initiative of the Internet community and the Dutch government.
</dd>
</dl>
</div>
<div class="grid-auto">
<dl class="u-list-clean u-font-size-medium">
<dt class="u-weight-bold">Programversion</dt>
<dd class="u-m-l-0 u-m-b-1">v1.1.0.6</dd>
<dt class="u-weight-bold">API-version</dt>
<dd class="u-m-l-0">Internetkollen-API 1.0</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="site__footer">
<div class="wrapper-fluid">
<footer class="o-footer" id="siteFooter">
<div class="row">
<div class="grid-18 o-footer__info">
<div class="row">
<div class="grid-18 grid-lg-9 o-footer__info__freetext">
Bredbandskollen är ett oberoende
konsumentverktyg som drivs av <a class='o-footer__link' href='https://internetstiftelsen.se'>Internetstiftelsen</a>.
Internetstiftelsen är en oberoende, affärsdriven och allmännyttig organisation. Vi verkar
för ett internet som bidrar positivt till människan och samhället. Vi ansvarar för internets
svenska toppdomän .se och sköter drift och administration av toppdomänen .nu. Intäkterna
från affärsverksamheten finansierar en rad satsningar i syfte att möjliggöra att människor
kan nyttja internet på bästa sätt, och stimulera kunskapsdelning och innovation med
inriktning på internet.
</div>
<div class="grid-18 grid-lg-9 o-footer__info__contact">
<div class="row o-footer__info__contact__row-first">
<div class="grid-md-9 grid-lg-18 grid-xl-9 grid-18 o-footer__info__address1">
<ul class='u-list-clean o-footer__info__contact__address'>
<li class='o-footer__info__contact__address__li'>Internetstiftelsen</li>
<li class='o-footer__info__contact__address__li'>Hammarby Kaj 10D</li>
<li class='o-footer__info__contact__address__li'>Box 92073</li>
<li class='o-footer__info__contact__address__li'>120 07 Stockholm</li>
</ul>
</div>
<div class="grid-md-9 grid-lg-18 grid-xl-9 grid-18 o-footer__info__address2">
<ul class='u-list-clean o-footer__info__contact__address'>
<li class='o-footer__info__contact__address__li'>E-post: <a class='o-footer__link' href='mailto:info@bredbandskollen.se'>info@bredbandskollen.se</a></li>
<li class='o-footer__info__contact__address__li'>Telefon: <a class='o-footer__link' href='tel:084523500'>08-452 35 00</a></li>
<li class='o-footer__info__contact__address__li'>Organisationsnummer: 802405-0190</li>
</ul>
</div>
</div>
<div class="row o-footer__info__contact__row-second">
<div class="grid-19 grid-md-9 o-footer__info__ISO">
<a href="https://internetstiftelsen.se/docs/Certifikat_27001_UKAS_sv.pdf" class="o-footer__link o-footer__external-link">
<img src="https://internetstiftelsen.se/app/themes/internetstiftelsen/images/ISO_27001_2013_black_TM.svg" class="o-footer__external-logo" alt="Certifierade enligt ISO/IEC 27001:2013">
<span>Certifierade enligt ISO/IEC<br>27001:2013</span>
</a>
</div>
<div class="grid-19 grid-md-9 o-footer__info__CC">
<a href="https://creativecommons.org/licenses/by/4.0/legalcode.sv" class="o-footer__link o-footer__external-link">
<img class="o-footer__external-logo o-footer__external-logo--cc" src="https://svenskarnaochinternet.se/app/uploads/2019/10/ccby.png" alt="Creative Commons licens<br>Erkännande 4.0 Internationell">
<span>Creative Commons licens<br>Erkännande 4.0 Internationell</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="grid-18 o-footer__logotypes">
<div class="row">
<div class="o-footer__logotypes__item">
<a href="https://svenskarnaochinternet.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Svenskarna och internet" src="https://static.internetstiftelsen.se/images/logotypes/svenskarna-och-internet.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>'En årlig studie av svenska folkets internetvanor</span>
</div>
</div>
</a>
</div>
<div class="o-footer__logotypes__item">
<a href="https://internetdagarna.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Internetdagarna" src="https://static.internetstiftelsen.se/images/logotypes/internetdagarna.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Internetdagarna är en konferens som arrangeras av Internetstiftelsen</span>
</div>
</div>
</a>
</div>
<div class="o-footer__logotypes__item">
<a href="https://goto10.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Goto10" src="https://static.internetstiftelsen.se/images/logotypes/goto-10.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Goto 10 är en start- och mötesplats för internetrelaterade frågor som drivs av Internetstiftelsen</span>
</div>
</div>
</a>
</div>
<!--<div class="o-footer__logotypes__item">
<a href="https://bredbandskollen.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Bredbandskollen" src="https://static.internetstiftelsen.se/images/logotypes/bredbandskollen.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Bredbandskollen är ett oberoende konsumentverktyg som drivs av Internetstiftelsen</span>
</div>
</div>
</a>
</div>-->
<div class="o-footer__logotypes__item">
<a href="https://internetmuseum.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Internetmuseum" src="https://static.internetstiftelsen.se/images/logotypes/internetmuseum.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Ett digitalt museum som byggts, och kureras av Internetstiftelsen</span>
</div>
</div>
</a>
</div>
<div class="o-footer__logotypes__item">
<a href="https://digitalalektioner.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Digitala lektioner" src="https://static.internetstiftelsen.se/images/logotypes/digitala-lektioner.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Öppen digital lärresurs med färdiga lektioner för alla stadier i grundskolan</span>
</div>
</div>
</a>
</div>
<div class="o-footer__logotypes__item">
<a href="https://digitalalektioner.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Internetkunskap" src="https://static.internetstiftelsen.se/images/logotypes/internetkunskap.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Samlad kunskap som hjälper dig att bli en säker och medveten internetanvändare</span>
</div>
</div>
</a>
</div>
<!--<div class="o-footer__logotypes__item">
<a href="https://internetstiftelsen.se" class="o-footer__logotypes__item__link">
<div class="o-footer__logotypes__item__inner">
<div class="o-footer__logotypes__item__front">
<img class="o-footer__logotype" alt="Internetstiftelsen" src="https://static.internetstiftelsen.se/images/logotypes/internetstiftelsen.svg">
</div>
<div class="o-footer__logotypes__item__back">
<h2></h2>
<span>Internetstiftelsen verkar för ett internet som bidrar positivt till människan och samhället</span>
</div>
</div>
</a>
</div>-->
</div>
</div>
</div>
<div class="row">
<div class="u-p-y-1 o-footer__bottom-links">
<nav>
<ul class="u-list-clean display-flex">
<li class="u-p-x-1"><a href="https://internetstiftelsen.se/om-webbplatsen/" class="o-footer__link o-footer__about-link">Om webbplatsen</a></li>
<li class="u-p-x-1"><a href="https://internetstiftelsen.se/om-webbplatsen/cookies/" class="o-footer__link o-footer__about-link">Om cookies</a></li>
<li class="u-p-x-1"><button id="ot-sdk-btn" class="o-footer__link o-footer__about-link ot-sdk-show-settings">Anpassa kakor</button></li>
</ul>
</nav>
</div>
</div>
</footer>
</div>
</div>
</div>
<style>
.chart-container {
max-width: 250px;
margin-left: auto;
margin-right: auto;
}
.chart-container--large {
max-width: 400px;
}
/* Chart container */
.chart-wrapper {
position: relative;
background: none !important;
}
/* Enforce transparent SVG background */
.chart-wrapper svg {
background: none !important;
fill: none !important;
}
.chart-wrapper svg text {
font-size: 14px;
white-space: wrap;
}
/* Legend: full width, wraps, 16px text always */
.chart-legend {
display: flex;
justify-content: center;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px;
/* row gap | column gap */
width: 100%;
margin-bottom: 8px;
/* space between legend and SVG */
font-size: 16px;
/* fixed legend text size */
line-height: 1.2;
}
.chart-legend .legend-item {
display: inline-flex;
align-items: center;
}
.chart-legend .legend-swatch {
width: 14px;
height: 14px;
border-radius: 2px;
margin-right: 6px;
flex: 0 0 auto;
}
.chart-legend .legend-label {
white-space: nowrap;
/* keep label itself on one line */
color: #000;
}
</style>
<script src="https://cdn.lordicon.com/lordicon-1.1.0.js"></script>
<div class="site u-z-index-foreground" id="site">
<div class="site__header" id="siteHeader">
<div class="u-position-relative">
<header class="o-header">
<div class="wrapper">
<div class="row justify-content-between align-items-center flex-nowrap">
<div class="grid-auto">
<a href="/" class="o-header__logo-link">
<span class="o-header__logo">
<img class="logotype" src="https://static.internetstiftelsen.se/images/logotypes/internetkollen.svg" alt="logotyp">
</span>
<span class="u-visuallyhidden">Till startsidan</span>
</a>
</div>
<div class="grid u-hide-sm">
<h1 id="portal-title" data-default="Internetkollen" class="u-b-0 u-b-l-1 u-b-solid u-b-concrete display-flex u-p-y-1 u-p-x-2 u-m-y-1 u-font-size-medium u-weight-normal">Test för moderna internetstandarder</h1>
</div>
<div class="grid">
<nav class="a-main-menu" aria-label="Huvudmeny">
<ul class="a-main-menu__list">
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Testa själv</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link is-current">
<span class="a-main-menu__list__text">Hälsoläget</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Om</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Hjälp</span>
</a>
</li>
<li class="u-hidden-mobile">
<a href="#" class="a-main-menu__list__link">
<span class="a-main-menu__list__text">Kontakt</span>
</a>
</li>
<li class="u-hide-xl-up">
<button type="button" class="a-main-menu__list__link" aria-expanded="false" data-a11y-toggle="dropdown">
<span class="a-main-menu__list__text">Meny</span>
<div class="icon-arrow-down"></div>
</button>
<ul id="dropdown" class="a-main-menu__subnav a-main-menu__subnav--right-aligned u-box-shadow-card">
<li class="a-main-menu__subnav__item">
<a href="#">Testa själv</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Hälsoläget</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Om</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Hjälp</a>
</li>
<li class="a-main-menu__subnav__item">
<a href="#">Kontakt</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</div>
</header>
</div>
</div>
<div class="site__main" id="siteMain">
<div class="u-p-2 background-snow display-flex justify-content-end align-items-center u-b-solid u-b-b-1 u-b-x-0 u-b-t-0 u-b-concrete">
<label for="history" class="u-m-r-1">Historiska tester</label>
<select name="history" id="history" class="a-select">
<option value="0" disabled selected>Välj datum</option>
<option value="1">2025-10-01 (latest)</option>
<option value="1">2025-09-01</option>
<option value="1">2025-08-01</option>
</select>
</div>
<div class="wrapper u-p-t-2 u-p-b-8">
<div class="row">
<main class="grid-18">
<section class="row justify-content-center">
<div class="grid-18 grid-md-12 u-p-t-4 u-m-b-2">
<h1 class="supersize u-align-center">
Hälsoläget - 1200 samhällskritiska domäner
</h1>
<p class="preamble u-align-center">
Sveriges Hälsoläget övervakar webb- och e-postsäkerhetskonfigurationen för Sveriges främsta internettjänster. Tjänsten drivs av <a href="">Internetstiftelsen.</a>
</p>
</div>
</section>
<section>
<div class="row justify-content-center u-p-b-2">
<div class="grid-18 u-m-b-2">
<hr>
<span class="a-meta justify-content-center u-m-t-6">Data from: 2025-10-01</span>
<h2 class="alpha u-align-center">Average score over time</h2>
<p class="u-align-center">1200 samhällskritiska domäner.</p>
</div>
<div class="grid-18 grid-md-12">
<div class="chart-container chart-container--large" id="container"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Web Configuration</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites well-configured HTTPS", value: 76 },
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 24 },
];
const container = document.getElementById("container");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">76%</strong><strong style="font-size: ${18 * fontScale}px; margin: 0; line-height:2;">Well configured</strong><span style="font-size: ${18 * fontScale}px; margin: 0;">912 av 1200</span></div>`;
const svg = d3.select("#container")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#ffce2e", "#ff4069"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
</div>
<div class="row justify-content-center flex-wrap u-p-b-2">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-6">Adoption of standards</h2>
<p class="u-align-center">Key aspects of web application security of the hosts monitored by this dashboard.</p>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container3"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Modern address (IPv6)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites well-configured HTTPS", value: 49 },
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 40 },
{ name: "Sites well-configured HTTP", value: 11 },
];
const container = document.getElementById("container3");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">49%</strong></div>`;
const svg = d3.select("#container3")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container4"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Signed domain name (DNSSEC)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 22 },
{ name: "Sites whose HTTPS configuration has errors or lacks critical features", value: 33 },
{ name: "Sites whose HTTP configuration has errors or lacks critical features", value: 55 },
];
const container = document.getElementById("container4");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">22%</strong></div>`;
const svg = d3.select("#container4")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container5"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Secure connection following NCSC requirements (HTTPS)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 92 },
{ name: "Sites whose HTTPS configuration has errors or lacks critical features", value: 5 },
{ name: "Sites whose HTTP configuration has errors or lacks critical features", value: 3 },
];
const container = document.getElementById("container5");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">92%</strong></div>`;
const svg = d3.select("#container5")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container6"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Security options</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 3 },
{ name: "Sites whose HTTPS configuration has errors or lacks critical features", value: 70 },
{ name: "Sites whose HTTP configuration has errors or lacks critical features", value: 27 },
];
const container = document.getElementById("container6");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">3%</strong></div>`;
const svg = d3.select("#container6")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container66"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Route authorisation (RPKI)</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 3 },
{ name: "Sites whose HTTPS configuration has errors or lacks critical features", value: 70 },
{ name: "Sites whose HTTP configuration has errors or lacks critical features", value: 27 },
];
const container = document.getElementById("container66");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">3%</strong></div>`;
const svg = d3.select("#container66")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
<div class="grid-18 grid-sm-9 grid-md-4 u-p-b-4">
<div class="chart-container" id="container666"></div>
<span class="a-meta u-wrap u-display-block u-align-center u-m-b-2 u-m-t-1">Extra Fields</span>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [
{ name: "Sites whose HTTPS configuration has smaller problems or lacks important features", value: 3 },
{ name: "Sites whose HTTPS configuration has errors or lacks critical features", value: 70 },
{ name: "Sites whose HTTP configuration has errors or lacks critical features", value: 27 },
];
const container = document.getElementById("container666");
const containerWidth = container.clientWidth || window.innerWidth;
const size = Math.min(containerWidth, window.innerHeight, 600);
const width = size;
const height = size;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.6;
const fontScale = size / 320;
const title = `<div style="line-height: 1;display: flex; flex-direction: column; justify-content: center; align-items: center;"><strong style="font-size: ${65 * fontScale}px; font-weight: bold; margin: 0;">3%</strong></div>`;
const svg = d3.select("#container666")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(["#25c279", "#d8d8d8", "#8E9297"]);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius);
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0, 0, 0, 0.8)")
.style("color", "white")
.style("padding", "10px")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "Arial, sans-serif")
.style("font-size", "14px")
.style("max-width", "300px");
const titleGroup = svg.append("foreignObject")
.attr("width", innerRadius * 2)
.attr("height", innerRadius * 2)
.attr("x", -innerRadius)
.attr("y", -innerRadius)
.append("xhtml:div")
.style('height', '100%')
.style('width', '100%')
.style('display', 'grid')
.style('place-items', 'center')
.style('pointer-events', 'none')
.html(title);
const arcs = svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 2)
.on("mouseover", function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.8);
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html(`<strong>${d.data.name}</strong><br>Value: ${d.data.value}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 10) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
tooltip.transition()
.duration(200)
.style("opacity", 0);
});
</script>
</div>
</div>
<div class="row justify-content-center u-p-b-2">
<div class="grid-18">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-6">Adoption of standards per category</h2>
<p class="u-align-center">This graph shows the average adoption per standard per report.</p>
</div>
<div class="chart-wrapper" id="chart-bar">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
</div>
</div>
<div class="row">
<div class="grid-18">
{{render '@table-advanced' id="tableSmall"}}
</div>
</div>
<div class="row justify-content-center u-p-b-2 u-m-t-4">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-4">Selective graphs</h2>
</div>
<div class="grid-18 u-p-b-4">
<div class="o-selectable o-selectable--padded o-selectable--shadow-small o-selectable--border-radius background-snow" data-selectable id="selectable">
<div class="form-control">
<fieldset>
<div class="row align-items-end">
<div class="grid u-m-b-1">
<legend>Välj</legend>
<label for="selactable" class="u-visuallyhidden">Valt</label>
<select name="selactable" id="selactable" class="a-select a-select--full-width" aria-controls="selectable-panel" data-selectable-select>
<option value="0">Välj</option>
<option value="1" selected>Modern address (IPv6)</option>
<option value="2">Signed domain name (DNSSEC)</option>
<option value="3">Secure connection following NCSC requirements (HTTPS)</option>
<option value="10">Security options</option>
<option value="13">Route authorization (RPKI)</option>
<option value="16">Extra <fields></fields></option>
</select>
</div>
<div class="grid-md-auto u-m-b-1">
<button class="a-button a-button--icon u-m-r-1" data-selectable-all>
<span class="a-button__text" data-label-unpressed="Visa alla" data-label-pressed="Visa en i taget">
Visa alla
</span>
<svg class="icon a-button__icon">
<use xlink:href="#icon-hamburger"></use>
</svg>
</button>
<button class="a-button a-button--ocean-light a-button--icon" data-selectable-copy>
<span class="a-button__text" data-copied="Kopierad!">
Kopiera länk
</span>
<svg class="icon a-button__icon">
<use xlink:href="#icon-link"></use>
</svg>
</button>
</div>
</div>
</fieldset>
</div>
<div id="selectable-panel" data-selectable-items>
<div class="o-selectable__item" data-selectable-item id="selectable-1">
<h2 class="u-align-center u-m-t-2">Modern address (IPv6)</h2>
<div class="chart-wrapper" id="chart-bar2">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar2");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Name servers of domain</h3>
<div class="chart-wrapper" id="chart-bar3">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar3");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Webserver</h3>
<div class="chart-wrapper" id="chart-bar4">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar4");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-2">
<h2 class="u-align-center u-m-t-2">Signed domain name (DNSSEC)</h2>
<div class="chart-wrapper" id="chart-bar0">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar0");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-3">
<h2 class="u-align-center u-m-t-2">HTTP</h2>
<div class="chart-wrapper" id="chart-bar5">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar5");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">TLS</h3>
<div class="chart-wrapper" id="chart-bar6">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar6");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Certificate</h3>
<div class="chart-wrapper" id="chart-bar7">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar7");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
<h3 class="u-align-center u-m-t-4">Dane</h3>
<div class="chart-wrapper" id="chart-bar8">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// === (Optional) Fade in .wrapper without jQuery ===
(function fadeInWrapper() {
const el = document.querySelector('.wrapper');
if (!el) return;
el.style.display = getComputedStyle(el).display === 'none' ? 'block' : getComputedStyle(el).display;
el.style.opacity = '0';
el.style.transition = 'opacity 500ms';
setTimeout(() => { void el.offsetWidth; el.style.opacity = '1'; }, 600);
})();
// ===== Data (percentages; each row should sum to ~100) =====
var barData = [
{ name: "Modern adress (IPv6)", Passed: 62, Info: 6, Warning: 8, Failed: 12, "Not tested": 5, "Not applicable": 5, "Test error": 2 },
{ name: "Signed domain name (DNSSEC)", Passed: 70, Info: 4, Warning: 7, Failed: 10, "Not tested": 4, "Not applicable": 3, "Test error": 2 },
{ name: "Secure connection following NCSC requirements (HTTPS)", Passed: 58, Info: 10, Warning: 12, Failed: 12, "Not tested": 4, "Not applicable": 2, "Test error": 2 },
{ name: "Security options", Passed: 54, Info: 9, Warning: 14, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 },
{ name: "Route authorization", Passed: 48, Info: 7, Warning: 12, Failed: 20, "Not tested": 6, "Not applicable": 5, "Test error": 2 },
{ name: "Extra fields", Passed: 60, Info: 8, Warning: 10, Failed: 12, "Not tested": 6, "Not applicable": 2, "Test error": 2 },
{ name: "Average", Passed: 59, Info: 7, Warning: 11, Failed: 13, "Not tested": 5, "Not applicable": 3, "Test error": 2 }
];
// ===== Statuses & Colors =====
var statuses = [
"Passed",
"Info",
"Warning",
"Failed",
"Not tested",
"Not applicable",
"Test error"
];
var colors = {
"Passed": "#25c279", // green
"Info": "#50b2fc", // blue
"Warning": "#f99963", // orange
"Failed": "#d9002f", // dark red
"Not tested": "#8E9297", // dark gray
"Not applicable": "#d8d8d8", // medium dark gray
"Test error": "#ededed" // light gray
};
// ===== Draw function =====
// options: { heightPx: number (total SVG pixel height), viewBoxW: number, viewBoxH: number }
function drawBarGraph(data, options) {
var opts = options || {};
var heightPx = opts.heightPx || 600; // how tall the SVG should appear (pixels)
var vbW = opts.viewBoxW || 1200; // internal coord width (viewBox)
var vbH = opts.viewBoxH || 600; // internal coord height (viewBox)
var containerSel = d3.select("#chart-bar8");
containerSel.selectAll("*").remove(); // clear previous render
containerSel.style("position", "relative");
function wrapText(text, width) {
text.each(function() {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.2, // ems
y = textEl.attr("y"),
dy = parseFloat(textEl.attr("dy") || 0),
tspan = textEl.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = textEl.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// ---- Legend (HTML, full width, wraps) ----
var legend = containerSel.append("div")
.attr("class", "chart-legend")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "8px 16px")
.style("width", "100%")
.style("margin-bottom", "8px")
.style("font-size", "16px")
.style("line-height", "1.2");
var legendItem = legend.selectAll(".legend-item")
.data(statuses)
.enter()
.append("div")
.attr("class", "legend-item")
.style("display", "inline-flex")
.style("align-items", "center");
legendItem.append("span")
.attr("class", "legend-swatch")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "2px")
.style("margin-right", "6px")
.style("background", function(d){ return colors[d]; });
legendItem.append("span")
.attr("class", "legend-label")
.style("white-space", "nowrap")
.style("color", "#000")
.text(function(d){ return d; });
// ---- Dimensions in viewBox units (layout space) ----
var margin = { top: 10, right: 20, bottom: 70, left: 60 };
var width = vbW - margin.left - margin.right;
var height = vbH - margin.top - margin.bottom;
// ---- SVG (responsive width, fixed pixel height, with viewBox) ----
var svgOuter = containerSel.append("svg")
.attr("width", "100%")
.attr("height", heightPx) // visible pixel height you choose
.attr("viewBox", "0 0 " + vbW + " " + vbH) // internal layout uses vbW/vbH
// keep default preserveAspectRatio="xMidYMid meet" to avoid distortion
.style("background", "none");
var svg = svgOuter.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- Scales (in viewBox units) ----
var x = d3.scale.ordinal()
.domain(data.map(function(d){ return d.name; }))
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5).tickFormat(function(d){ return d + "%"; });
// ---- Stacked data ----
var stack = d3.layout.stack()
.values(function(d){ return d.values; })
.x(function(d){ return d.x; })
.y(function(d){ return d.y; });
var series = statuses.map(function(key){
return {
key: key,
values: data.map(function(d){ return { x: d.name, y: +d[key] || 0 }; })
};
});
var layers = stack(series).map(function(s){
s.values.forEach(function(v){ v.key = s.key; });
return s.values;
});
// ---- Gridlines ----
function make_y_gridlines(){ return d3.svg.axis().scale(y).orient("left").ticks(5); }
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines().tickSize(-width).tickFormat(""))
.selectAll("line")
.style("stroke", "#ccc");
// ---- Axes (black) ----
var xAxisGroup = svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Wrap the tick labels
xAxisGroup.selectAll("text")
.style("fill", "#000")
.style("text-anchor", "middle")
.attr("dy", "1em")
.call(wrapText, x.rangeBand());
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.selectAll("text")
.style("fill", "#000");
svg.selectAll(".axis path, .axis line").style("stroke", "#000");
// ---- Axis labels ----
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 55) + ")")
.style("text-anchor", "middle")
.style("fill", "#000");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -45)
.style("text-anchor", "middle")
.style("fill", "#000")
.text("Share (%)");
// ---- Tooltip ----
var tooltip = d3.select("#chart-bar")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "rgba(0,0,0,0.75)")
.style("color", "#fff")
.style("padding", "4px 8px")
.style("border-radius", "4px")
.style("font", "12px/1.2 sans-serif");
function moveTooltip() {
var e = d3.event;
var rect = d3.select("#chart-bar").node().getBoundingClientRect();
tooltip.style("left", (e.clientX - rect.left + 8) + "px")
.style("top", (e.clientY - rect.top - 12) + "px");
}
// ---- Bars (stacked) ----
var band = x.rangeBand();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer");
layer.selectAll("rect")
.data(function(d){ return d; })
.enter().append("rect")
.attr("x", function(d){ return x(d.x); })
.attr("y", height)
.attr("width", band)
.attr("height", 0)
.style("fill", function(d){ return colors[d.key]; })
.on("mouseover", function(d){
tooltip.transition().duration(100).style("opacity", 1);
tooltip.html("<strong>" + d.x + "</strong><br/>" + d.key + ": " + Math.round(d.y) + "%");
moveTooltip();
})
.on("mousemove", moveTooltip)
.on("mouseout", function(){ tooltip.transition().duration(150).style("opacity", 0); })
.transition().duration(1200)
.attr("y", function(d){ return y(d.y + d.y0); })
.attr("height", function(d){ return y(d.y0) - y(d.y + d.y0); });
}
// ===== Initial draw =====
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
// ===== Optional: redraw on resize (keeps width responsive) =====
window.addEventListener("resize", function(){
drawBarGraph(barData, { heightPx: 600, viewBoxW: 1200, viewBoxH: 600 });
});
</script>
</div>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-10">
<h2 class="u-align-center u-m-t-2">Security options</h2>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-13">
<h2 class="u-align-center u-m-t-2">Route authorization (RPKI)</h2>
</div>
<div class="o-selectable__item" data-selectable-item id="selectable-16">
<h2 class="u-align-center u-m-t-2">Extra</h2>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="grid-18 u-m-b-2">
<hr>
<h2 class="alpha u-align-center u-m-t-6">Metrics table</h2>
</div>
<div class="grid-18 u-m-b-8">
{{render '@table-advanced--large-table' id="tableLarge"}}
</div>
</div>
</div>
<div class="background-snow u-border-radius u-p-2 u-m-b-2">
<span class="a-meta">Ikonförklaringar</span>
<h3 class="beta u-m-b-0">Ikoner per testkategori</h3>
<ul class="u-list-clean u-m-b-3">
<li class="display-flex">
{{>@icon id="check-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#25c279" role="image" aria_labelledby="test-result-definition-good"}}
<span id="test-result-definition-good">
<strong class="u-display-inline">Good</strong>: passed all subtests ⇒ full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="close-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#d9002f" role="image" aria_labelledby="test-result-definition-bad"}}
<span id="test-result-definition-bad">
<strong class="u-display-inline">Bad</strong>: failed at least one REQUIRED subtest ⇒ no full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="info-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#f99963" role="image" aria_labelledby="test-result-definition-warning"}}
<span id="test-result-definition-warning">
<strong class="u-display-inline">Warning</strong>: failed at least one RECOMMENDED subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="info-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#50b2fc" role="image" aria_labelledby="test-result-definition-informational"}}
<span id="test-result-definition-informational">
<strong class="u-display-inline">Informational</strong>: failed at least one OPTIONAL subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="close-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#8E9297" role="image" aria_labelledby="test-result-definition-error"}}
<span id="test-result-definition-error">
<strong class="u-display-inline">Test error</strong>: execution error for at least one subtest ⇒ no result in test category
</span>
</li>
</ul>
<h3 class="beta u-m-b-0">Ikoner per subtext</h3>
<ul class="u-list-clean u-m-b-3">
<li class="display-flex">
{{>@icon id="security-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#25c279" role="image" aria_labelledby="subtest-result-definition-good"}}
<span id="subtest-result-definition-good">
<strong class="u-display-inline">Good</strong>: pass on REQUIRED, RECOMMENDED or OPTIONAL subtest ⇒ first: full score / latter two: no score impact
</span>
</li>
<li class="display-flex">
{{>@icon id="unsecure-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#d9002f" role="image" aria_labelledby="subtest-result-definition-bad"}}
<span id="subtest-result-definition-bad">
<strong class="u-display-inline">Bad</strong>: failed at least one REQUIRED subtest ⇒ no full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="warning-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#f99963" role="image" aria_labelledby="subtest-result-definition-warning"}}
<span id="subtest-result-definition-warning">
<strong class="u-display-inline">Warning</strong>: failed at least one RECOMMENDED subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="info-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#50b2fc" role="image" aria_labelledby="subtest-result-definition-informational"}}
<span id="subtest-result-definition-informational">
<strong class="u-display-inline">Informational</strong>: failed at least one OPTIONAL subtest ⇒ full score in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="unsecure-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#8E9297" role="image" aria_labelledby="subtest-result-definition-error"}}
<span id="subtest-result-definition-error">
<strong class="u-display-inline">Test error</strong>: execution error for at least one subtest ⇒ no result in test category
</span>
</li>
<li class="display-flex">
{{>@icon id="unsecure-variant" additional_classes="u-icon--medium u-m-r-1 u-m-t-05 u-flex-shrink-0" fill="#d8d8d8" role="image" aria_labelledby="subtest-result-definition-untested"}}
<span id="subtest-result-definition-untested">
<strong class="u-display-inline">Not tested</strong>, because already failed related parent subtest ⇒ null score
</span>
</li>
</ul>
</div>
<div class="row">
<div class="grid-18">
<hr>
</div>
</div>
</section>
</main>
</div>
</div>
</div>
{{> @pre-footer }}
<div class="site__footer">
{{> @footer footer_text="Bredbandskollen är ett oberoende
konsumentverktyg som drivs av <a class='o-footer__link' href='https://internetstiftelsen.se'>Internetstiftelsen</a>.
Internetstiftelsen är en oberoende, affärsdriven och allmännyttig organisation. Vi verkar
för ett internet som bidrar positivt till människan och samhället. Vi ansvarar för internets
svenska toppdomän .se och sköter drift och administration av toppdomänen .nu. Intäkterna
från affärsverksamheten finansierar en rad satsningar i syfte att möjliggöra att människor
kan nyttja internet på bästa sätt, och stimulera kunskapsdelning och innovation med
inriktning på internet." ISO_text="Certifierade enligt ISO/IEC<br>27001:2013"
CC_license="Creative Commons licens<br>Erkännande 4.0 Internationell"
ISO_image="https://internetstiftelsen.se/app/themes/internetstiftelsen/images/ISO_27001_2013_black_TM.svg"
ISO_link="https://internetstiftelsen.se/docs/Certifikat_27001_UKAS_sv.pdf"
contact_1="<ul class='u-list-clean o-footer__info__contact__address'><li class='o-footer__info__contact__address__li'>Internetstiftelsen</li><li class='o-footer__info__contact__address__li'>Hammarby Kaj 10D</li><li class='o-footer__info__contact__address__li'>Box 92073</li><li class='o-footer__info__contact__address__li'>120 07 Stockholm</li></ul>"
contact_2="<ul class='u-list-clean o-footer__info__contact__address'><li class='o-footer__info__contact__address__li'>E-post: <a class='o-footer__link' href='mailto:info@bredbandskollen.se'>info@bredbandskollen.se</a></li><li class='o-footer__info__contact__address__li'>Telefon: <a class='o-footer__link' href='tel:084523500'>08-452 35 00</a></li><li class='o-footer__info__contact__address__li'>Organisationsnummer: 802405-0190</li></ul>"
site_description_1="'En årlig studie av svenska folkets internetvanor"
site_description_2="Internetdagarna är en konferens som arrangeras av Internetstiftelsen"
site_description_3="Goto 10 är en start- och mötesplats för internetrelaterade frågor som drivs av Internetstiftelsen"
site_description_4="Bredbandskollen är ett oberoende konsumentverktyg som drivs av Internetstiftelsen"
site_description_5="Ett digitalt museum som byggts, och kureras av Internetstiftelsen"
site_description_6="Öppen digital lärresurs med färdiga lektioner för alla stadier i grundskolan"
site_description_7="Samlad kunskap som hjälper dig att bli en säker och medveten internetanvändare"
site_description_8="Internetstiftelsen verkar för ett internet som bidrar positivt till människan och samhället"
}}
</div>
</div>
<style>
.chart-container {
max-width: 250px;
margin-left: auto;
margin-right: auto;
}
.chart-container--large {
max-width: 400px;
}
/* Chart container */
.chart-wrapper {
position: relative;
background: none !important;
}
/* Enforce transparent SVG background */
.chart-wrapper svg {
background: none !important;
fill: none !important;
}
.chart-wrapper svg text {
font-size: 14px;
white-space: wrap;
}
/* Legend: full width, wraps, 16px text always */
.chart-legend {
display: flex;
justify-content: center;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px; /* row gap | column gap */
width: 100%;
margin-bottom: 8px; /* space between legend and SVG */
font-size: 16px; /* fixed legend text size */
line-height: 1.2;
}
.chart-legend .legend-item {
display: inline-flex;
align-items: center;
}
.chart-legend .legend-swatch {
width: 14px;
height: 14px;
border-radius: 2px;
margin-right: 6px;
flex: 0 0 auto;
}
.chart-legend .legend-label {
white-space: nowrap; /* keep label itself on one line */
color: #000;
}
</style>
/* No context defined. */
No notes defined.