<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Pea blog &#187; cairo</title>
	<atom:link href="http://pea.somemilk.org/tag/cairo/feed/" rel="self" type="application/rss+xml" />
	<link>http://pea.somemilk.org</link>
	<description>Just another Somemilk.org Blogs weblog</description>
	<lastBuildDate>Sun, 12 Apr 2009 10:42:41 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>How to draw a smooth curve chart</title>
		<link>http://pea.somemilk.org/2008/10/29/how-draw-smooth-curve-chart/</link>
		<comments>http://pea.somemilk.org/2008/10/29/how-draw-smooth-curve-chart/#comments</comments>
		<pubDate>Wed, 29 Oct 2008 14:13:25 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[cairo]]></category>
		<category><![CDATA[django]]></category>

		<guid isPermaLink="false">http://pea.somemilk.org/?p=3</guid>
		<description><![CDATA[Some of us need to draw charts from time to time. Usually you have little choice, you either use a bar chart or a polygon chart.

In this post I will describe how to draw a pretty smooth curve chart using python and cairo. You can adapt this routine to be used with django, pycha or any other image creation library, code snippets in comments are appreciated.]]></description>
			<content:encoded><![CDATA[<p>Some of us need to draw charts from time to time. Usually you have little choice, you either use a bar chart:</p>
<p><img class="alignnone size-full wp-image-5" src="http://pea.somemilk.org/files/2008/10/vbarchart.png" alt="vbarchart" width="400" height="200" /></p>
<p>or a polygon chart:</p>
<p><img class="alignnone size-full wp-image-6" src="http://pea.somemilk.org/files/2008/10/linechart.png" alt="linechart" width="400" height="200" /></p>
<p>In this post I will describe how to draw a pretty smooth curve chart using python and cairo. You can adapt this routine to be used with django, pycha or any other image creation library, code snippets in comments are appreciated.<br />
<span id="more-3"></span></p>
<p>The main problem is, even if your image creation library has curve drawing functions (Bézier splines), those functions need some control points which DO NOT reside on the curve. So how do those points affect the curve? Let&#8217;s look at the example (it may seem complex, but it&#8217;s not, I just tried to make it more readable and easier to understand):</p>
<pre class="brush: python;">
import cairo
from math import pi

def draw_point(x, y):
    cr.move_to(x + 2, y)
    cr.arc(x, y, 2, 0, 2 * pi)
    cr.set_source_rgba(0, 0, 0, 1)
    cr.stroke()

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 100, 100)
cr = cairo.Context(surface)
cr.set_line_width(2)

x0, y0 = 5, 5 # starting point
x3, y3 = 95, 95 # end point

x1, y1 = 95, 5 # control point 1
x2, y2 = 5, 95 # control point 2

&quot;&quot;&quot;let's draw all the points to see everything clearly&quot;&quot;&quot;
draw_point(x0, y0)
draw_point(x1, y1)
draw_point(x2, y2)
draw_point(x3, y3)

&quot;&quot;&quot;the line from starting point to control point 1&quot;&quot;&quot;
cr.move_to(x0, y0)
cr.line_to(x1, y1)
cr.set_source_rgba(0, 0, 0, 0.1)
cr.stroke()

&quot;&quot;&quot;the line from starting point to control point 2&quot;&quot;&quot;
cr.move_to(x3, y3)
cr.line_to(x2, y2)
cr.set_source_rgba(0, 0, 0, 0.1)
cr.stroke()

&quot;&quot;&quot;the curve itself&quot;&quot;&quot;
cr.move_to(x0, y0)
cr.curve_to(x1, y1, x2, y2, x3, y3)
cr.set_line_width(5)
cr.set_source_rgba(0, 0, 1, 1)
cr.stroke()

surface.write_to_png('curve.png')
</pre>
<p>So we are going to draw a curve from upper left corner down to right lower, and control points are in upper right and down left respectively. That will look like this:</p>
<p><img class="alignnone size-full wp-image-16" src="http://pea.somemilk.org/files/2008/10/curve1.png" alt="curve1" width="100" height="100" /></p>
<p>You can clearly see that the lines between control and starting and end points are tangents to the curve, and the farther the control point gets from the starting or end point, the more &#8220;curvy&#8221; it becomes:</p>
<p><img class="alignnone size-full wp-image-17" src="http://pea.somemilk.org/files/2008/10/curve2.png" alt="curve2" width="100" height="100" /><br />
<img class="alignnone size-full wp-image-18" src="http://pea.somemilk.org/files/2008/10/curve3.png" alt="curve3" width="100" height="100" /></p>
<p>And that leads us to an obvious solution. We need to connect each two points of the chart with curves, and the tangents in those points must be the same from both sides, otherwise the curve won&#8217;t be smooth. If we don&#8217;t look ahead and don&#8217;t look back on the curve, we must make those tangents horizontal. So the first control point will be a little on the left from the starting point and the second one little on the right from the ending point, but on the same vertical position. Here is the code with some debug functions letting us see the control points and tangents, we&#8217;ll comment them out later:</p>
<pre class="brush: python;">
import cairo
from math import pi, sqrt

width = 500
height = 100
graph_data = [
(0, 10),(20, 50),(40, 80),(60, 5),(80, 10),(100, 20),
(120, 30),(140, 60),(160, 95),(180, 30),(200, 50),
(220, 70),(240, 80),(260, 10),(280, 60),(300, 30),
(320, 90),(340, 95),(360, 30),(380, 10),(400, 5),
(420, 20),(440, 80),(460, 70),(480, 20),(500, 40)
]

def prepare_curve_data(graph_data):
    prepared_data = []
    for i in range(0, len(graph_data)):
        x, y = graph_data[i][0], graph_data[i][1]
        if i == 0:
            cx1, cy1 = x, y
        else:
            step_x = x - graph_data[i - 1][0]
            cx1, cy1 = x - step_x/2, y

        if i == len(graph_data) - 1:
            cx2, cy2 = x, y
        else:
            step_x = graph_data[i + 1][0] - x
            cx2, cy2 = x + step_x/2, y

        prepared_data.append((x, y, cx1, cy1, cx2, cy2))

    return prepared_data

def draw_point(x, y, opacity):
    cr.move_to(x + 2, y)
    cr.arc(x, y, 2, 0, 2 * pi)
    cr.set_source_rgba(0, 0, 0, opacity)
    cr.stroke()

def debug_points(cr, prepared_data):
    for i in range(0, len(prepared_data)):
        x, y = prepared_data[i][0], prepared_data[i][1]
        cx1, cy1 = prepared_data[i][2], prepared_data[i][3]
        cx2, cy2 = prepared_data[i][4], prepared_data[i][5]

        draw_point(x, y, 1)
        if cx1 != x or cy1 != y:
            draw_point(cx1, cy1, 0.3)
            cr.move_to(x, y)
            cr.line_to(cx1, cy1)
            cr.set_source_rgba(0, 0, 0, 0.1)
            cr.stroke()

        if cx2 != x or cy2 != y:
            draw_point(cx2, cy2, 0.3)
            cr.move_to(x, y)
            cr.line_to(cx2, cy2)
            cr.set_source_rgba(0, 0, 0, 0.1)
            cr.stroke()

def poly_curve(cr, prepared_data):
    for i in range(0, len(prepared_data) - 1):
        x, y = prepared_data[i][0], prepared_data[i][1]
        cx1, cy1 = prepared_data[i][4], prepared_data[i][5]
        cx2, cy2 = prepared_data[i + 1][2], prepared_data[i + 1][3]
        x2, y2 = prepared_data[i + 1][0], prepared_data[i + 1][1]
        cr.move_to(x, y)
        cr.curve_to(cx1, cy1, cx2, cy2, x2, y2)

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
cr = cairo.Context(surface)
cr.set_line_width(2)

prepared_data = prepare_curve_data(graph_data)
debug_points(cr, prepared_data)
poly_curve(cr, prepared_data)
cr.set_source_rgba(0, 0, 0, 1)
cr.stroke()

surface.write_to_png('curve.png')
</pre>
<p>And this is the result:</p>
<p><img class="alignnone size-full wp-image-19" src="http://pea.somemilk.org/files/2008/10/curve4.png" alt="curve4" width="500" height="100" /></p>
<p>It is quite satisfactory, but it has some flaws. In the areas where the curve must go up or down steadily, we see some kind of bumps. It would be right to lean the tangents on those sections, so the control points would reside on the line parallel to the line connecting previous and next points. To make this more clear, I&#8217;ll rewrite the prepare_curve_data() and show you the result:</p>
<pre class="brush: python;">
def prepare_curve_data(graph_data):
    prepared_data = []
    for i in range(0, len(graph_data)):
        x, y = graph_data[i][0], graph_data[i][1]

        if (i != 0) and (i != len(graph_data) - 1):
            x_left, y_left = graph_data[i - 1][0], graph_data[i - 1][1]
            x_right, y_right = graph_data[i + 1][0], graph_data[i + 1][1]
            step_x_left = (x - x_left) / 2
            step_x_right = (x_right - x) / 2
            dx, dy = x_right - x_left, y_right - y_left
            h = sqrt(dx*dx + dy*dy)
            if h == 0:
                cx1, cy1, cx2, cy2 = x, y, x, y
            else:
                dx1, dy1 = (dx * step_x_left) / h, (dy * step_x_left) / h
                dx2, dy2 = (dx * step_x_right) / h, (dy * step_x_right) / h
                cx1, cx2 = x - dx1, x + dx2
                cy1, cy2 = y - dy1, y + dy2
        else:
            cx1, cy1, cx2, cy2 = x, y, x, y

        prepared_data.append((x, y, cx1, cy1, cx2, cy2))

    return prepared_data
</pre>
<p><img class="alignnone size-full wp-image-22" src="http://pea.somemilk.org/files/2008/10/curve5.png" alt="curve5" width="500" height="100" /></p>
<p>And finally: the snippet ready to be put in your code:</p>
<pre class="brush: python;">
import cairo
from math import pi, sqrt

width = 500
height = 100
graph_data = [
(0, 10),(20, 50),(40, 80),(60, 5),(80, 10),(100, 20),
(120, 30),(140, 60),(160, 95),(180, 30),(200, 50),
(220, 70),(240, 80),(260, 10),(280, 60),(300, 30),
(320, 90),(340, 95),(360, 30),(380, 10),(400, 5),
(420, 20),(440, 80),(460, 70),(480, 20),(500, 40)
]

def poly_curve(cr, graph_data):
    prepared_data = []
    for i in range(0, len(graph_data)):
        x, y = graph_data[i][0], graph_data[i][1]

        if (i != 0) and (i != len(graph_data) - 1):
            x_left, y_left = graph_data[i - 1][0], graph_data[i - 1][1]
            x_right, y_right = graph_data[i + 1][0], graph_data[i + 1][1]
            step_x_left = (x - x_left) / 2
            step_x_right = (x_right - x) / 2
            dx, dy = x_right - x_left, y_right - y_left
            h = sqrt(dx*dx + dy*dy)
            if h == 0:
                cx1, cy1, cx2, cy2 = x, y, x, y
            else:
                dx1, dy1 = (dx * step_x_left) / h, (dy * step_x_left) / h
                dx2, dy2 = (dx * step_x_right) / h, (dy * step_x_right) / h
                cx1, cx2 = x - dx1, x + dx2
                cy1, cy2 = y - dy1, y + dy2
        else:
            cx1, cy1, cx2, cy2 = x, y, x, y

        prepared_data.append((x, y, cx1, cy1, cx2, cy2))

    for i in range(0, len(prepared_data) - 1):
        x, y = prepared_data[i][0], prepared_data[i][1]
        cx1, cy1 = prepared_data[i][4], prepared_data[i][5]
        cx2, cy2 = prepared_data[i + 1][2], prepared_data[i + 1][3]
        x2, y2 = prepared_data[i + 1][0], prepared_data[i + 1][1]
        cr.move_to(x, y)
        cr.curve_to(cx1, cy1, cx2, cy2, x2, y2)

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
cr = cairo.Context(surface)
cr.set_line_width(2)
poly_curve(cr, graph_data)
cr.set_source_rgba(0, 0, 0, 1)
cr.stroke()

surface.write_to_png('curve.png')
</pre>
<p>And the result:</p>
<p><img class="alignnone size-full wp-image-23" src="http://pea.somemilk.org/files/2008/10/curve6.png" alt="curve6" width="500" height="100" /></p>
<p>And now compare this to the polygon chart:</p>
<p><img class="alignnone size-full wp-image-24" src="http://pea.somemilk.org/files/2008/10/curve7.png" alt="curve7" width="500" height="100" /></p>
<p>Neat, eh?</p>
<p>References and things to read:</p>
<p>Cairo — vector graphics library: <a href="http://cairographics.org/">http://cairographics.org/</a><br />
Cairo for Python (pycairo): <a href="http://www.cairographics.org/pycairo/">http://www.cairographics.org/pycairo/</a><br />
Nice pycairo tutorial: <a href="http://www.tortall.net/mu/wiki/CairoTutorial">http://www.tortall.net/mu/wiki/CairoTutorial</a></p>
]]></content:encoded>
			<wfw:commentRss>http://pea.somemilk.org/2008/10/29/how-draw-smooth-curve-chart/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
