SVG to bezier curve glyph

Is it possible to parse an SVG file with say, svg.path · PyPI , and then use glyphs.Bezier to draw those paths? Also, is it possible to “fill” a bezier curve? Im am not certain on how SVG files specify this to begin with

Im interested in developing a way that allows me to draw things in Abode Illustrator, so I can add those to my visualization (and programmatically manipulate them)

I would say it’s certaintly possible, but there is currently no API built in to Bokeh to do that conversion. You would need to figure out how to transform the SVG information into the inputs that Bokeh expects.

Bezier glyphs in Bokeh are also not fill-able, at least at present. You would need to discretize the curve into individual cartesian points and draw instead with patch or patches, which do support filling. A “filled Bezier area” is something that could be considered as a feature, if you want to open a GitHub issue to discuss (especially if you can help collaborate on implementation).

I created a simple proof of concept – and it worked!

from svg.path import parse_path #pip3 install svg.path
from svg.path.path import Line
from xml.dom import minidom

from bokeh.models import ColumnDataSource, Plot
from bokeh.models.glyphs import Bezier
from bokeh.io import show

import pandas as pd

# read the SVG file
doc = minidom.parse('./Apple_logo_black.svg')
path_strings = [path.getAttribute('d') for path
                in doc.getElementsByTagName('path')]
doc.unlink()

#parse path and load into ColumnDataSource
l=[]
path=parse_path(path_strings[0])#example is only of len == 1
for p in path:
    try: #y coords need to be negative for some reason
        x0 = p.start.real
        y0 = -1 * p.start.imag
        x1 = p.end.real
        y1 = -1 * p.end.imag
        cx0 = p.control1.real
        cy0 = -1 * p.control1.imag
        cx1 = p.control2.real
        cy1 = -1 * p.control2.imag
        l.append((x0,y0,x1,y1,cx0,cy0,cx1,cy1))
    except AttributeError: #this excepts the "move to" object
        pass
l=pd.DataFrame(l,columns=["x0","y0","x1","y1","cx0","cy0","cx1","cy1"])
l=ColumnDataSource(l)

#plot
plot = Plot(
    title=None, plot_width=400, plot_height=400,
    min_border=0, toolbar_location=None)

glyph = Bezier(x0="x0", y0="y0", x1="x1", y1="y1", cx0="cx0", cy0="cy0", cx1="cx1", cy1="cy1", line_color="#000000", line_width=2)
plot.add_glyph(l,glyph)
show(plot)

file: File:Apple logo black.svg - Wikimedia Commons

Im not certain why they y coords need to be flipped, but this is certainly a promising start. I’ll open a GitHub issue for filled beziers (or bezier patches?), though Im not certain how much I’ll be able to contribute as I am a mid-level python programmer.

@mmcguffi Wow this is awesome!

Im not certain why they y coords need to be flipped

Im guessing SVG has the origin upper left? Bokeh has the “data space” origin in the lower left (more like a typical mathematical axis).

Thanks for the kind words Bryan! I was surprised when it worked right away to be honest haha. I opened a github feature request outlining mostly what I have here, but in more detail (link for future posterity).

In the meantime, do you have a suggestion on how I could approach discretizing the curve(s) into individual cartesian points? Thanks so much!

Hi @mmcguffi I am certain that someone has figured out how to discretize a bezier curve (consider: the curves can be drawn on the raster HTML canvas at all). But I am afraid I don’t have any specific experience or knowledge to pass on about how to do it.

1 Like