Git Product home page Git Product logo

n2g's Introduction

Downloads Documentation Status

Need To Graph

N2G is a library to generate diagrams in yWorks graphml or Diagrams drawio formats or produce JSON data compatible with 3d-force-graph JSON input syntax allowing 3D visualization.

Demo

Why?

To save your time on producing consistently looking, editable diagrams of arbitrary size and complexity in a programmatic way helping to satisfy your "Need To Graph" desire.

How?

Not a secret that many applications use XML structured text to save their diagrams content, then why not to do the opposite - produce XML structured text that applications can open and understand and work with. N2G does exactly that, it takes structured data - csv, dictionary, list or api calls and gives back XML text that can be opened and edited by application of choice.

What?

All formats supported so far have very similar API capable of:

  • adding nodes and links with various attributes such as shape, labels, urls, data, styles
  • bulk graph creation using from_x methods supporting lists, dictionaries or csv data
  • existing nodes and links attributes manipulation and update
  • loading existing XML diagram files for processing and modification
  • deletion of nodes and links from diagrams
  • comparing two diagrams to highlight the difference between them
  • layout your diagram with algorithms available in igraph library
  • returning results in text format or saving directly into the file

Reference documentation for more information.

What it's not?

N2G is not a magic bullet that will produce perfect diagrams for you, it can help to simplify the process of adding elements to your diagrams. However, (manual) efforts required to put all the elements in positions where they will satisfy your inner sense of perfection, as a result, keep in mind that (normally) the more elements you have on your diagram, the more efforts required to make it looks good.

Quite unlikely it would ever be a tool with support of all capabilities available in subject applications, however, feature requests are welcomed.

Example

from N2G import yed_diagram

diagram = yed_diagram()
sample_list_graph = [
    {'source': {'id': 'SW1', 'top_label': 'CORE', 'bottom_label': '1,1,1,1'}, 'src_label': 'Gig0/0', 'target': 'R1', 'trgt_label': 'Gig0/1'},
    {'source': {'id': 'R2', 'top_label': 'DC-PE'}, 'src_label': 'Gig0/0', 'target': 'SW1', 'trgt_label': 'Gig0/2'},
    {'source': {'id':'R3', 'bottom_label': '1.1.1.3'}, 'src_label': 'Gig0/0', 'target': 'SW1', 'trgt_label': 'Gig0/3'},
    {'source': 'SW1', 'src_label': 'Gig0/4', 'target': 'R4', 'trgt_label': 'Gig0/1'},
    {'source': 'SW1', 'src_label': 'Gig0/5', 'target': 'R5', 'trgt_label': 'Gig0/7'},
    {'source': 'SW1', 'src_label': 'Gig0/6', 'target': 'R6', 'trgt_label': 'Gig0/11'}
]
diagram.from_list(sample_list_graph)
diagram.dump_file(filename="Sample_graph.graphml", folder="./")

Disclaimer

Author of this module not affiliated with any of the application Vendors mentioned so far. The choice of formats to support was primarily driven by the fact of how much functionality available in particular application for free. Moreover, this module does not use any aforementioned (diagramming) applications in any programmatic way to produce its results, in other words, none of the aforementioned applications required to be installed on the system for this (N2G) module to work.

Contributions

Feel free to submit an issue, to report a bug or ask a question, feature requests are welcomed or buy Author a coffee

n2g's People

Contributors

dmulyalin avatar itdependsnetworks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

n2g's Issues

[Question/Suggestion] NOT an issue

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

XML String
    # 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.

Python Code
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.

Output XML
<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.

DrawIO Diagram output

github-issue

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.

YAML Code
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?

Tests not passing

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 .........

Excellent Software! - one question regarding src_label and trgt_label

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?

image

Method for 'clustering' nodes as posistional argument

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;


CORE/ROUTER

SERVER/DISTRUBUTION

EDGE

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

Help for rt layout

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?

rtlayout.zip

Center text horizontally if only has "bottom_label", no "top-label"

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.

Unable to update node

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?

Use drawio library element as node style

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 ?

sample graph : src_label does not show in topology

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.

Sample graph:

from N2G import yed_diagram

diagram = yed_diagram()
sample_list_graph = [
{'source': {'id': 'SW1', 'top_label': 'CORE', 'bottom_label': '1,1,1,1'}, 'src_label': 'Gig0/0', 'target': 'R1', 'trgt_label': 'Gig0/1'},
{'source': {'id': 'R2', 'top_label': 'DC-PE'}, 'src_label': 'Gig0/0', 'target': 'SW1', 'trgt_label': 'Gig0/2'},
{'source': {'id':'R3', 'bottom_label': '1.1.1.3'}, 'src_label': 'Gig0/0', 'target': 'SW1', 'trgt_label': 'Gig0/3'},
{'source': 'SW1', 'src_label': 'Gig0/4', 'target': 'R4', 'trgt_label': 'Gig0/1'},
{'source': 'SW1', 'src_label': 'Gig0/5', 'target': 'R5', 'trgt_label': 'Gig0/7'},
{'source': 'SW1', 'src_label': 'Gig0/6', 'target': 'R6', 'trgt_label': 'Gig0/11'}
]
diagram.from_list(sample_list_graph)
diagram.dump_file(filename="Sample_graph.graphml", folder="./")

layout not working

Hi, we have some issue :

  1. we try to use layout in yEd, but the result is still piled up in one icon,
    we already try to use as example :
    diagram.layout(algo="drl", width=500, height=500)

but still not successful.

  1. for yEd, we want to try some parameter, like :
    Layout = hierarchical,
    node to node distance = 200

so we dont need to modify .graphml file again.

thanks.

YED Grouping example requested

Do you have an example available that shows creating graphml graphs used in yed (yworks) for the following use cases :

  1. how to create a group
  2. how to add nodes to a group
  3. how to add a group to a group

Can't Read New Diagrams.net XML

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.

Typing error documentation

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 ?

Add carriage return to node "label" in drawio_diagram class

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.

Connecting two nodes without labels on edges causes one of edges not to be created

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','./')

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.