function Range(range, options = {}) {
  const [min, max] = range;
  const {
    className = "Range",
    vertical = false,
    label = null,
    format = (x) => +x,
    step = 1,
    value = (min + max) / 2,
    style = "",
    labelStyle = "",
    rangeStyle = "",
    valueStyle = ""
  } = options;
  const rangeWrap = htl.html`<div class=${className} style="${style}"></div>`;
  Object.assign(rangeWrap.style, {
    display: "inline-flex",
    position: "relative",
    userSelect: "none"
  });
  const valueDisplay = htl.html`<output style="${valueStyle}">`;
  Object.assign(valueDisplay.style, {
    display: "inline-block"
  });
  const rangeInput = htl.html`<input type=range min=${min} max=${max} step=${step} value=${value} style=${rangeStyle}>`;
  Object.assign(rangeInput.style, {
    display: "inline-block"
  });
  if (vertical) {
    rangeInput.setAttribute("orient", "vertical");
    rangeInput.style.writingMode = "bt-lr"; /* IE */
    rangeInput.style["-webkit-appearance"] = "slider-vertical"; /* WebKit */
    rangeInput.style.width = "8px";
  }

  rangeWrap.append(rangeInput, valueDisplay);

  if (label) rangeWrap.prepend(htl.html`<label style=${labelStyle}>${label}`);

  rangeInput.oninput = () => {
    valueDisplay.innerHTML = format(rangeInput.valueAsNumber);
    rangeWrap.value = rangeWrap.valueAsNumber = +rangeInput.valueAsNumber;
    rangeWrap.dispatchEvent(new CustomEvent("input"));
  };

  rangeInput.oninput();
  return rangeWrap;
}

function extractDataForYear(data, year) {
  if (!data || typeof data !== 'object') return null;

  // Recursive function to search through the children
  function recursiveSearch(node) {
    if (!node || typeof node !== 'object') return null;
    
    // If the node has children, process them
    if (node.children) {
      let filteredChildren = node.children.map(child => recursiveSearch(child)).filter(child => child !== null);
      // If filtered children are found, return them within the same structure
      if (filteredChildren.length > 0) {
        return { ...node, children: filteredChildren };
      }
    }

    // If the node itself contains the year we're looking for, return it
    if (node.year === year) {
      return node;
    }

    // If neither children nor the node itself match, return null
    return null;
  }

  return recursiveSearch(data);
}

function highlighter(text, view, highlight) {
  const el =
    typeof text === 'string'
      ? html`<span style="color: #f54000; border-bottom: dotted 1px currentColor; cursor: pointer;${
          highlight ? 'background: rgba(245, 64, 0, .2);' : ''
        }">${text}</span>`
      : text;
  el.addEventListener('mouseover', () => {
    view.value = true;
  });
  el.addEventListener('mouseout', () => {
    view.value = false;
  });
  return el;
}

scaleValue = 4000000

function getBarValues(title) {
  const value = "EUR " + d3.format(",.0~f")(dataGalaxy.filter(d => d.title == title).map(d => d.value/1000000)[0]) + " m"
  const percent = d3.format(",.1~f")(dataGalaxy.filter(d => d.title == title).map(d => d.percent)[0]) + "% of total revenue"
  const percentOther = d3.format(",.1~f")(
    dataGalaxy.filter(d => d.title == title).map(d => d.value/1000000)[0] /
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.value/1000000)) *100
  ) + "% of other revenue"
  const changeValue = dataGalaxy.filter(d => d.title == title).map(d => d.changeValue)
  const change = (changeValue === undefined || isNaN(changeValue) || changeValue === null || changeValue > 10000) ? 
      "<span style='color:gray'>no data in previous year</span>" :
      changeValue > 0 ? 
      "change to previous year " + "<span style='color:green'>" + "↗ " + d3.format("+,.1~f")(changeValue) + "%" + "</span>" :
      "change to previous year " + "<span style='color:red'>" + "↘ " + d3.format("+,.1~f")(changeValue) + "%" + "</span>"
  const temp = "<br>" + value + "<br>" + percent + "<br>" + percentOther + "<br>" + change
  return temp
}

function getBarValuesAssigned() {
  const value = "EUR " + d3.format(",.0~f")(
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.assigned/1000000))
  ) + " m"
  const percent = d3.format(",.1~f")(
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.assigned/1000000)) /
    d3.sum(dataGalaxy.map(d => d.value/1000000)) * 100
  ) + "% of total revenue"
  const percentOther = d3.format(",.1~f")(
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.assigned/1000000)) /
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.value/1000000)) * 100
  ) + "% of other revenue"
  const temp = "<br>" + value + "<br>" + percent + "<br>" + percentOther
  return temp
}

function getBarValuesNonAssigned() {
  const value = "EUR " + d3.format(",.0~f")(
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.nonAssigned/1000000))
  ) + " m"
  const percent = d3.format(",.1~f")(
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.nonAssigned/1000000)) /
    d3.sum(dataGalaxy.map(d => d.value/1000000)) * 100
  ) + "% of total revenue"
  const percentOther = d3.format(",.1~f")(
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.nonAssigned/1000000)) /
    d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d.value/1000000)) * 100
  ) + "% of other revenue"
  const temp = "<br>" + value + "<br>" + percent + "<br>" + percentOther
  return temp
}

function getBarValuesOwn(title) {
  const value = "EUR " + d3.format(",.0~f")(
    d3.sum(dataGalaxy.filter(d => d.title == title).map(d => d.value/1000000))
  ) + " m"
  const percent = d3.format(",.1~f")(
    dataGalaxy.filter(d => d.title == title).map(d => d.value/1000000) /
    d3.sum(dataGalaxy.map(d => d.value/1000000)) * 100
  ) + "% of total revenue"
  const changeValue = dataGalaxy.filter(d => d.title == title).map(d => d.changeValue)
  const change = (changeValue === undefined || isNaN(changeValue) || changeValue === null || changeValue > 10000) ? 
      "<span style='color:gray'>no data in previous year</span>" :
      changeValue > 0 ? 
      "change to previous year " + "<span style='color:green'>" + "↗ " + d3.format("+,.1~f")(changeValue) + "%" + "</span>" :
      "change to previous year " + "<span style='color:red'>" + "↘ " + d3.format("+,.1~f")(changeValue) + "%" + "</span>"
  const temp = "<br>" + value + "<br>" + percent + "<br>" + change
  return temp
}

function getBarValuesOwnSum() {
  const value = "EUR " + d3.format(",.0~f")(
    d3.sum(dataGalaxy.filter(d => d.title == 11 || d.title == 13 || d.title == 14 || d.title == 17).map(d => d.value/1000000))
  ) + " m"
  const percent = d3.format(",.1~f")(
    d3.sum(dataGalaxy.filter(d => d.title == 11 || d.title == 13 || d.title == 14 || d.title == 17).map(d => d.value/1000000)) /
    d3.sum(dataGalaxy.map(d => d.value/1000000)) * 100
  ) + "%"
  const temp = "<br>" + value + "<br>" + percent
  return temp
}

function getRadius(title) {
  const temp = Math.sqrt(dataGalaxy.filter(d => d.title == title).map(d => d.value/scaleValue)[0])
  return temp
}

function getRadiusAssigned(title) {
  const temp = Math.sqrt(d3.sum(dataGalaxy.filter(d => d.title != 11 && d.title != 13 && d.title != 14 && d.title != 17).map(d => d[title]/scaleValue)))
  return temp
}

function progressBar(percentage) {
    const totalBars = 20; // Total number of bars in the progress bar
    const filledBars = Math.round((percentage / 100) * totalBars);
    const emptyBars = totalBars - filledBars;
    
    const filled = "▓".repeat(filledBars); // Lighter shade for the filled part
    const empty = "░".repeat(emptyBars); // Light shade for the empty part
    
    // return `[${filled}${empty}] ${d3.format(",.1~f")(percentage)}%`;
    return `${filled}${empty}`;
}
colorsWIFO = [
  "#72bb6f",
  "#559bd5",
  "#f3d039",
  "#c3423f",
  "#12a7e7",
  "#96b428",
  "#559e8f",
  "#f3d039",
  "#559e8f",
  "#2d5b5e"
]
colors = [
  "#0472c3",  // Existing blue
  "#ed8a04",  // Existing orange
  "#08818f",  // Existing teal
  "#f54000",  // Existing red
  "#82b74b",  // Complementary green
  "#ffdd44",  // Complementary yellow
  "#7a42f4",  // Complementary purple
  "#e91e63",  // Complementary pink
]
colorsShift = [
  "#ed8a04",  // Existing orange
  "#08818f",  // Existing teal
  "#f54000",  // Existing red
  "#82b74b",  // Complementary green
  "#ffdd44",  // Complementary yellow
  "#7a42f4",  // Complementary purple
  "#e91e63",  // Complementary pink
]
colorsBlue = [
  "#82b74b",
  "#024e82",  // Darker Shade 2
  "#0472c3",  // Original Color
  "#3995d3",  // Lighter Shade 1
  "#6ea9e3",  // Lighter Shade 2
  "#a3c7f3",  // Lighter Shade 3
  "#0459c3",  // Analogous Color 1
  "#0460c3",  // Analogous Color 2
  "#0483c3"   // Analogous Color 3
]
config = ({
  view: {stroke: null}, 
  padding: {
    left: 5, 
    top: 5, 
    right: 5, 
    bottom: 20
    },
  font: "Noto Sans",
  fontSize: 14, 
  title: {
    offset: 0, 
    fontSize: 16, 
    subtitleFontSize: 14,
  },
  facet: {
    fontSize: 14,
    labelFontSize: 14, 
    titleFontSize: 14,
    titleFontWeight: "normal",
  },
  axis: {
    labelFontSize: 14, 
    titleFontSize: 14,
    titleFontWeight: "normal",
  },
  legend: {
    labelFontSize: 14, 
    titleFontSize: 14,
    titleFontWeight: "normal",
  },
  mark: {
    fontSize: 14
  },
  locale: {
    number: {
      currency: ["EUR ", " m"],
      decimal: ".", 
      thousands: ",", 
      grouping: [3]
    },
    time: {
      dateTime: "%A %e %B %Y, %X",
      date: "%d/%m/%Y",
      time: "%H:%M:%S",
      periods: ["AM", "PM"],
      days: [
        "Montag",
        "Dienstag",
        "Mittwoch",
        "Donnerstag",
        "Freitag",
        "Samstag",
        "Sonntag"
      ],
      shortDays: [
        "Mo", 
        "Di", 
        "Mi", 
        "Do", 
        "Fr", 
        "Sa", 
        "So"
      ],
      months: [
        "Jänner",
        "Februar",
        "März",
        "April",
        "Mai",
        "Juni",
        "Juli",
        "August",
        "September",
        "Oktober",
        "November",
        "Dezember"
      ],
      shortMonths: [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "Mai",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Okt",
        "Nov",
        "Dez"
      ]
    }
  },
})
import { View } from '@mbostock/synchronized-views'
dataGalaxyRaw = FileAttachment("data/dataGalaxy.csv").csv({ typed: true })
dataGalaxy = dataGalaxyRaw.filter(d => d.year == range)
dataRaw = FileAttachment("data/nestedData.json").json()
dataRawSmall = FileAttachment("data/nestedDataSmall.json").json()
data = extractDataForYear(radios == "Total Revenue" ? dataRaw : dataRawSmall, range.toString())
viewof highlight1 = new View(false)
viewof highlight2 = new View(false)
  • OTHER REVENUE AT A GLANCE
  • REVENUE DETAILS
viewof radios = Inputs.radio(
    ["Total Revenue", "Other Revenue"], 
    {label: "", value: "Other Revenue"}
  )
viewof range = Inputs.range(
    [2019, 2023], 
    { style: "padding:12px; width:80%", value: 2023, step: 1, label: "" }
  )

radios === "Total Revenue" ? 
mermaid`
%%{init: {'theme': 'base', 'themeVariables': {'fontFamily': 'Noto Sans', 'fontSize': '30px', 'primaryBorderColor': 'black','primaryColor': 'white'}}}%%
graph TB

  subgraph container[" "]

    member[/"Member States"\] o-- % of the estimated <br>value added tax --> T13["VAT-based"]
    member o-- uniform % of GNI --> T14["GNI-based"]
    member o-- 25% of customs duties --> T11["Traditional Resources"]
    member o-- "0.80 per kilogram or lump sum" --> T17["Plastic based"]
      
    subgraph own["<b>OWN RESOURCES"]
      subgraph T11["Traditional Resources"]
        T11bar(("⠀"))
        T11box["${getBarValuesOwn(11)}</b>"]
      end
      subgraph T13["VAT-based"]
        T13bar(("⠀"))
        T13box["${getBarValuesOwn(13)}"]
      end
      subgraph T14["GNI-based"]
        T14bar(("⠀"))
        T14box["${getBarValuesOwn(14)}"]
      end
      subgraph T17["Plastic based"]
        T17bar(("⠀"))
        T17box["${getBarValuesOwn(17)}"]
      end
    end


    T11 --> B
    T13 --> B
    T14 --> B
    T17 --> B

    style own fill:transparent
    style T11bar r:${getRadius(11)}px,fill:${colors[0]}
    style T13bar r:${getRadius(13)}px,fill:${colors[0]}
    style T14bar r:${getRadius(14)}px,fill:${colors[0]}
    style T17bar r:${getRadius(17)}px,fill:${colors[0]}

    subgraph other["<b>OTHER REVENUE</b> "]
      subgraph T2["Balances and Adjustments"]
        T2bar(("  "))
        T2box["${getBarValues(2)}"]
      end
      subgraph T3["Administrative Revenue"]
        T3bar(("⠀"))
        T3box["${getBarValues(3)}"]
      end
      subgraph T4["Financial Revenue, Default Interest, Fines"]
        T4bar(("⠀"))
        T4box["${getBarValues(4)}"]
      end
      subgraph T5["Budgetary Guarantees, Borrowing-and-Lending Operations"]
        T5bar(("⠀"))
        T5box["${getBarValues(5)}"]
      end
      subgraph T6["Revenue, Contributions and Refunds Related to Union Policies"]
        T6bar(("⠀"))
        T6box["${getBarValues(6)}"]
      end
    end

    style T2bar r:${getRadius(2)}px,fill:${colors[1]}
    style T3bar r:${getRadius(3)}px,fill:${colors[2]}
    style T4bar r:${getRadius(4)}px,fill:${colors[3]}
    style T5bar r:${getRadius(5)}px,fill:${colors[4]}
    style T6bar r:${getRadius(6)}px,fill:${colors[5]}

    companies[/"Companies"\]
    last(("Previous<br>Financial Year"))
    member[/"Member States"\]
    staff[/"EU Staff"\]
    property[/"EU Property"\]
    third[/"Third Countries"\]
    project(("EU Projects<br>and Funds"))
    fin[/"Financial Institutions"\]

    companies -- fines --> T4
    member -- penalties --> T4
    companies -- purchase of emission allowances --> T6
    member -- contributions --> T6
    project -- adjustments --> T6
    third -- contributions --> T6
    last -- surplus and adjustments --> T2
    staff -- taxes and pension contributions --> T3
    property -- rent and sale --> T3
    fin -- lend --> T5

    subgraph A
      subgraph A1["Assigned Revenue"]
        spacer((" "))
        A1bar(("⠀"))
        A1box["${getBarValuesAssigned()}"]
      end
      subgraph A2["Non-Assigned Revenue"]
        A2bar(("⠀"))
        A2box["${getBarValuesNonAssigned()}"]
      end
    end

    style spacer r:1px,display:none;

    T2 -- "${Math.round(dataGalaxy.filter(d => d.title == 2).map(d => d.percentNonAssigned)[0])}%" --> A2
    T3 -- "${Math.round(dataGalaxy.filter(d => d.title == 3).map(d => d.percentNonAssigned)[0])}%" --> A2
    T4 -- "${Math.round(dataGalaxy.filter(d => d.title == 4).map(d => d.percentNonAssigned)[0])}%" --> A2
    T6 -- "${Math.round(dataGalaxy.filter(d => d.title == 6).map(d => d.percentNonAssigned)[0])}%" --> A2
    T3 -- "${Math.round(dataGalaxy.filter(d => d.title == 3).map(d => d.percentAssigned)[0])}%" --> A1
    T5 -- "${Math.round(dataGalaxy.filter(d => d.title == 5).map(d => d.percentAssigned)[0])}%" --> A1
    T6 -- "${Math.round(dataGalaxy.filter(d => d.title == 6).map(d => d.percentAssigned)[0])}%" --> A1

    B(("
    <span style='color:#FFDD00'>fa:fa-star</span> EU Revenue <span style='color:#FFDD00'>fa:fa-star</span>⠀
      ${
        "<br>" + "EUR " + d3.format(",.0~f")(d3.sum(dataGalaxy.map(d => d.value/1000000))) + " m" + "<br>" + 
        d3.format(",.1~f")(d3.sum(dataGalaxy.map(d => d.percent))) + "%"
      }
    "))

    A2 --> B
    A1 --> B
    A1 -.-> SP(["Special Instruments"])
    A1 -.-> P1
    A1 -.-> P2
    A1 -.-> P3
    A1 -.-> P4
    A1 -.-> P5
    A1 -.-> P6
    A1 -.-> P7
    A1 -.-> OP(["Off-Budget Funds"])

    classDef T stroke:transparent,fill:transparent;
    class T2,T3,T4,T5,T6,T11,T13,T14,T17,A1,A2 T;

    classDef bar stroke:black,stroke-width:2px,fill-opacity:0.7;
    class T2bar,T3bar,T4bar,T5bar,T6bar,T11bar,T13bar,T14bar,T17bar,A1bar,A2bar bar;

    classDef box fill:transparent,stroke:transparent;
    class T2box,T3box,T4box,T5box,T6box,T11box,T13box,T14box,T17box,A1box,A2box box;

    style A1bar r:${getRadiusAssigned("assigned")}px,fill:${colors[6]},stroke:black
    style A2bar r:${getRadiusAssigned("nonAssigned")}px,fill:${colors[7]},stroke:black
    style B r:${Math.sqrt(d3.sum(dataGalaxy.map(d => d.value/scaleValue)))}px,fill:${colors[0]},fill-opacity:0.7,stroke:black

    subgraph policy[" "]
      P1(["Single Market, Innovation and Digital"])
      P2(["Cohesion, Resilience and Values"]) 
      P3(["Natural Resources and Environment"])
      P4(["Migration and Border Management"])
      P5(["Security and Defense"])
      P6(["Neighbourhood and the World"])
      P7(["European Public Administration"])
    end

    style policy fill:transparent,stroke:black,stroke-width:1px
    style A fill:transparent,stroke:transparent,color:transparent
    style container fill:transparent,stroke:transparent
    style other fill:#ededed,fill-opacity:0.7,stroke:black,stroke-width:1px
    style own fill:#ededed,fill-opacity:0.7,stroke:black,stroke-width:1px

  end

  ${highlight1 ? "style B stroke:#333,stroke-width:3px,fill-opacity:0.4" : ""}
`
  :
mermaid`
%%{init: {'theme': 'base', 'themeVariables': {'fontFamily': 'Noto Sans', 'fontSize': '30px', 'primaryBorderColor': 'black','primaryColor': 'white'}, 'flowchart':{'nodeSpacing': 6, 'rankSpacing':'6'}}}%%
graph TB

  subgraph container[" "]

    subgraph other["<b>OTHER REVENUE</b> "]
      subgraph T2["Balances and Adjustments"]
        T2bar(("  "))
        T2box["${getBarValues(2)}"]
      end
      subgraph T3["Administrative Revenue"]
        T3bar(("⠀"))
        T3box["${getBarValues(3)}"]
      end
      subgraph T4["Financial Revenue, Default Interest, Fines"]
        T4bar(("⠀"))
        T4box["${getBarValues(4)}"]
      end
      subgraph T5["Budgetary Guarantees, Borrowing-and-Lending Operations"]
        T5bar(("⠀"))
        T5box["${getBarValues(5)}"]
      end
      subgraph T6["Revenue, Contributions and Refunds Related to Union Policies"]
        T6bar(("⠀"))
        T6box["${getBarValues(6)}"]
      end
    end

    style T2bar r:${getRadius(2)}px,fill:${colors[1]}
    style T3bar r:${getRadius(3)}px,fill:${colors[2]}
    style T4bar r:${getRadius(4)}px,fill:${colors[3]}
    style T5bar r:${getRadius(5)}px,fill:${colors[4]}
    style T6bar r:${getRadius(6)}px,fill:${colors[5]}

    companies[/"Companies"\]
    last(("Previous<br>Financial Year"))
    member[/"Member States"\]
    staff[/"EU Staff"\]
    property[/"EU Property"\]
    third[/"Third Countries"\]
    project(("EU Projects<br>and Funds"))
    fin[/"Financial Institutions"\]

    member -- "<b>OWN RESOURCES ${getBarValuesOwnSum()}</b>" --> boxB

    companies -- fines --> T4
    member -- penalties --> T4
    companies -- purchase of emission allowances --> T6
    member -- contributions --> T6
    project -- adjustments --> T6
    third -- contributions --> T6
    last -- surplus and adjustments --> T2
    staff -- taxes and pension contributions --> T3
    property -- rent and sale --> T3
    fin -- lend --> T5


    subgraph A
      subgraph A1["Assigned Revenue"]
        spacer((" "))
        A1bar(("⠀"))
        A1box["${getBarValuesAssigned()}"]
      end
      subgraph A2["Non-Assigned Revenue"]
        A2bar(("⠀"))
        A2box["${getBarValuesNonAssigned()}"]
      end
    end
    
    style spacer r:1px,display:none;

    T2 -- "${Math.round(dataGalaxy.filter(d => d.title == 2).map(d => d.percentNonAssigned)[0])}%" --> A2
    T3 -- "${Math.round(dataGalaxy.filter(d => d.title == 3).map(d => d.percentNonAssigned)[0])}%" --> A2
    T4 -- "${Math.round(dataGalaxy.filter(d => d.title == 4).map(d => d.percentNonAssigned)[0])}%" --> A2
    T6 -- "${Math.round(dataGalaxy.filter(d => d.title == 6).map(d => d.percentNonAssigned)[0])}%" --> A2
    T3 -- "${Math.round(dataGalaxy.filter(d => d.title == 3).map(d => d.percentAssigned)[0])}%" --> A1
    T5 -- "${Math.round(dataGalaxy.filter(d => d.title == 5).map(d => d.percentAssigned)[0])}%" --> A1
    T6 -- "${Math.round(dataGalaxy.filter(d => d.title == 6).map(d => d.percentAssigned)[0])}%" --> A1

  subgraph boxB[" "]
    B(("
    <span style='color:#FFDD00'>fa:fa-star</span> EU Revenue <span style='color:#FFDD00'>fa:fa-star</span>⠀
      ${
        "<br>" + "EUR " + d3.format(",.0~f")(d3.sum(dataGalaxy.map(d => d.value/1000000))) + " m" + "<br>" + 
        d3.format(",.1~f")(d3.sum(dataGalaxy.map(d => d.percent))) + "%"
      }
    "))
  end

    A2 --> boxB
    A1 --> boxB
    A1 -.-> SP(["Special Instruments"])
    A1 -.-> P1
    A1 -.-> P2
    A1 -.-> P3
    A1 -.-> P4
    A1 -.-> P5
    A1 -.-> P6
    A1 -.-> P7
    A1 -.-> OP(["Off-Budget Funds"])

    classDef T stroke:transparent,fill:transparent;
    class boxB,T2,T3,T4,T5,T6,T11,T13,T14,T17,A1,A2 T;

    classDef bar stroke:black,stroke-width:2px,fill-opacity:0.7;
    class T2bar,T3bar,T4bar,T5bar,T6bar,T11bar,T13bar,T14bar,T17bar,A1bar,A2bar bar;

    classDef box fill:transparent,stroke:transparent;
    class T2box,T3box,T4box,T5box,T6box,T11box,T13box,T14box,T17box,A1box,A2box box;

    style A1bar r:${getRadiusAssigned("assigned")}px,fill:${colors[6]},stroke:black
    style A2bar r:${getRadiusAssigned("nonAssigned")}px,fill:${colors[7]},stroke:black
    style B r:${Math.sqrt(d3.sum(dataGalaxy.map(d => d.value/scaleValue)))}px,fill:${colors[0]},fill-opacity:0.7,stroke:black

    subgraph policy[" "]
      P1(["Single Market, Innovation and Digital"])
      P2(["Cohesion, Resilience and Values"]) 
      P3(["Natural Resources and Environment"])
      P4(["Migration and Border Management"])
      P5(["Security and Defense"])
      P6(["Neighbourhood and the World"])
      P7(["European Public Administration"])
    end

    style policy fill:transparent,stroke:black,stroke-width:1px
    style A fill:transparent,stroke:transparent,color:transparent,margin-left:100px
    style container fill:transparent,stroke:transparent
    style other fill:#ededed,fill-opacity:0.7,stroke:black,stroke-width:1px

  end

  ${highlight1 ? "style B stroke:#333,stroke-width:3px,fill-opacity:0.4" : ""}
`
catchWidth = width // strangely the ojs width param only works outside {} chunks
Inputs.bind(Inputs.radio(
    ["Total Revenue", "Other Revenue"], 
    {label: "", value: "Other Revenue"}
  ),
  viewof radios
)
Inputs.bind(Inputs.range(
    [2019, 2023], 
    { style: "padding:12px; width:80%", value: 2023, step: 1, label: "" }
  ),
  viewof range
)

treemap = {
  const width = catchWidth >= 2000 ? 
    catchWidth * 0.60 : 
    catchWidth >= 1600 ? 
    catchWidth * 0.72 :
    catchWidth * 0.88; 

  const height = width * 0.33 ;

  const color = d3.scaleOrdinal(
    radios == "Total Revenue" ? colors : colorsShift // ["#72bb6f", "#559bd5", "#f3d039", "#c3423f", "#12a7e7"]
    );

  function tile(node, x0, y0, x1, y1) {
    d3.treemapSquarify(node, 0, 0, width, height);
    for (const child of node.children) {
      child.x0 = x0 + child.x0 / width * (x1 - x0);
      child.x1 = x0 + child.x1 / width * (x1 - x0);
      child.y0 = y0 + child.y0 / height * (y1 - y0);
      child.y1 = y0 + child.y1 / height * (y1 - y0);
    }
  }

  const hierarchy = d3.hierarchy(data)
    .sum(d => d.value)
    // .sort((a, b) => b.value - a.value);
  
  hierarchy.eachAfter(function(node) {
    var sumPercent = +node.data.percent || 0,
        sumPercentAssigned = +node.data.percentAssigned || 0,
        sumValueAssigned = +node.data.valueAssigned || 0,
        children = node.children,
        i = children && children.length;
    while (--i >= 0) 
      sumPercent += children[i].percent,
      sumPercentAssigned += children[i].percentAssigned,
      sumValueAssigned += children[i].valueAssigned;
    node.percent = sumPercent;
    node.percentAssigned = sumPercentAssigned;
    node.valueAssigned = sumValueAssigned;
  });
  
  const root = d3.treemap().tile(tile)(hierarchy);

  const x = d3.scaleLinear().rangeRound([0, width]);
  const y = d3.scaleLinear().rangeRound([0, height]);

  const format = d3.format(",d");
  const name = d => d.ancestors().reverse().map(d => d.data.name).join(" → ");

  const svg = d3.create("svg")
      .attr("viewBox", [0.5, -56.5, width, height + 56])
      .attr("width", width)
      .attr("class", "treemap")
      .attr("height", height + 56)
      .attr("style", "max-width: 100%; height: auto;")
      .style("font", "Noto Sans sans-serif"); // font size adjustment does not work here see below

  let group = svg.append("g")
      .call(render, root);

  function render(group, root) {
    const node = group
      .selectAll("g")
      .data(root.children.concat(root))
      .join("g");

    node.filter(d => d === root ? d.parent : d.children)
        .attr("cursor", "pointer")
        .on("click", (event, d) => d === root ? zoomout(root) : zoomin(d));

    // node.append("title")
    //     .text(d => `${name(d)}\n${format(d.value)}`);

    node.append("rect")
        .attr("id", d => (d.leafUid = DOM.uid("leaf")).id)
        .attr("fill", d => d === root ? "#ededed" : d.children ? "#ccc" : "#ddd")
        .attr("stroke", "black")
        .attr("stroke-width", "1px")
        .attr("fill-opacity", 0.7)
        .attr("fill", d => {
          if (d === root) return "#ededed";
          while (d.depth > 1) d = d.parent;
          return color(d.data.name);
        });

    node.append("clipPath")
        .attr("id", d => (d.clipUid = DOM.uid("clip")).id)
      .append("use")
        .attr("xlink:href", d => d.leafUid.href);

    node.append("text")
        .attr("clip-path", d => d.clipUid)
        .attr("font-weight", d => d === root ? "bold" : null)
        .attr("font-size", "0.9em")
      .selectAll("tspan")
      .data(
        d => d === root ? 
          d.data.name // name(d)
            .split(/(?=\/)/g)
            .concat(
              "EUR " + format(d.value / 1000000) + " m" + " / " + d3.format(",.1~f")(d.percent) + "%"
            )
        : 
          d.data.name
            .split(/(?<=\d+\s)/g)
            .concat("EUR " + format(d.value / 1000000) + " m")
            .concat(
              radios == "Total Revenue" ?
              d3.format(",.1~f")(d.percent) + "% of total revenue" :
              d3.format(",.1~f")(d.percent) + "% of other revenue"
              )
            .concat("Assigned Revenue: ") // → a
            .concat("EUR " + format(d.valueAssigned / 1000000) + " m")
            .concat(d3.format(",.1~f")(d.valueAssigned / d.value * 100) + "% of " + d.data.name.split(' ').slice(0, 2).join(' '))
            // .concat(progressBar(d.valueAssigned / d.value * 100))
        )
      .join("tspan")
        .attr("x", 5)
        .attr("y", (d, i, nodes) => `${(i >= nodes.length - 3) * 0.55 + 1.33 + (i > 3 ? i * 1.15 : i) * 1.2}em`)
        // .attr("fill-opacity", (d, i, nodes) => i >= nodes.length - 4 ? 0.7 : null) // 0.7 
        .attr("font-style", (d, i, nodes) => i >= 4 ? "italic" : null) // 0.7 
        .attr("font-weight", (d, i, nodes) => i >= nodes.length - 3 ? "normal" : null) 
        .text(d => d);

    group.call(position, root);
  }

  function position(group, root) {
    group.selectAll("g")
        .attr("transform", d => d === root ? `translate(0,-56)` : `translate(${x(d.x0)},${y(d.y0)})`)
      .select("rect")
        .attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
        .attr("height", d => d === root ? 56 : y(d.y1) - y(d.y0));
  }

  function zoomin(d) {
    const group0 = group.attr("pointer-events", "none");
    const group1 = group = svg.append("g").call(render, d);

    x.domain([d.x0, d.x1]);
    y.domain([d.y0, d.y1]);

    svg.transition()
        .duration(750)
        .call(t => group0.transition(t).remove()
          .call(position, d.parent))
        .call(t => group1.transition(t)
          .attrTween("opacity", () => d3.interpolate(0, 1))
          .call(position, d));
  }

  function zoomout(d) {
    const group0 = group.attr("pointer-events", "none");
    const group1 = group = svg.insert("g", "*").call(render, d.parent);

    x.domain([d.parent.x0, d.parent.x1]);
    y.domain([d.parent.y0, d.parent.y1]);

    svg.transition()
        .duration(750)
        .call(t => group0.transition(t).remove()
          .attrTween("opacity", () => d3.interpolate(1, 0))
          .call(position, d))
        .call(t => group1.transition(t) 
          .call(position, d.parent));
  }

  return svg.node();
}