1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2026-05-06 20:09:18 +08:00

Support circle pack as initial algorithm #136

This commit is contained in:
lana-k
2026-04-06 19:36:33 +02:00
parent d9435a80c3
commit c0d972c2ab
5 changed files with 74 additions and 72 deletions

View File

@@ -9,16 +9,13 @@
/> />
</Field> </Field>
<Field <component
v-if="modelValue.initialAlgorithm === 'random'" :is="layoutSettingsComponentMap[modelValue.initialAlgorithm]"
label="Seed value" v-if="modelValue.initialAlgorithm !== 'circular'"
fieldContainerClassName="test_fa2_seed_value" :model-value="modelValue"
> :keyOptions="keyOptions"
<NumericInput @update:model-value="this.$emit('update:modelValue', $event)"
:value="modelValue.seedValue" />
@update="update('seedValue', $event)"
/>
</Field>
</template> </template>
<script> <script>
@@ -28,12 +25,16 @@ import Field from 'react-chart-editor/lib/components/fields/Field'
import NumericInput from 'react-chart-editor/lib/components/widgets/NumericInput' import NumericInput from 'react-chart-editor/lib/components/widgets/NumericInput'
import Dropdown from 'react-chart-editor/lib/components/widgets/Dropdown' import Dropdown from 'react-chart-editor/lib/components/widgets/Dropdown'
import 'react-chart-editor/lib/react-chart-editor.css' import 'react-chart-editor/lib/react-chart-editor.css'
import CirclePackLayoutSettings from '@/components/Graph/CirclePackLayoutSettings.vue'
import RandomLayoutSettings from '@/components/Graph/RandomLayoutSettings.vue'
export default { export default {
components: { components: {
Field: applyPureReactInVue(Field), Field: applyPureReactInVue(Field),
Dropdown: applyPureReactInVue(Dropdown), Dropdown: applyPureReactInVue(Dropdown),
NumericInput: applyPureReactInVue(NumericInput) NumericInput: applyPureReactInVue(NumericInput),
RandomLayoutSettings,
CirclePackLayoutSettings
}, },
props: { props: {
modelValue: Object, modelValue: Object,
@@ -44,8 +45,13 @@ export default {
return { return {
layoutOptions: markRaw([ layoutOptions: markRaw([
{ label: 'Circular', value: 'circular' }, { label: 'Circular', value: 'circular' },
{ label: 'Random', value: 'random' } { label: 'Random', value: 'random' },
]) { label: 'Circle pack', value: 'circlepack' }
]),
layoutSettingsComponentMap: markRaw({
random: RandomLayoutSettings,
circlepack: CirclePackLayoutSettings
})
} }
}, },
methods: { methods: {

View File

@@ -337,6 +337,12 @@ export default {
circlepack: CirclePackLayoutSettings, circlepack: CirclePackLayoutSettings,
forceAtlas2: ForceAtlasLayoutSettings forceAtlas2: ForceAtlasLayoutSettings
}), }),
layoutMethodMap: markRaw({
circular: this.applyCircularLayout,
random: this.applyRandomLayout,
circlepack: this.applyCirclePackLayout,
forceAtlas2: this.applyFA2Layout
}),
selectedNodeId: undefined, selectedNodeId: undefined,
hoveredNodeId: undefined, hoveredNodeId: undefined,
selectedEdgeId: undefined, selectedEdgeId: undefined,
@@ -668,54 +674,10 @@ export default {
this.fa2Layout.kill() this.fa2Layout.kill()
} }
if (layoutType === 'circular') { this.layoutMethodMap[layoutType]()
this.applyCircularLayout()
return
}
if (layoutType === 'random') { if (layoutType === 'forceAtlas2' && layoutType !== prevLayout) {
this.applyRandomLayout() this.autoRunFA2Layout()
return
}
if (layoutType === 'circlepack') {
this.graph.forEachNode(nodeId => {
this.graph.updateNode(nodeId, attributes => {
const newAttributes = { ...attributes }
// Delete old hierarchy attributes
Object.keys(newAttributes)
.filter(key => key.startsWith('hierarchyAttribute'))
.forEach(
hierarchyAttributeKey =>
delete newAttributes[hierarchyAttributeKey]
)
// Set new hierarchy attributes
this.settings.layout.options.hierarchyAttributes?.forEach(
(hierarchyAttribute, index) => {
newAttributes['hierarchyAttribute' + index] =
attributes.data[hierarchyAttribute]
}
)
return newAttributes
})
})
circlepack.assign(this.graph, {
hierarchyAttributes:
this.settings.layout.options.hierarchyAttributes?.map(
(_, index) => 'hierarchyAttribute' + index
) || [],
rng: seedrandom(this.settings.layout.options.seedValue)
})
return
}
if (layoutType === 'forceAtlas2') {
this.applyFA2Layout()
if (layoutType !== prevLayout) {
this.autoRunFA2Layout()
}
} }
}, },
applyCircularLayout() { applyCircularLayout() {
@@ -726,6 +688,37 @@ export default {
rng: seedrandom(this.settings.layout.options.seedValue) rng: seedrandom(this.settings.layout.options.seedValue)
}) })
}, },
applyCirclePackLayout() {
this.graph.forEachNode(nodeId => {
this.graph.updateNode(nodeId, attributes => {
const newAttributes = { ...attributes }
// Delete old hierarchy attributes
Object.keys(newAttributes)
.filter(key => key.startsWith('hierarchyAttribute'))
.forEach(
hierarchyAttributeKey =>
delete newAttributes[hierarchyAttributeKey]
)
// Set new hierarchy attributes
this.settings.layout.options.hierarchyAttributes?.forEach(
(hierarchyAttribute, index) => {
newAttributes['hierarchyAttribute' + index] =
attributes.data[hierarchyAttribute]
}
)
return newAttributes
})
})
circlepack.assign(this.graph, {
hierarchyAttributes:
this.settings.layout.options.hierarchyAttributes?.map(
(_, index) => 'hierarchyAttribute' + index
) || [],
rng: seedrandom(this.settings.layout.options.seedValue)
})
},
applyFA2Layout() { applyFA2Layout() {
if ( if (
!this.graph.someNode( !this.graph.someNode(
@@ -733,20 +726,22 @@ export default {
typeof attributes.x === 'number' && typeof attributes.y === 'number' typeof attributes.x === 'number' && typeof attributes.y === 'number'
) )
) { ) {
if (this.settings.layout.options.initialAlgorithm === 'circular') { this.layoutMethodMap[this.settings.layout.options.initialAlgorithm]()
this.applyCircularLayout()
} else {
this.applyRandomLayout()
}
} }
const fa2settings = { ...this.settings.layout.options }
// delete all custom settings because they can break the algorithm running
delete fa2settings.initialAlgorithm
delete fa2settings.seedValue
delete fa2settings.initialIterationsAmount
delete fa2settings.hierarchyAttributes
this.fa2Layout = markRaw( this.fa2Layout = markRaw(
new FA2Layout(this.graph, { new FA2Layout(this.graph, {
getEdgeWeight: (_, attr) => getEdgeWeight: (_, attr) =>
this.settings.layout.options.weightSource this.settings.layout.options.weightSource
? attr.data[this.settings.layout.options.weightSource] ? attr.data[this.settings.layout.options.weightSource]
: 1, : 1,
settings: this.settings.layout.options settings: fa2settings
}) })
) )
}, },
@@ -773,6 +768,7 @@ export default {
if (this.fa2Layout.isRunning()) { if (this.fa2Layout.isRunning()) {
this.stopFA2Layout() this.stopFA2Layout()
} }
this.fa2Layout.kill()
clearNodeCoordinates(this.graph) clearNodeCoordinates(this.graph)
this.applyFA2Layout() this.applyFA2Layout()
this.autoRunFA2Layout() this.autoRunFA2Layout()

View File

@@ -1,5 +1,5 @@
<template> <template>
<Field label="Seed value"> <Field label="Seed value" fieldContainerClassName="test_seed_value">
<NumericInput <NumericInput
:value="modelValue.seedValue" :value="modelValue.seedValue"
@update="update('seedValue', $event)" @update="update('seedValue', $event)"

View File

@@ -38,8 +38,8 @@ export function dataSourceIsValid(dataSources) {
export function clearNodeCoordinates(graph) { export function clearNodeCoordinates(graph) {
graph.forEachNode((nodeId, attributes) => { graph.forEachNode((nodeId, attributes) => {
delete attributes.x attributes.x = undefined
delete attributes.y attributes.y = undefined
}) })
} }

View File

@@ -1423,7 +1423,7 @@ describe('GraphEditor', () => {
) )
// Change seed value // Change seed value
const seedValueInput = wrapper.find('.test_fa2_seed_value input') const seedValueInput = wrapper.find('.test_seed_value input')
await seedValueInput.setValue(123) await seedValueInput.setValue(123)
seedValueInput.wrapperElement.dispatchEvent( seedValueInput.wrapperElement.dispatchEvent(
new Event('blur', { bubbles: true }) new Event('blur', { bubbles: true })
@@ -1659,7 +1659,7 @@ describe('GraphEditor', () => {
.join() .join()
// Change seed value // Change seed value
const seedValueInput = wrapper.find('.test_fa2_seed_value input') const seedValueInput = wrapper.find('.test_seed_value input')
await seedValueInput.setValue(123) await seedValueInput.setValue(123)
seedValueInput.wrapperElement.dispatchEvent( seedValueInput.wrapperElement.dispatchEvent(
new Event('blur', { bubbles: true }) new Event('blur', { bubbles: true })
@@ -1787,7 +1787,7 @@ describe('GraphEditor', () => {
await nextTick() await nextTick()
// Change seed value // Change seed value
const seedValueInput = wrapper.find('.test_fa2_seed_value input') const seedValueInput = wrapper.find('.test_seed_value input')
await seedValueInput.setValue(123) await seedValueInput.setValue(123)
seedValueInput.wrapperElement.dispatchEvent( seedValueInput.wrapperElement.dispatchEvent(
new Event('blur', { bubbles: true }) new Event('blur', { bubbles: true })