From 10127035b4e37966d10b40892e4ebf069412face Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 1 Oct 2024 16:06:43 +0100 Subject: [PATCH 01/13] done --- demo-project/conf/base/catalog_01_raw.yml | 6 +- demo-project/conf/base/catalog_02_int.yml | 8 +- demo-project/conf/base/catalog_03_prm.yml | 4 +- demo-project/conf/base/catalog_04_feature.yml | 4 +- .../conf/base/catalog_05_model_input.yml | 2 +- .../conf/base/catalog_08_reporting.yml | 8 +- src/components/flowchart/styles/_node.scss | 1 + src/utils/graph/constraints.js | 4 +- src/utils/graph/layout.js | 183 ++++++++++++------ src/utils/graph/routing.js | 107 ++++++---- src/utils/graph/solver.js | 11 +- src/utils/graph/test.js | 59 ++++++ 12 files changed, 286 insertions(+), 111 deletions(-) create mode 100644 src/utils/graph/test.js diff --git a/demo-project/conf/base/catalog_01_raw.yml b/demo-project/conf/base/catalog_01_raw.yml index b2d71bf82c..c5a76bc83b 100644 --- a/demo-project/conf/base/catalog_01_raw.yml +++ b/demo-project/conf/base/catalog_01_raw.yml @@ -3,7 +3,7 @@ companies: filepath: ${_base_location}/01_raw/companies.csv metadata: kedro-viz: - layer: raw + layer1: raw preview_args: nrows: 5 @@ -12,7 +12,7 @@ reviews: filepath: ${_base_location}/01_raw/reviews.csv metadata: kedro-viz: - layer: raw + layer1: raw preview_args: nrows: 10 @@ -22,6 +22,6 @@ shuttles: filepath: ${_base_location}/01_raw/shuttles.xlsx metadata: kedro-viz: - layer: raw + layer1: raw preview_args: nrows: 15 diff --git a/demo-project/conf/base/catalog_02_int.yml b/demo-project/conf/base/catalog_02_int.yml index 9b75ff1f17..7377477113 100644 --- a/demo-project/conf/base/catalog_02_int.yml +++ b/demo-project/conf/base/catalog_02_int.yml @@ -3,25 +3,25 @@ ingestion.int_typed_companies: filepath: ${_base_location}/02_intermediate/typed_companies.pq metadata: kedro-viz: - layer: intermediate + layer1: intermediate ingestion.int_typed_shuttles@pandas1: type: pandas.ParquetDataset filepath: ${_base_location}/02_intermediate/typed_shuttles.pq metadata: kedro-viz: - layer: intermediate + layer1: intermediate ingestion.int_typed_shuttles@pandas2: type: pandas.ParquetDataset filepath: ${_base_location}/02_intermediate/typed_shuttles.pq metadata: kedro-viz: - layer: intermediate + layer1: intermediate ingestion.int_typed_reviews: type: pandas.ParquetDataset filepath: ${_base_location}/02_intermediate/typed_reviews.pq metadata: kedro-viz: - layer: intermediate + layer1: intermediate diff --git a/demo-project/conf/base/catalog_03_prm.yml b/demo-project/conf/base/catalog_03_prm.yml index f86f45854a..0f454bc7fc 100644 --- a/demo-project/conf/base/catalog_03_prm.yml +++ b/demo-project/conf/base/catalog_03_prm.yml @@ -3,11 +3,11 @@ prm_shuttle_company_reviews: filepath: ${_base_location}/03_primary/prm_shuttle_company_reviews.pq metadata: kedro-viz: - layer: primary + layer1: primary prm_spine_table: type: pandas.ParquetDataset filepath: ${_base_location}/03_primary/prm_spine_table.pq metadata: kedro-viz: - layer: primary + layer1: primary diff --git a/demo-project/conf/base/catalog_04_feature.yml b/demo-project/conf/base/catalog_04_feature.yml index 738a053352..68fb868b2d 100644 --- a/demo-project/conf/base/catalog_04_feature.yml +++ b/demo-project/conf/base/catalog_04_feature.yml @@ -2,11 +2,11 @@ "feature_engineering.feat_{metric_type}_metrics": type: pandas.ParquetDataset filepath: ${_base_location}/04_feature/feat_{metric_type}_metrics.pq - layer: feature + layer1: feature feature_importance_output: type: pandas.CSVDataset filepath: ${_base_location}/04_feature/feature_importance_output.csv metadata: kedro-viz: - layer: feature + layer1: feature diff --git a/demo-project/conf/base/catalog_05_model_input.yml b/demo-project/conf/base/catalog_05_model_input.yml index 031b2ba42a..54c5f1234f 100644 --- a/demo-project/conf/base/catalog_05_model_input.yml +++ b/demo-project/conf/base/catalog_05_model_input.yml @@ -3,4 +3,4 @@ model_input_table: filepath: ${_base_location}/05_model_input/model_input_table.pq metadata: kedro-viz: - layer: model_input + layer1: model_input diff --git a/demo-project/conf/base/catalog_08_reporting.yml b/demo-project/conf/base/catalog_08_reporting.yml index 148c0e1246..f43d740b89 100644 --- a/demo-project/conf/base/catalog_08_reporting.yml +++ b/demo-project/conf/base/catalog_08_reporting.yml @@ -3,7 +3,7 @@ reporting.cancellation_policy_breakdown: filepath: ${_base_location}/08_reporting/cancellation_breakdown.json metadata: kedro-viz: - layer: reporting + layer1: reporting plotly_args: type: bar fig: @@ -19,7 +19,7 @@ reporting.price_histogram: filepath: ${_base_location}/08_reporting/price_histogram.json metadata: kedro-viz: - layer: reporting + layer1: reporting versioned: true reporting.feature_importance: @@ -27,7 +27,7 @@ reporting.feature_importance: filepath: ${_base_location}/08_reporting/feature_importance_plot.json metadata: kedro-viz: - layer: reporting + layer1: reporting versioned: true reporting.cancellation_policy_grid: @@ -44,4 +44,4 @@ reporting.top_shuttle_data: filepath: ${_base_location}/08_reporting/top_shuttle_data.json metadata: kedro-viz: - layer: reporting + layer1: reporting diff --git a/src/components/flowchart/styles/_node.scss b/src/components/flowchart/styles/_node.scss index a2dc7b8d7a..6816fe3346 100644 --- a/src/components/flowchart/styles/_node.scss +++ b/src/components/flowchart/styles/_node.scss @@ -14,6 +14,7 @@ stroke-width: 1px; fill: var(--node-fill-default); stroke: var(--node-stroke-default); + opacity: 0; } &:focus { diff --git a/src/utils/graph/constraints.js b/src/utils/graph/constraints.js index cc47d6824b..f436108d8b 100644 --- a/src/utils/graph/constraints.js +++ b/src/utils/graph/constraints.js @@ -18,7 +18,7 @@ export const rowConstraint = { new Constraint( variableA.minus(variableB), Operator.Ge, - constants.spaceY, + constants.spaceSecondary, Strength.required ), }; @@ -42,7 +42,7 @@ export const layerConstraint = { * Layout constraint in X for minimising distance from source to target for straight edges */ export const parallelConstraint = { - property: 'x', + property: 'y', solve: (constraint) => { const { a, b, strength } = constraint; diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index e962110523..cda5bd255f 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -34,22 +34,52 @@ export const layout = ({ iterations, }) => { // Set initial positions for nodes + + let orientation = 'top-to-bottom' + // orientation = 'right-to-left' + + let coordPrimary = 'x'; + let coordSecondary = 'y'; + let sizePrimary = 'width'; + let sizeSecondary = 'height'; + let spacePrimary = spaceX; + let spaceSecondary = spaceY; + let spreadPrimary = spreadX; + let layerSpace = (spaceY + layerSpaceY) * 0.5; + + if (orientation === 'right-to-left') { + coordPrimary = 'y'; + coordSecondary = 'x'; + sizePrimary = 'height'; + sizeSecondary = 'width'; + spacePrimary = spaceY; + spaceSecondary = spaceX; + spreadPrimary = spreadX; // Adjust if necessary + layerSpace = (spaceX + layerSpaceY) * 0.5; + + } + for (const node of nodes) { - node.x = 0; - node.y = 0; + node[coordPrimary] = 0; + node[coordSecondary] = 0; } // Constants used by constraints const constants = { - spaceX, - spaceY, - spreadX, - layerSpace: (spaceY + layerSpaceY) * 0.5, + orientation, + spacePrimary, + spaceSecondary, + spreadPrimary, + layerSpace, + coordPrimary, + coordSecondary, + sizePrimary, + sizeSecondary }; // Constraints to separate nodes into rows and layers - const rowConstraints = createRowConstraints(edges); - const layerConstraints = createLayerConstraints(nodes, layers); + const rowConstraints = createRowConstraints(edges, constants); + const layerConstraints = createLayerConstraints(nodes, layers, constants); // Find the node positions given these constraints solveStrict([...rowConstraints, ...layerConstraints], constants, 1); @@ -74,7 +104,7 @@ export const layout = ({ solveStrict([...separationConstraints, ...parallelConstraints], constants, 1); // Adjust vertical spacing between rows for legibility - expandDenseRows(edges, rows, spaceY); + expandDenseRows(edges, rows, coordSecondary, spaceSecondary); }; /** @@ -82,11 +112,12 @@ export const layout = ({ * @param {Array} edges The input edges * @returns {Array} The constraints */ -const createRowConstraints = (edges) => +const createRowConstraints = (edges, constants) => edges.map((edge) => ({ base: rowConstraint, a: edge.targetNode, b: edge.sourceNode, + // property: constants.coordSecondary })); /** @@ -95,7 +126,7 @@ const createRowConstraints = (edges) => * @param {Array=} layers The input layers if any * @returns {Array} The constraints */ -const createLayerConstraints = (nodes, layers) => { +const createLayerConstraints = (nodes, layers, constants) => { const layerConstraints = []; // Early out if no layers defined @@ -122,6 +153,7 @@ const createLayerConstraints = (nodes, layers) => { base: layerConstraint, a: intermediary, b: node, + // property: constants.coordSecondary }); } @@ -131,6 +163,7 @@ const createLayerConstraints = (nodes, layers) => { base: layerConstraint, a: node, b: intermediary, + // property: constants.coordSecondary }); } } @@ -142,11 +175,11 @@ const createLayerConstraints = (nodes, layers) => { * Creates crossing constraints for the given edges. * @param {Array} edges The input edges * @param {Object} constants The constraint constants - * @param {Number} constants.spaceX The minimum gap between nodes in X + * @param {Number} constants.spacePrimary The minimum gap between nodes in X * @returns {Array} The constraints */ const createCrossingConstraints = (edges, constants) => { - const { spaceX } = constants; + const { spacePrimary, sizePrimary, coordPrimary } = constants; const crossingConstraints = []; // For every pair of edges @@ -179,11 +212,12 @@ const createCrossingConstraints = (edges, constants) => { crossingConstraints.push({ base: crossingConstraint, + // property : coordPrimary, edgeA: edgeA, edgeB: edgeB, // The required horizontal spacing between connected nodes - separationA: sourceA.width * 0.5 + spaceX + sourceB.width * 0.5, - separationB: targetA.width * 0.5 + spaceX + targetB.width * 0.5, + separationA: sourceA[sizePrimary] * 0.5 + spacePrimary + sourceB[sizePrimary] * 0.5, + separationB: targetA[sizePrimary] * 0.5 + spacePrimary + targetB[sizePrimary] * 0.5, // Evenly distribute the constraint strength: 1 / Math.max(1, (edgeADegree + edgeBDegree) / 4), }); @@ -201,9 +235,10 @@ const createCrossingConstraints = (edges, constants) => { * @param {Array} edges The input edges * @returns {Object} An object containing the constraints */ -const createParallelConstraints = (edges) => +const createParallelConstraints = (edges, constants) => edges.map(({ sourceNode, targetNode }) => ({ base: parallelConstraint, + property: constants.coordPrimary, a: sourceNode, b: targetNode, // Evenly distribute the constraint @@ -218,59 +253,97 @@ const createParallelConstraints = (edges) => * @returns {Array} The constraints */ const createSeparationConstraints = (rows, constants) => { - const { spaceX } = constants; + const { spacePrimary, spaceSecondary, coordPrimary, coordSecondary, sizePrimary, sizeSecondary, orientation, spreadPrimary } = constants; const separationConstraints = []; - // For each row of nodes - for (let i = 0; i < rows.length; i += 1) { - const rowNodes = rows[i]; - - // Stable sort row nodes horizontally, breaks ties with ids - rowNodes.sort((a, b) => compare(a.x, b.x, a.id, b.id)); - - // Update constraints given updated row node order - for (let j = 0; j < rowNodes.length - 1; j += 1) { - const nodeA = rowNodes[j]; - const nodeB = rowNodes[j + 1]; + rows.forEach((rowNodes, rowIndex) => { + // Separation constraints along the primary axis (either x or y) + if (orientation === 'top-to-bottom') { + // Sort row nodes along the primary axis + rowNodes.sort((a, b) => compare(a[coordPrimary], b[coordPrimary], a.id, b.id)); + // Handle separation between nodes within the same row + for (let j = 0; j < rowNodes.length - 1; j++) { + const nodeA = rowNodes[j]; + const nodeB = rowNodes[j + 1]; + + // Calculate degrees and spacing for the primary axis (either x or y) + const degreeA = Math.max(1, nodeA.targets.length + nodeA.sources.length - 2); + const degreeB = Math.max(1, nodeB.targets.length + nodeB.sources.length - 2); + const spread = Math.min(10, degreeA * degreeB * spreadPrimary); + const space = snap(spread * spacePrimary, spacePrimary); + separationConstraints.push({ + base: separationConstraint, + property: coordPrimary, // Separation along the primary axis + a: nodeA, + b: nodeB, + separation: nodeA[sizePrimary] * 0.5 + space + nodeB[sizePrimary] * 0.5, + }); + } + } - // Count the connected edges - const degreeA = Math.max( - 1, - nodeA.targets.length + nodeA.sources.length - 2 - ); - const degreeB = Math.max( - 1, - nodeB.targets.length + nodeB.sources.length - 2 - ); - - // Allow more spacing for nodes with more edges - const spread = Math.min(10, degreeA * degreeB * constants.spreadX); - const space = snap(spread * spaceX, spaceX); - - separationConstraints.push({ - base: separationConstraint, - a: nodeA, - b: nodeB, - separation: nodeA.width * 0.5 + space + nodeB.width * 0.5, - }); + if (orientation === 'right-to-left') { + // Ensure we're not at the last row + if (rowIndex < rows.length - 1) { + const nextRowNodes = rows[rowIndex + 1]; // Get nodes from the next row + const nodeB = nextRowNodes[0]; + + rowNodes.sort((a, b) => compare(a.y, b.y, a.id, b.id)); + + // Loop through each node in the current row + for (let j = 0; j < rowNodes.length; j++) { + const nodeA = rowNodes[j]; + const nodeC = j + 1 < rowNodes.length ? rowNodes[j + 1] : null; // Next node in the same row + + const degreeA = Math.max(1, nodeA.targets.length + nodeA.sources.length - 2); + const degreeB = Math.max(1, nodeB.targets.length + nodeB.sources.length - 2); + const spread = Math.min(10, degreeA * degreeB * spreadPrimary); + const space = snap(spread * spaceSecondary, spaceSecondary); + + // If there is a next node in the same row, apply primary axis separation (y-axis for right-to-left) + if (nodeC) { + const degreeC = Math.max(1, nodeC.targets.length + nodeC.sources.length - 2); + const spreadC = Math.min(10, degreeA * degreeC * spreadPrimary); + const spaceC = snap(spreadC * spaceSecondary, spaceSecondary); + separationConstraints.push({ + base: separationConstraint, + property: coordPrimary, // Primary axis (y for right-to-left) + a: nodeA, + b: nodeC, + separation: nodeA.height + spaceC + nodeC.height + }); + } + + separationConstraints.push({ + base: separationConstraint, + property: coordSecondary, + a: nodeA, + b: nodeB, + separation: nodeA.width * 0.5 + space + nodeB.width * 0.5 + }); + } + + } } - } + + }); return separationConstraints; }; + + /** * Adds additional spacing in Y relative to row density, see function `rowDensity` for definition. * Node positions are updated in-place * @param {Array} edges The input edges * @param {Array} rows The input rows of nodes - * @param {Number} spaceY The spacing between nodes in Y + * @param {Number} spaceSecondary The spacing between nodes in Y * @param {Number} [scale=1.25] The amount of expansion to apply relative to row density - * @param {Number} [unit=0.25] The unit size for rounding expansion relative to spaceY + * @param {Number} [unit=0.25] The unit size for rounding expansion relative to spaceSecondary */ -const expandDenseRows = (edges, rows, spaceY, scale = 1.25, unit = 0.25) => { +const expandDenseRows = (edges, rows, coordSecondary, spaceSecondary, scale = 1.25, unit = 0.25) => { const densities = rowDensity(edges); - const spaceYUnit = Math.round(spaceY * unit); + const spaceSecondaryUnit = Math.round(spaceSecondary * unit); let currentOffsetY = 0; // Add spacing based relative to row density @@ -278,12 +351,12 @@ const expandDenseRows = (edges, rows, spaceY, scale = 1.25, unit = 0.25) => { const density = densities[i] || 0; // Round offset to a common unit amount to improve vertical rhythm - const offsetY = snap(density * scale * spaceY, spaceYUnit); + const offsetY = snap(density * scale * spaceSecondary, spaceSecondaryUnit); currentOffsetY += offsetY; // Apply offset to all nodes following the current node for (const node of rows[i + 1]) { - node.y += currentOffsetY; + node[coordSecondary] += currentOffsetY; } } }; diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 466a357af6..0187ad1cea 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -42,10 +42,13 @@ export const routing = ({ stemSpaceTarget, }) => { // Find the rows formed by nodes + let orientation = 'top-to-bottom'; + orientation = 'right-to-left'; const rows = groupByRow(nodes); // For each node for (const node of nodes) { + console.log(node) // Sort the node's target edges by the angle between source and target nodes node.targets.sort((a, b) => compare( @@ -54,7 +57,7 @@ export const routing = ({ ) ); } - + // For each edge for (const edge of edges) { const source = edge.sourceNode; @@ -201,38 +204,76 @@ export const routing = ({ target.sources.length * (1 - Math.abs(targetEdgeDistance) / target.sources.length); - // Build the source stem for the edge - const sourceStem = [ - { - x: source.x + sourceOffsetX, - y: nodeBottom(source), - }, - { - x: source.x + sourceOffsetX, - y: nodeBottom(source) + stemMinSource, - }, - { - x: source.x + sourceOffsetX, - y: - nodeBottom(source) + stemMinSource + Math.min(sourceOffsetY, stemMax), - }, - ]; - - // Build the target stem for the edge - const targetStem = [ - { - x: target.x + targetOffsetX, - y: nodeTop(target) - stemMinTarget - Math.min(targetOffsetY, stemMax), - }, - { - x: target.x + targetOffsetX, - y: nodeTop(target) - stemMinTarget, - }, - { - x: target.x + targetOffsetX, - y: nodeTop(target), - }, - ]; + let sourceStem, targetStem; + + if (orientation === 'top-to-bottom') { + // Build the source stem for the edge (top-to-bottom) + sourceStem = [ + { + x: source.x + sourceOffsetX, + y: nodeBottom(source), + }, + { + x: source.x + sourceOffsetX, + y: nodeBottom(source) + stemMinSource, + }, + { + x: source.x + sourceOffsetX, + y: + nodeBottom(source) + stemMinSource + Math.min(sourceOffsetY, stemMax), + }, + ]; + + // Build the target stem for the edge (top-to-bottom) + targetStem = [ + { + x: target.x + targetOffsetX, + y: nodeTop(target) - stemMinTarget - Math.min(targetOffsetY, stemMax), + }, + { + x: target.x + targetOffsetX, + y: nodeTop(target) - stemMinTarget, + }, + { + x: target.x + targetOffsetX, + y: nodeTop(target), + }, + ]; + } else if (orientation === 'right-to-left') { + // Build the source stem for the edge (right-to-left) + sourceStem = [ + { + x: nodeRight(source), + y: source.y + sourceOffsetY, + }, + { + y: source.y + sourceOffsetY, + x: nodeRight(source) + stemMinSource, + }, + { + y: source.y + sourceOffsetY, + x: nodeRight(source) + stemMinSource + Math.min(sourceOffsetY, stemMax), + }, + ]; + + // Build the target stem for the edge (right-to-left) + targetStem = [ + { + y: target.y + targetOffsetY, + x: nodeLeft(target) - stemMinTarget - Math.min(targetOffsetY, stemMax), + }, + { + y: target.y + targetOffsetY, + x: nodeLeft(target) - stemMinTarget, + }, + { + y: target.y + targetOffsetY, + x: nodeLeft(target), + }, + ]; + } else { + throw new Error(`Unsupported orientation: ${orientation}`); + } // Combine all points const points = [...sourceStem, ...edge.points, ...targetStem]; diff --git a/src/utils/graph/solver.js b/src/utils/graph/solver.js index 769bb2b816..877d906d98 100644 --- a/src/utils/graph/solver.js +++ b/src/utils/graph/solver.js @@ -55,22 +55,23 @@ export const solveStrict = (constraints, constants) => { variable.obj = obj; } }; - for (const constraint of constraints) { - addVariable(constraint.a, constraint.base.property); - addVariable(constraint.b, constraint.base.property); + const property = constraint.property || constraint.base.property; + addVariable(constraint.a, property); + addVariable(constraint.b, property); } let unsolvableCount = 0; for (const constraint of constraints) { + const property = constraint.property || constraint.base.property; try { solver.addConstraint( constraint.base.strict( constraint, constants, - variables[variableId(constraint.a, constraint.base.property)], - variables[variableId(constraint.b, constraint.base.property)] + variables[variableId(constraint.a, property)], + variables[variableId(constraint.b, property)] ) ); } catch (err) { diff --git a/src/utils/graph/test.js b/src/utils/graph/test.js new file mode 100644 index 0000000000..c33d778c7f --- /dev/null +++ b/src/utils/graph/test.js @@ -0,0 +1,59 @@ +const createSeparationConstraints = (rows, constants) => { + const { spaceX, spaceY } = constants; + const separationConstraints = []; + + // For each row of nodes + for (let i = 0; i < rows.length - 1; i += 1) { + const rowNodes = rows[i]; + const nodeB = rows[i + 1][0]; + + // Stable sort row nodes horizontally, breaks ties with ids + rowNodes.sort((a, b) => compare(a.y, b.y, a.id, b.id)); + + // Update constraints given updated row node order + for (let j = 0; j < rowNodes.length; j += 1) { + const nodeA = rowNodes[j]; + const nodeC = j + 1 < rowNodes.length ? rowNodes[j + 1] : null; + + // Count the connected edges + const degreeA = Math.max( + 1, + nodeA.targets.length + nodeA.sources.length - 2 + ); + const degreeB = Math.max( + 1, + nodeB.targets.length + nodeB.sources.length - 2 + ); + if (nodeC) { + const degreeC = Math.max( + 1, + nodeC.targets.length + nodeC.sources.length - 2 + ); + // Allow more spacing for nodes with more edges + const spreadInX = Math.min(10, degreeA * degreeC * constants.spreadX); + const spaceInX = snap(spreadInX * spaceX, spaceX); + + separationConstraints.push({ + base: { property: 'x', ...separationConstraint }, + a: nodeA, + b: nodeC, + separation: nodeA.height + spaceInX + nodeC.height, + }); + } + + // Allow more spacing for nodes with more edges + const spreadInY = Math.min(10, degreeA * degreeB * constants.spreadX); + const spaceInY = snap(spreadInY * spaceY, spaceY); + + separationConstraints.push({ + base: { property: 'y', ...separationConstraint }, + a: nodeA, + b: nodeB, + separation: nodeA.width * 0.5 + spaceInY + nodeB.width * 0.5, + }); + } + } + + return separationConstraints; + }; + \ No newline at end of file From e64c67b2006be1b61b487f5e46f71d40a9ad8de8 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 1 Oct 2024 22:32:15 +0100 Subject: [PATCH 02/13] something working --- src/components/flowchart/styles/_node.scss | 1 - src/utils/graph/layout.js | 22 +++++++++++---------- src/utils/graph/routing.js | 23 +++++++++++----------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/flowchart/styles/_node.scss b/src/components/flowchart/styles/_node.scss index 6816fe3346..a2dc7b8d7a 100644 --- a/src/components/flowchart/styles/_node.scss +++ b/src/components/flowchart/styles/_node.scss @@ -14,7 +14,6 @@ stroke-width: 1px; fill: var(--node-fill-default); stroke: var(--node-stroke-default); - opacity: 0; } &:focus { diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index cda5bd255f..5756d39733 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -33,10 +33,10 @@ export const layout = ({ layerSpaceY, iterations, }) => { - // Set initial positions for nodes + let orientation = 'top-to-bottom' - // orientation = 'right-to-left' + orientation = 'right-to-left' let coordPrimary = 'x'; let coordSecondary = 'y'; @@ -59,11 +59,13 @@ export const layout = ({ } + // Set initial positions for nodes for (const node of nodes) { node[coordPrimary] = 0; node[coordSecondary] = 0; } + // Constants used by constraints const constants = { orientation, @@ -117,7 +119,6 @@ const createRowConstraints = (edges, constants) => base: rowConstraint, a: edge.targetNode, b: edge.sourceNode, - // property: constants.coordSecondary })); /** @@ -212,7 +213,7 @@ const createCrossingConstraints = (edges, constants) => { crossingConstraints.push({ base: crossingConstraint, - // property : coordPrimary, + property : coordPrimary, edgeA: edgeA, edgeB: edgeB, // The required horizontal spacing between connected nodes @@ -256,6 +257,8 @@ const createSeparationConstraints = (rows, constants) => { const { spacePrimary, spaceSecondary, coordPrimary, coordSecondary, sizePrimary, sizeSecondary, orientation, spreadPrimary } = constants; const separationConstraints = []; + console.log(rows) + rows.forEach((rowNodes, rowIndex) => { // Separation constraints along the primary axis (either x or y) if (orientation === 'top-to-bottom') { @@ -283,11 +286,11 @@ const createSeparationConstraints = (rows, constants) => { if (orientation === 'right-to-left') { // Ensure we're not at the last row + rowNodes.sort((a, b) => compare(a.y, b.y, a.id, b.id)); if (rowIndex < rows.length - 1) { const nextRowNodes = rows[rowIndex + 1]; // Get nodes from the next row - const nodeB = nextRowNodes[0]; - - rowNodes.sort((a, b) => compare(a.y, b.y, a.id, b.id)); + const maxIndex = nextRowNodes.reduce((maxIdx, node, idx) => node.width > nextRowNodes[maxIdx].width ? idx : maxIdx, 0); + const nodeB = nextRowNodes[maxIndex]; // Loop through each node in the current row for (let j = 0; j < rowNodes.length; j++) { @@ -306,19 +309,18 @@ const createSeparationConstraints = (rows, constants) => { const spaceC = snap(spreadC * spaceSecondary, spaceSecondary); separationConstraints.push({ base: separationConstraint, - property: coordPrimary, // Primary axis (y for right-to-left) + property: coordPrimary, a: nodeA, b: nodeC, separation: nodeA.height + spaceC + nodeC.height }); } - separationConstraints.push({ base: separationConstraint, property: coordSecondary, a: nodeA, b: nodeB, - separation: nodeA.width * 0.5 + space + nodeB.width * 0.5 + separation: nodeA[sizeSecondary]*0.5 + space + nodeB[sizeSecondary]*0.5, }); } diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 0187ad1cea..d16bd6cfff 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -48,7 +48,6 @@ export const routing = ({ // For each node for (const node of nodes) { - console.log(node) // Sort the node's target edges by the angle between source and target nodes node.targets.sort((a, b) => compare( @@ -278,17 +277,17 @@ export const routing = ({ // Combine all points const points = [...sourceStem, ...edge.points, ...targetStem]; - // Fix any invalid points caused by invalid layouts - let pointYMax = points[0].y; - - for (const point of points) { - // Ensure increasing Y values for each point - if (point.y < pointYMax) { - point.y = pointYMax; - } else { - pointYMax = point.y; - } - } + // // Fix any invalid points caused by invalid layouts + // let pointXMax = points[0].y; + + // for (const point of points) { + // // Ensure increasing Y values for each point + // if (point.x < pointXMax) { + // point.x = pointXMax; + // } else { + // pointXMax = point.x; + // } + // } // Assign finished points to edge edge.points = points; From ac00f63fce26755c72171c31acec8390387523d8 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Fri, 4 Oct 2024 09:41:35 +0100 Subject: [PATCH 03/13] wip --- src/actions/graph.js | 5 ++++- src/selectors/layout.js | 6 ++++-- src/utils/graph/graph.js | 6 +++--- src/utils/graph/index.js | 9 ++++++--- src/utils/graph/layout.js | 5 +---- src/utils/graph/routing.js | 5 ++--- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/actions/graph.js b/src/actions/graph.js index 4e39794e8d..a59946b4ba 100644 --- a/src/actions/graph.js +++ b/src/actions/graph.js @@ -33,7 +33,10 @@ export function updateGraph(graph) { * @param {Object} state A subset of main state * @return {Function} Promise function */ -const layout = async (instance, state) => instance.graphNew(state); + +const layout = async (instance, state) => { + return instance.graphNew(state); +} // Prepare new layout worker const layoutWorker = preventWorkerQueues(worker, layout); diff --git a/src/selectors/layout.js b/src/selectors/layout.js index d275ac990e..f7814107d3 100644 --- a/src/selectors/layout.js +++ b/src/selectors/layout.js @@ -19,6 +19,7 @@ const getVisibleCode = (state) => state.visible.code; const getIgnoreLargeWarning = (state) => state.ignoreLargeWarning; const getGraphHasNodes = (state) => Boolean(state.graph?.nodes?.length); const getChartSizeState = (state) => state.chartSize; +const getExpand = (state) => state.expandPipelines; /** * Show the large graph warning only if there are sufficient nodes + edges, @@ -51,13 +52,14 @@ export const getGraphInput = createSelector( getVisibleEdges, getVisibleLayerIDs, getTriggerLargeGraphWarning, + getExpand, ], - (nodes, edges, layers, triggerLargeGraphWarning) => { + (nodes, edges, layers, triggerLargeGraphWarning, expand) => { if (triggerLargeGraphWarning) { return null; } - return { nodes, edges, layers }; + return { nodes, edges, layers, expand }; } ); diff --git a/src/utils/graph/graph.js b/src/utils/graph/graph.js index 989a5b77d9..4d6cd6870f 100644 --- a/src/utils/graph/graph.js +++ b/src/utils/graph/graph.js @@ -41,12 +41,12 @@ const defaultOptions = { * @param {Object=} options The graph options * @returns {Object} The generated graph */ -export const graph = (nodes, edges, layers, options = defaultOptions) => { +export const graph = (nodes, edges, layers, orientation, options = defaultOptions) => { addEdgeLinks(nodes, edges); addNearestLayers(nodes, layers); - layout({ nodes, edges, layers, ...options.layout }); - routing({ nodes, edges, layers, ...options.routing }); + layout({ nodes, edges, layers, orientation, ...options.layout }); + routing({ nodes, edges, layers, orientation, ...options.routing }); const size = bounds(nodes, options.layout.padding); nodes.forEach((node) => offsetNode(node, size.min)); diff --git a/src/utils/graph/index.js b/src/utils/graph/index.js index a76602ee70..9aee38f432 100644 --- a/src/utils/graph/index.js +++ b/src/utils/graph/index.js @@ -6,7 +6,7 @@ import { graph } from './graph'; * as possible, and keep it separate from other properties (like node.active) * which don't affect layout. */ -export const graphNew = ({ nodes, edges, layers }) => { +export const graphNew = ({ nodes, edges, layers, expand =false }) => { for (const node of nodes) { node.iconSize = node.iconSize || 24; node.icon = node.icon || 'node'; @@ -24,8 +24,11 @@ export const graphNew = ({ nodes, edges, layers }) => { node.textOffset = node.textOffset || (innerWidth - textWidth) / 2; node.iconOffset = node.iconOffset || -innerWidth / 2; } - - const result = graph(nodes, edges, layers); + let orientation = "top-to-bottom"; + if (expand) { + orientation = "right-to-left" + } + const result = graph(nodes, edges, layers, orientation); return { ...result, diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index 5756d39733..571a8dd997 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -32,12 +32,9 @@ export const layout = ({ spreadX, layerSpaceY, iterations, + orientation, }) => { - - let orientation = 'top-to-bottom' - orientation = 'right-to-left' - let coordPrimary = 'x'; let coordSecondary = 'y'; let sizePrimary = 'width'; diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index d16bd6cfff..8897be1901 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -40,10 +40,9 @@ export const routing = ({ stemMax, stemSpaceSource, stemSpaceTarget, + orientation, }) => { - // Find the rows formed by nodes - let orientation = 'top-to-bottom'; - orientation = 'right-to-left'; + const rows = groupByRow(nodes); // For each node From aa2e9e05e992c94ba35913462e952daf3355aa51 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 15 Oct 2024 15:55:32 +0100 Subject: [PATCH 04/13] fixed layout --- .../flowchart-primary-toolbar.js | 5 +- src/selectors/layout.js | 8 +- src/utils/graph/common.js | 23 +- src/utils/graph/constraints.js | 14 +- src/utils/graph/index.js | 6 +- src/utils/graph/layout.js | 197 +++++++----------- src/utils/graph/routing.js | 8 +- 7 files changed, 112 insertions(+), 149 deletions(-) diff --git a/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js b/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js index c63cc9f186..7ab896c4d8 100644 --- a/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js +++ b/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js @@ -66,10 +66,9 @@ export const FlowchartPrimaryToolbar = ({ ariaLabel={`Turn data layers ${visibleLayers ? 'off' : 'on'}`} className={'pipeline-menu-button--layers'} dataTest={`sidebar-flowchart-layers-btn-${visibleLayers}`} - disabled={disableLayerBtn} icon={LayersIcon} labelText={`${visibleLayers ? 'Hide' : 'Show'} layers`} - onClick={() => onToggleLayers(!visibleLayers)} + onClick={() => onToggleLayers(!disableLayerBtn)} visible={display.layerBtn} /> ({ - disableLayerBtn: !state.layer.ids.length, + disableLayerBtn: state.layer.visible, textLabels: state.textLabels, visible: state.visible, display: state.display, diff --git a/src/selectors/layout.js b/src/selectors/layout.js index f7814107d3..58f342e374 100644 --- a/src/selectors/layout.js +++ b/src/selectors/layout.js @@ -19,7 +19,7 @@ const getVisibleCode = (state) => state.visible.code; const getIgnoreLargeWarning = (state) => state.ignoreLargeWarning; const getGraphHasNodes = (state) => Boolean(state.graph?.nodes?.length); const getChartSizeState = (state) => state.chartSize; -const getExpand = (state) => state.expandPipelines; +const getOrient = (state) => state.layer.visible; /** * Show the large graph warning only if there are sufficient nodes + edges, @@ -52,14 +52,14 @@ export const getGraphInput = createSelector( getVisibleEdges, getVisibleLayerIDs, getTriggerLargeGraphWarning, - getExpand, + getOrient, ], - (nodes, edges, layers, triggerLargeGraphWarning, expand) => { + (nodes, edges, layers, triggerLargeGraphWarning, orient) => { if (triggerLargeGraphWarning) { return null; } - return { nodes, edges, layers, expand }; + return { nodes, edges, layers, orient }; } ); diff --git a/src/utils/graph/common.js b/src/utils/graph/common.js index ef1e0b7ba7..08ca28937d 100644 --- a/src/utils/graph/common.js +++ b/src/utils/graph/common.js @@ -75,24 +75,30 @@ export const nodeBottom = (node) => node.y + node.height * 0.5; * @param {Array} nodes The input nodes * @returns {Array} The sorted rows of nodes */ -export const groupByRow = (nodes) => { +export const groupByRow = (nodes, orientation = 'top-to-bottom') => { const rows = {}; - // Create rows using node Y values + // Define the coordinate keys based on the orientation + const primaryCoord = orientation === 'left-to-right' ? 'x' : 'y'; + const secondaryCoord = orientation === 'left-to-right' ? 'y' : 'x'; + + // Create rows using the primary coordinate (Y for top-to-bottom, X for left-to-right) for (const node of nodes) { - rows[node.y] = rows[node.y] || []; - rows[node.y].push(node); + const key = snap(node[primaryCoord], 10); + rows[key] = rows[key] || []; + rows[key].push(node); } - // Sort the set of rows accounting for keys being strings + // Sort the set of rows by the primary coordinate const rowNumbers = Object.keys(rows).map((row) => parseFloat(row)); rowNumbers.sort((a, b) => a - b); - // Sort rows in order of X position if set. Break ties with ids for stability + // Sort rows in order of the secondary coordinate, then by ids for stability const sortedRows = rowNumbers.map((row) => rows[row]); for (let i = 0; i < sortedRows.length; i += 1) { - sortedRows[i].sort((a, b) => compare(a.x, b.x, a.id, b.id)); - + sortedRows[i].sort((a, b) => compare(a[secondaryCoord], b[secondaryCoord], a.id, b.id)); + + // Assign row index to each node in the row for (const node of sortedRows[i]) { node.row = i; } @@ -101,6 +107,7 @@ export const groupByRow = (nodes) => { return sortedRows; }; + /** * Generalised comparator function for sorting * If values are strings then `localeCompare` is used, otherwise values are subtracted diff --git a/src/utils/graph/constraints.js b/src/utils/graph/constraints.js index f436108d8b..c55e9290ab 100644 --- a/src/utils/graph/constraints.js +++ b/src/utils/graph/constraints.js @@ -12,13 +12,11 @@ import { Constraint, Operator, Strength } from 'kiwi.js'; * Layout constraint in Y for separating rows */ export const rowConstraint = { - property: 'y', - strict: (constraint, constants, variableA, variableB) => new Constraint( variableA.minus(variableB), Operator.Ge, - constants.spaceSecondary, + constraint.separation, Strength.required ), }; @@ -42,13 +40,13 @@ export const layerConstraint = { * Layout constraint in X for minimising distance from source to target for straight edges */ export const parallelConstraint = { - property: 'y', + property: 'x', - solve: (constraint) => { + solve: (constraint, constants) => { const { a, b, strength } = constraint; - const resolve = strength * (a.x - b.x); - a.x -= resolve; - b.x += resolve; + const resolve = strength * (a[constants.coordPrimary] - b[constants.coordPrimary]); + a[constants.coordPrimary] -= resolve; + b[constants.coordPrimary] += resolve; }, strict: (constraint, constants, variableA, variableB) => diff --git a/src/utils/graph/index.js b/src/utils/graph/index.js index 9aee38f432..dee58aa403 100644 --- a/src/utils/graph/index.js +++ b/src/utils/graph/index.js @@ -6,7 +6,7 @@ import { graph } from './graph'; * as possible, and keep it separate from other properties (like node.active) * which don't affect layout. */ -export const graphNew = ({ nodes, edges, layers, expand =false }) => { +export const graphNew = ({ nodes, edges, layers, orient =false }) => { for (const node of nodes) { node.iconSize = node.iconSize || 24; node.icon = node.icon || 'node'; @@ -25,8 +25,8 @@ export const graphNew = ({ nodes, edges, layers, expand =false }) => { node.iconOffset = node.iconOffset || -innerWidth / 2; } let orientation = "top-to-bottom"; - if (expand) { - orientation = "right-to-left" + if (orient) { + orientation = "left-to-right" } const result = graph(nodes, edges, layers, orientation); diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index 571a8dd997..a74ab57504 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -1,4 +1,4 @@ -import { HALF_PI, snap, angle, compare, groupByRow } from './common'; +import { HALF_PI, snap, angle, groupByRow } from './common'; import { solveLoose, solveStrict } from './solver'; import { rowConstraint, @@ -31,29 +31,15 @@ export const layout = ({ spaceY, spreadX, layerSpaceY, - iterations, orientation, }) => { let coordPrimary = 'x'; let coordSecondary = 'y'; - let sizePrimary = 'width'; - let sizeSecondary = 'height'; - let spacePrimary = spaceX; - let spaceSecondary = spaceY; - let spreadPrimary = spreadX; - let layerSpace = (spaceY + layerSpaceY) * 0.5; - - if (orientation === 'right-to-left') { + + if (orientation === 'left-to-right') { coordPrimary = 'y'; coordSecondary = 'x'; - sizePrimary = 'height'; - sizeSecondary = 'width'; - spacePrimary = spaceY; - spaceSecondary = spaceX; - spreadPrimary = spreadX; // Adjust if necessary - layerSpace = (spaceX + layerSpaceY) * 0.5; - } // Set initial positions for nodes @@ -66,14 +52,12 @@ export const layout = ({ // Constants used by constraints const constants = { orientation, - spacePrimary, - spaceSecondary, - spreadPrimary, - layerSpace, + spaceX, + spaceY, + spreadX, + layerSpace: (spaceX + layerSpaceY) * 0.5, coordPrimary, coordSecondary, - sizePrimary, - sizeSecondary }; // Constraints to separate nodes into rows and layers @@ -84,17 +68,17 @@ export const layout = ({ solveStrict([...rowConstraints, ...layerConstraints], constants, 1); // Find the solved rows using the node positions after solving - const rows = groupByRow(nodes); + const rows = groupByRow(nodes, orientation); // Constraints to avoid edges crossing and maintain parallel vertical edges const crossingConstraints = createCrossingConstraints(edges, constants); const parallelConstraints = createParallelConstraints(edges, constants); // Solve these constraints iteratively - for (let i = 0; i < iterations; i += 1) { + // for (let i = 0; i < iterations; i += 1) { solveLoose(crossingConstraints, 1, constants); solveLoose(parallelConstraints, 50, constants); - } + // } // Constraints to maintain a minimum horizontal node spacing const separationConstraints = createSeparationConstraints(rows, constants); @@ -103,7 +87,7 @@ export const layout = ({ solveStrict([...separationConstraints, ...parallelConstraints], constants, 1); // Adjust vertical spacing between rows for legibility - expandDenseRows(edges, rows, coordSecondary, spaceSecondary); + expandDenseRows(edges, rows, coordSecondary, spaceY, orientation); }; /** @@ -112,11 +96,15 @@ export const layout = ({ * @returns {Array} The constraints */ const createRowConstraints = (edges, constants) => - edges.map((edge) => ({ - base: rowConstraint, - a: edge.targetNode, - b: edge.sourceNode, - })); + edges.map((edge) => { + return { + base: rowConstraint, + property: constants.coordSecondary, + a: edge.targetNode, + b: edge.sourceNode, + separation: constants.spaceY, + }; + }); /** * Creates layer constraints for the given nodes and layers. @@ -173,11 +161,11 @@ const createLayerConstraints = (nodes, layers, constants) => { * Creates crossing constraints for the given edges. * @param {Array} edges The input edges * @param {Object} constants The constraint constants - * @param {Number} constants.spacePrimary The minimum gap between nodes in X + * @param {Number} constants.spaceX The minimum gap between nodes in X * @returns {Array} The constraints */ const createCrossingConstraints = (edges, constants) => { - const { spacePrimary, sizePrimary, coordPrimary } = constants; + const { spaceX, coordPrimary } = constants; const crossingConstraints = []; // For every pair of edges @@ -214,8 +202,8 @@ const createCrossingConstraints = (edges, constants) => { edgeA: edgeA, edgeB: edgeB, // The required horizontal spacing between connected nodes - separationA: sourceA[sizePrimary] * 0.5 + spacePrimary + sourceB[sizePrimary] * 0.5, - separationB: targetA[sizePrimary] * 0.5 + spacePrimary + targetB[sizePrimary] * 0.5, + separationA: sourceA.width * 0.5 + spaceX + sourceB.width * 0.5, + separationB: targetA.width * 0.5 + spaceX + targetB.width * 0.5, // Evenly distribute the constraint strength: 1 / Math.max(1, (edgeADegree + edgeBDegree) / 4), }); @@ -251,111 +239,82 @@ const createParallelConstraints = (edges, constants) => * @returns {Array} The constraints */ const createSeparationConstraints = (rows, constants) => { - const { spacePrimary, spaceSecondary, coordPrimary, coordSecondary, sizePrimary, sizeSecondary, orientation, spreadPrimary } = constants; + const { spaceX, coordPrimary, spreadX, orientation } = constants; const separationConstraints = []; - console.log(rows) - - rows.forEach((rowNodes, rowIndex) => { - // Separation constraints along the primary axis (either x or y) - if (orientation === 'top-to-bottom') { - // Sort row nodes along the primary axis - rowNodes.sort((a, b) => compare(a[coordPrimary], b[coordPrimary], a.id, b.id)); - // Handle separation between nodes within the same row - for (let j = 0; j < rowNodes.length - 1; j++) { - const nodeA = rowNodes[j]; - const nodeB = rowNodes[j + 1]; - - // Calculate degrees and spacing for the primary axis (either x or y) - const degreeA = Math.max(1, nodeA.targets.length + nodeA.sources.length - 2); - const degreeB = Math.max(1, nodeB.targets.length + nodeB.sources.length - 2); - const spread = Math.min(10, degreeA * degreeB * spreadPrimary); - const space = snap(spread * spacePrimary, spacePrimary); - separationConstraints.push({ - base: separationConstraint, - property: coordPrimary, // Separation along the primary axis - a: nodeA, - b: nodeB, - separation: nodeA[sizePrimary] * 0.5 + space + nodeB[sizePrimary] * 0.5, - }); - } - } - - if (orientation === 'right-to-left') { - // Ensure we're not at the last row - rowNodes.sort((a, b) => compare(a.y, b.y, a.id, b.id)); - if (rowIndex < rows.length - 1) { - const nextRowNodes = rows[rowIndex + 1]; // Get nodes from the next row - const maxIndex = nextRowNodes.reduce((maxIdx, node, idx) => node.width > nextRowNodes[maxIdx].width ? idx : maxIdx, 0); - const nodeB = nextRowNodes[maxIndex]; - - // Loop through each node in the current row - for (let j = 0; j < rowNodes.length; j++) { - const nodeA = rowNodes[j]; - const nodeC = j + 1 < rowNodes.length ? rowNodes[j + 1] : null; // Next node in the same row - - const degreeA = Math.max(1, nodeA.targets.length + nodeA.sources.length - 2); - const degreeB = Math.max(1, nodeB.targets.length + nodeB.sources.length - 2); - const spread = Math.min(10, degreeA * degreeB * spreadPrimary); - const space = snap(spread * spaceSecondary, spaceSecondary); - - // If there is a next node in the same row, apply primary axis separation (y-axis for right-to-left) - if (nodeC) { - const degreeC = Math.max(1, nodeC.targets.length + nodeC.sources.length - 2); - const spreadC = Math.min(10, degreeA * degreeC * spreadPrimary); - const spaceC = snap(spreadC * spaceSecondary, spaceSecondary); - separationConstraints.push({ - base: separationConstraint, - property: coordPrimary, - a: nodeA, - b: nodeC, - separation: nodeA.height + spaceC + nodeC.height - }); - } - separationConstraints.push({ - base: separationConstraint, - property: coordSecondary, - a: nodeA, - b: nodeB, - separation: nodeA[sizeSecondary]*0.5 + space + nodeB[sizeSecondary]*0.5, - }); - } - + // For each row of nodes + for (let i = 0; i < rows.length; i += 1) { + const rowNodes = rows[i]; + + // Update constraints for the sorted row node order + for (let j = 0; j < rowNodes.length-1; j += 1) { + const nodeA = rowNodes[j]; + const nodeB = rowNodes[j + 1]; + + + // Count the connected edges + const degreeA = Math.max(1, nodeA.targets.length + nodeA.sources.length - 2); + const degreeB = Math.max(1, nodeB.targets.length + nodeB.sources.length - 2); + + // Calculate spacing and separation for primary orientation + const spread = Math.min(10, degreeA * degreeB * spreadX); + const space = snap(spread * spaceX, spaceX); + let separation = nodeA.width * 0.5 + space + nodeB.width * 0.5; + + // Adjust separation if the orientation is 'left-to-right' (using height instead of width) + if (orientation === 'left-to-right') { + separation = nodeA.height + nodeB.height; } - } - }); - + separationConstraints.push({ + base: separationConstraint, + property: coordPrimary, + a: nodeA, + b: nodeB, + separation, + }); + } + } return separationConstraints; }; - - /** * Adds additional spacing in Y relative to row density, see function `rowDensity` for definition. * Node positions are updated in-place * @param {Array} edges The input edges * @param {Array} rows The input rows of nodes - * @param {Number} spaceSecondary The spacing between nodes in Y + * @param {Number} spaceY The spacing between nodes in Y * @param {Number} [scale=1.25] The amount of expansion to apply relative to row density - * @param {Number} [unit=0.25] The unit size for rounding expansion relative to spaceSecondary + * @param {Number} [unit=0.25] The unit size for rounding expansion relative to spaceY */ -const expandDenseRows = (edges, rows, coordSecondary, spaceSecondary, scale = 1.25, unit = 0.25) => { +const expandDenseRows = (edges, rows, coordSecondary, spaceY, orientation, scale = 1.25, unit = 0.25, ) => { const densities = rowDensity(edges); - const spaceSecondaryUnit = Math.round(spaceSecondary * unit); - let currentOffsetY = 0; + const spaceYUnit = Math.round(spaceY * unit); + let currentOffset = 0; // Use generic offset instead of currentOffsetY // Add spacing based relative to row density for (let i = 0; i < rows.length - 1; i += 1) { const density = densities[i] || 0; - // Round offset to a common unit amount to improve vertical rhythm - const offsetY = snap(density * scale * spaceSecondary, spaceSecondaryUnit); - currentOffsetY += offsetY; + // Round offset to a common unit amount to improve rhythm + const offset = snap(density * scale * spaceY, spaceYUnit); + + // Only calculate width-based offset if orientation is 'left-to-right' + if (orientation === 'left-to-right') { + // Calculate the maximum width between the two rows + const maxWidthInCurrentRow = Math.max(...rows[i].map(node => node.width)); + const maxWidthInNextRow = Math.max(...rows[i + 1].map(node => node.width)); + + // Adjust current offset by the max width between rows + currentOffset += offset + maxWidthInCurrentRow*0.25 + maxWidthInNextRow*0.25; + } else { + // If not left-to-right, only add the calculated offset + currentOffset += offset; + } - // Apply offset to all nodes following the current node + // Apply offset to all nodes following the current node in the appropriate coordinate (X or Y) for (const node of rows[i + 1]) { - node[coordSecondary] += currentOffsetY; + node[coordSecondary] += currentOffset; // This now handles both 'x' and 'y' } } }; diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 8897be1901..46f723a340 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -43,7 +43,7 @@ export const routing = ({ orientation, }) => { - const rows = groupByRow(nodes); + const rows = groupByRow(nodes, orientation); // For each node for (const node of nodes) { @@ -237,8 +237,8 @@ export const routing = ({ y: nodeTop(target), }, ]; - } else if (orientation === 'right-to-left') { - // Build the source stem for the edge (right-to-left) + } else if (orientation === 'left-to-right') { + // Build the source stem for the edge (left-to-right) sourceStem = [ { x: nodeRight(source), @@ -254,7 +254,7 @@ export const routing = ({ }, ]; - // Build the target stem for the edge (right-to-left) + // Build the target stem for the edge (left-to-right) targetStem = [ { y: target.y + targetOffsetY, From 941193ac43264bc4670097241809250906a4ec2e Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 15 Oct 2024 17:06:40 +0100 Subject: [PATCH 05/13] fix some crossing issues --- src/utils/graph/layout.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index a74ab57504..f88a0af198 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -1,4 +1,4 @@ -import { HALF_PI, snap, angle, groupByRow } from './common'; +import { HALF_PI, snap, angle, compare, groupByRow } from './common'; import { solveLoose, solveStrict } from './solver'; import { rowConstraint, @@ -245,6 +245,8 @@ const createSeparationConstraints = (rows, constants) => { // For each row of nodes for (let i = 0; i < rows.length; i += 1) { const rowNodes = rows[i]; + + rowNodes.sort((a, b) => compare(a.x, b.x, a.id, b.id)); // Update constraints for the sorted row node order for (let j = 0; j < rowNodes.length-1; j += 1) { From ecffb6f3776a5fe5d837be07158a9fa3a78d2a15 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Wed, 16 Oct 2024 11:46:14 +0100 Subject: [PATCH 06/13] wip --- package.json | 2 +- src/utils/graph/constraints.js | 17 +++++++---------- src/utils/graph/layout.js | 8 +++++--- src/utils/graph/routing.js | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 5883aa9594..f3899a366f 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "postbuild": "rm -rf build/api", "start": "REACT_APP_DATA_SOURCE=$DATA NODE_OPTIONS=\"--dns-result-order=ipv4first\" npm-run-all -p start:app start:lib", "start:dev": "rm -rf node_modules/.cache && npm start", - "start:app": "PORT=4141 react-scripts start", + "start:app": "PORT=4143 react-scripts start", "start:lib": "rm -rf lib && babel src --out-dir lib --copy-files --watch", "lib": "npm-run-all -s lib:clean lib:copy lib:webpack lib:babel lib:prune", "lib:clean": "rm -rf lib", diff --git a/src/utils/graph/constraints.js b/src/utils/graph/constraints.js index c55e9290ab..a16f720094 100644 --- a/src/utils/graph/constraints.js +++ b/src/utils/graph/constraints.js @@ -40,7 +40,6 @@ export const layerConstraint = { * Layout constraint in X for minimising distance from source to target for straight edges */ export const parallelConstraint = { - property: 'x', solve: (constraint, constants) => { const { a, b, strength } = constraint; @@ -62,25 +61,24 @@ export const parallelConstraint = { * Crossing constraint in X for minimising edge crossings */ export const crossingConstraint = { - property: 'x', - solve: (constraint) => { + solve: (constraint, constants) => { const { edgeA, edgeB, separationA, separationB, strength } = constraint; // Amount to move each node towards required separation const resolveSource = strength * - ((edgeA.sourceNode.x - edgeB.sourceNode.x - separationA) / separationA); + ((edgeA.sourceNode[constants.coordPrimary] - edgeB.sourceNode[constants.coordPrimary] - separationA) / separationA); const resolveTarget = strength * - ((edgeA.targetNode.x - edgeB.targetNode.x - separationB) / separationB); + ((edgeA.targetNode[constants.coordPrimary] - edgeB.targetNode[constants.coordPrimary] - separationB) / separationB); // Apply the resolve each node - edgeA.sourceNode.x -= resolveSource; - edgeB.sourceNode.x += resolveSource; - edgeA.targetNode.x -= resolveTarget; - edgeB.targetNode.x += resolveTarget; + edgeA.sourceNode[constants.coordPrimary] -= resolveSource; + edgeB.sourceNode[constants.coordPrimary] += resolveSource; + edgeA.targetNode[constants.coordPrimary] -= resolveTarget; + edgeB.targetNode[constants.coordPrimary] += resolveTarget; }, }; @@ -88,7 +86,6 @@ export const crossingConstraint = { * Layout constraint in X for minimum node separation */ export const separationConstraint = { - property: 'x', strict: (constraint, constants, variableA, variableB) => new Constraint( diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index f88a0af198..417ea03fe6 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -31,6 +31,7 @@ export const layout = ({ spaceY, spreadX, layerSpaceY, + iterations, orientation, }) => { @@ -75,10 +76,11 @@ export const layout = ({ const parallelConstraints = createParallelConstraints(edges, constants); // Solve these constraints iteratively - // for (let i = 0; i < iterations; i += 1) { + for (let i = 0; i < 300; i += 1) { + console.log("Being applied") solveLoose(crossingConstraints, 1, constants); solveLoose(parallelConstraints, 50, constants); - // } + } // Constraints to maintain a minimum horizontal node spacing const separationConstraints = createSeparationConstraints(rows, constants); @@ -246,7 +248,7 @@ const createSeparationConstraints = (rows, constants) => { for (let i = 0; i < rows.length; i += 1) { const rowNodes = rows[i]; - rowNodes.sort((a, b) => compare(a.x, b.x, a.id, b.id)); + rowNodes.sort((a, b) => compare(a[coordPrimary], b[coordPrimary], a.id, b.id)); // Update constraints for the sorted row node order for (let j = 0; j < rowNodes.length-1; j += 1) { diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 46f723a340..3f4ea83f0d 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -43,7 +43,7 @@ export const routing = ({ orientation, }) => { - const rows = groupByRow(nodes, orientation); + const rows = groupByRow(nodes); // For each node for (const node of nodes) { From 0d4e3ccd93da68071efe8fc78a88da7daf1ced5d Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Thu, 17 Oct 2024 12:39:18 +0100 Subject: [PATCH 07/13] in a working state --- src/utils/graph/common.js | 16 ++++++++++++++-- src/utils/graph/layout.js | 11 +++++------ src/utils/graph/routing.js | 24 ++++++++++++------------ 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/utils/graph/common.js b/src/utils/graph/common.js index 08ca28937d..6835736df6 100644 --- a/src/utils/graph/common.js +++ b/src/utils/graph/common.js @@ -33,12 +33,24 @@ export const snap = (value, unit) => Math.round(value / unit) * unit; export const distance1d = (a, b) => Math.abs(a - b); /** - * Returns the angle in radians between the points a and b relative to the X-axis about the origin + * Returns the angle in radians between the points a and b based on the given orientation * @param {Object} a The first point * @param {Object} b The second point + * @param {String} orientation The layout orientation ('top-to-bottom' or 'left-to-right') * @returns {Number} The angle */ -export const angle = (a, b) => Math.atan2(a.y - b.y, a.x - b.x); +export const angle = (a, b, orientation) => { + if (orientation === 'top-to-bottom') { + // Top-to-bottom orientation + return Math.atan2(a.y - b.y, a.x - b.x); + } else if (orientation === 'left-to-right') { + // Left-to-right orientation + return Math.atan2(a.x - b.x, a.y - b.y); + } else { + throw new Error(`Unsupported orientation: ${orientation}`); + } +}; + /** * Returns the left edge x-position of the node diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index 417ea03fe6..aa2b923ae2 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -76,8 +76,7 @@ export const layout = ({ const parallelConstraints = createParallelConstraints(edges, constants); // Solve these constraints iteratively - for (let i = 0; i < 300; i += 1) { - console.log("Being applied") + for (let i = 0; i < iterations; i += 1) { solveLoose(crossingConstraints, 1, constants); solveLoose(parallelConstraints, 50, constants); } @@ -292,7 +291,7 @@ const createSeparationConstraints = (rows, constants) => { * @param {Number} [unit=0.25] The unit size for rounding expansion relative to spaceY */ const expandDenseRows = (edges, rows, coordSecondary, spaceY, orientation, scale = 1.25, unit = 0.25, ) => { - const densities = rowDensity(edges); + const densities = rowDensity(edges, orientation); const spaceYUnit = Math.round(spaceY * unit); let currentOffset = 0; // Use generic offset instead of currentOffsetY @@ -310,7 +309,7 @@ const expandDenseRows = (edges, rows, coordSecondary, spaceY, orientation, scale const maxWidthInNextRow = Math.max(...rows[i + 1].map(node => node.width)); // Adjust current offset by the max width between rows - currentOffset += offset + maxWidthInCurrentRow*0.25 + maxWidthInNextRow*0.25; + currentOffset += offset + maxWidthInCurrentRow*0.5 + maxWidthInNextRow*0.5; } else { // If not left-to-right, only add the calculated offset currentOffset += offset; @@ -332,13 +331,13 @@ const expandDenseRows = (edges, rows, coordSecondary, spaceY, orientation, scale * @param {Array} edges The input edges * @returns {Array} The density of each row */ -const rowDensity = (edges) => { +const rowDensity = (edges, orientation) => { const rows = {}; for (const edge of edges) { // Find the normalized angle of the edge source and target nodes, relative to the X axis const edgeAngle = - Math.abs(angle(edge.targetNode, edge.sourceNode) - HALF_PI) / HALF_PI; + Math.abs(angle(edge.targetNode, edge.sourceNode, orientation) - HALF_PI) / HALF_PI; const sourceRow = edge.sourceNode.row; const targetRow = edge.targetNode.row - 1; diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 3f4ea83f0d..485e306bb2 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -50,8 +50,8 @@ export const routing = ({ // Sort the node's target edges by the angle between source and target nodes node.targets.sort((a, b) => compare( - angle(b.sourceNode, b.targetNode), - angle(a.sourceNode, a.targetNode) + angle(b.sourceNode, b.targetNode, orientation), + angle(a.sourceNode, a.targetNode, orientation) ) ); } @@ -154,15 +154,15 @@ export const routing = ({ // Sort the node's outgoing edges by the starting angle of the edge path node.targets.sort((a, b) => compare( - angle(b.sourceNode, b.points[0] || b.targetNode), - angle(a.sourceNode, a.points[0] || a.targetNode) + angle(b.sourceNode, b.points[0] || b.targetNode, orientation), + angle(a.sourceNode, a.points[0] || a.targetNode, orientation) ) ); // Sort the node's incoming edges by the ending angle of the edge path node.sources.sort((a, b) => compare( - angle(a.points[a.points.length - 1] || a.sourceNode, a.targetNode), - angle(b.points[b.points.length - 1] || b.sourceNode, b.targetNode) + angle(a.points[a.points.length - 1] || a.sourceNode, a.targetNode, orientation), + angle(b.points[b.points.length - 1] || b.sourceNode, b.targetNode, orientation) ) ); } @@ -242,14 +242,14 @@ export const routing = ({ sourceStem = [ { x: nodeRight(source), - y: source.y + sourceOffsetY, + y: source.y + sourceOffsetX, }, { - y: source.y + sourceOffsetY, + y: source.y + sourceOffsetX, x: nodeRight(source) + stemMinSource, }, { - y: source.y + sourceOffsetY, + y: source.y + sourceOffsetX, x: nodeRight(source) + stemMinSource + Math.min(sourceOffsetY, stemMax), }, ]; @@ -257,15 +257,15 @@ export const routing = ({ // Build the target stem for the edge (left-to-right) targetStem = [ { - y: target.y + targetOffsetY, + y: target.y + targetOffsetX, x: nodeLeft(target) - stemMinTarget - Math.min(targetOffsetY, stemMax), }, { - y: target.y + targetOffsetY, + y: target.y + targetOffsetX, x: nodeLeft(target) - stemMinTarget, }, { - y: target.y + targetOffsetY, + y: target.y + targetOffsetX, x: nodeLeft(target), }, ]; From 407a725c64d231b6dedcbedc94994646d5c46242 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Thu, 17 Oct 2024 13:00:07 +0100 Subject: [PATCH 08/13] in a working state --- src/utils/graph/graph.js | 2 +- src/utils/graph/routing.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/graph/graph.js b/src/utils/graph/graph.js index 4d6cd6870f..4e7610f959 100644 --- a/src/utils/graph/graph.js +++ b/src/utils/graph/graph.js @@ -24,7 +24,7 @@ const defaultOptions = { stemUnit: 8, stemMinSource: 5, stemMinTarget: 5, - stemMax: 20, + stemMax: 10, stemSpaceSource: 6, stemSpaceTarget: 10, }, diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 485e306bb2..df23a9344c 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -43,7 +43,7 @@ export const routing = ({ orientation, }) => { - const rows = groupByRow(nodes); + const rows = groupByRow(nodes, orientation); // For each node for (const node of nodes) { From bc124d1f86a0c45ac0daf4347c3ada97a72e6709 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Thu, 17 Oct 2024 16:36:57 +0100 Subject: [PATCH 09/13] temp soln for weird edges --- src/utils/graph/routing.js | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index df23a9344c..8e058c43cf 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -105,17 +105,30 @@ export const routing = ({ continue; } + const offsetX = Math.min(spaceX, nodeGap * 0.5); - // Find the next potential point. Include offset to reduce overlapping edges - const candidatePoint = nearestOnLine( - currentPoint.x, - currentPoint.y, - nodeRight(node) + offsetX, - nodeTop(node) - spaceY, - nodeLeft(nextNode) - offsetX, - nodeTop(nextNode) - spaceY - ); + // Define variables for source and target positions based on orientation + let sourceX, sourceY, targetX, targetY; + + if (orientation === 'top-to-bottom') { + // Top-to-bottom orientation + sourceX = nodeRight(node) + offsetX; // Right side of the current node + sourceY = nodeTop(node) - spaceY; // Above the current node + targetX = nodeLeft(nextNode) - offsetX; // Left side of the next node + targetY = nodeTop(nextNode) - spaceY; // Above the next node + } + + // Calculate the nearest point using the computed source and target positions + const candidatePoint = nearestOnLine( + currentPoint.x, + currentPoint.y, + sourceX, + sourceY, + targetX, + targetY + ); + const distance = distance1d(currentPoint.x, candidatePoint.x); From 605828951c1cb9e9248a1b88f402ec7b59104199 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Fri, 18 Oct 2024 15:45:15 +0100 Subject: [PATCH 10/13] add layers logic --- demo-project/conf/base/catalog_01_raw.yml | 6 +++--- demo-project/conf/base/catalog_02_int.yml | 8 ++++---- demo-project/conf/base/catalog_03_prm.yml | 4 ++-- demo-project/conf/base/catalog_04_feature.yml | 4 ++-- .../conf/base/catalog_05_model_input.yml | 2 +- src/selectors/layers.js | 16 ++++++++-------- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/demo-project/conf/base/catalog_01_raw.yml b/demo-project/conf/base/catalog_01_raw.yml index c5a76bc83b..b2d71bf82c 100644 --- a/demo-project/conf/base/catalog_01_raw.yml +++ b/demo-project/conf/base/catalog_01_raw.yml @@ -3,7 +3,7 @@ companies: filepath: ${_base_location}/01_raw/companies.csv metadata: kedro-viz: - layer1: raw + layer: raw preview_args: nrows: 5 @@ -12,7 +12,7 @@ reviews: filepath: ${_base_location}/01_raw/reviews.csv metadata: kedro-viz: - layer1: raw + layer: raw preview_args: nrows: 10 @@ -22,6 +22,6 @@ shuttles: filepath: ${_base_location}/01_raw/shuttles.xlsx metadata: kedro-viz: - layer1: raw + layer: raw preview_args: nrows: 15 diff --git a/demo-project/conf/base/catalog_02_int.yml b/demo-project/conf/base/catalog_02_int.yml index 7377477113..9b75ff1f17 100644 --- a/demo-project/conf/base/catalog_02_int.yml +++ b/demo-project/conf/base/catalog_02_int.yml @@ -3,25 +3,25 @@ ingestion.int_typed_companies: filepath: ${_base_location}/02_intermediate/typed_companies.pq metadata: kedro-viz: - layer1: intermediate + layer: intermediate ingestion.int_typed_shuttles@pandas1: type: pandas.ParquetDataset filepath: ${_base_location}/02_intermediate/typed_shuttles.pq metadata: kedro-viz: - layer1: intermediate + layer: intermediate ingestion.int_typed_shuttles@pandas2: type: pandas.ParquetDataset filepath: ${_base_location}/02_intermediate/typed_shuttles.pq metadata: kedro-viz: - layer1: intermediate + layer: intermediate ingestion.int_typed_reviews: type: pandas.ParquetDataset filepath: ${_base_location}/02_intermediate/typed_reviews.pq metadata: kedro-viz: - layer1: intermediate + layer: intermediate diff --git a/demo-project/conf/base/catalog_03_prm.yml b/demo-project/conf/base/catalog_03_prm.yml index 0f454bc7fc..f86f45854a 100644 --- a/demo-project/conf/base/catalog_03_prm.yml +++ b/demo-project/conf/base/catalog_03_prm.yml @@ -3,11 +3,11 @@ prm_shuttle_company_reviews: filepath: ${_base_location}/03_primary/prm_shuttle_company_reviews.pq metadata: kedro-viz: - layer1: primary + layer: primary prm_spine_table: type: pandas.ParquetDataset filepath: ${_base_location}/03_primary/prm_spine_table.pq metadata: kedro-viz: - layer1: primary + layer: primary diff --git a/demo-project/conf/base/catalog_04_feature.yml b/demo-project/conf/base/catalog_04_feature.yml index 68fb868b2d..738a053352 100644 --- a/demo-project/conf/base/catalog_04_feature.yml +++ b/demo-project/conf/base/catalog_04_feature.yml @@ -2,11 +2,11 @@ "feature_engineering.feat_{metric_type}_metrics": type: pandas.ParquetDataset filepath: ${_base_location}/04_feature/feat_{metric_type}_metrics.pq - layer1: feature + layer: feature feature_importance_output: type: pandas.CSVDataset filepath: ${_base_location}/04_feature/feature_importance_output.csv metadata: kedro-viz: - layer1: feature + layer: feature diff --git a/demo-project/conf/base/catalog_05_model_input.yml b/demo-project/conf/base/catalog_05_model_input.yml index 54c5f1234f..031b2ba42a 100644 --- a/demo-project/conf/base/catalog_05_model_input.yml +++ b/demo-project/conf/base/catalog_05_model_input.yml @@ -3,4 +3,4 @@ model_input_table: filepath: ${_base_location}/05_model_input/model_input_table.pq metadata: kedro-viz: - layer1: model_input + layer: model_input diff --git a/src/selectors/layers.js b/src/selectors/layers.js index f11c3f996a..974f697a1a 100644 --- a/src/selectors/layers.js +++ b/src/selectors/layers.js @@ -23,12 +23,12 @@ export const getLayers = createSelector( if (layer) { const bound = bounds[layer] || (bounds[layer] = [Infinity, -Infinity]); - if (node.y - node.height < bound[0]) { - bound[0] = node.y - node.height; + if (node.x - node.width < bound[0]) { + bound[0] = node.x - node.width; } - if (node.y + node.height > bound[1]) { - bound[1] = node.y + node.height; + if (node.x + node.width > bound[1]) { + bound[1] = node.x + node.width; } } } @@ -50,10 +50,10 @@ export const getLayers = createSelector( return { id, name: layerName[id], - x: (rectWidth - width) / -2, - y: start, - width: rectWidth, - height: Math.max(end - start, 0), + y: (rectWidth - width) / -2, + x: start, + height: rectWidth, + width: Math.max(end - start, 0), }; }); } From 190a49bd714b74476e5ba599181ef26748ff61ba Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 22 Oct 2024 09:30:20 +0100 Subject: [PATCH 11/13] wip layers --- demo-project/conf/base/catalog_08_reporting.yml | 8 ++++---- src/components/flowchart/draw.js | 6 ++++-- src/components/flowchart/flowchart.js | 4 ++-- src/selectors/layout.js | 2 +- src/utils/graph/constraints.js | 4 +--- src/utils/graph/graph.js | 2 +- src/utils/graph/layout.js | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/demo-project/conf/base/catalog_08_reporting.yml b/demo-project/conf/base/catalog_08_reporting.yml index f43d740b89..148c0e1246 100644 --- a/demo-project/conf/base/catalog_08_reporting.yml +++ b/demo-project/conf/base/catalog_08_reporting.yml @@ -3,7 +3,7 @@ reporting.cancellation_policy_breakdown: filepath: ${_base_location}/08_reporting/cancellation_breakdown.json metadata: kedro-viz: - layer1: reporting + layer: reporting plotly_args: type: bar fig: @@ -19,7 +19,7 @@ reporting.price_histogram: filepath: ${_base_location}/08_reporting/price_histogram.json metadata: kedro-viz: - layer1: reporting + layer: reporting versioned: true reporting.feature_importance: @@ -27,7 +27,7 @@ reporting.feature_importance: filepath: ${_base_location}/08_reporting/feature_importance_plot.json metadata: kedro-viz: - layer1: reporting + layer: reporting versioned: true reporting.cancellation_policy_grid: @@ -44,4 +44,4 @@ reporting.top_shuttle_data: filepath: ${_base_location}/08_reporting/top_shuttle_data.json metadata: kedro-viz: - layer1: reporting + layer: reporting diff --git a/src/components/flowchart/draw.js b/src/components/flowchart/draw.js index f2f300bf43..3da67fed93 100644 --- a/src/components/flowchart/draw.js +++ b/src/components/flowchart/draw.js @@ -71,14 +71,16 @@ export const drawLayers = function () { */ export const drawLayerNames = function () { const { - chartSize: { sidebarWidth = 0 }, + graphSize, layers, } = this.props; + const layerNamePosition = graphSize?.max?.y || 0; + this.el.layerNameGroup .transition('layer-names-sidebar-width') .duration(this.DURATION) - .style('transform', `translateX(${sidebarWidth}px)`); + .style('transform', `translateY(${layerNamePosition}px)`); this.el.layerNames = this.el.layerNameGroup .selectAll('.pipeline-layer-name') diff --git a/src/components/flowchart/flowchart.js b/src/components/flowchart/flowchart.js index 1afc5c4b93..af6eafc727 100644 --- a/src/components/flowchart/flowchart.js +++ b/src/components/flowchart/flowchart.js @@ -358,8 +358,8 @@ export class FlowChart extends Component { // Update layer label y positions if (this.el.layerNames) { this.el.layerNames.style('transform', (d) => { - const updateY = y + (d.y + d.height / 2) * scale; - return `translateY(${updateY}px)`; + const updateY = x + (d.x + d.width/6) * scale; + return `translateX(${updateY}px)`; }); } diff --git a/src/selectors/layout.js b/src/selectors/layout.js index 58f342e374..dc72a80af9 100644 --- a/src/selectors/layout.js +++ b/src/selectors/layout.js @@ -19,7 +19,7 @@ const getVisibleCode = (state) => state.visible.code; const getIgnoreLargeWarning = (state) => state.ignoreLargeWarning; const getGraphHasNodes = (state) => Boolean(state.graph?.nodes?.length); const getChartSizeState = (state) => state.chartSize; -const getOrient = (state) => state.layer.visible; +const getOrient = (state) => state.textLabels; /** * Show the large graph warning only if there are sufficient nodes + edges, diff --git a/src/utils/graph/constraints.js b/src/utils/graph/constraints.js index a16f720094..e94420d4e7 100644 --- a/src/utils/graph/constraints.js +++ b/src/utils/graph/constraints.js @@ -22,11 +22,9 @@ export const rowConstraint = { }; /** - * Layout constraint in Y for separating layers + * Layout constraint for separating layers */ export const layerConstraint = { - property: 'y', - strict: (constraint, constants, variableA, variableB) => new Constraint( variableA.minus(variableB), diff --git a/src/utils/graph/graph.js b/src/utils/graph/graph.js index 4e7610f959..857019569c 100644 --- a/src/utils/graph/graph.js +++ b/src/utils/graph/graph.js @@ -12,7 +12,7 @@ const defaultOptions = { layout: { spaceX: 14, spaceY: 110, - layerSpaceY: 55, + layerSpaceY: 100, spreadX: 2.2, padding: 100, iterations: 25, diff --git a/src/utils/graph/layout.js b/src/utils/graph/layout.js index aa2b923ae2..86a680f748 100644 --- a/src/utils/graph/layout.js +++ b/src/utils/graph/layout.js @@ -140,7 +140,7 @@ const createLayerConstraints = (nodes, layers, constants) => { base: layerConstraint, a: intermediary, b: node, - // property: constants.coordSecondary + property: constants.coordSecondary }); } @@ -150,7 +150,7 @@ const createLayerConstraints = (nodes, layers, constants) => { base: layerConstraint, a: node, b: intermediary, - // property: constants.coordSecondary + property: constants.coordSecondary }); } } From ad0153d90ca7f6b236ea88a27c93dd90778a4f2a Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 22 Oct 2024 14:54:47 +0100 Subject: [PATCH 12/13] fix orientation switch --- src/components/flowchart/draw.js | 16 +++++-- src/components/flowchart/flowchart.js | 12 ++++-- src/selectors/layers.js | 60 +++++++++++++++++++-------- src/selectors/nodes.js | 2 +- src/utils/graph/routing.js | 6 +++ 5 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/components/flowchart/draw.js b/src/components/flowchart/draw.js index 3da67fed93..dd83915480 100644 --- a/src/components/flowchart/draw.js +++ b/src/components/flowchart/draw.js @@ -71,16 +71,26 @@ export const drawLayers = function () { */ export const drawLayerNames = function () { const { - graphSize, + chartSize, layers, + orientation } = this.props; - const layerNamePosition = graphSize?.max?.y || 0; + + // Calculate the layer name position based on orientation + const layerNamePosition = orientation + ? chartSize.height - 100 || 0 // Vertical: position based on height + : chartSize.sidebarWidth || 0; // Horizontal: position based on sidebar width + + // Apply the correct translation based on orientation + const transformValue = orientation + ? `translateY(${layerNamePosition}px)` // Vertical: use translateY + : `translateX(${layerNamePosition}px)`; // Horizontal: use translateX this.el.layerNameGroup .transition('layer-names-sidebar-width') .duration(this.DURATION) - .style('transform', `translateY(${layerNamePosition}px)`); + .style('transform', transformValue); this.el.layerNames = this.el.layerNameGroup .selectAll('.pipeline-layer-name') diff --git a/src/components/flowchart/flowchart.js b/src/components/flowchart/flowchart.js index af6eafc727..e98e1b6bc9 100644 --- a/src/components/flowchart/flowchart.js +++ b/src/components/flowchart/flowchart.js @@ -183,7 +183,7 @@ export class FlowChart extends Component { this.updateChartSize(); } - if (changed('layers', 'chartSize')) { + if (changed('layers', 'chartSize', 'orientation')) { drawLayers.call(this); drawLayerNames.call(this); } @@ -358,8 +358,13 @@ export class FlowChart extends Component { // Update layer label y positions if (this.el.layerNames) { this.el.layerNames.style('transform', (d) => { - const updateY = x + (d.x + d.width/6) * scale; - return `translateX(${updateY}px)`; + if (this.props.orientation) { // Vertical orientation + const updateX = x + (d.x + d.width / 4) * scale; + return `translateX(${updateX}px)`; // Use translateX for horizontal layout + } else { // Horizontal orientation + const updateY = y + (d.y + d.height / 2) * scale; + return `translateY(${updateY}px)`; // Use translateY for vertical layout + } }); } @@ -974,6 +979,7 @@ const emptyGraphSize = {}; export const mapStateToProps = (state, ownProps) => ({ clickedNode: state.node.clicked, chartSize: getChartSize(state), + orientation: state.textLabels, chartZoom: getChartZoom(state), displayGlobalNavigation: state.display.globalNavigation, displaySidebar: state.display.sidebar, diff --git a/src/selectors/layers.js b/src/selectors/layers.js index 974f697a1a..59299e51c2 100644 --- a/src/selectors/layers.js +++ b/src/selectors/layers.js @@ -3,13 +3,14 @@ import { getVisibleLayerIDs } from './disabled'; const getGraph = (state) => state.graph; const getLayerName = (state) => state.layer.name; +const getOrientation = (state) => state.textLabels; /** * Get layer positions */ export const getLayers = createSelector( - [getGraph, getVisibleLayerIDs, getLayerName], - ({ nodes, size }, layerIDs, layerName) => { + [getGraph, getVisibleLayerIDs, getLayerName, getOrientation], + ({ nodes, size }, layerIDs, layerName, orientation) => { if (!nodes || !size || !nodes.length || !layerIDs.length) { return []; } @@ -17,22 +18,35 @@ export const getLayers = createSelector( const bounds = {}; + // Calculate the bounds for each layer based on node positions for (const node of nodes) { const layer = node.nearestLayer || node.layer; if (layer) { const bound = bounds[layer] || (bounds[layer] = [Infinity, -Infinity]); - if (node.x - node.width < bound[0]) { - bound[0] = node.x - node.width; - } + if (orientation) { // Vertical orientation (when true) + if (node.x - node.width < bound[0]) { + bound[0] = node.x - node.width; + } + + if (node.x + node.width > bound[1]) { + bound[1] = node.x + node.width; + } + + } else { // Horizontal orientation (when false) + if (node.y - node.height < bound[0]) { + bound[0] = node.y - node.height; + } - if (node.x + node.width > bound[1]) { - bound[1] = node.x + node.width; + if (node.y + node.height > bound[1]) { + bound[1] = node.y + node.height; + } } } } + // Calculate the layer positions based on the orientation return layerIDs.map((id, i) => { const currentBound = bounds[id] || [0, 0]; const prevBound = bounds[layerIDs[i - 1]] || [ @@ -45,16 +59,28 @@ export const getLayers = createSelector( ]; const start = (prevBound[1] + currentBound[0]) / 2; const end = (currentBound[1] + nextBound[0]) / 2; - const rectWidth = Math.max(width, height) * 5; - - return { - id, - name: layerName[id], - y: (rectWidth - width) / -2, - x: start, - height: rectWidth, - width: Math.max(end - start, 0), - }; + const rectSize = Math.max(width, height) * 5; // Adjust size calculation + + // Return positions based on boolean orientation + if (orientation) { // Vertical layout when orientation is true + return { + id, + name: layerName[id], + x: start, // Horizontal layout moves along the x-axis + y: (rectSize - height) / -2, // Centered along y-axis + width: Math.max(end - start, 0), + height: rectSize, + }; + } else { // Horizontal layout when orientation is false + return { + id, + name: layerName[id], + y: start, // Vertical layout moves along the y-axis + x: (rectSize - width) / -2, // Centered along x-axis + height: Math.max(end - start, 0), + width: rectSize, + }; + } }); } ); diff --git a/src/selectors/nodes.js b/src/selectors/nodes.js index adaaaedf73..f44da46c43 100644 --- a/src/selectors/nodes.js +++ b/src/selectors/nodes.js @@ -24,7 +24,7 @@ const getHoveredNode = (state) => state.node.hovered; const getIsPrettyName = (state) => state.isPrettyName; const getTagActive = (state) => state.tag.active; const getModularPipelineActive = (state) => state.modularPipeline.active; -const getTextLabels = (state) => state.textLabels; +const getTextLabels = (state) => true; const getNodeTypeDisabled = (state) => state.nodeType.disabled; const getClickedNode = (state) => state.node.clicked; const getEdgeIDs = (state) => state.edge.ids; diff --git a/src/utils/graph/routing.js b/src/utils/graph/routing.js index 8e058c43cf..25958e8c16 100644 --- a/src/utils/graph/routing.js +++ b/src/utils/graph/routing.js @@ -118,6 +118,12 @@ export const routing = ({ targetX = nodeLeft(nextNode) - offsetX; // Left side of the next node targetY = nodeTop(nextNode) - spaceY; // Above the next node } + // else if (orientation === 'left-to-right') { + // sourceX = nodeTop(node); // Right side of the current node + // sourceY = nodeLeft(node) ; // Above the current node + // targetX = nodeBottom(nextNode); // Left side of the next node + // targetY = nodeLeft(nextNode); // Above the next node + // } // Calculate the nearest point using the computed source and target positions const candidatePoint = nearestOnLine( From 4698a46fedec55fc51da18d81add7b5ab3bb49b5 Mon Sep 17 00:00:00 2001 From: rashidakanchwala Date: Tue, 22 Oct 2024 15:05:09 +0100 Subject: [PATCH 13/13] remove unwanted code --- .../flowchart-primary-toolbar.js | 5 +++-- src/components/flowchart/draw.js | 15 +++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js b/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js index 7ab896c4d8..c63cc9f186 100644 --- a/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js +++ b/src/components/flowchart-primary-toolbar/flowchart-primary-toolbar.js @@ -66,9 +66,10 @@ export const FlowchartPrimaryToolbar = ({ ariaLabel={`Turn data layers ${visibleLayers ? 'off' : 'on'}`} className={'pipeline-menu-button--layers'} dataTest={`sidebar-flowchart-layers-btn-${visibleLayers}`} + disabled={disableLayerBtn} icon={LayersIcon} labelText={`${visibleLayers ? 'Hide' : 'Show'} layers`} - onClick={() => onToggleLayers(!disableLayerBtn)} + onClick={() => onToggleLayers(!visibleLayers)} visible={display.layerBtn} /> ({ - disableLayerBtn: state.layer.visible, + disableLayerBtn: !state.layer.ids.length, textLabels: state.textLabels, visible: state.visible, display: state.display, diff --git a/src/components/flowchart/draw.js b/src/components/flowchart/draw.js index dd83915480..3944763200 100644 --- a/src/components/flowchart/draw.js +++ b/src/components/flowchart/draw.js @@ -70,22 +70,17 @@ export const drawLayers = function () { * Render layer name labels */ export const drawLayerNames = function () { - const { - chartSize, - layers, - orientation - } = this.props; - + const { chartSize, layers, orientation } = this.props; // Calculate the layer name position based on orientation const layerNamePosition = orientation - ? chartSize.height - 100 || 0 // Vertical: position based on height - : chartSize.sidebarWidth || 0; // Horizontal: position based on sidebar width + ? chartSize.height - 100 || 0 // Vertical: position based on height + : chartSize.sidebarWidth || 0; // Horizontal: position based on sidebar width // Apply the correct translation based on orientation const transformValue = orientation - ? `translateY(${layerNamePosition}px)` // Vertical: use translateY - : `translateX(${layerNamePosition}px)`; // Horizontal: use translateX + ? `translateY(${layerNamePosition}px)` // Vertical: use translateY + : `translateX(${layerNamePosition}px)`; // Horizontal: use translateX this.el.layerNameGroup .transition('layer-names-sidebar-width')