dmulyalin / n2g Goto Github PK
View Code? Open in Web Editor NEWNeed To Graph
License: MIT License
Need To Graph
License: MIT License
This is a "bug" because of the pattern of using tuple(sorted([source,target,label]))
to create identities. Tried to fix, but see other issue on tests not passing.
Example:
from N2G import drawio_diagram
diagram_dict = {
'nodes' : [
{ 'id': 'a' , 'label' : 'A'},
{ 'id': 'b' , 'label' : 'B'},
],
'edges' : [
{ 'source':'a',
'target':'b',
'style': 'endArrow=classic;endFill=0;sourcePortConstraint=east;targetPortConstraint=west;edgeStyle=orthogonalEdgeStyle;'
},
{ 'source':'b',
'target':'a',
'style': 'endArrow=classic;endFill=0;sourcePortConstraint=east;targetPortConstraint=west;edgeStyle=orthogonalEdgeStyle;'
},
]
}
diagram = drawio_diagram()
diagram.from_dict(diagram_dict)
diagram.layout('kk')
diagram.dump_file('example.drawio','./')
Did a fork to fix a bug, but current tests do not pass
Running pytest:
collected 60 items
test_IP_drawer.py ....... [ 11%]
test_L2_drawer.py ......................FFF...... [ 63%]
test_drawio_module.py .....F..F.... [ 85%]
test_yed_module.py .........
python da_n2g.py
Traceback (most recent call last):
File "da_n2g.py", line 17, in
diagram.add_link("R1", "R2", label="DF", src_label="Gi1/1", trgt_label="GE23")
TypeError: add_link() got an unexpected keyword argument 'src_label'
Hi there,
First of, thanks for this work, I found out we've done the same only that yours is awesome and reusable and mine was script style hehe.
It's good to see that you actively adding more stuff. I thought it was a one off thing and wouldn't be any more updates and I started to refactor it on a local repo on my machine.
I see you've added src_label and target_label but in a different way of how I've done it.
Following the string declarations for nodes and links I went down this path
# source edge label x="-0.5" y="1"
drawio_link_source_label_xml = """
<mxCell id="{id}" value="{value}" style="{style}" parent="{parent}" vertex="1" connectable="0">
<mxGeometry x="-0.5" y="1" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
"""
# target edge label x="0.5" y="-1"
drawio_link_target_label_xml = """
<mxCell id="{id}" value="{value}" style="{style}" parent="{parent}" vertex="1" connectable="0">
<mxGeometry x="0.5" y="-1" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
"""
And then something like below, basically assuming that if you pass a src_label there will be target_label, otherwise just pass label.
if src_label != '' and tgt_label != '':
src_id = f"{edge_id}-src"
tgt_id = f"{edge_id}-tgt"
source_label = ET.fromstring(
self.drawio_link_source_label_xml.format(
id=f"{edge_id}-src",
value=src_label,
parent=edge_id,
style=style if style else self.default_link_style,
)
)
target_label = ET.fromstring(
self.drawio_link_target_label_xml.format(
id=f"{edge_id}-tgt",
value=tgt_label,
parent=edge_id,
style=style if style else self.default_link_style,
)
)
else:
source_label = None
target_label = None
The XML should look like below, I'm adding a suffix -src
and -tgt
to the link id.
<object label="" id="2d123edf1f03cdcb18fa393bc8d1e719">
<mxCell style="endArrow=none;" parent="1" source="R1" target="SW1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</object>
<mxCell id="2d123edf1f03cdcb18fa393bc8d1e719-src" value="G1/0/1" style="endArrow=none;" parent="2d123edf1f03cdcb18fa393bc8d1e719" vertex="1" connectable="0">
<mxGeometry x="-0.5" y="1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="2d123edf1f03cdcb18fa393bc8d1e719-tgt" value="Eth1/1/1" style="endArrow=none;" parent="2d123edf1f03cdcb18fa393bc8d1e719" vertex="1" connectable="0">
<mxGeometry x="0.5" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
Ignoring the style, the picture below illustrates what I mean.
Additionally I found it a lot easier for my use cases to have a yaml file with styles where I can basically declare more entries on each dict for customization instead of having multiple txt files.
shapes_map:
'bus': 'shape=mxgraph.networks.bus;fillColor=#d5e8d4;strokeColor=#82b366;outlineConnect=0;strokeWidth=2;gradientColor=none;gradientDirection=north;perimeter=backbonePerimeter;backboneSize=20;glass=0;'
'firewall': 'shape=mxgraph.office.concepts.firewall;fillColor=#DA4026;strokeColor=none;outlineConnect=0;aspect=fixed;'
'hafirewall': 'shape=mxgraph.office.concepts.firewall;fillColor=#DA4026;aspect=fixed;'
'router': 'shape=mxgraph.cisco19.rect;prIcon=router;fillColor=#FAFAFA;strokeColor=#005073;aspect=fixed;'
'access switch': 'shape=mxgraph.cisco19.rect;prIcon=l2_switch;fillColor=#FAFAFA;strokeColor=#005073;align=center;outlineConnect=0;aspect=fixed;'
'management switch': 'shape=mxgraph.vvd.switch;strokeColor=none;fillColor=#434445;align=center;outlineConnect=0;aspect=fixed;'
'core switch': 'shape=mxgraph.cisco19.rect;prIcon=secure_catalyst_switch_color2;fillColor=#FAFAFA;strokeColor=#005073;aspect=fixed;'
'distribution switch': 'shape=mxgraph.cisco19.rect;prIcon=l3_switch;fillColor=#FAFAFA;strokeColor=#005073;aspect=fixed;'
'server': 'shape=mxgraph.aws3.traditional_server;fillColor=#7D7C7C;strokeColor=#67AB9F;outlineConnect=0;'
'cloud': 'shape=mxgraph.cisco19.cloud2;fillColor=#FFE9AA;strokeColor=none;aspect=fixed;'
'cluster': 'shape=mxgraph.veeam.cluster;rounded=1;fillColor=#ffe6cc;strokeColor=#d79b00;aspect=fixed;'
'workstation': 'shape=mxgraph.signs.tech.computer;fillColor=#000000;strokeColor=none;'
'other': 'whiteSpace=wrap;html=1;rounded=1;verticalLabelPosition=bottom;verticalAlign=top;align=center;aspect=fixed;'
'ont': 'mxgraph.cisco.modems_and_phones.cable_modem;html=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;'
'media converter': 'mxgraph.cisco.modems_and_phones.cable_modem;html=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;'
'storage': 'mxgraph.cisco_safe.compositeIcon;bgIcon=ellipse;resIcon=mxgraph.cisco_safe.capability.storage;fillColor=#999999;html=1;strokeColor=#ffffff;aspect=fixed;'
'hardware security modules': 'mxgraph.cisco_safe.compositeIcon;bgIcon=ellipse;resIcon=mxgraph.cisco_safe.capability.secure_server;rounded=1;glass=0;fillColor=#999999;strokeColor=#ffffff;aspect=fixed;'
shapes_label:
'bus': 'labelBackgroundColor=none;labelPosition=middle;align=center;html=1;fontStyle=1;fontSize=13;fontColor=#000000;'
'bus_vertical': 'labelBackgroundColor=none;labelPosition=left;verticalLabelPosition=middle;align=rigth;verticalAlign=middle;html=1;fontColor=#000000;spacingTop=999;spacing=0;direction=west;horizontal=0;spacingBottom=-365;'
'firewall': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontSize=13;fontColor=#0066CC;'
'hafirewall': 'labelBackgroundColor=#004C99;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#D4D4D4;'
'router': 'labelBackgroundColor=#004C99;;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#D4D4D4;'
'access switch': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontSize=13;fontColor=#0066CC;'
'management switch': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontSize=13;fontColor=#0066CC;'
'core switch': 'labelBackgroundColor=#99CCFF;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#000000;'
'distribution switch': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#0066CC;'
'server': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#0066CC;'
'cloud': 'labelBackgroundColor=#004C99;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#D4D4D4;'
'cluster': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#0066CC;'
'workstation': 'verticalLabelPosition=bottom;verticalAlign=top;align=center;'
'other': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#0066CC;'
'ont': 'labelBackgroundColor=#99CCFF;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#000000;'
'media converter': 'labelBackgroundColor=#99CCFF;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#000000;'
'storage': 'labelBackgroundColor=none;verticalLabelPosition=bottom;labelPosition=center;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#000000;'
'hardware security modules': 'labelBackgroundColor=#99CCFF;verticalLabelPosition=bottom;align=center;verticalAlign=top;spacing=2;fontStyle=1;fontSize=13;fontColor=#000000;'
shapes_size:
'bus': {'width': '1435', 'height': '20'}
'firewall': {'width': '47', 'height': '43'}
'hafirewall': {'width': '47', 'height': '43'}
'router': {'width': '50', 'height': '50'}
'access switch': {'width': '50', 'height': '50'}
'management switch': {'width': '50', 'height': '50'}
'core switch': {'width': '50', 'height': '50'}
'distribution switch': {'width': '50', 'height': '50'}
'server': {'width': '46', 'height': '60'}
'cloud': {'width': '60', 'height': '60'}
'cluster': {'width': '60', 'height': '60'}
'workstation': {'width': '68', 'height': '68'}
'other': {'width': '60', 'height': '60'}
'ont': {'width': '60', 'height': '30'}
'media converter': {'width': '60', 'height': '30'}
'storage': {'width': '43', 'height': '43'}
'hardware security modules': {'width': '70', 'height': '70'}
Thoughts?
Hy @dmulyalin !
Just want to let you know that we are using your lib in this project:
(FPrime Topology visualisation using Diagrams.Net)[https://github.com/nasa/fprime/discussions/868]
In terms of feature requests.
It would be great to be able to create ports for nodes. Under ports I mean special attachement points for links.
It would be helpful to be able to assign layers while mapping out a diagram, like network and end devices so you can filter the amount of data your are viewing.
Hi, we have some issue :
but still not successful.
so we dont need to modify .graphml file again.
thanks.
good day to all,
I try to create sample graph from read me file. Config is like below.
Issue : it is not show any "src_label" in the graph.
It will only show if we click the link / edge and we will see src_label in the "neighborhood" tab.
Expectation : src_label is shown just like trgt_label in the graph.
from N2G import yed_diagram
Hello,
I would like to use existing drawio library. I successfully extracted the content of each element with few line of code.
However, it is not support to be used in the style parameter.
For example, I extracted some paloalto stencils and tried to use it as a style (sorry for the lenght of the style):
pan_pa220 = '<mxGraphModel style="default-style2"><root><mxCell id="0"/><mxCell id="1" parent="0"/><UserObject label="" Manufacturer="Palo Alto Networks" ProductNumber="PA-220" PartNumber="" View="Front" ProductDescription="PA-220 Firewall Platform" Ipadress="" NetworkName="" RackUnits="" MountingPosition="" Bay="" RowLineup="" Room="" Floor="" Building="" LocationCode="" Department="" PurchaseDate="" InstalledDate="" WarrantyExp="" SupportAgreement="" SupportContactName="" ContactPhone="(866) 898-9087" Comments="" Cost="" AssetNumber="" SerialNumber="" id="2"><mxCell style="vsdxID=5;fillColor=none;gradientColor=none;strokeColor=none;strokeWidth=0;spacingTop=-3;spacingBottom=-3;spacingLeft=-3;spacingRight=-3;labelBackgroundColor=none;rounded=0;html=1;whiteSpace=wrap;" vertex="1" parent="1"><mxGeometry width="81.9" height="16.400000000000002" as="geometry"/></mxCell></UserObject><mxCell id="3" style="vsdxID=6;fillColor=none;gradientColor=none;image;aspect=fixed;image=data:image/png,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;strokeColor=none;strokeWidth=0;spacingTop=-3;spacingBottom=-3;spacingLeft=-3;spacingRight=-3;labelBackgroundColor=none;rounded=0;html=1;whiteSpace=wrap;" vertex="1" parent="2"><mxGeometry x="-0.30000000000000004" y="-0.30000000000000004" width="82.4" height="16.8" as="geometry"/></mxCell></root></mxGraphModel>'
diagram = N2G.drawio_diagram()
diagram.add_diagram("Page-1")
diagram.add_node(id="R1", style=pan_pa220)
diagram.add_node(id="R2")
#diagram.add_link("R1", "R2", label="DF", src_label="Gi1/1", trgt_label="GE23")
diagram.layout(algo="kk")
diagram.dump_file(filename="Sample_graph.drawio", folder="./Output/")
I got the following error :
Traceback (most recent call last):
File "XXX/N2G/read_drawio_lib.py", line 35, in <module>
diagram.add_node(id="R1", style=pan_pa220)
File "XXX//N2G/plugins/diagrams/N2G_DrawIO.py", line 205, in add_node
node = ET.fromstring(
File "/usr/local/Cellar/[email protected]/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/xml/etree/ElementTree.py", line 1342, in XML
parser.feed(text)
xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 3, column 21
If I only take the content of the last mxcell it works, but I loose some other information about the shape.
If I copy / paste the full extracted mxGraphModel in drawio, it works fine.
Is there a way to fix this ?
Thank you for providing this tool.
Requesting guidance on using the rt layout to look like screenshot B if possible.
If no root is passed to rt layout the nodes all end up in a horizontal line.
If root is passed to rt layout then the root node ends up higher
but the rest of the nodes remain in a horizontal line in screenshot A.
The node numbering passed to root in the rt layout was determined according
to a standard definition of rt.
Screenshots:
A. The rtlayout-have.png is what I'm getting.
B. The rtlayout-want.png is what I'm trying to get to from a standard definition of rt.
How can I get screenshot B if possible?
Hi,
want to suggest to allow script to specify font name and font size.
Currently it is hard coded at "N2G_yEd.py" if not mistaken.
Thanks.
Hello -
I am trying to see if it's possible to add label information on a node and include a carriage return, so that info will be displayed on the drawio diagram.
Something like this:
from N2G import drawio_diagram
diagram = drawio_diagram()
diagram.add_diagram("mydiagram")
diagram.add_node(
id="device123", label="Name:device123\nIP:1.2.3.4\nSW:v10.1.2.3.4")
diagram.add_node(
id="device456", label="Name:device456\nIP:5.6.7.8\nSW:v11.3.4.5")
diagram.add_link("device123", "device456",
label="Copper", src_label="Gi0/0", trgt_label="Gi1/0/1")
diagram.layout(algo="kk")
diagram.dump_file(filename="Sample_graph.drawio", folder="./Output/")
Any help or tips would be greatly appreciated. Thank you.
Hi! do you have in mind any support for Nokia, both OSPF or ISIS? thanks!
docs/source/diagram_plugins/DrawIo Module.rst
from N2G import drawio_diagram
diagram = drawio_diagram()
drawing.from_file("./source/old_office_diagram.drawio")
"it probably is diagram instead of drawing" typing error ?
Please depend on igraph
instead of python-igraph
, and suggest using pip install igraph
in diagnostic messages. See igraph/python-igraph#699 for an explanation. Places in this package that might need an update:
https://github.com/search?q=repo%3Admulyalin%2FN2G%20python-igraph&type=code
This is one of the most popular packages on PyPI that still hasn't switched to depending on igraph
instead of python-igraph
.
Hi,
Im currently working on scripting this together with Ansible/LibreNMS. So far everything is looking very nice but i'm banging my head against the wall trying to group/cluster devices based on their names.
So far Im able to identify and give the node a position but the y_pos argument dosent seem to do anything. Is the x_pos argument mandatory when y_pos has been given?
General thought of what im trying to achieve is something along these lines;
servers/clients, etc
By being able to 'preplace' or group the device it might be easier to finalize the drawing after the initial creation. I was hoping I could use the y_pos argument to place it along an axis and it would automaticly preplace the nodes along this line.
Any thoughts or suggestions would be greatly appriciated
Do you have an example available that shows creating graphml graphs used in yed (yworks) for the following use cases :
I am trying to send multiple lines of data to the src/dest labels.
for example,
Gi0/1
mgt
.251
I have tried trgt_label='Gi0/1\nMTU 1500\n.251' but it appears on the same line.
I think it can be supported - because I can manually add the carriage returns in draw.io after the graph has been drawn.
Any ideas?
If i have a 2 connections between a pair of switches, it getting over lapped on each other. We have to manually drag the redudant connection & its label.
I have tried to read the XML file saved from Diagrams.net. It shows zero nodes and edges but it were objects in the file.
Seems the Diagrams.net uses format like following to describe the node.
<mxCell id="GfU-AREGhvKuv_HTweEu-1" value="R1" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="320" y="340" width="120" height="60" as="geometry" />
</mxCell>
If I read the source code, it expect in following format:
<object id="{id}" label="{label}"> <mxCell style="{style}" vertex="1" parent="1"> <mxGeometry x="{x_pos}" y="{y_pos}" width="{width}" height="{height}" as="geometry"/> </mxCell> </object>
There is no tag from Diagrams.net's export. Not sure if that is issue.
Awesome project! I ran into an issue where I was pulling in a diagram off of a drawio file and attempting to update the node. Here is the code I am using:
diagram = drawio_diagram()
diagram.from_file("test.drawio")
diagram.update_node(id="node-1", label="Update Test")
diagram.dump_file(filename="Sample_graph.drawio", folder="./")
But when it gets to updating the node I receive the following error:
AttributeError Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_xxx\xxx.py in
3 diagram.from_file("test.drawio")
4
----> 5 diagram.update_node(id="node-1", label="Update Test")
6
7 diagram.dump_file(filename="Sample_graph.drawio", folder="./")
~\Anaconda3\lib\site-packages\N2G\plugins\diagrams\N2G_DrawIO.py in update_node(self, id, label, data, url, style, width, height, **kwargs)
247 data = data or {}
248 node_data = {}
--> 249 node = self.current_root.find("./object[@id='{}']".format(id))
250 # update data and url attributes
251 node_data.update(data)
AttributeError: 'NoneType' object has no attribute 'find'
I've confirmed the node ID is accurate and that it is loading in the correct template successfully. I looked at the xml and confirmed the ID there as well, no luck. Any idea what is going on here?
hi, lets say i have equipment (from sample doc) with :
{'id': 'SW1', 'top_label': 'CORE', 'bottom_label': '1,1,1,1'}
it will create a reacangular with equal spacing at top and bottom.
But if i only have one label, either top_label or bottom_label, it will create unequal spacing.
How to make spacing equal ?
thanks.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.