Skip to content

Instantly share code, notes, and snippets.

@petekneller
Forked from rpmuller/svg-display.ipynb
Created May 15, 2018 22:33
Show Gist options
  • Save petekneller/3b5dd04536e34bc95a542929342bfa1a to your computer and use it in GitHub Desktop.
Save petekneller/3b5dd04536e34bc95a542929342bfa1a to your computer and use it in GitHub Desktop.
IPython's SVG display functionality, in conjunction with the ease of making SVG strings using ElementTrees, makes it really easy to have a nice drawing canvas inside of IPython.
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": "svg-display"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": "# Playing with SVG graphics in IPython"
},
{
"cell_type": "markdown",
"metadata": {},
"source": "IPython's SVG display functionality, in conjunction with the ease of making SVG strings using ElementTrees, makes it really easy to have a nice drawing canvas inside of IPython."
},
{
"cell_type": "code",
"collapsed": false,
"input": "from IPython.display import SVG\n\ndef tosvg(polygons,border=0):\n \"\"\"\n Convert a list of polygons into an SVG image.\n Polygons are lists of x,y tuples.\n \"\"\"\n import xml.etree.ElementTree as ET\n\n colors = ['aqua','blue','fuchsia','gray','green','lime','maroon',\n 'navy','olive','purple','red','silver','teal','yellow']\n\n xmin,xmax,ymin,ymax = bbox(polygons,border)\n width = xmax-xmin\n height = ymax-ymin\n\n svg = ET.Element('svg', xmlns=\"http://www.w3.org/2000/svg\", version=\"1.1\",\n height=\"%s\" % height, width=\"%s\" % width)\n for i,polygon in enumerate(polygons):\n point_list = \" \".join([\"%d,%d\" % (x-xmin,y-ymin) for (x,y) in polygon])\n ET.SubElement(svg,\"polygon\",fill=colors[i%len(colors)],\n stroke=\"black\",points=point_list)\n #ET.dump(svg)\n return ET.tostring(svg)\n\ndef bbox(polygons,border=0,BIG=1e10):\n \"\"\"\n Compute the bounding box of a list of polygons. Border adds an optional\n border amount to all values in the bbox. \n \"\"\"\n xmin=ymin = BIG\n xmax=ymax = -BIG\n for polygon in polygons:\n for x,y in polygon:\n xmax = max(xmax,x)\n xmin = min(xmin,x)\n ymax = max(ymax,y)\n ymin = min(ymin,y)\n return xmin-border,xmax+border,ymin-border,ymax+border",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": "Given a list of polygons as a list of x,y tuples, you can display them:"
},
{
"cell_type": "code",
"collapsed": false,
"input": "polygons = [\n [(-20,0),(0,100),(5,100),(5,0)],\n [(1,1),(1,10),(10,10),(10,1)],\n [(10,10),(10,50),(50,50),(50,10)],\n [(50,50),(50,150),(150,150),(150,50)],\n ]\nSVG(tosvg(polygons))",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 3,
"svg": "<svg height=\"150\" version=\"1.1\" width=\"170\" xmlns=\"http://www.w3.org/2000/svg\"><polygon fill=\"aqua\" points=\"0,0 20,100 25,100 25,0\" stroke=\"black\"/><polygon fill=\"blue\" points=\"21,1 21,10 30,10 30,1\" stroke=\"black\"/><polygon fill=\"fuchsia\" points=\"30,10 30,50 70,50 70,10\" stroke=\"black\"/><polygon fill=\"gray\" points=\"70,50 70,150 170,150 170,50\" stroke=\"black\"/></svg>",
"text": "<IPython.core.display.SVG at 0x112e66e50>"
}
],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": "and play with the padding:"
},
{
"cell_type": "code",
"collapsed": false,
"input": "SVG(tosvg(polygons,100))",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 4,
"svg": "<svg height=\"350\" version=\"1.1\" width=\"370\" xmlns=\"http://www.w3.org/2000/svg\"><polygon fill=\"aqua\" points=\"100,100 120,200 125,200 125,100\" stroke=\"black\"/><polygon fill=\"blue\" points=\"121,101 121,110 130,110 130,101\" stroke=\"black\"/><polygon fill=\"fuchsia\" points=\"130,110 130,150 170,150 170,110\" stroke=\"black\"/><polygon fill=\"gray\" points=\"170,150 170,250 270,250 270,150\" stroke=\"black\"/></svg>",
"text": "<IPython.core.display.SVG at 0x112e59c50>"
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": "## Fancier Version\nHere's a fancier version I wrote a long time ago, updated to use _repr_svg_ for automatic display."
},
{
"cell_type": "code",
"collapsed": false,
"input": "import xml.etree.ElementTree as ET\n\nclass SVGScene:\n def __init__(self):\n self.items = []\n self.height = 400 # override with bbox calculation\n self.width = 400 # override with bbox calculation\n return\n\n def add(self,item): self.items.append(item)\n \n def bbox(self,border=0,BIG=1e10):\n self.xmin = self.ymin = BIG\n self.xmax = self.ymax = -BIG\n for item in self.items:\n xmin,xmax,ymin,ymax = item.bbox()\n self.xmin = min(self.xmin,xmin)\n self.xmax = max(self.xmax,xmax)\n self.ymin = min(self.ymin,ymin)\n self.ymax = max(self.ymax,ymax)\n self.xmin -= border\n self.ymin -= border\n self.xmax += border\n self.ymax += border\n self.height = self.ymax\n self.width = self.xmax\n return\n \n def _repr_svg_(self): return self.to_svg()\n \n def to_svg(self):\n self.bbox(10)\n svg = ET.Element('svg', xmlns=\"http://www.w3.org/2000/svg\", version=\"1.1\",\n height=\"%s\" % self.height, width=\"%s\" % self.width)\n g = ET.SubElement(svg,\"g\",style=\"fill-opacity:1.0; stroke:black; stroke-width:1;\")\n for item in self.items:\n item.to_svg(g)\n #ET.dump(svg) # useful for debugging\n return ET.tostring(svg)\n\n def line(self,start,end): self.items.append(Line(start,end))\n def circle(self,center,radius,color='blue'): self.items.append(Circle(center,radius,color))\n def rectangle(self,origin,height,width,color='blue'): self.items.append(Rectangle(origin,height,width,color))\n def text(self,origin,text,size=24): self.items.append(Text(origin,text,size))\n \nclass Line:\n def __init__(self,start,end):\n self.start = start #xy tuple\n self.end = end #xy tuple\n return\n \n def to_svg(self,parent):\n ET.SubElement(parent,\"line\",x1=str(self.start[0]),y1=str(self.start[1]),x2=str(self.end[0]),y2=str(self.end[1]))\n\n def bbox(self):\n return min(self.start[0],self.end[0]),max(self.start[0],self.end[0]),min(self.start[1],self.end[1]),max(self.start[1],self.end[1])\n\n\nclass Circle:\n def __init__(self,center,radius,color):\n self.center = center #xy tuple\n self.radius = radius #xy tuple\n self.color = color #rgb tuple in range(0,256)\n return\n \n def to_svg(self,parent):\n color = colorstr(self.color)\n ET.SubElement(parent,\"circle\",cx=str(self.center[0]),cy=str(self.center[1]),r=str(self.radius),\n style=\"fill:%s;\" % color)\n\n def bbox(self):\n return self.center[0]-self.radius,self.center[0]+self.radius,self.center[1]-self.radius,self.center[1]+self.radius\n\nclass Rectangle:\n def __init__(self,origin,height,width,color):\n self.origin = origin\n self.height = height\n self.width = width\n self.color = color\n return\n\n def to_svg(self,parent):\n color = colorstr(self.color)\n ET.SubElement(parent,\"rect\",x=str(self.origin[0]),y=str(self.origin[1]),height=str(self.height),\n width=str(self.width),style=\"fill:%s;\" % color)\n\n def bbox(self):\n return self.origin[0],self.origin[0]+self.width,self.origin[1],self.origin[1]+self.height\n\nclass Text:\n def __init__(self,origin,text,size=24):\n self.origin = origin\n self.text = text\n self.size = size\n return\n\n def to_svg(self,parent):\n fs = \"font-size\"\n el = ET.SubElement(parent,\"text\",x=str(self.origin[0]),y=str(self.origin[1]))\n el.set(\"font-size\",str(self.size))\n el.text = self.text\n \n def bbox(self):\n return self.origin[0],self.origin[0]+self.size,self.origin[1],self.origin[1]+self.size # Guessing here\n \ndef colorstr(rgb): \n if type(rgb) == type(\"\"): return rgb\n return \"#%x%x%x\" % (rgb[0]/16,rgb[1]/16,rgb[2]/16)",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 14
},
{
"cell_type": "markdown",
"metadata": {},
"source": "For example:"
},
{
"cell_type": "code",
"collapsed": false,
"input": "scene = SVGScene()\nscene.rectangle((100,100),200,200,(0,255,255))\nscene.line((200,200),(200,300))\nscene.line((200,200),(300,200))\nscene.line((200,200),(100,200))\nscene.line((200,200),(200,100))\nscene.circle((200,200),30,(0,0,255))\nscene.circle((200,300),30,(0,255,0))\nscene.circle((300,200),30,(255,0,0))\nscene.circle((100,200),30,(255,255,0))\nscene.circle((200,100),30,\"fuchsia\")\nscene.text((50,50),\"Testing SVG\")\nscene",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 15,
"svg": "<svg height=\"340\" version=\"1.1\" width=\"340\" xmlns=\"http://www.w3.org/2000/svg\"><g style=\"fill-opacity:1.0; stroke:black; stroke-width:1;\"><rect height=\"200\" style=\"fill:#0ff;\" width=\"200\" x=\"100\" y=\"100\" /><line x1=\"200\" x2=\"200\" y1=\"200\" y2=\"300\" /><line x1=\"200\" x2=\"300\" y1=\"200\" y2=\"200\" /><line x1=\"200\" x2=\"100\" y1=\"200\" y2=\"200\" /><line x1=\"200\" x2=\"200\" y1=\"200\" y2=\"100\" /><circle cx=\"200\" cy=\"200\" r=\"30\" style=\"fill:#00f;\" /><circle cx=\"200\" cy=\"300\" r=\"30\" style=\"fill:#0f0;\" /><circle cx=\"300\" cy=\"200\" r=\"30\" style=\"fill:#f00;\" /><circle cx=\"100\" cy=\"200\" r=\"30\" style=\"fill:#ff0;\" /><circle cx=\"200\" cy=\"100\" r=\"30\" style=\"fill:fuchsia;\" /><text font-size=\"24\" x=\"50\" y=\"50\">Testing SVG</text></g></svg>",
"text": "<__main__.SVGScene instance at 0x1129e6c20>"
}
],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": "",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 6
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment