/* ============================================================
   Pipeline dashboard charts — the reads the live board lacked.
   Sparkline · KpiSparkCell · AgingHeatmap · ComboBars ·
   FlowBars · StackedTimeBars · RankedBars · Quadrant.
   Graphite tokens only (DCH from df_charts.jsx). All drill via onX.
   ============================================================ */

const PCH = {
  ink: '#1a1c1e', muted: '#6e7479', soft: '#9aa0a5', grid: '#e7e8e9', axis: '#9aa0a5',
  accent: '#3e6e8c', accentSoft: '#9db4c0', ghost: '#d3d5d7',
  pos: '#3f7d58', warn: '#b07a2e', danger: '#a4453c',
  // categorical (channels / phases) — desaturated graphite family
  cat: ['#3e6e8c', '#9db4c0', '#6e8f7d', '#b6a06a', '#a98a84', '#7c8a93'],
  exit: { Passed: '#9aa0a5', 'On Hold': '#b07a2e', Lost: '#a4453c' },
};

function pNiceMax(v) {
  if (v <= 5) return 5;
  const pow = Math.pow(10, Math.floor(Math.log10(v)));
  const n = v / pow, step = n <= 1 ? 1 : n <= 2 ? 2 : n <= 5 ? 5 : 10;
  return Math.ceil(v / (step * pow / 5)) * (step * pow / 5);
}

/* ---------- Sparkline (tiny trend in KPI cells) ---------- */
function Sparkline({ values, color = PCH.accent, w = 88, h = 28 }) {
  if (!values || values.length < 2) return null;
  const max = Math.max(...values), min = Math.min(...values), rng = (max - min) || 1;
  const x = (i) => i / (values.length - 1) * (w - 2) + 1;
  const y = (v) => h - 3 - (v - min) / rng * (h - 6);
  const line = values.map((v, i) => `${i ? 'L' : 'M'}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(' ');
  const area = line + ` L${x(values.length - 1).toFixed(1)},${h} L${x(0).toFixed(1)},${h} Z`;
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} style={{ display: 'block', overflow: 'visible' }}>
      <path d={area} fill={color} opacity="0.10" />
      <path d={line} fill="none" stroke={color} strokeWidth="1.6" />
      <circle cx={x(values.length - 1)} cy={y(values[values.length - 1])} r="2.4" fill={color} />
    </svg>
  );
}

/* ---------- KPI cell with sparkline + optional € companion ---------- */
function KpiSparkCell({ label, value, delta, hint, sub, companion, spark, sparkColor, accent, onClick, active }) {
  return (
    <button onClick={onClick} disabled={!onClick} className="card"
      style={{ flex: 1, minWidth: 0, textAlign: 'left', padding: '12px 14px 12px', cursor: onClick ? 'pointer' : 'default',
        background: active ? 'var(--accent-soft)' : 'var(--surface-card)', borderColor: active ? 'var(--accent)' : 'var(--line)',
        position: 'relative', transition: 'all .12s', display: 'flex', flexDirection: 'column' }}
      onMouseEnter={(e) => { if (onClick && !active) e.currentTarget.style.borderColor = '#c4c6c8'; }}
      onMouseLeave={(e) => { if (onClick && !active) e.currentTarget.style.borderColor = 'var(--line)'; }}>
      <div className="micro" style={{ marginBottom: 6, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
        <span className="tabular" style={{ fontSize: 24, fontWeight: 700, lineHeight: 1, color: accent ? 'var(--accent)' : 'var(--ink)' }}>{value}</span>
        {delta != null && (
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 1, fontSize: 11, fontWeight: 700, color: delta >= 0 ? 'var(--positive)' : 'var(--danger)' }}>
            <Icon name={delta >= 0 ? 'arrowUp' : 'arrowDown'} size={11} stroke={2.4} />{Math.abs(delta)}{hint === 'pp' ? 'pp' : hint === 'pct' ? '%' : ''}
          </span>
        )}
      </div>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 8, marginTop: 7, flex: 1 }}>
        <div style={{ minWidth: 0 }}>
          {companion && <div className="tabular" style={{ fontSize: 11.5, fontWeight: 700, color: 'var(--ink-muted)' }}>{companion}</div>}
          {sub && <div style={{ fontSize: 10.5, color: 'var(--ink-soft)', marginTop: companion ? 1 : 0, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{sub}</div>}
        </div>
        {spark && <div style={{ flexShrink: 0, opacity: .9 }}><Sparkline values={spark} color={sparkColor || (accent ? 'var(--accent)' : PCH.accentSoft)} /></div>}
      </div>
    </button>
  );
}

/* ---------- Stage aging heatmap (rows = stage, cols = age bucket) ---------- */
function AgingHeatmap({ rows, colLabels, onCell }) {
  // rows: [{ label, key, cells:[n,n,n,n], total }]
  const max = Math.max(1, ...rows.flatMap((r) => r.cells));
  const hue = [PCH.accent, '#7c8a93', PCH.warn, PCH.danger]; // fresh → stale
  return (
    <div style={{ display: 'grid', gridTemplateColumns: `120px repeat(${colLabels.length}, 1fr) 56px`, gap: 5, alignItems: 'center' }}>
      <div></div>
      {colLabels.map((c, i) => (
        <div key={c} style={{ textAlign: 'center', fontSize: 10.5, fontWeight: 700, color: i >= 2 ? (i === 3 ? 'var(--danger)' : 'var(--warn)') : 'var(--ink-soft)' }}>{c}<span style={{ display: 'block', fontSize: 9, fontWeight: 500, color: 'var(--ink-faint)' }}>days</span></div>
      ))}
      <div style={{ textAlign: 'right', fontSize: 10, fontWeight: 600, color: 'var(--ink-faint)' }}>Active</div>
      {rows.map((r) => (
        <React.Fragment key={r.key}>
          <div style={{ fontSize: 12, fontWeight: 600, textAlign: 'right', paddingRight: 4 }}>{r.label}</div>
          {r.cells.map((n, c) => {
            const t = n / max;
            const bg = n === 0 ? 'var(--surface)' : `color-mix(in srgb, ${hue[c]} ${Math.round(18 + t * 72)}%, #ffffff)`;
            const dark = t > 0.55;
            return (
              <button key={c} onClick={() => n && onCell && onCell(r, c)} disabled={!n}
                title={`${r.label} · ${colLabels[c]} days · ${n}`}
                style={{ height: 34, border: '1px solid var(--line-soft)', borderRadius: 6, background: bg, cursor: n ? 'pointer' : 'default',
                  color: dark ? '#fff' : 'var(--ink)', fontWeight: 700, fontSize: 13, fontFamily: 'var(--mono)', transition: 'all .12s' }}
                onMouseEnter={(e) => { if (n) e.currentTarget.style.outline = '2px solid var(--accent)'; }}
                onMouseLeave={(e) => { e.currentTarget.style.outline = 'none'; }}>
                {n || ''}
              </button>
            );
          })}
          <div className="tabular" style={{ textAlign: 'right', fontSize: 12.5, fontWeight: 700, color: 'var(--ink-muted)' }}>{r.total}</div>
        </React.Fragment>
      ))}
    </div>
  );
}

/* ---------- ComboBars: stacked bars (primary axis) + optional ghost
   outline (prior) + optional target line + optional overlaid line
   (primary or secondary % axis). Used by B1, B3, B4. ---------- */
function ComboBars({ data, series, ghostKey, line, target, height = 200, onBar, fmtBar }) {
  const [tip, setTip] = React.useState(null);
  const pad = { l: 30, r: line && line.pct ? 34 : 12, t: 16, b: 26 };
  const W = 560, H = height, iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const totals = data.map((d) => series.reduce((s, k) => s + (d[k.key] || 0), 0));
  const ghostVals = ghostKey ? data.map((d) => d[ghostKey] || 0) : [];
  const max = pNiceMax(Math.max(1, ...totals, ...ghostVals, target ? target.value : 0));
  const lineMax = line ? (line.pct ? 100 : pNiceMax(Math.max(1, ...data.map((d) => d[line.key] || 0)))) : 1;
  const n = data.length, bw = Math.min(46, iw / n * 0.62), gap = iw / n;
  const xC = (i) => pad.l + gap * i + gap / 2;
  const yOf = (v) => pad.t + ih - v / max * ih;
  const yLine = (v) => pad.t + ih - v / lineMax * ih;
  const lp = line ? data.map((d, i) => `${i ? 'L' : 'M'}${xC(i)},${yLine(d[line.key] || 0)}`).join(' ') : '';
  return (
    <div style={{ position: 'relative' }}>
      <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block', overflow: 'visible' }}>
        {Array.from({ length: 5 }).map((_, i) => { const v = max / 4 * i, y = yOf(v); return <g key={i}><line x1={pad.l} x2={W - pad.r} y1={y} y2={y} stroke={PCH.grid} /><text x={pad.l - 6} y={y + 3} textAnchor="end" fontSize="9.5" fill={PCH.axis} fontFamily="var(--mono)">{Math.round(v)}</text></g>; })}
        {line && line.pct && Array.from({ length: 5 }).map((_, i) => { const v = 25 * i; return <text key={i} x={W - pad.r + 6} y={yLine(v) + 3} textAnchor="start" fontSize="9" fill={line.color} fontFamily="var(--mono)">{v}</text>; })}
        {data.map((d, i) => {
          let acc = 0;
          return (
            <g key={i}>
              {ghostKey && <rect x={xC(i) - bw / 2 - 3} y={yOf(d[ghostKey] || 0)} width={bw + 6} height={Math.max(0, pad.t + ih - yOf(d[ghostKey] || 0))} fill="none" stroke={PCH.ghost} strokeWidth="1.3" strokeDasharray="3 2" rx="2" />}
              {series.map((s, si) => {
                const v = d[s.key] || 0; const h = v / max * ih; const yy = yOf(acc + v); acc += v;
                return <rect key={si} x={xC(i) - bw / 2} y={yy} width={bw} height={Math.max(0, h)} fill={s.color} rx={si === series.length - 1 ? 2.5 : 0}
                  style={{ cursor: onBar ? 'pointer' : 'default' }}
                  onMouseEnter={() => setTip({ x: xC(i), y: yy, d, i })} onMouseLeave={() => setTip(null)}
                  onClick={() => onBar && onBar(d, i, s)} />;
              })}
              <text x={xC(i)} y={H - pad.b + 15} textAnchor="middle" fontSize="9.5" fill={PCH.muted} fontWeight="600">{d.label}</text>
            </g>
          );
        })}
        {target && <g><line x1={pad.l} x2={W - pad.r} y1={yOf(target.value)} y2={yOf(target.value)} stroke={PCH.danger} strokeWidth="1.5" strokeDasharray="5 3" opacity="0.8" /><text x={W - pad.r} y={yOf(target.value) - 4} textAnchor="end" fontSize="9.5" fontWeight="700" fill={PCH.danger}>{target.label}</text></g>}
        {line && <path d={lp} fill="none" stroke={line.color} strokeWidth="2" strokeDasharray={line.dash ? '4 3' : 'none'} />}
        {line && data.map((d, i) => <circle key={i} cx={xC(i)} cy={yLine(d[line.key] || 0)} r="2.6" fill="#fff" stroke={line.color} strokeWidth="1.8" />)}
      </svg>
      {tip && <div style={{ position: 'absolute', left: (tip.x / W * 100) + '%', top: tip.y, transform: 'translate(-50%,-115%)', background: '#1a1c1e', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: 11, fontWeight: 500, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 20, lineHeight: 1.4 }}>
        <div style={{ fontWeight: 700, marginBottom: 2 }}>{tip.d.label}</div>
        {series.map((s) => <div key={s.key}><span style={{ color: s.color }}>●</span> {s.label}: {fmtBar ? fmtBar(tip.d[s.key] || 0) : (tip.d[s.key] || 0)}</div>)}
        {line && <div style={{ color: line.color }}>{line.label}: {Math.round(tip.d[line.key] || 0)}{line.pct ? '%' : ''}</div>}
      </div>}
    </div>
  );
}

/* ---------- FlowBars: inflow up / outflow down + cumulative line ---------- */
function FlowBars({ data, height = 200, onBar }) {
  const [tip, setTip] = React.useState(null);
  const pad = { l: 32, r: 34, t: 14, b: 26 };
  const W = 560, H = height, iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const maxBar = pNiceMax(Math.max(1, ...data.map((d) => Math.max(d.inflow, d.outflow))));
  const maxLine = pNiceMax(Math.max(1, ...data.map((d) => d.active)));
  const n = data.length, gap = iw / n, bw = Math.min(20, gap * 0.34);
  const xC = (i) => pad.l + gap * i + gap / 2;
  const mid = pad.t + ih / 2;
  const yUp = (v) => mid - v / maxBar * (ih / 2);
  const yDn = (v) => mid + v / maxBar * (ih / 2);
  const yLine = (v) => pad.t + ih - v / maxLine * ih;
  const lp = data.map((d, i) => `${i ? 'L' : 'M'}${xC(i)},${yLine(d.active)}`).join(' ');
  return (
    <div style={{ position: 'relative' }}>
      <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block', overflow: 'visible' }}>
        <line x1={pad.l} x2={W - pad.r} y1={mid} y2={mid} stroke={PCH.axis} strokeWidth="1" />
        {data.map((d, i) => (
          <g key={i} style={{ cursor: onBar ? 'pointer' : 'default' }} onMouseEnter={() => setTip({ x: xC(i), y: yUp(d.inflow), d })} onMouseLeave={() => setTip(null)} onClick={() => onBar && onBar(d)}>
            <rect x={xC(i) - bw - 1} y={yUp(d.inflow)} width={bw} height={Math.max(0, mid - yUp(d.inflow))} fill={PCH.accent} rx="2" />
            <rect x={xC(i) + 1} y={mid} width={bw} height={Math.max(0, yDn(d.outflow) - mid)} fill={PCH.danger} opacity="0.78" rx="2" />
            <text x={xC(i)} y={H - pad.b + 15} textAnchor="middle" fontSize="9.5" fill={PCH.muted} fontWeight="600">{d.label}</text>
          </g>
        ))}
        <path d={lp} fill="none" stroke={PCH.warn} strokeWidth="2" />
        {data.map((d, i) => <circle key={i} cx={xC(i)} cy={yLine(d.active)} r="2.6" fill="#fff" stroke={PCH.warn} strokeWidth="1.8" />)}
      </svg>
      {tip && <div style={{ position: 'absolute', left: (tip.x / W * 100) + '%', top: tip.y, transform: 'translate(-50%,-115%)', background: '#1a1c1e', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: 11, fontWeight: 500, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 20, lineHeight: 1.4 }}>
        <div style={{ fontWeight: 700, marginBottom: 2 }}>{tip.d.label}</div>
        <div><span style={{ color: PCH.accent }}>●</span> In: {tip.d.inflow}</div>
        <div><span style={{ color: PCH.danger }}>●</span> Out: {tip.d.outflow}</div>
        <div><span style={{ color: PCH.warn }}>●</span> Active: {tip.d.active}</div>
      </div>}
    </div>
  );
}

/* ---------- StackedTimeBars: count or 100% mix ---------- */
function StackedTimeBars({ data, series, mode = 'count', height = 200, onSeg }) {
  const [tip, setTip] = React.useState(null);
  const pad = { l: 30, r: 12, t: 12, b: 26 };
  const W = 560, H = height, iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const totals = data.map((d) => series.reduce((s, k) => s + (d[k.key] || 0), 0));
  const max = mode === 'mix' ? 1 : pNiceMax(Math.max(1, ...totals));
  const n = data.length, gap = iw / n, bw = Math.min(44, gap * 0.62);
  const xC = (i) => pad.l + gap * i + gap / 2;
  const yOf = (v) => pad.t + ih - v / max * ih;
  return (
    <div style={{ position: 'relative' }}>
      <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block', overflow: 'visible' }}>
        {Array.from({ length: 5 }).map((_, i) => { const v = max / 4 * i, y = yOf(v); return <g key={i}><line x1={pad.l} x2={W - pad.r} y1={y} y2={y} stroke={PCH.grid} /><text x={pad.l - 6} y={y + 3} textAnchor="end" fontSize="9.5" fill={PCH.axis} fontFamily="var(--mono)">{mode === 'mix' ? Math.round(v * 100) : Math.round(v)}</text></g>; })}
        {data.map((d, i) => {
          const tot = totals[i] || 1; let acc = 0;
          return (
            <g key={i}>
              {series.map((s, si) => {
                const raw = d[s.key] || 0; const v = mode === 'mix' ? raw / tot : raw; const h = v / max * ih; const yy = yOf(acc + v); acc += v;
                return <rect key={si} x={xC(i) - bw / 2} y={yy} width={bw} height={Math.max(0, h)} fill={s.color}
                  style={{ cursor: onSeg ? 'pointer' : 'default' }}
                  onMouseEnter={() => setTip({ x: xC(i), y: yy, d, raw, s })} onMouseLeave={() => setTip(null)}
                  onClick={() => onSeg && onSeg(d, s)} />;
              })}
              <text x={xC(i)} y={H - pad.b + 15} textAnchor="middle" fontSize="9.5" fill={PCH.muted} fontWeight="600">{d.label}</text>
            </g>
          );
        })}
      </svg>
      {tip && <div style={{ position: 'absolute', left: (tip.x / W * 100) + '%', top: tip.y, transform: 'translate(-50%,-115%)', background: '#1a1c1e', color: '#fff', padding: '5px 8px', borderRadius: 6, fontSize: 11, fontWeight: 500, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 20, lineHeight: 1.4 }}>
        <div style={{ fontWeight: 700, marginBottom: 2 }}>{tip.d.label}</div>
        <div><span style={{ color: tip.s.color }}>●</span> {tip.s.label}: {tip.raw}</div>
      </div>}
    </div>
  );
}

/* ---------- RankedBars: horizontal ranked, optional prior ghost ---------- */
function RankedBars({ rows, color = PCH.accent, height, onRow, fmt, prior, note }) {
  const max = Math.max(1, ...rows.map((r) => Math.max(r.value, prior ? (r.prev || 0) : 0)));
  const f = fmt || ((v) => v.toLocaleString('en-US'));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8, minHeight: height || 'auto' }}>
      {rows.map((r) => {
        const pct = r.value / max * 100, ppct = prior ? (r.prev || 0) / max * 100 : 0;
        return (
          <button key={r.label} onClick={() => onRow && onRow(r)} style={{ border: 'none', background: 'transparent', padding: 0, textAlign: 'left', cursor: onRow ? 'pointer' : 'default', display: 'block' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 3 }}>
              <span style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--ink-muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '70%' }}>{r.label}</span>
              <span className="tabular" style={{ fontSize: 12, fontWeight: 700 }}>{f(r.value)}</span>
            </div>
            <div style={{ position: 'relative', height: prior ? 14 : 9 }}>
              <div style={{ position: 'absolute', top: 0, height: prior ? 8 : 9, width: pct + '%', minWidth: 2, background: color, borderRadius: 3 }}></div>
              {prior && <div title="prior season" style={{ position: 'absolute', top: 9, height: 4, width: ppct + '%', minWidth: prior ? 2 : 0, background: PCH.ghost, borderRadius: 2 }}></div>}
            </div>
          </button>
        );
      })}
      {note && <div style={{ fontSize: 10.5, color: 'var(--ink-faint)', marginTop: 2 }}>{note}</div>}
    </div>
  );
}

/* ---------- Quadrant: scatter, median lines, bubbles ---------- */
function Quadrant({ points, xLabel, yLabel, xMed, yMed, height = 280, onPoint, fmtY }) {
  const [tip, setTip] = React.useState(null);
  const pad = { l: 40, r: 16, t: 14, b: 34 };
  const W = 560, H = height, iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const xMax = Math.max(1, ...points.map((p) => p.x)) * 1.12;
  const yMax = Math.max(1, ...points.map((p) => p.y)) * 1.12;
  const rMax = Math.max(1, ...points.map((p) => p.r));
  const xOf = (v) => pad.l + v / xMax * iw;
  const yOf = (v) => pad.t + ih - v / yMax * ih;
  const rOf = (v) => 5 + Math.sqrt(v / rMax) * 16;
  const fy = fmtY || ((v) => v);
  return (
    <div style={{ position: 'relative' }}>
      <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block', overflow: 'visible' }}>
        {/* quadrant guides */}
        <line x1={xOf(xMed)} x2={xOf(xMed)} y1={pad.t} y2={pad.t + ih} stroke={PCH.grid} strokeWidth="1.4" strokeDasharray="4 3" />
        <line x1={pad.l} x2={pad.l + iw} y1={yOf(yMed)} y2={yOf(yMed)} stroke={PCH.grid} strokeWidth="1.4" strokeDasharray="4 3" />
        <rect x={xOf(xMed)} y={pad.t} width={pad.l + iw - xOf(xMed)} height={yOf(yMed) - pad.t} fill={PCH.pos} opacity="0.05" />
        <text x={pad.l + iw} y={pad.t + 11} textAnchor="end" fontSize="9.5" fill={PCH.pos} fontWeight="700" opacity="0.9">high vol · high close</text>
        <text x={pad.l + 2} y={pad.t + 11} textAnchor="start" fontSize="9.5" fill={PCH.soft} fontWeight="600">low vol · high close</text>
        {/* axes labels */}
        <text x={pad.l + iw / 2} y={H - 4} textAnchor="middle" fontSize="10" fill={PCH.muted} fontWeight="600">{xLabel}</text>
        <text transform={`translate(11,${pad.t + ih / 2}) rotate(-90)`} textAnchor="middle" fontSize="10" fill={PCH.muted} fontWeight="600">{yLabel}</text>
        {[0, 0.25, 0.5, 0.75, 1].map((t, i) => <text key={i} x={pad.l - 6} y={yOf(yMax * t) + 3} textAnchor="end" fontSize="9" fill={PCH.axis} fontFamily="var(--mono)">{fy(Math.round(yMax * t))}</text>)}
        {points.map((p, i) => (
          <g key={i} style={{ cursor: onPoint ? 'pointer' : 'default' }}
            onMouseEnter={() => setTip({ x: xOf(p.x), y: yOf(p.y), p })} onMouseLeave={() => setTip(null)} onClick={() => onPoint && onPoint(p)}>
            <circle cx={xOf(p.x)} cy={yOf(p.y)} r={rOf(p.r)} fill={PCH.accent} opacity="0.22" />
            <circle cx={xOf(p.x)} cy={yOf(p.y)} r="3" fill={PCH.accent} />
          </g>
        ))}
      </svg>
      {tip && <div style={{ position: 'absolute', left: (tip.x / W * 100) + '%', top: tip.y, transform: 'translate(-50%,-118%)', background: '#1a1c1e', color: '#fff', padding: '6px 9px', borderRadius: 6, fontSize: 11, fontWeight: 500, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 20, lineHeight: 1.45 }}>
        <div style={{ fontWeight: 700, marginBottom: 2 }}>{tip.p.label}</div>
        <div>{xLabel}: {tip.p.x}</div>
        <div>{yLabel}: {Math.round(tip.p.y)}%</div>
        <div>€ introduced: €{tip.p.r.toFixed(0)}m</div>
      </div>}
    </div>
  );
}

Object.assign(window, { PCH, Sparkline, KpiSparkCell, AgingHeatmap, ComboBars, FlowBars, StackedTimeBars, RankedBars, Quadrant });
