Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

child placement and rendering bounding box fixes #130

Merged
merged 2 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/klighd-core/src/views-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,42 @@ export function transformationToSVGString(transformation: Transformation): strin
}
}

/**
* Reverses an array of transformations such that applying the transformation and its reverse counterpart will result in the identity transformation.
* @param transformations The transformations to reverse.
* @returns The reversed transformations.
*/
export function reverseTransformations(transformations: Transformation[]): Transformation[] {
return transformations.map(transformation => reverseTransformation(transformation)).reverse()
}

/**
* Reverses a transformation such that applying the transformation and its reverse counterpart will result in the identity transformation.
* @param transformation The transformation to reverse.
* @returns The reversed transformation.
*/
export function reverseTransformation(transformation: Transformation): Transformation {
if (isTranslation(transformation)) {
return {
kind: 'translate',
x: -transformation.x,
y: -transformation.y
} as Translation
} else if (isRotation(transformation)) {
return {
kind: 'rotate',
angle: -transformation.angle,
x: transformation.x,
y: transformation.y
} as Rotation
} else {
return {
kind: 'scale',
factor: 1 / (transformation as Scale).factor
} as Scale
}
}

/**
* Calculates the SVG transformation string that has to be applied to the SVG element.
* @param bounds The bounds of the rendering.
Expand Down
71 changes: 53 additions & 18 deletions packages/klighd-core/src/views-rendering.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
K_ROUNDED_BENDS_POLYLINE, K_ROUNDED_RECTANGLE, K_SPLINE, K_TEXT, SKEdge, SKGraphElement, SKLabel, SKNode, VerticalAlignment
} from './skgraph-models';
import { hasAction } from './skgraph-utils';
import { BoundsAndTransformation, calculateX, findBoundsAndTransformationData, getKRendering, getPoints, isRotation, isTranslation, Rotation, Scale, Transformation, transformationToSVGString, Translation } from './views-common';
import { BoundsAndTransformation, calculateX, findBoundsAndTransformationData, getKRendering, getPoints, isRotation, isTranslation, reverseTransformations, Rotation, Scale, Transformation, transformationToSVGString, Translation } from './views-common';
import {
ColorStyles, DEFAULT_CLICKABLE_FILL, DEFAULT_FILL, getKStyles, getSvgColorStyle, getSvgColorStyles, getSvgLineStyles, getSvgShadowStyles, getSvgTextStyles, isInvisible,
KStyles, LineStyles
Expand All @@ -43,15 +43,38 @@ import {
* @param propagatedStyles The styles propagated from parent elements that should be taken into account.
* @param context The rendering context for this element.
*/
export function renderChildArea(rendering: KChildArea, parent: SKGraphElement, propagatedStyles: KStyles, context: SKGraphModelRenderer): VNode {
export function renderChildArea(
rendering: KChildArea,
parent: SKGraphElement,
boundsAndTransformation: BoundsAndTransformation,
context: SKGraphModelRenderer): VNode {

// Sprotty expects the graph elements to always be relative to the parent element, while KLighD usually has the graph elements relative to the child area.
// Here we expect the graph elements to behave as Sprotty expects, thus requiring to reverse offset the transformation towards this child area again.

// First, we need to find the total transformations that were applied to the child area.
const totalTansformation = [...context.titleStorage.getTransformations()]

// Second, we need to find the transformation only applicable to the current child area.
const childAreaTransformation = boundsAndTransformation.transformation

// Finally, we need to reverse the translation that was applied to every element hierarchially above of the child area.
// Note that this causes a little difference in what the coordinates are relative to the parent graph element, as entire child area rotations that are possible in KLighD are
// not possible in Sprotty.
totalTansformation.splice(totalTansformation.length - childAreaTransformation.length, childAreaTransformation.length)
const reverseTranslation: Transformation[] = reverseTransformations(totalTansformation.filter(transformation => isTranslation(transformation)))

const gAttrs = {
...(reverseTranslation.length !== 0 ? { transform: reverseTranslation.map(transformationToSVGString).join('') } : {})
}
if (parent.areChildAreaChildrenRendered) {
console.error('This element contains multiple child areas, skipping this one.')
return <g />
}
// remember, that this parent's children are now already rendered
parent.areChildAreaChildrenRendered = true

const element = <g id={rendering.properties['klighd.lsp.rendering.id'] as string}>
const element = <g {...gAttrs} id={rendering.properties['klighd.lsp.rendering.id'] as string}>
{context.renderChildAreaChildren(parent)}
</g>

Expand Down Expand Up @@ -166,7 +189,7 @@ export function renderRectangularShape(
// eslint-disable-next-line
case K_ELLIPSE: {
element = <g id={rendering.properties['klighd.lsp.rendering.id'] as string} {...gAttrs}>
{...renderSVGEllipse(boundsAndTransformation.bounds, lineStyles, colorStyles, shadowStyles, styles.kShadow)}
{...renderSVGEllipse(boundsAndTransformation.bounds, styles.kLineWidth.lineWidth, lineStyles, colorStyles, shadowStyles, styles.kShadow)}
{renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)}
</g>
break
Expand All @@ -181,7 +204,7 @@ export function renderRectangularShape(
const ry = (rendering as KRoundedRectangle).cornerHeight

element = <g id={rendering.properties['klighd.lsp.rendering.id'] as string} {...gAttrs}>
{...renderSVGRect(boundsAndTransformation.bounds, rx, ry, lineStyles, colorStyles, shadowStyles, styles.kShadow)}
{...renderSVGRect(boundsAndTransformation.bounds, styles.kLineWidth.lineWidth, rx, ry, lineStyles, colorStyles, shadowStyles, styles.kShadow)}
{renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)}
</g>
break
Expand Down Expand Up @@ -567,6 +590,7 @@ export function renderWithShadow<T extends any[]>(
* Renders a rectangle with all given information.
*
* @param bounds bounds data calculated for this rectangle.
* @param lineWidth width of the line to offset the rectangle's position and size by.
* @param rx rx parameter of SVG rect
* @param ry ry parameter of SVG rect
* @param lineStyles style information for lines (stroke etc.)
Expand All @@ -575,8 +599,8 @@ export function renderWithShadow<T extends any[]>(
* @param kShadow general shadow information.
* @returns An array of SVG <rects> resulting from this. Only multiple <rect>s if a simple shadow effect should be applied.
*/
export function renderSVGRect(bounds: Bounds, rx: number, ry: number, lineStyles: LineStyles, colorStyles: ColorStyles, shadowStyles: string | undefined, kShadow: KShadow | undefined): VNode[] {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGRect, bounds, rx, ry, lineStyles, colorStyles)
export function renderSVGRect(bounds: Bounds, lineWidth: number, rx: number, ry: number, lineStyles: LineStyles, colorStyles: ColorStyles, shadowStyles: string | undefined, kShadow: KShadow | undefined): VNode[] {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGRect, bounds, rx, ry, lineWidth, lineStyles, colorStyles)
}

/**
Expand All @@ -589,18 +613,27 @@ export function renderSVGRect(bounds: Bounds, rx: number, ry: number, lineStyles
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow shadow information. Controls what this method does.
* @param bounds bounds data calculated for this rectangle.
* @param lineWidth width of the line to offset the rectangle's position and size by.
* @param rx rx parameter of SVG rect
* @param ry ry parameter of SVG rect
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @returns A single SVG <rect>.
*/
export function renderSingleSVGRect(x: number | undefined, y: number | undefined, shadowStyles: string | undefined, kShadow: KShadow | undefined, bounds: Bounds, rx: number, ry: number, lineStyles: LineStyles, colorStyles: ColorStyles): VNode {
export function renderSingleSVGRect(x: number | undefined, y: number | undefined, shadowStyles: string | undefined, kShadow: KShadow | undefined, bounds: Bounds, rx: number, ry: number, lineWidth: number, lineStyles: LineStyles, colorStyles: ColorStyles): VNode {
// Offset the x/y by the lineWidth.
let theX: number | undefined = x ? x : 0
theX += lineWidth / 2
theX = theX === 0 ? undefined : theX
let theY: number | undefined = y ? y : 0
theY += lineWidth / 2
theY = theY === 0 ? undefined : theY

return <rect
width={bounds.width}
height={bounds.height}
{...(x ? { x: x } : {})}
{...(y ? { y: y } : {})}
width={bounds.width - lineWidth}
height={bounds.height - lineWidth}
{...(theX ? { x: theX } : {})}
{...(theY ? { y: theY } : {})}
{...(rx ? { rx: rx } : {})}
{...(ry ? { ry: ry } : {})}
style={{
Expand Down Expand Up @@ -716,14 +749,15 @@ export function renderSingleSVGArc(x: number | undefined, y: number | undefined,
/**
* Renders an ellipse with all given information.
*
* @param lineWidth width of the line to offset the ellipse's position and size by.
* @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.
* @param kShadow general shadow information.
* @returns An array of SVG <ellipse>s resulting from this. Only multiple <ellipse>s if a simple shadow effect should be applied.
*/
export function renderSVGEllipse(bounds: Bounds, lineStyles: LineStyles, colorStyles: ColorStyles, shadowStyles: string | undefined, kShadow: KShadow | undefined): VNode[] {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGEllipse, bounds, lineStyles, colorStyles)
export function renderSVGEllipse(bounds: Bounds, lineWidth: number, lineStyles: LineStyles, colorStyles: ColorStyles, shadowStyles: string | undefined, kShadow: KShadow | undefined): VNode[] {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGEllipse, bounds, lineWidth, lineStyles, colorStyles)
}

/**
Expand All @@ -736,17 +770,18 @@ export function renderSVGEllipse(bounds: Bounds, lineStyles: LineStyles, colorSt
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow shadow information. Controls what this method does.
* @param bounds bounds data calculated for this ellipse.
* @param lineWidth width of the line to offset the ellipse's position and size by.
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @returns A single SVG <ellipse>.
*/
export function renderSingleSVGEllipse(x: number | undefined, y: number | undefined, shadowStyles: string | undefined, kShadow: KShadow | undefined, bounds: Bounds, lineStyles: LineStyles, colorStyles: ColorStyles): VNode {
export function renderSingleSVGEllipse(x: number | undefined, y: number | undefined, shadowStyles: string | undefined, kShadow: KShadow | undefined, bounds: Bounds, lineWidth: number, lineStyles: LineStyles, colorStyles: ColorStyles): VNode {
return <ellipse
{...(x && y ? { transform: `translate(${x},${y})` } : {})}
cx={bounds.width / 2}
cy={bounds.height / 2}
rx={bounds.width / 2}
ry={bounds.height / 2}
rx={bounds.width / 2 - lineWidth / 2}
ry={bounds.height / 2 - lineWidth / 2}
style={{
...(kShadow ? {} : { 'stroke-linecap': lineStyles.lineCap }),
...(kShadow ? {} : { 'stroke-linejoin': lineStyles.lineJoin }),
Expand Down Expand Up @@ -996,7 +1031,7 @@ export function renderKRendering(kRendering: KRendering,
return undefined
}
case K_CHILD_AREA: {
svgRendering = renderChildArea(kRendering as KChildArea, parent, propagatedStyles, context)
svgRendering = renderChildArea(kRendering as KChildArea, parent, boundsAndTransformation, context)
break
}
case K_CUSTOM_RENDERING: {
Expand Down