Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New flowchart LTR #2244

Closed
wants to merge 14 commits into from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion src/actions/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
28 changes: 18 additions & 10 deletions src/components/flowchart/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,22 @@ export const drawLayers = function () {
* Render layer name labels
*/
export const drawLayerNames = function () {
const {
chartSize: { sidebarWidth = 0 },
layers,
} = this.props;
const { chartSize, layers, orientation } = this.props;

// Calculate the layer name position based on orientation
const layerNamePosition = orientation
? 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', `translateX(${sidebarWidth}px)`);
.style('transform', transformValue);

this.el.layerNames = this.el.layerNameGroup
.selectAll('.pipeline-layer-name')
Expand Down Expand Up @@ -126,12 +133,12 @@ const updateNodeRects = (nodeRects) =>
return node.height / 2;
});

const updateParameterRect = (nodeRects) =>
const updateParameterRect = (nodeRects, orientation) =>
nodeRects
.attr('width', 12)
.attr('height', 12)
.attr('x', (node) => (node.width + 20) / -2)
.attr('y', -6);
.attr('x', (node) => orientation ? -node.width/2 + 10: (node.width + 20)/-2)
.attr('y', (node) => orientation ? -node.height+12 : -6);

/**
* Render node icons and name labels
Expand All @@ -150,6 +157,7 @@ export const drawNodes = function (changed) {
focusMode,
hoveredFocusMode,
isSlicingPipelineApplied,
orientation
} = this.props;
const {
from: slicedPipelineFromId,
Expand Down Expand Up @@ -223,7 +231,7 @@ export const drawNodes = function (changed) {
.append('rect')
.attr('class', 'pipeline-node__parameter-indicator')
.on('mouseover', this.handleParamsIndicatorMouseOver)
.call(updateParameterRect);
.call(updateParameterRect, orientation);

// Performance: use a single path per icon
enterNodes
Expand Down Expand Up @@ -344,7 +352,7 @@ export const drawNodes = function (changed) {
)
.transition('node-rect')
.duration((node) => (node.showText ? 200 : 600))
.call(updateParameterRect);
.call(updateParameterRect, orientation);

// Performance: icon transitions with CSS on GPU
allNodes
Expand Down
12 changes: 9 additions & 3 deletions src/components/flowchart/flowchart.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 = y + (d.y + d.height / 2) * scale;
return `translateY(${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
}
});
}

Expand Down Expand Up @@ -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,
Expand Down
29 changes: 20 additions & 9 deletions src/components/flowchart/styles/_layers.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,28 @@
}

.pipeline-layer-name {
align-items: center;
color: var(--layer-text);
display: flex;
position: absolute;
font-size: 1.6em;
font-weight: bold;
height: 20px;
padding-left: 18px;
position: absolute;
top: -10px;
transition: opacity ease 0.5s;
white-space: nowrap;
color: var(--layer-text); // Ensure this matches your theme
white-space: nowrap; // Prevent text wrapping
overflow: hidden; // Hide text overflow
text-overflow: ellipsis; // Show ellipsis when text overflows
transition: opacity ease 0.5s, transform ease 0.5s; // Smooth transitions
pointer-events: none; // Avoid interfering with user interactions
}

.pipeline-layer-name--horizontal {
text-align: left; // Align text to the left
text-anchor: start; // Align text anchor for SVG-like elements
padding-left: 12px; // Add some padding for spacing
}

.pipeline-layer-name--vertical {
transform: translateY(-50%); // Center the text along the vertical axis
text-align: center; // Align text in the middle
text-anchor: middle; // Align text anchor for SVG-like elements
left: 0; // Position at the start of the layer
}

.pipeline-layer-name--active {
Expand Down
60 changes: 43 additions & 17 deletions src/selectors/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,50 @@ 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 [];
}
const { width, height } = size;

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.y - node.height < bound[0]) {
bound[0] = node.y - node.height;
}
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.y + node.height > bound[1]) {
bound[1] = node.y + node.height;
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]] || [
Expand All @@ -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],
x: (rectWidth - width) / -2,
y: start,
width: rectWidth,
height: 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,
};
}
});
}
);
6 changes: 4 additions & 2 deletions src/selectors/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 getOrient = (state) => state.textLabels;

/**
* Show the large graph warning only if there are sufficient nodes + edges,
Expand Down Expand Up @@ -51,13 +52,14 @@ export const getGraphInput = createSelector(
getVisibleEdges,
getVisibleLayerIDs,
getTriggerLargeGraphWarning,
getOrient,
],
(nodes, edges, layers, triggerLargeGraphWarning) => {
(nodes, edges, layers, triggerLargeGraphWarning, orient) => {
if (triggerLargeGraphWarning) {
return null;
}

return { nodes, edges, layers };
return { nodes, edges, layers, orient };
}
);

Expand Down
2 changes: 1 addition & 1 deletion src/selectors/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
39 changes: 29 additions & 10 deletions src/utils/graph/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -75,24 +87,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;
}
Expand All @@ -101,6 +119,7 @@ export const groupByRow = (nodes) => {
return sortedRows;
};


/**
* Generalised comparator function for sorting
* If values are strings then `localeCompare` is used, otherwise values are subtracted
Expand Down
Loading
Loading