{ "metadata": { "name": "", "signature": "sha256:9c80572eb91c1bb013cd18b7aeb7a032eb5bc38871a012dd57bb6eedbf2ccbc2" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Esta vez vamos a ver como usar una peque\u00f1a librer\u00eda que he creado para poder dibujar en el canvas de HTML5 usando python (v\u00eda Brython)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La librer\u00eda la pod\u00e9is encontrar en este [repo alojado en bitbucket](https://bitbucket.org/kikocorreoso/brython-bryplot). Solo voy a usar el m\u00f3dulo `base` y para no complicar el tema lo voy a pegar como c\u00f3digo directamente aqu\u00ed y as\u00ed no habr\u00e1 que importarlo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pero primero algunos apuntes:" ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "\u00bfPara qu\u00e9 otra librer\u00eda para dibujar?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por varios motivos:\n", "\n", "* Porque encontr\u00e9 algo de tiempo.\n", "* Porque me da la gana :-P\n", "* Para aprender a usar `canvas`.\n", "* Para aprender sobre el DOM, eventos,...\n", "* Para aprender." ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Preparativos antes de empezar." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para poder usar este engendro dentro del notebook de IPython vamos a usar una extensi\u00f3n creada para ello. La extensi\u00f3n se llama [brythonmagic](https://github.com/kikocorreoso/brythonmagic) (\u00a1qu\u00e9 derroche de creatividad!) y permite usar brython internamente en el notebook de IPython." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para instalarla solo ten\u00e9is que hacer:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%install_ext https://raw.github.com/kikocorreoso/brythonmagic/master/brythonmagic.py" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Installed brythonmagic.py. To use it, type:\n", " %load_ext brythonmagic\n" ] } ], "prompt_number": 86 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Y para poder usarla hacemos:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%load_ext brythonmagic" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "The brythonmagic extension is already loaded. To reload it, use:\n", " %reload_ext brythonmagic\n" ] } ], "prompt_number": 87 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Adem\u00e1s, hemos de cargar la librer\u00eda javascript brython." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%HTML\n", "" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 88 }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Cargamos el m\u00f3dulo `base` directamente en el notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Una vez listo para empezar a usar brython en el notebook vamos a meter en la siguiente celda el m\u00f3dulo `base`, citado anteriormente, y que contiene una serie de clases para poder dibujar texto y formas simples (c\u00edrculos, cuadrados, polil\u00edneas,...) en el `canvas`." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%brython -s base\n", "from browser import document as doc\n", "import math\n", "\n", "## Base classes for higher level objects\n", "class Figure:\n", " \"\"\"\n", " Base class to create other elements.\n", " \"\"\"\n", " def __init__(self, canvasid, \n", " facecolor = \"white\", \n", " edgecolor = \"black\", \n", " borderwidth = None):\n", " \"\"\" \n", " Parameters\n", " ----------\n", " *canvasid*: String\n", " String indicating the canvas id where the image should be \n", " rendered.\n", " *facecolor*: String\n", " String value containing a valid HTML color\n", " *edgecolor*: String\n", " String value containing a valid HTML color\n", " *borderwidth*: Integer\n", " Value indicating the width of the border in pixels.\n", " If not provided it will 0 and the edgecolor will not be\n", " visible\n", " \"\"\"\n", "\n", " if isinstance(canvasid, str):\n", " self.id = canvasid\n", " else:\n", " raise Exception(\"The canvasid parameter should be a string\")\n", " \n", " try:\n", " self.canvas = doc[self.id]\n", " except:\n", " raise Exception(\"No HTML element with id=%s\" %\n", " self.id)\n", " \n", " try:\n", " self._W = self.canvas.width\n", " self._H = self.canvas.height\n", " self._ctx = self.canvas.getContext(\"2d\")\n", " except:\n", " raise Exception(\"You must provide the ID of a element\")\n", " \n", " self.facecolor = facecolor\n", " self.borderwidth = borderwidth\n", " self.edgecolor = edgecolor\n", " self.clf()\n", " \n", " def clf(self):\n", " \"clear the figure\"\n", " self._ctx.save()\n", " \n", " # The following line should clear the canvas but I found a\n", " # problem when I use beginPath \u00c2\u00bf\u00c2\u00bf\u00c2\u00bfjQuery2030011017107678294003_1414965477473?\n", " #self._ctx.clearRect(0, 0, self._W, self._H)\n", " # So I use the following line tat is less performant but\n", " # this operation shouldn't be done very often...\n", " self.canvas.width = self.canvas.width\n", " \n", " self._ctx.fillStyle = self.facecolor\n", " self._ctx.fillRect(0, 0, self._W, self._H)\n", " self._ctx.fill()\n", " if self.borderwidth:\n", " self._ctx.lineWidth = self.borderwidth\n", " self._ctx.strokeStyle = self.edgecolor\n", " self._ctx.strokeRect(0, 0, self._W, self._H)\n", " self._ctx.stroke()\n", " self._ctx.restore()\n", " \n", "\n", "class Text:\n", " \"\"\"\n", " Base class for text\n", " \"\"\"\n", " def __init__(self, context, x, y, s, \n", " font = \"Verdana\", fontsize = 12,\n", " horizontalalignment='center',\n", " verticalalignment='middle',\n", " color = \"black\",\n", " alpha = 1,\n", " rotate = 0):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " *context: a canvas context\n", " a valid canvas context where the text will be rendered\n", " *x*: int or float\n", " x value for location in pixels\n", " *y*: int or float\n", " y value for location in pixels\n", " *s*: String\n", " String value with the text to be rendered\n", " *font*: String\n", " String value with the font type\n", " *fontsize*: int or float\n", " Size of the font in pixels\n", " *horizontalalignment*: String\n", " ``left``, ``right`` or ``center``\n", " *verticalalignment*: String\n", " ``top``, ``bottom``, ``middle``\n", " *color*: String\n", " A string with a valid HTML color\n", " *alpha*: int or float\n", " Value between 0 (transparent) and 1 (opaque) to set the\n", " transparency of the text\n", " *rotate*: int or float\n", " Value indicating an angle to rotate the text in the\n", " clockwise direction\n", " \"\"\"\n", " self._ctx = context\n", " self.x = x\n", " self.y = y\n", " self.s = s\n", " self.font = font\n", " self.fontsize = fontsize\n", " self.font_complete = \"{0}pt {1} sans-serif\".format(fontsize,\n", " font)\n", " self.horizontalalignment = horizontalalignment\n", " self.verticalalignment = verticalalignment\n", " self.color = color\n", " self.alpha = alpha\n", " self.rotate = rotate\n", " self.draw()\n", " \n", " def draw(self):\n", " self._ctx.save()\n", " self._ctx.translate(self.x, self.y)\n", " self._ctx.rotate(self.rotate * math.pi/ 180.)\n", " self._ctx.textAlign = self.horizontalalignment\n", " self._ctx.textBaseline = self.verticalalignment\n", " self._ctx.font = self.font_complete\n", " self._ctx.globalAlpha = self.alpha\n", " self._ctx.fillStyle = self.color\n", " self._ctx.fillText(self.s, 0, 0)\n", " _ = self._ctx.measureText(self.s)\n", " self.text_width = _.width\n", " self._ctx.restore()\n", " \n", " @property\n", " def transparency(self):\n", " return self.alpha\n", " \n", " @transparency.setter\n", " def transparency(self, alpha):\n", " if alpha >= 0 and alpha <= 1:\n", " self.alpha = alpha\n", " else:\n", " print(\"alpha value must be between 0 and 1\")\n", " \n", " # create more setters and getters for other properties?\n", " \n", "class Shape:\n", " \"\"\"\n", " Base class to create other elements.\n", " \"\"\"\n", " def __init__(self, context, x, y,\n", " facecolor = \"black\", \n", " edgecolor = \"black\",\n", " alpha = 1,\n", " borderwidth = None):\n", " \"\"\" \n", " Parameters\n", " ----------\n", " *context*: a canvas context\n", " a valid canvas context where the text will be rendered\n", " *x*: int or float\n", " x value for location in pixels\n", " *y*: int or float\n", " y value for location in pixels\n", " *facecolor*: String\n", " String value containing a valid HTML color\n", " *edgecolor*: String\n", " String value containing a valid HTML color\n", " *alpha*: int or float\n", " Value between 0 (transparent) and 1 (opaque) to set the\n", " transparency of the text\n", " *borderwidth*: Integer\n", " Value indicating the width of the border in pixels.\n", " If not provided it will 0 and the edgecolor will not be\n", " visible\n", " \"\"\"\n", " self._ctx = context\n", " self.x = x\n", " self.y = y\n", " self.facecolor = facecolor\n", " self.borderwidth = borderwidth\n", " self.edgecolor = edgecolor\n", " self.alpha = alpha\n", "\n", "class Rectangle(Shape):\n", " def __init__(self, *args, size = (0,0), rotation = 0, **kwargs):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " *size*: tuple\n", " (width, height) size of the rectangle in pixels.\n", " *rotation*: int or float\n", " Value indicating an angle to rotate the shape in the\n", " clockwise direction \n", " \"\"\"\n", " Shape.__init__(self, *args, **kwargs)\n", " self.x_size = size[0]\n", " self.y_size = size[1]\n", " self.rotation = rotation\n", " self.draw()\n", " \n", " def draw(self):\n", " self._ctx.save()\n", " self._ctx.globalAlpha = self.alpha\n", " x0 = -self.x_size / 2.\n", " y0 = -self.y_size / 2. \n", " self._ctx.translate(self.x, self.y)\n", " self._ctx.rotate(self.rotation * math.pi / 180.)\n", " self._ctx.fillStyle = self.facecolor\n", " self._ctx.fillRect(x0, y0, self.x_size, self.y_size)\n", " self._ctx.fill()\n", " if self.borderwidth:\n", " self._ctx.lineWidth = self.borderwidth\n", " self._ctx.strokeStyle = self.edgecolor\n", " self._ctx.strokeRect(x0, y0, self.x_size, self.y_size)\n", " self._ctx.stroke()\n", " self._ctx.restore()\n", "\n", "class Circle(Shape):\n", " def __init__(self, *args, radius = 10, **kwargs):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " *radius*: int or float\n", " radius of the circle in pixels.\n", " \"\"\"\n", " Shape.__init__(self, *args, **kwargs)\n", " self.r = radius\n", " self.draw()\n", " \n", " def draw(self):\n", " self._ctx.save()\n", " self._ctx.globalAlpha = self.alpha\n", " self._ctx.beginPath()\n", " self._ctx.fillStyle = self.facecolor\n", " self._ctx.arc(self.x, self.y, self.r, 0, 2 * math.pi)\n", " self._ctx.fill()\n", " if self.borderwidth:\n", " self._ctx.lineWidth = self.borderwidth\n", " self._ctx.strokeStyle = self.edgecolor\n", " self._ctx.arc(self.x, self.y, self.r, 0, 2 * math.pi)\n", " self._ctx.stroke()\n", " self._ctx.closePath()\n", " self._ctx.restore()\n", "\n", "class Wedge(Shape):\n", " def __init__(self, *args, radius = 10, angle = 30, rotation = 0, **kwargs):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " *radius*: int or float\n", " radius of the pie wedge in pixels.\n", " *angle*: int or float\n", " angle width in degrees.\n", " *rotation*: int or float\n", " Value indicating an angle to rotate the shape in the\n", " clockwise direction \n", " \"\"\"\n", " Shape.__init__(self, *args, **kwargs)\n", " self.r = radius\n", " self.angle = angle\n", " self.rotation = rotation\n", " self.draw()\n", " \n", " def draw(self):\n", " self._ctx.save()\n", " self._ctx.globalAlpha = self.alpha\n", " self._ctx.fillStyle = self.facecolor\n", " self._ctx.beginPath()\n", " self._ctx.arc(self.x, self.y, self.r, \n", " (self.rotation - self.angle / 2 - 90) * math.pi / 180.,\n", " (self.rotation + self.angle / 2 - 90) * math.pi / 180., \n", " False)\n", " self._ctx.lineTo(self.x, self.y)\n", " self._ctx.closePath()\n", " self._ctx.fill()\n", " if self.borderwidth:\n", " self._ctx.lineWidth = self.borderwidth\n", " self._ctx.strokeStyle = self.edgecolor\n", " self._ctx.arc(self.x, self.y, self.r, \n", " (self.rotation - self.angle / 2 - 90) * math.pi / 180.,\n", " (self.rotation + self.angle / 2 - 90) * math.pi / 180., \n", " False)\n", " self._ctx.stroke()\n", " self._ctx.restore()\n", "\n", "class Line(Shape):\n", " def __init__(self, *args, polygon = False, borderwidth = 2, **kwargs):\n", " Shape.__init__(self, *args, **kwargs)\n", " self.borderwidth = borderwidth\n", " self.polygon = polygon\n", " self.draw()\n", " \n", " def draw(self):\n", " self._ctx.save()\n", " self._ctx.globalAlpha = self.alpha\n", " self._ctx.beginPath()\n", " self._ctx.moveTo(self.x[0], self.y[0])\n", " for i in range(len(self.x)):\n", " self._ctx.lineTo(self.x[i], self.y[i])\n", " if self.polygon:\n", " self._ctx.closePath()\n", " if self.facecolor:\n", " self._ctx.fillStyle = self.facecolor\n", " self._ctx.fill()\n", " if self.borderwidth:\n", " self._ctx.lineWidth = self.borderwidth\n", " self._ctx.strokeStyle = self.edgecolor\n", " self._ctx.stroke()\n", " self._ctx.restore()\n", "\n", "class Polygon(Line):\n", " def __init__(self, *args, polygon = True, \n", " facecolor = None, **kwargs):\n", " Line.__init__(self, *args, **kwargs)\n", " self.polygon = polygon\n", " self.facecolor = facecolor\n", " self.draw()" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ " \n", "
\n", " \n" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 89 }, { "cell_type": "markdown", "metadata": {}, "source": [ "B\u00e1sicamente, el m\u00f3dulo que acabamos de cargar nos permite usar el canvas de HTML5 a un nivel m\u00e1s alto que usando la API oficial y con una sintaxis pyth\u00f3nica." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para ver todas las posibilidades actuales de la librer\u00eda pod\u00e9is usar [este notebook que est\u00e1 en el mismo repo de la librer\u00eda](http://nbviewer.ipython.org/urls/bitbucket.org/kikocorreoso/brython-bryplot/raw/2de8de16b41b241a295bcd2759ff0c008597dc13/Testing_Bryplot.ipynb)." ] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Ejemplo de uso, el tri\u00e1ngulo de Sierpinski." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El tri\u00e1ngulo de Sierpinski es un fractal y es lo que vamos a dibujar para ver las \u00bfcapacidades? de Bryplot." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Primero hemos de crear el elemento HTML donde vamos a dibujar y que posteriormente le pasaremos al script Brython para que lo use." ] }, { "cell_type": "code", "collapsed": true, "input": [ "HTML = \"\"\"
\"\"\"" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 90 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Y ahora vamos a crear simplemente la figura, que es donde pintaremos todo lo que queramos, con un fondo negro:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%brython -S base -h HTML\n", "fig = Figure('cnvs01', facecolor = \"black\")" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ " \n", "
\n", " \n" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 92 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ahora vamos a dar un peque\u00f1o paso m\u00e1s y vamos a dibujar un tri\u00e1ngulo usando la clase `Polygon`. A esta clase le hemos de pasar el *contexto* del `canvas` que, dicho de forma muy simplificada, es lo que nos permite realmente dibujar en el `canvas`. Adem\u00e1s, le hemos de pasar los puntos que delimitan el pol\u00edgono y luego podemos pararle diferentes valores opcionales." ] }, { "cell_type": "code", "collapsed": true, "input": [ "HTML = \"\"\"
\"\"\"" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 93 }, { "cell_type": "code", "collapsed": false, "input": [ "%%brython -S base -h HTML\n", "fig = Figure('cnvs02', facecolor = \"#bbb\")\n", "ctx = fig._ctx\n", "width = fig._W\n", "height = fig._H\n", "\n", "def triangle(ctx, p1, p2, p3, color = \"yellow\"):\n", " x = [p1[0], p2[0], p3[0]]\n", " y = [p1[1], p2[1], p3[1]]\n", " Polygon(ctx, x, y, facecolor = color)\n", " \n", "p1 = [0,height]\n", "p2 = [width, height]\n", "p3 = [width / 2, 0]\n", "\n", "triangle(ctx, p1, p2, p3)" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ " \n", "
\n", " \n" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 94 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como he comentado anteriormente, ten\u00e9is m\u00e1s clases y con un poco de ma\u00f1a pod\u00e9is crear vuestras propias figuras como las que ten\u00e9is en el [README del repo](https://bitbucket.org/kikocorreoso/brython-bryplot/src/2de8de16b41b241a295bcd2759ff0c008597dc13/README.md?at=default).\n", "\n", "![pie](https://bytebucket.org/kikocorreoso/brython-bryplot/raw/c49dca54fb12a87d211881a5f1e16e573ec35a49/images/pie.png)\n", "\n", "![Rose](https://bytebucket.org/kikocorreoso/brython-bryplot/raw/c49dca54fb12a87d211881a5f1e16e573ec35a49/images/rose.png)\n", "\n", "![Radar](https://bytebucket.org/kikocorreoso/brython-bryplot/raw/c49dca54fb12a87d211881a5f1e16e573ec35a49/images/radar.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por \u00faltimo, vamos a dibujar un fractal usando recursi\u00f3n. No voy a explicar el ejemplo y si os hace falta alguna explicaci\u00f3n pod\u00e9is preguntar en los comentarios del blog puesto que este notebook se convertir\u00e1 en post." ] }, { "cell_type": "code", "collapsed": true, "input": [ "HTML = \"\"\"
\"\"\"" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 98 }, { "cell_type": "code", "collapsed": false, "input": [ "%%brython -S base -h HTML\n", "from __random import randint\n", "\n", "fig = Figure('cnvs03', facecolor = \"#bbb\")\n", "ctx = fig._ctx\n", "width = fig._W\n", "height = fig._H\n", "\n", "def triangle(ctx, p1, p2, p3, color = \"yellow\"):\n", " x = [p1[0], p2[0], p3[0]]\n", " y = [p1[1], p2[1], p3[1]]\n", " Polygon(ctx, x, y, facecolor = color)\n", " \n", "def mitad(p1,p2):\n", " return [abs((p2[0]+p1[0]) / 2.), abs((p2[1]+p1[1]) / 2.)]\n", " \n", "def sierpinski(ctx, p1, p2, p3, degree):\n", " if degree > 7:\n", " raise Exception(\"Degree should be <= 8\")\n", " COLORS = ('#0000FF', '#008000', '#FF0000', '#00BFBF',\n", " '#BF00BF', '#BFBF00', '#000000', '#FFFFFF')\n", " triangle(ctx, p1, p2, p3, color = COLORS[degree])\n", " if degree > 0:\n", " sierpinski(ctx, p1, mitad(p1,p2), mitad(p1,p3), degree-1)\n", " sierpinski(ctx, p2, mitad(p1,p2), mitad(p3,p2), degree-1)\n", " sierpinski(ctx, p3, mitad(p3,p2), mitad(p1,p3), degree-1)\n", " \n", "p1 = [0,0]\n", "p2 = [width, 0]\n", "p3 = [width / 2, height]\n", "sierpinski(ctx, p1, p2, p3, 6)" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ " \n", "
\n", " \n" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 101 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Y eso es todo por hoy.\n", "\n", "\u00bfNos vemos en la PyConES?" ] } ], "metadata": {} } ] }