Wrong/Strange position nodes with Material-UI
See original GitHub issueHi ! It’s me again ^^
Describe the bug
I’ve made my tree graph with staticGraph
property to true and I’d a good shape of tree diagram (see the first image of Sreenshot section)
So, to build a modeler, i set the staticGraphWithDragAndDrop
to true and i’ve got this graph with that (see the 2nd picture of Screenshot section)
But i use Material UI for UI interface of my react app, and I made this system : You can see graph in medium size (like the seconde image upside) and you can see in full screen. The full width mode pass with a Dialog Component of Material UI and in this mode : I’VE GOT THE GOOD SHAPE (like the 1st image upside) ! If i came back, i’ve the wrong shape …
I tried some CSS or JS stuff but, nothing change…
To Reproduce
I’ve got two class to do that :
- The container which i build position of nodes in
loadData()
function afterrender()
:
[...]
class ResultGraph extends Component<ResultGraphProps, ResultGraphState> {
constructor(props) {
super(props);
this.state = {
project: null,
data: null,
isLoading: true,
editNodeDialogOpen: false,
nodeTarget: null,
anchorElement: null,
openCreateNodeOption: false,
openEditNodeOption: false,
openEditTypeNodeOption: false,
typeRelationNodes: DirectionQuestionNeighbour.CHILD
};
}
async componentDidMount() {
try {
await this.loadData(this.props.projectId);
} catch (error) {
toast.error('Impossible de récupérer le graph correspondant. Problème de connexion au serveur.');
log.info('Impossible to recover corresponding graph', error);
}
}
[...]
/**
* Method to have the number of rank in function of children nodes
* @param listOfLinks - List of links needed
* @param nodeFocusId - Id of node focus at start
*/
getRankNodeInFunctionOfLinks(listOfLinks, nodeFocusId: string) {
let numberOfRank = 0;
const startList = _.cloneDeep(listOfLinks);
// We iterate on the list of links and if our node is a source,
// we then delete the links where it is source and we look again for the target node if it has links too
startList.map(link => {
if (link.source === nodeFocusId) {
_.remove(startList, { source: nodeFocusId });
numberOfRank++;
numberOfRank += this.getRankNodeInFunctionOfLinks(startList, link.target);
}
});
return numberOfRank;
}
fillNodeToMapAndGraphArray(nodeGraphInSameLine, nodesByRank, value, graphNodesTemp) {
const nodeGraphInSameLineOrdered = _.orderBy(nodeGraphInSameLine, ['positionInLine', 'asc']);
nodesByRank.set(value, nodeGraphInSameLineOrdered);
nodeGraphInSameLineOrdered.map(nodeToPush => graphNodesTemp.push(nodeToPush));
}
[...]
render() {
const { dossardRestClient, projectId, classes } = this.props;
const { project, data, isLoading, nodeTarget, editNodeDialogOpen } = this.state;
if (isLoading) {
return <LoaderData text={'Chargement de l\'arbre correspondant.'}/>;
}
return (
<div>
<Container>
{/* Project's Name */}
<h1>{project.name}</h1>
{/* Timeline of the project */}
<StepsLine
step={StepProjectEnum.RESULTS}
className={classes.timeLine}
chevronLeftLink={'/' + projectId + '/details/affinage'}
chevronRightDisable
/>
</Container>
<Col xl={10} md={10} offset={{ xl: 1, md: 1 }} className={classes.diagramContainer}>
<DiagramResult model={data}
dossardRestClient={dossardRestClient}
onClickNode={this.onClickNode.bind(this)}
onDoubleClickNode={this.onClickRenameNode.bind(this)}
onRightClickNode={this.onRightClickNode.bind(this)}
onClickGraph={this.onClickGraph.bind(this)}/>
<NodeEditDialog node={nodeTarget}
open={editNodeDialogOpen}
onClose={this.closeNodeEditDialog.bind(this)}
onSubmit={this.updateNode.bind(this)}/>
{this.buildGraphMenu(classes)}
</Col>
</div>
);
}
async loadData(projectId) {
const projectFound = await this.getProjectNeed(projectId);
const graphData = await this.getGraphNeed(projectId);
const graphNodes = new Array(0);
const graphNodesTemp = new Array(0);
const numberChildrenByIdNodes = new Map();
const nodesByRank = new Map();
const nodesToNotAppear = new Array(0);
const sizeOfGraph = 1700;
const marginBetweenNodes = 200;
// Initialize all variables which will changing :
// actualRank : informed on which rank we are
// xNode : position x of node
// yNode : position y of node
// positionInLine : informed position of node in same line
// nodeGraphInSameLine : nodes array in same rank
let actualRank = 0;
let xNode = 0;
let yNode = 0;
let positionNodeInLine = 1;
let nodeGraphInSameLine = new Array(0);
// First, we search links with AND nodes without children or parents in Array
const allAndNodes = graphData.nodes.filter(node => node.family === TypeQuestionsEnum.AND);
const linksForAndWithNoChildrenOrNoParents = new Array(0);
allAndNodes.map(andNode => {
const andNodeWithTarget = graphData.links.filter(link => link.source === andNode.id);
const andNodeWithSource = graphData.links.filter(link => link.target === andNode.id);
if (andNodeWithTarget.length === 0 || andNodeWithSource.length === 0) {
linksForAndWithNoChildrenOrNoParents.push(andNode);
}
});
// With last array, we're gonna find node thanks to links
graphData.nodes.map(node => linksForAndWithNoChildrenOrNoParents.find(nodeAnd => nodeAnd.id === node.id) !== undefined && nodesToNotAppear.push(node));
// We're gonna search nodes associate with AND nodes which have no children or parents
nodesToNotAppear.map(node => {
const linksAffiliate = graphData.links.filter(link => link.target === node.id);
linksAffiliate.map(link => {
const getAlsoParent = graphData.links.filter(linkParent => linkParent.target === link.source);
if (getAlsoParent.length === 0) {
const nodeToNotAppear = graphData.nodes.find(nodeToFound => nodeToFound.id === node.id);
nodesToNotAppear.push(nodeToNotAppear);
}
});
_.remove(graphData.links, link => link.source === node.id || link.target === node.id);
});
// Get rank node by rank
graphData.nodes.map(node => {
// We filter links object to find if this node have links associate
// If yes, we check if node it's not contains of nodeToNotAppear Array to add him
const nodesWithLinks = graphData.links.filter(link => link.source === node.id || link.target === node.id);
if (nodesWithLinks.length !== 0) {
if (linksForAndWithNoChildrenOrNoParents.find(nodeAnd => nodeAnd.id === node.id) === undefined) {
// Get the rank of nodes in function of his links (number children nodes)
const numberOfLevelNode = this.getRankNodeInFunctionOfLinks(graphData.links, node.id);
numberChildrenByIdNodes.set(node.id, numberOfLevelNode);
}
}
});
// Sort map in function of nodes rank asc
// @ts-ignore
const numberChildrenByIdNodesSortedByValues = new Map([...numberChildrenByIdNodes.entries()].sort((a, b) => a[1] - b[1]));
const lastNode = Array.from(numberChildrenByIdNodesSortedByValues.entries()).pop();
// Iterate map on all nodes vertically
numberChildrenByIdNodesSortedByValues.forEach((value: number, key: string) => {
// Get the node matching
const nodeFound = graphData.nodes.find(nodeInArray => nodeInArray.id === key);
// If we're not in the same last rank, we increment y position, adjust actualRank and reinitialize positionNodeInLine
// And we add on map all node in last rank ordered by position by their rank
// And we add on temp array nodes with all properties but x position set after
if (value !== actualRank) {
this.fillNodeToMapAndGraphArray(nodeGraphInSameLine, nodesByRank, value - 1, graphNodesTemp);
yNode += 200;
actualRank = value;
positionNodeInLine = 1;
nodeGraphInSameLine = [];
}
// Got all children links
const linksChild = graphData.links.filter(link => link.source === key);
let nodePosition = 1;
if (linksChild.length !== 0) {
const firstParent = graphNodesTemp.find(node => node.id === linksChild[0].target);
if (firstParent !== undefined) {
nodePosition = (firstParent.positionInLine * 10) + positionNodeInLine;
}
} else {
nodePosition = positionNodeInLine;
}
// We add on array node with all properties except x position
nodeGraphInSameLine.push({
id: nodeFound.id,
questionLabel: nodeFound.questionLabel,
family: nodeFound.family,
size: nodeFound.family === TypeQuestionsEnum.AND && 500,
graphRank: value,
positionInLine: nodePosition,
y: yNode
});
// For the last node, as we not pass again in first block where we fill map and push in array.
if (key === lastNode[0]) {
this.fillNodeToMapAndGraphArray(nodeGraphInSameLine, nodesByRank, value, graphNodesTemp);
}
positionNodeInLine++;
});
const lastNodeWithAllProperties = graphNodesTemp.find(nodeInArray => nodeInArray.id === lastNode[0]);
// We iterate on map which has rank in index and an array of Nodes in same rank.
// And we iterate on this node array to set in horizontal vision x node position
nodesByRank.forEach((nodesArray: NodeInterface[], rank: number) => {
let xIndex = 1;
nodesArray.map(nodeToPosition => {
const linksChild = graphData.links.filter(link => link.source === nodeToPosition.id);
const linksParent = graphData.links.filter(link => link.target === nodeToPosition.id);
const conditionNoLinks = linksParent.length === 0 && linksChild.length === 0;
xNode = (sizeOfGraph - marginBetweenNodes * nodesArray.length) / 2 + marginBetweenNodes * xIndex;
graphNodes.push({
...nodeToPosition,
positionInLine: nodeToPosition.positionInLine,
x: xNode,
y: conditionNoLinks ? lastNodeWithAllProperties.y + 400 : nodeToPosition.y // In case of node have no links associate, we put him in bottom with 1 difference line
});
xIndex++;
});
});
this.setState({
project: projectFound,
data: {
nodes: graphNodes,
links: graphData.links
},
isLoading: false
});
}
[...]
export default withStyles(styles)(ResultGraph);
- The component with Graph Object inside :
[...]
/**
* Configuration for react-d3-graph
*/
const myConfig = {
'automaticRearrangeAfterDropNode': false,
'collapsible': false,
'directed': true,
'focusAnimationDuration': 0.75,
'height': 700,
'highlightDegree': 2,
'highlightOpacity': 0.2,
'linkHighlightBehavior': true,
'maxZoom': 12,
'minZoom': 0.05,
'nodeHighlightBehavior': true,
'panAndZoom': false,
'staticGraph': false,
'staticGraphWithDragAndDrop': true,
'd3': {
'alphaTarget': 0.05,
'gravity': -100,
'linkLength': 150,
'linkStrength': 1
},
'node': {
'mouseCursor': 'grab',
'opacity': 1,
'renderLabel': false,
'size': 1500,
'viewGenerator': node => <QuestionNodeSvg question={node}/>
},
'link': {
'color': '#000',
'highlightColor': 'red',
'mouseCursor': 'pointer',
'opacity': 1,
'semanticStrokeWidth': false,
'strokeWidth': 2
}
};
[...]
class DiagramResult extends Component<DiagramResultProps, DiagramResultState> {
constructor(props) {
super(props);
this.state = {
config: myConfig,
data: this.props.model,
fullWidth: false,
editNodeDialogOpen: false,
nodeTarget: null,
isLoading: true
};
}
[...]
render() {
const { model, onClickNode, onDoubleClickNode, onRightClickNode, onClickGraph, classes } = this.props;
const { fullWidth } = this.state;
const graphProps = {
id: 'treeDiagram',
data: model,
config: this.state.config,
onClickNode,
onDoubleClickNode,
onRightClickNode,
onClickGraph
};
if (fullWidth){
graphProps.config = Object.assign({}, graphProps.config, {
height: window.innerHeight - 10,
width: window.innerWidth - 10
});
}
const graph = <Graph {...graphProps} />
if (fullWidth) {
return (
<Dialog fullScreen onClose={this.closeFullDiagram.bind(this)} open={fullWidth}
className={classes.fullDiagramDialog}>
<ActionButton icon={<Close/>} onClick={this.closeFullDiagram.bind(this)}
classes={{ iconButton: classes.closeFullDiagramButton }}/>
{graph}
</Dialog>
);
} else {
return (
<div className={classes.diagram}>
{graph}
<ActionButton
icon={<ZoomOutMap/>}
onClick={this.openFullDiagram.bind(this)}
classes={{
iconButton: classes.seeFullDiagramButton
}}
/>
</div>
);
}
}
}
export default withStyles(styles)(DiagramResult);
Expected behavior
I want to have two graph with the same shape (like the first image)
Screenshots
First picture (good shape wanted and graph in full screen mode ) :
Second picture (bad shape not wanted in normal mode) :
Environment:
- OS: Windows
- Browser: Chrome
- Version: latest
- Node version: 10.16.3
- react-d3-graph version : 2.3.0
- d3 version: 5.12.0
- react version : 16.10.1
Thanks in advance for your help
Issue Analytics
- State:
- Created 4 years ago
- Comments:10 (5 by maintainers)
Top GitHub Comments
hi @danielcaldas ,
I didn’t find solution, but i keep configuration in full screen and this one worked ! So, it doesn’t matter for the minimum configuration doesn’t work. Thanks anyway dude ! 😉
Oh okey, no problem dude, thx in advance to you or someone who take a look to fix this ! 😉