Skip to content

Commit

Permalink
added font arg, node labels to sankey diagram and text measuring to t…
Browse files Browse the repository at this point in the history
…emplates
  • Loading branch information
ishubin committed Jan 25, 2025
1 parent e9a96a4 commit 622eeb2
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 22 deletions.
2 changes: 1 addition & 1 deletion assets/templates/diagrams/sankey.json

Large diffs are not rendered by default.

23 changes: 21 additions & 2 deletions assets/templates/diagrams/sankey.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ 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\nOther [120] Budget\nBudget [1000] Housing\nBudget [450] Taxes\n", name: "Diagram", textarea: true, rows: 15}
font: {type: font, value: Arial, name: Font}
colorTheme: {type: "choice", value: "default", options: ["default", "light", "dark"], name: "Color theme"}
diagramCode: {type: "string", value: "Wages [2000] Budget\n //Comment\n\nOther [120] Budget\nInvalid line\n", name: "Diagram", textarea: true, rows: 15}
fontSize: {type: number, value: 14, name: "Font size", min: 1}
magnify: {type: number, value: 0, name: "Magnify value", min: -50, max: 50}

preview: "/assets/templates/previews/mind-map.svg"
defaultArea: {x: 0, y: 0, w: 200, h: 60}
Expand All @@ -30,6 +33,20 @@ item:
w: {$-expr: "width"}
h: {$-expr: "height"}
childItems:
- $-foreach: {source: "connectorItems", it: "it"}
id: {$-expr: "it.id"}
name: {$-expr: "it.name"}
shape: {$-expr: "it.shape"}
shapeProps: {$-expr: "toJSON(it.shapeProps)"}
args: {$-expr: "it.getArgs()"}
locked: {$-expr: "it.locked"}
textSlots: {$-expr: "toJSON(it.textSlots)"}
area:
x: {$-expr: "it.x"}
y: {$-expr: "it.y"}
w: {$-expr: "it.w"}
h: {$-expr: "it.h"}

- $-foreach: {source: "nodeItems", it: "it"}
id: {$-expr: "it.id"}
name: {$-expr: "it.name"}
Expand All @@ -44,7 +61,7 @@ item:
w: {$-expr: "it.w"}
h: {$-expr: "it.h"}

- $-foreach: {source: "connectorItems", it: "it"}
- $-foreach: {source: "nodeLabels", it: "it"}
id: {$-expr: "it.id"}
name: {$-expr: "it.name"}
shape: {$-expr: "it.shape"}
Expand All @@ -57,3 +74,5 @@ item:
y: {$-expr: "it.y"}
w: {$-expr: "it.w"}
h: {$-expr: "it.h"}


52 changes: 46 additions & 6 deletions assets/templates/diagrams/src/sankey.sch
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
gapRatio = 0.40
nodeWidth = 20
labelPadding = 5

labelFontSize = max(1, round(fontSize * (100 - magnify) / 100))
valueFontSize = max(1, round(fontSize * (100 + magnify) / 100))


colorThemes = Map(
Expand Down Expand Up @@ -293,12 +297,14 @@ func buildNodeItems(levels) {
node.color = colorPalette.get(abs(hashCode) % colorPalette.size)


local nodeItem = Item(node.id, node.name, 'rect')
local nodeItem = Item('n-' + node.id, node.name, 'rect')
nodeItem.w = node.width
nodeItem.h = node.height
nodeItem.x = node.position
nodeItem.y = node.offset
nodeItem.shapeProps.set('strokeSize', 0)
nodeItem.shapeProps.set('strokeSize', 1)
nodeItem.shapeProps.set('strokeColor', '#ffffff')
nodeItem.shapeProps.set('cornerRadius', 2)
nodeItem.shapeProps.set('fill', Fill.solid(node.color))
nodeItems.add(nodeItem)

Expand Down Expand Up @@ -389,10 +395,7 @@ func buildSingleConnectorItem(connector, srcNode, dstNode) {
item.w = dx
item.h = dy

item.shapeProps.set('fill', Map(
'type', 'solid',
'color', 'rgba(200, 160, 180, 1)'
))
item.shapeProps.set('fill', Fill.linearGradient(90, 0, srcNode.color, 100, dstNode.color))
item.shapeProps.set('strokeSize', 0)
item.shapeProps.set('paths', List(Map(
'id', 'p-' + connector.id,
Expand All @@ -403,10 +406,47 @@ func buildSingleConnectorItem(connector, srcNode, dstNode) {
item
}

func buildNodeLabels(nodes) {
local labelItems = List()
nodes.forEach(node => {
local item = Item('ln-' + node.id, node.name, 'none')
local textSize = calculateTextSize(node.name, font, fontSize)
item.w = textSize.w + 4
item.h = textSize.h + 4
local halign = 'left'
if (node.dstNodes.size > 0) {
item.x = node.position + node.width + labelPadding
} else {
item.x = node.position - item.w - labelPadding
halign = 'right'
}

item.y = node.offset + node.height / 2 - item.h / 2

item.args.set('templateForceText', true)
item.textSlots.set('body', Map(
'text', node.name,
'font', font,
'fontSize', fontSize,
'halign', halign,
'valign', 'middle',
'paddingLeft', 0,
'paddingRight', 0,
'paddingTop', 0,
'paddingBottom', 0,
'whiteSpace', 'nowrap'
))
labelItems.add(item)
})
labelItems
}


allConnections = parseConnections(diagramCode)
allNodes = extractNodesFromConnections(allConnections)

local levels = buildLevels(allNodes, allConnections)

nodeItems = buildNodeItems(levels)
connectorItems = buildConnectorItems(levels, allConnections)
nodeLabels = buildNodeLabels(allNodes)
33 changes: 20 additions & 13 deletions src/ui/components/editor/ArgumentsEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
:borderless="true"
:options="argumentBindStates[argName].options"
title="Bind argument"
@selected="onArgumentBindSelected(argName, arguments[0])"
@selected="onArgumentBindSelected(argName, $event)"
>
<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>
Expand All @@ -39,32 +39,37 @@
class="textfield"
:value="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
@input="onValueChange(argName, arguments[0].target.value)"/>
@input="onValueChange(argName, $event.target.value)"/>

<dropdown v-if="arg.type === 'font'"
:options="allFonts"
:value="argumentValues[argName]"
@selected="onValueChange(argName, $event.name)"/>

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

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

<AdvancedColorEditor v-if="arg.type === 'advanced-color'" :value="argumentValues[argName]"
:apiClient="apiClient"
:editorId="editorId"
@changed="onValueChange(argName, arguments[0])"
@changed="onValueChange(argName, $event)"
: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)"/>
@input="onValueChange(argName, $event.target.checked)"/>

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

Expand All @@ -74,13 +79,13 @@
:element="argumentValues[argName]"
:disabled="!argumentControlStates[argName].shown"
:use-self="false"
@selected="onValueChange(argName, arguments[0])"
@selected="onValueChange(argName, $event)"
/>

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

<PathCapDropdown v-if="arg.type === 'path-cap'"
Expand All @@ -90,7 +95,7 @@
width="100%"
:height="16"
:disabled="!argumentControlStates[argName].shown"
@selected="onValueChange(argName, arguments[0])"/>
@selected="onValueChange(argName, $event)"/>
</div>
</td>
</template>
Expand All @@ -107,7 +112,7 @@
:value="argumentValues[argName]"
:schemeContainer="schemeContainer"
:previousScripts="[schemeContainer.scheme.scripts.main.source]"
@changed="onValueChange(argName, arguments[0])"
@changed="onValueChange(argName, $event)"
/>
</td>
</template>
Expand All @@ -119,7 +124,7 @@
</div>

<textarea class="property-textarea" :value="argumentValues[argName]" :rows="arg.rows || 10"
@input="onValueChange(argName, arguments[0].target.value)"
@input="onValueChange(argName, $event.target.value)"
></textarea>
</td>
</template>
Expand All @@ -130,7 +135,7 @@
</div>
</template>
<script>
import {forEach, mapObjectValues} from '../../collections';
import {forEach, map, mapObjectValues} from '../../collections';
import ColorPicker from './ColorPicker.vue';
import AdvancedColorEditor from './AdvancedColorEditor.vue';
import Modal from '../Modal.vue';
Expand All @@ -141,6 +146,7 @@ import NumberTextfield from '../NumberTextfield.vue';
import ScriptEditor from './ScriptEditor.vue';
import PathCapDropdown from './PathCapDropdown.vue';
import Dropdown from '../Dropdown.vue';
import { getAllFonts } from '../../scheme/Fonts';

export default {
props: {
Expand Down Expand Up @@ -191,6 +197,7 @@ export default {
});

return {
allFonts: map(getAllFonts(), font => {return {name: font.name, style: {'font-family': font.family}}}),
argumentBindStates,
argumentValues,
argumentControlStates: mapObjectValues(this.argsDefinition, () => {return {shown: true};}),
Expand Down
3 changes: 3 additions & 0 deletions src/ui/components/editor/items/ItemTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ export function regenerateTemplatedItem(rootItem, template, templateArgs, width,
if (shouldCopyField && !parentItem) {
shouldCopyField = key !== 'name' && key !== 'description' && key !== 'tags' && key !== 'area' && key !== 'autoLayout';
}
if (key === 'textSlots' && item.args.templateForceText) {
shouldCopyField = true;
}
if (shouldCopyField) {
if (key === 'shapeProps' && regeneratedItem.shapeProps) {
if (!srcItem.shapeProps) {
Expand Down
9 changes: 9 additions & 0 deletions src/ui/components/editor/items/ItemTemplateFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,20 @@ export function createTemplateFunctions(editorId, rootItem) {
duplicateItem: duplicateItem(rootItem),
updateItem: updateItemFunc(editorId, rootItem),

calculateTextSize,
clone: (obj) => utils.clone(obj),
log: (...args) => console.log(...args)
}
}

function calculateTextSize(text, font, fontSize) {
const canvas = calculateTextSize.canvas || (calculateTextSize.canvas = document.createElement("canvas"));
const context = canvas.getContext("2d");
context.font = `${fontSize}px ${font}`;
const metrics = context.measureText(text);
return {w: metrics.width, h: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent};
}

/**
* @param {Item} rootItem
* @returns {function(string): Item}
Expand Down
1 change: 1 addition & 0 deletions src/ui/typedef.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
* @property {String|undefined} templateOriginalRef - used only when item is copied so that it marks the template which the item was originally generated with
* @property {String|undefined} templatedId - id that was created from the template. It is used to track the items after they were generated from template
* @property {Object|undefined} templateArgs - args that were used for generating the templated item
* @property {Boolean|undefined} templateForceText - flag forces templed item to update its textSlots
* @property {Array<String>|undefined} templateIgnoredProps - array of shapeProps field names that should be ignored when template is regenerated
* this gives users possibility of editing individual template items
* @property {String|undefined} tplArea - 'controlled' or 'fixed'. If specified as 'controlled' then it tells Schemio
Expand Down

0 comments on commit 622eeb2

Please sign in to comment.