Граф: не перестраивать при ресайзе окна
- onWidth/HeightChanged теперь только clampPositions()+requestPaint, без force-симуляции и без сброса зума/панорамы/позиций - nodes/edges меняются парой -> дебаунс 120мс (requestRelayout), добавлен onEdgesChanged - relayout больше не трогает zoom/pan (resetView только по двойному клику), убран Math.random в пользу детерминированного сдвига, guard на нулевой размер
This commit is contained in:
@@ -23,9 +23,37 @@ Canvas {
|
||||
return null
|
||||
}
|
||||
|
||||
// Дебаунс перестройки на случай если nodes и edges меняются парой
|
||||
Timer {
|
||||
id: layoutDebounce
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: g.relayout()
|
||||
}
|
||||
function requestRelayout() { layoutDebounce.restart() }
|
||||
|
||||
// При ресайзе только подрезаем координаты и перерисовываем — без force-симуляции
|
||||
function clampPositions() {
|
||||
let changed = false
|
||||
for (const k in pos) {
|
||||
const p = pos[k]
|
||||
const nx = Math.max(30, Math.min(width - 30, p.x))
|
||||
const ny = Math.max(30, Math.min(height - 30, p.y))
|
||||
if (nx !== p.x || ny !== p.y) {
|
||||
pos[k] = { x: nx, y: ny }
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// присваивание нужно только если что-то вылезло за границы,
|
||||
// requestPaint вызываем в любом случае из обработчиков размера
|
||||
if (changed) pos = Object.assign({}, pos)
|
||||
}
|
||||
|
||||
function relayout() {
|
||||
const n = nodes.length
|
||||
if (n === 0) { pos = {}; requestPaint(); return }
|
||||
// Размер ещё не известен (создание компонента) — откладываем
|
||||
if (width < 50 || height < 50) { layoutDebounce.restart(); return }
|
||||
if (n > 600) {
|
||||
// Слишком много узлов — показываем упрощённо без симуляции чтобы не заморозить UI
|
||||
const cx = width / 2, cy = height / 2
|
||||
@@ -35,16 +63,17 @@ Canvas {
|
||||
const a = (i / n) * 2 * Math.PI - Math.PI / 2
|
||||
newPos[nodes[i].key] = { x: cx + rad * Math.cos(a), y: cy + rad * Math.sin(a) }
|
||||
}
|
||||
pos = newPos; zoom = 1.0; panX = 0; panY = 0; requestPaint(); return
|
||||
pos = newPos; requestPaint(); return
|
||||
}
|
||||
const cx = width / 2, cy = height / 2
|
||||
const rad = Math.min(width, height) / 2 - 36
|
||||
let newPos = {}
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const a = (i / n) * 2 * Math.PI - Math.PI / 2
|
||||
// небольшой джиттер чтобы не накладывались ровно
|
||||
const jitter = (Math.random() - 0.5) * 8
|
||||
newPos[nodes[i].key] = { x: cx + rad * Math.cos(a) + jitter, y: cy + rad * Math.sin(a) + jitter }
|
||||
// детерминированный сдвиг вместо Math.random — раскладка не прыгает при каждом ребилде
|
||||
const jitterX = ((i * 37) % 9) - 4
|
||||
const jitterY = ((i * 53) % 9) - 4
|
||||
newPos[nodes[i].key] = { x: cx + rad * Math.cos(a) + jitterX, y: cy + rad * Math.sin(a) + jitterY }
|
||||
}
|
||||
const iterCount = n > 200 ? 25 : (n > 100 ? 40 : 80)
|
||||
// простая force-симуляция для разведения узлов (репульсия + притяжение по рёбрам)
|
||||
@@ -89,13 +118,16 @@ Canvas {
|
||||
}
|
||||
}
|
||||
pos = newPos
|
||||
zoom = 1.0; panX = 0; panY = 0
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
onNodesChanged: relayout()
|
||||
onWidthChanged: relayout()
|
||||
onHeightChanged: relayout()
|
||||
function resetView() { zoom = 1.0; panX = 0; panY = 0; requestPaint() }
|
||||
|
||||
onNodesChanged: requestRelayout()
|
||||
onEdgesChanged: requestRelayout()
|
||||
// Ресайз окна: только перерисовка, БЕЗ перестройки графа и БЕЗ сброса зума/панорамы
|
||||
onWidthChanged: { clampPositions(); requestPaint() }
|
||||
onHeightChanged: { clampPositions(); requestPaint() }
|
||||
|
||||
// пустое состояние
|
||||
Text {
|
||||
@@ -213,6 +245,6 @@ Canvas {
|
||||
g.dragKey = ""
|
||||
g.requestPaint()
|
||||
}
|
||||
onDoubleClicked: { g.zoom = 1.0; g.panX = 0; g.panY = 0; g.relayout() }
|
||||
onDoubleClicked: { g.resetView(); g.relayout() }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user