Fix spouse overlap, reset defaults to 3 gen, remove sliders
- Add collision resolution pass after Y-snapping: sweeps each generation row left-to-right pushing overlapping cards apart, then re-centers the row to prevent drift - Reset default generations to 3 (up from 4) to avoid layout issues - Remove generation sliders from chart page for simplicity - Add sync.sh for deployment to server
This commit is contained in:
2
resources/js/full-diagram.min.js
vendored
2
resources/js/full-diagram.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -338,7 +338,7 @@ function extractPositions(elkResult, builder, config) {
|
|||||||
if (raw) unionGenY.set(id, raw.cy);
|
if (raw) unionGenY.set(id, raw.cy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 4: Build final positioned nodes (centered on root) ──
|
// ── Step 4: Build initial positions (centered on root) ──
|
||||||
const posMap = new Map(); // id → { x, y }
|
const posMap = new Map(); // id → { x, y }
|
||||||
|
|
||||||
// Recalculate rootY using the snapped generation Y
|
// Recalculate rootY using the snapped generation Y
|
||||||
@@ -359,17 +359,65 @@ function extractPositions(elkResult, builder, config) {
|
|||||||
|
|
||||||
const finalX = raw.cx - rootX;
|
const finalX = raw.cx - rootX;
|
||||||
posMap.set(id, { x: finalX, y: finalY });
|
posMap.set(id, { x: finalX, y: finalY });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Step 4b: Resolve overlaps per generation row ──
|
||||||
|
// After Y-snapping, nodes from different ELK layers may now share a row.
|
||||||
|
// For each generation, sort by X and push overlapping nodes apart.
|
||||||
|
const minGap = config.cardWidth + config.horizontalSpacing;
|
||||||
|
|
||||||
|
for (const [gen, nodes] of genGroups) {
|
||||||
|
// Get current X positions for this generation's person nodes
|
||||||
|
const rowNodes = nodes
|
||||||
|
.map((n) => ({ id: n.id, x: posMap.get(n.id)?.x ?? 0 }))
|
||||||
|
.sort((a, b) => a.x - b.x);
|
||||||
|
|
||||||
|
// Sweep left to right, push overlapping nodes to the right
|
||||||
|
for (let i = 1; i < rowNodes.length; i++) {
|
||||||
|
const prev = rowNodes[i - 1];
|
||||||
|
const curr = rowNodes[i];
|
||||||
|
const overlap = prev.x + minGap - curr.x;
|
||||||
|
if (overlap > 0) {
|
||||||
|
curr.x = prev.x + minGap;
|
||||||
|
const pos = posMap.get(curr.id);
|
||||||
|
if (pos) pos.x = curr.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-center the row so shifts don't drift the whole diagram
|
||||||
|
// Calculate how much the row center shifted and compensate
|
||||||
|
const origXs = nodes
|
||||||
|
.map((n) => rawPos.get(n.id)?.cx ?? 0)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
const newXs = rowNodes.map((n) => n.x).sort((a, b) => a - b);
|
||||||
|
const origCenter = (origXs[0] + origXs[origXs.length - 1]) / 2 - rootX;
|
||||||
|
const newCenter = (newXs[0] + newXs[newXs.length - 1]) / 2;
|
||||||
|
const drift = newCenter - origCenter;
|
||||||
|
|
||||||
|
if (Math.abs(drift) > 1) {
|
||||||
|
for (const rn of rowNodes) {
|
||||||
|
rn.x -= drift;
|
||||||
|
const pos = posMap.get(rn.id);
|
||||||
|
if (pos) pos.x = rn.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Step 4c: Collect final positioned nodes ──
|
||||||
|
for (const [id, node] of builder.nodes) {
|
||||||
|
const pos = posMap.get(id);
|
||||||
|
if (!pos) continue;
|
||||||
|
|
||||||
if (node.type === "person") {
|
if (node.type === "person") {
|
||||||
persons.push({
|
persons.push({
|
||||||
x: finalX,
|
x: pos.x,
|
||||||
y: finalY,
|
y: pos.y,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
isMain: node.isMain,
|
isMain: node.isMain,
|
||||||
data: node.data,
|
data: node.data,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
unions.push({ id: id, x: finalX, y: finalY });
|
unions.push({ id: id, x: pos.x, y: pos.y });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ use FullDiagram\Configuration;
|
|||||||
* @var int $ancestor_generations
|
* @var int $ancestor_generations
|
||||||
* @var int $descendant_generations
|
* @var int $descendant_generations
|
||||||
* @var bool $show_siblings
|
* @var bool $show_siblings
|
||||||
* @var int $max_generations
|
|
||||||
* @var int $min_generations
|
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -35,47 +33,6 @@ use FullDiagram\Configuration;
|
|||||||
|
|
||||||
<h2 class="wt-page-title"><?= $title ?></h2>
|
<h2 class="wt-page-title"><?= $title ?></h2>
|
||||||
|
|
||||||
<div class="wt-page-options wt-page-options-chart d-print-none mb-3">
|
|
||||||
<div class="row g-3 align-items-end">
|
|
||||||
<div class="col-auto">
|
|
||||||
<label for="ancestor_generations" class="form-label">
|
|
||||||
<?= I18N::translate('Generations of ancestors') ?>
|
|
||||||
</label>
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
class="form-range"
|
|
||||||
id="ancestor_generations"
|
|
||||||
min="<?= $min_generations ?>"
|
|
||||||
max="<?= $max_generations ?>"
|
|
||||||
value="<?= $ancestor_generations ?>"
|
|
||||||
style="width: 120px;"
|
|
||||||
>
|
|
||||||
<span id="ancestor_gen_label" class="badge bg-secondary"><?= $ancestor_generations ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-auto">
|
|
||||||
<label for="descendant_generations" class="form-label">
|
|
||||||
<?= I18N::translate('Generations of descendants') ?>
|
|
||||||
</label>
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
class="form-range"
|
|
||||||
id="descendant_generations"
|
|
||||||
min="<?= $min_generations ?>"
|
|
||||||
max="<?= $max_generations ?>"
|
|
||||||
value="<?= $descendant_generations ?>"
|
|
||||||
style="width: 120px;"
|
|
||||||
>
|
|
||||||
<span id="descendant_gen_label" class="badge bg-secondary"><?= $descendant_generations ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?= view($module->name() . '::modules/full-diagram/chart', [
|
<?= view($module->name() . '::modules/full-diagram/chart', [
|
||||||
'module' => $module,
|
'module' => $module,
|
||||||
'individual' => $individual,
|
'individual' => $individual,
|
||||||
@@ -87,42 +44,3 @@ use FullDiagram\Configuration;
|
|||||||
'descendant_generations' => $descendant_generations,
|
'descendant_generations' => $descendant_generations,
|
||||||
'show_siblings' => $show_siblings,
|
'show_siblings' => $show_siblings,
|
||||||
]) ?>
|
]) ?>
|
||||||
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
var baseChartUrl = <?= json_encode(route($module::ROUTE_NAME, [
|
|
||||||
'tree' => $tree->name(),
|
|
||||||
'xref' => $individual->xref(),
|
|
||||||
]), JSON_THROW_ON_ERROR) ?>;
|
|
||||||
|
|
||||||
var timer = null;
|
|
||||||
|
|
||||||
function navigate() {
|
|
||||||
var ag = document.getElementById('ancestor_generations').value;
|
|
||||||
var dg = document.getElementById('descendant_generations').value;
|
|
||||||
var sep = baseChartUrl.indexOf('?') === -1 ? '?' : '&';
|
|
||||||
window.location.href = baseChartUrl + sep
|
|
||||||
+ 'ancestor_generations=' + ag
|
|
||||||
+ '&descendant_generations=' + dg
|
|
||||||
+ '&show_siblings=1';
|
|
||||||
}
|
|
||||||
|
|
||||||
function scheduleNavigate() {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(navigate, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ag = document.getElementById('ancestor_generations');
|
|
||||||
var dg = document.getElementById('descendant_generations');
|
|
||||||
|
|
||||||
ag.addEventListener('input', function() {
|
|
||||||
document.getElementById('ancestor_gen_label').textContent = this.value;
|
|
||||||
});
|
|
||||||
dg.addEventListener('input', function() {
|
|
||||||
document.getElementById('descendant_gen_label').textContent = this.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
ag.addEventListener('change', scheduleNavigate);
|
|
||||||
dg.addEventListener('change', scheduleNavigate);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ namespace FullDiagram;
|
|||||||
class Configuration
|
class Configuration
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly int $ancestorGenerations = 4,
|
private readonly int $ancestorGenerations = 3,
|
||||||
private readonly int $descendantGenerations = 4,
|
private readonly int $descendantGenerations = 3,
|
||||||
private readonly bool $showSiblings = true,
|
private readonly bool $showSiblings = true,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ class Module extends AbstractModule implements ModuleChartInterface, ModuleCusto
|
|||||||
public const ROUTE_NAME = 'full-diagram';
|
public const ROUTE_NAME = 'full-diagram';
|
||||||
public const ROUTE_URL = '/tree/{tree}/full-diagram/{xref}';
|
public const ROUTE_URL = '/tree/{tree}/full-diagram/{xref}';
|
||||||
|
|
||||||
private const DEFAULT_ANCESTOR_GENERATIONS = 4;
|
private const DEFAULT_ANCESTOR_GENERATIONS = 3;
|
||||||
private const DEFAULT_DESCENDANT_GENERATIONS = 4;
|
private const DEFAULT_DESCENDANT_GENERATIONS = 3;
|
||||||
private const BLOCK_DEFAULT_ANCESTOR_GENS = 4;
|
private const BLOCK_DEFAULT_ANCESTOR_GENS = 3;
|
||||||
private const BLOCK_DEFAULT_DESCENDANT_GENS = 4;
|
private const BLOCK_DEFAULT_DESCENDANT_GENS = 3;
|
||||||
private const MINIMUM_GENERATIONS = 1;
|
private const MINIMUM_GENERATIONS = 1;
|
||||||
private const MAXIMUM_GENERATIONS = 10;
|
private const MAXIMUM_GENERATIONS = 10;
|
||||||
|
|
||||||
|
|||||||
13
sync.sh
Executable file
13
sync.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
rsync -avz --delete \
|
||||||
|
--exclude 'node_modules' \
|
||||||
|
--exclude '.git' \
|
||||||
|
--exclude 'docker' \
|
||||||
|
--exclude 'build' \
|
||||||
|
--exclude 'resources/js/modules' \
|
||||||
|
--exclude 'rollup.config.js' \
|
||||||
|
--exclude 'package.json' \
|
||||||
|
--exclude 'package-lock.json' \
|
||||||
|
--exclude 'CLAUDE.md' \
|
||||||
|
--exclude 'sync.sh' \
|
||||||
|
./ root@bocken.org:/usr/share/webapps/webtrees/modules_v4/full-diagram/
|
||||||
Reference in New Issue
Block a user