forskjeller.naiv.no/public/boutgifter/app.js
Ole-Morten Duesund 2eef9d9f31 Ny visualisering: «Det er dyrt å være fattig» — boutgifter etter inntekt
SSB-data (tabeller 14061 og 14067) som viser at:
- 41 % av fattigste kvartil bruker >40 % av inntekten på bolig (vs 7 % rikeste)
- 17 % av fattigste bruker 60 %+ av inntekten bare på bolig
- 82 % av sosialhjelpsmottakere leier — bygger ingen formue
- Byrden har økt kraftig for alle grupper etter rentehoppet i 2022

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 20:04:26 +01:00

262 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SSB data: Boutgifter og boligeierskap etter inntekt
// Tabell 14061: Boligøkonomi etter inntektsgruppe (boutgifter som andel av inntekt)
// Tabell 14067: Boligeierskap etter inntektsgruppe
// Hentet 2026-03-16 via SSB PxWeb API v2
// --- Data ---
const YEARS = ['2015', '2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024'];
// Boutgifter som andel av inntekt per kvartil, 2024 (tabell 14061)
const QUARTILE_LABELS = ['Laveste kvartil', 'Andre kvartil', 'Tredje kvartil', 'Høyeste kvartil'];
const HOUSING_BURDEN = {
under10: [ 5.2, 10.8, 16.6, 22.0],
from10to19: [12.4, 26.1, 22.8, 24.6],
from20to29: [19.3, 28.2, 29.0, 28.2],
from30to39: [22.4, 19.5, 18.9, 17.7],
from40to59: [23.6, 12.7, 11.5, 6.4],
over60: [17.1, 2.8, 1.2, 1.0]
};
// Andel med boutgifter >40% av inntekt (40-59% + 60%+) over tid (tabell 14061)
const HEAVY_BURDEN = {
q1: [33.6, 33.4, 32.9, 34.5, 33.8, 33.5, 35.6, 41.8, 42.9, 40.7],
q2: [ 7.3, 7.0, 7.0, 6.3, 8.3, 6.0, 7.6, 12.3, 12.5, 15.5],
q3: [ 4.2, 3.1, 4.2, 3.9, 3.2, 3.6, 3.6, 6.0, 9.3, 12.7],
q4: [ 2.5, 1.2, 1.9, 2.5, 2.5, 3.5, 2.5, 4.6, 5.9, 7.4]
};
// Eierskap etter inntektsgruppe og utsatte grupper, 2024 (tabell 14067)
const OWNERSHIP_GROUPS = [
{ name: 'Sosialhjelpsmottakere', selveier: 13.9, andel: 4.6, leier: 81.5 },
{ name: 'Lavinntekt (EU 60 %)', selveier: 21.2, andel: 10.8, leier: 68.0 },
{ name: 'Laveste kvartil', selveier: 33.2, andel: 12.6, leier: 54.3 },
{ name: 'Barnefamilier lav inntekt', selveier: 39.0, andel: 8.5, leier: 52.5 },
{ name: 'Aleneboende eldre lav innt.', selveier: 52.4, andel: 23.9, leier: 23.7 },
{ name: 'Uføretrygdede', selveier: 55.6, andel: 13.7, leier: 30.7 },
{ name: 'Andre kvartil', selveier: 60.3, andel: 18.2, leier: 21.5 },
{ name: 'Tredje kvartil', selveier: 70.2, andel: 15.6, leier: 14.2 },
{ name: 'Høyeste kvartil', selveier: 79.7, andel: 13.6, leier: 6.7 },
];
// --- Formattering ---
function fmtPct(n) {
return n.toLocaleString('nb-NO', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '\u202f%';
}
const GRID_COLOR = 'rgba(0,0,0,0.06)';
const TICK_COLOR = '#8a857e';
function baseOpts() {
return {
responsive: true, maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#1a1714',
titleColor: '#f5f2eb',
bodyColor: 'rgba(245,242,235,0.7)',
padding: 10,
cornerRadius: 4,
titleFont: { family: 'DM Sans', size: 12 },
bodyFont: { family: 'DM Sans', size: 12 }
}
},
scales: {
x: {
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, maxRotation: 0 },
grid: { display: false },
border: { color: 'rgba(0,0,0,0.1)' }
},
y: {
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, callback: v => v + ' %' },
grid: { color: GRID_COLOR },
border: { dash: [3, 3], color: 'transparent' }
}
}
};
}
// --- Diagram 1: Boutgifter som andel av inntekt (stablet horisontal) ---
var chart1Opts = baseOpts();
chart1Opts.indexAxis = 'y';
chart1Opts.scales = {
x: {
stacked: true,
max: 100,
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, callback: v => v + ' %' },
grid: { color: GRID_COLOR },
border: { dash: [3, 3], color: 'transparent' }
},
y: {
stacked: true,
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, autoSkip: false },
grid: { display: false },
border: { color: 'rgba(0,0,0,0.1)' }
}
};
chart1Opts.plugins.tooltip = {
mode: 'index',
callbacks: {
label: function(c) {
return ' ' + c.dataset.label + ': ' + fmtPct(c.parsed.x);
}
},
backgroundColor: '#1a1714',
titleColor: '#f5f2eb',
bodyColor: 'rgba(245,242,235,0.7)',
padding: 10,
cornerRadius: 4,
titleFont: { family: 'DM Sans', size: 12 },
bodyFont: { family: 'DM Sans', size: 12 }
};
new Chart(document.getElementById('chart1'), {
type: 'bar',
data: {
labels: QUARTILE_LABELS,
datasets: [
{ label: 'Under 10 %', data: HOUSING_BURDEN.under10, backgroundColor: '#2c6e49cc', borderColor: '#2c6e49', borderWidth: 1, borderRadius: 1 },
{ label: '1019 %', data: HOUSING_BURDEN.from10to19, backgroundColor: '#27ae60cc', borderColor: '#27ae60', borderWidth: 1, borderRadius: 1 },
{ label: '2029 %', data: HOUSING_BURDEN.from20to29, backgroundColor: '#e67e22cc', borderColor: '#e67e22', borderWidth: 1, borderRadius: 1 },
{ label: '3039 %', data: HOUSING_BURDEN.from30to39, backgroundColor: '#e74c3ccc', borderColor: '#e74c3c', borderWidth: 1, borderRadius: 1 },
{ label: '4059 %', data: HOUSING_BURDEN.from40to59, backgroundColor: '#c0392bcc', borderColor: '#c0392b', borderWidth: 1, borderRadius: 1 },
{ label: '60 %+', data: HOUSING_BURDEN.over60, backgroundColor: '#8b1a1acc', borderColor: '#8b1a1a', borderWidth: 1, borderRadius: 1 }
]
},
options: chart1Opts
});
// --- Diagram 2: Tung boutgiftsbyrde over tid (linje) ---
function buildLegend(containerId, items) {
var container = document.getElementById(containerId);
items.forEach(function(item) {
var span = document.createElement('span');
var swatch = document.createElement('span');
swatch.className = 'swatch';
swatch.style.background = item.color;
span.appendChild(swatch);
span.appendChild(document.createTextNode(item.name));
container.appendChild(span);
});
}
var chart2Lines = [
{ name: 'Laveste kvartil', data: HEAVY_BURDEN.q1, color: '#c0392b' },
{ name: 'Andre kvartil', data: HEAVY_BURDEN.q2, color: '#e67e22' },
{ name: 'Tredje kvartil', data: HEAVY_BURDEN.q3, color: '#1a4a8a' },
{ name: 'Høyeste kvartil', data: HEAVY_BURDEN.q4, color: '#2c6e49' }
];
buildLegend('legend2', chart2Lines);
var chart2Opts = baseOpts();
chart2Opts.scales.y.max = 50;
chart2Opts.plugins.tooltip = {
callbacks: {
label: function(c) {
return ' ' + c.dataset.label + ': ' + fmtPct(c.parsed.y);
}
},
backgroundColor: '#1a1714',
titleColor: '#f5f2eb',
bodyColor: 'rgba(245,242,235,0.7)',
padding: 10,
cornerRadius: 4,
titleFont: { family: 'DM Sans', size: 12 },
bodyFont: { family: 'DM Sans', size: 12 }
};
new Chart(document.getElementById('chart2'), {
type: 'line',
data: {
labels: YEARS,
datasets: chart2Lines.map(function(line) {
return {
label: line.name,
data: line.data,
borderColor: line.color,
backgroundColor: line.color + '12',
fill: false,
tension: 0.3,
pointRadius: 0,
pointHoverRadius: 4,
borderWidth: 2.5
};
})
},
options: chart2Opts
});
// --- Diagram 3: Eierskap etter inntektsgruppe (horisontal stablet) ---
var chart3Opts = baseOpts();
chart3Opts.indexAxis = 'y';
chart3Opts.scales = {
x: {
stacked: true,
max: 100,
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, callback: v => v + ' %' },
grid: { color: GRID_COLOR },
border: { dash: [3, 3], color: 'transparent' }
},
y: {
stacked: true,
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, autoSkip: false },
grid: { display: false },
border: { color: 'rgba(0,0,0,0.1)' }
}
};
chart3Opts.plugins.tooltip = {
mode: 'index',
callbacks: {
label: function(c) {
return ' ' + c.dataset.label + ': ' + fmtPct(c.parsed.x);
}
},
backgroundColor: '#1a1714',
titleColor: '#f5f2eb',
bodyColor: 'rgba(245,242,235,0.7)',
padding: 10,
cornerRadius: 4,
titleFont: { family: 'DM Sans', size: 12 },
bodyFont: { family: 'DM Sans', size: 12 }
};
new Chart(document.getElementById('chart3'), {
type: 'bar',
data: {
labels: OWNERSHIP_GROUPS.map(function(g) { return g.name; }),
datasets: [
{
label: 'Selveier',
data: OWNERSHIP_GROUPS.map(function(g) { return g.selveier; }),
backgroundColor: '#2c6e49cc',
borderColor: '#2c6e49',
borderWidth: 1,
borderRadius: 2
},
{
label: 'Andelseier',
data: OWNERSHIP_GROUPS.map(function(g) { return g.andel; }),
backgroundColor: '#1a4a8acc',
borderColor: '#1a4a8a',
borderWidth: 1,
borderRadius: 2
},
{
label: 'Leier',
data: OWNERSHIP_GROUPS.map(function(g) { return g.leier; }),
backgroundColor: '#c0392bcc',
borderColor: '#c0392b',
borderWidth: 1,
borderRadius: 2
}
]
},
options: chart3Opts
});