mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 10:08:52 +08:00
autostart, reset and fixes
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@*": ["./src/*"]
|
"@\/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
117
src/components/Graph/AdvancedForceAtlasLayoutSettings.vue
Normal file
117
src/components/Graph/AdvancedForceAtlasLayoutSettings.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<Field label="Adjust sizes">
|
||||||
|
<RadioBlocks
|
||||||
|
:options="booleanOptions"
|
||||||
|
:activeOption="modelValue.adjustSizes"
|
||||||
|
@option-change="update('adjustSizes', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Barnes-Hut optimize">
|
||||||
|
<RadioBlocks
|
||||||
|
:options="booleanOptions"
|
||||||
|
:activeOption="modelValue.barnesHutOptimize"
|
||||||
|
@option-change="update('barnesHutOptimize', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field v-show="modelValue.barnesHutOptimize" label="Barnes-Hut Theta">
|
||||||
|
<NumericInput
|
||||||
|
:value="modelValue.barnesHutTheta"
|
||||||
|
@update="update('barnesHutTheta', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Gravity">
|
||||||
|
<NumericInput
|
||||||
|
:value="modelValue.gravity"
|
||||||
|
@update="update('gravity', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Strong gravity mode">
|
||||||
|
<RadioBlocks
|
||||||
|
:options="booleanOptions"
|
||||||
|
:activeOption="modelValue.strongGravityMode"
|
||||||
|
@option-change="update('strongGravityMode', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Noack's LinLog model">
|
||||||
|
<RadioBlocks
|
||||||
|
:options="booleanOptions"
|
||||||
|
:activeOption="modelValue.linLogMode"
|
||||||
|
@option-change="update('linLogMode', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Out bound attraction distribution">
|
||||||
|
<RadioBlocks
|
||||||
|
:options="booleanOptions"
|
||||||
|
:activeOption="modelValue.outboundAttractionDistribution"
|
||||||
|
@option-change="update('outboundAttractionDistribution', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Slow down">
|
||||||
|
<NumericInput
|
||||||
|
:value="modelValue.slowDown"
|
||||||
|
:min="0"
|
||||||
|
@update="update('slowDown', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Edge weight">
|
||||||
|
<Dropdown
|
||||||
|
:options="keyOptions"
|
||||||
|
:value="modelValue.weightSource"
|
||||||
|
@change="update('weightSource', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field v-show="modelValue.weightSource" label="Edge weight influence">
|
||||||
|
<NumericInput
|
||||||
|
:value="modelValue.edgeWeightInfluence"
|
||||||
|
@update="update('edgeWeightInfluence', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { markRaw } from 'vue'
|
||||||
|
import { applyPureReactInVue } from 'veaury'
|
||||||
|
import Field from 'react-chart-editor/lib/components/fields/Field'
|
||||||
|
import RadioBlocks from 'react-chart-editor/lib/components/widgets/RadioBlocks'
|
||||||
|
import NumericInput from 'react-chart-editor/lib/components/widgets/NumericInput'
|
||||||
|
import Dropdown from 'react-chart-editor/lib/components/widgets/Dropdown'
|
||||||
|
import 'react-chart-editor/lib/react-chart-editor.css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Field: applyPureReactInVue(Field),
|
||||||
|
RadioBlocks: applyPureReactInVue(RadioBlocks),
|
||||||
|
Dropdown: applyPureReactInVue(Dropdown),
|
||||||
|
NumericInput: applyPureReactInVue(NumericInput)
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: Object,
|
||||||
|
keyOptions: Array
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
booleanOptions: markRaw([
|
||||||
|
{ label: 'Yes', value: true },
|
||||||
|
{ label: 'No', value: false }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update(attributeName, value) {
|
||||||
|
this.$emit('update:modelValue', {
|
||||||
|
...this.modelValue,
|
||||||
|
[attributeName]: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,55 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Adjust sizes">
|
<Field label="Initial iterations">
|
||||||
<RadioBlocks
|
|
||||||
:options="booleanOptions"
|
|
||||||
:activeOption="modelValue.adjustSizes"
|
|
||||||
@option-change="update('adjustSizes', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Barnes-Hut optimize">
|
|
||||||
<RadioBlocks
|
|
||||||
:options="booleanOptions"
|
|
||||||
:activeOption="modelValue.barnesHutOptimize"
|
|
||||||
@option-change="update('barnesHutOptimize', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field v-show="modelValue.barnesHutOptimize" label="Barnes-Hut Theta">
|
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.barnesHutTheta"
|
:value="modelValue.initialIterationsAmount"
|
||||||
@update="update('barnesHutTheta', $event)"
|
:min="1"
|
||||||
/>
|
@update="update('initialIterationsAmount', $event)"
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Gravity">
|
|
||||||
<NumericInput
|
|
||||||
:value="modelValue.gravity"
|
|
||||||
@update="update('gravity', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Strong gravity mode">
|
|
||||||
<RadioBlocks
|
|
||||||
:options="booleanOptions"
|
|
||||||
:activeOption="modelValue.strongGravityMode"
|
|
||||||
@option-change="update('strongGravityMode', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Noack's LinLog model">
|
|
||||||
<RadioBlocks
|
|
||||||
:options="booleanOptions"
|
|
||||||
:activeOption="modelValue.linLogMode"
|
|
||||||
@option-change="update('linLogMode', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Out bound attraction distribution">
|
|
||||||
<RadioBlocks
|
|
||||||
:options="booleanOptions"
|
|
||||||
:activeOption="modelValue.outboundAttractionDistribution"
|
|
||||||
@option-change="update('outboundAttractionDistribution', $event)"
|
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
@@ -59,45 +13,17 @@
|
|||||||
@update="update('scalingRatio', $event)"
|
@update="update('scalingRatio', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Slow down">
|
|
||||||
<NumericInput
|
|
||||||
:value="modelValue.slowDown"
|
|
||||||
:min="1"
|
|
||||||
@update="update('slowDown', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Edge weight influence">
|
|
||||||
<NumericInput
|
|
||||||
:value="modelValue.edgeWeightInfluence"
|
|
||||||
@update="update('edgeWeightInfluence', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Edge weight">
|
|
||||||
<Dropdown
|
|
||||||
:options="keyOptions"
|
|
||||||
:value="modelValue.weightSource"
|
|
||||||
@change="update('weightSource', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { markRaw } from 'vue'
|
|
||||||
import { applyPureReactInVue } from 'veaury'
|
import { applyPureReactInVue } from 'veaury'
|
||||||
import Field from 'react-chart-editor/lib/components/fields/Field'
|
import Field from 'react-chart-editor/lib/components/fields/Field'
|
||||||
import RadioBlocks from 'react-chart-editor/lib/components/widgets/RadioBlocks'
|
|
||||||
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 'react-chart-editor/lib/react-chart-editor.css'
|
import 'react-chart-editor/lib/react-chart-editor.css'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Field: applyPureReactInVue(Field),
|
Field: applyPureReactInVue(Field),
|
||||||
RadioBlocks: applyPureReactInVue(RadioBlocks),
|
|
||||||
Dropdown: applyPureReactInVue(Dropdown),
|
|
||||||
NumericInput: applyPureReactInVue(NumericInput)
|
NumericInput: applyPureReactInVue(NumericInput)
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -105,14 +31,6 @@ export default {
|
|||||||
keyOptions: Array
|
keyOptions: Array
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
booleanOptions: markRaw([
|
|
||||||
{ label: 'Yes', value: true },
|
|
||||||
{ label: 'No', value: false }
|
|
||||||
])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
update(attributeName, value) {
|
update(attributeName, value) {
|
||||||
this.$emit('update:modelValue', {
|
this.$emit('update:modelValue', {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export function buildNodes(graph, dataSources, options) {
|
|||||||
const nodes = dataSources[docColumn]
|
const nodes = dataSources[docColumn]
|
||||||
.map(json => JSON.parse(json))
|
.map(json => JSON.parse(json))
|
||||||
.filter(item => item[objectType] === TYPE_NODE)
|
.filter(item => item[objectType] === TYPE_NODE)
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
graph.addNode(node[nodeId], {
|
graph.addNode(node[nodeId], {
|
||||||
data: node,
|
data: node,
|
||||||
@@ -89,8 +88,8 @@ export function updateEdges(graph, attributeUpdates) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graph.forEachEdge((edgeId, attributes, source, target) => {
|
graph.forEachEdge((edgeId, attributes, source, target) => {
|
||||||
graph.updateEdge(source, target, attributes => {
|
graph.updateEdgeWithKey(edgeId, source, target, attr => {
|
||||||
const newAttributes = { ...attributes }
|
const newAttributes = { ...attr }
|
||||||
changeMethods.forEach(method => method(newAttributes, edgeId))
|
changeMethods.forEach(method => method(newAttributes, edgeId))
|
||||||
return newAttributes
|
return newAttributes
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -133,8 +133,19 @@
|
|||||||
:keyOptions="keysOptions"
|
:keyOptions="keysOptions"
|
||||||
@update:model-value="updateLayout(settings.layout.type)"
|
@update:model-value="updateLayout(settings.layout.type)"
|
||||||
/>
|
/>
|
||||||
|
</Fold>
|
||||||
<Field v-if="settings.layout.type === 'forceAtlas2'">
|
<template v-if="settings.layout.type === 'forceAtlas2'">
|
||||||
|
<Fold name="Advanced layout settings">
|
||||||
|
<AdvancedForceAtlasLayoutSettings
|
||||||
|
v-model="settings.layout.options"
|
||||||
|
:keyOptions="keysOptions"
|
||||||
|
@update:model-value="updateLayout(settings.layout.type)"
|
||||||
|
/>
|
||||||
|
</Fold>
|
||||||
|
<div class="force-atlas-buttons">
|
||||||
|
<Button variant="secondary" @click="resetFA2LayoutSettings">
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
<Button variant="primary" @click="toggleFA2Layout">
|
<Button variant="primary" @click="toggleFA2Layout">
|
||||||
<template #node:icon>
|
<template #node:icon>
|
||||||
<div
|
<div
|
||||||
@@ -143,12 +154,13 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<RunIcon v-if="!fa2Running" />
|
<RunIcon v-if="!fa2Running" />
|
||||||
<StopIcon v-else /></div
|
<StopIcon v-else />
|
||||||
></template>
|
</div>
|
||||||
|
</template>
|
||||||
{{ fa2Running ? 'Stop' : 'Start' }}
|
{{ fa2Running ? 'Stop' : 'Start' }}
|
||||||
</Button>
|
</Button>
|
||||||
</Field>
|
</div>
|
||||||
</Fold>
|
</template>
|
||||||
</Panel>
|
</Panel>
|
||||||
</PanelMenuWrapper>
|
</PanelMenuWrapper>
|
||||||
</GraphEditorControls>
|
</GraphEditorControls>
|
||||||
@@ -176,6 +188,7 @@ import Button from 'react-chart-editor/lib/components/widgets/Button'
|
|||||||
import Field from 'react-chart-editor/lib/components/fields/Field'
|
import Field from 'react-chart-editor/lib/components/fields/Field'
|
||||||
import RandomLayoutSettings from '@/components/Graph/RandomLayoutSettings.vue'
|
import RandomLayoutSettings from '@/components/Graph/RandomLayoutSettings.vue'
|
||||||
import ForceAtlasLayoutSettings from '@/components/Graph/ForceAtlasLayoutSettings.vue'
|
import ForceAtlasLayoutSettings from '@/components/Graph/ForceAtlasLayoutSettings.vue'
|
||||||
|
import AdvancedForceAtlasLayoutSettings from '@/components/Graph/AdvancedForceAtlasLayoutSettings.vue'
|
||||||
import CirclePackLayoutSettings from '@/components/Graph/CirclePackLayoutSettings.vue'
|
import CirclePackLayoutSettings from '@/components/Graph/CirclePackLayoutSettings.vue'
|
||||||
import FA2Layout from 'graphology-layout-forceatlas2/worker'
|
import FA2Layout from 'graphology-layout-forceatlas2/worker'
|
||||||
import forceAtlas2 from 'graphology-layout-forceatlas2'
|
import forceAtlas2 from 'graphology-layout-forceatlas2'
|
||||||
@@ -216,7 +229,8 @@ export default {
|
|||||||
NodeColorSettings,
|
NodeColorSettings,
|
||||||
NodeSizeSettings,
|
NodeSizeSettings,
|
||||||
EdgeSizeSettings,
|
EdgeSizeSettings,
|
||||||
EdgeColorSettings
|
EdgeColorSettings,
|
||||||
|
AdvancedForceAtlasLayoutSettings
|
||||||
},
|
},
|
||||||
inject: ['tabLayout'],
|
inject: ['tabLayout'],
|
||||||
props: {
|
props: {
|
||||||
@@ -226,10 +240,11 @@ export default {
|
|||||||
emits: ['update'],
|
emits: ['update'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
graph: new Graph(),
|
graph: new Graph({ multi: true, allowSelfLoops: true }),
|
||||||
renderer: null,
|
renderer: null,
|
||||||
fa2Layout: null,
|
fa2Layout: null,
|
||||||
fa2Running: false,
|
fa2Running: false,
|
||||||
|
checkIteration: null,
|
||||||
visibilityOptions: markRaw([
|
visibilityOptions: markRaw([
|
||||||
{ label: 'Show', value: true },
|
{ label: 'Show', value: true },
|
||||||
{ label: 'Hide', value: false }
|
{ label: 'Hide', value: false }
|
||||||
@@ -246,7 +261,9 @@ export default {
|
|||||||
forceAtlas2: ForceAtlasLayoutSettings
|
forceAtlas2: ForceAtlasLayoutSettings
|
||||||
}),
|
}),
|
||||||
|
|
||||||
settings: this.initOptions || {
|
settings: this.initOptions
|
||||||
|
? JSON.parse(JSON.stringify(this.initOptions))
|
||||||
|
: {
|
||||||
structure: {
|
structure: {
|
||||||
nodeId: null,
|
nodeId: null,
|
||||||
objectType: null,
|
objectType: null,
|
||||||
@@ -370,13 +387,16 @@ export default {
|
|||||||
updateNodes(this.graph, this.settings.style.nodes)
|
updateNodes(this.graph, this.settings.style.nodes)
|
||||||
updateEdges(this.graph, this.settings.style.edges)
|
updateEdges(this.graph, this.settings.style.edges)
|
||||||
|
|
||||||
circular.assign(this.graph)
|
this.updateLayout(this.settings.layout.type)
|
||||||
this.renderer = new Sigma(this.graph, this.$refs.graph, {
|
this.renderer = new Sigma(this.graph, this.$refs.graph, {
|
||||||
renderEdgeLabels: true,
|
renderEdgeLabels: true,
|
||||||
allowInvalidContainer: true,
|
allowInvalidContainer: true,
|
||||||
labelColor: { attribute: 'labelColor', color: '#444444' },
|
labelColor: { attribute: 'labelColor', color: '#444444' },
|
||||||
edgeLabelColor: { attribute: 'labelColor', color: '#a2b1c6' }
|
edgeLabelColor: { attribute: 'labelColor', color: '#a2b1c6' }
|
||||||
})
|
})
|
||||||
|
if (this.settings.layout.type === 'forceAtlas2') {
|
||||||
|
this.autoRunFA2Layout()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateStructure(attributeName, value) {
|
updateStructure(attributeName, value) {
|
||||||
this.settings.structure[attributeName] = value
|
this.settings.structure[attributeName] = value
|
||||||
@@ -410,41 +430,33 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateLayout(layoutType) {
|
updateLayout(layoutType) {
|
||||||
if (layoutType !== this.settings.layout.type) {
|
|
||||||
const prevLayout = this.settings.layout.type
|
const prevLayout = this.settings.layout.type
|
||||||
|
|
||||||
|
// Change layout type? - restore layout settings or set default settings
|
||||||
|
if (layoutType !== prevLayout) {
|
||||||
this.layoutOptionsArchive[prevLayout] = this.settings.layout.options
|
this.layoutOptionsArchive[prevLayout] = this.settings.layout.options
|
||||||
this.settings.layout.options = this.layoutOptionsArchive[layoutType]
|
this.settings.layout.options = this.layoutOptionsArchive[layoutType]
|
||||||
|
|
||||||
if (layoutType === 'forceAtlas2' && !this.settings.layout.options) {
|
if (!this.settings.layout.options) {
|
||||||
const sensibleSettings = forceAtlas2.inferSettings(this.graph)
|
if (layoutType === 'forceAtlas2') {
|
||||||
this.settings.layout.options = {
|
this.setRecommendedFA2Settings()
|
||||||
adjustSizes: false,
|
} else if (['random', 'circlepack'].includes(layoutType)) {
|
||||||
barnesHutOptimize: false,
|
|
||||||
barnesHutTheta: 0.5,
|
|
||||||
edgeWeightInfluence: 1,
|
|
||||||
gravity: 1,
|
|
||||||
linLogMode: false,
|
|
||||||
outboundAttractionDistribution: false,
|
|
||||||
scalingRatio: 1,
|
|
||||||
slowDown: 1,
|
|
||||||
strongGravityMode: false,
|
|
||||||
...sensibleSettings
|
|
||||||
}
|
|
||||||
} else if (layoutType === 'random' && !this.settings.layout.options) {
|
|
||||||
this.settings.layout.options = {
|
this.settings.layout.options = {
|
||||||
seedValue: 1
|
seedValue: 1
|
||||||
}
|
}
|
||||||
} else if (
|
|
||||||
layoutType === 'circlepack' &&
|
|
||||||
!this.settings.layout.options
|
|
||||||
) {
|
|
||||||
this.settings.layout.options = {
|
|
||||||
seedValue: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.settings.layout.type = layoutType
|
this.settings.layout.type = layoutType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In any case kill FA2 if it exists
|
||||||
|
if (this.fa2Layout) {
|
||||||
|
if (this.fa2Layout.isRunning()) {
|
||||||
|
this.stopFA2Layout()
|
||||||
|
}
|
||||||
|
this.fa2Layout.kill()
|
||||||
|
}
|
||||||
|
|
||||||
if (layoutType === 'circular') {
|
if (layoutType === 'circular') {
|
||||||
circular.assign(this.graph)
|
circular.assign(this.graph)
|
||||||
return
|
return
|
||||||
@@ -491,27 +503,99 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (layoutType === 'forceAtlas2') {
|
if (layoutType === 'forceAtlas2') {
|
||||||
if (this.fa2Layout) {
|
if (
|
||||||
this.fa2Layout.kill()
|
!this.graph.someNode(
|
||||||
|
(nodeKey, attributes) =>
|
||||||
|
typeof attributes.x === 'number' &&
|
||||||
|
typeof attributes.y === 'number'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
circular.assign(this.graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fa2Layout = markRaw(
|
this.fa2Layout = markRaw(
|
||||||
new FA2Layout(this.graph, {
|
new FA2Layout(this.graph, {
|
||||||
getEdgeWeight: (_, attr) =>
|
getEdgeWeight: (_, attr) =>
|
||||||
attr.data[this.settings.layout.options.weightSource || 'weight'],
|
this.settings.layout.options.weightSource
|
||||||
|
? attr.data[this.settings.layout.options.weightSource]
|
||||||
|
: 1,
|
||||||
settings: this.settings.layout.options
|
settings: this.settings.layout.options
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
if (layoutType !== prevLayout) {
|
||||||
|
this.autoRunFA2Layout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleFA2Layout() {
|
toggleFA2Layout() {
|
||||||
if (this.fa2Layout.isRunning()) {
|
if (this.fa2Layout.isRunning()) {
|
||||||
this.fa2Running = false
|
this.stopFA2Layout()
|
||||||
this.fa2Layout.stop()
|
|
||||||
} else {
|
} else {
|
||||||
this.fa2Running = true
|
this.fa2Running = true
|
||||||
this.fa2Layout.start()
|
this.fa2Layout.start()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
stopFA2Layout() {
|
||||||
|
this.fa2Running = false
|
||||||
|
this.fa2Layout.stop()
|
||||||
|
if (this.checkIteration) {
|
||||||
|
this.fa2Layout.worker.removeEventListener(
|
||||||
|
'message',
|
||||||
|
this.checkIteration
|
||||||
|
)
|
||||||
|
this.checkIteration = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoRunFA2Layout() {
|
||||||
|
if (this.fa2Layout.isRunning()) {
|
||||||
|
this.stopFA2Layout()
|
||||||
|
}
|
||||||
|
|
||||||
|
let iteration = 1
|
||||||
|
this.checkIteration = () => {
|
||||||
|
if (
|
||||||
|
iteration === this.settings.layout.options.initialIterationsAmount
|
||||||
|
) {
|
||||||
|
this.stopFA2Layout()
|
||||||
|
}
|
||||||
|
iteration++
|
||||||
|
}
|
||||||
|
this.fa2Layout.worker.addEventListener('message', this.checkIteration)
|
||||||
|
this.fa2Running = true
|
||||||
|
this.fa2Layout.start()
|
||||||
|
},
|
||||||
|
setRecommendedFA2Settings() {
|
||||||
|
const sensibleSettings = forceAtlas2.inferSettings(this.graph)
|
||||||
|
this.settings.layout.options = {
|
||||||
|
initialIterationsAmount: 50,
|
||||||
|
adjustSizes: false,
|
||||||
|
barnesHutOptimize: false,
|
||||||
|
barnesHutTheta: 0.5,
|
||||||
|
edgeWeightInfluence: 0,
|
||||||
|
gravity: 1,
|
||||||
|
linLogMode: false,
|
||||||
|
outboundAttractionDistribution: false,
|
||||||
|
scalingRatio: 1,
|
||||||
|
slowDown: 1,
|
||||||
|
strongGravityMode: false,
|
||||||
|
...sensibleSettings
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[Infinity, -Infinity].includes(this.settings.layout.options.slowDown)
|
||||||
|
) {
|
||||||
|
this.settings.layout.options.slowDown = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetFA2LayoutSettings() {
|
||||||
|
if (this.initOptions?.layout.type === 'forceAtlas2') {
|
||||||
|
this.settings.layout = JSON.parse(
|
||||||
|
JSON.stringify(this.initOptions.layout)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.setRecommendedFA2Settings()
|
||||||
|
}
|
||||||
|
this.updateLayout(this.settings.layout.type)
|
||||||
|
},
|
||||||
saveAsPng() {
|
saveAsPng() {
|
||||||
return downloadAsPNG(this.renderer, {
|
return downloadAsPNG(this.renderer, {
|
||||||
backgroundColor: this.settings.style.backgroundColor
|
backgroundColor: this.settings.style.backgroundColor
|
||||||
@@ -534,4 +618,14 @@ export default {
|
|||||||
:deep(.customPickerContainer) {
|
:deep(.customPickerContainer) {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
.force-atlas-buttons {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-atlas-buttons :deep(button) {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -71,10 +71,12 @@ export default {
|
|||||||
},
|
},
|
||||||
async handleResize() {
|
async handleResize() {
|
||||||
const renderer = this.$refs.graphEditor.renderer
|
const renderer = this.$refs.graphEditor.renderer
|
||||||
|
if (renderer) {
|
||||||
renderer.refresh()
|
renderer.refresh()
|
||||||
renderer.getCamera().setState({ x: 0.5, y: 0.5 })
|
renderer.getCamera().setState({ x: 0.5, y: 0.5 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user