/* global React */
// Vizualizace stromu — reaguje na zadané parametry
// Props: { taxon, diameter, height, stemHeight, crownSpread, vitality, health, microHabitats, valuableMicroHabitats, width, height: canvasH, variant }
const TreeViz = (function () {
const { useMemo } = React;
// Mapping vitalita → barvy listů (od svěží zelené po mrtvý)
const VITALITY_COLORS = {
1: { dark: '#2d6a3e', fg: '#3f8e4f', mid: '#69ad6a', light: '#a8c98a', empty: 0.02 },
2: { dark: '#516e2a', fg: '#6e8a3a', mid: '#9aaf52', light: '#c7c069', empty: 0.10 },
3: { dark: '#6a5a30', fg: '#9c8a48', mid: '#b8a55b', light: '#c7b272', empty: 0.30 },
4: { dark: '#5a4830', fg: '#7d6840', mid: '#9a8456', light: '#b59a6c', empty: 0.55 },
5: { dark: '#46352a', fg: '#5a4530', mid: '#6f5840', light: '#7d6850', empty: 0.92 },
};
// Healh: ovlivňuje barvu kmene (zhoršený = více skvrn) — jen subtilně
const HEALTH_TRUNK = {
1: { base: '#6b4a30', shadow: '#3d2918' },
2: { base: '#664530', shadow: '#3a2716' },
3: { base: '#5e3f2b', shadow: '#352314' },
4: { base: '#553a28', shadow: '#2f1f12' },
5: { base: '#4a3424', shadow: '#291b0f' },
};
// PRNG s fixním seedem - deterministicky pro stejný strom
function mulberry32(a) {
return function () {
let t = (a += 0x6D2B79F5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// Generuj klastry listů uvnitř koruny - tečkovaný "pointilismus"
function generateLeafClusters(shape, w, h, count, seed) {
const rng = mulberry32(seed);
const clusters = [];
let cx = w / 2;
for (let i = 0; i < count; i++) {
const rx = rng();
const ry = rng();
let x, y;
if (shape === 'kuželovitá') {
// Trojúhelník - širší dole
y = ry * h;
const widthAtY = (1 - y / h) * w * 0.45 + w * 0.05;
x = cx + (rx - 0.5) * 2 * widthAtY;
} else if (shape === 'sloupovitá') {
// Úzký vysoký sloupec
y = ry * h;
x = cx + (rx - 0.5) * w * 0.45;
} else if (shape === 'kulová') {
// Kruh
const angle = rx * Math.PI * 2;
const r = Math.sqrt(ry) * Math.min(w, h) * 0.45;
x = cx + Math.cos(angle) * r;
y = h / 2 + Math.sin(angle) * r;
} else {
// vejčitá - elipsa svisle
const angle = rx * Math.PI * 2;
const r = Math.sqrt(ry);
x = cx + Math.cos(angle) * r * w * 0.42;
y = h / 2 + Math.sin(angle) * r * h * 0.46;
}
clusters.push({ x, y, r: 8 + rng() * 16, hue: rng() });
}
return clusters;
}
// Pozice mikrohabitats na kmeni / koruně
function microHabitatMarks(habitats, valuable, trunkX, trunkW, trunkTop, trunkBottom, crownBox, seed) {
const rng = mulberry32(seed + 1000);
const marks = [];
const valuableSet = new Set(valuable || []);
(habitats || []).forEach((id) => {
const isLarge = valuableSet.has(id);
const onTrunk = [4, 5, 6, 7, 8, 11, 13].includes(id);
let x, y;
if (onTrunk) {
const t = 0.2 + rng() * 0.6;
x = trunkX + (rng() - 0.5) * trunkW * 0.6;
y = trunkBottom - t * (trunkBottom - trunkTop);
} else {
x = crownBox.x + rng() * crownBox.w;
y = crownBox.y + rng() * crownBox.h * 0.6;
}
marks.push({ id, x, y, large: isLarge });
});
return marks;
}
function MicroIcon({ id, x, y, large }) {
const size = large ? 14 : 9;
// Renderuj jednoduchou ikonku pro každý habitát
const common = { transform: `translate(${x} ${y})` };
switch (id) {
case 1: case 2: // dutiny od ptáků / větví — tmavý kruh
return ;
case 4: // kmenové dutiny — větší ovál
return ;
case 7: // plodnice hub
return
;
case 10: // suché větve — šedý klacek
return
;
case 11: // trhliny — vertikální čára
return
;
case 5: case 8: // odlupující/poškozená borka
return
;
case 13: // výtoky mízy
return
;
default:
return ;
}
}
function TreeViz({
taxon,
diameter = 50,
treeHeight: heightProp = 12,
stemHeight = 4,
crownSpread = 6,
vitality = 1,
health = 1,
microHabitats = [],
valuableMicroHabitats = [],
memorial = false,
width = 520,
canvasHeight = 600,
showLabels = false,
background = 'transparent',
}) {
const W = width;
const H = canvasHeight;
const height = heightProp;
const groundY = H - 40;
const skyTop = 20;
// Měřítka — typický strom 5–25m, fill ~ 85–95% výšky
const maxRealH = 24;
const realH = Math.max(2, Math.min(maxRealH, height || 12));
const treeTotalPx = (realH / maxRealH) * (groundY - skyTop) * 0.98;
const realStemH = Math.max(0.5, Math.min(realH * 0.8, stemHeight || realH * 0.35));
const stemPxFrac = realStemH / realH;
const trunkH = treeTotalPx * stemPxFrac;
const crownH = treeTotalPx - trunkH;
// Šířka kmene — 0–150 cm → 8–60 px (kořeny pod tím trochu rozšířené)
const d = Math.max(5, Math.min(200, diameter || 30));
const trunkW = 6 + (d / 150) * 38;
// Šířka koruny — 0–25 m → odpovídající px
const cs = Math.max(0.5, crownSpread || Math.max(3, realH * 0.4));
const crownW = Math.min(W * 0.85, (cs / 25) * (W * 0.85));
const cx = W / 2;
const trunkTop = groundY - trunkH;
const crownTop = trunkTop - crownH;
const crownLeft = cx - crownW / 2;
const vitalPalette = VITALITY_COLORS[vitality] || VITALITY_COLORS[1];
const trunkPalette = HEALTH_TRUNK[health] || HEALTH_TRUNK[1];
// Tvar koruny — podle taxon.shape (default vejčitá)
// Backend posílá tvary bez diakritiky (kuzelovita / kulovita / sloupovita / zaoblena / jiny);
// mock data.js používá tvary s diakritikou. Mapujeme oboje na jednu sadu.
const SHAPE_NORMALIZE = {
'kuzelovita': 'kuželovitá', 'kuželovitá': 'kuželovitá',
'sloupovita': 'sloupovitá', 'sloupovitá': 'sloupovitá',
'kulovita': 'kulová', 'kulová': 'kulová',
'zaoblena': 'vejčitá', 'vejčitá': 'vejčitá',
'jiny': 'vejčitá',
};
const shape = SHAPE_NORMALIZE[taxon?.shape] || 'vejčitá';
const conifer = !!taxon?.conifer;
// Listové klastry
const seed = Math.round(d * 17 + realH * 31 + cs * 7);
const baseCount = Math.round(crownW * crownH / 180);
const clusters = useMemo(
() => generateLeafClusters(shape, crownW, crownH, baseCount, seed),
[shape, crownW, crownH, baseCount, seed]
);
// Ořezová cesta pro tvar koruny
const clipId = `crown-clip-${seed}`;
let crownPath;
let crownCenterX = cx;
let crownCenterY;
if (shape === 'kuželovitá') {
crownPath = `M ${cx} ${crownTop} L ${cx + crownW / 2} ${trunkTop} L ${cx - crownW / 2} ${trunkTop} Z`;
crownCenterY = (crownTop + trunkTop) / 2;
} else if (shape === 'sloupovitá') {
const r = Math.min(crownW * 0.4, 40);
crownPath = `M ${crownLeft + r} ${crownTop} L ${crownLeft + crownW - r} ${crownTop} Q ${crownLeft + crownW} ${crownTop} ${crownLeft + crownW} ${crownTop + r} L ${crownLeft + crownW} ${trunkTop - r} Q ${crownLeft + crownW} ${trunkTop} ${crownLeft + crownW - r} ${trunkTop} L ${crownLeft + r} ${trunkTop} Q ${crownLeft} ${trunkTop} ${crownLeft} ${trunkTop - r} L ${crownLeft} ${crownTop + r} Q ${crownLeft} ${crownTop} ${crownLeft + r} ${crownTop} Z`;
crownCenterY = (crownTop + trunkTop) / 2;
} else if (shape === 'kulová') {
const r = Math.min(crownW, crownH) / 2;
const cyC = trunkTop - r;
// Dva půlkruhové oblouky (sweep=1 → consistent CW circle)
crownPath = `M ${cx - r} ${cyC} a ${r} ${r} 0 1 1 ${2 * r} 0 a ${r} ${r} 0 1 1 ${-2 * r} 0 Z`;
crownCenterY = cyC;
} else {
// vejčitá — elipsa
const rx = crownW / 2;
const ry = crownH / 2;
const ccy = (crownTop + trunkTop) / 2;
crownPath = `M ${cx - rx} ${ccy} a ${rx} ${ry} 0 1 1 ${2 * rx} 0 a ${rx} ${ry} 0 1 1 ${-2 * rx} 0 Z`;
crownCenterY = ccy;
}
// Mikrohabitats — pozice
const crownBox = { x: crownLeft, y: crownTop, w: crownW, h: crownH };
const marks = useMemo(
() => microHabitatMarks(microHabitats, valuableMicroHabitats, cx, trunkW, trunkTop, groundY - 5, crownBox, seed),
[microHabitats, valuableMicroHabitats, cx, trunkW, trunkTop, groundY, crownBox.x, crownBox.y, crownBox.w, crownBox.h, seed]
);
// "Sun" pamatny strom - zlatá záře
return (
);
}
return TreeViz;
})();
window.TreeViz = TreeViz;