Skip to content

Commit

Permalink
added decoding of sankey diagram from textarea
Browse files Browse the repository at this point in the history
  • Loading branch information
ishubin committed Jan 24, 2025
1 parent c074274 commit 368013f
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 114 deletions.
7 changes: 7 additions & 0 deletions assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2141,6 +2141,13 @@ ul.button-group.disabled > li, ul.button-group > li.disabled {
.properties-table .property-arg-binder {
max-width: 44px;
}
.properties-table textarea.property-textarea {
margin-top: 5px;
margin-bottom: 5px;
padding: 4px;
border-radius: 3px;
width: 100%;
}
.property-arg-binder-icon {
opacity: 0.6;
}
Expand Down
2 changes: 1 addition & 1 deletion assets/templates/diagrams/sankey.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/templates/diagrams/sankey.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: ""
args:
nodes: {type: "string", value: "a1_1;x=0;y=0;|a1_2|a2", name: "Nodes encoded", hidden: true}
connections: {type: "string", value: "a1_1-a2;s=a1_1;d=a2;v=100|a1_2-a2;s=a1_2;d=a2;v=30", name: "Connections encoded", hidden: true}
diagramCode: {type: "string", value: "Wages [2000] Budget\n //Comment\n\nOther [120] Budget\nInvalid line\n", name: "Diagram", textarea: true, rows: 15}

preview: "/assets/templates/previews/mind-map.svg"
defaultArea: {x: 0, y: 0, w: 200, h: 60}
Expand Down
58 changes: 56 additions & 2 deletions assets/templates/diagrams/src/sankey.sch
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,60 @@ func decodeConnections(text) {
})
}


func parseConnection(line) {
local s1 = line.indexOf('[')
local s2 = line.indexOf(']')

if (s1 > 0 && s2 > s1) {
local nodeName1 = line.substring(0, s1).trim()
local nodeName2 = line.substring(s2+1).trim()
local valueText = line.substring(s1+1, s2)

if (nodeName1 != '' && nodeName2 != '') {
local value = parseFloat(valueText)
local id = nodeName1 + '[]' + nodeName2
Connection(id, nodeName1, nodeName2, value)
} else {
null
}
} else {
null
}
}

func parseConnections(text) {
local connections = List()
splitString(text, '\n').forEach(line => {
line = line.trim()
if (line != '' && !line.startsWith('//')) {
local c = parseConnection(line)
if (c) {
connections.add(c)
}
}
})
connections
}

func extractNodesFromConnections(connections) {
local nodeIds = Set()

connections.forEach(c => {
nodeIds.add(c.srcId)
nodeIds.add(c.dstId)
})

local list = List()

nodeIds.forEach(id => {
list.add(Node(id, id))
})
list
}



// Performs a recursive tree iteration and updates the levels in nodes
// maxVisitCount is used in order to prevent from infinite loop in case there is a cyclic dependency
func updateLevels(node, maxVisitCount) {
Expand Down Expand Up @@ -326,8 +380,8 @@ func buildSingleConnectorItem(connector, srcNode, dstNode) {
item
}

allNodes = decodeNodes(nodes)
allConnections = decodeConnections(connections)
allConnections = parseConnections(diagramCode)
allNodes = extractNodesFromConnections(allConnections)

local levels = buildLevels(allNodes, allConnections)

Expand Down
229 changes: 131 additions & 98 deletions src/ui/components/editor/ArgumentsEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,104 +4,128 @@
<template>
<div>
<table class="properties-table">
<tr v-for="(arg, argName) in argsDefinition" v-if="argumentControlStates[argName] && !isDisabledScript(arg, argName) && !arg.hidden">
<td v-if="arg.type !== 'script'" class="label" :class="{disabled: !argumentControlStates[argName].shown}" width="50%">
{{arg.name}}
<tooltip v-if="arg.description">{{arg.description}}</tooltip>
</td>
<td v-if="arg.type !== 'script' && hasScopeArgs" class="property-arg-binder">
<Dropdown
v-if="argumentBindStates[argName] && argumentBindStates[argName].options.length > 0"
:key="`dropdown-binder-${argName}-${argumentBindStates[argName].revision}`"
:inline="true"
:borderless="true"
:options="argumentBindStates[argName].options"
title="Bind argument"
@selected="onArgumentBindSelected(argName, arguments[0])"
>
<i v-if="argumentBindStates[argName] && argumentBindStates[argName].isBinded" class="fa-solid fa-link property-arg-binder-icon binded"></i>
<i v-else class="fa-solid fa-link-slash property-arg-binder-icon"></i>
</Dropdown>
</td>
<td v-if="arg.type !== 'script'" class="value" :class="{disabled: !argumentControlStates[argName].shown}" width="50%">
<div v-if="argumentBindStates[argName] && argumentBindStates[argName].isBinded">
<span class="property-arg-binder-ref" title="Class argument">{{ argumentBindStates[argName].value.ref }}</span>
</div>
<div v-else>
<input v-if="arg.type === 'string' || arg.type === 'image'"
class="textfield"
:value="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.value)"/>

<number-textfield v-if="arg.type === 'number'"
:value="argumentValues[argName]"
:min="arg.min"
:max="arg.max"
:disabled="!argumentControlStates[argName].shown"
@changed="onValueChange(argName, arguments[0])"/>

<color-picker :editorId="editorId" v-if="arg.type === 'color'" :color="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0])"/>

<advanced-color-editor v-if="arg.type === 'advanced-color'" :value="argumentValues[argName]"
:apiClient="apiClient"
:editorId="editorId"
@changed="onValueChange(argName, arguments[0])"
:disabled="!argumentControlStates[argName].shown" />

<input v-if="arg.type === 'boolean'" type="checkbox" :checked="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.checked)"/>

<select v-if="arg.type === 'choice'" :value="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.value)">
<option v-for="option in arg.options">{{option}}</option>
</select>

<ElementPicker v-if="arg.type === 'element'"
:editorId="editorId"
:scheme-container="schemeContainer"
:element="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
:use-self="false"
@selected="onValueChange(argName, arguments[0])"
/>

<DiagramPicker v-if="arg.type === 'scheme-ref'"
:key="`args-editor-diagram-picker-${editorId}-${argumentValues[argName]}`"
:diagramId="argumentValues[argName]"
@diagram-selected="onDiagramPicked(argName, arguments[0])"
/>

<PathCapDropdown v-if="arg.type === 'path-cap'"
:value="argumentValues[argName]"
:is-source="false"
:is-thick="false"
width="100%"
:height="16"
:disabled="!argumentControlStates[argName].shown"
@selected="onValueChange(argName, arguments[0])"/>
</div>
</td>
<td v-else colspan="3">
<div class="label">
{{arg.name}}
<tooltip v-if="arg.description">{{arg.description}}</tooltip>
<a class="link" target="_blank" href="https://github.com/ishubin/schemio/blob/master/docs/Scripting.md">(documentation)</a>
</div>

<ScriptEditor v-if="arg.type === 'script'"
:key="`script-editor-${argName}-${editorId}`"
:value="argumentValues[argName]"
:schemeContainer="schemeContainer"
:previousScripts="[schemeContainer.scheme.scripts.main.source]"
@changed="onValueChange(argName, arguments[0])"
/>
</td>
</tr>
<tbody>
<template v-for="(arg, argName) in argsDefinition">
<tr v-if="argumentControlStates[argName] && !isDisabledScript(arg, argName) && !arg.hidden">
<template v-if="isRegularArg(arg)">
<td class="label" :class="{disabled: !argumentControlStates[argName].shown}" width="50%">
{{arg.name}}
<tooltip v-if="arg.description">{{arg.description}}</tooltip>
</td>
<td v-if="hasScopeArgs" class="property-arg-binder">
<Dropdown
v-if="argumentBindStates[argName] && argumentBindStates[argName].options.length > 0"
:key="`dropdown-binder-${argName}-${argumentBindStates[argName].revision}`"
:inline="true"
:borderless="true"
:options="argumentBindStates[argName].options"
title="Bind argument"
@selected="onArgumentBindSelected(argName, arguments[0])"
>
<i v-if="argumentBindStates[argName] && argumentBindStates[argName].isBinded" class="fa-solid fa-link property-arg-binder-icon binded"></i>
<i v-else class="fa-solid fa-link-slash property-arg-binder-icon"></i>
</Dropdown>
</td>
<td class="value"
:class="{disabled: !argumentControlStates[argName].shown}"
width="50%"
:colspan="hasScopeArgs ? 1 : 2"
>
<div v-if="argumentBindStates[argName] && argumentBindStates[argName].isBinded">
<span class="property-arg-binder-ref" title="Class argument">{{ argumentBindStates[argName].value.ref }}</span>
</div>
<div v-else>
<input v-if="arg.type === 'string' || arg.type === 'image'"
class="textfield"
:value="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.value)"/>

<NumberTextfield v-if="arg.type === 'number'"
:value="argumentValues[argName]"
:min="arg.min"
:max="arg.max"
:disabled="!argumentControlStates[argName].shown"
@changed="onValueChange(argName, arguments[0])"/>

<ColorPicker :editorId="editorId" v-if="arg.type === 'color'" :color="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0])"/>

<AdvancedColorEditor v-if="arg.type === 'advanced-color'" :value="argumentValues[argName]"
:apiClient="apiClient"
:editorId="editorId"
@changed="onValueChange(argName, arguments[0])"
:disabled="!argumentControlStates[argName].shown" />

<input v-if="arg.type === 'boolean'" type="checkbox" :checked="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.checked)"/>

<select v-if="arg.type === 'choice'" :value="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.value)">
<option v-for="option in arg.options">{{option}}</option>
</select>

<ElementPicker v-if="arg.type === 'element'"
:editorId="editorId"
:scheme-container="schemeContainer"
:element="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
:use-self="false"
@selected="onValueChange(argName, arguments[0])"
/>

<DiagramPicker v-if="arg.type === 'scheme-ref'"
:key="`args-editor-diagram-picker-${editorId}-${argumentValues[argName]}`"
:diagramId="argumentValues[argName]"
@diagram-selected="onDiagramPicked(argName, arguments[0])"
/>

<PathCapDropdown v-if="arg.type === 'path-cap'"
:value="argumentValues[argName]"
:is-source="false"
:is-thick="false"
width="100%"
:height="16"
:disabled="!argumentControlStates[argName].shown"
@selected="onValueChange(argName, arguments[0])"/>
</div>
</td>
</template>
<template v-else-if="arg.type === 'script'">
<td colspan="3">
<div class="label">
{{arg.name}}
<tooltip v-if="arg.description">{{arg.description}}</tooltip>
<a class="link" target="_blank" href="https://github.com/ishubin/schemio/blob/master/docs/Scripting.md">(documentation)</a>
</div>

<ScriptEditor v-if="arg.type === 'script'"
:key="`script-editor-${argName}-${editorId}`"
:value="argumentValues[argName]"
:schemeContainer="schemeContainer"
:previousScripts="[schemeContainer.scheme.scripts.main.source]"
@changed="onValueChange(argName, arguments[0])"
/>
</td>
</template>
<template v-else-if="arg.type === 'string' && arg.textarea">
<td colspan="3">
<div class="label">
{{arg.name}}
<tooltip v-if="arg.description">{{arg.description}}</tooltip>
</div>

<textarea class="property-textarea" :value="argumentValues[argName]" :rows="arg.rows || 10"
@input="onValueChange(argName, arguments[0].target.value)"
></textarea>
</td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</template>
Expand Down Expand Up @@ -175,6 +199,15 @@ export default {
},

methods: {
isRegularArg(arg) {
if (arg.type === 'script') {
return false;
} else if (arg.type === 'string' && arg.textarea) {
return false;
}
return true;
},

buildArgumentBindOptions(argName) {
const options = [];
if (this.argBinds && this.argBinds.hasOwnProperty(argName)) {
Expand Down
10 changes: 8 additions & 2 deletions src/ui/components/editor/properties/TemplateProperties.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {forEach, forEachObject} from '../../../collections';
import EditorEventBus from '../EditorEventBus';
import ItemSvg from '../items/ItemSvg.vue';
import Panel from '../Panel.vue';
import { createDelayer } from '../../../delayer';
export default {
props: {
Expand All @@ -76,6 +77,7 @@ export default {
},
beforeDestroy() {
this.updateDelayer.destroy();
EditorEventBus.item.templateArgsUpdated.specific.$off(this.editorId, this.item.id, this.updateTemplateArgs);
},
Expand All @@ -87,6 +89,10 @@ export default {
templateNotFound: false,
template: null,
editorPanels: [],
lastChangedArgName: null,
updateDelayer: createDelayer(200, () => {
this.$emit('updated', this.item.id, this.template, this.args, this.lastChangedArgName);
}),
args: this.item.args && this.item.args.templateArgs ? this.item.args.templateArgs : {}
}
},
Expand Down Expand Up @@ -161,8 +167,8 @@ export default {
});
}
this.args[name] = value;
this.$emit('updated', this.item.id, this.template, this.args, name);
this.lastChangedArgName = name;
this.updateDelayer.trigger();
this.updateEditorPanels();
this.$forceUpdate();
},
Expand Down
Loading

0 comments on commit 368013f

Please sign in to comment.