Sve što želite znati o državi i javnim politikama u hrvatskoj
  • Knjiga
  • Vodič
  • Resursi
  • Alat
  • Podaci
  • Twitter
  • Facebook
  • LinkedIn
  1. Appendices
  2. Podaci

Appendix D — Podaci

DJP-ATLAS
Indeks pokazatelja javnih financija
Rashodi i prihodi
01.1Rashodi opće države% BDP
01.2Prihodi opće države% BDP
01.3Funkcionalna struktura rashoda (COFOG)% BDP
Porezi i doprinosi
02.1Ukupni porezi i doprinosi% BDP
02.2Tax wedge (OECD)u pripremi
02.3Struktura poreza po vrstamau pripremi
Socijalna zaštita
03.1Izdaci za socijalnu zaštituu pripremi
RASHODI I PRIHODI — 01.1

Rashodi opće države, % BDP-a

Karta
Vremenska serija
Tablica
% BDP
Rashodi opće države
Izvor — Eurostat, gov_10a_main. Sektor S13 (opća država).
expenditure = FileAttachment("data/atlas/expenditure_pct_gdp.csv").csv({typed: true})
revenue     = FileAttachment("data/atlas/revenue_pct_gdp.csv").csv({typed: true})
taxes       = FileAttachment("data/atlas/taxes_pct_gdp.csv").csv({typed: true})
cofog       = FileAttachment("data/atlas/cofog_pct_gdp.csv").csv({typed: true})
indicators = ({
  expenditure: {
    title: "Rashodi opće države, % BDP-a",
    crumb: "RASHODI I PRIHODI — 01.1",
    legend: "Rashodi opće države",
    source: "Izvor — Eurostat, gov_10a_main (TE, S13).",
    kind: "timeseries",
    data: expenditure
  },
  revenue: {
    title: "Prihodi opće države, % BDP-a",
    crumb: "RASHODI I PRIHODI — 01.2",
    legend: "Prihodi opće države",
    source: "Izvor — Eurostat, gov_10a_main (TR, S13).",
    kind: "timeseries",
    data: revenue
  },
  taxes: {
    title: "Ukupni porezi i doprinosi, % BDP-a",
    crumb: "POREZI I DOPRINOSI — 02.1",
    legend: "Porezi + socijalni doprinosi",
    source: "Izvor — Eurostat, gov_10a_main (D2REC + D5REC + D91REC + D61REC).",
    kind: "timeseries-sum",
    data: taxes
  },
  cofog: {
    title: "Funkcionalna struktura rashoda (COFOG), % BDP-a",
    crumb: "RASHODI I PRIHODI — 01.3",
    legend: "Rashodi po funkcijama — najnovija godina",
    source: "Izvor — Eurostat, gov_10a_exp (COFOG, S13).",
    kind: "cofog",
    data: cofog
  }
})
mutable active = document.querySelector(".atlas-rail__item.is-active")?.dataset.indicator || "expenditure"
{
  const handler = (e) => {
    const el = e.target.closest(".atlas-rail__item[data-indicator]");
    if (!el || el.classList.contains("is-disabled")) return;
    document.querySelectorAll(".atlas-rail__item").forEach(n => n.classList.remove("is-active"));
    el.classList.add("is-active");
    mutable active = el.dataset.indicator;
  };
  document.addEventListener("click", handler);
  invalidation.then(() => document.removeEventListener("click", handler));
}
spec = indicators[active]
// Sync header chrome (title, breadcrumb, legend) with the active indicator.
{
  document.getElementById("atlas-title").textContent = spec.title;
  document.getElementById("atlas-breadcrumb").textContent = spec.crumb;
  document.getElementById("atlas-legend-title").textContent = spec.legend;
  document.getElementById("atlas-source").textContent = spec.source;
}
// Aggregate tax components per (geo, year) for the "taxes" indicator.
aggregated = {
  if (spec.kind === "timeseries-sum") {
    const m = new Map();
    for (const r of spec.data) {
      const k = r.geo + "|" + r.year;
      m.set(k, (m.get(k) || 0) + (+r.value || 0));
    }
    return Array.from(m, ([k, v]) => {
      const [geo, year] = k.split("|");
      return { geo, year: +year, value: v };
    });
  }
  if (spec.kind === "timeseries") return spec.data;
  return spec.data;
}
// Peer-country picker (only shown for time-series indicators).
mutable peer = "SI"
{
  const sel = document.getElementById("atlas-peer");
  if (sel) {
    const h = () => { mutable peer = sel.value; };
    sel.addEventListener("change", h);
    invalidation.then(() => sel.removeEventListener("change", h));
  }
}
{
  // Hide peer selector for COFOG view (single-year snapshot, no peer comparison).
  const tb = document.getElementById("atlas-toolbar");
  if (tb) tb.style.visibility = (spec.kind === "cofog") ? "hidden" : "visible";
}
mutable view = "chart"
{
  const handler = (e) => {
    const el = e.target.closest(".atlas-panel__tabs .tab[data-view]");
    if (!el || el.classList.contains("is-disabled")) return;
    document.querySelectorAll(".atlas-panel__tabs .tab").forEach(n => n.classList.remove("is-active"));
    el.classList.add("is-active");
    mutable view = el.dataset.view;
  };
  document.addEventListener("click", handler);
  invalidation.then(() => document.removeEventListener("click", handler));
}
{
  // Swap viz / table containers and hide the legend in table view.
  const viz = document.getElementById("atlas-viz-wrap");
  const tbl = document.getElementById("atlas-table-wrap");
  if (viz && tbl) {
    const isTable = view === "table";
    viz.style.display = isTable ? "none" : "";
    tbl.style.display = isTable ? "" : "none";
  }
}
// Pivot aggregated data to a wide table: rows = year (or COFOG), cols = geo.
table = {
  const wrap = document.getElementById("atlas-table-wrap");
  if (!wrap) return null;
  wrap.innerHTML = "";

  const isCofog = spec.kind === "cofog";
  const labels = {
    GF01:"Opće javne usluge", GF02:"Obrana", GF03:"Javni red", GF04:"Ekonomske djelatnosti",
    GF05:"Zaštita okoliša", GF06:"Stanovanje", GF07:"Zdravstvo", GF08:"Kultura i sport",
    GF09:"Obrazovanje", GF10:"Socijalna zaštita"
  };

  // Determine row key and column key.
  const rowKey = isCofog ? "cofog99" : "year";
  const colKey = "geo";
  const rows = new Map();
  const cols = new Set();
  for (const d of aggregated) {
    const rk = d[rowKey];
    const ck = d[colKey];
    cols.add(ck);
    if (!rows.has(rk)) rows.set(rk, {});
    rows.get(rk)[ck] = d.value;
  }
  const colsArr = Array.from(cols).sort((a, b) => {
    const order = ["HR", "EU27_2020"];
    const ai = order.indexOf(a), bi = order.indexOf(b);
    if (ai !== -1 || bi !== -1) return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
    return a.localeCompare(b);
  });
  const rowsArr = Array.from(rows.keys()).sort((a, b) =>
    isCofog ? String(a).localeCompare(String(b)) : (+a) - (+b)
  );

  const fmt = v => (v == null || Number.isNaN(+v)) ? "—" : (+v).toFixed(1);
  const rowLabel = r => isCofog ? `${r} · ${labels[r] || ""}` : r;
  const colLabel = c => c === "EU27_2020" ? "EU27" : c;

  const html = `
    <table>
      <thead>
        <tr>
          <th>${isCofog ? "Funkcija" : "Godina"}</th>
          ${colsArr.map(c => `<th>${colLabel(c)}</th>`).join("")}
        </tr>
      </thead>
      <tbody>
        ${rowsArr.map(r => `
          <tr>
            <td>${rowLabel(r)}</td>
            ${colsArr.map(c => `<td>${fmt(rows.get(r)[c])}</td>`).join("")}
          </tr>
        `).join("")}
      </tbody>
    </table>
  `;
  wrap.innerHTML = html;
  return rowsArr.length;
}
// Country centroids (lon/lat) used for the Karta view.
countryCentroid = ({
  HR: [15.2, 45.1], DE: [10.4, 51.0], SI: [14.8, 46.1], HU: [19.5, 47.2],
  AT: [14.5, 47.5], IT: [12.6, 41.9], PL: [19.4, 52.1], CZ: [15.5, 49.8],
  SK: [19.7, 48.7], RO: [24.9, 45.9], BG: [25.5, 42.7], FR: [2.2, 46.6],
  ES: [-3.7, 40.4], NL: [5.3, 52.1], SE: [18.6, 60.1], DK: [9.5, 56.0]
})
chart = {
  const root = document.getElementById("atlas-chart");
  root.innerHTML = "";

  const ink       = "#1C1916";
  const verdigris = "#4A6B5C";
  const ochre     = "#C8985E";
  const slate     = "#6B6357";

  // --- Karta (map) view: organic blobs per country, Statecraft style ---
  if (view === "map") {
    if (spec.kind === "cofog") {
      root.innerHTML = `<div style="padding:3rem; text-align:center; color:#6B6357; font-style:italic;">
        Karta nije primjenjiva za funkcionalnu strukturu rashoda.
      </div>`;
      return null;
    }

    const W = root.clientWidth || 900, H = 460;
    // Project lon/lat → x/y using a stretched equirectangular fit to Europe.
    const lonExt = [-12, 32], latExt = [34, 66];
    const project = (lon, lat) => [
      40 + ((lon - lonExt[0]) / (lonExt[1] - lonExt[0])) * (W - 80),
      30 + ((latExt[1] - lat) / (latExt[1] - latExt[0])) * (H - 60)
    ];

    // Names for label
    const names = {
      HR:"Hrvatska", DE:"Njemačka", SI:"Slovenija", HU:"Mađarska", AT:"Austrija",
      IT:"Italija", PL:"Poljska", CZ:"Češka", SK:"Slovačka", RO:"Rumunjska",
      BG:"Bugarska", FR:"Francuska", ES:"Španjolska", NL:"Nizozemska",
      SE:"Švedska", DK:"Danska"
    };

    const latest = Math.max(...aggregated.map(d => +d.year));
    const rows = aggregated
      .filter(d => +d.year === latest && countryCentroid[d.geo])
      .map(d => ({geo: d.geo, value: +d.value, lon: countryCentroid[d.geo][0], lat: countryCentroid[d.geo][1]}));
    const vmin = Math.min(...rows.map(d => d.value));
    const vmax = Math.max(...rows.map(d => d.value));

    // Sequential green ramp (paper → verdigris-deep).
    const ramp = ["#E8EFE9", "#C8D5CB", "#9CB7A4", "#6E9580", "#4A6B5C", "#3A5648"];
    const colorOf = v => {
      const t = (v - vmin) / (vmax - vmin || 1);
      const i = Math.min(ramp.length - 1, Math.floor(t * ramp.length));
      return ramp[i];
    };
    const radiusOf = v => 26 + 26 * ((v - vmin) / (vmax - vmin || 1));

    // Deterministic PRNG so each country's blob is stable across re-renders.
    const mulberry32 = (s) => () => {
      s |= 0; s = (s + 0x6D2B79F5) | 0;
      let t = Math.imul(s ^ (s >>> 15), 1 | s);
      t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
    const seedOf = (s) => { let h = 2166136261; for (const c of s) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619); } return h >>> 0; };

    // Catmull-Rom closed → smooth cubic Bezier path through points.
    const blobPath = (cx, cy, r, seed) => {
      const rand = mulberry32(seed);
      const n = 10;
      const pts = [];
      for (let i = 0; i < n; i++) {
        const ang = (i / n) * Math.PI * 2 + (rand() - 0.5) * 0.25;
        const rr = r * (0.78 + rand() * 0.42);
        // Squash slightly horizontally for a more "country-like" feel.
        pts.push([cx + Math.cos(ang) * rr * 1.15, cy + Math.sin(ang) * rr * 0.9]);
      }
      let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
      for (let i = 0; i < n; i++) {
        const p0 = pts[(i - 1 + n) % n], p1 = pts[i], p2 = pts[(i + 1) % n], p3 = pts[(i + 2) % n];
        const c1x = p1[0] + (p2[0] - p0[0]) / 6, c1y = p1[1] + (p2[1] - p0[1]) / 6;
        const c2x = p2[0] - (p3[0] - p1[0]) / 6, c2y = p2[1] - (p3[1] - p1[1]) / 6;
        d += ` C${c1x.toFixed(1)},${c1y.toFixed(1)} ${c2x.toFixed(1)},${c2y.toFixed(1)} ${p2[0].toFixed(1)},${p2[1].toFixed(1)}`;
      }
      return d + " Z";
    };

    const svgNS = "http://www.w3.org/2000/svg";
    const svg = document.createElementNS(svgNS, "svg");
    svg.setAttribute("viewBox", `0 0 ${W} ${H}`);
    svg.setAttribute("width", "100%");
    svg.setAttribute("height", H);
    svg.style.fontFamily = "Public Sans, sans-serif";

    // Draw blobs ordered largest → smallest so smaller ones overlay nicely.
    rows.sort((a, b) => b.value - a.value).forEach(d => {
      const [x, y] = project(d.lon, d.lat);
      const r = radiusOf(d.value);
      const path = document.createElementNS(svgNS, "path");
      path.setAttribute("d", blobPath(x, y, r, seedOf(d.geo)));
      path.setAttribute("fill", colorOf(d.value));
      path.setAttribute("stroke", ink);
      path.setAttribute("stroke-width", "0.8");
      path.setAttribute("stroke-opacity", "0.55");
      svg.appendChild(path);

      const label = document.createElementNS(svgNS, "text");
      label.setAttribute("x", x);
      label.setAttribute("y", y);
      label.setAttribute("text-anchor", "middle");
      label.setAttribute("dominant-baseline", "middle");
      label.setAttribute("font-size", "11");
      label.setAttribute("fill", ink);
      label.textContent = names[d.geo] || d.geo;
      svg.appendChild(label);
    });

    root.appendChild(svg);
    return svg;
  }

  let plot;
  if (spec.kind === "cofog") {
    const labels = {
      GF01:"Opće javne usluge", GF02:"Obrana", GF03:"Javni red", GF04:"Ekonomske djelatnosti",
      GF05:"Zaštita okoliša", GF06:"Stanovanje", GF07:"Zdravstvo", GF08:"Kultura i sport",
      GF09:"Obrazovanje", GF10:"Socijalna zaštita"
    };
    const latest = Math.max(...aggregated.map(d => +d.year));
    const rows = aggregated
      .filter(d => +d.year === latest)
      .map(d => ({...d, fn: labels[d.cofog99] || d.cofog99}));

    plot = Plot.plot({
      style: {background: "transparent", color: "#1C1916"},
      marginLeft: 170, marginRight: 30, height: 460,
      x: {label: "% BDP-a", grid: true},
      y: {label: null, domain: Object.values(labels)},
      color: {domain: ["HR","EU27_2020"], range: [verdigris, ochre], legend: true},
      marks: [
        Plot.barX(rows, {
          y: "fn", x: "value", fill: "geo",
          fy: null, sort: {y: "x", reverse: true},
          dx: 0
        }),
        Plot.ruleX([0], {stroke: ink})
      ]
    });
  } else {
    const focusGeos = new Set(["HR", "EU27_2020", peer]);
    const rows = aggregated.filter(d => focusGeos.has(d.geo));
    plot = Plot.plot({
      style: {background: "transparent", color: "#1C1916"},
      marginLeft: 50, marginRight: 30, marginBottom: 36, height: 440,
      x: {label: null, tickFormat: d => "" + d, grid: false},
      y: {label: "% BDP-a", grid: true},
      color: {
        domain: ["HR","EU27_2020", peer],
        range: [verdigris, ink, ochre],
        legend: true
      },
      marks: [
        Plot.ruleY([0], {stroke: slate, strokeOpacity: 0.4}),
        Plot.lineY(rows, {
          x: "year", y: "value", stroke: "geo",
          strokeWidth: d => d.geo === "HR" ? 2.6 : 1.5,
          curve: "monotone-x"
        }),
        Plot.dot(rows.filter(d => d.geo === "HR" && d.year === Math.max(...rows.map(r => +r.year))),
          {x: "year", y: "value", fill: verdigris, r: 4})
      ]
    });
  }
  root.appendChild(plot);
  return plot;
}
// Wire the download button to dump the active CSV.
{
  const btn = document.getElementById("atlas-download");
  if (btn) {
    btn.onclick = () => {
      const rows = aggregated;
      const cols = Object.keys(rows[0] || {});
      const csv = [cols.join(",")]
        .concat(rows.map(r => cols.map(c => JSON.stringify(r[c] ?? "")).join(",")))
        .join("\n");
      const blob = new Blob([csv], {type: "text/csv"});
      const url = URL.createObjectURL(blob);
      const a = Object.assign(document.createElement("a"), {href: url, download: `${active}.csv`});
      a.click(); URL.revokeObjectURL(url);
    };
  }
}
 

© 2026 Deskar-Škrbić, Palić, Šikić | Javne politike u Hrvatskoj