From 5fce18774d53d57f2f300b962da6b12e237480db Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 11:41:08 +0100 Subject: [PATCH 01/95] define a default for UseSmartZoom and use it --- packages/klighd-core/src/options/render-options-registry.ts | 1 + packages/klighd-core/src/views.tsx | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 36fafd3f..0147730c 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -71,6 +71,7 @@ export class ShowConstraintOption implements RenderOption { export class UseSmartZoom implements RenderOption { static readonly ID: string = 'use-smart-zoom' static readonly NAME: string = 'Smart Zoom' + static readonly DEFAULT: boolean = false readonly id: string = UseSmartZoom.ID readonly name: string = UseSmartZoom.NAME readonly type: TransformationOptionType = TransformationOptionType.CHECK diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index dd59ab57..0d154b1e 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -57,10 +57,7 @@ export class SKGraphView implements IView { // Add depthMap to context for rendering, when required. - const smartZoomOption = ctx.renderOptionsRegistry.getValue(UseSmartZoom) - - // Only enable, if option is found. - const useSmartZoom = smartZoomOption ?? false + const useSmartZoom = this.renderOptionsRegistry.getValueOrDefault(UseSmartZoom) if (useSmartZoom && ctx.targetKind !== 'hidden') { ctx.depthMap = DepthMap.getDM() From dd016f5f7796cbbbf8dfe62d8d7b2d53e1466b95 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 11:42:36 +0100 Subject: [PATCH 02/95] whitespace --- packages/klighd-core/src/views-rendering.tsx | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 0e40fee4..ae04138f 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -470,7 +470,7 @@ export function renderKText(rendering: KText, attrs.textLength = rendering.calculatedTextLineWidths[0] attrs.lengthAdjust = 'spacingAndGlyphs' } - + elements = [ {...lines} @@ -542,7 +542,7 @@ export function renderError(rendering: KRendering): VNode { * Renders some SVG shape, possibly with an added shadow, as given by the svgFunction. If a simple shadow * should be added, it is added as four copies of the SVG shape with rgba(0,0,0,0.1) and the * offsets defined by the kShadow, if a nice shadow should be added, it is added via SVG filter. - * + * * @param kShadow The shadow definition for the rendering, or undefined if no shadow should be added * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. * @param svgFunction The callback function rendering the wanted SVG shape. x and y are the offsets @@ -582,7 +582,7 @@ export function renderWithShadow( /** * Renders a rectangle with all given information. - * + * * @param bounds bounds data calculated for this rectangle. * @param rx rx parameter of SVG rect * @param ry ry parameter of SVG rect @@ -600,7 +600,7 @@ export function renderSVGRect(bounds: Bounds, rx: number, ry: number, lineStyles * Renders a rectangle with all given information. * If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a * black fill with 0.1 alpha is returned. - * + * * @param x x offset of the rectangle, to be used for shadows only. * @param y y offset of the rectangle, to be used for shadows only. * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -638,7 +638,7 @@ export function renderSingleSVGRect(x: number | undefined, y: number | undefined /** * Renders an image with all given information. - * + * * @param bounds bounds data calculated for this image. * @param image The image href string * @param kShadow shadow information. @@ -651,7 +651,7 @@ export function renderSVGImage(bounds: Bounds, shadowStyles: string | undefined, /** * Renders an image with all given information. * If the rendering is a shadow, a shadow rect is drawn instead. - * + * * @param x x offset of the image, to be used for shadows only. * @param y y offset of the image, to be used for shadows only. * @param kShadow shadow information. Controls what this method does. @@ -684,7 +684,7 @@ export function renderSingleSVGImage(x: number | undefined, y: number | undefine /** * Renders an arc with all given information. - * + * * @param lineStyles style information for lines (stroke etc.) * @param colorStyles style information for color * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -694,13 +694,13 @@ export function renderSingleSVGImage(x: number | undefined, y: number | undefine */ export function renderSVGArc(lineStyles: LineStyles, colorStyles: ColorStyles, shadowStyles: string | undefined, path: string, kShadow: KShadow | undefined): VNode[] { return renderWithShadow(kShadow, shadowStyles, renderSingleSVGArc, lineStyles, colorStyles, path) -} +} /** * Renders an arc with all given information. * If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a * black fill with 0.1 alpha is returned. - * + * * @param x x offset of the arc, to be used for shadows only. * @param y y offset of the arc, to be used for shadows only. * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -732,7 +732,7 @@ export function renderSingleSVGArc(x: number | undefined, y: number | undefined, /** * Renders an ellipse with all given information. - * + * * @param lineStyles style information for lines (stroke etc.) * @param colorStyles style information for color * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -747,7 +747,7 @@ export function renderSVGEllipse(bounds: Bounds, lineStyles: LineStyles, colorSt * Renders an ellipse with all given information. * If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a * black fill with 0.1 alpha is returned. - * + * * @param x x offset of the ellipse, to be used for shadows only. * @param y y offset of the ellipse, to be used for shadows only. * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -782,7 +782,7 @@ export function renderSingleSVGEllipse(x: number | undefined, y: number | undefi /** * Renders a rendering with a specific path (polyline, polygon, etc.) with all given information. - * + * * @param lineStyles style information for lines (stroke etc.) * @param colorStyles style information for color * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -798,7 +798,7 @@ export function renderSVGLine(lineStyles: LineStyles, colorStyles: ColorStyles, * Renders a rendering with a specific path (polyline, polygon, etc.) with all given information. * If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a * black fill with 0.1 alpha is returned. - * + * * @param x x offset of the line, to be used for shadows only. * @param y y offset of the line, to be used for shadows only. * @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one. @@ -910,8 +910,8 @@ export function renderKRendering(kRendering: KRendering, if (kRendering.type === K_TEXT) { boundingBox = findBoundsAndTransformationData(kRendering, styles, parent, context, isEdge, true)?.bounds ?? boundingBox } - - + + const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds const originalWidth = boundingBox.width const originalHeight = boundingBox.height @@ -1135,4 +1135,4 @@ export function getJunctionPointRenderings(edge: SKEdge, context: SKGraphModelRe renderings.push(junctionPointVNode) }) return renderings -} \ No newline at end of file +} From ab9e4af0b406236cdc058fb3709106031af12f31 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 11:47:26 +0100 Subject: [PATCH 03/95] rewrite conditions probably slightly less efficient due to eager evaluation instead of short circuiting, but easier to read --- packages/klighd-core/src/views-rendering.tsx | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index ae04138f..3426f0ff 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -20,7 +20,7 @@ import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unus import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -891,18 +891,27 @@ export function renderKRendering(kRendering: KRendering, let overlayRectangle: VNode | undefined = undefined // remembers if this rendering is a title rendering and should therefore be rendered overlaying the other renderings. let isOverlay = false - + + const applyTitleScaling = context.renderOptionsRegistry.getValueOrDefault(UseSmartZoom) + // If this rendering is the main title rendering of the element, either render it usually if // zoomed in far enough or remember it to be rendered later scaled up and overlayed on top of the parent rendering. - if (context.depthMap && boundsAndTransformation.bounds.width && boundsAndTransformation.bounds.height && kRendering.isNodeTitle) { - // Scale to limit of bounding box or max size. + if (applyTitleScaling && boundsAndTransformation.bounds.width && boundsAndTransformation.bounds.height && kRendering.isNodeTitle) { + + // Scale to limit of bounding box or max size. const titleScalingFactorOption = context.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor) as number let maxScale = titleScalingFactorOption if (context.viewport) { maxScale = maxScale / context.viewport.zoom } - if (providingRegion && providingRegion.detail !== DetailLevel.FullDetails && parent.children.length > 1 - || kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height) { + + // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) + const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height + const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails + const multipleChildren = parent.children.length > 1 + + if ( (notFullDetail && multipleChildren) || tooSmall ) { + isOverlay = true let boundingBox = boundsAndTransformation.bounds @@ -983,11 +992,12 @@ export function renderKRendering(kRendering: KRendering, providingRegion.regionTitleHeight = newHeight providingRegion.regionTitleIndentation = newX } + + // Don't draw if the rendering is an empty KText + const isEmptyText = kRendering.type === K_TEXT && (kRendering as KText).text === "" + // Draw white background for overlaying titles - if (context.depthMap && kRendering.isNodeTitle && ((providingRegion && providingRegion.detail === DetailLevel.FullDetails) || !providingRegion) - && kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height - // Don't draw if the rendering is an empty KText - && (kRendering.type !== K_TEXT || (kRendering as KText).text !== "")) { + if ((!providingRegion || providingRegion.detail === DetailLevel.FullDetails) && tooSmall && !isEmptyText) { overlayRectangle = } } From d5ac370ebe81de45cb0300c0b574bd690721345b Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 11:58:11 +0100 Subject: [PATCH 04/95] the viewport is not supposed to be optional in the context anyway --- packages/klighd-core/src/views-rendering.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 3426f0ff..1b52d839 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -900,10 +900,7 @@ export function renderKRendering(kRendering: KRendering, // Scale to limit of bounding box or max size. const titleScalingFactorOption = context.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor) as number - let maxScale = titleScalingFactorOption - if (context.viewport) { - maxScale = maxScale / context.viewport.zoom - } + const maxScale = titleScalingFactorOption / context.viewport.zoom // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height @@ -929,7 +926,8 @@ export function renderKRendering(kRendering: KRendering, const maxScaleX = parentBounds.width / originalWidth const maxScaleY = parentBounds.height / originalHeight - // Don't let scalingfactor get too big. + + // limit the scaling so that it does not exceed the parents size let scalingFactor = Math.min(maxScaleX, maxScaleY, maxScale) // Make sure we never scale down. scalingFactor = Math.max(scalingFactor, 1) From 5b9cb2fe559af788eab47b3f7331db34e46f8c41 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 11:59:27 +0100 Subject: [PATCH 05/95] change title scaling to use a minimum height instead of a minimum scale --- .../src/options/render-options-registry.ts | 27 +++++++++---------- packages/klighd-core/src/views-rendering.tsx | 11 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 0147730c..8c156148 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -156,24 +156,23 @@ export class TextSimplificationThreshold implements RangeOption { } /** - * The factor by which titles of collapsed regions get scaled by - * in relation to their size at native resolution. + * The minimum size a title should attempt to scale up to */ -export class TitleScalingFactor implements RangeOption { - static readonly ID: string = 'title-scaling-factor' - static readonly NAME: string = 'Title Scaling Factor' - static readonly DEFAULT: number = 1 - readonly id: string = TitleScalingFactor.ID - readonly name: string = TitleScalingFactor.NAME +export class MinimumTitleHeight implements RangeOption { + static readonly ID: string = 'min-title-height' + static readonly NAME: string = 'Minimum Title Height' + static readonly DEFAULT: number = 10 + readonly id: string = MinimumTitleHeight.ID + readonly name: string = MinimumTitleHeight.NAME readonly type: TransformationOptionType = TransformationOptionType.RANGE readonly values: any[] = [] readonly range = { - first: 0.5, - second: 3 + first: 0, + second: 80 } - readonly stepSize = 0.01 - readonly initialValue: number = TitleScalingFactor.DEFAULT - currentValue = 1 + readonly stepSize = 1 + readonly initialValue: number = MinimumTitleHeight.DEFAULT + currentValue = MinimumTitleHeight.DEFAULT } /** @@ -270,7 +269,7 @@ export class RenderOptionsRegistry extends Registry { this.register(SimplifySmallText); this.register(TextSimplificationThreshold); - this.register(TitleScalingFactor); + this.register(MinimumTitleHeight); this.register(UseMinimumLineWidth); this.register(MinimumLineWidth); diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 1b52d839..f5247be6 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -20,7 +20,7 @@ import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unus import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, MinimumTitleHeight, UseSmartZoom } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -898,12 +898,11 @@ export function renderKRendering(kRendering: KRendering, // zoomed in far enough or remember it to be rendered later scaled up and overlayed on top of the parent rendering. if (applyTitleScaling && boundsAndTransformation.bounds.width && boundsAndTransformation.bounds.height && kRendering.isNodeTitle) { - // Scale to limit of bounding box or max size. - const titleScalingFactorOption = context.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor) as number - const maxScale = titleScalingFactorOption / context.viewport.zoom + const overlayThreshold = context.renderOptionsRegistry.getValueOrDefault(MinimumTitleHeight) as number + // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) - const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height + const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= overlayThreshold const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails const multipleChildren = parent.children.length > 1 @@ -927,6 +926,8 @@ export function renderKRendering(kRendering: KRendering, const maxScaleX = parentBounds.width / originalWidth const maxScaleY = parentBounds.height / originalHeight + let maxScale = overlayThreshold / (boundingBox.height * context.viewport.zoom); + // limit the scaling so that it does not exceed the parents size let scalingFactor = Math.min(maxScaleX, maxScaleY, maxScale) // Make sure we never scale down. From c23df12094cef046a5dc3e68e0066b0ed7fecdd3 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 11 Jan 2022 14:54:15 +0100 Subject: [PATCH 06/95] add launch option to use a lsp running on port 5007 --- .vscode/launch.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7164569d..68e8d08b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,6 +14,19 @@ "outFiles": ["${workspaceFolder}/applications/klighd-vscode/dist/**/*.js"], "preLaunchTask": "${defaultBuildTask}" }, + { + "name": "Launch VS Code Extension (socket)", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}/applications/klighd-vscode"], + "env": { + "KEITH_LS_PORT": "5007" + }, + "sourceMaps": true, + "smartStep": true, + "outFiles": ["${workspaceFolder}/applications/klighd-vscode/dist/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + }, { "name": "Start CLI Webserver", "type": "node", From 4eeb794569e174d5e6444478ee2c3635ff3b4170 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 12 Jan 2022 13:19:05 +0100 Subject: [PATCH 07/95] workaround no length on undefined exception apparently parentRendering can be undefined sometimes workaround rather than fix as the field is not supposed to be undefined, so this just suppresses the symptom rather than solving the source --- packages/klighd-core/src/views-rendering.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index f5247be6..8b3cefe7 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -519,7 +519,8 @@ export function renderKText(rendering: KText, export function renderChildRenderings(parentRendering: KContainerRendering, parentElement: SKGraphElement, propagatedStyles: KStyles, context: SKGraphModelRenderer, childOfNodeTitle?: boolean): (VNode | undefined)[] { // children only should be rendered if the parentElement is not a shadow - if (!(parentElement instanceof SKNode) || !parentElement.shadow) { + const isShadow = (parentElement instanceof SKNode) && parentElement.shadow + if (!isShadow && parentRendering.children) { const renderings: (VNode | undefined)[] = [] for (const childRendering of parentRendering.children) { const rendering = getRendering([childRendering], parentElement, propagatedStyles, context, childOfNodeTitle) From d6aa4d8f1a102bab73daebdfc6381e97f593052e Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 18 Jan 2022 16:06:59 +0100 Subject: [PATCH 08/95] add path to help vs-code find imported modules --- packages/klighd-core/tsconfig.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/tsconfig.json b/packages/klighd-core/tsconfig.json index 8aa0aec3..42d63e24 100644 --- a/packages/klighd-core/tsconfig.json +++ b/packages/klighd-core/tsconfig.json @@ -23,5 +23,8 @@ }, "include": [ "src" - ] -} \ No newline at end of file + ], + "paths": { + "@kieler/klighd-interactive/lib/*": ["../klighd-interactive/src/*"] + } +} From 87adbdb64c7a37a4050563e02d05c90c756b4ffe Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 12:16:11 +0100 Subject: [PATCH 09/95] whitespace --- packages/klighd-core/src/depth-map.ts | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 58d5f1d7..4cb8a3a9 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -2,7 +2,7 @@ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://rtsys.informatik.uni-kiel.de/kieler - * + * * Copyright 2021 by * + Kiel University * + Department of Computer Science @@ -59,13 +59,13 @@ type RegionIndexEntry = { containingRegion: Region, providingRegion: undefined } export class DepthMap { /** - * The region for immediate children of the SModelRoot, + * The region for immediate children of the SModelRoot, * aka. the root regions */ rootRegions: Region[]; - /** - * The model for which the DepthMap is generated + /** + * The model for which the DepthMap is generated */ rootElement: SModelRoot; @@ -86,7 +86,7 @@ export class DepthMap { */ lastThreshold?: number; - /** + /** * Set for handling regions, that need to be checked for detail level changes. * Consists of the region that contain at least one child with a lower detail level. */ @@ -95,8 +95,8 @@ export class DepthMap { /** Singleton pattern */ private static instance?: DepthMap; - /** - * @param rootElement The root element of the model. + /** + * @param rootElement The root element of the model. */ private constructor(rootElement: SModelRoot) { this.rootElement = rootElement @@ -133,13 +133,13 @@ export class DepthMap { /** * Returns the current DepthMap instance or undefined if its not initialized - * @returns DepthMap | undefined + * @returns DepthMap | undefined */ public static getDM(): DepthMap | undefined { return DepthMap.instance } - /** + /** * Returns the current DepthMap instance or returns a new one. * @param rootElement The model root element. */ @@ -155,7 +155,7 @@ export class DepthMap { /** * It is generally advised to initialize the elements from root to leaf - * + * * @param element The KGraphElement to initialize for DepthMap usage */ public initKGraphElement(element: SChildElement & KGraphElement, viewport: Viewport, renderingOptions: RenderOptionsRegistry): RegionIndexEntry { @@ -223,7 +223,7 @@ export class DepthMap { this.regionIndexMap.set(element.id, entry) return entry } - + /** * Finds the KRendering in the given graph element. * @param element The graph element to look up the rendering for. @@ -250,10 +250,10 @@ export class DepthMap { return this.initKGraphElement(node, viewport, renderOptions).providingRegion } - /** + /** * Decides the appropriate detail level for regions based on their size in the viewport and applies that state. - * - * @param viewport The current viewport. + * + * @param viewport The current viewport. */ updateDetailLevels(viewport: Viewport, renderingOptions: RenderOptionsRegistry): void { @@ -286,7 +286,7 @@ export class DepthMap { /** * Set detail level for the given region and recursively determine and update the children's detail level - * + * * @param region The root region * @param viewport The current viewport * @param relativeThreshold The detail level threshold @@ -327,7 +327,7 @@ export class DepthMap { /** * Looks for a change in detail level for all critical regions. * Applies the level change and manages the critical regions. - * + * * @param viewport The current viewport * @param relativeThreshold The full detail threshold */ @@ -368,7 +368,7 @@ export class DepthMap { /** * Decides the appropriate detail level for a region * based on their size in the viewport and visibility - * + * * @param region The region in question * @param viewport The current viewport * @param relativeThreshold The full detail threshold @@ -392,12 +392,12 @@ export class DepthMap { } } - /** + /** * Checks visibility of a region with position from browser coordinates in current viewport. - * + * * @param region The region in question for visibility. * @param viewport The current viewport. - * @returns Boolean value indicating the visibility of the region in the current viewport. + * @returns Boolean value indicating the visibility of the region in the current viewport. */ isInBounds(region: Region, viewport: Viewport): boolean { if (region.absolutePosition) { @@ -413,9 +413,9 @@ export class DepthMap { } } - /** + /** * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. - * + * * @param node The KNode in question * @param viewport The current viewport * @returns the relative size of the KNodes shortest dimension @@ -429,8 +429,8 @@ export class DepthMap { /** - * Combines KNodes into regions. These correspond to child areas. A region can correspond to - * a region or a super state in the model. Also manages the boundaries, title candidates, + * Combines KNodes into regions. These correspond to child areas. A region can correspond to + * a region or a super state in the model. Also manages the boundaries, title candidates, * tree structure of the model and application of detail level of its KNodes. */ export class Region { @@ -455,7 +455,7 @@ export class Region { this.detail = DetailLevel.FullDetails } - /** + /** * Applies the detail level to all elements of a region. * @param level the detail level to apply */ From ebac50b026b062044e85c93f2173db2815f981d5 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 12:16:16 +0100 Subject: [PATCH 10/95] perform simple node scaling TODO: * hide labels, comments etc. * adjust edges * make sure not to overlap siblings --- packages/klighd-core/src/depth-map.ts | 2 +- .../src/options/render-options-registry.ts | 16 +++- packages/klighd-core/src/views-rendering.tsx | 78 ++++++++++--------- packages/klighd-core/src/views.tsx | 54 +++++++++++-- 4 files changed, 104 insertions(+), 46 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 4cb8a3a9..91c2d815 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -38,7 +38,7 @@ type DetailWithChildren = DetailLevel.FullDetails /** * Type predicate to determine whether a DetailLevel is a DetailWithChildren level */ -function isDetailWithChildren(detail: DetailLevel): detail is DetailWithChildren { +export function isDetailWithChildren(detail: DetailLevel): detail is DetailWithChildren { return detail === DetailLevel.FullDetails } diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 8c156148..5ff87f6d 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -168,7 +168,7 @@ export class MinimumTitleHeight implements RangeOption { readonly values: any[] = [] readonly range = { first: 0, - second: 80 + second: 40 } readonly stepSize = 1 readonly initialValue: number = MinimumTitleHeight.DEFAULT @@ -207,6 +207,19 @@ export class MinimumLineWidth implements RangeOption { readonly initialValue: number = 0.5 currentValue = 0.5 } +/** + * Boolean option to toggle the scaling of lines based on zoom level. + */ +export class PerformNodeScaling implements RenderOption { + static readonly ID: string = 'perform-node-scaling' + static readonly NAME: string = 'Perform Node Scaling' + static readonly DEFAULT: boolean = false + readonly id: string = PerformNodeScaling.ID + readonly name: string = PerformNodeScaling.NAME + readonly type: TransformationOptionType = TransformationOptionType.CHECK + readonly initialValue: boolean = true + currentValue = true +} /** * The style shadows should be drawn in, either the paper mode shadows (nice, but slow in @@ -273,6 +286,7 @@ export class RenderOptionsRegistry extends Registry { this.register(UseMinimumLineWidth); this.register(MinimumLineWidth); + this.register(PerformNodeScaling); this.register(PaperShadows) this.register(AnimateGoToBookmark); diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 8b3cefe7..f2e380e4 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -16,8 +16,8 @@ */ /** @jsx svg */ import { VNode } from 'snabbdom'; -import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { Bounds } from 'sprotty-protocol'; +import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { Bounds ,Dimension} from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, MinimumTitleHeight, UseSmartZoom } from './options/render-options-registry'; @@ -856,6 +856,30 @@ export function getRendering(datas: KGraphData[], parent: SKGraphElement, propag return renderKRendering(kRendering, parent, propagatedStyles, context, childOfNodeTitle) } +export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { + const originalWidth = originalBounds.width + const originalHeight = originalBounds.height + const originalX = originalBounds.x + const originalY = originalBounds.y + + // Calculate the new x and y indentation: + // width required of scaled rendering + const newWidth = originalWidth * scale + // space to the left of the rendering without scaling... + const spaceL = originalX + // ...and to its right + const spaceR = availableSpace.width - originalX - originalWidth + // New x value after taking space off both sides at an equal ratio + const newX = originalX - spaceL * (newWidth - originalWidth) / (spaceL + spaceR) + + // Same for y axis, just with switched dimensional variables. + const newHeight = originalHeight * scale + const spaceT = originalY + const spaceB = availableSpace.height - originalY - originalHeight + const newY = originalY - spaceT * (newHeight - originalHeight) / (spaceT + spaceB) + return {x: newX, y : newY, width: newWidth, height: newHeight} +} + /** * Translates any KRendering into an SVG rendering. * @param kRendering The rendering. @@ -919,13 +943,10 @@ export function renderKRendering(kRendering: KRendering, const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds - const originalWidth = boundingBox.width - const originalHeight = boundingBox.height - const originalX = boundingBox.x - const originalY = boundingBox.y + const originalBounds = boundingBox - const maxScaleX = parentBounds.width / originalWidth - const maxScaleY = parentBounds.height / originalHeight + const maxScaleX = parentBounds.width / originalBounds.width + const maxScaleY = parentBounds.height / originalBounds.height let maxScale = overlayThreshold / (boundingBox.height * context.viewport.zoom); @@ -934,30 +955,20 @@ export function renderKRendering(kRendering: KRendering, // Make sure we never scale down. scalingFactor = Math.max(scalingFactor, 1) - // Calculate the new x and y indentation: - // width required of scaled rendering - const newWidth = originalWidth * scalingFactor - // space to the left of the rendering without scaling... - const spaceL = originalX - // ...and to its right - const spaceR = parentBounds.width - originalX - originalWidth - // New x value after taking space off both sides at an equal ratio - const newX = originalX - spaceL * (newWidth - originalWidth) / (spaceL + spaceR) - - // Same for y axis, just with switched dimensional variables. - const newHeight = originalHeight * scalingFactor - const spaceT = originalY - const spaceB = parentBounds.height - originalY - originalHeight - const newY = originalY - spaceT * (newHeight - originalHeight) / (spaceT + spaceB) + const newBounds = calculateScaledBounds(boundingBox, parentBounds, scalingFactor) // Apply the new bounds and scaling as the element's transformation. - const translateAndScale = `translate(${newX},${newY})scale(${scalingFactor})` + const translateAndScale = `translate(${newBounds.x},${newBounds.y})scale(${scalingFactor})` if (!providingRegion) { // Add the transformations necessary for correct placement const positionOffset = context.positions[context.positions.length - 1] boundsAndTransformation.transformation = positionOffset + translateAndScale } else { boundsAndTransformation.transformation = translateAndScale + + // Store exact height of title text + providingRegion.regionTitleHeight = newBounds.height + providingRegion.regionTitleIndentation = newBounds.x } // For text renderings, recalculate the required bounds the text needs with the updated data. if (kRendering.type === K_TEXT && (kRendering as KText).calculatedTextBounds) { @@ -973,32 +984,27 @@ export function renderKRendering(kRendering: KRendering, verticalAlignment: VerticalAlignment.CENTER } as KVerticalAlignment boundsAndTransformation.bounds = { - x: calculateX(0, originalWidth, styles.kHorizontalAlignment, textWidth), - y: originalHeight * 0.5, - width: originalWidth, - height: originalHeight + x: calculateX(0, originalBounds.width, styles.kHorizontalAlignment, textWidth), + y: originalBounds.height * 0.5, + width: originalBounds.width, + height: originalBounds.height } } else { // Offsets are already applied in the transformation, so set them to 0 here. boundsAndTransformation.bounds = { x: 0, y: 0, - width: originalWidth, - height: originalHeight + width: originalBounds.width, + height: originalBounds.height } } - if (providingRegion) { - // Store exact height of title text - providingRegion.regionTitleHeight = newHeight - providingRegion.regionTitleIndentation = newX - } // Don't draw if the rendering is an empty KText const isEmptyText = kRendering.type === K_TEXT && (kRendering as KText).text === "" // Draw white background for overlaying titles if ((!providingRegion || providingRegion.detail === DetailLevel.FullDetails) && tooSmall && !isEmptyText) { - overlayRectangle = + overlayRectangle = } } } diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 0d154b1e..e97d0070 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -21,13 +21,13 @@ import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/k import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { DepthMap, DetailLevel } from './depth-map'; +import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; -import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom } from './options/render-options-registry'; +import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom,MinimumTitleHeight, PerformNodeScaling } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; -import { getJunctionPointRenderings, getRendering } from './views-rendering'; +import { getJunctionPointRenderings, getRendering,calculateScaledBounds } from './views-rendering'; import { KStyles } from './views-styles'; /** @@ -89,7 +89,7 @@ export class KNodeView implements IView { if (ctx.depthMap) { const containingRegion = ctx.depthMap.getContainingRegion(node, ctx.viewport, ctx.renderOptionsRegistry) - if (ctx.depthMap && containingRegion && containingRegion.detail !== DetailLevel.FullDetails) { + if (ctx.depthMap && containingRegion && !isDetailWithChildren(containingRegion.detail)) { // Make sure this node and its children are not drawn as long as it is not on full details. node.areChildAreaChildrenRendered = true node.areNonChildAreaChildrenRendered = true @@ -157,6 +157,38 @@ export class KNodeView implements IView { } node.shadow = isShadow + + const providingRegion = ctx.depthMap?.getProvidingRegion(node , ctx.viewport, ctx.renderOptionsRegistry); + const minHeight = ctx.renderOptionsRegistry.getValueOrDefault(MinimumTitleHeight); + + const calcScale = function() {if (node.parent + && providingRegion + && providingRegion.regionTitleHeight + && providingRegion.regionTitleHeight * ctx.viewport.zoom < minHeight + && ctx.renderOptionsRegistry.getValueOrDefault(PerformNodeScaling)) { + + + // the scale required to scale the title to the minHeight + const desiredHightScale = minHeight / (providingRegion.regionTitleHeight * ctx.viewport.zoom); + // the maximum scale that keeps the node in bounds height wise + const maxHeightScale = (node.parent as SKNode).bounds.height / node.bounds.height + // the maximum scale that keeps the node in bounds width wise + const maxWidthScale = (node.parent as SKNode).bounds.width / node.bounds.width + + // the most restrictive scaling of the three above + const preferredScale = Math.min(desiredHightScale, maxHeightScale,maxWidthScale) + + const scalingFactor = Math.max(1, preferredScale) + const newBounds = calculateScaledBounds(node.bounds, (node.parent as SKNode).bounds, scalingFactor) + if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ + // On initial load node.parent.bounds has all fields as 0 causing a division by 0 + return "" + } else { + // Apply the new bounds and scaling as the element's transformation. + return `translate(${newBounds.x - node.bounds.x },${newBounds.y - node.bounds.y})scale(${scalingFactor})` + } + }} + if (node.id === '$root') { // The root node should not be rendered, only its children should. const children = ctx.renderChildren(node) @@ -184,10 +216,14 @@ export class KNodeView implements IView { result.push(rendering) } else { ctx.positions.pop() - return - {ctx.titles.pop() ?? []} - {ctx.renderChildren(node)} + const titles = ctx.titles.pop() ?? [] + const childRenderings = ctx.renderChildren(node) + const translateAndScale = calcScale() + const ret = + {titles} + {childRenderings} + return ret } if (interactiveNodes) { result.push(interactiveNodes) @@ -203,7 +239,9 @@ export class KNodeView implements IView { } result.push(...(ctx.titles.pop() ?? [])) ctx.positions.pop() - return {...result} + const translateAndScale = calcScale() + const ret = {...result} + return ret } } From 4a56bd2ef3fcc53dd4405b73385491e8100e71ed Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 12:40:46 +0100 Subject: [PATCH 11/95] fix deprecation warning --- packages/klighd-core/src/skgraph-models.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 1458c742..1655ad2b 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -16,7 +16,8 @@ */ import { KEdge, KGraphData, KGraphElement, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; -import { Bounds, boundsFeature, moveFeature, Point, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement } from 'sprotty'; +import { boundsFeature, moveFeature, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement } from 'sprotty'; +import { Point, Bounds } from 'sprotty-protocol' /** * This is the superclass of all elements of a graph such as nodes, edges, ports, @@ -116,7 +117,7 @@ export interface KRendering extends KGraphData, KStyleHolder { tooltip?: string /** - * Whether the server pre-determined this KRendering to be the title of a node or not. + * Whether the server pre-determined this KRendering to be the title of a node or not. */ isNodeTitle?: boolean /** @@ -724,4 +725,4 @@ export function isSKGraphElement(test: unknown): test is SKGraphElement { && (test as any)['areNonChildAreaChildrenRendered'] !== undefined && (test as any)['opacity'] !== undefined && (test as any)['data'] !== undefined -} \ No newline at end of file +} From 34f2e6eebd6843b552f9baf4e8e47e1726309cbe Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 12:44:38 +0100 Subject: [PATCH 12/95] fix missing superclass based on the comment on SKGraphElement SKNode and SKEdge should have this superclass --- packages/klighd-core/src/skgraph-models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 1655ad2b..9866836d 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -37,7 +37,7 @@ export const LABEL_TYPE = 'label' /** * Represents the Sprotty version of its java counterpart in KLighD. */ -export class SKNode extends KNode { +export class SKNode extends KNode implements SKGraphElement { tooltip?: string hasFeature(feature: symbol): boolean { return feature === selectFeature @@ -79,7 +79,7 @@ export class SKLabel extends SLabel implements SKGraphElement { /** * Represents the Sprotty version of its java counterpart in KLighD. */ -export class SKEdge extends KEdge { +export class SKEdge extends KEdge implements SKGraphElement { tooltip?: string hasFeature(feature: symbol): boolean { return feature === selectFeature || feature === popupFeature From 9bce8b5d8442996550c4bcf039a43e2dcabacb2a Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 14:44:40 +0100 Subject: [PATCH 13/95] move the computeDetailLevel and isInBounds from DepthMap to Region neither depends on the DepthMap and both operate on Region so it makes more sense to have them there also marks DepthMap fields as private and reduces type requirement from KNode to KShapeELement as that is sufficient for a Regions boundingRectangle --- packages/klighd-core/src/depth-map.ts | 148 +++++++++++++------------- 1 file changed, 76 insertions(+), 72 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 91c2d815..84233544 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -15,8 +15,8 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { KGraphElement, KNode } from "@kieler/klighd-interactive/lib/constraint-classes"; -import { SChildElement, SModelRoot } from "sprotty"; +import { KGraphElement } from "@kieler/klighd-interactive/lib/constraint-classes"; +import { SChildElement, SModelRoot, SShapeElement } from "sprotty"; import { Point, Viewport } from "sprotty-protocol"; import { RenderOptionsRegistry, FullDetailRelativeThreshold, FullDetailScaleThreshold } from "./options/render-options-registry"; import { isContainerRendering, isRendering, KRendering } from "./skgraph-models"; @@ -35,6 +35,9 @@ export enum DetailLevel { */ type DetailWithChildren = DetailLevel.FullDetails +type KChildElement = SChildElement & KGraphElement; +type KShapeElement = SShapeElement & KGraphElement; + /** * Type predicate to determine whether a DetailLevel is a DetailWithChildren level */ @@ -62,35 +65,35 @@ export class DepthMap { * The region for immediate children of the SModelRoot, * aka. the root regions */ - rootRegions: Region[]; + private rootRegions: Region[]; /** * The model for which the DepthMap is generated */ - rootElement: SModelRoot; + private rootElement: SModelRoot; /** * Maps a given node id to the containing/providing Region * Root Child Nodes will have a providing region and no containing Region, while all * other nodes will have at least a containing region */ - protected regionIndexMap: Map; + private regionIndexMap: Map; /** * The last viewport for which we updated the state of KNodes */ - viewport?: Viewport; + private viewport?: Viewport; /** * The threshold for which we updated the state of KNodes */ - lastThreshold?: number; + private lastThreshold?: number; /** * Set for handling regions, that need to be checked for detail level changes. * Consists of the region that contain at least one child with a lower detail level. */ - criticalRegions: Set; + private criticalRegions: Set; /** Singleton pattern */ private static instance?: DepthMap; @@ -158,7 +161,7 @@ export class DepthMap { * * @param element The KGraphElement to initialize for DepthMap usage */ - public initKGraphElement(element: SChildElement & KGraphElement, viewport: Viewport, renderingOptions: RenderOptionsRegistry): RegionIndexEntry { + public initKGraphElement(element: KChildElement, viewport: Viewport, renderingOptions: RenderOptionsRegistry): RegionIndexEntry { let entry = this.regionIndexMap.get(element.id) if (entry) { @@ -170,33 +173,33 @@ export class DepthMap { const scaleThreshold = renderingOptions.getValueOrDefault(FullDetailScaleThreshold) - if (element.parent === element.root && element instanceof KNode) { + if (element.parent === element.root && element instanceof SShapeElement) { const providedRegion = new Region(element) providedRegion.absolutePosition = element.bounds entry = { providingRegion: providedRegion, containingRegion: undefined } - providedRegion.detail = this.computeDetailLevel(providedRegion, viewport, relativeThreshold, scaleThreshold) + providedRegion.detail = providedRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) this.rootRegions.push(providedRegion) } else { - const parentEntry = this.initKGraphElement(element.parent as KNode, viewport, renderingOptions); + const parentEntry = this.initKGraphElement(element.parent as KChildElement, viewport, renderingOptions); entry = { containingRegion: parentEntry.providingRegion ?? parentEntry.containingRegion, providingRegion: undefined } const kRendering = this.findRendering(element) - if (element instanceof KNode && kRendering && isContainerRendering(kRendering) && kRendering.children.length !== 0) { + if (element instanceof SShapeElement && kRendering && isContainerRendering(kRendering) && kRendering.children.length !== 0) { entry = { containingRegion: entry.containingRegion, providingRegion: new Region(element) } - entry.providingRegion.detail = this.computeDetailLevel(entry.providingRegion, viewport, relativeThreshold, scaleThreshold) + entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) entry.providingRegion.parent = entry.containingRegion entry.containingRegion.children.push(entry.providingRegion); - let current = element.parent as KNode; + let current = element.parent as SShapeElement; let offsetX = 0; let offsetY = 0; @@ -205,7 +208,7 @@ export class DepthMap { while (current && currentEntry && !currentEntry.providingRegion) { offsetX += current.bounds.x offsetY += current.bounds.y - current = current.parent as KNode + current = current.parent as SShapeElement currentEntry = this.regionIndexMap.get(current.id) } @@ -240,12 +243,12 @@ export class DepthMap { return undefined } - public getContainingRegion(element: SChildElement & KGraphElement, viewport: Viewport, renderOptions: RenderOptionsRegistry): Region | undefined { + public getContainingRegion(element: KChildElement, viewport: Viewport, renderOptions: RenderOptionsRegistry): Region | undefined { // initKGraphELement already checks if it is already initialized and if it is returns the existing value return this.initKGraphElement(element, viewport, renderOptions).containingRegion } - public getProvidingRegion(node: SChildElement & KNode, viewport: Viewport, renderOptions: RenderOptionsRegistry): Region | undefined { + public getProvidingRegion(node: KShapeElement, viewport: Viewport, renderOptions: RenderOptionsRegistry): Region | undefined { // initKGraphElement already checks if it is already initialized and if it is returns the existing value return this.initKGraphElement(node, viewport, renderOptions).providingRegion } @@ -274,7 +277,7 @@ export class DepthMap { // Initialize detail level on first run. if (this.criticalRegions.size == 0) { for (const region of this.rootRegions) { - const vis = this.computeDetailLevel(region, viewport, relativeThreshold, scaleThreshold) + const vis = region.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) if (vis === DetailLevel.FullDetails) { this.updateRegionDetailLevel(region, vis, viewport, relativeThreshold, scaleThreshold) } @@ -296,7 +299,7 @@ export class DepthMap { let isCritical = false; region.children.forEach(childRegion => { - const childVis = this.computeDetailLevel(childRegion, viewport, relativeThreshold, scaleThreshold); + const childVis = childRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold); if (childVis < vis) { isCritical = true } @@ -341,7 +344,7 @@ export class DepthMap { while (toBeProcessed.size !== 0) { toBeProcessed.forEach(region => { - const vis = this.computeDetailLevel(region, viewport, relativeThreshold, scaleThreshold); + const vis = region.computeDetailLevel(viewport, relativeThreshold, scaleThreshold); region.setDetailLevel(vis) if (region.parent && vis !== region.parent.detail) { @@ -365,54 +368,6 @@ export class DepthMap { } - /** - * Decides the appropriate detail level for a region - * based on their size in the viewport and visibility - * - * @param region The region in question - * @param viewport The current viewport - * @param relativeThreshold The full detail threshold - * @returns The appropriate detail level - */ - computeDetailLevel(region: Region, viewport: Viewport, relativeThreshold: number, scaleThreshold: number): DetailLevel { - if (!this.isInBounds(region, viewport)) { - return DetailLevel.OutOfBounds - } else if (!region.parent) { - // Regions without parents should always be full detail if they are visible - return DetailLevel.FullDetails - } else { - const viewportSize = this.sizeInViewport(region.boundingRectangle, viewport) - const scale = viewport.zoom - // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. - if (viewportSize >= relativeThreshold || scale > scaleThreshold) { - return DetailLevel.FullDetails - } else { - return DetailLevel.MinimalDetails - } - } - } - - /** - * Checks visibility of a region with position from browser coordinates in current viewport. - * - * @param region The region in question for visibility. - * @param viewport The current viewport. - * @returns Boolean value indicating the visibility of the region in the current viewport. - */ - isInBounds(region: Region, viewport: Viewport): boolean { - if (region.absolutePosition) { - const canvasBounds = this.rootElement.canvasBounds - - return region.absolutePosition.x + region.boundingRectangle.bounds.width - viewport.scroll.x >= 0 - && region.absolutePosition.x - viewport.scroll.x <= (canvasBounds.width / viewport.zoom) - && region.absolutePosition.y + region.boundingRectangle.bounds.height - viewport.scroll.y >= 0 - && region.absolutePosition.y - viewport.scroll.y <= (canvasBounds.height / viewport.zoom) - } else { - // Better to assume it is visible, if information are not sufficient - return true - } - } - /** * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. * @@ -420,7 +375,7 @@ export class DepthMap { * @param viewport The current viewport * @returns the relative size of the KNodes shortest dimension */ - sizeInViewport(node: KNode, viewport: Viewport): number { + static sizeInViewport(node: SShapeElement, viewport: Viewport): number { const horizontal = node.bounds.width / (node.root.canvasBounds.width / viewport.zoom) const vertical = node.bounds.height / (node.root.canvasBounds.height / viewport.zoom) return horizontal < vertical ? horizontal : vertical @@ -435,7 +390,7 @@ export class DepthMap { */ export class Region { /** The rectangle of the child area in which the region lies. */ - boundingRectangle: KNode + boundingRectangle: SShapeElement /** The absolute position of the boundingRectangle based on the layout information of the SModel. */ absolutePosition: Point /** the regions current detail level that is used by all children */ @@ -449,12 +404,61 @@ export class Region { /** Indentation of region title. */ regionTitleIndentation?: number /** Constructor initializes element array for region. */ - constructor(boundingRectangle: KNode) { + constructor(boundingRectangle: SShapeElement) { this.boundingRectangle = boundingRectangle this.children = [] this.detail = DetailLevel.FullDetails } + /** + * Checks visibility of a region with position from browser coordinates in current viewport. + * + * @param region The region in question for visibility. + * @param viewport The current viewport. + * @returns Boolean value indicating the visibility of the region in the current viewport. + */ + isInBounds(viewport: Viewport): boolean { + if (this.absolutePosition) { + const canvasBounds = this.boundingRectangle.root.canvasBounds + + return this.absolutePosition.x + this.boundingRectangle.bounds.width - viewport.scroll.x >= 0 + && this.absolutePosition.x - viewport.scroll.x <= (canvasBounds.width / viewport.zoom) + && this.absolutePosition.y + this.boundingRectangle.bounds.height - viewport.scroll.y >= 0 + && this.absolutePosition.y - viewport.scroll.y <= (canvasBounds.height / viewport.zoom) + } else { + // Better to assume it is visible, if information are not sufficient + return true + } + } + + + /** + * Decides the appropriate detail level for a region + * based on their size in the viewport and visibility + * + * @param region The region in question + * @param viewport The current viewport + * @param relativeThreshold The full detail threshold + * @returns The appropriate detail level + */ + computeDetailLevel(viewport: Viewport, relativeThreshold: number, scaleThreshold: number): DetailLevel { + if (!this.isInBounds(viewport)) { + return DetailLevel.OutOfBounds + } else if (!this.parent) { + // Regions without parents should always be full detail if they are visible + return DetailLevel.FullDetails + } else { + const viewportSize = DepthMap.sizeInViewport(this.boundingRectangle, viewport) + const scale = viewport.zoom + // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. + if (viewportSize >= relativeThreshold || scale > scaleThreshold) { + return DetailLevel.FullDetails + } else { + return DetailLevel.MinimalDetails + } + } + } + /** * Applies the detail level to all elements of a region. * @param level the detail level to apply From c29771b485d015f6114dfa6f4ec8c0e5a16792e0 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 15:06:35 +0100 Subject: [PATCH 14/95] calculate the detailLevel after determining the absolute position --- packages/klighd-core/src/depth-map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 84233544..a1815176 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -194,8 +194,6 @@ export class DepthMap { entry = { containingRegion: entry.containingRegion, providingRegion: new Region(element) } - entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) - entry.providingRegion.parent = entry.containingRegion entry.containingRegion.children.push(entry.providingRegion); @@ -219,6 +217,8 @@ export class DepthMap { x: offsetX + element.bounds.x, y: offsetY + element.bounds.y } + + entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) } } From 789c349e2e4f34c689b0142e6c5632f66671f3d1 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 15:08:10 +0100 Subject: [PATCH 15/95] move sizeInViewport to Region as it is only used on the boundingRectangle of Regions --- packages/klighd-core/src/depth-map.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index a1815176..be4f75e2 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -368,18 +368,6 @@ export class DepthMap { } - /** - * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. - * - * @param node The KNode in question - * @param viewport The current viewport - * @returns the relative size of the KNodes shortest dimension - */ - static sizeInViewport(node: SShapeElement, viewport: Viewport): number { - const horizontal = node.bounds.width / (node.root.canvasBounds.width / viewport.zoom) - const vertical = node.bounds.height / (node.root.canvasBounds.height / viewport.zoom) - return horizontal < vertical ? horizontal : vertical - } } @@ -431,6 +419,18 @@ export class Region { } } + /** + * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. + * + * @param node The KNode in question + * @param viewport The current viewport + * @returns the relative size of the KNodes shortest dimension + */ + sizeInViewport(viewport: Viewport): number { + const horizontal = this.boundingRectangle.bounds.width / (this.boundingRectangle.root.canvasBounds.width / viewport.zoom) + const vertical = this.boundingRectangle.bounds.height / (this.boundingRectangle.root.canvasBounds.height / viewport.zoom) + return horizontal < vertical ? horizontal : vertical + } /** * Decides the appropriate detail level for a region @@ -448,7 +448,7 @@ export class Region { // Regions without parents should always be full detail if they are visible return DetailLevel.FullDetails } else { - const viewportSize = DepthMap.sizeInViewport(this.boundingRectangle, viewport) + const viewportSize = this.sizeInViewport(viewport) const scale = viewport.zoom // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. if (viewportSize >= relativeThreshold || scale > scaleThreshold) { From 0a52477cd57f5a6de40b55a664394925c9a5dec6 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 12:01:42 +0100 Subject: [PATCH 16/95] cleanup duplication between title scaling and node scaling --- packages/klighd-core/src/scaling-util.ts | 55 ++++++++++++++++++++ packages/klighd-core/src/views-rendering.tsx | 39 ++------------ packages/klighd-core/src/views.tsx | 18 ++----- 3 files changed, 62 insertions(+), 50 deletions(-) create mode 100644 packages/klighd-core/src/scaling-util.ts diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts new file mode 100644 index 00000000..01136f83 --- /dev/null +++ b/packages/klighd-core/src/scaling-util.ts @@ -0,0 +1,55 @@ +import { Bounds, Dimension, Viewport } from 'sprotty-protocol' + +export function maxParentScale(node: Bounds, parent: Bounds): number { + // the maximum scale that keeps the node in bounds height wise + const maxHeightScale = parent.height / node.height + // the maximum scale that keeps the node in bounds width wise + const maxWidthScale = parent.width / node.width + + return Math.max(maxHeightScale, maxWidthScale) +} + +/* +* Calculates at what scale we reach the desired screen size based on our model size and the viewport +*/ +export function desiredScale(desiredSize: number, originalSize: number, viewport: Viewport) : number{ + return desiredSize / (originalSize * viewport.zoom) +} + +export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { + const originalWidth = originalBounds.width + const originalHeight = originalBounds.height + const originalX = originalBounds.x + const originalY = originalBounds.y + + // Calculate the new x and y indentation: + // width required of scaled rendering + const newWidth = originalWidth * scale + // space to the left of the rendering without scaling... + const spaceL = originalX + // ...and to its right + const spaceR = availableSpace.width - originalX - originalWidth + // New x value after taking space off both sides at an equal ratio + const newX = originalX - spaceL * (newWidth - originalWidth) / (spaceL + spaceR) + + // Same for y axis, just with switched dimensional variables. + const newHeight = originalHeight * scale + const spaceT = originalY + const spaceB = availableSpace.height - originalY - originalHeight + const newY = originalY - spaceT * (newHeight - originalHeight) / (spaceT + spaceB) + return {x: newX, y : newY, width: newWidth, height: newHeight} +} + +export function upscaleBounds(currentSize: number, desiredSize: number, childBounds: Bounds, parentBounds: Bounds, viewport: Viewport) : {bounds: Bounds, scale: number} { + + const desiredHightScale = desiredScale(desiredSize, currentSize, viewport); + const parentScaling = maxParentScale(childBounds, parentBounds) + + const preferredScale = Math.min(desiredHightScale, parentScaling) + + const scalingFactor = Math.max(1, preferredScale) + + const newBounds = calculateScaledBounds(childBounds, parentBounds, scalingFactor) + + return {bounds:newBounds, scale: scalingFactor} +} diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index f2e380e4..a5d0305e 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -17,7 +17,7 @@ /** @jsx svg */ import { VNode } from 'snabbdom'; import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { Bounds ,Dimension} from 'sprotty-protocol'; +import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, MinimumTitleHeight, UseSmartZoom } from './options/render-options-registry'; @@ -32,6 +32,7 @@ import { ColorStyles, DEFAULT_CLICKABLE_FILL, DEFAULT_FILL, getKStyles, getSvgColorStyle, getSvgColorStyles, getSvgLineStyles, getSvgShadowStyles, getSvgTextStyles, isInvisible, KStyles, LineStyles } from './views-styles'; +import { upscaleBounds } from './scaling-util'; // ----------------------------- Functions for rendering different KRendering as VNodes in svg -------------------------------------------- @@ -856,30 +857,6 @@ export function getRendering(datas: KGraphData[], parent: SKGraphElement, propag return renderKRendering(kRendering, parent, propagatedStyles, context, childOfNodeTitle) } -export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { - const originalWidth = originalBounds.width - const originalHeight = originalBounds.height - const originalX = originalBounds.x - const originalY = originalBounds.y - - // Calculate the new x and y indentation: - // width required of scaled rendering - const newWidth = originalWidth * scale - // space to the left of the rendering without scaling... - const spaceL = originalX - // ...and to its right - const spaceR = availableSpace.width - originalX - originalWidth - // New x value after taking space off both sides at an equal ratio - const newX = originalX - spaceL * (newWidth - originalWidth) / (spaceL + spaceR) - - // Same for y axis, just with switched dimensional variables. - const newHeight = originalHeight * scale - const spaceT = originalY - const spaceB = availableSpace.height - originalY - originalHeight - const newY = originalY - spaceT * (newHeight - originalHeight) / (spaceT + spaceB) - return {x: newX, y : newY, width: newWidth, height: newHeight} -} - /** * Translates any KRendering into an SVG rendering. * @param kRendering The rendering. @@ -945,17 +922,7 @@ export function renderKRendering(kRendering: KRendering, const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds const originalBounds = boundingBox - const maxScaleX = parentBounds.width / originalBounds.width - const maxScaleY = parentBounds.height / originalBounds.height - - let maxScale = overlayThreshold / (boundingBox.height * context.viewport.zoom); - - // limit the scaling so that it does not exceed the parents size - let scalingFactor = Math.min(maxScaleX, maxScaleY, maxScale) - // Make sure we never scale down. - scalingFactor = Math.max(scalingFactor, 1) - - const newBounds = calculateScaledBounds(boundingBox, parentBounds, scalingFactor) + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(originalBounds.height,overlayThreshold, originalBounds, parentBounds, context.viewport ); // Apply the new bounds and scaling as the element's transformation. const translateAndScale = `translate(${newBounds.x},${newBounds.y})scale(${scalingFactor})` diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index e97d0070..32047892 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -20,14 +20,15 @@ import { renderConstraints, renderInteractiveLayout } from '@kieler/klighd-inter import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/klighd-interactive-mouselistener'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, SShapeElement, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom,MinimumTitleHeight, PerformNodeScaling } from './options/render-options-registry'; +import { upscaleBounds } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; -import { getJunctionPointRenderings, getRendering,calculateScaledBounds } from './views-rendering'; +import { getJunctionPointRenderings, getRendering } from './views-rendering'; import { KStyles } from './views-styles'; /** @@ -167,19 +168,8 @@ export class KNodeView implements IView { && providingRegion.regionTitleHeight * ctx.viewport.zoom < minHeight && ctx.renderOptionsRegistry.getValueOrDefault(PerformNodeScaling)) { + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(providingRegion.regionTitleHeight, minHeight, node.bounds, (node.parent as SShapeElement).bounds, ctx.viewport); - // the scale required to scale the title to the minHeight - const desiredHightScale = minHeight / (providingRegion.regionTitleHeight * ctx.viewport.zoom); - // the maximum scale that keeps the node in bounds height wise - const maxHeightScale = (node.parent as SKNode).bounds.height / node.bounds.height - // the maximum scale that keeps the node in bounds width wise - const maxWidthScale = (node.parent as SKNode).bounds.width / node.bounds.width - - // the most restrictive scaling of the three above - const preferredScale = Math.min(desiredHightScale, maxHeightScale,maxWidthScale) - - const scalingFactor = Math.max(1, preferredScale) - const newBounds = calculateScaledBounds(node.bounds, (node.parent as SKNode).bounds, scalingFactor) if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 return "" From 1e0887e39c5fd7d7affc290c77cc506736f3a5a3 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 17:37:40 +0100 Subject: [PATCH 17/95] take siblings into account when scaling nodes --- packages/klighd-core/src/scaling-util.ts | 57 ++++++++++++++++++------ packages/klighd-core/src/views.tsx | 7 ++- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 01136f83..d86ffce3 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -9,6 +9,36 @@ export function maxParentScale(node: Bounds, parent: Bounds): number { return Math.max(maxHeightScale, maxWidthScale) } + +function inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number): number { + // we want to find positive scale so that + // result_a = scaleDimension(offset_a, length_a, available, scale) + // result_b = scaleDimension(offset_b, length_b, available, scale) + // result_a.offset = result_b.offset + result_b.length + // || result_b.offset = result_a.offset + result_a.length + + const fa = (offset_a * length_a) / (available - length_a) + const fb = (offset_b * length_b) / (available - length_b) + + const numerator = offset_a + fa - offset_b - fb + + const result_1 = numerator / (fa - fb + length_a) + const result_2 = -numerator / (fb - fa + length_b) + + // the scale should be at least one and at most one of the results should be positive + return Math.max(result_1, result_2, 1) +} + +export function maxSiblingScale(node: Bounds, parent:Bounds, sibling: Bounds) : number { + + // calculate the scale for each dimension at which we reach our sibling + const result_1 = inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width) + const result_2 = inverseScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height) + + // take the max as that which ever is further is relevant for bounding us, but should be at least 1 + return Math.max(result_1, result_2, 1) +} + /* * Calculates at what scale we reach the desired screen size based on our model size and the viewport */ @@ -24,28 +54,29 @@ export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Di // Calculate the new x and y indentation: // width required of scaled rendering - const newWidth = originalWidth * scale - // space to the left of the rendering without scaling... - const spaceL = originalX - // ...and to its right - const spaceR = availableSpace.width - originalX - originalWidth - // New x value after taking space off both sides at an equal ratio - const newX = originalX - spaceL * (newWidth - originalWidth) / (spaceL + spaceR) + const {length: newWidth, offset: newX} = scaleDimension(originalX, originalWidth, availableSpace.width, scale) // Same for y axis, just with switched dimensional variables. - const newHeight = originalHeight * scale - const spaceT = originalY - const spaceB = availableSpace.height - originalY - originalHeight - const newY = originalY - spaceT * (newHeight - originalHeight) / (spaceT + spaceB) + const {length: newHeight, offset: newY} = scaleDimension(originalY, originalHeight, availableSpace.height, scale) return {x: newX, y : newY, width: newWidth, height: newHeight} } -export function upscaleBounds(currentSize: number, desiredSize: number, childBounds: Bounds, parentBounds: Bounds, viewport: Viewport) : {bounds: Bounds, scale: number} { +export function scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ + const newLength = length * scale; + const prefix = offset + const postfix = available - offset - length + const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) + return {offset: newOffset, length: newLength} +} + +export function upscaleBounds(currentSize: number, desiredSize: number, childBounds: Bounds, parentBounds: Bounds, viewport: Viewport, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { const desiredHightScale = desiredScale(desiredSize, currentSize, viewport); const parentScaling = maxParentScale(childBounds, parentBounds) - const preferredScale = Math.min(desiredHightScale, parentScaling) + const siblingScaling = siblings.map((siblingBounds) => maxSiblingScale(childBounds, parentBounds, siblingBounds)) + + const preferredScale = Math.min(desiredHightScale, parentScaling, ...siblingScaling) const scalingFactor = Math.max(1, preferredScale) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 32047892..76dbba1e 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -21,13 +21,14 @@ import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/k import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, SShapeElement, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { Bounds } from 'sprotty-protocol' import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom,MinimumTitleHeight, PerformNodeScaling } from './options/render-options-registry'; import { upscaleBounds } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; -import { SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; +import { NODE_TYPE, SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; import { getJunctionPointRenderings, getRendering } from './views-rendering'; import { KStyles } from './views-styles'; @@ -168,7 +169,9 @@ export class KNodeView implements IView { && providingRegion.regionTitleHeight * ctx.viewport.zoom < minHeight && ctx.renderOptionsRegistry.getValueOrDefault(PerformNodeScaling)) { - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(providingRegion.regionTitleHeight, minHeight, node.bounds, (node.parent as SShapeElement).bounds, ctx.viewport); + const siblings: Bounds[] = node.parent.children.filter((sibling) => sibling != node && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) + + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(providingRegion.regionTitleHeight, minHeight, node.bounds, (node.parent as SShapeElement).bounds, ctx.viewport, siblings); if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From 8e7c479a6c4608af1ca47cfece8668efd59dc4ab Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 17:48:37 +0100 Subject: [PATCH 18/95] fix maxParentScale --- packages/klighd-core/src/scaling-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index d86ffce3..de7b20b3 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -6,7 +6,7 @@ export function maxParentScale(node: Bounds, parent: Bounds): number { // the maximum scale that keeps the node in bounds width wise const maxWidthScale = parent.width / node.width - return Math.max(maxHeightScale, maxWidthScale) + return Math.min(maxHeightScale, maxWidthScale) } From 51e9b9e0480dd06bf9f218c61954e35b2861160e Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 18:08:27 +0100 Subject: [PATCH 19/95] fix siblings --- packages/klighd-core/src/scaling-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index de7b20b3..02f8a3fd 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -22,8 +22,8 @@ function inverseScaleDimension(offset_a: number, length_a: number, offset_b: num const numerator = offset_a + fa - offset_b - fb - const result_1 = numerator / (fa - fb + length_a) - const result_2 = -numerator / (fb - fa + length_b) + const result_1 = numerator / (fa - fb + length_b) + const result_2 = -numerator / (fb - fa + length_a) // the scale should be at least one and at most one of the results should be positive return Math.max(result_1, result_2, 1) From 24f7cf31d286ff528f415da3d53df6d210ffa7e7 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 19 Jan 2022 18:09:36 +0100 Subject: [PATCH 20/95] make scaling nodes independent of scaling titles still require the title height to scale the node accordingly and thus require the DepthMap and thus UseSmartZoom --- .../src/options/render-options-registry.ts | 15 ++++++++++++ packages/klighd-core/src/views-rendering.tsx | 24 +++++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 5ff87f6d..3119beed 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -155,6 +155,20 @@ export class TextSimplificationThreshold implements RangeOption { currentValue = 3 } +/** + * Boolean option to enable and disable the scaling of titles + */ +export class ScaleTitles implements RenderOption { + static readonly ID: string = 'use-title-scaling' + static readonly NAME: string = 'Scale Titles' + static readonly DEFAULT: boolean = false + readonly id: string = ScaleTitles.ID + readonly name: string = ScaleTitles.NAME + readonly type: TransformationOptionType = TransformationOptionType.CHECK + readonly initialValue: boolean = true + currentValue = true +} + /** * The minimum size a title should attempt to scale up to */ @@ -282,6 +296,7 @@ export class RenderOptionsRegistry extends Registry { this.register(SimplifySmallText); this.register(TextSimplificationThreshold); + this.register(ScaleTitles); this.register(MinimumTitleHeight); this.register(UseMinimumLineWidth); diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index a5d0305e..8be97c35 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -20,7 +20,7 @@ import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unu import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, MinimumTitleHeight, UseSmartZoom } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, MinimumTitleHeight, UseSmartZoom, ScaleTitles } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -894,11 +894,11 @@ export function renderKRendering(kRendering: KRendering, // remembers if this rendering is a title rendering and should therefore be rendered overlaying the other renderings. let isOverlay = false - const applyTitleScaling = context.renderOptionsRegistry.getValueOrDefault(UseSmartZoom) + const applyTitleScaling = context.renderOptionsRegistry.getValueOrDefault(UseSmartZoom) && context.renderOptionsRegistry.getValueOrDefault(ScaleTitles) // If this rendering is the main title rendering of the element, either render it usually if // zoomed in far enough or remember it to be rendered later scaled up and overlayed on top of the parent rendering. - if (applyTitleScaling && boundsAndTransformation.bounds.width && boundsAndTransformation.bounds.height && kRendering.isNodeTitle) { + if ( boundsAndTransformation.bounds.width && boundsAndTransformation.bounds.height && kRendering.isNodeTitle) { const overlayThreshold = context.renderOptionsRegistry.getValueOrDefault(MinimumTitleHeight) as number @@ -908,15 +908,19 @@ export function renderKRendering(kRendering: KRendering, const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails const multipleChildren = parent.children.length > 1 - if ( (notFullDetail && multipleChildren) || tooSmall ) { + let boundingBox = boundsAndTransformation.bounds + // For KTexts the x and y coordinates define the origin of the baseline, not the bounding box. + if (kRendering.type === K_TEXT) { + boundingBox = findBoundsAndTransformationData(kRendering, styles, parent, context, isEdge, true)?.bounds ?? boundingBox + } - isOverlay = true + if (providingRegion) { + providingRegion.regionTitleHeight = boundingBox.height + } - let boundingBox = boundsAndTransformation.bounds - // For KTexts the x and y coordinates define the origin of the baseline, not the bounding box. - if (kRendering.type === K_TEXT) { - boundingBox = findBoundsAndTransformationData(kRendering, styles, parent, context, isEdge, true)?.bounds ?? boundingBox - } + if ( applyTitleScaling && ((notFullDetail && multipleChildren) || tooSmall) ) { + + isOverlay = true const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds From 7436da2e88fc057c62416375bdfd611b71af37da Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 12:48:21 +0100 Subject: [PATCH 21/95] Revert "change title scaling to use a minimum height instead of a minimum scale" This reverts commit 5b9cb2fe559af788eab47b3f7331db34e46f8c41. This revert requires some adjustments to node scaling for that to continue working as intended # Conflicts: # packages/klighd-core/src/options/render-options-registry.ts # packages/klighd-core/src/views-rendering.tsx --- packages/klighd-core/src/depth-map.ts | 2 ++ .../src/options/render-options-registry.ts | 27 ++++++++++--------- packages/klighd-core/src/scaling-util.ts | 12 ++------- packages/klighd-core/src/views-rendering.tsx | 12 +++++---- packages/klighd-core/src/views.tsx | 10 ++++--- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index be4f75e2..74e5ab1a 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -387,6 +387,8 @@ export class Region { parent?: Region /** All immediate child regions of this region */ children: Region[] + /** The title height as defined in the model */ + originalTitleHeight?: number /** Contains the height of the title of the region, if there is one. */ regionTitleHeight?: number /** Indentation of region title. */ diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 3119beed..84c2575e 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -170,23 +170,24 @@ export class ScaleTitles implements RenderOption { } /** - * The minimum size a title should attempt to scale up to + * The factor by which titles of collapsed regions get scaled by + * in relation to their size at native resolution. */ -export class MinimumTitleHeight implements RangeOption { - static readonly ID: string = 'min-title-height' - static readonly NAME: string = 'Minimum Title Height' - static readonly DEFAULT: number = 10 - readonly id: string = MinimumTitleHeight.ID - readonly name: string = MinimumTitleHeight.NAME +export class TitleScalingFactor implements RangeOption { + static readonly ID: string = 'title-scaling-factor' + static readonly NAME: string = 'Title Scaling Factor' + static readonly DEFAULT: number = 1 + readonly id: string = TitleScalingFactor.ID + readonly name: string = TitleScalingFactor.NAME readonly type: TransformationOptionType = TransformationOptionType.RANGE readonly values: any[] = [] readonly range = { - first: 0, - second: 40 + first: 0.5, + second: 3 } - readonly stepSize = 1 - readonly initialValue: number = MinimumTitleHeight.DEFAULT - currentValue = MinimumTitleHeight.DEFAULT + readonly stepSize = 0.01 + readonly initialValue: number = TitleScalingFactor.DEFAULT + currentValue = 1 } /** @@ -297,7 +298,7 @@ export class RenderOptionsRegistry extends Registry { this.register(TextSimplificationThreshold); this.register(ScaleTitles); - this.register(MinimumTitleHeight); + this.register(TitleScalingFactor); this.register(UseMinimumLineWidth); this.register(MinimumLineWidth); diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 02f8a3fd..159cf95d 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -39,13 +39,6 @@ export function maxSiblingScale(node: Bounds, parent:Bounds, sibling: Bounds) : return Math.max(result_1, result_2, 1) } -/* -* Calculates at what scale we reach the desired screen size based on our model size and the viewport -*/ -export function desiredScale(desiredSize: number, originalSize: number, viewport: Viewport) : number{ - return desiredSize / (originalSize * viewport.zoom) -} - export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { const originalWidth = originalBounds.width const originalHeight = originalBounds.height @@ -69,14 +62,13 @@ export function scaleDimension(offset: number, length: number, available: number return {offset: newOffset, length: newLength} } -export function upscaleBounds(currentSize: number, desiredSize: number, childBounds: Bounds, parentBounds: Bounds, viewport: Viewport, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { +export function upscaleBounds(currentSize: number, maxScale: number, childBounds: Bounds, parentBounds: Bounds, viewport: Viewport, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { - const desiredHightScale = desiredScale(desiredSize, currentSize, viewport); const parentScaling = maxParentScale(childBounds, parentBounds) const siblingScaling = siblings.map((siblingBounds) => maxSiblingScale(childBounds, parentBounds, siblingBounds)) - const preferredScale = Math.min(desiredHightScale, parentScaling, ...siblingScaling) + const preferredScale = Math.min(maxScale, parentScaling, ...siblingScaling) const scalingFactor = Math.max(1, preferredScale) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 8be97c35..b3792a64 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -20,7 +20,7 @@ import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unu import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, MinimumTitleHeight, UseSmartZoom, ScaleTitles } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -900,11 +900,12 @@ export function renderKRendering(kRendering: KRendering, // zoomed in far enough or remember it to be rendered later scaled up and overlayed on top of the parent rendering. if ( boundsAndTransformation.bounds.width && boundsAndTransformation.bounds.height && kRendering.isNodeTitle) { - const overlayThreshold = context.renderOptionsRegistry.getValueOrDefault(MinimumTitleHeight) as number - + // Scale to limit of bounding box or max size. + const titleScalingFactorOption = context.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor) as number + const maxScale = titleScalingFactorOption / context.viewport.zoom // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) - const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= overlayThreshold + const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails const multipleChildren = parent.children.length > 1 @@ -915,6 +916,7 @@ export function renderKRendering(kRendering: KRendering, } if (providingRegion) { + providingRegion.originalTitleHeight = boundingBox.height providingRegion.regionTitleHeight = boundingBox.height } @@ -926,7 +928,7 @@ export function renderKRendering(kRendering: KRendering, const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds const originalBounds = boundingBox - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(originalBounds.height,overlayThreshold, originalBounds, parentBounds, context.viewport ); + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(originalBounds.height, maxScale, originalBounds, parentBounds, context.viewport ); // Apply the new bounds and scaling as the element's transformation. const translateAndScale = `translate(${newBounds.x},${newBounds.y})scale(${scalingFactor})` diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 76dbba1e..58151873 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -25,7 +25,7 @@ import { Bounds } from 'sprotty-protocol' import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; -import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom,MinimumTitleHeight, PerformNodeScaling } from './options/render-options-registry'; +import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, PerformNodeScaling, TitleScalingFactor } from './options/render-options-registry'; import { upscaleBounds } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { NODE_TYPE, SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; @@ -161,17 +161,19 @@ export class KNodeView implements IView { const providingRegion = ctx.depthMap?.getProvidingRegion(node , ctx.viewport, ctx.renderOptionsRegistry); - const minHeight = ctx.renderOptionsRegistry.getValueOrDefault(MinimumTitleHeight); + const minTitleScale = ctx.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor); const calcScale = function() {if (node.parent && providingRegion + && providingRegion.originalTitleHeight && providingRegion.regionTitleHeight - && providingRegion.regionTitleHeight * ctx.viewport.zoom < minHeight + && providingRegion.regionTitleHeight * ctx.viewport.zoom < providingRegion.originalTitleHeight * minTitleScale && ctx.renderOptionsRegistry.getValueOrDefault(PerformNodeScaling)) { const siblings: Bounds[] = node.parent.children.filter((sibling) => sibling != node && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(providingRegion.regionTitleHeight, minHeight, node.bounds, (node.parent as SShapeElement).bounds, ctx.viewport, siblings); + const maxScale = minTitleScale / ctx.viewport.zoom / (providingRegion.regionTitleHeight / providingRegion.originalTitleHeight) + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(providingRegion.regionTitleHeight, maxScale, node.bounds, (node.parent as SShapeElement).bounds, ctx.viewport, siblings); if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From cd4211a8db552f22e2e981336985b449a2b639cb Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 20 Jan 2022 18:10:06 +0100 Subject: [PATCH 22/95] fix incorrectly placed end tag --- packages/klighd-core/src/views.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 58151873..0c37f4ed 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -214,10 +214,10 @@ export class KNodeView implements IView { const titles = ctx.titles.pop() ?? [] const childRenderings = ctx.renderChildren(node) const translateAndScale = calcScale() - const ret = + const ret = {titles} {childRenderings} - + return ret } if (interactiveNodes) { From e41352f08117165fd96b362884bc399d684f842e Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 21 Jan 2022 13:22:49 +0100 Subject: [PATCH 23/95] apply node scaling before title scaling --- .../src/options/render-options-registry.ts | 36 +++++++-- packages/klighd-core/src/scaling-util.ts | 14 +++- .../klighd-core/src/skgraph-model-renderer.ts | 17 +++- packages/klighd-core/src/views-rendering.tsx | 21 +++-- packages/klighd-core/src/views.tsx | 78 +++++++++++-------- 5 files changed, 114 insertions(+), 52 deletions(-) diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 84c2575e..00caddea 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -225,17 +225,38 @@ export class MinimumLineWidth implements RangeOption { /** * Boolean option to toggle the scaling of lines based on zoom level. */ -export class PerformNodeScaling implements RenderOption { - static readonly ID: string = 'perform-node-scaling' - static readonly NAME: string = 'Perform Node Scaling' +export class ScaleNodes implements RenderOption { + static readonly ID: string = 'use-node-scaling' + static readonly NAME: string = 'Scale Nodes' static readonly DEFAULT: boolean = false - readonly id: string = PerformNodeScaling.ID - readonly name: string = PerformNodeScaling.NAME + readonly id: string = ScaleNodes.ID + readonly name: string = ScaleNodes.NAME readonly type: TransformationOptionType = TransformationOptionType.CHECK readonly initialValue: boolean = true currentValue = true } +/** + * The factor by which titles of collapsed regions get scaled by + * in relation to their size at native resolution. + */ +export class NodeScalingFactor implements RangeOption { + static readonly ID: string = 'node-scaling-factor' + static readonly NAME: string = 'Node Scaling Factor' + static readonly DEFAULT: number = 1 + readonly id: string = NodeScalingFactor.ID + readonly name: string = NodeScalingFactor.NAME + readonly type: TransformationOptionType = TransformationOptionType.RANGE + readonly values: any[] = [] + readonly range = { + first: 0.5, + second: 3 + } + readonly stepSize = 0.01 + readonly initialValue: number = NodeScalingFactor.DEFAULT + currentValue = 1 +} + /** * The style shadows should be drawn in, either the paper mode shadows (nice, but slow in * performance) or in default KIELER-style (fast, not as nice looking). @@ -300,9 +321,12 @@ export class RenderOptionsRegistry extends Registry { this.register(ScaleTitles); this.register(TitleScalingFactor); + this.register(ScaleNodes); + this.register(NodeScalingFactor); + + this.register(UseMinimumLineWidth); this.register(MinimumLineWidth); - this.register(PerformNodeScaling); this.register(PaperShadows) this.register(AnimateGoToBookmark); diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 159cf95d..3389d21b 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,4 +1,4 @@ -import { Bounds, Dimension, Viewport } from 'sprotty-protocol' +import { Bounds, Dimension } from 'sprotty-protocol' export function maxParentScale(node: Bounds, parent: Bounds): number { // the maximum scale that keeps the node in bounds height wise @@ -62,14 +62,22 @@ export function scaleDimension(offset: number, length: number, available: number return {offset: newOffset, length: newLength} } -export function upscaleBounds(currentSize: number, maxScale: number, childBounds: Bounds, parentBounds: Bounds, viewport: Viewport, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { +export function upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Bounds, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { + // we want that the effectiveScale * desiredScale = maxScale + // so that the we effectively up scale to maxScale + const desiredScale = maxScale / effectiveScale; + + // the maximum scale at which the child still fits into the parent const parentScaling = maxParentScale(childBounds, parentBounds) + // some maximum scale at which the child does not interfere with its siblings const siblingScaling = siblings.map((siblingBounds) => maxSiblingScale(childBounds, parentBounds, siblingBounds)) - const preferredScale = Math.min(maxScale, parentScaling, ...siblingScaling) + // the most restrictive scale between our desired scale and the maximum imposed by the parent and siblings + const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScaling) + // we never want to shrink, should only be relevant if our desired scale is less than 1 const scalingFactor = Math.max(1, preferredScale) const newBounds = calculateScaledBounds(childBounds, parentBounds, scalingFactor) diff --git a/packages/klighd-core/src/skgraph-model-renderer.ts b/packages/klighd-core/src/skgraph-model-renderer.ts index 6b70a9a5..b9c73903 100644 --- a/packages/klighd-core/src/skgraph-model-renderer.ts +++ b/packages/klighd-core/src/skgraph-model-renderer.ts @@ -42,7 +42,20 @@ export class SKGraphModelRenderer extends ModelRenderer { renderOptionsRegistry: RenderOptionsRegistry titles: VNode[][] viewport: Viewport - + private _effectiveZoom: number[] = [1] + + get effectiveZoom(): number { + return this._effectiveZoom[this._effectiveZoom.length - 1] + } + + pushEffectiveZoom(zoom: number): void { + this._effectiveZoom.push(zoom) + } + + popEffectiveZoom(): number | undefined { + return this._effectiveZoom.pop() + } + /** * Renders all children of the SKGraph that should be rendered within the child area of the element. @@ -77,4 +90,4 @@ export class SKGraphModelRenderer extends ModelRenderer { }) .filter(vnode => vnode !== undefined) as VNode[] } -} \ No newline at end of file +} diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index b3792a64..b5a36510 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -210,10 +210,9 @@ export function renderRectangularShape( const bounds = Math.min(region.boundingRectangle.bounds.height - offsetY, region.boundingRectangle.bounds.width - offsetX) const size = 50 let scalingFactor = Math.max(bounds, 0) / size + // Use zoom for constant size in viewport. - if (context.viewport) { - scalingFactor = Math.min(1 / context.viewport.zoom, scalingFactor) - } + scalingFactor = Math.min(1 / context.effectiveZoom, scalingFactor) const y = scalingFactor > 0 ? offsetY / scalingFactor : 0 const x = scalingFactor > 0 ? offsetX / scalingFactor : 0 @@ -416,8 +415,7 @@ export function renderKText(rendering: KText, const simplificationThreshold = context.renderOptionsRegistry.getValueOrDefault(TextSimplificationThreshold) const proportionalHeight = 0.5 // height of replacement compared to full text height - if (context.viewport && rendering.calculatedTextBounds - && rendering.calculatedTextBounds.height * context.viewport.zoom <= simplificationThreshold) { + if (rendering.calculatedTextBounds && rendering.calculatedTextBounds.height * context.effectiveZoom <= simplificationThreshold) { const replacements: VNode[] = [] lines.forEach((line, index) => { const xPos = boundsAndTransformation && boundsAndTransformation.bounds.x ? boundsAndTransformation.bounds.x : 0 @@ -902,7 +900,7 @@ export function renderKRendering(kRendering: KRendering, // Scale to limit of bounding box or max size. const titleScalingFactorOption = context.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor) as number - const maxScale = titleScalingFactorOption / context.viewport.zoom + const maxScale = titleScalingFactorOption // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height @@ -928,7 +926,8 @@ export function renderKRendering(kRendering: KRendering, const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds const originalBounds = boundingBox - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(originalBounds.height, maxScale, originalBounds, parentBounds, context.viewport ); + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(context.effectiveZoom, maxScale, originalBounds, parentBounds); + context.pushEffectiveZoom(context.effectiveZoom * scalingFactor) // Apply the new bounds and scaling as the element's transformation. const translateAndScale = `translate(${newBounds.x},${newBounds.y})scale(${scalingFactor})` @@ -979,7 +978,11 @@ export function renderKRendering(kRendering: KRendering, if ((!providingRegion || providingRegion.detail === DetailLevel.FullDetails) && tooSmall && !isEmptyText) { overlayRectangle = } + } else { + context.pushEffectiveZoom(context.effectiveZoom) } + } else { + context.pushEffectiveZoom(context.effectiveZoom) } // Add the transformations to be able to positon the title correctly and above other elements context.positions[context.positions.length - 1] += (boundsAndTransformation?.transformation ?? "") @@ -987,6 +990,7 @@ export function renderKRendering(kRendering: KRendering, let svgRendering: VNode switch (kRendering.type) { case K_CONTAINER_RENDERING: { + context.popEffectiveZoom() console.error('A rendering can not be a ' + kRendering.type + ' by itself, it needs to be a subclass of it.') return undefined } @@ -995,6 +999,7 @@ export function renderKRendering(kRendering: KRendering, break } case K_CUSTOM_RENDERING: { + context.popEffectiveZoom() console.error('The rendering for ' + kRendering.type + ' is not implemented yet.') // data as KCustomRendering return undefined @@ -1019,6 +1024,7 @@ export function renderKRendering(kRendering: KRendering, break } default: { + context.popEffectiveZoom() console.error('The rendering is of an unknown type:' + kRendering.type) return undefined } @@ -1027,6 +1033,7 @@ export function renderKRendering(kRendering: KRendering, if (overlayRectangle) { svgRendering.children?.unshift(overlayRectangle) } + context.popEffectiveZoom() if (isOverlay) { // Don't render this now if we have an overlay, but remember it to be put on top by the node rendering. context.titles[context.titles.length - 1].push(svgRendering) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 0c37f4ed..66b7ca98 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -25,7 +25,7 @@ import { Bounds } from 'sprotty-protocol' import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; -import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, PerformNodeScaling, TitleScalingFactor } from './options/render-options-registry'; +import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes, NodeScalingFactor } from './options/render-options-registry'; import { upscaleBounds } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { NODE_TYPE, SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; @@ -52,6 +52,9 @@ export class SKGraphView implements IView { const viewport = findParentByFeature(model, isViewport) if (viewport) { ctx.viewport = viewport + ctx.pushEffectiveZoom(ctx.effectiveZoom * viewport.zoom) + } else { + ctx.pushEffectiveZoom(ctx.effectiveZoom) } ctx.titles = [] ctx.positions = [] @@ -71,11 +74,14 @@ export class SKGraphView implements IView { } const transform = `scale(${model.zoom}) translate(${-model.scroll.x},${-model.scroll.y})`; - return + + const rendered = - {context.renderChildren(model)} - - ; + {context.renderChildren(model)} + + ; + ctx.popEffectiveZoom() + return rendered; } } @@ -112,6 +118,34 @@ export class KNodeView implements IView { let interactiveNodes = undefined let interactiveConstraints = undefined + + + const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); + const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); + + let transformation: string; + + // we push a new effective zoom in all cases so we can pop later without checking whether we pushed + if (node.parent && performNodeScaling) { + + const siblings: Bounds[] = node.parent.children.filter((sibling) => sibling != node && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) + + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(ctx.effectiveZoom, minNodeScale, node.bounds, (node.parent as SShapeElement).bounds, siblings); + + if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ + // On initial load node.parent.bounds has all fields as 0 causing a division by 0 + transformation = "" + ctx.pushEffectiveZoom(ctx.effectiveZoom) + } else { + // Apply the new bounds and scaling as the element's transformation. + transformation = `translate(${newBounds.x - node.bounds.x },${newBounds.y - node.bounds.y})scale(${scalingFactor})` + ctx.pushEffectiveZoom(ctx.effectiveZoom * scalingFactor) + } + } else { + transformation = "" + ctx.pushEffectiveZoom(ctx.effectiveZoom) + } + if (isShadow) { // Render shadow of the node shadow = getRendering(node.data, node, new KStyles, ctx) @@ -159,31 +193,6 @@ export class KNodeView implements IView { } node.shadow = isShadow - - const providingRegion = ctx.depthMap?.getProvidingRegion(node , ctx.viewport, ctx.renderOptionsRegistry); - const minTitleScale = ctx.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor); - - const calcScale = function() {if (node.parent - && providingRegion - && providingRegion.originalTitleHeight - && providingRegion.regionTitleHeight - && providingRegion.regionTitleHeight * ctx.viewport.zoom < providingRegion.originalTitleHeight * minTitleScale - && ctx.renderOptionsRegistry.getValueOrDefault(PerformNodeScaling)) { - - const siblings: Bounds[] = node.parent.children.filter((sibling) => sibling != node && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - - const maxScale = minTitleScale / ctx.viewport.zoom / (providingRegion.regionTitleHeight / providingRegion.originalTitleHeight) - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(providingRegion.regionTitleHeight, maxScale, node.bounds, (node.parent as SShapeElement).bounds, ctx.viewport, siblings); - - if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ - // On initial load node.parent.bounds has all fields as 0 causing a division by 0 - return "" - } else { - // Apply the new bounds and scaling as the element's transformation. - return `translate(${newBounds.x - node.bounds.x },${newBounds.y - node.bounds.y})scale(${scalingFactor})` - } - }} - if (node.id === '$root') { // The root node should not be rendered, only its children should. const children = ctx.renderChildren(node) @@ -200,6 +209,7 @@ export class KNodeView implements IView { result.push(...children) result.push(...(ctx.titles.pop() ?? [])) ctx.positions.pop() + ctx.popEffectiveZoom() return {...result} } @@ -213,8 +223,8 @@ export class KNodeView implements IView { ctx.positions.pop() const titles = ctx.titles.pop() ?? [] const childRenderings = ctx.renderChildren(node) - const translateAndScale = calcScale() - const ret = + ctx.popEffectiveZoom() + const ret = {titles} {childRenderings} @@ -234,8 +244,8 @@ export class KNodeView implements IView { } result.push(...(ctx.titles.pop() ?? [])) ctx.positions.pop() - const translateAndScale = calcScale() - const ret = {...result} + ctx.popEffectiveZoom() + const ret ={...result} return ret } } From e2fdaa57a2c2e099959df9520723a796e75a6cc1 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 21 Jan 2022 14:09:59 +0100 Subject: [PATCH 24/95] simplify the code that works with the title scopes --- .../klighd-core/src/skgraph-model-renderer.ts | 15 +++++++++- packages/klighd-core/src/views-rendering.tsx | 2 +- packages/klighd-core/src/views.tsx | 29 +++++++++---------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/klighd-core/src/skgraph-model-renderer.ts b/packages/klighd-core/src/skgraph-model-renderer.ts index b9c73903..50f57415 100644 --- a/packages/klighd-core/src/skgraph-model-renderer.ts +++ b/packages/klighd-core/src/skgraph-model-renderer.ts @@ -40,7 +40,7 @@ export class SKGraphModelRenderer extends ModelRenderer { positions: string[] renderingDefs: Map renderOptionsRegistry: RenderOptionsRegistry - titles: VNode[][] + private titles: VNode[][] = [] viewport: Viewport private _effectiveZoom: number[] = [1] @@ -48,6 +48,19 @@ export class SKGraphModelRenderer extends ModelRenderer { return this._effectiveZoom[this._effectiveZoom.length - 1] } + enterTitleScope() : void { + this.titles.push([]) + } + + // leaves the current title scope and returns the titles collected in the left scope + exitTitleScope() : VNode[] { + return this.titles.pop() ?? [] + } + + pushTitle(title: VNode): void { + this.titles[this.titles.length - 1].push(title) + } + pushEffectiveZoom(zoom: number): void { this._effectiveZoom.push(zoom) } diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index b5a36510..9ba04f6c 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -1036,7 +1036,7 @@ export function renderKRendering(kRendering: KRendering, context.popEffectiveZoom() if (isOverlay) { // Don't render this now if we have an overlay, but remember it to be put on top by the node rendering. - context.titles[context.titles.length - 1].push(svgRendering) + context.pushTitle(svgRendering) return } else { return svgRendering diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 66b7ca98..82ab5bc9 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -56,7 +56,6 @@ export class SKGraphView implements IView { } else { ctx.pushEffectiveZoom(ctx.effectiveZoom) } - ctx.titles = [] ctx.positions = [] @@ -105,7 +104,7 @@ export class KNodeView implements IView { } } - ctx.titles.push([]) + ctx.enterTitleScope() ctx.positions.push("") // reset these properties, if the diagram is drawn a second time node.areChildAreaChildrenRendered = false @@ -207,7 +206,7 @@ export class KNodeView implements IView { result.push(interactiveNodes) } result.push(...children) - result.push(...(ctx.titles.pop() ?? [])) + result.push(...ctx.exitTitleScope()) ctx.positions.pop() ctx.popEffectiveZoom() return {...result} @@ -221,7 +220,7 @@ export class KNodeView implements IView { result.push(rendering) } else { ctx.positions.pop() - const titles = ctx.titles.pop() ?? [] + const titles = ctx.exitTitleScope() const childRenderings = ctx.renderChildren(node) ctx.popEffectiveZoom() const ret = @@ -242,7 +241,7 @@ export class KNodeView implements IView { } else if (!node.areNonChildAreaChildrenRendered) { result.push(...ctx.renderNonChildAreaChildren(node)) } - result.push(...(ctx.titles.pop() ?? [])) + result.push(...ctx.exitTitleScope()) ctx.positions.pop() ctx.popEffectiveZoom() const ret ={...result} @@ -270,7 +269,7 @@ export class KPortView implements IView { } } - ctx.titles.push([]) + ctx.enterTitleScope() ctx.positions.push("") port.areChildAreaChildrenRendered = false port.areNonChildAreaChildrenRendered = false @@ -278,7 +277,7 @@ export class KPortView implements IView { // If no rendering could be found, just render its children. if (rendering === undefined) { const element = - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} {ctx.renderChildren(port)} @@ -290,19 +289,19 @@ export class KPortView implements IView { if (!port.areChildAreaChildrenRendered) { element = {rendering} - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} {ctx.renderChildren(port)} } else if (!port.areNonChildAreaChildrenRendered) { element = {rendering} - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} {ctx.renderNonChildAreaChildren(port)} } else { element = {rendering} - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} } @@ -329,7 +328,7 @@ export class KLabelView implements IView { return undefined } } - ctx.titles.push([]) + ctx.enterTitleScope() ctx.positions.push("") label.areChildAreaChildrenRendered = false label.areNonChildAreaChildrenRendered = false @@ -344,7 +343,7 @@ export class KLabelView implements IView { // If no rendering could be found, just render its children. if (rendering === undefined) { const element = - {ctx.renderChildren(label).push(...ctx.titles.pop() ?? [])} + {ctx.renderChildren(label).push(...ctx.exitTitleScope())} ctx.positions.pop() @@ -355,19 +354,19 @@ export class KLabelView implements IView { if (!label.areChildAreaChildrenRendered) { element = {rendering} - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} {ctx.renderChildren(label)} } else if (!label.areNonChildAreaChildrenRendered) { element = {rendering} - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} {ctx.renderNonChildAreaChildren(label)} } else { element = {rendering} - {ctx.titles.pop() ?? []} + {ctx.exitTitleScope()} } From 4f8bc952b4301ef858927201e28cf6ea19f9a7bd Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 21 Jan 2022 14:18:27 +0100 Subject: [PATCH 25/95] fix missing effectiveZoom --- packages/klighd-core/src/views-rendering.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 9ba04f6c..9e15ecf7 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -903,7 +903,7 @@ export function renderKRendering(kRendering: KRendering, const maxScale = titleScalingFactorOption // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) - const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.viewport.zoom <= titleScalingFactorOption * kRendering.calculatedBounds.height + const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.effectiveZoom <= titleScalingFactorOption * kRendering.calculatedBounds.height const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails const multipleChildren = parent.children.length > 1 From e97a983ed5a84c0307d130bbf99ca9e7e1057901 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 21 Jan 2022 14:19:49 +0100 Subject: [PATCH 26/95] we don't actually need the calculated bounds here --- packages/klighd-core/src/views-rendering.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 9e15ecf7..58a3a785 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -903,7 +903,7 @@ export function renderKRendering(kRendering: KRendering, const maxScale = titleScalingFactorOption // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) - const tooSmall = kRendering.calculatedBounds && kRendering.calculatedBounds.height * context.effectiveZoom <= titleScalingFactorOption * kRendering.calculatedBounds.height + const tooSmall = context.effectiveZoom <= titleScalingFactorOption const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails const multipleChildren = parent.children.length > 1 From 91bb6bd20fbcb83baff3b6a97e289c9358adea83 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 21 Jan 2022 14:34:55 +0100 Subject: [PATCH 27/95] change scaled title background border color to grey --- packages/klighd-core/src/views-rendering.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 58a3a785..58b70965 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -976,7 +976,7 @@ export function renderKRendering(kRendering: KRendering, // Draw white background for overlaying titles if ((!providingRegion || providingRegion.detail === DetailLevel.FullDetails) && tooSmall && !isEmptyText) { - overlayRectangle = + overlayRectangle = } } else { context.pushEffectiveZoom(context.effectiveZoom) From 29712cd763c8a8f32a4f215dd581686f993f9eb3 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 24 Jan 2022 11:23:47 +0100 Subject: [PATCH 28/95] add margin --- .../src/options/render-options-registry.ts | 22 +++++++++++++++++ packages/klighd-core/src/scaling-util.ts | 24 +++++++++---------- packages/klighd-core/src/views-rendering.tsx | 5 ++-- packages/klighd-core/src/views.tsx | 5 ++-- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 00caddea..d61b6019 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -257,6 +257,27 @@ export class NodeScalingFactor implements RangeOption { currentValue = 1 } +/** + * The factor by which titles of collapsed regions get scaled by + * in relation to their size at native resolution. + */ +export class NodeMargin implements RangeOption { + static readonly ID: string = 'node-margin' + static readonly NAME: string = 'Node Margin' + static readonly DEFAULT: number = 1 + readonly id: string = NodeMargin.ID + readonly name: string = NodeMargin.NAME + readonly type: TransformationOptionType = TransformationOptionType.RANGE + readonly values: any[] = [] + readonly range = { + first: 0, + second: 20 + } + readonly stepSize = 0.5 + readonly initialValue: number = NodeMargin.DEFAULT + currentValue = 10 +} + /** * The style shadows should be drawn in, either the paper mode shadows (nice, but slow in * performance) or in default KIELER-style (fast, not as nice looking). @@ -323,6 +344,7 @@ export class RenderOptionsRegistry extends Registry { this.register(ScaleNodes); this.register(NodeScalingFactor); + this.register(NodeMargin); this.register(UseMinimumLineWidth); diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 3389d21b..43f0e3d6 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,16 +1,16 @@ import { Bounds, Dimension } from 'sprotty-protocol' -export function maxParentScale(node: Bounds, parent: Bounds): number { +export function maxParentScale(node: Bounds, parent: Bounds, margin: number): number { // the maximum scale that keeps the node in bounds height wise - const maxHeightScale = parent.height / node.height + const maxHeightScale = (parent.height + 2 * margin) / node.height // the maximum scale that keeps the node in bounds width wise - const maxWidthScale = parent.width / node.width + const maxWidthScale = (parent.width + 2 * margin) / node.width return Math.min(maxHeightScale, maxWidthScale) } -function inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number): number { +function inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number, margin: number): number { // we want to find positive scale so that // result_a = scaleDimension(offset_a, length_a, available, scale) // result_b = scaleDimension(offset_b, length_b, available, scale) @@ -22,18 +22,18 @@ function inverseScaleDimension(offset_a: number, length_a: number, offset_b: num const numerator = offset_a + fa - offset_b - fb - const result_1 = numerator / (fa - fb + length_b) - const result_2 = -numerator / (fb - fa + length_a) + const result_1 = ( numerator - margin) / (fa - fb + length_b) + const result_2 = (-numerator - margin) / (fb - fa + length_a) // the scale should be at least one and at most one of the results should be positive return Math.max(result_1, result_2, 1) } -export function maxSiblingScale(node: Bounds, parent:Bounds, sibling: Bounds) : number { +export function maxSiblingScale(node: Bounds, parent:Bounds, sibling: Bounds, margin: number) : number { // calculate the scale for each dimension at which we reach our sibling - const result_1 = inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width) - const result_2 = inverseScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height) + const result_1 = inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) + const result_2 = inverseScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height, margin) // take the max as that which ever is further is relevant for bounding us, but should be at least 1 return Math.max(result_1, result_2, 1) @@ -62,17 +62,17 @@ export function scaleDimension(offset: number, length: number, available: number return {offset: newOffset, length: newLength} } -export function upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Bounds, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { +export function upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Bounds, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { // we want that the effectiveScale * desiredScale = maxScale // so that the we effectively up scale to maxScale const desiredScale = maxScale / effectiveScale; // the maximum scale at which the child still fits into the parent - const parentScaling = maxParentScale(childBounds, parentBounds) + const parentScaling = maxParentScale(childBounds, parentBounds, margin) // some maximum scale at which the child does not interfere with its siblings - const siblingScaling = siblings.map((siblingBounds) => maxSiblingScale(childBounds, parentBounds, siblingBounds)) + const siblingScaling = siblings.map((siblingBounds) => maxSiblingScale(childBounds, parentBounds, siblingBounds, margin)) // the most restrictive scale between our desired scale and the maximum imposed by the parent and siblings const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScaling) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 58b70965..22379ec1 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -20,7 +20,7 @@ import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unu import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -893,6 +893,7 @@ export function renderKRendering(kRendering: KRendering, let isOverlay = false const applyTitleScaling = context.renderOptionsRegistry.getValueOrDefault(UseSmartZoom) && context.renderOptionsRegistry.getValueOrDefault(ScaleTitles) + const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin); // If this rendering is the main title rendering of the element, either render it usually if // zoomed in far enough or remember it to be rendered later scaled up and overlayed on top of the parent rendering. @@ -926,7 +927,7 @@ export function renderKRendering(kRendering: KRendering, const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds const originalBounds = boundingBox - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(context.effectiveZoom, maxScale, originalBounds, parentBounds); + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(context.effectiveZoom, maxScale, originalBounds, parentBounds, margin); context.pushEffectiveZoom(context.effectiveZoom * scalingFactor) // Apply the new bounds and scaling as the element's transformation. diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 82ab5bc9..67c65580 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -25,7 +25,7 @@ import { Bounds } from 'sprotty-protocol' import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; -import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes, NodeScalingFactor } from './options/render-options-registry'; +import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes, NodeScalingFactor, NodeMargin } from './options/render-options-registry'; import { upscaleBounds } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { NODE_TYPE, SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; @@ -121,6 +121,7 @@ export class KNodeView implements IView { const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); + const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); let transformation: string; @@ -129,7 +130,7 @@ export class KNodeView implements IView { const siblings: Bounds[] = node.parent.children.filter((sibling) => sibling != node && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(ctx.effectiveZoom, minNodeScale, node.bounds, (node.parent as SShapeElement).bounds, siblings); + const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(ctx.effectiveZoom, minNodeScale, node.bounds, (node.parent as SShapeElement).bounds, margin, siblings); if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From a722519103589d3a918d932d241ffc5ca4126e31 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 24 Jan 2022 14:32:28 +0100 Subject: [PATCH 29/95] add some comments also we only need to know the dimensions of the parent --- packages/klighd-core/src/scaling-util.ts | 29 +++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 43f0e3d6..a3d6fb3a 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,6 +1,13 @@ import { Bounds, Dimension } from 'sprotty-protocol' -export function maxParentScale(node: Bounds, parent: Bounds, margin: number): number { + +/** + * @param node the bounds of the node to scale + * @param parent the dimensions of the parent of the node to scale + * @param margin the margin node shall retain to parent + * @returns the maximum scale at which node retains the margin to parent + */ +export function maxParentScale(node: Bounds, parent: Dimension, margin: number): number { // the maximum scale that keeps the node in bounds height wise const maxHeightScale = (parent.height + 2 * margin) / node.height // the maximum scale that keeps the node in bounds width wise @@ -29,7 +36,15 @@ function inverseScaleDimension(offset_a: number, length_a: number, offset_b: num return Math.max(result_1, result_2, 1) } -export function maxSiblingScale(node: Bounds, parent:Bounds, sibling: Bounds, margin: number) : number { +/** + * Calculate the maximum scale at which node and sibling retain the given margin between them + * @param node the bounds of node to scale + * @param parent the dimensions of the parent of the node to scale + * @param sibling a sibling og the nod to scale + * @param margin the margin node and sibling shall retain when both scaled by the result + * @returns the maximum scale at which node and sibling retain margin between them + */ +export function maxSiblingScale(node: Bounds, parent:Dimension, sibling: Bounds, margin: number) : number { // calculate the scale for each dimension at which we reach our sibling const result_1 = inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) @@ -39,6 +54,14 @@ export function maxSiblingScale(node: Bounds, parent:Bounds, sibling: Bounds, ma return Math.max(result_1, result_2, 1) } + +/** + * Scale bounds in the specified dimensions by the specified scale + * @param originalBounds the bounds to scale + * @param availableSpace the space available to scale the bounds + * @param scale the scale by which to scale + * @returns the scaled bounds + */ export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { const originalWidth = originalBounds.width const originalHeight = originalBounds.height @@ -62,7 +85,7 @@ export function scaleDimension(offset: number, length: number, available: number return {offset: newOffset, length: newLength} } -export function upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Bounds, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { +export function upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { // we want that the effectiveScale * desiredScale = maxScale // so that the we effectively up scale to maxScale From 0ea701ba93d131cb7181ba4b09d980500141e1a2 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 24 Jan 2022 16:09:55 +0100 Subject: [PATCH 30/95] fix incorrect margin calculation --- packages/klighd-core/src/scaling-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index a3d6fb3a..03178b90 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -9,9 +9,9 @@ import { Bounds, Dimension } from 'sprotty-protocol' */ export function maxParentScale(node: Bounds, parent: Dimension, margin: number): number { // the maximum scale that keeps the node in bounds height wise - const maxHeightScale = (parent.height + 2 * margin) / node.height + const maxHeightScale = (parent.height - 2 * margin) / node.height // the maximum scale that keeps the node in bounds width wise - const maxWidthScale = (parent.width + 2 * margin) / node.width + const maxWidthScale = (parent.width - 2 * margin) / node.width return Math.min(maxHeightScale, maxWidthScale) } From 643cc80856c02dac87d17bfcc1c02027590ba13f Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 24 Jan 2022 18:30:23 +0100 Subject: [PATCH 31/95] initial edge adjustment --- packages/klighd-core/src/scaling-util.ts | 29 ++++++++++++- packages/klighd-core/src/skgraph-models.ts | 1 + packages/klighd-core/src/views-rendering.tsx | 43 ++++++++++++++++++-- packages/klighd-core/src/views.tsx | 9 ++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 03178b90..fad4b732 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,4 +1,4 @@ -import { Bounds, Dimension } from 'sprotty-protocol' +import { Bounds, Dimension, Point} from 'sprotty-protocol' /** @@ -77,6 +77,28 @@ export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Di return {x: newX, y : newY, width: newWidth, height: newHeight} } +export function calculateScaledPoint(originalBounds: Bounds, newBounds: Bounds, originalPoint: Point) : Point { + + let newX + let newY + + if (originalBounds.width == 0 || newBounds.width == 0) { + newX = originalPoint.x - originalBounds.x + newBounds.x + } else { + const alongX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width + newX = newBounds.x + alongX * newBounds.width + } + + if (originalBounds.height == 0 || newBounds.height == 0) { + newY = originalPoint.y - originalBounds.y + newBounds.y + }else { + const alongY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height + newY = newBounds.y + alongY * newBounds.height + } + + return {x: newX, y: newY} +} + export function scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ const newLength = length * scale; const prefix = offset @@ -107,3 +129,8 @@ export function upscaleBounds(effectiveScale: number, maxScale: number, childBou return {bounds:newBounds, scale: scalingFactor} } + + +export function equalBounds(a: Bounds, b: Bounds) : boolean { + return a.x == b.x && a.y == b.y && a.height == b.height && a.width == b.width +} diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 9866836d..b0e8ffc1 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -39,6 +39,7 @@ export const LABEL_TYPE = 'label' */ export class SKNode extends KNode implements SKGraphElement { tooltip?: string + node_scaled_bounds?: Bounds hasFeature(feature: symbol): boolean { return feature === selectFeature || (feature === moveFeature && (this.parent as SKNode).properties && (this.parent as SKNode).properties.interactiveLayout) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 22379ec1..b3e8ed74 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -32,7 +32,7 @@ import { ColorStyles, DEFAULT_CLICKABLE_FILL, DEFAULT_FILL, getKStyles, getSvgColorStyle, getSvgColorStyles, getSvgLineStyles, getSvgShadowStyles, getSvgTextStyles, isInvisible, KStyles, LineStyles } from './views-styles'; -import { upscaleBounds } from './scaling-util'; +import { calculateScaledPoint, equalBounds, upscaleBounds } from './scaling-util'; // ----------------------------- Functions for rendering different KRendering as VNodes in svg -------------------------------------------- @@ -266,13 +266,41 @@ export function renderLine(rendering: KPolyline, const shadowStyles = paperShadows ? getSvgShadowStyles(styles, context) : undefined const lineStyles = getSvgLineStyles(styles, parent, context) - const points = getPoints(parent, rendering, boundsAndTransformation) + let points = getPoints(parent, rendering, boundsAndTransformation) if (points.length === 0) { return {renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)} } + let skip_children = false + + if (rendering.type !== K_POLYGON && parent instanceof SKEdge) { + + const s = parent.source + const t = parent.target + + if (parent.routingPoints.length > 0 + && s instanceof SKNode + && t instanceof SKNode + && s.node_scaled_bounds + && t.node_scaled_bounds + ) { + + const start = points[0] + const end = points[points.length-1] + + const scaled_start = calculateScaledPoint(s.bounds, s.node_scaled_bounds, start) + const scaled_end = calculateScaledPoint(t.bounds, t.node_scaled_bounds, end) + + const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} + const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} + + points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)) + skip_children = !equalBounds(s.bounds, s.node_scaled_bounds) || !(t.bounds , t.node_scaled_bounds) + } + } + // now define the line's path. let path = '' switch (rendering.type) { @@ -359,10 +387,17 @@ export function renderLine(rendering: KPolyline, // Create the svg element for this rendering. // Only apply the fast shadow to KPolygons, other shadows are not allowed there. - const element = + let element + if (skip_children) { + element = + {...renderSVGLine(lineStyles, colorStyles, shadowStyles, path, rendering.type == K_POLYGON ? styles.kShadow : undefined)} + + } else { + element = {...renderSVGLine(lineStyles, colorStyles, shadowStyles, path, rendering.type == K_POLYGON ? styles.kShadow : undefined)} {renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)} - + + } return element } diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 67c65580..06a588e0 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -125,6 +125,8 @@ export class KNodeView implements IView { let transformation: string; + node.node_scaled_bounds = node.bounds + // we push a new effective zoom in all cases so we can pop later without checking whether we pushed if (node.parent && performNodeScaling) { @@ -132,6 +134,8 @@ export class KNodeView implements IView { const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(ctx.effectiveZoom, minNodeScale, node.bounds, (node.parent as SShapeElement).bounds, margin, siblings); + node.node_scaled_bounds = newBounds; + if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 transformation = "" @@ -404,11 +408,16 @@ export class KEdgeView implements IView { if (s === undefined || t === undefined) { return } + + + // edge should be greyed out if the source or target is moved if (s !== undefined && t !== undefined && s instanceof SKNode && t instanceof SKNode) { edge.moved = (s.selected || t.selected) && ctx.mListener.hasDragged + } + let rendering = undefined if (!ctx.mListener.hasDragged || isChildSelected(edge.parent as SKNode)) { // edge should only be visible if it is in the same hierarchical level as From ddb62a6ae062d488b18b152728b1a2f133371549 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 25 Jan 2022 13:28:08 +0100 Subject: [PATCH 32/95] fix depthmap size and in bounds calculations in the presence of node scaling --- packages/klighd-core/src/depth-map.ts | 141 ++++++++----------- packages/klighd-core/src/scaling-util.ts | 15 ++ packages/klighd-core/src/skgraph-models.ts | 49 ++++++- packages/klighd-core/src/views-rendering.tsx | 15 +- packages/klighd-core/src/views.tsx | 27 ++-- packages/klighd-core/tsconfig.json | 2 +- 6 files changed, 140 insertions(+), 109 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 74e5ab1a..4757dc7c 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -16,10 +16,12 @@ */ import { KGraphElement } from "@kieler/klighd-interactive/lib/constraint-classes"; -import { SChildElement, SModelRoot, SShapeElement } from "sprotty"; -import { Point, Viewport } from "sprotty-protocol"; -import { RenderOptionsRegistry, FullDetailRelativeThreshold, FullDetailScaleThreshold } from "./options/render-options-registry"; -import { isContainerRendering, isRendering, KRendering } from "./skgraph-models"; +import { SChildElement, SModelRoot } from "sprotty"; +import { Viewport } from "sprotty-protocol"; +import { FullDetailRelativeThreshold, FullDetailScaleThreshold } from "./options/render-options-registry"; +import { getAbsoluteRenderedBounds } from "./scaling-util"; +import { SKGraphModelRenderer } from "./skgraph-model-renderer"; +import { isContainerRendering, isRendering, KRendering, SKNode } from "./skgraph-models"; /** * The possible detail level of a KNode as determined by the DepthMap @@ -36,7 +38,6 @@ export enum DetailLevel { type DetailWithChildren = DetailLevel.FullDetails type KChildElement = SChildElement & KGraphElement; -type KShapeElement = SShapeElement & KGraphElement; /** * Type predicate to determine whether a DetailLevel is a DetailWithChildren level @@ -161,7 +162,7 @@ export class DepthMap { * * @param element The KGraphElement to initialize for DepthMap usage */ - public initKGraphElement(element: KChildElement, viewport: Viewport, renderingOptions: RenderOptionsRegistry): RegionIndexEntry { + public initKGraphElement(element: KChildElement, ctx: SKGraphModelRenderer): RegionIndexEntry { let entry = this.regionIndexMap.get(element.id) if (entry) { @@ -169,56 +170,33 @@ export class DepthMap { return entry } - const relativeThreshold = renderingOptions.getValueOrDefault(FullDetailRelativeThreshold) - - const scaleThreshold = renderingOptions.getValueOrDefault(FullDetailScaleThreshold) - - if (element.parent === element.root && element instanceof SShapeElement) { + if (element.parent === element.root && element instanceof SKNode) { const providedRegion = new Region(element) - providedRegion.absolutePosition = element.bounds entry = { providingRegion: providedRegion, containingRegion: undefined } - providedRegion.detail = providedRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) + element.forceNodeScaleBounds(ctx) + providedRegion.detail = providedRegion.computeDetailLevel(ctx) this.rootRegions.push(providedRegion) } else { - const parentEntry = this.initKGraphElement(element.parent as KChildElement, viewport, renderingOptions); + const parentEntry = this.initKGraphElement(element.parent as KChildElement, ctx); entry = { containingRegion: parentEntry.providingRegion ?? parentEntry.containingRegion, providingRegion: undefined } const kRendering = this.findRendering(element) - if (element instanceof SShapeElement && kRendering && isContainerRendering(kRendering) && kRendering.children.length !== 0) { + if (element instanceof SKNode && kRendering && isContainerRendering(kRendering) && kRendering.children.length !== 0) { + entry = { containingRegion: entry.containingRegion, providingRegion: new Region(element) } entry.providingRegion.parent = entry.containingRegion entry.containingRegion.children.push(entry.providingRegion); - let current = element.parent as SShapeElement; - let offsetX = 0; - let offsetY = 0; - - let currentEntry = this.regionIndexMap.get(current.id) - - while (current && currentEntry && !currentEntry.providingRegion) { - offsetX += current.bounds.x - offsetY += current.bounds.y - current = current.parent as SShapeElement - currentEntry = this.regionIndexMap.get(current.id) - } - - offsetX += currentEntry?.providingRegion?.absolutePosition?.x ?? 0 - offsetY += currentEntry?.providingRegion?.absolutePosition?.y ?? 0 - - entry.providingRegion.absolutePosition = { - x: offsetX + element.bounds.x, - y: offsetY + element.bounds.y - } - - entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) + element.forceNodeScaleBounds(ctx) + entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(ctx) } } @@ -243,14 +221,14 @@ export class DepthMap { return undefined } - public getContainingRegion(element: KChildElement, viewport: Viewport, renderOptions: RenderOptionsRegistry): Region | undefined { + public getContainingRegion(element: KChildElement, ctx: SKGraphModelRenderer): Region | undefined { // initKGraphELement already checks if it is already initialized and if it is returns the existing value - return this.initKGraphElement(element, viewport, renderOptions).containingRegion + return this.initKGraphElement(element, ctx).containingRegion } - public getProvidingRegion(node: KShapeElement, viewport: Viewport, renderOptions: RenderOptionsRegistry): Region | undefined { + public getProvidingRegion(node: SKNode, ctx: SKGraphModelRenderer): Region | undefined { // initKGraphElement already checks if it is already initialized and if it is returns the existing value - return this.initKGraphElement(node, viewport, renderOptions).providingRegion + return this.initKGraphElement(node, ctx).providingRegion } /** @@ -258,32 +236,30 @@ export class DepthMap { * * @param viewport The current viewport. */ - updateDetailLevels(viewport: Viewport, renderingOptions: RenderOptionsRegistry): void { - - const relativeThreshold = renderingOptions.getValueOrDefault(FullDetailRelativeThreshold) + updateDetailLevels(ctx: SKGraphModelRenderer): void { - const scaleThreshold = renderingOptions.getValueOrDefault(FullDetailScaleThreshold) + const relativeThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailRelativeThreshold) - if (this.viewport?.scroll === viewport.scroll - && this.viewport?.zoom === viewport.zoom + if (this.viewport?.scroll === ctx.viewport.scroll + && this.viewport?.zoom === ctx.viewport.zoom && this.lastThreshold === relativeThreshold) { // the viewport did not change, no need to update return } - this.viewport = { zoom: viewport.zoom, scroll: viewport.scroll } + this.viewport = { zoom: ctx.viewport.zoom, scroll: ctx.viewport.scroll } this.lastThreshold = relativeThreshold; // Initialize detail level on first run. if (this.criticalRegions.size == 0) { for (const region of this.rootRegions) { - const vis = region.computeDetailLevel(viewport, relativeThreshold, scaleThreshold) + const vis = region.computeDetailLevel(ctx) if (vis === DetailLevel.FullDetails) { - this.updateRegionDetailLevel(region, vis, viewport, relativeThreshold, scaleThreshold) + this.updateRegionDetailLevel(region, vis, ctx) } } } else { - this.checkCriticalRegions(viewport, relativeThreshold, scaleThreshold) + this.checkCriticalRegions(ctx) } } @@ -294,17 +270,17 @@ export class DepthMap { * @param viewport The current viewport * @param relativeThreshold The detail level threshold */ - updateRegionDetailLevel(region: Region, vis: DetailWithChildren, viewport: Viewport, relativeThreshold: number, scaleThreshold: number): void { + updateRegionDetailLevel(region: Region, vis: DetailWithChildren, ctx: SKGraphModelRenderer): void { region.setDetailLevel(vis) let isCritical = false; region.children.forEach(childRegion => { - const childVis = childRegion.computeDetailLevel(viewport, relativeThreshold, scaleThreshold); + const childVis = childRegion.computeDetailLevel(ctx); if (childVis < vis) { isCritical = true } if (isDetailWithChildren(childVis)) { - this.updateRegionDetailLevel(childRegion, childVis, viewport, relativeThreshold, scaleThreshold) + this.updateRegionDetailLevel(childRegion, childVis, ctx) } else { this.recursiveSetOOB(childRegion, childVis) } @@ -334,7 +310,7 @@ export class DepthMap { * @param viewport The current viewport * @param relativeThreshold The full detail threshold */ - checkCriticalRegions(viewport: Viewport, relativeThreshold: number, scaleThreshold: number): void { + checkCriticalRegions(ctx: SKGraphModelRenderer): void { // All regions that are at a detail level boundary (child has lower detail level and parent is at a DetailWithChildren level). let toBeProcessed: Set = new Set(this.criticalRegions) @@ -344,7 +320,7 @@ export class DepthMap { while (toBeProcessed.size !== 0) { toBeProcessed.forEach(region => { - const vis = region.computeDetailLevel(viewport, relativeThreshold, scaleThreshold); + const vis = region.computeDetailLevel(ctx); region.setDetailLevel(vis) if (region.parent && vis !== region.parent.detail) { @@ -355,7 +331,7 @@ export class DepthMap { } if (isDetailWithChildren(vis)) { - this.updateRegionDetailLevel(region, vis, viewport, relativeThreshold, scaleThreshold) + this.updateRegionDetailLevel(region, vis, ctx) } else { this.recursiveSetOOB(region, vis) } @@ -378,9 +354,7 @@ export class DepthMap { */ export class Region { /** The rectangle of the child area in which the region lies. */ - boundingRectangle: SShapeElement - /** The absolute position of the boundingRectangle based on the layout information of the SModel. */ - absolutePosition: Point + boundingRectangle: SKNode /** the regions current detail level that is used by all children */ detail: DetailLevel /** The immediate parent region of this region. */ @@ -394,7 +368,7 @@ export class Region { /** Indentation of region title. */ regionTitleIndentation?: number /** Constructor initializes element array for region. */ - constructor(boundingRectangle: SShapeElement) { + constructor(boundingRectangle: SKNode) { this.boundingRectangle = boundingRectangle this.children = [] this.detail = DetailLevel.FullDetails @@ -407,18 +381,16 @@ export class Region { * @param viewport The current viewport. * @returns Boolean value indicating the visibility of the region in the current viewport. */ - isInBounds(viewport: Viewport): boolean { - if (this.absolutePosition) { - const canvasBounds = this.boundingRectangle.root.canvasBounds - - return this.absolutePosition.x + this.boundingRectangle.bounds.width - viewport.scroll.x >= 0 - && this.absolutePosition.x - viewport.scroll.x <= (canvasBounds.width / viewport.zoom) - && this.absolutePosition.y + this.boundingRectangle.bounds.height - viewport.scroll.y >= 0 - && this.absolutePosition.y - viewport.scroll.y <= (canvasBounds.height / viewport.zoom) - } else { - // Better to assume it is visible, if information are not sufficient - return true - } + isInBounds(ctx: SKGraphModelRenderer): boolean { + const bounds = getAbsoluteRenderedBounds(this.boundingRectangle, ctx) + + const canvasBounds = this.boundingRectangle.root.canvasBounds + + return bounds.x + bounds.width - ctx.viewport.scroll.x >= 0 + && bounds.x - ctx.viewport.scroll.x <= (canvasBounds.width / ctx.viewport.zoom) + && bounds.y + bounds.height - ctx.viewport.scroll.y >= 0 + && bounds.y - ctx.viewport.scroll.y <= (canvasBounds.height / ctx.viewport.zoom) + } /** @@ -428,9 +400,13 @@ export class Region { * @param viewport The current viewport * @returns the relative size of the KNodes shortest dimension */ - sizeInViewport(viewport: Viewport): number { - const horizontal = this.boundingRectangle.bounds.width / (this.boundingRectangle.root.canvasBounds.width / viewport.zoom) - const vertical = this.boundingRectangle.bounds.height / (this.boundingRectangle.root.canvasBounds.height / viewport.zoom) + sizeInViewport(ctx: SKGraphModelRenderer,): number { + const bounds = getAbsoluteRenderedBounds(this.boundingRectangle, ctx) + + const canvasBounds = this.boundingRectangle.root.canvasBounds + + const horizontal = bounds.width / (canvasBounds.width / ctx.viewport.zoom) + const vertical = bounds.height / (canvasBounds.height / ctx.viewport.zoom) return horizontal < vertical ? horizontal : vertical } @@ -443,15 +419,20 @@ export class Region { * @param relativeThreshold The full detail threshold * @returns The appropriate detail level */ - computeDetailLevel(viewport: Viewport, relativeThreshold: number, scaleThreshold: number): DetailLevel { - if (!this.isInBounds(viewport)) { + computeDetailLevel(ctx: SKGraphModelRenderer): DetailLevel { + + const relativeThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailRelativeThreshold) + const scaleThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailScaleThreshold) + + if (!this.isInBounds(ctx)) { return DetailLevel.OutOfBounds } else if (!this.parent) { // Regions without parents should always be full detail if they are visible return DetailLevel.FullDetails } else { - const viewportSize = this.sizeInViewport(viewport) - const scale = viewport.zoom + const viewportSize = this.sizeInViewport(ctx) + + const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx).effective_child_zoom // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. if (viewportSize >= relativeThreshold || scale > scaleThreshold) { return DetailLevel.FullDetails diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index fad4b732..7bad12a0 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,4 +1,6 @@ import { Bounds, Dimension, Point} from 'sprotty-protocol' +import { SKNode } from './skgraph-models' +import { SKGraphModelRenderer } from './skgraph-model-renderer' /** @@ -134,3 +136,16 @@ export function upscaleBounds(effectiveScale: number, maxScale: number, childBou export function equalBounds(a: Bounds, b: Bounds) : boolean { return a.x == b.x && a.y == b.y && a.height == b.height && a.width == b.width } + +export function getAbsoluteRenderedBounds(element: SKNode, ctx: SKGraphModelRenderer) : Bounds { + let current: SKNode = element; + let {bounds: bounds} = element.forceNodeScaleBounds(ctx) + + while (current.parent instanceof SKNode) { + const parent = current.parent; + bounds = parent.localToParentRendered(bounds, ctx); + current = parent; + } + + return bounds; +} diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index b0e8ffc1..8bd71a82 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -16,8 +16,11 @@ */ import { KEdge, KGraphData, KGraphElement, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; -import { boundsFeature, moveFeature, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement } from 'sprotty'; -import { Point, Bounds } from 'sprotty-protocol' +import { boundsFeature, moveFeature, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement, SShapeElement } from 'sprotty'; +import { Point, isBounds, Bounds } from 'sprotty-protocol' +import { NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; +import { upscaleBounds } from './scaling-util'; +import { SKGraphModelRenderer } from './skgraph-model-renderer'; /** * This is the superclass of all elements of a graph such as nodes, edges, ports, @@ -39,12 +42,52 @@ export const LABEL_TYPE = 'label' */ export class SKNode extends KNode implements SKGraphElement { tooltip?: string - node_scaled_bounds?: Bounds + + private _node_scaled_bounds?: {bounds: Bounds, scale: number, effective_child_zoom: number} + hasFeature(feature: symbol): boolean { return feature === selectFeature || (feature === moveFeature && (this.parent as SKNode).properties && (this.parent as SKNode).properties.interactiveLayout) || feature === popupFeature } + + forceNodeScaleBounds(ctx: SKGraphModelRenderer): {bounds: Bounds, scale: number, effective_child_zoom: number}{ + const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); + + if (this.parent && this.parent instanceof SKNode && performNodeScaling) { + const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); + const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); + + const parent_scaled = this.parent.forceNodeScaleBounds(ctx) + + const effective_zoom = parent_scaled.effective_child_zoom + const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) + + const upscale = upscaleBounds(ctx.effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); + this._node_scaled_bounds = {...upscale, effective_child_zoom: effective_zoom * upscale.scale} + } else { + this._node_scaled_bounds = {bounds : this.bounds, scale: 1, effective_child_zoom: ctx.viewport.zoom} + } + + return this._node_scaled_bounds + } + + localToParentRendered(point: Point | Bounds, ctx: SKGraphModelRenderer): Bounds { + const scaled_bounds = this.forceNodeScaleBounds(ctx) + + const result = {x: point.x * scaled_bounds.scale, y: point.y * scaled_bounds.scale, width: -1, height:-1} + + if (isBounds(point)) { + result.width = point.width * scaled_bounds.scale + result.height = point.height * scaled_bounds.scale + } + + result.x += scaled_bounds.bounds.x + result.y += scaled_bounds.bounds.y + + return result + } + } /** diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index b3e8ed74..be466fbd 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -203,7 +203,7 @@ export function renderRectangularShape( } if (element && context.depthMap) { - const region = context.depthMap.getProvidingRegion(parent as KNode, context.viewport, context.renderOptionsRegistry) + const region = context.depthMap.getProvidingRegion(parent as SKNode, context) if (region && region.detail !== DetailLevel.FullDetails && parent.children.length >= 1) { const offsetY = region.regionTitleHeight ?? 0 const offsetX = region.regionTitleIndentation ?? 0 @@ -283,21 +283,22 @@ export function renderLine(rendering: KPolyline, if (parent.routingPoints.length > 0 && s instanceof SKNode && t instanceof SKNode - && s.node_scaled_bounds - && t.node_scaled_bounds ) { + const s_scaled = s.forceNodeScaleBounds(context) + const t_scaled = t.forceNodeScaleBounds(context) + const start = points[0] const end = points[points.length-1] - const scaled_start = calculateScaledPoint(s.bounds, s.node_scaled_bounds, start) - const scaled_end = calculateScaledPoint(t.bounds, t.node_scaled_bounds, end) + const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) + const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)) - skip_children = !equalBounds(s.bounds, s.node_scaled_bounds) || !(t.bounds , t.node_scaled_bounds) + skip_children = !equalBounds(s.bounds, s_scaled.bounds) || !(t.bounds , t_scaled.bounds) } } @@ -917,7 +918,7 @@ export function renderKRendering(kRendering: KRendering, return renderError(kRendering) } - const providingRegion = context.depthMap?.getProvidingRegion(parent as KNode, context.viewport, context.renderOptionsRegistry) + const providingRegion = context.depthMap?.getProvidingRegion(parent as SKNode, context) // Check if this is a title rendering. If we have a title, create that rendering, remember where it should be and how much space it has. // If we are zoomed in far enough, return that rendering, otherwise put it into the list to be rendered on top by the element rendering. diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 06a588e0..841b7b7f 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -20,15 +20,13 @@ import { renderConstraints, renderInteractiveLayout } from '@kieler/klighd-inter import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/klighd-interactive-mouselistener'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, SShapeElement, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { Bounds } from 'sprotty-protocol' +import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; -import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes, NodeScalingFactor, NodeMargin } from './options/render-options-registry'; -import { upscaleBounds } from './scaling-util'; +import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; -import { NODE_TYPE, SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; +import { SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; import { getJunctionPointRenderings, getRendering } from './views-rendering'; import { KStyles } from './views-styles'; @@ -66,7 +64,7 @@ export class SKGraphView implements IView { if (useSmartZoom && ctx.targetKind !== 'hidden') { ctx.depthMap = DepthMap.getDM() if (ctx.viewport && ctx.depthMap) { - ctx.depthMap.updateDetailLevels(ctx.viewport, ctx.renderOptionsRegistry) + ctx.depthMap.updateDetailLevels(ctx) } } else { ctx.depthMap = undefined @@ -95,7 +93,7 @@ export class KNodeView implements IView { const ctx = context as SKGraphModelRenderer if (ctx.depthMap) { - const containingRegion = ctx.depthMap.getContainingRegion(node, ctx.viewport, ctx.renderOptionsRegistry) + const containingRegion = ctx.depthMap.getContainingRegion(node, ctx) if (ctx.depthMap && containingRegion && !isDetailWithChildren(containingRegion.detail)) { // Make sure this node and its children are not drawn as long as it is not on full details. node.areChildAreaChildrenRendered = true @@ -119,22 +117,15 @@ export class KNodeView implements IView { - const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); - const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); let transformation: string; - node.node_scaled_bounds = node.bounds // we push a new effective zoom in all cases so we can pop later without checking whether we pushed if (node.parent && performNodeScaling) { - const siblings: Bounds[] = node.parent.children.filter((sibling) => sibling != node && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(ctx.effectiveZoom, minNodeScale, node.bounds, (node.parent as SShapeElement).bounds, margin, siblings); - - node.node_scaled_bounds = newBounds; + const {bounds: newBounds, scale: scalingFactor} = node.forceNodeScaleBounds(ctx) if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 @@ -266,7 +257,7 @@ export class KPortView implements IView { const ctx = context as SKGraphModelRenderer if (ctx.depthMap) { - const containingRegion = ctx.depthMap.getContainingRegion(port, ctx.viewport, ctx.renderOptionsRegistry) + const containingRegion = ctx.depthMap.getContainingRegion(port, ctx) if (ctx.depthMap && containingRegion && containingRegion.detail !== DetailLevel.FullDetails) { port.areChildAreaChildrenRendered = true port.areNonChildAreaChildrenRendered = true @@ -326,7 +317,7 @@ export class KLabelView implements IView { const ctx = context as SKGraphModelRenderer if (ctx.depthMap) { - const containingRegion = ctx.depthMap.getContainingRegion(label, ctx.viewport, ctx.renderOptionsRegistry) + const containingRegion = ctx.depthMap.getContainingRegion(label, ctx) if (ctx.depthMap && containingRegion && containingRegion.detail !== DetailLevel.FullDetails) { label.areChildAreaChildrenRendered = true label.areNonChildAreaChildrenRendered = true @@ -390,7 +381,7 @@ export class KEdgeView implements IView { const ctx = context as SKGraphModelRenderer if (ctx.depthMap) { - const containingRegion = ctx.depthMap.getContainingRegion(edge, ctx.viewport, ctx.renderOptionsRegistry) + const containingRegion = ctx.depthMap.getContainingRegion(edge, ctx) if (ctx.depthMap && containingRegion && containingRegion.detail !== DetailLevel.FullDetails) { edge.areChildAreaChildrenRendered = true edge.areNonChildAreaChildrenRendered = true diff --git a/packages/klighd-core/tsconfig.json b/packages/klighd-core/tsconfig.json index 42d63e24..6f5df611 100644 --- a/packages/klighd-core/tsconfig.json +++ b/packages/klighd-core/tsconfig.json @@ -25,6 +25,6 @@ "src" ], "paths": { - "@kieler/klighd-interactive/lib/*": ["../klighd-interactive/src/*"] + "@kieler/klighd-interactive/lib/*": ["../klighd-interactive/lib/*"] } } From 5358877c158ebaa7595029c75699fbfe7035e28f Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 25 Jan 2022 18:56:23 +0100 Subject: [PATCH 33/95] scale and position edge direction markers --- packages/klighd-core/src/views-rendering.tsx | 72 ++++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index be466fbd..ad424e29 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -17,10 +17,10 @@ /** @jsx svg */ import { VNode } from 'snabbdom'; import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { Bounds } from 'sprotty-protocol'; +import { Bounds, Point } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin, NodeScalingFactor } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -32,7 +32,7 @@ import { ColorStyles, DEFAULT_CLICKABLE_FILL, DEFAULT_FILL, getKStyles, getSvgColorStyle, getSvgColorStyles, getSvgLineStyles, getSvgShadowStyles, getSvgTextStyles, isInvisible, KStyles, LineStyles } from './views-styles'; -import { calculateScaledPoint, equalBounds, upscaleBounds } from './scaling-util'; +import { calculateScaledPoint, upscaleBounds } from './scaling-util'; // ----------------------------- Functions for rendering different KRendering as VNodes in svg -------------------------------------------- @@ -273,35 +273,56 @@ export function renderLine(rendering: KPolyline, } - let skip_children = false - - if (rendering.type !== K_POLYGON && parent instanceof SKEdge) { - + if (parent instanceof SKEdge) { const s = parent.source const t = parent.target - if (parent.routingPoints.length > 0 - && s instanceof SKNode - && t instanceof SKNode - ) { - + if (s instanceof SKNode && t instanceof SKNode) { const s_scaled = s.forceNodeScaleBounds(context) const t_scaled = t.forceNodeScaleBounds(context) - const start = points[0] - const end = points[points.length-1] + if (rendering.type !== K_POLYGON) { + if (points.length > 0) { + + + const start = points[0] + const end = points[points.length-1] + + const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) + const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) + + const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} + const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} + + points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)) + } + } else { + let newPoint = boundsAndTransformation.bounds as Point + + + if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { + newPoint = calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) + } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { + newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) + } + + const offset = Point.subtract(newPoint, boundsAndTransformation.bounds) + const offset_str = "translate("+offset.x+","+offset.y+")" + + gAttrs.transform = offset_str + " " +(gAttrs.transform ?? "") - const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) - const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) + const p_scaled = (parent.parent as SKNode).forceNodeScaleBounds(context); - const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} - const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} + const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) - points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)) - skip_children = !equalBounds(s.bounds, s_scaled.bounds) || !(t.bounds , t_scaled.bounds) + const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) + + gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -newPoint.x + "," + -newPoint.y + ") " + gAttrs.transform + } } } + // now define the line's path. let path = '' switch (rendering.type) { @@ -388,17 +409,10 @@ export function renderLine(rendering: KPolyline, // Create the svg element for this rendering. // Only apply the fast shadow to KPolygons, other shadows are not allowed there. - let element - if (skip_children) { - element = - {...renderSVGLine(lineStyles, colorStyles, shadowStyles, path, rendering.type == K_POLYGON ? styles.kShadow : undefined)} - - } else { - element = + const element = {...renderSVGLine(lineStyles, colorStyles, shadowStyles, path, rendering.type == K_POLYGON ? styles.kShadow : undefined)} {renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)} - - } + return element } From 6e37b04d2c4ba703d5aa2186fd4a6332a1bdae40 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 25 Jan 2022 19:17:49 +0100 Subject: [PATCH 34/95] use effective zoom for min line width --- packages/klighd-core/src/views-styles.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/klighd-core/src/views-styles.tsx b/packages/klighd-core/src/views-styles.tsx index 99030574..de47b1f6 100644 --- a/packages/klighd-core/src/views-styles.tsx +++ b/packages/klighd-core/src/views-styles.tsx @@ -16,7 +16,7 @@ */ /** @jsx svg */ import { VNode } from 'snabbdom'; -import { getZoom, isSelectable, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { getZoom, isSelectable, SChildElement, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars import { MinimumLineWidth, UseMinimumLineWidth } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { @@ -628,7 +628,7 @@ export function isInvisible(styles: KStyles): boolean { * 'stroke-miterlimit' has to be set to the miterLimit style. (This is not a string, but a number.) * @param styles The KStyles of the rendering. * @param target The target of the line - * @param context The current rendering context + * @param context The current rendering context */ export function getSvgLineStyles(styles: KStyles, target: SKGraphElement, context: SKGraphModelRenderer): LineStyles { // The line width as requested by the element @@ -640,7 +640,17 @@ export function getSvgLineStyles(styles: KStyles, target: SKGraphElement, contex // The line witdh in px that the drawn line should not be less than. const minimumLineWidth = context.renderOptionsRegistry.getValueOrDefault(MinimumLineWidth) // The line width the requested one would have when rendered in the current zoom level. - const realLineWidth = lineWidth * getZoom(target) + + + let effectiveZoom; + + if(target instanceof SChildElement && target.parent instanceof SKNode) { + effectiveZoom = target.parent.forceNodeScaleBounds(context).effective_child_zoom + } else { + effectiveZoom = getZoom(target) + } + + const realLineWidth = lineWidth * effectiveZoom if (styles.kLineWidth.lineWidth == 0) { lineWidth = 0 } else if (realLineWidth < minimumLineWidth) { @@ -730,4 +740,4 @@ export interface TextStyles { fontWeight: string | undefined, textDecorationLine: string | undefined, textDecorationStyle: string | undefined, -} \ No newline at end of file +} From cf9868072f6b8b8a1aafbb853366c71a252fbe04 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 26 Jan 2022 11:04:22 +0100 Subject: [PATCH 35/95] fix edge ends ignoring whether node scaling was disabled --- packages/klighd-core/src/views-rendering.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index ad424e29..dece2b4d 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -20,7 +20,7 @@ import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unu import { Bounds, Point } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin, NodeScalingFactor } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -273,7 +273,9 @@ export function renderLine(rendering: KPolyline, } - if (parent instanceof SKEdge) { + const performScaling = context.renderOptionsRegistry.getValueOrDefault(ScaleNodes) + + if (performScaling && parent instanceof SKEdge) { const s = parent.source const t = parent.target From ee5fa53a20f022713c4fa52758d5e6ab5ddf886c Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 26 Jan 2022 12:14:42 +0100 Subject: [PATCH 36/95] bound edge points when scaling by parents bounds taking the margin into account --- packages/klighd-core/src/views-rendering.tsx | 22 +++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index dece2b4d..d5f7ffb4 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -274,14 +274,16 @@ export function renderLine(rendering: KPolyline, } const performScaling = context.renderOptionsRegistry.getValueOrDefault(ScaleNodes) + const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin) if (performScaling && parent instanceof SKEdge) { const s = parent.source const t = parent.target - if (s instanceof SKNode && t instanceof SKNode) { + if (s instanceof SKNode && t instanceof SKNode && parent.parent instanceof SKNode) { const s_scaled = s.forceNodeScaleBounds(context) const t_scaled = t.forceNodeScaleBounds(context) + const p_scaled = parent.parent.forceNodeScaleBounds(context); if (rendering.type !== K_POLYGON) { if (points.length > 0) { @@ -296,7 +298,22 @@ export function renderLine(rendering: KPolyline, const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} - points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)) + + points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)).map(point => { + if (Bounds.includes(p_scaled.bounds, point)) { + return point + } else { + const clamp = function(min: number,max: number,val: number): number { + return Math.min(max, Math.max(min, val)) + } + const newX = clamp(margin, p_scaled.bounds.width - margin, point.x) + const newY = clamp(margin, p_scaled.bounds.height - margin, point.y) + + return {x: newX, y: newY} + } + }) + // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) + // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) } } else { let newPoint = boundsAndTransformation.bounds as Point @@ -313,7 +330,6 @@ export function renderLine(rendering: KPolyline, gAttrs.transform = offset_str + " " +(gAttrs.transform ?? "") - const p_scaled = (parent.parent as SKNode).forceNodeScaleBounds(context); const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) From a08a135b5121ad652b511177035fe9108919eddf Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 26 Jan 2022 13:31:18 +0100 Subject: [PATCH 37/95] actually use _node_scaled_bounds for caching the result of forceNodeScaleBounds should reduce the performance impact of node scaling --- packages/klighd-core/src/depth-map.ts | 6 ++--- packages/klighd-core/src/skgraph-models.ts | 28 ++++++++++++---------- packages/klighd-core/src/views.tsx | 4 +--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 4757dc7c..f989342c 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -175,7 +175,7 @@ export class DepthMap { entry = { providingRegion: providedRegion, containingRegion: undefined } - element.forceNodeScaleBounds(ctx) + element.forceNodeScaleBounds(ctx, true) providedRegion.detail = providedRegion.computeDetailLevel(ctx) this.rootRegions.push(providedRegion) @@ -195,7 +195,7 @@ export class DepthMap { entry.providingRegion.parent = entry.containingRegion entry.containingRegion.children.push(entry.providingRegion); - element.forceNodeScaleBounds(ctx) + element.forceNodeScaleBounds(ctx, true) entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(ctx) } @@ -432,7 +432,7 @@ export class Region { } else { const viewportSize = this.sizeInViewport(ctx) - const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx).effective_child_zoom + const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx, true).effective_child_zoom // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. if (viewportSize >= relativeThreshold || scale > scaleThreshold) { return DetailLevel.FullDetails diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 8bd71a82..b8d7f88b 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -51,22 +51,26 @@ export class SKNode extends KNode implements SKGraphElement { || feature === popupFeature } - forceNodeScaleBounds(ctx: SKGraphModelRenderer): {bounds: Bounds, scale: number, effective_child_zoom: number}{ - const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); + forceNodeScaleBounds(ctx: SKGraphModelRenderer, force = false): {bounds: Bounds, scale: number, effective_child_zoom: number}{ - if (this.parent && this.parent instanceof SKNode && performNodeScaling) { - const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); - const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); + if (force || this._node_scaled_bounds === undefined) { + const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); - const parent_scaled = this.parent.forceNodeScaleBounds(ctx) + if (this.parent && this.parent instanceof SKNode && performNodeScaling) { + const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); + const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); - const effective_zoom = parent_scaled.effective_child_zoom - const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) + const parent_scaled = this.parent.forceNodeScaleBounds(ctx, force) + + const effective_zoom = parent_scaled.effective_child_zoom + const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) + + const upscale = upscaleBounds(ctx.effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); + this._node_scaled_bounds = {...upscale, effective_child_zoom: effective_zoom * upscale.scale} + } else { + this._node_scaled_bounds = {bounds : this.bounds, scale: 1, effective_child_zoom: ctx.viewport.zoom} + } - const upscale = upscaleBounds(ctx.effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); - this._node_scaled_bounds = {...upscale, effective_child_zoom: effective_zoom * upscale.scale} - } else { - this._node_scaled_bounds = {bounds : this.bounds, scale: 1, effective_child_zoom: ctx.viewport.zoom} } return this._node_scaled_bounds diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 841b7b7f..18915f0d 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -56,8 +56,6 @@ export class SKGraphView implements IView { } ctx.positions = [] - - // Add depthMap to context for rendering, when required. const useSmartZoom = this.renderOptionsRegistry.getValueOrDefault(UseSmartZoom) @@ -125,7 +123,7 @@ export class KNodeView implements IView { // we push a new effective zoom in all cases so we can pop later without checking whether we pushed if (node.parent && performNodeScaling) { - const {bounds: newBounds, scale: scalingFactor} = node.forceNodeScaleBounds(ctx) + const {bounds: newBounds, scale: scalingFactor} = node.forceNodeScaleBounds(ctx, true) if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From 57148ee48593ddfd5436f7fd7d11dd382612917d Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 26 Jan 2022 14:13:14 +0100 Subject: [PATCH 38/95] ignore margin for a lines start and end point --- packages/klighd-core/src/views-rendering.tsx | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index d5f7ffb4..dd03099d 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -298,18 +298,29 @@ export function renderLine(rendering: KPolyline, const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} - - points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)).map(point => { + // clamp all edge point to be inside our parent + points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)).map((point, idx) => { if (Bounds.includes(p_scaled.bounds, point)) { return point } else { const clamp = function(min: number,max: number,val: number): number { return Math.min(max, Math.max(min, val)) } - const newX = clamp(margin, p_scaled.bounds.width - margin, point.x) - const newY = clamp(margin, p_scaled.bounds.height - margin, point.y) - return {x: newX, y: newY} + if (idx == 0 || idx == points.length - 1) { + // for the start and end point we cannot account for the margin + const newX = clamp(0, p_scaled.bounds.width, point.x) + const newY = clamp(0, p_scaled.bounds.height, point.y) + + return {x: newX, y: newY} + } else{ + const newX = clamp(margin, p_scaled.bounds.width - margin, point.x) + const newY = clamp(margin, p_scaled.bounds.height - margin, point.y) + + return {x: newX, y: newY} + } + + } }) // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) From 8b6a73f0603d400ac301c0d84a166a5e6c3bbfd2 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 26 Jan 2022 18:40:14 +0100 Subject: [PATCH 39/95] simplify transform --- packages/klighd-core/src/views-rendering.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index dd03099d..34c670b6 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -336,17 +336,11 @@ export function renderLine(rendering: KPolyline, newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) } - const offset = Point.subtract(newPoint, boundsAndTransformation.bounds) - const offset_str = "translate("+offset.x+","+offset.y+")" - - gAttrs.transform = offset_str + " " +(gAttrs.transform ?? "") - - const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) - gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -newPoint.x + "," + -newPoint.y + ") " + gAttrs.transform + gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + gAttrs.transform } } } From 652ea3fadb1e90622aae1343fb775340bf752b29 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 26 Jan 2022 19:06:59 +0100 Subject: [PATCH 40/95] routingPoints may be missing initially without Incremental Diagram Generator --- packages/klighd-core/src/views-rendering.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 34c670b6..60c1ef0d 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -326,7 +326,7 @@ export function renderLine(rendering: KPolyline, // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) } - } else { + } else if (parent.routingPoints.length > 0){ let newPoint = boundsAndTransformation.bounds as Point From 1b5333ad588750e19024d71c7dc9cfd8de80310b Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 10:39:48 +0100 Subject: [PATCH 41/95] collapse if if --- packages/klighd-core/src/views-rendering.tsx | 98 ++++++++++---------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 60c1ef0d..8bb69439 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -276,72 +276,74 @@ export function renderLine(rendering: KPolyline, const performScaling = context.renderOptionsRegistry.getValueOrDefault(ScaleNodes) const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin) - if (performScaling && parent instanceof SKEdge) { + if (performScaling + && parent instanceof SKEdge + && parent.source instanceof SKNode + && parent.target instanceof SKNode + && parent.parent instanceof SKNode + ) { const s = parent.source const t = parent.target + const s_scaled = s.forceNodeScaleBounds(context) + const t_scaled = t.forceNodeScaleBounds(context) + const p_scaled = parent.parent.forceNodeScaleBounds(context); - if (s instanceof SKNode && t instanceof SKNode && parent.parent instanceof SKNode) { - const s_scaled = s.forceNodeScaleBounds(context) - const t_scaled = t.forceNodeScaleBounds(context) - const p_scaled = parent.parent.forceNodeScaleBounds(context); + if (rendering.type !== K_POLYGON) { + if (points.length > 0) { - if (rendering.type !== K_POLYGON) { - if (points.length > 0) { + const start = points[0] + const end = points[points.length-1] - const start = points[0] - const end = points[points.length-1] + const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) + const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) - const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) - const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) + const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} + const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} - const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} - const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} - - // clamp all edge point to be inside our parent - points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)).map((point, idx) => { - if (Bounds.includes(p_scaled.bounds, point)) { - return point - } else { - const clamp = function(min: number,max: number,val: number): number { - return Math.min(max, Math.max(min, val)) - } + // clamp all edge point to be inside our parent + points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)).map((point, idx) => { + if (Bounds.includes(p_scaled.bounds, point)) { + return point + } else { + const clamp = function(min: number,max: number,val: number): number { + return Math.min(max, Math.max(min, val)) + } - if (idx == 0 || idx == points.length - 1) { - // for the start and end point we cannot account for the margin - const newX = clamp(0, p_scaled.bounds.width, point.x) - const newY = clamp(0, p_scaled.bounds.height, point.y) + if (idx == 0 || idx == points.length - 1) { + // for the start and end point we cannot account for the margin + const newX = clamp(0, p_scaled.bounds.width, point.x) + const newY = clamp(0, p_scaled.bounds.height, point.y) - return {x: newX, y: newY} - } else{ - const newX = clamp(margin, p_scaled.bounds.width - margin, point.x) - const newY = clamp(margin, p_scaled.bounds.height - margin, point.y) + return {x: newX, y: newY} + } else{ + const newX = clamp(margin, p_scaled.bounds.width - margin, point.x) + const newY = clamp(margin, p_scaled.bounds.height - margin, point.y) - return {x: newX, y: newY} - } + return {x: newX, y: newY} + } - } - }) - // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) - // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) - } - } else if (parent.routingPoints.length > 0){ - let newPoint = boundsAndTransformation.bounds as Point + } + }) + // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) + // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) + } + } else if (parent.routingPoints.length > 0){ + let newPoint = boundsAndTransformation.bounds as Point - if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { - newPoint = calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) - } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { - newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) - } + if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { + newPoint = calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) + } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { + newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) + } - const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) + const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) - const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) + const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) - gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + gAttrs.transform - } + gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + gAttrs.transform } } From 5b3fc1c59978a066e01ad30add758038ab4c17ec Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 10:40:14 +0100 Subject: [PATCH 42/95] add spaces for readability of the svg paths --- packages/klighd-core/src/views-rendering.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 8bb69439..06c1aaf2 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -352,29 +352,29 @@ export function renderLine(rendering: KPolyline, let path = '' switch (rendering.type) { case K_SPLINE: { - path += `M${points[0].x},${points[0].y}` + path += `M ${points[0].x},${points[0].y} ` for (let i = 1; i < points.length; i = i + 3) { const remainingPoints = points.length - i if (remainingPoints === 1) { // if one routing point is left, draw a straight line to there. - path += `L${points[i].x},${points[i].y}` + path += `L ${points[i].x},${points[i].y} ` } else if (remainingPoints === 2) { // if two routing points are left, draw a quadratic bezier curve with those two points. - path += `Q${points[i].x},${points[i].y} ${points[i + 1].x},${points[i + 1].y}` + path += `Q ${points[i].x},${points[i].y} ${points[i + 1].x},${points[i + 1].y} ` } else { // if three or more routing points are left, draw a cubic bezier curve with those points. - path += `C${points[i].x},${points[i].y} ` + path += `C ${points[i].x},${points[i].y} ` + `${points[i + 1].x},${points[i + 1].y} ` - + `${points[i + 2].x},${points[i + 2].y}` + + `${points[i + 2].x},${points[i + 2].y} ` } } break } case K_POLYLINE: // Fall through to next case. KPolylines are just KPolygons without the closing end. case K_POLYGON: { - path += `M${points[0].x},${points[0].y}` + path += `M ${points[0].x},${points[0].y} ` for (let i = 1; i < points.length; i++) { - path += `L${points[i].x},${points[i].y}` + path += `L ${points[i].x},${points[i].y} ` } if (rendering.type === K_POLYGON) { path += 'Z' @@ -386,7 +386,7 @@ export function renderLine(rendering: KPolyline, const bendRadius = (rendering as KRoundedBendsPolyline).bendRadius // now define the rounded polyline's path. - path += `M${points[0].x},${points[0].y}` + path += `M ${points[0].x},${points[0].y} ` for (let i = 1; i < points.length - 1; i++) { const p0 = points[i - 1] const p = points[i] @@ -422,11 +422,11 @@ export function renderLine(rendering: KPolyline, } // draw a line to the start of the bend point (from the last end of its bend) // and then draw the bend with the control points of the point itself and the bend end point. - path += `L${xs},${ys}Q${xp},${yp} ${xe},${ye}` + path += `L ${xs},${ys} Q ${xp},${yp} ${xe},${ye} ` } if (points.length > 1) { const lastPoint = points[points.length - 1] - path += `L${lastPoint.x},${lastPoint.y}` + path += `L ${lastPoint.x},${lastPoint.y}` } break } From fe47f606d41eb91f4c7031cdb892373fb85210c2 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 10:46:24 +0100 Subject: [PATCH 43/95] point.length === 0 was already checked --- packages/klighd-core/src/views-rendering.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 06c1aaf2..b5d0641d 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -289,8 +289,6 @@ export function renderLine(rendering: KPolyline, const p_scaled = parent.parent.forceNodeScaleBounds(context); if (rendering.type !== K_POLYGON) { - if (points.length > 0) { - const start = points[0] const end = points[points.length-1] @@ -328,7 +326,7 @@ export function renderLine(rendering: KPolyline, }) // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) - } + } else if (parent.routingPoints.length > 0){ let newPoint = boundsAndTransformation.bounds as Point From f2614a34a6eedf202f053f5650750306d13f78c1 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 10:50:26 +0100 Subject: [PATCH 44/95] use a switch as we probably need to handle splines seperately --- packages/klighd-core/src/views-rendering.tsx | 36 ++++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index b5d0641d..8a701384 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -288,8 +288,10 @@ export function renderLine(rendering: KPolyline, const t_scaled = t.forceNodeScaleBounds(context) const p_scaled = parent.parent.forceNodeScaleBounds(context); - if (rendering.type !== K_POLYGON) { - + switch (rendering.type) { + case K_SPLINE: + case K_ROUNDED_BENDS_POLYLINE: + case K_POLYLINE: { const start = points[0] const end = points[points.length-1] @@ -326,26 +328,32 @@ export function renderLine(rendering: KPolyline, }) // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) - - } else if (parent.routingPoints.length > 0){ - let newPoint = boundsAndTransformation.bounds as Point + break + } + case K_POLYGON: { + if (parent.routingPoints.length > 0){ + let newPoint = boundsAndTransformation.bounds as Point - if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { - newPoint = calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) - } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { - newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) - } + if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { + newPoint = calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) + } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { + newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) + } - const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) + const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) - const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) + const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) - gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + gAttrs.transform + gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + gAttrs.transform + } + break + } + default: + console.error("Unexpected Line Type: ", rendering.type) } } - // now define the line's path. let path = '' switch (rendering.type) { From 3092cf2e786f0957c91bb261d65f99519107b61c Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 11:19:03 +0100 Subject: [PATCH 45/95] fix undefined in transform --- packages/klighd-core/src/views-rendering.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 8a701384..7e5614df 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -345,7 +345,7 @@ export function renderLine(rendering: KPolyline, const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) - gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + gAttrs.transform + gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + (gAttrs.transform ?? "") } break } From dd32784289df9ea51af8d6477a1da2093878db73 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 11:19:41 +0100 Subject: [PATCH 46/95] change to dropping points that overlap end or start node --- packages/klighd-core/src/views-rendering.tsx | 64 ++++++++++---------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 7e5614df..7b7b98cb 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -274,7 +274,6 @@ export function renderLine(rendering: KPolyline, } const performScaling = context.renderOptionsRegistry.getValueOrDefault(ScaleNodes) - const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin) if (performScaling && parent instanceof SKEdge @@ -288,46 +287,49 @@ export function renderLine(rendering: KPolyline, const t_scaled = t.forceNodeScaleBounds(context) const p_scaled = parent.parent.forceNodeScaleBounds(context); + const start = points[0] + const end = points[points.length-1] + + const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) + const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) + switch (rendering.type) { - case K_SPLINE: - case K_ROUNDED_BENDS_POLYLINE: - case K_POLYLINE: { - const start = points[0] - const end = points[points.length-1] + case K_SPLINE: { - const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) - const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) + points = [...points] - const curve_bounds = {x: start.x, y: start.y, width : end.x - start.x, height: end.y - start.y} - const scaled_curve_bounds = {x: scaled_start.x, y: scaled_start.y, width : scaled_end.x - scaled_start.x, height: scaled_end.y - scaled_start.y} + points.pop() - // clamp all edge point to be inside our parent - points = points.map(point => calculateScaledPoint(curve_bounds, scaled_curve_bounds, point)).map((point, idx) => { - if (Bounds.includes(p_scaled.bounds, point)) { - return point - } else { - const clamp = function(min: number,max: number,val: number): number { - return Math.min(max, Math.max(min, val)) - } + for (let i = 0 ; i< points.length ;) { + const remainingPoints = points.length - i - if (idx == 0 || idx == points.length - 1) { - // for the start and end point we cannot account for the margin - const newX = clamp(0, p_scaled.bounds.width, point.x) - const newY = clamp(0, p_scaled.bounds.height, point.y) + const z = Math.min(3, remainingPoints) - return {x: newX, y: newY} - } else{ - const newX = clamp(margin, p_scaled.bounds.width - margin, point.x) - const newY = clamp(margin, p_scaled.bounds.height - margin, point.y) + const p = points[i + z - 1] - return {x: newX, y: newY} + if ( + Bounds.includes(s_scaled.bounds, p) + || Bounds.includes(t_scaled.bounds, p) + ) { + for (let j = 0 ;j < z; j++){ + points.pop() } + } else { + i+=3 + } + } + points.unshift(scaled_start) + points.push(scaled_end) + + break; + } + case K_ROUNDED_BENDS_POLYLINE: + case K_POLYLINE: { + points = points.filter(p => !Bounds.includes(s_scaled.bounds, p) && !Bounds.includes(t_scaled.bounds,p)) + points.unshift(scaled_start) + points.push(scaled_end) - } - }) - // points[0] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[0]) - // points[points.length - 1] = calculateScaledPoint(curve_bounds, scaled_curve_bounds, points[points.length -1]) break } case K_POLYGON: { From 597df5f0d42c0a29b1ed759853f0b94dbf50aefb Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 12:46:44 +0100 Subject: [PATCH 47/95] refactor scaling-utils --- packages/klighd-core/src/depth-map.ts | 6 +- packages/klighd-core/src/scaling-util.ts | 234 ++++++++++--------- packages/klighd-core/src/skgraph-models.ts | 4 +- packages/klighd-core/src/views-rendering.tsx | 12 +- 4 files changed, 130 insertions(+), 126 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index f989342c..0d3d2ac2 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -19,7 +19,7 @@ import { KGraphElement } from "@kieler/klighd-interactive/lib/constraint-classes import { SChildElement, SModelRoot } from "sprotty"; import { Viewport } from "sprotty-protocol"; import { FullDetailRelativeThreshold, FullDetailScaleThreshold } from "./options/render-options-registry"; -import { getAbsoluteRenderedBounds } from "./scaling-util"; +import { ScalingUtil } from "./scaling-util"; import { SKGraphModelRenderer } from "./skgraph-model-renderer"; import { isContainerRendering, isRendering, KRendering, SKNode } from "./skgraph-models"; @@ -382,7 +382,7 @@ export class Region { * @returns Boolean value indicating the visibility of the region in the current viewport. */ isInBounds(ctx: SKGraphModelRenderer): boolean { - const bounds = getAbsoluteRenderedBounds(this.boundingRectangle, ctx) + const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds @@ -401,7 +401,7 @@ export class Region { * @returns the relative size of the KNodes shortest dimension */ sizeInViewport(ctx: SKGraphModelRenderer,): number { - const bounds = getAbsoluteRenderedBounds(this.boundingRectangle, ctx) + const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 7bad12a0..79d5f620 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -2,150 +2,154 @@ import { Bounds, Dimension, Point} from 'sprotty-protocol' import { SKNode } from './skgraph-models' import { SKGraphModelRenderer } from './skgraph-model-renderer' +export class ScalingUtil { + private constructor(){ + // private constructor as this class should not be instantiated + } -/** - * @param node the bounds of the node to scale - * @param parent the dimensions of the parent of the node to scale - * @param margin the margin node shall retain to parent - * @returns the maximum scale at which node retains the margin to parent - */ -export function maxParentScale(node: Bounds, parent: Dimension, margin: number): number { - // the maximum scale that keeps the node in bounds height wise - const maxHeightScale = (parent.height - 2 * margin) / node.height - // the maximum scale that keeps the node in bounds width wise - const maxWidthScale = (parent.width - 2 * margin) / node.width - - return Math.min(maxHeightScale, maxWidthScale) -} + /** + * @param node the bounds of the node to scale + * @param parent the dimensions of the parent of the node to scale + * @param margin the margin node shall retain to parent + * @returns the maximum scale at which node retains the margin to parent + */ + public static maxParentScale(node: Bounds, parent: Dimension, margin: number): number { + // the maximum scale that keeps the node in bounds height wise + const maxHeightScale = (parent.height - 2 * margin) / node.height + // the maximum scale that keeps the node in bounds width wise + const maxWidthScale = (parent.width - 2 * margin) / node.width + + return Math.min(maxHeightScale, maxWidthScale) + } + private static inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number, margin: number): number { + // we want to find positive scale so that + // result_a = scaleDimension(offset_a, length_a, available, scale) + // result_b = scaleDimension(offset_b, length_b, available, scale) + // result_a.offset = result_b.offset + result_b.length + // || result_b.offset = result_a.offset + result_a.length -function inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number, margin: number): number { - // we want to find positive scale so that - // result_a = scaleDimension(offset_a, length_a, available, scale) - // result_b = scaleDimension(offset_b, length_b, available, scale) - // result_a.offset = result_b.offset + result_b.length - // || result_b.offset = result_a.offset + result_a.length + const fa = (offset_a * length_a) / (available - length_a) + const fb = (offset_b * length_b) / (available - length_b) - const fa = (offset_a * length_a) / (available - length_a) - const fb = (offset_b * length_b) / (available - length_b) + const numerator = offset_a + fa - offset_b - fb - const numerator = offset_a + fa - offset_b - fb + const result_1 = ( numerator - margin) / (fa - fb + length_b) + const result_2 = (-numerator - margin) / (fb - fa + length_a) - const result_1 = ( numerator - margin) / (fa - fb + length_b) - const result_2 = (-numerator - margin) / (fb - fa + length_a) + // the scale should be at least one and at most one of the results should be positive + return Math.max(result_1, result_2, 1) + } - // the scale should be at least one and at most one of the results should be positive - return Math.max(result_1, result_2, 1) -} -/** - * Calculate the maximum scale at which node and sibling retain the given margin between them - * @param node the bounds of node to scale - * @param parent the dimensions of the parent of the node to scale - * @param sibling a sibling og the nod to scale - * @param margin the margin node and sibling shall retain when both scaled by the result - * @returns the maximum scale at which node and sibling retain margin between them - */ -export function maxSiblingScale(node: Bounds, parent:Dimension, sibling: Bounds, margin: number) : number { - - // calculate the scale for each dimension at which we reach our sibling - const result_1 = inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) - const result_2 = inverseScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height, margin) - - // take the max as that which ever is further is relevant for bounding us, but should be at least 1 - return Math.max(result_1, result_2, 1) -} + /** + * Calculate the maximum scale at which node and sibling retain the given margin between them + * @param node the bounds of node to scale + * @param parent the dimensions of the parent of the node to scale + * @param sibling a sibling og the nod to scale + * @param margin the margin node and sibling shall retain when both scaled by the result + * @returns the maximum scale at which node and sibling retain margin between them + */ + public static maxSiblingScale(node: Bounds, parent:Dimension, sibling: Bounds, margin: number) : number { + // calculate the scale for each dimension at which we reach our sibling + const result_1 = ScalingUtil.inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) + const result_2 = ScalingUtil.inverseScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height, margin) -/** - * Scale bounds in the specified dimensions by the specified scale - * @param originalBounds the bounds to scale - * @param availableSpace the space available to scale the bounds - * @param scale the scale by which to scale - * @returns the scaled bounds - */ -export function calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { - const originalWidth = originalBounds.width - const originalHeight = originalBounds.height - const originalX = originalBounds.x - const originalY = originalBounds.y - - // Calculate the new x and y indentation: - // width required of scaled rendering - const {length: newWidth, offset: newX} = scaleDimension(originalX, originalWidth, availableSpace.width, scale) - - // Same for y axis, just with switched dimensional variables. - const {length: newHeight, offset: newY} = scaleDimension(originalY, originalHeight, availableSpace.height, scale) - return {x: newX, y : newY, width: newWidth, height: newHeight} -} + // take the max as that which ever is further is relevant for bounding us, but should be at least 1 + return Math.max(result_1, result_2, 1) + } + /** + * Scale bounds in the specified dimensions by the specified scale + * @param originalBounds the bounds to scale + * @param availableSpace the space available to scale the bounds + * @param scale the scale by which to scale + * @returns the scaled bounds + */ + public static calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { + const originalWidth = originalBounds.width + const originalHeight = originalBounds.height + const originalX = originalBounds.x + const originalY = originalBounds.y + + // Calculate the new x and y indentation: + // width required of scaled rendering + const {length: newWidth, offset: newX} = ScalingUtil.scaleDimension(originalX, originalWidth, availableSpace.width, scale) + + // Same for y axis, just with switched dimensional variables. + const {length: newHeight, offset: newY} = ScalingUtil.scaleDimension(originalY, originalHeight, availableSpace.height, scale) + return {x: newX, y : newY, width: newWidth, height: newHeight} + } -export function calculateScaledPoint(originalBounds: Bounds, newBounds: Bounds, originalPoint: Point) : Point { + public static calculateScaledPoint(originalBounds: Bounds, newBounds: Bounds, originalPoint: Point) : Point { - let newX - let newY + let newX + let newY - if (originalBounds.width == 0 || newBounds.width == 0) { - newX = originalPoint.x - originalBounds.x + newBounds.x - } else { - const alongX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width - newX = newBounds.x + alongX * newBounds.width - } + if (originalBounds.width == 0 || newBounds.width == 0) { + newX = originalPoint.x - originalBounds.x + newBounds.x + } else { + const alongX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width + newX = newBounds.x + alongX * newBounds.width + } + + if (originalBounds.height == 0 || newBounds.height == 0) { + newY = originalPoint.y - originalBounds.y + newBounds.y + }else { + const alongY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height + newY = newBounds.y + alongY * newBounds.height + } - if (originalBounds.height == 0 || newBounds.height == 0) { - newY = originalPoint.y - originalBounds.y + newBounds.y - }else { - const alongY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height - newY = newBounds.y + alongY * newBounds.height + return {x: newX, y: newY} } - return {x: newX, y: newY} -} + public static scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ + const newLength = length * scale; + const prefix = offset + const postfix = available - offset - length + const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) + return {offset: newOffset, length: newLength} + } -export function scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ - const newLength = length * scale; - const prefix = offset - const postfix = available - offset - length - const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) - return {offset: newOffset, length: newLength} -} + public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { -export function upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { + // we want that the effectiveScale * desiredScale = maxScale + // so that the we effectively up scale to maxScale + const desiredScale = maxScale / effectiveScale; - // we want that the effectiveScale * desiredScale = maxScale - // so that the we effectively up scale to maxScale - const desiredScale = maxScale / effectiveScale; + // the maximum scale at which the child still fits into the parent + const parentScaling = ScalingUtil.maxParentScale(childBounds, parentBounds, margin) - // the maximum scale at which the child still fits into the parent - const parentScaling = maxParentScale(childBounds, parentBounds, margin) + // some maximum scale at which the child does not interfere with its siblings + const siblingScaling = siblings.map((siblingBounds) => ScalingUtil.maxSiblingScale(childBounds, parentBounds, siblingBounds, margin)) - // some maximum scale at which the child does not interfere with its siblings - const siblingScaling = siblings.map((siblingBounds) => maxSiblingScale(childBounds, parentBounds, siblingBounds, margin)) + // the most restrictive scale between our desired scale and the maximum imposed by the parent and siblings + const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScaling) - // the most restrictive scale between our desired scale and the maximum imposed by the parent and siblings - const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScaling) + // we never want to shrink, should only be relevant if our desired scale is less than 1 + const scalingFactor = Math.max(1, preferredScale) - // we never want to shrink, should only be relevant if our desired scale is less than 1 - const scalingFactor = Math.max(1, preferredScale) + const newBounds = ScalingUtil.calculateScaledBounds(childBounds, parentBounds, scalingFactor) - const newBounds = calculateScaledBounds(childBounds, parentBounds, scalingFactor) + return {bounds:newBounds, scale: scalingFactor} + } - return {bounds:newBounds, scale: scalingFactor} -} + public static equalBounds(a: Bounds, b: Bounds) : boolean { + return a.x == b.x && a.y == b.y && a.height == b.height && a.width == b.width + } -export function equalBounds(a: Bounds, b: Bounds) : boolean { - return a.x == b.x && a.y == b.y && a.height == b.height && a.width == b.width -} + public static getAbsoluteRenderedBounds(element: SKNode, ctx: SKGraphModelRenderer) : Bounds { + let current: SKNode = element; + let {bounds: bounds} = element.forceNodeScaleBounds(ctx) -export function getAbsoluteRenderedBounds(element: SKNode, ctx: SKGraphModelRenderer) : Bounds { - let current: SKNode = element; - let {bounds: bounds} = element.forceNodeScaleBounds(ctx) + while (current.parent instanceof SKNode) { + const parent = current.parent; + bounds = parent.localToParentRendered(bounds, ctx); + current = parent; + } - while (current.parent instanceof SKNode) { - const parent = current.parent; - bounds = parent.localToParentRendered(bounds, ctx); - current = parent; + return bounds; } - return bounds; } diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index b8d7f88b..f1b2adef 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -19,7 +19,7 @@ import { KEdge, KGraphData, KGraphElement, KNode } from '@kieler/klighd-interact import { boundsFeature, moveFeature, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement, SShapeElement } from 'sprotty'; import { Point, isBounds, Bounds } from 'sprotty-protocol' import { NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; -import { upscaleBounds } from './scaling-util'; +import { ScalingUtil } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; /** @@ -65,7 +65,7 @@ export class SKNode extends KNode implements SKGraphElement { const effective_zoom = parent_scaled.effective_child_zoom const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - const upscale = upscaleBounds(ctx.effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); + const upscale = ScalingUtil.upscaleBounds(ctx.effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); this._node_scaled_bounds = {...upscale, effective_child_zoom: effective_zoom * upscale.scale} } else { this._node_scaled_bounds = {bounds : this.bounds, scale: 1, effective_child_zoom: ctx.viewport.zoom} diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 7b7b98cb..9d578cc2 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -32,7 +32,7 @@ import { ColorStyles, DEFAULT_CLICKABLE_FILL, DEFAULT_FILL, getKStyles, getSvgColorStyle, getSvgColorStyles, getSvgLineStyles, getSvgShadowStyles, getSvgTextStyles, isInvisible, KStyles, LineStyles } from './views-styles'; -import { calculateScaledPoint, upscaleBounds } from './scaling-util'; +import { ScalingUtil } from './scaling-util'; // ----------------------------- Functions for rendering different KRendering as VNodes in svg -------------------------------------------- @@ -290,8 +290,8 @@ export function renderLine(rendering: KPolyline, const start = points[0] const end = points[points.length-1] - const scaled_start = calculateScaledPoint(s.bounds, s_scaled.bounds, start) - const scaled_end = calculateScaledPoint(t.bounds, t_scaled.bounds, end) + const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.bounds, start) + const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.bounds, end) switch (rendering.type) { case K_SPLINE: { @@ -338,9 +338,9 @@ export function renderLine(rendering: KPolyline, if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { - newPoint = calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) + newPoint = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { - newPoint = calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) + newPoint = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) } const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) @@ -1010,7 +1010,7 @@ export function renderKRendering(kRendering: KRendering, const parentBounds = providingRegion ? providingRegion.boundingRectangle.bounds : (parent as KNode).bounds const originalBounds = boundingBox - const {bounds: newBounds, scale: scalingFactor} = upscaleBounds(context.effectiveZoom, maxScale, originalBounds, parentBounds, margin); + const {bounds: newBounds, scale: scalingFactor} = ScalingUtil.upscaleBounds(context.effectiveZoom, maxScale, originalBounds, parentBounds, margin); context.pushEffectiveZoom(context.effectiveZoom * scalingFactor) // Apply the new bounds and scaling as the element's transformation. From 7c07580236e7a9f6213e8a122575f94b74c08347 Mon Sep 17 00:00:00 2001 From: Skgland Date: Thu, 27 Jan 2022 18:42:04 +0100 Subject: [PATCH 48/95] [WIP] change edge adjustment edge end markers still need to be corrected correctly --- packages/klighd-core/src/scaling-util.ts | 29 +++++ packages/klighd-core/src/skgraph-models.ts | 3 + packages/klighd-core/src/views-rendering.tsx | 110 +++++++++++++++---- 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 79d5f620..183a0dce 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,6 +1,7 @@ import { Bounds, Dimension, Point} from 'sprotty-protocol' import { SKNode } from './skgraph-models' import { SKGraphModelRenderer } from './skgraph-model-renderer' +import { PointToPointLine } from 'sprotty' export class ScalingUtil { private constructor(){ @@ -152,4 +153,32 @@ export class ScalingUtil { return bounds; } + public static intersections(bounds: Bounds, line: PointToPointLine ) : Point[] { + + const tl = bounds as Point + const tr = Point.add(bounds, {x: 0 , y: bounds.height}) + const bl = Point.add(bounds, {x: bounds.width, y: 0 }) + const br = Point.add(bounds, {x: bounds.width, y: bounds.height}) + + const top = new PointToPointLine(tl, tr) + const bottom = new PointToPointLine(bl, br) + const left = new PointToPointLine(tl, bl) + const right = new PointToPointLine(tr, br) + + return [line.intersection(top), line.intersection(bottom), line.intersection(left), line.intersection(right)].filter(p => p !== undefined).map(p => p as Point) + } + + public static sort_by_dist(point: Point) : (a:Point, b:Point)=>number { + return (a: Point,b: Point) => { + const a_dist = Point.euclideanDistance(a, point) + const b_dist = Point.euclideanDistance(b, point) + if (a_dist > b_dist) { + return 1 + } else if (b_dist > a_dist) { + return -1 + } else { + return 0 + } + } + } } diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index f1b2adef..bff40699 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -129,6 +129,9 @@ export class SKLabel extends SLabel implements SKGraphElement { */ export class SKEdge extends KEdge implements SKGraphElement { tooltip?: string + + moved_ends_by?: {start: Point, end: Point} + hasFeature(feature: symbol): boolean { return feature === selectFeature || feature === popupFeature } diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 9d578cc2..d2bf4afc 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -16,7 +16,7 @@ */ /** @jsx svg */ import { VNode } from 'snabbdom'; -import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { PointToPointLine, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars import { Bounds, Point } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './depth-map'; @@ -293,54 +293,116 @@ export function renderLine(rendering: KPolyline, const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.bounds, start) const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.bounds, end) + let max_coord_per_point = 1 switch (rendering.type) { - case K_SPLINE: { + case K_SPLINE: + max_coord_per_point = 3 + // fallthrough + case K_ROUNDED_BENDS_POLYLINE: + case K_POLYLINE: { - points = [...points] + const newPoints = [] - points.pop() + let i = 1 - for (let i = 0 ; i< points.length ;) { + // skip points in the start node + while (i < points.length) { const remainingPoints = points.length - i - const z = Math.min(3, remainingPoints) + const z = Math.min(max_coord_per_point, remainingPoints) const p = points[i + z - 1] if ( Bounds.includes(s_scaled.bounds, p) - || Bounds.includes(t_scaled.bounds, p) ) { - for (let j = 0 ;j < z; j++){ - points.pop() + i+=z + } else { + break + } + } + + // determine new start point + + let start_choice = scaled_start + if (i < points.length) { + + const remainingPoints = points.length - i + const z = Math.min(max_coord_per_point, remainingPoints) + + const prev = points[i-1] + const next = points[i + z - 1] + + const edge = new PointToPointLine(prev, next) + + const intersections = ScalingUtil.intersections(s_scaled.bounds, edge) + + intersections.sort(ScalingUtil.sort_by_dist(next)) + + if (intersections.length > 0){ + start_choice = intersections[0] + } + + } + newPoints.push(start_choice) + + + // keep points not in end node + while (i < points.length) { + const remainingPoints = points.length - i + + const z = Math.min(max_coord_per_point, remainingPoints) + + const p = points[i + z - 1] + + if ( + !Bounds.includes(t_scaled.bounds, p) + ) { + for (let j = 0; j < z ; j++) { + i++ + newPoints.push(points[i]) } } else { - i+=3 + break } } - points.unshift(scaled_start) - points.push(scaled_end) + // determine new end point - break; - } - case K_ROUNDED_BENDS_POLYLINE: - case K_POLYLINE: { - points = points.filter(p => !Bounds.includes(s_scaled.bounds, p) && !Bounds.includes(t_scaled.bounds,p)) - points.unshift(scaled_start) - points.push(scaled_end) + let end_choice = scaled_end + if (i < points.length) { - break + const remainingPoints = points.length - i + const z = Math.min(max_coord_per_point, remainingPoints) + + const prev = points[i-1] + const next = points[i + z - 1] + + const edge = new PointToPointLine(prev, next) + + const intersections = ScalingUtil.intersections(t_scaled.bounds, edge) + + intersections.sort(ScalingUtil.sort_by_dist(prev)) + + if (intersections.length > 0){ + end_choice = intersections[0] + } + } + newPoints.push(end_choice) + + + parent.moved_ends_by = {start: Point.subtract(points[0], start_choice), end: Point.subtract(points[points.length -1], end_choice)} + points = newPoints + break; } case K_POLYGON: { - if (parent.routingPoints.length > 0){ + if (parent.moved_ends_by && parent.routingPoints.length > 0){ let newPoint = boundsAndTransformation.bounds as Point - if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { - newPoint = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.bounds, boundsAndTransformation.bounds) + newPoint = Point.add(parent.moved_ends_by.start, boundsAndTransformation.bounds) } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { - newPoint = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.bounds, boundsAndTransformation.bounds) + newPoint = Point.add(parent.moved_ends_by.end, boundsAndTransformation.bounds) } const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) From ab4710f7da7b7678dc53d4873aa871ea76279d98 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 10:53:33 +0100 Subject: [PATCH 49/95] fix end_moved_by offset --- packages/klighd-core/src/views-rendering.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index d2bf4afc..883717cf 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -323,8 +323,8 @@ export function renderLine(rendering: KPolyline, } // determine new start point - let start_choice = scaled_start + if (i < points.length) { const remainingPoints = points.length - i @@ -344,6 +344,7 @@ export function renderLine(rendering: KPolyline, } } + newPoints.push(start_choice) @@ -391,7 +392,7 @@ export function renderLine(rendering: KPolyline, newPoints.push(end_choice) - parent.moved_ends_by = {start: Point.subtract(points[0], start_choice), end: Point.subtract(points[points.length -1], end_choice)} + parent.moved_ends_by = {start: Point.subtract(start_choice, points[0]), end: Point.subtract(end_choice, points[points.length -1])} points = newPoints break; } From 7e0a291ad820992eb42c9c674e9ea398be0f364d Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 11:31:20 +0100 Subject: [PATCH 50/95] fix off by one error --- packages/klighd-core/src/views-rendering.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 883717cf..9f514168 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -360,8 +360,8 @@ export function renderLine(rendering: KPolyline, !Bounds.includes(t_scaled.bounds, p) ) { for (let j = 0; j < z ; j++) { - i++ newPoints.push(points[i]) + i++ } } else { break From 54887e24ff343ac9f69f186d17ded875aa2137ff Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 11:32:00 +0100 Subject: [PATCH 51/95] keep the control points for the end_choice --- packages/klighd-core/src/views-rendering.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 9f514168..308b31ba 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -388,9 +388,17 @@ export function renderLine(rendering: KPolyline, if (intersections.length > 0){ end_choice = intersections[0] } + + if (z >= 2) { + newPoints.push(points[i]) + if (z == 3) { + newPoints.push(points[i + 1]) + } + } + } - newPoints.push(end_choice) + newPoints.push(end_choice) parent.moved_ends_by = {start: Point.subtract(start_choice, points[0]), end: Point.subtract(end_choice, points[points.length -1])} points = newPoints From 8d4e7bb27ca626c3e92f05b8ffab93cb977a08a8 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 13:42:02 +0100 Subject: [PATCH 52/95] cleanup scaling-util --- packages/klighd-core/src/scaling-util.ts | 59 ++++++++++++++++-------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 183a0dce..8303b8b8 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -27,8 +27,8 @@ export class ScalingUtil { // we want to find positive scale so that // result_a = scaleDimension(offset_a, length_a, available, scale) // result_b = scaleDimension(offset_b, length_b, available, scale) - // result_a.offset = result_b.offset + result_b.length - // || result_b.offset = result_a.offset + result_a.length + // result_a.offset = result_b.offset + result_b.length + margin + // || result_b.offset = result_a.offset + result_a.length + margin const fa = (offset_a * length_a) / (available - length_a) const fb = (offset_b * length_b) / (available - length_b) @@ -51,7 +51,7 @@ export class ScalingUtil { * @param margin the margin node and sibling shall retain when both scaled by the result * @returns the maximum scale at which node and sibling retain margin between them */ - public static maxSiblingScale(node: Bounds, parent:Dimension, sibling: Bounds, margin: number) : number { + public static maxSiblingScale(node: Bounds, parent: Dimension, sibling: Bounds, margin: number) : number { // calculate the scale for each dimension at which we reach our sibling const result_1 = ScalingUtil.inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) @@ -67,21 +67,36 @@ export class ScalingUtil { * @param scale the scale by which to scale * @returns the scaled bounds */ - public static calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { + private static calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { const originalWidth = originalBounds.width const originalHeight = originalBounds.height const originalX = originalBounds.x const originalY = originalBounds.y - // Calculate the new x and y indentation: - // width required of scaled rendering + // Calculate the new x offset and width: const {length: newWidth, offset: newX} = ScalingUtil.scaleDimension(originalX, originalWidth, availableSpace.width, scale) - // Same for y axis, just with switched dimensional variables. + // Same for y offset and height const {length: newHeight, offset: newY} = ScalingUtil.scaleDimension(originalY, originalHeight, availableSpace.height, scale) + return {x: newX, y : newY, width: newWidth, height: newHeight} } + private static scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ + const newLength = length * scale; + const prefix = offset + const postfix = available - offset - length + const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) + return {offset: newOffset, length: newLength} + } + + /** + * Calculate where a point ends up after scaling some bound + * @param originalBounds The original bounds before scaling + * @param newBounds The bounds after scaling + * @param originalPoint The point before scaling + * @returns The point after scaling + */ public static calculateScaledPoint(originalBounds: Bounds, newBounds: Bounds, originalPoint: Point) : Point { let newX @@ -104,14 +119,6 @@ export class ScalingUtil { return {x: newX, y: newY} } - public static scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ - const newLength = length * scale; - const prefix = offset - const postfix = available - offset - length - const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) - return {offset: newOffset, length: newLength} - } - public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { // we want that the effectiveScale * desiredScale = maxScale @@ -135,11 +142,12 @@ export class ScalingUtil { return {bounds:newBounds, scale: scalingFactor} } - - public static equalBounds(a: Bounds, b: Bounds) : boolean { - return a.x == b.x && a.y == b.y && a.height == b.height && a.width == b.width - } - + /** + * Get the absolute bounds of a node as rendered, might differ from the model bounds due to node scaling + * @param element the node to determine the absolute bounds for + * @param ctx the rendering context + * @returns the absolute bounds of teh node + */ public static getAbsoluteRenderedBounds(element: SKNode, ctx: SKGraphModelRenderer) : Bounds { let current: SKNode = element; let {bounds: bounds} = element.forceNodeScaleBounds(ctx) @@ -153,6 +161,12 @@ export class ScalingUtil { return bounds; } + /** + * Determine the intersections between the bounding box of bounds and the line + * @param bounds the bounding box to intersect + * @param line the line to intersect + * @returns the intersection points (0 - 4), might contain duplicates when going thru a corner + */ public static intersections(bounds: Bounds, line: PointToPointLine ) : Point[] { const tl = bounds as Point @@ -168,6 +182,11 @@ export class ScalingUtil { return [line.intersection(top), line.intersection(bottom), line.intersection(left), line.intersection(right)].filter(p => p !== undefined).map(p => p as Point) } + /** + * + * @param point the point to calculate the distance to + * @returns Function that can be used to sort by distance to point + */ public static sort_by_dist(point: Point) : (a:Point, b:Point)=>number { return (a: Point,b: Point) => { const a_dist = Point.euclideanDistance(a, point) From 64260eb674441e54496ef8296cbbe4f47b05cabb Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 13:46:26 +0100 Subject: [PATCH 53/95] remove never read originalTitleHeight again --- packages/klighd-core/src/depth-map.ts | 2 -- packages/klighd-core/src/views-rendering.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/depth-map.ts index 0d3d2ac2..1e7ad9f1 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/depth-map.ts @@ -361,8 +361,6 @@ export class Region { parent?: Region /** All immediate child regions of this region */ children: Region[] - /** The title height as defined in the model */ - originalTitleHeight?: number /** Contains the height of the title of the region, if there is one. */ regionTitleHeight?: number /** Indentation of region title. */ diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 308b31ba..89774f34 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -1069,7 +1069,6 @@ export function renderKRendering(kRendering: KRendering, } if (providingRegion) { - providingRegion.originalTitleHeight = boundingBox.height providingRegion.regionTitleHeight = boundingBox.height } From e6030b391acdd82adb4ee12a7c9997f011a218a1 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 13:51:47 +0100 Subject: [PATCH 54/95] remove classes added for debugging --- packages/klighd-core/src/views.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 18915f0d..29e927f8 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -217,7 +217,7 @@ export class KNodeView implements IView { const titles = ctx.exitTitleScope() const childRenderings = ctx.renderChildren(node) ctx.popEffectiveZoom() - const ret = + const ret = {titles} {childRenderings} @@ -238,7 +238,7 @@ export class KNodeView implements IView { result.push(...ctx.exitTitleScope()) ctx.positions.pop() ctx.popEffectiveZoom() - const ret ={...result} + const ret ={...result} return ret } } From 0bdf18858ad3c3c765c176d29a1511266b92fe26 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 28 Jan 2022 15:41:51 +0100 Subject: [PATCH 55/95] add a comment --- packages/klighd-core/src/views-rendering.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 89774f34..3eafe238 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -389,6 +389,7 @@ export function renderLine(rendering: KPolyline, end_choice = intersections[0] } + // keep the control points of the current point if (z >= 2) { newPoints.push(points[i]) if (z == 3) { From 19835a8fd42ef1e3835a47eb1360e091ea74bc5b Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 19:38:38 +0100 Subject: [PATCH 56/95] move depth-map.ts into a sub-directory and split of region.ts --- .../src/{ => hierarchy}/depth-map.ts | 111 +----------------- packages/klighd-core/src/hierarchy/region.ts | 109 +++++++++++++++++ .../klighd-core/src/skgraph-model-renderer.ts | 2 +- .../src/update/update-depthmap-model.ts | 6 +- packages/klighd-core/src/views-rendering.tsx | 2 +- packages/klighd-core/src/views.tsx | 2 +- 6 files changed, 119 insertions(+), 113 deletions(-) rename packages/klighd-core/src/{ => hierarchy}/depth-map.ts (72%) create mode 100644 packages/klighd-core/src/hierarchy/region.ts diff --git a/packages/klighd-core/src/depth-map.ts b/packages/klighd-core/src/hierarchy/depth-map.ts similarity index 72% rename from packages/klighd-core/src/depth-map.ts rename to packages/klighd-core/src/hierarchy/depth-map.ts index 1e7ad9f1..37af72c8 100644 --- a/packages/klighd-core/src/depth-map.ts +++ b/packages/klighd-core/src/hierarchy/depth-map.ts @@ -18,10 +18,10 @@ import { KGraphElement } from "@kieler/klighd-interactive/lib/constraint-classes"; import { SChildElement, SModelRoot } from "sprotty"; import { Viewport } from "sprotty-protocol"; -import { FullDetailRelativeThreshold, FullDetailScaleThreshold } from "./options/render-options-registry"; -import { ScalingUtil } from "./scaling-util"; -import { SKGraphModelRenderer } from "./skgraph-model-renderer"; -import { isContainerRendering, isRendering, KRendering, SKNode } from "./skgraph-models"; +import { FullDetailRelativeThreshold } from "../options/render-options-registry"; +import { SKGraphModelRenderer } from "../skgraph-model-renderer"; +import { isContainerRendering, isRendering, KRendering, SKNode } from "../skgraph-models"; +import { Region } from "./region"; /** * The possible detail level of a KNode as determined by the DepthMap @@ -345,106 +345,3 @@ export class DepthMap { } } - - -/** - * Combines KNodes into regions. These correspond to child areas. A region can correspond to - * a region or a super state in the model. Also manages the boundaries, title candidates, - * tree structure of the model and application of detail level of its KNodes. - */ -export class Region { - /** The rectangle of the child area in which the region lies. */ - boundingRectangle: SKNode - /** the regions current detail level that is used by all children */ - detail: DetailLevel - /** The immediate parent region of this region. */ - parent?: Region - /** All immediate child regions of this region */ - children: Region[] - /** Contains the height of the title of the region, if there is one. */ - regionTitleHeight?: number - /** Indentation of region title. */ - regionTitleIndentation?: number - /** Constructor initializes element array for region. */ - constructor(boundingRectangle: SKNode) { - this.boundingRectangle = boundingRectangle - this.children = [] - this.detail = DetailLevel.FullDetails - } - - /** - * Checks visibility of a region with position from browser coordinates in current viewport. - * - * @param region The region in question for visibility. - * @param viewport The current viewport. - * @returns Boolean value indicating the visibility of the region in the current viewport. - */ - isInBounds(ctx: SKGraphModelRenderer): boolean { - const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx) - - const canvasBounds = this.boundingRectangle.root.canvasBounds - - return bounds.x + bounds.width - ctx.viewport.scroll.x >= 0 - && bounds.x - ctx.viewport.scroll.x <= (canvasBounds.width / ctx.viewport.zoom) - && bounds.y + bounds.height - ctx.viewport.scroll.y >= 0 - && bounds.y - ctx.viewport.scroll.y <= (canvasBounds.height / ctx.viewport.zoom) - - } - - /** - * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. - * - * @param node The KNode in question - * @param viewport The current viewport - * @returns the relative size of the KNodes shortest dimension - */ - sizeInViewport(ctx: SKGraphModelRenderer,): number { - const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx) - - const canvasBounds = this.boundingRectangle.root.canvasBounds - - const horizontal = bounds.width / (canvasBounds.width / ctx.viewport.zoom) - const vertical = bounds.height / (canvasBounds.height / ctx.viewport.zoom) - return horizontal < vertical ? horizontal : vertical - } - - /** - * Decides the appropriate detail level for a region - * based on their size in the viewport and visibility - * - * @param region The region in question - * @param viewport The current viewport - * @param relativeThreshold The full detail threshold - * @returns The appropriate detail level - */ - computeDetailLevel(ctx: SKGraphModelRenderer): DetailLevel { - - const relativeThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailRelativeThreshold) - const scaleThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailScaleThreshold) - - if (!this.isInBounds(ctx)) { - return DetailLevel.OutOfBounds - } else if (!this.parent) { - // Regions without parents should always be full detail if they are visible - return DetailLevel.FullDetails - } else { - const viewportSize = this.sizeInViewport(ctx) - - const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx, true).effective_child_zoom - // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. - if (viewportSize >= relativeThreshold || scale > scaleThreshold) { - return DetailLevel.FullDetails - } else { - return DetailLevel.MinimalDetails - } - } - } - - /** - * Applies the detail level to all elements of a region. - * @param level the detail level to apply - */ - setDetailLevel(level: DetailLevel): void { - this.detail = level - } -} diff --git a/packages/klighd-core/src/hierarchy/region.ts b/packages/klighd-core/src/hierarchy/region.ts new file mode 100644 index 00000000..e6692ca6 --- /dev/null +++ b/packages/klighd-core/src/hierarchy/region.ts @@ -0,0 +1,109 @@ +import { FullDetailRelativeThreshold, FullDetailScaleThreshold } from "../options/render-options-registry"; +import { ScalingUtil } from "../scaling-util"; +import { SKGraphModelRenderer } from "../skgraph-model-renderer"; +import { SKNode } from "../skgraph-models"; +import { DetailLevel } from "./depth-map"; + +/** + * Combines KNodes into regions. These correspond to child areas. A region can correspond to + * a region or a super state in the model. Also manages the boundaries, title candidates, + * tree structure of the model and application of detail level of its KNodes. + */ + + +export class Region { + /** The rectangle of the child area in which the region lies. */ + boundingRectangle: SKNode; + /** the regions current detail level that is used by all children */ + detail: DetailLevel; + /** The immediate parent region of this region. */ + parent?: Region; + /** All immediate child regions of this region */ + children: Region[]; + /** Contains the height of the title of the region, if there is one. */ + regionTitleHeight?: number; + /** Indentation of region title. */ + regionTitleIndentation?: number; + /** Constructor initializes element array for region. */ + constructor(boundingRectangle: SKNode) { + this.boundingRectangle = boundingRectangle; + this.children = []; + this.detail = DetailLevel.FullDetails; + } + + /** + * Checks visibility of a region with position from browser coordinates in current viewport. + * + * @param region The region in question for visibility. + * @param viewport The current viewport. + * @returns Boolean value indicating the visibility of the region in the current viewport. + */ + isInBounds(ctx: SKGraphModelRenderer): boolean { + const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx); + + const canvasBounds = this.boundingRectangle.root.canvasBounds; + + return bounds.x + bounds.width - ctx.viewport.scroll.x >= 0 + && bounds.x - ctx.viewport.scroll.x <= (canvasBounds.width / ctx.viewport.zoom) + && bounds.y + bounds.height - ctx.viewport.scroll.y >= 0 + && bounds.y - ctx.viewport.scroll.y <= (canvasBounds.height / ctx.viewport.zoom); + + } + + /** + * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. + * + * @param node The KNode in question + * @param viewport The current viewport + * @returns the relative size of the KNodes shortest dimension + */ + sizeInViewport(ctx: SKGraphModelRenderer): number { + const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx); + + const canvasBounds = this.boundingRectangle.root.canvasBounds; + + const horizontal = bounds.width / (canvasBounds.width / ctx.viewport.zoom); + const vertical = bounds.height / (canvasBounds.height / ctx.viewport.zoom); + return horizontal < vertical ? horizontal : vertical; + } + + /** + * Decides the appropriate detail level for a region + * based on their size in the viewport and visibility + * + * @param region The region in question + * @param viewport The current viewport + * @param relativeThreshold The full detail threshold + * @returns The appropriate detail level + */ + computeDetailLevel(ctx: SKGraphModelRenderer): DetailLevel { + + const relativeThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailRelativeThreshold); + const scaleThreshold = ctx.renderOptionsRegistry.getValueOrDefault(FullDetailScaleThreshold); + + if (!this.isInBounds(ctx)) { + return DetailLevel.OutOfBounds; + } else if (!this.parent) { + // Regions without parents should always be full detail if they are visible + return DetailLevel.FullDetails; + } else { + const viewportSize = this.sizeInViewport(ctx); + + const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx, true).effective_child_zoom; + // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. + if (viewportSize >= relativeThreshold || scale > scaleThreshold) { + return DetailLevel.FullDetails; + } else { + return DetailLevel.MinimalDetails; + } + } + } + + /** + * Applies the detail level to all elements of a region. + * @param level the detail level to apply + */ + setDetailLevel(level: DetailLevel): void { + this.detail = level; + } +} diff --git a/packages/klighd-core/src/skgraph-model-renderer.ts b/packages/klighd-core/src/skgraph-model-renderer.ts index 50f57415..2907cd49 100644 --- a/packages/klighd-core/src/skgraph-model-renderer.ts +++ b/packages/klighd-core/src/skgraph-model-renderer.ts @@ -19,7 +19,7 @@ import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/k import { VNode } from 'snabbdom'; import { IVNodePostprocessor, ModelRenderer, RenderingTargetKind, SParentElement, ViewRegistry } from 'sprotty'; import { Viewport } from 'sprotty-protocol'; -import { DepthMap } from './depth-map'; +import { DepthMap } from './hierarchy/depth-map'; import { RenderOptionsRegistry } from './options/render-options-registry'; import { KRenderingLibrary, EDGE_TYPE, LABEL_TYPE, NODE_TYPE, PORT_TYPE, SKGraphElement } from './skgraph-models'; diff --git a/packages/klighd-core/src/update/update-depthmap-model.ts b/packages/klighd-core/src/update/update-depthmap-model.ts index 1ae8898a..1bc8b8b4 100644 --- a/packages/klighd-core/src/update/update-depthmap-model.ts +++ b/packages/klighd-core/src/update/update-depthmap-model.ts @@ -2,7 +2,7 @@ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://rtsys.informatik.uni-kiel.de/kieler - * + * * Copyright 2021 by * + Kiel University * + Department of Computer Science @@ -19,7 +19,7 @@ import { inject, injectable } from 'inversify'; import { Command, TYPES } from 'sprotty' import { CommandExecutionContext, CommandReturn } from 'sprotty'; import { Action } from 'sprotty-protocol'; -import { DepthMap } from '../depth-map'; +import { DepthMap } from '../hierarchy/depth-map'; /** * Simple UpdateDepthMapAction fires the UpdateDepthMapModelCommand @@ -63,4 +63,4 @@ export class UpdateDepthMapModelCommand extends Command { redo(context: CommandExecutionContext): CommandReturn { return context.root } -} \ No newline at end of file +} diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 3eafe238..b006b13b 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -19,7 +19,7 @@ import { VNode } from 'snabbdom'; import { PointToPointLine, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars import { Bounds, Point } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; -import { DetailLevel } from './depth-map'; +import { DetailLevel } from './hierarchy/depth-map'; import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 29e927f8..15501c18 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -21,7 +21,7 @@ import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/k import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { DepthMap, DetailLevel, isDetailWithChildren } from './depth-map'; +import { DepthMap, DetailLevel, isDetailWithChildren } from './hierarchy/depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes } from './options/render-options-registry'; From cf237c67de38ae8f250722cba138251746ad7734 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 19:44:06 +0100 Subject: [PATCH 57/95] update doc-comments --- packages/klighd-core/src/hierarchy/depth-map.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/klighd-core/src/hierarchy/depth-map.ts b/packages/klighd-core/src/hierarchy/depth-map.ts index 37af72c8..8c22937c 100644 --- a/packages/klighd-core/src/hierarchy/depth-map.ts +++ b/packages/klighd-core/src/hierarchy/depth-map.ts @@ -160,7 +160,8 @@ export class DepthMap { /** * It is generally advised to initialize the elements from root to leaf * - * @param element The KGraphElement to initialize for DepthMap usage + * @param element The KChildElement to initialize for DepthMap usage + * @param ctx The rendering context */ public initKGraphElement(element: KChildElement, ctx: SKGraphModelRenderer): RegionIndexEntry { @@ -234,7 +235,7 @@ export class DepthMap { /** * Decides the appropriate detail level for regions based on their size in the viewport and applies that state. * - * @param viewport The current viewport. + * @param ctx The current rendering context */ updateDetailLevels(ctx: SKGraphModelRenderer): void { @@ -267,8 +268,8 @@ export class DepthMap { * Set detail level for the given region and recursively determine and update the children's detail level * * @param region The root region - * @param viewport The current viewport - * @param relativeThreshold The detail level threshold + * @param vis the detail level to apply + * @param ctx The current rendering context */ updateRegionDetailLevel(region: Region, vis: DetailWithChildren, ctx: SKGraphModelRenderer): void { region.setDetailLevel(vis) @@ -307,8 +308,7 @@ export class DepthMap { * Looks for a change in detail level for all critical regions. * Applies the level change and manages the critical regions. * - * @param viewport The current viewport - * @param relativeThreshold The full detail threshold + * @param ctx The current rendering context */ checkCriticalRegions(ctx: SKGraphModelRenderer): void { From aac85a9551667189cb578275a0958fd48daa59eb Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 19:49:44 +0100 Subject: [PATCH 58/95] update doc-comments --- packages/klighd-core/src/hierarchy/region.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/klighd-core/src/hierarchy/region.ts b/packages/klighd-core/src/hierarchy/region.ts index e6692ca6..b72b12e1 100644 --- a/packages/klighd-core/src/hierarchy/region.ts +++ b/packages/klighd-core/src/hierarchy/region.ts @@ -9,8 +9,6 @@ import { DetailLevel } from "./depth-map"; * a region or a super state in the model. Also manages the boundaries, title candidates, * tree structure of the model and application of detail level of its KNodes. */ - - export class Region { /** The rectangle of the child area in which the region lies. */ boundingRectangle: SKNode; @@ -34,8 +32,7 @@ export class Region { /** * Checks visibility of a region with position from browser coordinates in current viewport. * - * @param region The region in question for visibility. - * @param viewport The current viewport. + * @param ctx The current rendering context * @returns Boolean value indicating the visibility of the region in the current viewport. */ isInBounds(ctx: SKGraphModelRenderer): boolean { @@ -51,11 +48,10 @@ export class Region { } /** - * Compares the size of a node to the viewport and returns the smallest fraction of either height or width. + * Compares the size of a region to the viewport and returns the smallest fraction of either height or width. * - * @param node The KNode in question - * @param viewport The current viewport - * @returns the relative size of the KNodes shortest dimension + * @param ctx The current rendering context + * @returns the relative size of the region's shortest dimension */ sizeInViewport(ctx: SKGraphModelRenderer): number { const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx); @@ -71,9 +67,7 @@ export class Region { * Decides the appropriate detail level for a region * based on their size in the viewport and visibility * - * @param region The region in question - * @param viewport The current viewport - * @param relativeThreshold The full detail threshold + * @param ctx The current rendering context * @returns The appropriate detail level */ computeDetailLevel(ctx: SKGraphModelRenderer): DetailLevel { From 33e5539ee6798e44af3b0062b16391d608013bd0 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 20:05:03 +0100 Subject: [PATCH 59/95] add missing documentation for ScalingUtil --- packages/klighd-core/src/scaling-util.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 8303b8b8..9b56ca0e 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -3,7 +3,12 @@ import { SKNode } from './skgraph-models' import { SKGraphModelRenderer } from './skgraph-model-renderer' import { PointToPointLine } from 'sprotty' +/** + * A class for some helper methods used calculate the scale to be used for graphs elements and + * for actually scaling them. + */ export class ScalingUtil { + private constructor(){ // private constructor as this class should not be instantiated } @@ -119,6 +124,18 @@ export class ScalingUtil { return {x: newX, y: newY} } + /** + * Calculate upscaled bounds for a graph element + * + * @param effectiveScale the effective scale at the position the element will be rendered + * @param maxScale the maximum factor to upscale the element by + * @param childBounds the bounds of the element to scale + * @param parentBounds the bounds of the parent of the element to scale + * @param margin the margin to keep between the element and its parent as well as it and its siblings, + * it is assumed that the element does not violate this margin at normal scale (1) + * @param siblings the bounds of the elements siblings that should be taken into account while scaling + * @returns the upscaled local bounds and local scale + */ public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { // we want that the effectiveScale * desiredScale = maxScale From f075670b2562bab504952a5c4c57e1643d1e4ce1 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 20:22:32 +0100 Subject: [PATCH 60/95] add more documentation for inverseScaleDimension I am not sure how to include the derivation of the calculation in comments, I currently have it in latex in markdown at --- packages/klighd-core/src/scaling-util.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 9b56ca0e..357b1ddb 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -28,8 +28,20 @@ export class ScalingUtil { return Math.min(maxHeightScale, maxWidthScale) } + /** For some elements a and b determine the maximum scale to which they can be scaled without violating the margin in one dimension + * this is used by maxSiblingScaling to determine the max scale for width and height separately + * + * @param offset_a the offset of a + * @param length_a the length of a + * @param offset_b the offset of b + * @param length_b the length of b + * @param available the available space for both a and b + * @param margin the margin to preserve between a and b + * @returns the calculated maximum scale + */ private static inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number, margin: number): number { - // we want to find positive scale so that + + // we want to find positive scale so that the following equations hold // result_a = scaleDimension(offset_a, length_a, available, scale) // result_b = scaleDimension(offset_b, length_b, available, scale) // result_a.offset = result_b.offset + result_b.length + margin @@ -43,7 +55,6 @@ export class ScalingUtil { const result_1 = ( numerator - margin) / (fa - fb + length_b) const result_2 = (-numerator - margin) / (fb - fa + length_a) - // the scale should be at least one and at most one of the results should be positive return Math.max(result_1, result_2, 1) } From 797ba7b2e1f35d5603e437cba62b591092fe9cdc Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 20:56:49 +0100 Subject: [PATCH 61/95] fix spelling --- packages/klighd-core/src/scaling-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 357b1ddb..fb1ee1c4 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -61,9 +61,9 @@ export class ScalingUtil { /** * Calculate the maximum scale at which node and sibling retain the given margin between them - * @param node the bounds of node to scale + * @param node the bounds of the node to scale * @param parent the dimensions of the parent of the node to scale - * @param sibling a sibling og the nod to scale + * @param sibling a sibling of the node to scale * @param margin the margin node and sibling shall retain when both scaled by the result * @returns the maximum scale at which node and sibling retain margin between them */ From 378ecc1771e0bc1d7b7982bcdee05a266d42af0b Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 20:58:54 +0100 Subject: [PATCH 62/95] clarification --- packages/klighd-core/src/scaling-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index fb1ee1c4..05effa14 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -80,7 +80,7 @@ export class ScalingUtil { * Scale bounds in the specified dimensions by the specified scale * @param originalBounds the bounds to scale * @param availableSpace the space available to scale the bounds - * @param scale the scale by which to scale + * @param scale the scale factor by which to scale * @returns the scaled bounds */ private static calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { From e35b59101c362d452753d653d35a35553b512621 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 21:03:10 +0100 Subject: [PATCH 63/95] apply suggestion for better variable name --- packages/klighd-core/src/scaling-util.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 05effa14..4cbfc04a 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -121,15 +121,15 @@ export class ScalingUtil { if (originalBounds.width == 0 || newBounds.width == 0) { newX = originalPoint.x - originalBounds.x + newBounds.x } else { - const alongX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width - newX = newBounds.x + alongX * newBounds.width + const relativeX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width + newX = newBounds.x + relativeX * newBounds.width } if (originalBounds.height == 0 || newBounds.height == 0) { newY = originalPoint.y - originalBounds.y + newBounds.y - }else { - const alongY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height - newY = newBounds.y + alongY * newBounds.height + } else { + const relativeY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height + newY = newBounds.y + relativeY * newBounds.height } return {x: newX, y: newY} From 716168834231bc4cf681225bde488eb9f47d9cc9 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 21:04:35 +0100 Subject: [PATCH 64/95] use plural as this are potentially multiple --- packages/klighd-core/src/scaling-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 4cbfc04a..a3af7152 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -157,10 +157,10 @@ export class ScalingUtil { const parentScaling = ScalingUtil.maxParentScale(childBounds, parentBounds, margin) // some maximum scale at which the child does not interfere with its siblings - const siblingScaling = siblings.map((siblingBounds) => ScalingUtil.maxSiblingScale(childBounds, parentBounds, siblingBounds, margin)) + const siblingScalings = siblings.map((siblingBounds) => ScalingUtil.maxSiblingScale(childBounds, parentBounds, siblingBounds, margin)) // the most restrictive scale between our desired scale and the maximum imposed by the parent and siblings - const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScaling) + const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScalings) // we never want to shrink, should only be relevant if our desired scale is less than 1 const scalingFactor = Math.max(1, preferredScale) From 9c641a486d3bca15cd95cdb5ad1422f63ab6dc9f Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 21:10:54 +0100 Subject: [PATCH 65/95] stop calculating sibling scale if we are already at a scale of 1 --- packages/klighd-core/src/scaling-util.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index a3af7152..2529f2a5 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -149,18 +149,22 @@ export class ScalingUtil { */ public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { - // we want that the effectiveScale * desiredScale = maxScale - // so that the we effectively up scale to maxScale - const desiredScale = maxScale / effectiveScale; + // we want that the effectiveScale * desiredScale = maxScale + // so that the we effectively up scale to maxScale + const desiredScale = maxScale / effectiveScale; - // the maximum scale at which the child still fits into the parent - const parentScaling = ScalingUtil.maxParentScale(childBounds, parentBounds, margin) + // the maximum scale at which the child still fits into the parent + const parentScaling = ScalingUtil.maxParentScale(childBounds, parentBounds, margin) - // some maximum scale at which the child does not interfere with its siblings - const siblingScalings = siblings.map((siblingBounds) => ScalingUtil.maxSiblingScale(childBounds, parentBounds, siblingBounds, margin)) + let preferredScale = Math.min(desiredScale, parentScaling) - // the most restrictive scale between our desired scale and the maximum imposed by the parent and siblings - const preferredScale = Math.min(desiredScale, parentScaling, ...siblingScalings) + for (const sibling of siblings) { + if (preferredScale <= 1) { + break + } + const siblingScaling = ScalingUtil.maxSiblingScale(childBounds, parentBounds, sibling, margin) + preferredScale = Math.min(preferredScale, siblingScaling) + } // we never want to shrink, should only be relevant if our desired scale is less than 1 const scalingFactor = Math.max(1, preferredScale) From f9ee0708079bc496ea87fe5a11486c1b68c22aa9 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 22:11:10 +0100 Subject: [PATCH 66/95] calculate absolute bounds in SKNode.forceNodeScaleBounds this should address to make this easier we no longer include the viewport zoom in the effective_child_zoom which is now named effective_child_scale, usages are adjusted acordingly a bit of additional churn due to renaming struct members in the return type of forceNodeScaleBounds fix accidental use of ctx.effective_zoom in forceNodeScaleBounds - made a problem with the edge tip scaling noticeable, scaling of edge tips is now bounded by the parents size --- packages/klighd-core/src/hierarchy/region.ts | 8 +-- packages/klighd-core/src/scaling-util.ts | 21 ------- packages/klighd-core/src/skgraph-models.ts | 59 ++++++++++++++++---- packages/klighd-core/src/views-rendering.tsx | 21 ++++--- packages/klighd-core/src/views-styles.tsx | 6 +- packages/klighd-core/src/views.tsx | 2 +- 6 files changed, 68 insertions(+), 49 deletions(-) diff --git a/packages/klighd-core/src/hierarchy/region.ts b/packages/klighd-core/src/hierarchy/region.ts index b72b12e1..2f7a1df1 100644 --- a/packages/klighd-core/src/hierarchy/region.ts +++ b/packages/klighd-core/src/hierarchy/region.ts @@ -1,5 +1,4 @@ import { FullDetailRelativeThreshold, FullDetailScaleThreshold } from "../options/render-options-registry"; -import { ScalingUtil } from "../scaling-util"; import { SKGraphModelRenderer } from "../skgraph-model-renderer"; import { SKNode } from "../skgraph-models"; import { DetailLevel } from "./depth-map"; @@ -36,8 +35,7 @@ export class Region { * @returns Boolean value indicating the visibility of the region in the current viewport. */ isInBounds(ctx: SKGraphModelRenderer): boolean { - const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx); - + const {absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds; return bounds.x + bounds.width - ctx.viewport.scroll.x >= 0 @@ -54,7 +52,7 @@ export class Region { * @returns the relative size of the region's shortest dimension */ sizeInViewport(ctx: SKGraphModelRenderer): number { - const bounds = ScalingUtil.getAbsoluteRenderedBounds(this.boundingRectangle, ctx); + const {absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds; @@ -83,7 +81,7 @@ export class Region { } else { const viewportSize = this.sizeInViewport(ctx); - const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx, true).effective_child_zoom; + const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx, true).effective_child_scale * ctx.viewport.zoom; // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. if (viewportSize >= relativeThreshold || scale > scaleThreshold) { return DetailLevel.FullDetails; diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 2529f2a5..d2761703 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,6 +1,4 @@ import { Bounds, Dimension, Point} from 'sprotty-protocol' -import { SKNode } from './skgraph-models' -import { SKGraphModelRenderer } from './skgraph-model-renderer' import { PointToPointLine } from 'sprotty' /** @@ -174,25 +172,6 @@ export class ScalingUtil { return {bounds:newBounds, scale: scalingFactor} } - /** - * Get the absolute bounds of a node as rendered, might differ from the model bounds due to node scaling - * @param element the node to determine the absolute bounds for - * @param ctx the rendering context - * @returns the absolute bounds of teh node - */ - public static getAbsoluteRenderedBounds(element: SKNode, ctx: SKGraphModelRenderer) : Bounds { - let current: SKNode = element; - let {bounds: bounds} = element.forceNodeScaleBounds(ctx) - - while (current.parent instanceof SKNode) { - const parent = current.parent; - bounds = parent.localToParentRendered(bounds, ctx); - current = parent; - } - - return bounds; - } - /** * Determine the intersections between the bounding box of bounds and the line * @param bounds the bounding box to intersect diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index bff40699..5e47ae18 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -37,13 +37,32 @@ export const EDGE_TYPE = 'edge' export const PORT_TYPE = 'port' export const LABEL_TYPE = 'label' +type NodeScaleBoundsResult = { + /** + * The nodes scaled bounds relative to it's parent + */ + relative_bounds: Bounds, + /** + * the nodes scale relative to its parent and its original size + */ + relative_scale: number, + /** + * the nodes absolute scaled bounds + */ + absolute_bounds: Bounds, + /** + * the scale children inherit form this node and its ancestors, not including the viewport zoom + */ + effective_child_scale: number +} + /** * Represents the Sprotty version of its java counterpart in KLighD. */ export class SKNode extends KNode implements SKGraphElement { tooltip?: string - private _node_scaled_bounds?: {bounds: Bounds, scale: number, effective_child_zoom: number} + private _node_scaled_bounds?: NodeScaleBoundsResult hasFeature(feature: symbol): boolean { return feature === selectFeature @@ -51,7 +70,7 @@ export class SKNode extends KNode implements SKGraphElement { || feature === popupFeature } - forceNodeScaleBounds(ctx: SKGraphModelRenderer, force = false): {bounds: Bounds, scale: number, effective_child_zoom: number}{ + forceNodeScaleBounds(ctx: SKGraphModelRenderer, force = false): NodeScaleBoundsResult{ if (force || this._node_scaled_bounds === undefined) { const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); @@ -62,13 +81,31 @@ export class SKNode extends KNode implements SKGraphElement { const parent_scaled = this.parent.forceNodeScaleBounds(ctx, force) - const effective_zoom = parent_scaled.effective_child_zoom + const effective_zoom = parent_scaled.effective_child_scale * ctx.viewport.zoom const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - const upscale = ScalingUtil.upscaleBounds(ctx.effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); - this._node_scaled_bounds = {...upscale, effective_child_zoom: effective_zoom * upscale.scale} + const upscale = ScalingUtil.upscaleBounds(effective_zoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); + + const abs_bounds = { + x: parent_scaled.absolute_bounds.x + upscale.bounds.x * parent_scaled.effective_child_scale, + y: parent_scaled.absolute_bounds.y + upscale.bounds.y * parent_scaled.effective_child_scale, + width: upscale.bounds.width * parent_scaled.effective_child_scale, + height: upscale.bounds.height * parent_scaled.effective_child_scale + } + + this._node_scaled_bounds = { + relative_bounds: upscale.bounds, + relative_scale: upscale.scale, + absolute_bounds: abs_bounds, + effective_child_scale: parent_scaled.effective_child_scale * upscale.scale + } } else { - this._node_scaled_bounds = {bounds : this.bounds, scale: 1, effective_child_zoom: ctx.viewport.zoom} + this._node_scaled_bounds = { + relative_bounds: this.bounds, + relative_scale: 1, + absolute_bounds: this.bounds, + effective_child_scale: 1 + } } } @@ -79,15 +116,15 @@ export class SKNode extends KNode implements SKGraphElement { localToParentRendered(point: Point | Bounds, ctx: SKGraphModelRenderer): Bounds { const scaled_bounds = this.forceNodeScaleBounds(ctx) - const result = {x: point.x * scaled_bounds.scale, y: point.y * scaled_bounds.scale, width: -1, height:-1} + const result = {x: point.x * scaled_bounds.relative_scale, y: point.y * scaled_bounds.relative_scale, width: -1, height:-1} if (isBounds(point)) { - result.width = point.width * scaled_bounds.scale - result.height = point.height * scaled_bounds.scale + result.width = point.width * scaled_bounds.relative_scale + result.height = point.height * scaled_bounds.relative_scale } - result.x += scaled_bounds.bounds.x - result.y += scaled_bounds.bounds.y + result.x += scaled_bounds.relative_bounds.x + result.y += scaled_bounds.relative_bounds.y return result } diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index b006b13b..98b4ab21 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -290,8 +290,8 @@ export function renderLine(rendering: KPolyline, const start = points[0] const end = points[points.length-1] - const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.bounds, start) - const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.bounds, end) + const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.relative_bounds, start) + const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.relative_bounds, end) let max_coord_per_point = 1 switch (rendering.type) { @@ -314,7 +314,7 @@ export function renderLine(rendering: KPolyline, const p = points[i + z - 1] if ( - Bounds.includes(s_scaled.bounds, p) + Bounds.includes(s_scaled.relative_bounds, p) ) { i+=z } else { @@ -335,7 +335,7 @@ export function renderLine(rendering: KPolyline, const edge = new PointToPointLine(prev, next) - const intersections = ScalingUtil.intersections(s_scaled.bounds, edge) + const intersections = ScalingUtil.intersections(s_scaled.relative_bounds, edge) intersections.sort(ScalingUtil.sort_by_dist(next)) @@ -357,7 +357,7 @@ export function renderLine(rendering: KPolyline, const p = points[i + z - 1] if ( - !Bounds.includes(t_scaled.bounds, p) + !Bounds.includes(t_scaled.relative_bounds, p) ) { for (let j = 0; j < z ; j++) { newPoints.push(points[i]) @@ -381,7 +381,7 @@ export function renderLine(rendering: KPolyline, const edge = new PointToPointLine(prev, next) - const intersections = ScalingUtil.intersections(t_scaled.bounds, edge) + const intersections = ScalingUtil.intersections(t_scaled.relative_bounds, edge) intersections.sort(ScalingUtil.sort_by_dist(prev)) @@ -416,8 +416,15 @@ export function renderLine(rendering: KPolyline, } const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) + const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin) + + const parent_scale = ScalingUtil.maxParentScale(boundsAndTransformation.bounds, parent.parent.bounds, margin) + + const desired_scale = target_scale / (p_scaled.effective_child_scale * context.viewport.zoom) + const preferred_scale = Math.min(desired_scale, parent_scale) + + const scale = Math.max(preferred_scale, 1) - const scale = Math.max(target_scale / p_scaled.effective_child_zoom, 1) gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + (gAttrs.transform ?? "") } diff --git a/packages/klighd-core/src/views-styles.tsx b/packages/klighd-core/src/views-styles.tsx index de47b1f6..1bd90e43 100644 --- a/packages/klighd-core/src/views-styles.tsx +++ b/packages/klighd-core/src/views-styles.tsx @@ -642,12 +642,10 @@ export function getSvgLineStyles(styles: KStyles, target: SKGraphElement, contex // The line width the requested one would have when rendered in the current zoom level. - let effectiveZoom; + let effectiveZoom = getZoom(target); if(target instanceof SChildElement && target.parent instanceof SKNode) { - effectiveZoom = target.parent.forceNodeScaleBounds(context).effective_child_zoom - } else { - effectiveZoom = getZoom(target) + effectiveZoom *= target.parent.forceNodeScaleBounds(context).effective_child_scale } const realLineWidth = lineWidth * effectiveZoom diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 15501c18..07f14757 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -123,7 +123,7 @@ export class KNodeView implements IView { // we push a new effective zoom in all cases so we can pop later without checking whether we pushed if (node.parent && performNodeScaling) { - const {bounds: newBounds, scale: scalingFactor} = node.forceNodeScaleBounds(ctx, true) + const {relative_bounds: newBounds, relative_scale: scalingFactor} = node.forceNodeScaleBounds(ctx, true) if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From 84375bdfbd707bfa9dba7f980f9fa322e71e2ddb Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 22:15:59 +0100 Subject: [PATCH 67/95] fix spelling --- packages/klighd-core/src/scaling-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index d2761703..f60e8427 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -176,7 +176,7 @@ export class ScalingUtil { * Determine the intersections between the bounding box of bounds and the line * @param bounds the bounding box to intersect * @param line the line to intersect - * @returns the intersection points (0 - 4), might contain duplicates when going thru a corner + * @returns the intersection points (0 - 4), might contain duplicates when going through a corner */ public static intersections(bounds: Bounds, line: PointToPointLine ) : Point[] { From 1c2b1c943b03cf5ccde46bd429764f129519fc90 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 22:17:40 +0100 Subject: [PATCH 68/95] remove no longer necessary localToParentRendered --- packages/klighd-core/src/skgraph-models.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 5e47ae18..99263031 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -17,7 +17,7 @@ import { KEdge, KGraphData, KGraphElement, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { boundsFeature, moveFeature, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement, SShapeElement } from 'sprotty'; -import { Point, isBounds, Bounds } from 'sprotty-protocol' +import { Point, Bounds } from 'sprotty-protocol' import { NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; import { ScalingUtil } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; @@ -113,22 +113,6 @@ export class SKNode extends KNode implements SKGraphElement { return this._node_scaled_bounds } - localToParentRendered(point: Point | Bounds, ctx: SKGraphModelRenderer): Bounds { - const scaled_bounds = this.forceNodeScaleBounds(ctx) - - const result = {x: point.x * scaled_bounds.relative_scale, y: point.y * scaled_bounds.relative_scale, width: -1, height:-1} - - if (isBounds(point)) { - result.width = point.width * scaled_bounds.relative_scale - result.height = point.height * scaled_bounds.relative_scale - } - - result.x += scaled_bounds.relative_bounds.x - result.y += scaled_bounds.relative_bounds.y - - return result - } - } /** From 7b68e03b8be0f97585e6a68af8d0a13012170214 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 21 Feb 2022 22:33:04 +0100 Subject: [PATCH 69/95] re-add comments that got lost while moving the scaling algorithm --- packages/klighd-core/src/scaling-util.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index f60e8427..7bc295dd 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -96,10 +96,22 @@ export class ScalingUtil { return {x: newX, y : newY, width: newWidth, height: newHeight} } - private static scaleDimension(offset: number, length: number, available: number, scale: number) : {offset:number, length:number}{ + /** Scale along one axis taking up space before and after the element at an equal ratio + * + * @param offset the start of the element in the scaled dimension + * @param length the length of the element in the scaled dimension + * @param available the space available in the scaled dimension + * @param scale the factor by which to scale the element + * @returns the scaled length and adjusted offset + */ + private static scaleDimension(offset: number, length: number, available: number, scale: number): { offset: number, length: number }{ + // calculate the scaled length const newLength = length * scale; + // space before the element to be scaped const prefix = offset + // space after the element to be scaled const postfix = available - offset - length + // new offset after taking space from before and after the scaled element at an equal ratio const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) return {offset: newOffset, length: newLength} } @@ -164,7 +176,7 @@ export class ScalingUtil { preferredScale = Math.min(preferredScale, siblingScaling) } - // we never want to shrink, should only be relevant if our desired scale is less than 1 + // we never want to scale down const scalingFactor = Math.max(1, preferredScale) const newBounds = ScalingUtil.calculateScaledBounds(childBounds, parentBounds, scalingFactor) From f53ef65f675d3ab9faa30b39eafc5788401b5b5e Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 22 Feb 2022 20:14:49 +0100 Subject: [PATCH 70/95] remove confused comment --- packages/klighd-core/src/views-rendering.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 98b4ab21..318e01db 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -1065,7 +1065,6 @@ export function renderKRendering(kRendering: KRendering, const titleScalingFactorOption = context.renderOptionsRegistry.getValueOrDefault(TitleScalingFactor) as number const maxScale = titleScalingFactorOption - // is the rendering at the current zoom level smaller in height than our set threshold (apparently the threshold is minimum height?) const tooSmall = context.effectiveZoom <= titleScalingFactorOption const notFullDetail = providingRegion && providingRegion.detail !== DetailLevel.FullDetails const multipleChildren = parent.children.length > 1 From 14d71aec4c8066fad9ac538194974849e5562ea5 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 22 Feb 2022 20:58:50 +0100 Subject: [PATCH 71/95] remove unnecessary spaces in path --- packages/klighd-core/src/views-rendering.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 318e01db..1895c470 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -439,29 +439,29 @@ export function renderLine(rendering: KPolyline, let path = '' switch (rendering.type) { case K_SPLINE: { - path += `M ${points[0].x},${points[0].y} ` + path += `M${points[0].x},${points[0].y}` for (let i = 1; i < points.length; i = i + 3) { const remainingPoints = points.length - i if (remainingPoints === 1) { // if one routing point is left, draw a straight line to there. - path += `L ${points[i].x},${points[i].y} ` + path += `L${points[i].x},${points[i].y}` } else if (remainingPoints === 2) { // if two routing points are left, draw a quadratic bezier curve with those two points. - path += `Q ${points[i].x},${points[i].y} ${points[i + 1].x},${points[i + 1].y} ` + path += `Q${points[i].x},${points[i].y} ${points[i + 1].x},${points[i + 1].y}` } else { // if three or more routing points are left, draw a cubic bezier curve with those points. - path += `C ${points[i].x},${points[i].y} ` + path += `C${points[i].x},${points[i].y} ` + `${points[i + 1].x},${points[i + 1].y} ` - + `${points[i + 2].x},${points[i + 2].y} ` + + `${points[i + 2].x},${points[i + 2].y}` } } break } case K_POLYLINE: // Fall through to next case. KPolylines are just KPolygons without the closing end. case K_POLYGON: { - path += `M ${points[0].x},${points[0].y} ` + path += `M${points[0].x},${points[0].y}` for (let i = 1; i < points.length; i++) { - path += `L ${points[i].x},${points[i].y} ` + path += `L${points[i].x},${points[i].y}` } if (rendering.type === K_POLYGON) { path += 'Z' @@ -473,7 +473,7 @@ export function renderLine(rendering: KPolyline, const bendRadius = (rendering as KRoundedBendsPolyline).bendRadius // now define the rounded polyline's path. - path += `M ${points[0].x},${points[0].y} ` + path += `M${points[0].x},${points[0].y}` for (let i = 1; i < points.length - 1; i++) { const p0 = points[i - 1] const p = points[i] @@ -509,11 +509,11 @@ export function renderLine(rendering: KPolyline, } // draw a line to the start of the bend point (from the last end of its bend) // and then draw the bend with the control points of the point itself and the bend end point. - path += `L ${xs},${ys} Q ${xp},${yp} ${xe},${ye} ` + path += `L${xs},${ys}Q${xp},${yp} ${xe},${ye}` } if (points.length > 1) { const lastPoint = points[points.length - 1] - path += `L ${lastPoint.x},${lastPoint.y}` + path += `L${lastPoint.x},${lastPoint.y}` } break } From 8e14cc1f4e2aa4817d59b887164b29cc6aa19e2f Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 2 Mar 2022 15:08:47 +0100 Subject: [PATCH 72/95] fix absolute bounds calculation when node scaling is off --- packages/klighd-core/src/skgraph-models.ts | 53 ++++++++++++++-------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 99263031..868f4a44 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -75,29 +75,44 @@ export class SKNode extends KNode implements SKGraphElement { if (force || this._node_scaled_bounds === undefined) { const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); - if (this.parent && this.parent instanceof SKNode && performNodeScaling) { - const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); - const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); - + if (this.parent && this.parent instanceof SKNode) { const parent_scaled = this.parent.forceNodeScaleBounds(ctx, force) - const effective_zoom = parent_scaled.effective_child_scale * ctx.viewport.zoom - const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) + if (performNodeScaling) { + const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); + const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); - const upscale = ScalingUtil.upscaleBounds(effective_zoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); - const abs_bounds = { - x: parent_scaled.absolute_bounds.x + upscale.bounds.x * parent_scaled.effective_child_scale, - y: parent_scaled.absolute_bounds.y + upscale.bounds.y * parent_scaled.effective_child_scale, - width: upscale.bounds.width * parent_scaled.effective_child_scale, - height: upscale.bounds.height * parent_scaled.effective_child_scale - } + const effective_zoom = parent_scaled.effective_child_scale * ctx.viewport.zoom + const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - this._node_scaled_bounds = { - relative_bounds: upscale.bounds, - relative_scale: upscale.scale, - absolute_bounds: abs_bounds, - effective_child_scale: parent_scaled.effective_child_scale * upscale.scale + const upscale = ScalingUtil.upscaleBounds(effective_zoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); + + let abs_bounds = { + x: upscale.bounds.x * parent_scaled.effective_child_scale, + y: upscale.bounds.y * parent_scaled.effective_child_scale, + width: upscale.bounds.width * parent_scaled.effective_child_scale, + height: upscale.bounds.height * parent_scaled.effective_child_scale + } + + abs_bounds = Bounds.translate(abs_bounds, parent_scaled.absolute_bounds) + + this._node_scaled_bounds = { + relative_bounds: upscale.bounds, + relative_scale: upscale.scale, + absolute_bounds: abs_bounds, + effective_child_scale: parent_scaled.effective_child_scale * upscale.scale + } + + } else { + const abs_bounds = Bounds.translate(this.bounds, parent_scaled.absolute_bounds) + + this._node_scaled_bounds = { + relative_bounds: this.bounds, + relative_scale: 1, + absolute_bounds: abs_bounds, + effective_child_scale: 1 + } } } else { this._node_scaled_bounds = { @@ -107,9 +122,7 @@ export class SKNode extends KNode implements SKGraphElement { effective_child_scale: 1 } } - } - return this._node_scaled_bounds } From 01d3d26a543c30dbada7daaee14007e5939cc405 Mon Sep 17 00:00:00 2001 From: Skgland Date: Wed, 2 Mar 2022 17:57:16 +0100 Subject: [PATCH 73/95] reduce the amount of cache invalidation instead of recursively forcing an update with the force flag parameter we check whether one of the input values has changed --- .../klighd-core/src/hierarchy/depth-map.ts | 4 +-- packages/klighd-core/src/hierarchy/region.ts | 6 ++-- packages/klighd-core/src/skgraph-models.ts | 36 +++++++++++++------ packages/klighd-core/src/views.tsx | 2 +- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/packages/klighd-core/src/hierarchy/depth-map.ts b/packages/klighd-core/src/hierarchy/depth-map.ts index 8c22937c..68fbcd6c 100644 --- a/packages/klighd-core/src/hierarchy/depth-map.ts +++ b/packages/klighd-core/src/hierarchy/depth-map.ts @@ -176,7 +176,7 @@ export class DepthMap { entry = { providingRegion: providedRegion, containingRegion: undefined } - element.forceNodeScaleBounds(ctx, true) + element.forceNodeScaleBounds(ctx) providedRegion.detail = providedRegion.computeDetailLevel(ctx) this.rootRegions.push(providedRegion) @@ -196,7 +196,7 @@ export class DepthMap { entry.providingRegion.parent = entry.containingRegion entry.containingRegion.children.push(entry.providingRegion); - element.forceNodeScaleBounds(ctx, true) + element.forceNodeScaleBounds(ctx) entry.providingRegion.detail = entry.providingRegion.computeDetailLevel(ctx) } diff --git a/packages/klighd-core/src/hierarchy/region.ts b/packages/klighd-core/src/hierarchy/region.ts index 2f7a1df1..8e4262de 100644 --- a/packages/klighd-core/src/hierarchy/region.ts +++ b/packages/klighd-core/src/hierarchy/region.ts @@ -35,7 +35,7 @@ export class Region { * @returns Boolean value indicating the visibility of the region in the current viewport. */ isInBounds(ctx: SKGraphModelRenderer): boolean { - const {absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) + const { absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds; return bounds.x + bounds.width - ctx.viewport.scroll.x >= 0 @@ -52,7 +52,7 @@ export class Region { * @returns the relative size of the region's shortest dimension */ sizeInViewport(ctx: SKGraphModelRenderer): number { - const {absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) + const { absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds; @@ -81,7 +81,7 @@ export class Region { } else { const viewportSize = this.sizeInViewport(ctx); - const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx, true).effective_child_scale * ctx.viewport.zoom; + const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx).effective_child_scale * ctx.viewport.zoom; // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. if (viewportSize >= relativeThreshold || scale > scaleThreshold) { return DetailLevel.FullDetails; diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index 868f4a44..ebe61579 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -19,7 +19,7 @@ import { KEdge, KGraphData, KGraphElement, KNode } from '@kieler/klighd-interact import { boundsFeature, moveFeature, popupFeature, RectangularPort, RGBColor, selectFeature, SLabel, SModelElement, SShapeElement } from 'sprotty'; import { Point, Bounds } from 'sprotty-protocol' import { NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; -import { ScalingUtil } from './scaling-util'; +import { ScalingUtil } from './scaling-util'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; /** @@ -62,6 +62,10 @@ type NodeScaleBoundsResult = { export class SKNode extends KNode implements SKGraphElement { tooltip?: string + private _scale_nodes_cache_key?: boolean + private _min_scale_cache_key?: number + private _zoom_cache_key?: number + private _margin_key?: boolean private _node_scaled_bounds?: NodeScaleBoundsResult hasFeature(feature: symbol): boolean { @@ -70,17 +74,21 @@ export class SKNode extends KNode implements SKGraphElement { || feature === popupFeature } - forceNodeScaleBounds(ctx: SKGraphModelRenderer, force = false): NodeScaleBoundsResult{ + forceNodeScaleBounds(ctx: SKGraphModelRenderer): NodeScaleBoundsResult { + const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); + const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); + const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); - if (force || this._node_scaled_bounds === undefined) { - const performNodeScaling = ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes); + const needsUpdate = this._scale_nodes_cache_key !== performNodeScaling + || this._margin_key !== margin + || this._min_scale_cache_key !== minNodeScale + || this._zoom_cache_key !== ctx.viewport.zoom + if (this._node_scaled_bounds === undefined || needsUpdate) { if (this.parent && this.parent instanceof SKNode) { - const parent_scaled = this.parent.forceNodeScaleBounds(ctx, force) + const parent_scaled = this.parent.forceNodeScaleBounds(ctx) if (performNodeScaling) { - const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); - const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); const effective_zoom = parent_scaled.effective_child_scale * ctx.viewport.zoom @@ -89,9 +97,9 @@ export class SKNode extends KNode implements SKGraphElement { const upscale = ScalingUtil.upscaleBounds(effective_zoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); let abs_bounds = { - x: upscale.bounds.x * parent_scaled.effective_child_scale, - y: upscale.bounds.y * parent_scaled.effective_child_scale, - width: upscale.bounds.width * parent_scaled.effective_child_scale, + x: upscale.bounds.x * parent_scaled.effective_child_scale, + y: upscale.bounds.y * parent_scaled.effective_child_scale, + width: upscale.bounds.width * parent_scaled.effective_child_scale, height: upscale.bounds.height * parent_scaled.effective_child_scale } @@ -123,6 +131,12 @@ export class SKNode extends KNode implements SKGraphElement { } } } + + this._scale_nodes_cache_key = performNodeScaling + this._margin_key = margin + this._zoom_cache_key = ctx.viewport.zoom + this._min_scale_cache_key = minNodeScale + return this._node_scaled_bounds } @@ -164,7 +178,7 @@ export class SKLabel extends SLabel implements SKGraphElement { export class SKEdge extends KEdge implements SKGraphElement { tooltip?: string - moved_ends_by?: {start: Point, end: Point} + moved_ends_by?: { start: Point, end: Point } hasFeature(feature: symbol): boolean { return feature === selectFeature || feature === popupFeature diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 07f14757..6da92825 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -123,7 +123,7 @@ export class KNodeView implements IView { // we push a new effective zoom in all cases so we can pop later without checking whether we pushed if (node.parent && performNodeScaling) { - const {relative_bounds: newBounds, relative_scale: scalingFactor} = node.forceNodeScaleBounds(ctx, true) + const {relative_bounds: newBounds, relative_scale: scalingFactor} = node.forceNodeScaleBounds(ctx) if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From 8e4a43cc3698662f3dc6c03cf57c5c07c680880f Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 12 Mar 2022 21:45:21 +0100 Subject: [PATCH 74/95] improve naming of inverseScaleDimension and expand comment should resolve https://github.com/kieler/klighd-vscode/pull/59#discussion_r813068524 --- packages/klighd-core/src/scaling-util.ts | 86 +++++++++++++----------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 7bc295dd..14353fc4 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,4 +1,4 @@ -import { Bounds, Dimension, Point} from 'sprotty-protocol' +import { Bounds, Dimension, Point } from 'sprotty-protocol' import { PointToPointLine } from 'sprotty' /** @@ -7,7 +7,7 @@ import { PointToPointLine } from 'sprotty' */ export class ScalingUtil { - private constructor(){ + private constructor() { // private constructor as this class should not be instantiated } @@ -21,7 +21,7 @@ export class ScalingUtil { // the maximum scale that keeps the node in bounds height wise const maxHeightScale = (parent.height - 2 * margin) / node.height // the maximum scale that keeps the node in bounds width wise - const maxWidthScale = (parent.width - 2 * margin) / node.width + const maxWidthScale = (parent.width - 2 * margin) / node.width return Math.min(maxHeightScale, maxWidthScale) } @@ -37,20 +37,30 @@ export class ScalingUtil { * @param margin the margin to preserve between a and b * @returns the calculated maximum scale */ - private static inverseScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number, margin: number): number { - - // we want to find positive scale so that the following equations hold + private static maxSiblingScaleDimension(offset_a: number, length_a: number, offset_b: number, length_b: number, available: number, margin: number): number { + + // There are three scenarios that can happen + // either a is before b, + // b is before a, + // or a and b overlap + // + // In the last case we can just use one as we never want a scale below one and we take the maximum of both dimensions outside this function + // In the other two cases we need to solve one of the following two equations for scale + // result_a.offset = result_b.offset + result_b.length + margin + // result_b.offset = result_a.offset + result_a.length + margin + // both with // result_a = scaleDimension(offset_a, length_a, available, scale) // result_b = scaleDimension(offset_b, length_b, available, scale) - // result_a.offset = result_b.offset + result_b.length + margin - // || result_b.offset = result_a.offset + result_a.length + margin + // + // result_a and result_b should only be positive and larger than one if that is the case present + // below we have solve both equations and take the maximum of the solution to all three cases as the result. const fa = (offset_a * length_a) / (available - length_a) const fb = (offset_b * length_b) / (available - length_b) const numerator = offset_a + fa - offset_b - fb - const result_1 = ( numerator - margin) / (fa - fb + length_b) + const result_1 = (numerator - margin) / (fa - fb + length_b) const result_2 = (-numerator - margin) / (fb - fa + length_a) return Math.max(result_1, result_2, 1) @@ -65,11 +75,11 @@ export class ScalingUtil { * @param margin the margin node and sibling shall retain when both scaled by the result * @returns the maximum scale at which node and sibling retain margin between them */ - public static maxSiblingScale(node: Bounds, parent: Dimension, sibling: Bounds, margin: number) : number { + public static maxSiblingScale(node: Bounds, parent: Dimension, sibling: Bounds, margin: number): number { // calculate the scale for each dimension at which we reach our sibling - const result_1 = ScalingUtil.inverseScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) - const result_2 = ScalingUtil.inverseScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height, margin) + const result_1 = ScalingUtil.maxSiblingScaleDimension(node.x, node.width, sibling.x, sibling.width, parent.width, margin) + const result_2 = ScalingUtil.maxSiblingScaleDimension(node.y, node.height, sibling.y, sibling.height, parent.height, margin) // take the max as that which ever is further is relevant for bounding us, but should be at least 1 return Math.max(result_1, result_2, 1) @@ -81,19 +91,19 @@ export class ScalingUtil { * @param scale the scale factor by which to scale * @returns the scaled bounds */ - private static calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number) : Bounds { + private static calculateScaledBounds(originalBounds: Bounds, availableSpace: Dimension, scale: number): Bounds { const originalWidth = originalBounds.width const originalHeight = originalBounds.height const originalX = originalBounds.x const originalY = originalBounds.y // Calculate the new x offset and width: - const {length: newWidth, offset: newX} = ScalingUtil.scaleDimension(originalX, originalWidth, availableSpace.width, scale) + const { length: newWidth, offset: newX } = ScalingUtil.scaleDimension(originalX, originalWidth, availableSpace.width, scale) // Same for y offset and height - const {length: newHeight, offset: newY} = ScalingUtil.scaleDimension(originalY, originalHeight, availableSpace.height, scale) + const { length: newHeight, offset: newY } = ScalingUtil.scaleDimension(originalY, originalHeight, availableSpace.height, scale) - return {x: newX, y : newY, width: newWidth, height: newHeight} + return { x: newX, y: newY, width: newWidth, height: newHeight } } /** Scale along one axis taking up space before and after the element at an equal ratio @@ -104,7 +114,7 @@ export class ScalingUtil { * @param scale the factor by which to scale the element * @returns the scaled length and adjusted offset */ - private static scaleDimension(offset: number, length: number, available: number, scale: number): { offset: number, length: number }{ + private static scaleDimension(offset: number, length: number, available: number, scale: number): { offset: number, length: number } { // calculate the scaled length const newLength = length * scale; // space before the element to be scaped @@ -113,7 +123,7 @@ export class ScalingUtil { const postfix = available - offset - length // new offset after taking space from before and after the scaled element at an equal ratio const newOffset = offset - prefix * (newLength - length) / (prefix + postfix) - return {offset: newOffset, length: newLength} + return { offset: newOffset, length: newLength } } /** @@ -123,7 +133,7 @@ export class ScalingUtil { * @param originalPoint The point before scaling * @returns The point after scaling */ - public static calculateScaledPoint(originalBounds: Bounds, newBounds: Bounds, originalPoint: Point) : Point { + public static calculateScaledPoint(originalBounds: Bounds, newBounds: Bounds, originalPoint: Point): Point { let newX let newY @@ -131,18 +141,18 @@ export class ScalingUtil { if (originalBounds.width == 0 || newBounds.width == 0) { newX = originalPoint.x - originalBounds.x + newBounds.x } else { - const relativeX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width + const relativeX = originalBounds.width == 0 ? 0 : (originalPoint.x - originalBounds.x) / originalBounds.width newX = newBounds.x + relativeX * newBounds.width } if (originalBounds.height == 0 || newBounds.height == 0) { newY = originalPoint.y - originalBounds.y + newBounds.y } else { - const relativeY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height - newY = newBounds.y + relativeY * newBounds.height + const relativeY = originalBounds.height == 0 ? 0 : (originalPoint.y - originalBounds.y) / originalBounds.height + newY = newBounds.y + relativeY * newBounds.height } - return {x: newX, y: newY} + return { x: newX, y: newY } } /** @@ -157,7 +167,7 @@ export class ScalingUtil { * @param siblings the bounds of the elements siblings that should be taken into account while scaling * @returns the upscaled local bounds and local scale */ - public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin:number, siblings: Bounds[] = []) : {bounds: Bounds, scale: number} { + public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin: number, siblings: Bounds[] = []): { bounds: Bounds, scale: number } { // we want that the effectiveScale * desiredScale = maxScale // so that the we effectively up scale to maxScale @@ -176,12 +186,12 @@ export class ScalingUtil { preferredScale = Math.min(preferredScale, siblingScaling) } - // we never want to scale down - const scalingFactor = Math.max(1, preferredScale) + // we never want to scale down + const scalingFactor = Math.max(1, preferredScale) - const newBounds = ScalingUtil.calculateScaledBounds(childBounds, parentBounds, scalingFactor) + const newBounds = ScalingUtil.calculateScaledBounds(childBounds, parentBounds, scalingFactor) - return {bounds:newBounds, scale: scalingFactor} + return { bounds: newBounds, scale: scalingFactor } } /** @@ -190,17 +200,17 @@ export class ScalingUtil { * @param line the line to intersect * @returns the intersection points (0 - 4), might contain duplicates when going through a corner */ - public static intersections(bounds: Bounds, line: PointToPointLine ) : Point[] { + public static intersections(bounds: Bounds, line: PointToPointLine): Point[] { - const tl = bounds as Point - const tr = Point.add(bounds, {x: 0 , y: bounds.height}) - const bl = Point.add(bounds, {x: bounds.width, y: 0 }) - const br = Point.add(bounds, {x: bounds.width, y: bounds.height}) + const tl = bounds as Point + const tr = Point.add(bounds, { x: 0, y: bounds.height }) + const bl = Point.add(bounds, { x: bounds.width, y: 0 }) + const br = Point.add(bounds, { x: bounds.width, y: bounds.height }) - const top = new PointToPointLine(tl, tr) + const top = new PointToPointLine(tl, tr) const bottom = new PointToPointLine(bl, br) - const left = new PointToPointLine(tl, bl) - const right = new PointToPointLine(tr, br) + const left = new PointToPointLine(tl, bl) + const right = new PointToPointLine(tr, br) return [line.intersection(top), line.intersection(bottom), line.intersection(left), line.intersection(right)].filter(p => p !== undefined).map(p => p as Point) } @@ -210,8 +220,8 @@ export class ScalingUtil { * @param point the point to calculate the distance to * @returns Function that can be used to sort by distance to point */ - public static sort_by_dist(point: Point) : (a:Point, b:Point)=>number { - return (a: Point,b: Point) => { + public static sort_by_dist(point: Point): (a: Point, b: Point) => number { + return (a: Point, b: Point) => { const a_dist = Point.euclideanDistance(a, point) const b_dist = Point.euclideanDistance(b, point) if (a_dist > b_dist) { From 41a4ce357810536abfbb5d4dbeb3b22819f4609f Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 12 Mar 2022 22:13:07 +0100 Subject: [PATCH 75/95] add the minor optimization mentioned in https://github.com/kieler/klighd-vscode/pull/59#discussion_r813075452 pull the scale calculation out into its own function for easier short circuiting --- packages/klighd-core/src/scaling-util.ts | 39 ++++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 14353fc4..5337e703 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -155,24 +155,15 @@ export class ScalingUtil { return { x: newX, y: newY } } - /** - * Calculate upscaled bounds for a graph element - * - * @param effectiveScale the effective scale at the position the element will be rendered - * @param maxScale the maximum factor to upscale the element by - * @param childBounds the bounds of the element to scale - * @param parentBounds the bounds of the parent of the element to scale - * @param margin the margin to keep between the element and its parent as well as it and its siblings, - * it is assumed that the element does not violate this margin at normal scale (1) - * @param siblings the bounds of the elements siblings that should be taken into account while scaling - * @returns the upscaled local bounds and local scale - */ - public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin: number, siblings: Bounds[] = []): { bounds: Bounds, scale: number } { - + static calculateUpscale(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin: number, siblings: Bounds[] = []): number { // we want that the effectiveScale * desiredScale = maxScale // so that the we effectively up scale to maxScale const desiredScale = maxScale / effectiveScale; + if (desiredScale < 1) { + return 1; + } + // the maximum scale at which the child still fits into the parent const parentScaling = ScalingUtil.maxParentScale(childBounds, parentBounds, margin) @@ -180,15 +171,31 @@ export class ScalingUtil { for (const sibling of siblings) { if (preferredScale <= 1) { - break + return 1 } const siblingScaling = ScalingUtil.maxSiblingScale(childBounds, parentBounds, sibling, margin) preferredScale = Math.min(preferredScale, siblingScaling) } // we never want to scale down - const scalingFactor = Math.max(1, preferredScale) + return Math.max(1, preferredScale) + } + + /** + * Calculate upscaled bounds for a graph element + * + * @param effectiveScale the effective scale at the position the element will be rendered + * @param maxScale the maximum factor to upscale the element by + * @param childBounds the bounds of the element to scale + * @param parentBounds the bounds of the parent of the element to scale + * @param margin the margin to keep between the element and its parent as well as it and its siblings, + * it is assumed that the element does not violate this margin at normal scale (1) + * @param siblings the bounds of the elements siblings that should be taken into account while scaling + * @returns the upscaled local bounds and local scale + */ + public static upscaleBounds(effectiveScale: number, maxScale: number, childBounds: Bounds, parentBounds: Dimension, margin: number, siblings: Bounds[] = []): { bounds: Bounds, scale: number } { + const scalingFactor = ScalingUtil.calculateUpscale(effectiveScale, maxScale, childBounds, parentBounds, margin, siblings) const newBounds = ScalingUtil.calculateScaledBounds(childBounds, parentBounds, scalingFactor) return { bounds: newBounds, scale: scalingFactor } From 66abdf985fb308e8e9b7eb89db2a590b023048a7 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 12 Mar 2022 22:20:47 +0100 Subject: [PATCH 76/95] fix some not camel case names --- packages/klighd-core/src/hierarchy/region.ts | 6 +- packages/klighd-core/src/skgraph-models.ts | 82 ++++++++++---------- packages/klighd-core/src/views-rendering.tsx | 14 ++-- packages/klighd-core/src/views-styles.tsx | 2 +- packages/klighd-core/src/views.tsx | 2 +- 5 files changed, 53 insertions(+), 53 deletions(-) diff --git a/packages/klighd-core/src/hierarchy/region.ts b/packages/klighd-core/src/hierarchy/region.ts index 8e4262de..c7a88f9f 100644 --- a/packages/klighd-core/src/hierarchy/region.ts +++ b/packages/klighd-core/src/hierarchy/region.ts @@ -35,7 +35,7 @@ export class Region { * @returns Boolean value indicating the visibility of the region in the current viewport. */ isInBounds(ctx: SKGraphModelRenderer): boolean { - const { absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) + const { absoluteBounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds; return bounds.x + bounds.width - ctx.viewport.scroll.x >= 0 @@ -52,7 +52,7 @@ export class Region { * @returns the relative size of the region's shortest dimension */ sizeInViewport(ctx: SKGraphModelRenderer): number { - const { absolute_bounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) + const { absoluteBounds: bounds } = this.boundingRectangle.forceNodeScaleBounds(ctx) const canvasBounds = this.boundingRectangle.root.canvasBounds; @@ -81,7 +81,7 @@ export class Region { } else { const viewportSize = this.sizeInViewport(ctx); - const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx).effective_child_scale * ctx.viewport.zoom; + const scale = (this.boundingRectangle.parent as SKNode).forceNodeScaleBounds(ctx).effectiveChildScale * ctx.viewport.zoom; // change to full detail when relative size threshold is reached or the scaling within the region is big enough to be readable. if (viewportSize >= relativeThreshold || scale > scaleThreshold) { return DetailLevel.FullDetails; diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index ebe61579..d98fbc78 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -41,19 +41,19 @@ type NodeScaleBoundsResult = { /** * The nodes scaled bounds relative to it's parent */ - relative_bounds: Bounds, + relativeBounds: Bounds, /** * the nodes scale relative to its parent and its original size */ - relative_scale: number, + relativeScale: number, /** * the nodes absolute scaled bounds */ - absolute_bounds: Bounds, + absoluteBounds: Bounds, /** * the scale children inherit form this node and its ancestors, not including the viewport zoom */ - effective_child_scale: number + effectiveChildScale: number } /** @@ -62,11 +62,11 @@ type NodeScaleBoundsResult = { export class SKNode extends KNode implements SKGraphElement { tooltip?: string - private _scale_nodes_cache_key?: boolean - private _min_scale_cache_key?: number - private _zoom_cache_key?: number - private _margin_key?: boolean - private _node_scaled_bounds?: NodeScaleBoundsResult + private _scaleNodesCacheKey?: boolean + private _minScaleCacheKey?: number + private _zoomCacheKey?: number + private _marginKey?: boolean + private _nodeScaledBounds?: NodeScaleBoundsResult hasFeature(feature: symbol): boolean { return feature === selectFeature @@ -79,65 +79,65 @@ export class SKNode extends KNode implements SKGraphElement { const minNodeScale = ctx.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor); const margin = ctx.renderOptionsRegistry.getValueOrDefault(NodeMargin); - const needsUpdate = this._scale_nodes_cache_key !== performNodeScaling - || this._margin_key !== margin - || this._min_scale_cache_key !== minNodeScale - || this._zoom_cache_key !== ctx.viewport.zoom + const needsUpdate = this._scaleNodesCacheKey !== performNodeScaling + || this._marginKey !== margin + || this._minScaleCacheKey !== minNodeScale + || this._zoomCacheKey !== ctx.viewport.zoom - if (this._node_scaled_bounds === undefined || needsUpdate) { + if (this._nodeScaledBounds === undefined || needsUpdate) { if (this.parent && this.parent instanceof SKNode) { const parent_scaled = this.parent.forceNodeScaleBounds(ctx) if (performNodeScaling) { - const effective_zoom = parent_scaled.effective_child_scale * ctx.viewport.zoom + const effective_zoom = parent_scaled.effectiveChildScale * ctx.viewport.zoom const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) const upscale = ScalingUtil.upscaleBounds(effective_zoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); let abs_bounds = { - x: upscale.bounds.x * parent_scaled.effective_child_scale, - y: upscale.bounds.y * parent_scaled.effective_child_scale, - width: upscale.bounds.width * parent_scaled.effective_child_scale, - height: upscale.bounds.height * parent_scaled.effective_child_scale + x: upscale.bounds.x * parent_scaled.effectiveChildScale, + y: upscale.bounds.y * parent_scaled.effectiveChildScale, + width: upscale.bounds.width * parent_scaled.effectiveChildScale, + height: upscale.bounds.height * parent_scaled.effectiveChildScale } - abs_bounds = Bounds.translate(abs_bounds, parent_scaled.absolute_bounds) + abs_bounds = Bounds.translate(abs_bounds, parent_scaled.absoluteBounds) - this._node_scaled_bounds = { - relative_bounds: upscale.bounds, - relative_scale: upscale.scale, - absolute_bounds: abs_bounds, - effective_child_scale: parent_scaled.effective_child_scale * upscale.scale + this._nodeScaledBounds = { + relativeBounds: upscale.bounds, + relativeScale: upscale.scale, + absoluteBounds: abs_bounds, + effectiveChildScale: parent_scaled.effectiveChildScale * upscale.scale } } else { - const abs_bounds = Bounds.translate(this.bounds, parent_scaled.absolute_bounds) + const abs_bounds = Bounds.translate(this.bounds, parent_scaled.absoluteBounds) - this._node_scaled_bounds = { - relative_bounds: this.bounds, - relative_scale: 1, - absolute_bounds: abs_bounds, - effective_child_scale: 1 + this._nodeScaledBounds = { + relativeBounds: this.bounds, + relativeScale: 1, + absoluteBounds: abs_bounds, + effectiveChildScale: 1 } } } else { - this._node_scaled_bounds = { - relative_bounds: this.bounds, - relative_scale: 1, - absolute_bounds: this.bounds, - effective_child_scale: 1 + this._nodeScaledBounds = { + relativeBounds: this.bounds, + relativeScale: 1, + absoluteBounds: this.bounds, + effectiveChildScale: 1 } } } - this._scale_nodes_cache_key = performNodeScaling - this._margin_key = margin - this._zoom_cache_key = ctx.viewport.zoom - this._min_scale_cache_key = minNodeScale + this._scaleNodesCacheKey = performNodeScaling + this._marginKey = margin + this._zoomCacheKey = ctx.viewport.zoom + this._minScaleCacheKey = minNodeScale - return this._node_scaled_bounds + return this._nodeScaledBounds } } diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index 1895c470..b25329c9 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -290,8 +290,8 @@ export function renderLine(rendering: KPolyline, const start = points[0] const end = points[points.length-1] - const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.relative_bounds, start) - const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.relative_bounds, end) + const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.relativeBounds, start) + const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.relativeBounds, end) let max_coord_per_point = 1 switch (rendering.type) { @@ -314,7 +314,7 @@ export function renderLine(rendering: KPolyline, const p = points[i + z - 1] if ( - Bounds.includes(s_scaled.relative_bounds, p) + Bounds.includes(s_scaled.relativeBounds, p) ) { i+=z } else { @@ -335,7 +335,7 @@ export function renderLine(rendering: KPolyline, const edge = new PointToPointLine(prev, next) - const intersections = ScalingUtil.intersections(s_scaled.relative_bounds, edge) + const intersections = ScalingUtil.intersections(s_scaled.relativeBounds, edge) intersections.sort(ScalingUtil.sort_by_dist(next)) @@ -357,7 +357,7 @@ export function renderLine(rendering: KPolyline, const p = points[i + z - 1] if ( - !Bounds.includes(t_scaled.relative_bounds, p) + !Bounds.includes(t_scaled.relativeBounds, p) ) { for (let j = 0; j < z ; j++) { newPoints.push(points[i]) @@ -381,7 +381,7 @@ export function renderLine(rendering: KPolyline, const edge = new PointToPointLine(prev, next) - const intersections = ScalingUtil.intersections(t_scaled.relative_bounds, edge) + const intersections = ScalingUtil.intersections(t_scaled.relativeBounds, edge) intersections.sort(ScalingUtil.sort_by_dist(prev)) @@ -420,7 +420,7 @@ export function renderLine(rendering: KPolyline, const parent_scale = ScalingUtil.maxParentScale(boundsAndTransformation.bounds, parent.parent.bounds, margin) - const desired_scale = target_scale / (p_scaled.effective_child_scale * context.viewport.zoom) + const desired_scale = target_scale / (p_scaled.effectiveChildScale * context.viewport.zoom) const preferred_scale = Math.min(desired_scale, parent_scale) const scale = Math.max(preferred_scale, 1) diff --git a/packages/klighd-core/src/views-styles.tsx b/packages/klighd-core/src/views-styles.tsx index 1bd90e43..a4bbb2d8 100644 --- a/packages/klighd-core/src/views-styles.tsx +++ b/packages/klighd-core/src/views-styles.tsx @@ -645,7 +645,7 @@ export function getSvgLineStyles(styles: KStyles, target: SKGraphElement, contex let effectiveZoom = getZoom(target); if(target instanceof SChildElement && target.parent instanceof SKNode) { - effectiveZoom *= target.parent.forceNodeScaleBounds(context).effective_child_scale + effectiveZoom *= target.parent.forceNodeScaleBounds(context).effectiveChildScale } const realLineWidth = lineWidth * effectiveZoom diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 6da92825..845a2d65 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -123,7 +123,7 @@ export class KNodeView implements IView { // we push a new effective zoom in all cases so we can pop later without checking whether we pushed if (node.parent && performNodeScaling) { - const {relative_bounds: newBounds, relative_scale: scalingFactor} = node.forceNodeScaleBounds(ctx) + const {relativeBounds: newBounds, relativeScale: scalingFactor} = node.forceNodeScaleBounds(ctx) if(Number.isNaN(newBounds.x) || Number.isNaN(newBounds.y) || Number.isNaN(scalingFactor)){ // On initial load node.parent.bounds has all fields as 0 causing a division by 0 From da982d4e838e1f8b94f15704fb01a85f7b12fa6d Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 12 Mar 2022 23:00:42 +0100 Subject: [PATCH 77/95] extract line scaling into a function --- packages/klighd-core/src/scaling-util.ts | 175 ++++++++++++++++++- packages/klighd-core/src/views-rendering.tsx | 159 +---------------- 2 files changed, 178 insertions(+), 156 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 5337e703..d4ced9a0 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -1,5 +1,10 @@ -import { Bounds, Dimension, Point } from 'sprotty-protocol' + +import { NodeMargin, NodeScalingFactor } from './options/render-options-registry'; +import { KPolyline, K_POLYGON, K_POLYLINE, K_ROUNDED_BENDS_POLYLINE, K_SPLINE, SKEdge, SKNode } from './skgraph-models'; +import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { PointToPointLine } from 'sprotty' +import { Bounds, Dimension, Point } from 'sprotty-protocol' +import { BoundsAndTransformation } from './views-common'; /** * A class for some helper methods used calculate the scale to be used for graphs elements and @@ -240,4 +245,172 @@ export class ScalingUtil { } } } + + /** + * For lines calculate new line points to account for node scaling + * For polygons (arrow head) adjusts position to new line end point and perform scaling + */ + public static performLineScaling(rendering: KPolyline, + edge: SKEdge, + parent: SKNode, + source: SKNode, + target: SKNode, + boundsAndTransformation: BoundsAndTransformation, + context: SKGraphModelRenderer, + gAttrs: { transform?: string | undefined }, + points: Point[] + ): Point[] { + const s = source + const t = target + const s_scaled = s.forceNodeScaleBounds(context) + const t_scaled = t.forceNodeScaleBounds(context) + const p_scaled = parent.forceNodeScaleBounds(context); + + const start = points[0] + const end = points[points.length - 1] + + const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.relativeBounds, start) + const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.relativeBounds, end) + + let max_coord_per_point = 1 + switch (rendering.type) { + case K_SPLINE: + max_coord_per_point = 3 + // fallthrough + case K_ROUNDED_BENDS_POLYLINE: + case K_POLYLINE: { + + const newPoints = [] + + let i = 1 + + // skip points in the start node + while (i < points.length) { + const remainingPoints = points.length - i + + const z = Math.min(max_coord_per_point, remainingPoints) + + const p = points[i + z - 1] + + if ( + Bounds.includes(s_scaled.relativeBounds, p) + ) { + i += z + } else { + break + } + } + + // determine new start point + let start_choice = scaled_start + + if (i < points.length) { + const remainingPoints = points.length - i + + const z = Math.min(max_coord_per_point, remainingPoints) + + const prev = points[i - 1] + const next = points[i + z - 1] + + const edge = new PointToPointLine(prev, next) + + const intersections = ScalingUtil.intersections(s_scaled.relativeBounds, edge) + + intersections.sort(ScalingUtil.sort_by_dist(next)) + + if (intersections.length > 0) { + start_choice = intersections[0] + } + + } + + newPoints.push(start_choice) + + + // keep points not in end node + while (i < points.length) { + const remainingPoints = points.length - i + + const z = Math.min(max_coord_per_point, remainingPoints) + + const p = points[i + z - 1] + + if ( + !Bounds.includes(t_scaled.relativeBounds, p) + ) { + for (let j = 0; j < z; j++) { + newPoints.push(points[i]) + i++ + } + } else { + break + } + } + + // determine new end point + + let end_choice = scaled_end + if (i < points.length) { + + const remainingPoints = points.length - i + const z = Math.min(max_coord_per_point, remainingPoints) + + const prev = points[i - 1] + const next = points[i + z - 1] + + const edge = new PointToPointLine(prev, next) + + const intersections = ScalingUtil.intersections(t_scaled.relativeBounds, edge) + + intersections.sort(ScalingUtil.sort_by_dist(prev)) + + if (intersections.length > 0) { + end_choice = intersections[0] + } + + // keep the control points of the current point + if (z >= 2) { + newPoints.push(points[i]) + if (z == 3) { + newPoints.push(points[i + 1]) + } + } + + } + + newPoints.push(end_choice) + + edge.moved_ends_by = { start: Point.subtract(start_choice, points[0]), end: Point.subtract(end_choice, points[points.length - 1]) } + return newPoints; + } + case K_POLYGON: { + if (edge.moved_ends_by && edge.routingPoints.length > 0) { + let newPoint = boundsAndTransformation.bounds as Point + + if (Bounds.includes(boundsAndTransformation.bounds, edge.routingPoints[0])) { + newPoint = Point.add(edge.moved_ends_by.start, boundsAndTransformation.bounds) + } else if (Bounds.includes(boundsAndTransformation.bounds, edge.routingPoints[edge.routingPoints.length - 1])) { + newPoint = Point.add(edge.moved_ends_by.end, boundsAndTransformation.bounds) + } + + const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) + const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin) + + const parent_scale = ScalingUtil.maxParentScale(boundsAndTransformation.bounds, parent.bounds, margin) + + const desired_scale = target_scale / (p_scaled.effectiveChildScale * context.viewport.zoom) + const preferred_scale = Math.min(desired_scale, parent_scale) + + const scale = Math.max(preferred_scale, 1) + + + gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale(" + scale + ") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + (gAttrs.transform ?? "") + } + break + } + default: + console.error("Unexpected Line Type: ", rendering.type) + } + return points + } } diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index b25329c9..c1a5ff8b 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -16,11 +16,11 @@ */ /** @jsx svg */ import { VNode } from 'snabbdom'; -import { PointToPointLine, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { Bounds, Point } from 'sprotty-protocol'; +import { svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { Bounds } from 'sprotty-protocol'; import { KGraphData, KNode } from '@kieler/klighd-interactive/lib/constraint-classes'; import { DetailLevel } from './hierarchy/depth-map'; -import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin, NodeScalingFactor, ScaleNodes } from './options/render-options-registry'; +import { PaperShadows, SimplifySmallText, TextSimplificationThreshold, TitleScalingFactor, UseSmartZoom, ScaleTitles, NodeMargin, ScaleNodes } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; import { Arc, HorizontalAlignment, isRendering, KArc, KChildArea, KContainerRendering, KForeground, KHorizontalAlignment, KImage, KPolyline, KRendering, KRenderingLibrary, KRenderingRef, KRoundedBendsPolyline, @@ -281,158 +281,7 @@ export function renderLine(rendering: KPolyline, && parent.target instanceof SKNode && parent.parent instanceof SKNode ) { - const s = parent.source - const t = parent.target - const s_scaled = s.forceNodeScaleBounds(context) - const t_scaled = t.forceNodeScaleBounds(context) - const p_scaled = parent.parent.forceNodeScaleBounds(context); - - const start = points[0] - const end = points[points.length-1] - - const scaled_start = ScalingUtil.calculateScaledPoint(s.bounds, s_scaled.relativeBounds, start) - const scaled_end = ScalingUtil.calculateScaledPoint(t.bounds, t_scaled.relativeBounds, end) - - let max_coord_per_point = 1 - switch (rendering.type) { - case K_SPLINE: - max_coord_per_point = 3 - // fallthrough - case K_ROUNDED_BENDS_POLYLINE: - case K_POLYLINE: { - - const newPoints = [] - - let i = 1 - - // skip points in the start node - while (i < points.length) { - const remainingPoints = points.length - i - - const z = Math.min(max_coord_per_point, remainingPoints) - - const p = points[i + z - 1] - - if ( - Bounds.includes(s_scaled.relativeBounds, p) - ) { - i+=z - } else { - break - } - } - - // determine new start point - let start_choice = scaled_start - - if (i < points.length) { - - const remainingPoints = points.length - i - const z = Math.min(max_coord_per_point, remainingPoints) - - const prev = points[i-1] - const next = points[i + z - 1] - - const edge = new PointToPointLine(prev, next) - - const intersections = ScalingUtil.intersections(s_scaled.relativeBounds, edge) - - intersections.sort(ScalingUtil.sort_by_dist(next)) - - if (intersections.length > 0){ - start_choice = intersections[0] - } - - } - - newPoints.push(start_choice) - - - // keep points not in end node - while (i < points.length) { - const remainingPoints = points.length - i - - const z = Math.min(max_coord_per_point, remainingPoints) - - const p = points[i + z - 1] - - if ( - !Bounds.includes(t_scaled.relativeBounds, p) - ) { - for (let j = 0; j < z ; j++) { - newPoints.push(points[i]) - i++ - } - } else { - break - } - } - - // determine new end point - - let end_choice = scaled_end - if (i < points.length) { - - const remainingPoints = points.length - i - const z = Math.min(max_coord_per_point, remainingPoints) - - const prev = points[i-1] - const next = points[i + z - 1] - - const edge = new PointToPointLine(prev, next) - - const intersections = ScalingUtil.intersections(t_scaled.relativeBounds, edge) - - intersections.sort(ScalingUtil.sort_by_dist(prev)) - - if (intersections.length > 0){ - end_choice = intersections[0] - } - - // keep the control points of the current point - if (z >= 2) { - newPoints.push(points[i]) - if (z == 3) { - newPoints.push(points[i + 1]) - } - } - - } - - newPoints.push(end_choice) - - parent.moved_ends_by = {start: Point.subtract(start_choice, points[0]), end: Point.subtract(end_choice, points[points.length -1])} - points = newPoints - break; - } - case K_POLYGON: { - if (parent.moved_ends_by && parent.routingPoints.length > 0){ - let newPoint = boundsAndTransformation.bounds as Point - - if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[0])) { - newPoint = Point.add(parent.moved_ends_by.start, boundsAndTransformation.bounds) - } else if (Bounds.includes(boundsAndTransformation.bounds, parent.routingPoints[parent.routingPoints.length -1])) { - newPoint = Point.add(parent.moved_ends_by.end, boundsAndTransformation.bounds) - } - - const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) - const margin = context.renderOptionsRegistry.getValueOrDefault(NodeMargin) - - const parent_scale = ScalingUtil.maxParentScale(boundsAndTransformation.bounds, parent.parent.bounds, margin) - - const desired_scale = target_scale / (p_scaled.effectiveChildScale * context.viewport.zoom) - const preferred_scale = Math.min(desired_scale, parent_scale) - - const scale = Math.max(preferred_scale, 1) - - - gAttrs.transform = "translate(" + newPoint.x + "," + newPoint.y + ") scale("+scale+") translate(" + -boundsAndTransformation.bounds.x + "," + -boundsAndTransformation.bounds.y + ") " + (gAttrs.transform ?? "") - } - break - } - default: - console.error("Unexpected Line Type: ", rendering.type) - } + points = ScalingUtil.performLineScaling(rendering, parent, parent.parent, parent.source, parent.target, boundsAndTransformation, context, gAttrs, points) } // now define the line's path. From d2a347f7383f150e8a87512cf3f7d1dcb8eb05e1 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 14 Mar 2022 09:46:58 +0100 Subject: [PATCH 78/95] further de-duplicate this time with refactoring error fixed --- packages/klighd-core/src/scaling-util.ts | 87 ++++++++++-------------- 1 file changed, 37 insertions(+), 50 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index d4ced9a0..df34ceea 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -280,7 +280,7 @@ export class ScalingUtil { case K_ROUNDED_BENDS_POLYLINE: case K_POLYLINE: { - const newPoints = [] + const newPoints: Point[] = [] let i = 1 @@ -302,27 +302,7 @@ export class ScalingUtil { } // determine new start point - let start_choice = scaled_start - - if (i < points.length) { - const remainingPoints = points.length - i - - const z = Math.min(max_coord_per_point, remainingPoints) - - const prev = points[i - 1] - const next = points[i + z - 1] - - const edge = new PointToPointLine(prev, next) - - const intersections = ScalingUtil.intersections(s_scaled.relativeBounds, edge) - - intersections.sort(ScalingUtil.sort_by_dist(next)) - - if (intersections.length > 0) { - start_choice = intersections[0] - } - - } + const start_choice = calculateEndPoint(i, newPoints, true) ?? scaled_start; newPoints.push(start_choice) @@ -349,34 +329,7 @@ export class ScalingUtil { // determine new end point - let end_choice = scaled_end - if (i < points.length) { - - const remainingPoints = points.length - i - const z = Math.min(max_coord_per_point, remainingPoints) - - const prev = points[i - 1] - const next = points[i + z - 1] - - const edge = new PointToPointLine(prev, next) - - const intersections = ScalingUtil.intersections(t_scaled.relativeBounds, edge) - - intersections.sort(ScalingUtil.sort_by_dist(prev)) - - if (intersections.length > 0) { - end_choice = intersections[0] - } - - // keep the control points of the current point - if (z >= 2) { - newPoints.push(points[i]) - if (z == 3) { - newPoints.push(points[i + 1]) - } - } - - } + const end_choice = calculateEndPoint(i, newPoints, false) ?? scaled_end; newPoints.push(end_choice) @@ -412,5 +365,39 @@ export class ScalingUtil { console.error("Unexpected Line Type: ", rendering.type) } return points + + function calculateEndPoint(i: number, newPoints: any[], start: boolean): Point | void { + if (i < points.length) { + + let choice; + + const remainingPoints = points.length - i; + const z = Math.min(max_coord_per_point, remainingPoints); + + const prev = points[i - 1]; + const next = points[i + z - 1]; + + const edge = new PointToPointLine(prev, next); + + const intersections = ScalingUtil.intersections((start ? s_scaled : t_scaled).relativeBounds, edge); + + intersections.sort(ScalingUtil.sort_by_dist(start ? next : prev)); + + if (intersections.length > 0) { + choice = intersections[0]; + } + + // keep the control points of the current point + if (!start && z >= 2) { + newPoints.push(points[i]); + if (z == 3) { + newPoints.push(points[i + 1]); + } + } + + return choice; + } + } + } } From 8c963c2995614cf3857bfc6bfc26ba7922d0a9fb Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 12 Mar 2022 23:36:04 +0100 Subject: [PATCH 79/95] fix more names not following the naming scheme --- .../klighd-core/src/hierarchy/depth-map.ts | 14 ++++++------ packages/klighd-core/src/scaling-util.ts | 8 +++---- packages/klighd-core/src/skgraph-models.ts | 22 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/klighd-core/src/hierarchy/depth-map.ts b/packages/klighd-core/src/hierarchy/depth-map.ts index 68fbcd6c..5918bd93 100644 --- a/packages/klighd-core/src/hierarchy/depth-map.ts +++ b/packages/klighd-core/src/hierarchy/depth-map.ts @@ -117,20 +117,20 @@ export class DepthMap { this.lastThreshold = undefined this.regionIndexMap.clear() - let current_regions = this.rootRegions + let currentRegions = this.rootRegions this.rootRegions = [] - let remaining_regions: Region[] = [] + let remainingRegions: Region[] = [] // Go through all regions and clear the references to other Regions and KNodes - while (current_regions.length !== 0) { - for (const region of current_regions) { - remaining_regions.concat(region.children) + while (currentRegions.length !== 0) { + for (const region of currentRegions) { + remainingRegions.concat(region.children) region.children = [] region.parent = undefined } - current_regions = remaining_regions - remaining_regions = [] + currentRegions = remainingRegions + remainingRegions = [] } } diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index df34ceea..771ff61a 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -333,17 +333,17 @@ export class ScalingUtil { newPoints.push(end_choice) - edge.moved_ends_by = { start: Point.subtract(start_choice, points[0]), end: Point.subtract(end_choice, points[points.length - 1]) } + edge.movedEndsBy = { start: Point.subtract(start_choice, points[0]), end: Point.subtract(end_choice, points[points.length - 1]) } return newPoints; } case K_POLYGON: { - if (edge.moved_ends_by && edge.routingPoints.length > 0) { + if (edge.movedEndsBy && edge.routingPoints.length > 0) { let newPoint = boundsAndTransformation.bounds as Point if (Bounds.includes(boundsAndTransformation.bounds, edge.routingPoints[0])) { - newPoint = Point.add(edge.moved_ends_by.start, boundsAndTransformation.bounds) + newPoint = Point.add(edge.movedEndsBy.start, boundsAndTransformation.bounds) } else if (Bounds.includes(boundsAndTransformation.bounds, edge.routingPoints[edge.routingPoints.length - 1])) { - newPoint = Point.add(edge.moved_ends_by.end, boundsAndTransformation.bounds) + newPoint = Point.add(edge.movedEndsBy.end, boundsAndTransformation.bounds) } const target_scale = context.renderOptionsRegistry.getValueOrDefault(NodeScalingFactor) diff --git a/packages/klighd-core/src/skgraph-models.ts b/packages/klighd-core/src/skgraph-models.ts index d98fbc78..81488747 100644 --- a/packages/klighd-core/src/skgraph-models.ts +++ b/packages/klighd-core/src/skgraph-models.ts @@ -86,34 +86,34 @@ export class SKNode extends KNode implements SKGraphElement { if (this._nodeScaledBounds === undefined || needsUpdate) { if (this.parent && this.parent instanceof SKNode) { - const parent_scaled = this.parent.forceNodeScaleBounds(ctx) + const parentScaled = this.parent.forceNodeScaleBounds(ctx) if (performNodeScaling) { - const effective_zoom = parent_scaled.effectiveChildScale * ctx.viewport.zoom + const effectiveZoom = parentScaled.effectiveChildScale * ctx.viewport.zoom const siblings: Bounds[] = this.parent.children.filter((sibling) => sibling != this && sibling.type == NODE_TYPE).map((sibling) => (sibling as SShapeElement).bounds) - const upscale = ScalingUtil.upscaleBounds(effective_zoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); + const upscale = ScalingUtil.upscaleBounds(effectiveZoom, minNodeScale, this.bounds, this.parent.bounds, margin, siblings); let abs_bounds = { - x: upscale.bounds.x * parent_scaled.effectiveChildScale, - y: upscale.bounds.y * parent_scaled.effectiveChildScale, - width: upscale.bounds.width * parent_scaled.effectiveChildScale, - height: upscale.bounds.height * parent_scaled.effectiveChildScale + x: upscale.bounds.x * parentScaled.effectiveChildScale, + y: upscale.bounds.y * parentScaled.effectiveChildScale, + width: upscale.bounds.width * parentScaled.effectiveChildScale, + height: upscale.bounds.height * parentScaled.effectiveChildScale } - abs_bounds = Bounds.translate(abs_bounds, parent_scaled.absoluteBounds) + abs_bounds = Bounds.translate(abs_bounds, parentScaled.absoluteBounds) this._nodeScaledBounds = { relativeBounds: upscale.bounds, relativeScale: upscale.scale, absoluteBounds: abs_bounds, - effectiveChildScale: parent_scaled.effectiveChildScale * upscale.scale + effectiveChildScale: parentScaled.effectiveChildScale * upscale.scale } } else { - const abs_bounds = Bounds.translate(this.bounds, parent_scaled.absoluteBounds) + const abs_bounds = Bounds.translate(this.bounds, parentScaled.absoluteBounds) this._nodeScaledBounds = { relativeBounds: this.bounds, @@ -178,7 +178,7 @@ export class SKLabel extends SLabel implements SKGraphElement { export class SKEdge extends KEdge implements SKGraphElement { tooltip?: string - moved_ends_by?: { start: Point, end: Point } + movedEndsBy?: { start: Point, end: Point } hasFeature(feature: symbol): boolean { return feature === selectFeature || feature === popupFeature From a57474de9a43124b87f5e48b9e0ee1c8bc688a50 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 13 Mar 2022 00:35:58 +0100 Subject: [PATCH 80/95] fix unnecessary indirect return --- packages/klighd-core/src/views.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 845a2d65..c22b77cb 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -217,11 +217,10 @@ export class KNodeView implements IView { const titles = ctx.exitTitleScope() const childRenderings = ctx.renderChildren(node) ctx.popEffectiveZoom() - const ret = + return {titles} {childRenderings} - return ret } if (interactiveNodes) { result.push(interactiveNodes) @@ -238,8 +237,7 @@ export class KNodeView implements IView { result.push(...ctx.exitTitleScope()) ctx.positions.pop() ctx.popEffectiveZoom() - const ret ={...result} - return ret + return {...result} } } From ffef1431f8af61d6db16ea6f9f2f70f07a84f13c Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 13 Mar 2022 00:44:27 +0100 Subject: [PATCH 81/95] move popEffectiveZoom outside of the switch --- packages/klighd-core/src/views-rendering.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index c1a5ff8b..cc4b0447 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -997,22 +997,20 @@ export function renderKRendering(kRendering: KRendering, // Add the transformations to be able to positon the title correctly and above other elements context.positions[context.positions.length - 1] += (boundsAndTransformation?.transformation ?? "") - let svgRendering: VNode + let svgRendering: VNode | undefined switch (kRendering.type) { case K_CONTAINER_RENDERING: { - context.popEffectiveZoom() console.error('A rendering can not be a ' + kRendering.type + ' by itself, it needs to be a subclass of it.') - return undefined + break } case K_CHILD_AREA: { svgRendering = renderChildArea(kRendering as KChildArea, parent, propagatedStyles, context) break } case K_CUSTOM_RENDERING: { - context.popEffectiveZoom() console.error('The rendering for ' + kRendering.type + ' is not implemented yet.') // data as KCustomRendering - return undefined + break } case K_ARC: case K_ELLIPSE: @@ -1034,16 +1032,19 @@ export function renderKRendering(kRendering: KRendering, break } default: { - context.popEffectiveZoom() console.error('The rendering is of an unknown type:' + kRendering.type) - return undefined + break } } // Put the rectangle for the overlay behind the rendering itself. - if (overlayRectangle) { + if (overlayRectangle && svgRendering) { svgRendering.children?.unshift(overlayRectangle) } context.popEffectiveZoom() + if (!svgRendering) { + return undefined + } + if (isOverlay) { // Don't render this now if we have an overlay, but remember it to be put on top by the node rendering. context.pushTitle(svgRendering) From 06a96bd6d882f858985643cb510ea5b5bc5cdf1c Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 14 Mar 2022 15:57:44 +0100 Subject: [PATCH 82/95] fix another unnecessary indirect return --- packages/klighd-core/src/views-rendering.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/klighd-core/src/views-rendering.tsx b/packages/klighd-core/src/views-rendering.tsx index cc4b0447..c24a1ed4 100644 --- a/packages/klighd-core/src/views-rendering.tsx +++ b/packages/klighd-core/src/views-rendering.tsx @@ -370,11 +370,10 @@ export function renderLine(rendering: KPolyline, // Create the svg element for this rendering. // Only apply the fast shadow to KPolygons, other shadows are not allowed there. - const element = + return {...renderSVGLine(lineStyles, colorStyles, shadowStyles, path, rendering.type == K_POLYGON ? styles.kShadow : undefined)} {renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)} - - return element + ; } /** From d2ca249f557e1b599f7760afb41699383a3bfe2a Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 15 Mar 2022 12:39:14 +0100 Subject: [PATCH 83/95] improve debuggability by setting development mode and eval-source-maps --- applications/klighd-cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/klighd-cli/package.json b/applications/klighd-cli/package.json index 9ffee47d..f6bd4382 100644 --- a/applications/klighd-cli/package.json +++ b/applications/klighd-cli/package.json @@ -20,7 +20,7 @@ "clean": "rm -rf lib dist bin", "lint": "eslint .", "build": "run-p --print-label \"build:*\"", - "build:app": "webpack --mode production --devtool hidden-source-map", + "build:app": "webpack --mode development --devtool eval-source-map", "build:server": "tsc -p ./tsconfig.server.json", "watch": "run-p --print-label \"watch:*\"", "watch:app": "webpack --watch", From bf19305a95732a5d092e122af5423bd49a25e6d8 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 15 Mar 2022 12:46:35 +0100 Subject: [PATCH 84/95] initial version of edge label hiding for node scaling --- packages/klighd-core/src/views.tsx | 43 +++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index c22b77cb..43f64c8c 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -20,13 +20,14 @@ import { renderConstraints, renderInteractiveLayout } from '@kieler/klighd-inter import { KlighdInteractiveMouseListener } from '@kieler/klighd-interactive/lib/klighd-interactive-mouselistener'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { findParentByFeature, isViewport, IView, RenderingContext, SGraph, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { findParentByFeature, isViewport, IView, RenderingContext, SChildElement, SGraph, svg } from 'sprotty'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { Bounds } from 'sprotty-protocol' import { DepthMap, DetailLevel, isDetailWithChildren } from './hierarchy/depth-map'; import { DISymbol } from './di.symbols'; import { overpass_mono_regular_style, overpass_regular_style } from './fonts/overpass'; import { RenderOptionsRegistry, ShowConstraintOption, UseSmartZoom, ScaleNodes } from './options/render-options-registry'; import { SKGraphModelRenderer } from './skgraph-model-renderer'; -import { SKEdge, SKLabel, SKNode, SKPort } from './skgraph-models'; +import { SKEdge, SKLabel, SKNode, SKPort, LABEL_TYPE, NODE_TYPE } from './skgraph-models'; import { getJunctionPointRenderings, getRendering } from './views-rendering'; import { KStyles } from './views-styles'; @@ -425,9 +426,45 @@ export class KEdgeView implements IView { } // Default case. If no child area children or no non-child area children are already rendered within the rendering, add the children by default. if (!edge.areChildAreaChildrenRendered) { + + let children: readonly SChildElement[]; + + if (ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes)) { + + const intersects = function (a: Bounds, b: Bounds): boolean { + return (a.x < b.x + b.width + && a.y < b.y + b.height + && b.x < a.x + a.width + && b.y < a.y + a.height) + } + + const label_bounds = edge.children.filter(elem => elem.type === LABEL_TYPE) + .map(elem => (elem as SKEdge).bounds).reduce(Bounds.combine, Bounds.EMPTY); + + const siblings = edge.parent.children.filter(elem => elem.type === NODE_TYPE).map(elem => elem as SKNode); + + let keep_labels = true; + + for (const sibling of siblings) { + const sib = sibling.forceNodeScaleBounds(ctx).relativeBounds + + if (intersects(sib, label_bounds)) { + keep_labels = false; + break + } + } + + children = edge.children.filter(elem => (elem.type !== LABEL_TYPE) || keep_labels) + } else { + children = edge.children + } + + const children_rendered = children.map(elem => ctx.renderElement(elem)) + .filter(elem => elem !== undefined); + return {rendering} - {ctx.renderChildren(edge)} + {children_rendered} {...junctionPointRenderings} } else if (!edge.areNonChildAreaChildrenRendered) { From 318f388ac443d24063fcdf310979a25c4cb29719 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 15 Mar 2022 18:23:40 +0100 Subject: [PATCH 85/95] fix line ends sometimes overlapping scaled nde --- packages/klighd-core/src/scaling-util.ts | 33 ++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/klighd-core/src/scaling-util.ts b/packages/klighd-core/src/scaling-util.ts index 771ff61a..4354a373 100644 --- a/packages/klighd-core/src/scaling-util.ts +++ b/packages/klighd-core/src/scaling-util.ts @@ -285,20 +285,18 @@ export class ScalingUtil { let i = 1 // skip points in the start node - while (i < points.length) { + out: while (i < points.length) { const remainingPoints = points.length - i const z = Math.min(max_coord_per_point, remainingPoints) - const p = points[i + z - 1] - - if ( - Bounds.includes(s_scaled.relativeBounds, p) - ) { - i += z - } else { - break + for (let j = i; j < i + z; j++) { + if (Bounds.includes(s_scaled.relativeBounds, points[j])) { + i += z + continue out; + } } + break; } // determine new start point @@ -379,7 +377,9 @@ export class ScalingUtil { const edge = new PointToPointLine(prev, next); - const intersections = ScalingUtil.intersections((start ? s_scaled : t_scaled).relativeBounds, edge); + const target = (start ? s_scaled : t_scaled).relativeBounds + + const intersections = ScalingUtil.intersections(target, edge); intersections.sort(ScalingUtil.sort_by_dist(start ? next : prev)); @@ -387,13 +387,14 @@ export class ScalingUtil { choice = intersections[0]; } - // keep the control points of the current point - if (!start && z >= 2) { - newPoints.push(points[i]); - if (z == 3) { - newPoints.push(points[i + 1]); + // keep the control points of the current point if they are not in the target + if (!start) + if (z >= 2 && !Bounds.includes(target, points[i])) { + newPoints.push(points[i]); + if (z == 3 && !Bounds.includes(target, points[i + 1])) { + newPoints.push(points[i + 1]); + } } - } return choice; } From bd274735c72414d3370fc2937f8cbf382ffb26cb Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 15 Mar 2022 18:34:27 +0100 Subject: [PATCH 86/95] extract filtering of labels and reuse it in the case that only children are rendered --- packages/klighd-core/src/views.tsx | 69 +++++++++++++++--------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 43f64c8c..68f146ca 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -419,47 +419,17 @@ export class KEdgeView implements IView { // If no rendering could be found, just render its children. if (rendering === undefined) { + const children_rendered = filterEdgeChildren(edge, ctx).map(elem => ctx.renderElement(elem)) + .filter(elem => elem !== undefined); return - {ctx.renderChildren(edge)} + {children_rendered} {...junctionPointRenderings} } // Default case. If no child area children or no non-child area children are already rendered within the rendering, add the children by default. if (!edge.areChildAreaChildrenRendered) { - let children: readonly SChildElement[]; - - if (ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes)) { - - const intersects = function (a: Bounds, b: Bounds): boolean { - return (a.x < b.x + b.width - && a.y < b.y + b.height - && b.x < a.x + a.width - && b.y < a.y + a.height) - } - - const label_bounds = edge.children.filter(elem => elem.type === LABEL_TYPE) - .map(elem => (elem as SKEdge).bounds).reduce(Bounds.combine, Bounds.EMPTY); - - const siblings = edge.parent.children.filter(elem => elem.type === NODE_TYPE).map(elem => elem as SKNode); - - let keep_labels = true; - - for (const sibling of siblings) { - const sib = sibling.forceNodeScaleBounds(ctx).relativeBounds - - if (intersects(sib, label_bounds)) { - keep_labels = false; - break - } - } - - children = edge.children.filter(elem => (elem.type !== LABEL_TYPE) || keep_labels) - } else { - children = edge.children - } - - const children_rendered = children.map(elem => ctx.renderElement(elem)) + const children_rendered = filterEdgeChildren(edge, ctx).map(elem => ctx.renderElement(elem)) .filter(elem => elem !== undefined); return @@ -482,6 +452,37 @@ export class KEdgeView implements IView { } } +function filterEdgeChildren(edge: Readonly, ctx: SKGraphModelRenderer): readonly SChildElement[] { + if (ctx.renderOptionsRegistry.getValueOrDefault(ScaleNodes)) { + const intersects = function (a: Bounds, b: Bounds): boolean { + return (a.x < b.x + b.width + && a.y < b.y + b.height + && b.x < a.x + a.width + && b.y < a.y + a.height) + } + + const label_bounds = edge.children.filter(elem => elem.type === LABEL_TYPE) + .map(elem => (elem as SKEdge).bounds).reduce(Bounds.combine, Bounds.EMPTY); + + const siblings = edge.parent.children.filter(elem => elem.type === NODE_TYPE).map(elem => elem as SKNode); + + let keep_labels = true; + + for (const sibling of siblings) { + const sib = sibling.forceNodeScaleBounds(ctx).relativeBounds + + if (intersects(sib, label_bounds)) { + keep_labels = false; + break + } + } + + return edge.children.filter(elem => (elem.type !== LABEL_TYPE) || keep_labels) + } else { + return edge.children + } +} + function fontDefinition(): VNode { // TODO: maybe find a way to only include the font if it is used in the SVG. return