svgdiagrammxgraphdraw.io

How to import/convert SVG to native Draw.io shape?


I'm in search of a diagram editor that supports importing intricate shapes while maintaining the page's styling. Draw.io seems promising, but if I'm not mistaken, it primarily facilitates SVG imports or raw XML inputs without offering a WYSIWYG interface.

There are various questions on StackOverflow regarding this, but none of them seem to address my specific concern. For instance, this question details the process of importing an SVG and then altering its colors.

To illustrate my point, I've provided two screenshots below. The left screenshot depicts an SVG I imported, whereas the one on the right showcases a standard Draw.io shape. As observed, I can only modify attributes like Fill .cls-1 or Line .cls-1 for the SVG. Conversely, for the standard shape, I can assign a distinct style.

I am dreaming of a diagram editor that allows for importing complex shapes and preserving the style of the page. Draw.io has potential, but from my understanding, it only supports SVG import or raw XML code with no WYSIWYG editor.

Numerous question on SO exists, but none currently answer my questions. shows how to import an SVG and then edit the colors.

For example on the this screenshot I have to the left a imported SVG and on the right a regular Draw.io shape. You can see that for the SVG, I can only edit Fill .cls-1 or Line .cls-1, but for the second shape, I can associate a style:

enter image description here enter image description here

Is there a method to transform an SVG into native Draw.io shapes, enabling style application?

My naive approach was to translate the SVG:

<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" 
     viewBox="0 0 91.5 91.5">
  <defs>
    <style>
      .cls-1 {
        fill: none;
        stroke: #231f20;
        stroke-miterlimit: 10;
      }
    </style>
  </defs>
  <g id="Layer_1-2" data-name="Layer 1">
    <rect class="cls-1" x=".5" y=".5" width="90.5" height="90.5"/>
    <polygon class="cls-1" points="61.09 19.17 30.4 19.17 
    15.06 45.75 30.4 72.32 61.09 72.32 76.44 45.75 61.09 19.17"/>
    <line class="cls-1" x1="30.4" y1="72.32" x2="61.09" y2="19.17"/>
    <line class="cls-1" x1="76.44" y1="45.75" x2="15.86" y2="45.75"/>
    <line class="cls-1" x1="30.4" y1="19.17" x2="61.09" y2="72.32"/>
  </g>
</svg>

Into the Draw.io shape language:

<shape aspect="variable" h="90.5" w="90.5" strokewidth="inherit">    
  <background>
    <rect h="90.5" w="90.5" x="0.5" y=".5"/>
  </background>  
  <foreground>
    <fillstroke />
    <path>
      <move x="61.09" y="19.17"/>
      <line x="30.4" y="19.17"/>
      <line x="15.06" y="45.75"/>
      <line x="30.4" y="72.32"/>
      <line x="30.4" y="72.32"/>
      <line x="61.09" y="72.32"/>
      <line x="76.44" y="45.75"/>
      <line x="61.09" y="19.17"/>
    </path>
    <stroke />
    <path>
      <move x="30.4" y="72.32"/>
      <line x="61.09" y="19.17"/>
    </path>
    <stroke />
    <path>
      <move x="76.44" y="45.75"/>
      <line x="15.86" y="45.75"/>
    </path>
    <stroke />
    <path>
      <move x="30.4" y="19.17"/>
      <line x="61.09" y="72.32"/> 
    </path>
    <stroke />
  </foreground>
</shape>

Solution

  • Why not writing a Python Script using minidom?

    from xml.dom import minidom
    file = minidom.parse('specimen.svg')
    svg = file.getElementsByTagName('svg')[0]
    viewBox = svg.getAttribute('viewBox').split(' ')
    width, height = float(viewBox[2]), float(viewBox[3])
    
    
    def create_constraint(document, parent, x, y, perimeter=1):
        """ Create a constraint """
        constraint = document.createElement('constraint')
        constraint.setAttribute('x', str(x))
        constraint.setAttribute('y', str(y))
        constraint.setAttribute('perimeter', str(perimeter))
        parent.appendChild(constraint)
    
    
    def create_connections(document, parent, height, width, nx=3, ny=3):
        connections = document.createElement('connections')
        parent.appendChild(connections)
    
        nx += 1
        ny += 1
        for x in [1/nx*n for n in range(1, nx)]:
            create_constraint(document, connections, 0, x)
            create_constraint(document, connections, 1, x)
            create_constraint(document, connections, x, 0)
            create_constraint(document, connections, x, 1)
    
    
    def process_rect(document, parent, rect):
        rectangle = document.createElement('rect')
        rectangle.setAttribute('x', rect.getAttribute('x'))
        rectangle.setAttribute('y', rect.getAttribute('y'))
        rectangle.setAttribute('width', rect.getAttribute('width'))
        rectangle.setAttribute('height', rect.getAttribute('height'))
        parent.appendChild(rectangle)
        parent.appendChild(document.createElement('stroke'))
    
    
    def process_polygon(document, parent, polygon):
        """ Process a polygon """
        points = polygon.getAttribute('points').split(' ')
        points = zip(*(iter(points),) * 2)
        path = document.createElement('path')
        parent.appendChild(path)
        move = document.createElement('move')
        p = next(points)
        move.setAttribute('x', p[0])
        move.setAttribute('y', p[1])
        path.appendChild(move)
        for (x, y) in points:
            line = document.createElement('line')
            line.setAttribute('x', x)
            line.setAttribute('y', y)
            path.appendChild(line)
        parent.appendChild(document.createElement('stroke'))
    
    
    def process_line(document, parent, line):
        """ Process a line """
        x1 = line.getAttribute('x1')
        y1 = line.getAttribute('y1')
        x2 = line.getAttribute('x2')
        y2 = line.getAttribute('y2')
        path = document.createElement('path')
        parent.appendChild(path)
        move = document.createElement('move')
        move.setAttribute('x', x1)
        move.setAttribute('y', y1)
        path.appendChild(move)
        line = document.createElement('line')
        line.setAttribute('x', x2)
        line.setAttribute('y', y2)
        path.appendChild(line)
        parent.appendChild(document.createElement('stroke'))
    
    
    def process_group(document, parent, group):
        """ Process a group """
        for child in group.childNodes:
            if child.nodeType != child.ELEMENT_NODE:
                continue
    
            if child.tagName == 'g':
                process_group(document, parent, child)
            elif child.tagName == 'polygon':
                process_polygon(document, parent, child)
            elif child.tagName == 'line':
                process_line(document, parent, child)
            elif child.tagName == 'rect':
                process_rect(document, parent, child)
    
    
    document = minidom.Document()
    shape = document.createElement('shape')
    shape.setAttribute('aspect', 'variable')
    shape.setAttribute('h', viewBox[2])
    shape.setAttribute('w', viewBox[3])
    shape.setAttribute('strokewidth', "inherit")
    
    parent = document.appendChild(shape)
    
    create_connections(document, parent, height, width)
    
    background = document.createElement('background')
    rect = document.createElement('rect')
    rect.setAttribute('x', viewBox[0])
    rect.setAttribute('y', viewBox[1])
    rect.setAttribute('w', viewBox[2])
    rect.setAttribute('h', viewBox[3])
    background.appendChild(rect)
    foreground = document.createElement('foreground')
    foreground.appendChild(document.createElement('fillstroke'))
    shape.appendChild(background)
    shape.appendChild(foreground)
    
    process_group(document, foreground, svg)
    print(document.toprettyxml())
    

    Just for Fun

    I draw this cat in SVG:

    enter image description here

    <?xml version="1.0" encoding="UTF-8"?>
    <svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 91.5 91.5">
      <defs>
        <style>
          .cls-1 {
            stroke: #231f20;
            stroke-miterlimit: 10;
          }
    
          .cls-1, .cls-2 {
            fill: none;
          }
    
          .cls-2, .cls-3 {
            stroke: #000;
            stroke-linecap: round;
            stroke-linejoin: round;
          }
    
          .cls-3 {
            fill: #231f20;
          }
        </style>
      </defs>
      <g id="Layer_1-2" data-name="Layer 1">
        <rect class="cls-1" x=".5" y=".5" width="90.5" height="90.5"/>
        <path class="cls-2" d="m52.95,78.36c.66.31,1.32.14,1.97,0,1.23-.28,2.47-.57,3.66-.96,1.32-.43,2.52-1.19,3.36-2.26.61-.78.91-1.84,1.17-2.83.28-1.02.74-2,.73-3.12-.01-.88.26-1.76.3-2.65.17-3.33-.22-6.59-1.84-9.56-1.09-1.99-2.43-3.82-4.12-5.38-2.08-1.93-3.84-4.13-5.37-6.51-.49-.76-.36-1.33.41-1.83.66-.43,1.35-.81,1.96-1.3,1.25-1,1.71-2.45,2.04-3.93.4-1.79.32-3.61.22-5.43-.07-1.31-.06-2.63-.15-3.94-.06-.87-.29-1.72-.38-2.58-.04-.42,0-.88.13-1.28.42-1.28.93-2.53,1.33-3.81.27-.86.56-1.76.59-2.65.03-1.13-.08-2.29-.33-3.39-.29-1.28-1.18-1.74-2.42-1.5-2.14.42-4.17,1.13-6.11,2.11-.4.2-.87.37-1.14.69-.77.91-1.68.61-2.6.46-1.54-.25-3.07-.55-4.62-.74-2.48-.3-4.87.26-7.26.8-.84.19-1.57.11-2.33-.45-1.4-1.03-3.03-1.66-4.67-2.17-1.09-.34-2.25-.54-3.39-.68-.9-.11-1.44.32-1.69,1.2-.48,1.75-.61,3.54-.06,5.29.53,1.69,1.18,3.35,1.74,5.03.11.34.2.74.13,1.08-.51,2.45-.51,4.93-.52,7.4,0,1.24.03,2.49.14,3.73.28,3.12,1.8,5.41,4.82,6.42,1.38.46,1.78,1.19,1.64,2.55-.19,1.87-.25,3.75-.36,5.63-.16,2.8-.38,5.6-.22,8.42.1,1.81.07,3.62.15,5.43.11,2.31.27,4.62.4,6.93.07,1.22.1,2.45.22,3.67.06.64.22,1.28.4,1.9.32,1.07,2.39,1.76,3.18,1.55.79-.21,1.19-.9,1.5-1.61.23-.56.45-1.12.7-1.74.82.56.78,1.34.92,2.02.25,1.15.46,1.33,1.57,1.51.57.1,1.09.13,1.63-.08.87-.34,1.32-1.08,1.37-2.04.07-1.52.21-3.03.33-4.55s.22-3.03.34-4.55c.05-.66.11-1.31.17-1.97"/>
        <path class="cls-2" d="m52.88,78.36c.11-.86.34-1.72.32-2.58-.04-1.86.09-3.73-.37-5.57-.27-1.09-.32-2.23-.58-3.32-.18-.73-.49-1.46-.88-2.1-.53-.87-1.62-.99-2.34-.26-.41.41-.78.99-.87,1.55-.26,1.54-.37,3.11-.11,4.68.16,1.01.14,2.04.3,3.05.18,1.12.49,2.22.73,3.33.09.4.03.88.23,1.2.25.39.65.73,1.07.95.63.33,1.27.23,1.82-.25.23-.2.45-.41.68-.61"/>
        <path class="cls-2" d="m63.75,70.69c.84.23,1.66.51,2.51.67,2.45.45,4.31-.33,5.01-2.91.33-1.22.54-2.48.75-3.73.23-1.35.89-2.09,2.11-2.24.92-.11,1.95.62,2.38,1.7.56,1.42.53,2.88.26,4.34-.32,1.75-.88,3.46-1.85,4.94-1.31,1.99-3.06,3.36-5.61,3.66-2.17.25-4.09-.24-5.98-1.14-.46-.22-.86-.53-1.29-.81"/>
        <path class="cls-3" d="m54.38,21.94c.05-.11.06-.26.14-.33.95-.89,1.21-2.05,1.28-3.26.05-.9-.46-1.31-1.27-1-.89.34-1.76.76-2.46,1.47-.29.29-.34.49-.06.76.77.73,1.54,1.45,2.31,2.17"/>
        <path class="cls-3" d="m26.74,21.87c.79-.68,1.59-1.35,2.42-2.06-.7-1.08-1.66-1.61-2.77-1.86-.51-.11-.86.25-.8.8.07.62.22,1.24.41,1.83.17.52.44,1,.67,1.5"/>
        <path class="cls-2" d="m49.22,78.5c-.52.05-1.04.14-1.56.12-.91-.03-1.81-.12-2.72-.19-1.02-.08-2.04-.18-3.06-.27"/>
        <path class="cls-2" d="m36.52,65.87c-.29,2.22-.05,4.44,0,6.65.02,1.29-.04,2.58-.06,3.87"/>
        <path class="cls-2" d="m40.53,33.89c0,.23.03.46,0,.68-.08.45.21.94-.19,1.36-.61.63-1.26,1.18-2.18,1.21-.36.01-.72-.12-1.09-.19"/>
        <path class="cls-2" d="m40.53,35.86c.23.2.46.4.68.61.77.76,1.66.88,2.65.54"/>
        <path class="cls-2" d="m52.82,34.63c-1.02.09-2.04.18-3.06.27-.07,0-.14-.04-.2-.06"/>
        <path class="cls-2" d="m28.37,34.57c.72.02,1.45.04,2.17.07.27.01.54.04.81.07"/>
        <path class="cls-2" d="m31.16,36.94c-.88.25-1.77.5-2.65.75"/>
        <path class="cls-2" d="m49.29,37.21c.86.32,1.72.63,2.58.95"/>
        <path class="cls-2" d="m39.51,33.14c.34.23.67.46,1.06.73.33-.24.65-.49.98-.73"/>
        <circle class="cls-3" cx="33.12" cy="28.1" r="1.76"/>
        <circle class="cls-3" cx="46.15" cy="28.1" r="1.76"/>
      </g>
    </svg>
    

    From this script I am able to generate this:

    enter image description here

    As you can see, you can apply native Draw.io styles...