From bb8ac7ea4240b3ba31b15dc41c1aeaa1c2b7269e Mon Sep 17 00:00:00 2001 From: Vikas Awaghade <67629551+vikas-cldcvr@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:55:56 +0530 Subject: [PATCH] FDS-655 vertical direction in f-lineage implemented (#248) * FDS-655 vertical forward links implemented * FDS-655 reverse vertical links updated * FDS-655 stoires updated to test vertical layout * FDS-655 stories updated * FDS-655 playground story updated * FDS-655 flow-core changelogs updated --- packages/flow-core/CHANGELOG.md | 6 + packages/flow-core/package.json | 2 +- .../f-icon-button/f-icon-button.scss | 2 + packages/flow-lineage/CHANGELOG.md | 6 + packages/flow-lineage/package.json | 2 +- .../f-lineage/create/create-node-elements.ts | 14 +- .../components/f-lineage/draw/curve-steps.ts | 20 +- .../components/f-lineage/draw/draw-elbow.ts | 174 +++++++++++++++--- .../components/f-lineage/draw/draw-links.ts | 12 ++ .../components/f-lineage/draw/draw-nodes.ts | 66 +++++-- packages/flow-table/CHANGELOG.md | 6 + packages/flow-table/package.json | 2 +- .../f-table-schema/f-table-schema.ts | 9 +- .../src/components/f-tcell/f-tcell.ts | 2 +- stories/flow-lineage/icon-buttons.stories.ts | 106 +++++++++++ 15 files changed, 364 insertions(+), 65 deletions(-) create mode 100644 stories/flow-lineage/icon-buttons.stories.ts diff --git a/packages/flow-core/CHANGELOG.md b/packages/flow-core/CHANGELOG.md index c09ae43b4..ae103559a 100644 --- a/packages/flow-core/CHANGELOG.md +++ b/packages/flow-core/CHANGELOG.md @@ -2,6 +2,12 @@ # Change Log +## [2.9.3] - 2024-03-20 + +### Improvements + +- `f-icon-button` box-sizing updated. + ## [2.9.2] - 2024-03-19 ### Improvements diff --git a/packages/flow-core/package.json b/packages/flow-core/package.json index b9a78e91d..89e07d13d 100644 --- a/packages/flow-core/package.json +++ b/packages/flow-core/package.json @@ -1,6 +1,6 @@ { "name": "@ollion/flow-core", - "version": "2.9.2", + "version": "2.9.3", "description": "Core package of flow design system", "module": "dist/flow-core.es.js", "main": "dist/flow-core.cjs.js", diff --git a/packages/flow-core/src/components/f-icon-button/f-icon-button.scss b/packages/flow-core/src/components/f-icon-button/f-icon-button.scss index 0ce87d979..0219f508a 100644 --- a/packages/flow-core/src/components/f-icon-button/f-icon-button.scss +++ b/packages/flow-core/src/components/f-icon-button/f-icon-button.scss @@ -82,6 +82,8 @@ in this case it is `f-icon-button` padding: 0px; position: relative; + box-sizing: border-box; + // if counter is specified then overflow has to be visible &[counter] { overflow: visible; diff --git a/packages/flow-lineage/CHANGELOG.md b/packages/flow-lineage/CHANGELOG.md index 5a953bc2e..fc580a616 100644 --- a/packages/flow-lineage/CHANGELOG.md +++ b/packages/flow-lineage/CHANGELOG.md @@ -2,6 +2,12 @@ # Change Log +## [3.2.0] - 2024-03-19 + +### Features + +- `direction="vertical"` implemented in `f-lineage`. + ## [3.1.1] - 2023-12-19 ### Patch Changes diff --git a/packages/flow-lineage/package.json b/packages/flow-lineage/package.json index a747637ea..072a25109 100644 --- a/packages/flow-lineage/package.json +++ b/packages/flow-lineage/package.json @@ -1,6 +1,6 @@ { "name": "@ollion/flow-lineage", - "version": "3.1.1", + "version": "3.2.0", "description": "Lineage dependency for flow design system", "module": "dist/flow-lineage.es.js", "main": "dist/flow-lineage.cjs.js", diff --git a/packages/flow-lineage/src/components/f-lineage/create/create-node-elements.ts b/packages/flow-lineage/src/components/f-lineage/create/create-node-elements.ts index be4923f4e..a33bfeaab 100644 --- a/packages/flow-lineage/src/components/f-lineage/create/create-node-elements.ts +++ b/packages/flow-lineage/src/components/f-lineage/create/create-node-elements.ts @@ -120,12 +120,14 @@ export default function createNodeElements( /** * checking level max Y */ - const maxYWhenScrollBar = nodeElement.y + nodeSize.height + maxChildrenHeight; - if (nodeElement.hasScrollbaleChildren && levelPointer.y > maxYWhenScrollBar) { - levelPointer.maxY = maxYWhenScrollBar; - nodeElement.childrenYMax = maxYWhenScrollBar; - } else if (levelPointer.y > (levelPointer.maxY ?? 0)) { - levelPointer.maxY = levelPointer.y; + if (!nodeElement.fHideChildren) { + const maxYWhenScrollBar = nodeElement.y + nodeSize.height + maxChildrenHeight; + if (nodeElement.hasScrollbaleChildren && levelPointer.y > maxYWhenScrollBar) { + levelPointer.maxY = maxYWhenScrollBar; + nodeElement.childrenYMax = maxYWhenScrollBar; + } else if (levelPointer.y > (levelPointer.maxY ?? 0)) { + levelPointer.maxY = levelPointer.y; + } } levelPointer.y = nodeElement.y; diff --git a/packages/flow-lineage/src/components/f-lineage/draw/curve-steps.ts b/packages/flow-lineage/src/components/f-lineage/draw/curve-steps.ts index 82b9ccc16..d65656b7d 100644 --- a/packages/flow-lineage/src/components/f-lineage/draw/curve-steps.ts +++ b/packages/flow-lineage/src/components/f-lineage/draw/curve-steps.ts @@ -7,11 +7,13 @@ class Step { private _point?: number; private _lastX?: number; private _lastY?: number; + private _direction?: CurveDirection; - constructor(context: CanvasRenderingContext2D, curve: number) { + constructor(context: CanvasRenderingContext2D, curve: number, direction?: CurveDirection) { this._context = context; this._curve = curve; this._line = NaN; + this._direction = direction; } /** @@ -61,7 +63,11 @@ class Step { this._point = 2; this._lastX = x; this._lastY = y; - this._context.lineTo(x - this._curve, y); + if (this._direction === "vertical") { + this._context.lineTo(x, y - this._curve); + } else { + this._context.lineTo(x - this._curve, y); + } break; default: { if ( @@ -102,14 +108,16 @@ class Step { } } +type CurveDirection = "horizontal" | "vertical"; + /** * A factory function that creates a curved step function. * @param angle - The angle for curves. * @returns A function to create curved steps. */ -function curvedStep(angle: number) { +function curvedStep(angle: number, direction?: CurveDirection) { function createStep(context: CanvasRenderingContext2D) { - return new Step(context, angle); + return new Step(context, angle, direction); } /** @@ -117,8 +125,8 @@ function curvedStep(angle: number) { * @param angle - The new angle for curves. * @returns A curved step function with the specified angle. */ - createStep.angle = function (angle: number) { - return curvedStep(+angle); + createStep.angle = function (angle: number, direction?: CurveDirection) { + return curvedStep(+angle, direction); }; return createStep; diff --git a/packages/flow-lineage/src/components/f-lineage/draw/draw-elbow.ts b/packages/flow-lineage/src/components/f-lineage/draw/draw-elbow.ts index 6fcb0086d..838c5266c 100644 --- a/packages/flow-lineage/src/components/f-lineage/draw/draw-elbow.ts +++ b/packages/flow-lineage/src/components/f-lineage/draw/draw-elbow.ts @@ -22,6 +22,7 @@ export type DrawElbowParams = { element: FLineage; }; import curveStep from "./curve-steps"; +import { getChildrenArray } from "../../../utils"; type Point = { x: number; y: number }; @@ -62,13 +63,38 @@ export default function drawElbow({ .x(p => (p as unknown as Point).x) .y(p => (p as unknown as Point).y) //@ts-expect-error @todo vikas to check - .curve(curveStep.angle(curveAngle)); + .curve(curveStep.angle(curveAngle, element.direction)); // add point on node for connection let startPoint: Point = { x: link.source.x + (link.source.isChildren ? childrenNodeSize.width : nodeSize.width), y: link.source.y + (link.source.isChildren ? childrenNodeSize.height / 2 : nodeSize.height / 2) }; + if (element.direction === "vertical") { + let startY = + link.source.y + (link.source.isChildren ? childrenNodeSize.height : nodeSize.height); + if (!link.source.fHideChildren && link.source.fChildren) { + const maxChildrenHeight = (element.maxChildren ?? 8) * childrenNodeSize.height; + const childrens = getChildrenArray(link.source.fChildren); + const totalChildNodeHeight = childrenNodeSize.height * childrens.length; + + if (totalChildNodeHeight > maxChildrenHeight) { + startY = + link.source.y + + (link.source.isChildren ? childrenNodeSize.height : nodeSize.height) + + maxChildrenHeight; + } else { + startY = + link.source.y + + (link.source.isChildren ? childrenNodeSize.height : nodeSize.height) + + totalChildNodeHeight; + } + } + startPoint = { + x: link.source.x + (link.source.isChildren ? childrenNodeSize.width / 2 : nodeSize.width / 2), + y: startY + }; + } const points: Point[] = []; points.push(startPoint); @@ -83,12 +109,21 @@ export default function drawElbow({ let closestGapPoint: LineageGapElement; if (link.target.level === l + 1) { - closestGapPoint = { - x: link.target.x, - y: - link.target.y + - (link.target.isChildren ? childrenNodeSize.height / 2 : nodeSize.height / 2) - }; + if (element.direction === "vertical") { + closestGapPoint = { + x: + link.target.x + + (link.target.isChildren ? childrenNodeSize.width / 2 : nodeSize.width / 2), + y: link.target.y + }; + } else { + closestGapPoint = { + x: link.target.x, + y: + link.target.y + + (link.target.isChildren ? childrenNodeSize.height / 2 : nodeSize.height / 2) + }; + } } else { closestGapPoint = lineage.gaps[l + 1].reduce( (previous, current) => { @@ -102,26 +137,61 @@ export default function drawElbow({ { x: -1, y: -1 } ); - closestGapPoint = { - x: closestGapPoint.x + nodeSize.width, - y: closestGapPoint.y + gap / 2 - }; + if (element.direction === "vertical") { + closestGapPoint = { + y: closestGapPoint.y + nodeSize.height, + x: closestGapPoint.x + gap / 2 + }; + } else { + closestGapPoint = { + x: closestGapPoint.x + nodeSize.width, + y: closestGapPoint.y + gap / 2 + }; + } } - const secondPoint = { + let secondPoint = { x: startPoint.x + gapDelta, y: startPoint.y }; + + const nextGapPoint = lineage.gaps[l + 1].reduce( + (previous, current) => { + if (previous.x === -1) { + return current; + } + return Math.abs(current.y - startPoint.y) < Math.abs(previous.y - startPoint.y) + ? current + : previous; + }, + { x: -1, y: -1 } + ); + + if (element.direction === "vertical") { + secondPoint = { + x: startPoint.x, + y: nextGapPoint.y - gapDelta + }; + } points.push(secondPoint); // check if curve is feasible - const isCurveFeasible = Math.abs(closestGapPoint.y - startPoint.y) >= curveAngle; + let isCurveFeasible = Math.abs(closestGapPoint.y - startPoint.y) >= curveAngle; - const thirdPoint = { + if (element.direction === "vertical") { + isCurveFeasible = Math.abs(closestGapPoint.x - startPoint.x) >= curveAngle; + } + let thirdPoint = { x: startPoint.x + gapDelta, y: isCurveFeasible ? closestGapPoint.y : startPoint.y }; + if (element.direction === "vertical") { + thirdPoint = { + x: isCurveFeasible ? closestGapPoint.x : startPoint.x, + y: nextGapPoint.y - gapDelta + }; + } points.push(thirdPoint); points.push(closestGapPoint); @@ -139,12 +209,21 @@ export default function drawElbow({ let closestGapPoint: LineageGapElement; if (link.target.level - 1 === l) { - closestGapPoint = { - x: link.target.x, - y: - link.target.y + - (link.target.isChildren ? childrenNodeSize.height / 2 : nodeSize.height / 2) - }; + if (element.direction === "vertical") { + closestGapPoint = { + x: + link.target.x + + (link.target.isChildren ? childrenNodeSize.width / 2 : nodeSize.width / 2), + y: link.target.y + }; + } else { + closestGapPoint = { + x: link.target.x, + y: + link.target.y + + (link.target.isChildren ? childrenNodeSize.height / 2 : nodeSize.height / 2) + }; + } } else { closestGapPoint = lineage.gaps[l].reduce( (previous, current) => { @@ -157,27 +236,47 @@ export default function drawElbow({ }, { x: -1, y: -1 } ); - - closestGapPoint = { - x: closestGapPoint.x - gapDelta, - y: closestGapPoint.y + gap / 2 - }; + if (element.direction === "vertical") { + closestGapPoint = { + y: closestGapPoint.y - gapDelta, + x: closestGapPoint.x + gap / 2 + }; + } else { + closestGapPoint = { + x: closestGapPoint.x - gapDelta, + y: closestGapPoint.y + gap / 2 + }; + } } - const secondPoint = { + let secondPoint = { x: startPoint.x - gapDelta * (link.source.level == l ? -1 : 1), y: startPoint.y }; + + if (element.direction === "vertical") { + secondPoint = { + y: startPoint.y - gapDelta * (link.source.level == l ? -1 : 1), + x: startPoint.x + }; + } points.push(secondPoint); // check if curve is feasible const isCurveFeasible = Math.abs(closestGapPoint.y - startPoint.y) >= curveAngle; - const thirdPoint = { + let thirdPoint = { x: startPoint.x - gapDelta * (link.source.level == l ? -1 : 1), y: isCurveFeasible ? closestGapPoint.y : startPoint.y }; + if (element.direction === "vertical") { + thirdPoint = { + y: startPoint.y - gapDelta * (link.source.level == l ? -1 : 1), + x: isCurveFeasible ? closestGapPoint.x : startPoint.x + }; + } + points.push(thirdPoint); points.push(closestGapPoint); @@ -187,7 +286,10 @@ export default function drawElbow({ } if (element) { - if (element.getDrawParams().svg.attr("transform") == null) { + if ( + element.getDrawParams().svg.attr("transform") == null && + element.direction === "horizontal" + ) { const leftX = element?.padding ?? 0; const leftMostPoint = points.find(p => { @@ -200,6 +302,22 @@ export default function drawElbow({ `${leftMostPoint.x - gap} 0 ${element.getWidth()} ${element.getHeight()}` ); } + } else if ( + element.getDrawParams().svg.attr("transform") == null && + element.direction === "vertical" + ) { + const topY = element?.padding ?? 0; + + const topMostPoint = points.find(p => { + return p.y < topY; + }); + + if (topMostPoint) { + d3.select(element.svg).attr( + "viewBox", + `0 ${topMostPoint.y - gap} ${element.getWidth()} ${element.getHeight()}` + ); + } } } const path = line(points as unknown as [number, number][]); diff --git a/packages/flow-lineage/src/components/f-lineage/draw/draw-links.ts b/packages/flow-lineage/src/components/f-lineage/draw/draw-links.ts index d5e64c960..58cb77ae0 100644 --- a/packages/flow-lineage/src/components/f-lineage/draw/draw-links.ts +++ b/packages/flow-lineage/src/components/f-lineage/draw/draw-links.ts @@ -209,5 +209,17 @@ export default function drawLinks({ .attr("fill", "var(--color-border-default)") .text("◀"); + /** + * Remove duplicate link plotted in multiple pages + */ + const linkIds = Array.from(element.shadowRoot!.querySelectorAll(".link")).map(el => { + return el.id; + }); + const duplicates = linkIds.filter((item, index) => linkIds.indexOf(item) !== index); + duplicates.forEach(dupLink => { + const dupLinkEl = element.shadowRoot!.querySelectorAll(`[id="${dupLink}"]`); + + dupLinkEl[1]?.remove(); + }); //console.timeEnd("Links duration"); } diff --git a/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts b/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts index 4ed017325..8c911d455 100644 --- a/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts +++ b/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts @@ -114,11 +114,18 @@ export default function drawNodes(params: DrawLineageParams) { const childIds = allChildNodes.map(c => c.id); if (d.childrenYMax) { let childHeight = d.childrenYMax - (d.y + nodeSize.height); - + let nodesToUpdate: LineageNodeElement[] = []; // finding all nodes below children - const nodesToUpdate = lineage.nodes.filter( - n => n.level === d.level && n.y > d.y && !childIds.includes(n.id) - ); + + if (element.direction === "vertical") { + nodesToUpdate = lineage.nodes.filter( + n => n.level >= d.level && n.y > d.y && !childIds.includes(n.id) + ); + } else { + nodesToUpdate = lineage.nodes.filter( + n => n.level === d.level && n.y > d.y && !childIds.includes(n.id) + ); + } // compare height and apply max height with scroll bar if required if (childHeight > maxChildrenHeight) { @@ -137,22 +144,43 @@ export default function drawNodes(params: DrawLineageParams) { } } }); - if (!d.fHideChildren) { - lineage.levelPointers[d.level].y += childHeight; - } else { - lineage.levelPointers[d.level].y -= childHeight; - } + if (element.direction === "vertical") { + for (let i = d.level; i <= element.maxAvailableLevels; i++) { + if (!d.fHideChildren) { + lineage.levelPointers[i].y += childHeight; + } else { + lineage.levelPointers[i].y -= childHeight; + } - const gapsToUpdate = lineage.gaps[d.level].filter(n => n.y > d.y); + const gapsToUpdate = lineage.gaps[i].filter(n => n.y > d.y); - gapsToUpdate.forEach(n => { + gapsToUpdate.forEach(n => { + if (!d.fHideChildren) { + n.y += childHeight; + } else { + n.y -= childHeight; + } + }); + removeLinks(nodesToUpdate, element); + } + } else { if (!d.fHideChildren) { - n.y += childHeight; + lineage.levelPointers[d.level].y += childHeight; } else { - n.y -= childHeight; + lineage.levelPointers[d.level].y -= childHeight; } - }); - removeLinks(nodesToUpdate, element); + + const gapsToUpdate = lineage.gaps[d.level].filter(n => n.y > d.y); + + gapsToUpdate.forEach(n => { + if (!d.fHideChildren) { + n.y += childHeight; + } else { + n.y -= childHeight; + } + }); + removeLinks(nodesToUpdate, element); + } } allChildNodes.forEach(cn => { @@ -161,7 +189,13 @@ export default function drawNodes(params: DrawLineageParams) { removeDistantLinks(element); removeLinks(allChildNodes, element); const pageNo = this.parentElement?.parentElement?.dataset.page ?? 0; - element.reDrawChunk(+pageNo, d.level); + if (element.direction === "vertical") { + for (let i = +pageNo; i <= element.page; i++) { + element.reDrawChunk(i, d.level); + } + } else { + element.reDrawChunk(+pageNo, d.level); + } } }) .each(function (d) { diff --git a/packages/flow-table/CHANGELOG.md b/packages/flow-table/CHANGELOG.md index 5104243f2..b1dff6466 100644 --- a/packages/flow-table/CHANGELOG.md +++ b/packages/flow-table/CHANGELOG.md @@ -2,6 +2,12 @@ # Change Log +## [2.4.1] - 2024-03-20 + +### Bug Fixes + +- `f-tcell` aligment in `f-table-schema` fixed. + ## [2.4.0] - 2024-03-01 ### Features diff --git a/packages/flow-table/package.json b/packages/flow-table/package.json index fc559055b..a6a76f783 100644 --- a/packages/flow-table/package.json +++ b/packages/flow-table/package.json @@ -1,6 +1,6 @@ { "name": "@ollion/flow-table", - "version": "2.4.0", + "version": "2.4.1", "description": "Table component for flow library", "module": "dist/flow-table.es.js", "main": "dist/flow-table.cjs.js", diff --git a/packages/flow-table/src/components/f-table-schema/f-table-schema.ts b/packages/flow-table/src/components/f-table-schema/f-table-schema.ts index e55d34a95..1d4e8be05 100644 --- a/packages/flow-table/src/components/f-table-schema/f-table-schema.ts +++ b/packages/flow-table/src/components/f-table-schema/f-table-schema.ts @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { FButton, FDiv, flowElement, FRoot, FSearch, FText } from "@ollion/flow-core"; import { html, HTMLTemplateResult, nothing, PropertyValueMap, unsafeCSS } from "lit"; -import { ifDefined } from "lit-html/directives/if-defined.js"; import { property, query, state } from "lit/decorators.js"; import { FTable, FTableSelectable, FTableSize, FTableVariant } from "../f-table/f-table"; import { FTcell, FTcellActions, FTcellAlign } from "../f-tcell/f-tcell"; @@ -226,13 +225,13 @@ export class FTableSchema extends FRoot { aria-sort="${this.sortBy === columnHeader[0] ? this.ariaSortOrder : "none"}" .align=${columnHeader[1].align} data-background="${this.stickyCellBackground}" - ?sticky-left=${ifDefined(sticky)} - ?sticky-top=${ifDefined(this.stickyHeader)} + ?sticky-left=${sticky} + ?sticky-top=${this.stickyHeader} @selected-column=${this.handleColumnSelection} @update-row-selection=${(event: CustomEvent) => this.handleHeaderInput(event, columnHeader[1])} > - + ${this.getHeaderCellTemplate(columnHeader[1])} ${columnHeader[1].disableSort ? nothing : this.getSortIcon(columnHeader[0])}${this.getCellTemplate(row.data[columnHeader[0]], highlightTerm)} `; })} diff --git a/packages/flow-table/src/components/f-tcell/f-tcell.ts b/packages/flow-table/src/components/f-tcell/f-tcell.ts index 797bc7707..3099746d3 100644 --- a/packages/flow-table/src/components/f-tcell/f-tcell.ts +++ b/packages/flow-table/src/components/f-tcell/f-tcell.ts @@ -154,7 +154,7 @@ export class FTcell extends FRoot { > ` : nothing} - + ) => { + const nodes: LineageNodes = { + user: { + fData: { + icon: "i-user", + state: "neutral" + } + }, + computer: { + fData: { + icon: "i-computer", + state: "primary", + effect: "pulse" + } + }, + failed: { + fData: { + icon: "i-org-fill", + state: "danger" + } + }, + result: { + fData: { + icon: "i-crown", + state: "neutral" + } + } + }; + const links: LineageNodeLinks = [ + { + from: "user", + to: "computer" + }, + { + from: "user", + to: "failed" + }, + { + from: "failed", + to: "result" + }, + { + from: "computer", + to: "result" + }, + { + from: "user", + to: "result" + } + ]; + + const nodeTemplate: LineageNodeTemplate = node => { + return html``; + }; + + const nodeSize: LineageNodeSize = { + width: 36, + height: 36 + }; + let idx = 0; + const states = ["primary", "danger", "success", "neutral"]; + setInterval(() => { + if (nodes.result.fData?.state) { + nodes.result.fData.state = states[idx]; + } + idx++; + if (idx === 4) { + idx = 0; + } + }, 1500); + return html``; + }, + name: "Icon Buttons Nodes ", + height: "500px" +};