<?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>code.openark.org &#187; Graphs</title>
	<atom:link href="http://code.openark.org/blog/tag/graphs/feed" rel="self" type="application/rss+xml" />
	<link>http://code.openark.org/blog</link>
	<description>Blog by Shlomi Noach</description>
	<lastBuildDate>Fri, 12 Mar 2010 13:27:07 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Static charts vs. interactive charts</title>
		<link>http://code.openark.org/blog/mysql/static-charts-vs-interactive-charts</link>
		<comments>http://code.openark.org/blog/mysql/static-charts-vs-interactive-charts#comments</comments>
		<pubDate>Tue, 02 Mar 2010 13:28:08 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Analysis]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[mycheckpoint]]></category>
		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=2027</guid>
		<description><![CDATA[I&#8217;m having my usual fun with charts. Working on mycheckpoint, I&#8217;ve generated monitoring charts using the Google Chars API. But I&#8217;ve also had chance to experiment and deploy interactive charts, JavaScript based. In particular, I used and tweaked dygraphs.
I&#8217;d like to note some differences in using charts of both kinds. And I think it makes [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m having my usual fun with charts. Working on <a href="http://code.openark.org/forge/mycheckpoint">mycheckpoint</a>, I&#8217;ve generated monitoring charts using the <a href="http://code.google.com/apis/chart/image_charts.html">Google Chars API</a>. But I&#8217;ve also had chance to experiment and deploy interactive charts, JavaScript based. In particular, I used and tweaked <a href="http://www.danvk.org/dygraphs/">dygraphs</a>.</p>
<p>I&#8217;d like to note some differences in using charts of both kinds. And I think it makes a very big difference.</p>
<h4>Static charts</h4>
<p>I&#8217;ll call any image-based chart by &#8220;static chart&#8221;. It&#8217;s just a static image. Example of such charts are those generated by Google Image Charts (they now also have new, interactive charts), or <a href="http://oss.oetiker.ch/rrdtool/index.en.html">RRDtool</a>. Show below is an example of a static chart; in this example, generated by Google:<span id="more-2027"></span></p>
<blockquote>
<pre><img class="alignnone" src="http://chart.apis.google.com/chart?cht=lc&amp;chs=400x200&amp;chts=808080,12&amp;chtt=Dec+4,+15:00++-++Dec+5,+15:00&amp;chf=c,s,ffffff&amp;chdl=com_select_psec|com_insert_psec|com_delete_psec|com_update_psec|com_replace_psec&amp;chdlp=b&amp;chco=ff8c00,4682b4,9acd32,dc143c,9932cc&amp;chd=s:zpvxszxsxur11p1xt10wyxzuyv6xw4yx3x041x2zz6zvz7y91x23z4niqkkmojllkhknhlgnmimohilkkqgkkmnmhlljinjmnhmo________________imlnpmkukopmnpjsojnrrlrqnpprs,iZagVcgWXaZdjVbgSbhYdXZXZcbcXZbcadabccabbZaaZeabdYbbZXceWUXaXYXSXVXaSSZUUXWYUUXbTYUUVabWWVZZVYaWZZYa________________ZYbVcXWdYWZcXaYaYWXYfZcdaVZaZ,MNNNNNONLKOMPNNMNNOPMNNLMQNOMMMNMNNNNRONNOPMNQMPONPOLMTNKJKMKKJILKILJJJLIIKUMHJJIIHHHKJIIHIIIHJHIIJM________________NOMLMMLOPOPKLKKNPKMMNMOPQNNOL,NIIMHKOIHKIKOHKMGKNJKIJIKMKLJJMKIKJKLLJLLJLLKMLJLJKKKIIVIIJLJKJHJIKLIHMIIKIKIIKLHKIIJLKIJJKKIJKIKKJK________________IIKILJJLKIKKHJJJJIJJMIKLKGJKK,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAA&amp;chxt=x,y&amp;chxr=1,0,136.620000&amp;chxl=0:||16:00||+||20:00||+||00:00||+||04:00||+||08:00||+||12:00||+||&amp;chxs=0,505050,10,0,lt&amp;chg=4.17,25,1,2,0.00,0&amp;chxp=0,0.00,4.17,8.34,12.51,16.68,20.85,25.02,29.19,33.36,37.53,41.70,45.87,50.04,54.21,58.38,62.55,66.72,70.89,75.06,79.23,83.40,87.57,91.74,95.91,100.08" alt="" width="400" height="200" /></pre>
</blockquote>
<h4>Pros and cons of static charts</h4>
<p>Pros</p>
<ul>
<li>Images can be viewed on any graphical platform. Browsers, email clients, cell phones, whatever.</li>
<li>Self contained: chart image, legend, scales: all in one image.</li>
<li>As such, easy to move around.</li>
<li>Are safe to use.</li>
</ul>
<p>Cons</p>
<ul>
<li>Images are fuzzy. Is the <strong>com_replace_psec</strong> really 0? Maybe it&#8217;s 0.1? A larger value can make lower values hard to tell.</li>
<li>Images are inaccurate: the colors can lie. The red and green lines showing are hard to tell apart. The red is painted above the green. Data gets &#8220;lost&#8221;.</li>
<li>They do not zoom (one needs to regenerate larger image)</li>
<li>Unless encoded with base64, HTML pages which include images need to link outside.</li>
<li>In the particular case of Google Charts, one is limited to 2K length URL. Trust me, it&#8217;s a big limitation! (PS, Google now support POST method to allow for up to 16K. But&#8230; it&#8217;s a POST method&#8230;)</li>
<li>In the particular case of Google Charts, one must have an internet connection.</li>
<li>In the particular case of Google Charts, one must submit data to Google.</li>
</ul>
<h4>Interactive charts</h4>
<p>Interactive charts are those which react to your commands. These are either JavaScript or Flash based, mostly. They allow for really nice features. Take the following chart as an example: try and <strong>move over with your mouse</strong>; or <strong>select sections to zoom in</strong>.</p>
<p><script src="http://code.openark.org/blog/wp-content/uploads/2010/03/dygraph-combined.js" type="text/javascript"></script><br />
<!--[if IE]><script src="http://code.openark.org/blog/wp-content/uploads/2010/03/excanvas-min.js"></script>< ![endif]--></p>
<blockquote>
<pre>
<h4>DML</h4>
<div id="graphDiv_DML" class="graphdiv" style="width: 400px; height: 160px;">[graphDiv]</div>
<div id="labelsDiv_DML" class="legend">[labelsDiv]</div>
</pre>
</blockquote>
<p><script type="text/javascript">// < ![CDATA[
// < ![CDATA[
 g_DML = new Dygraph( document.getElementById("graphDiv_DML"),                         "Date,com_select_psec,com_insert_psec,com_delete_psec,com_update_psec,com_replace_psec\n2009-12-04 15:00:00,113.28,76.54,26.14,29.54,0.00\n2009-12-04 15:10:00,91.51,55.74,30.07,18.13,0.00\n2009-12-04 15:20:00,104.98,57.75,28.26,18.24,0.00\n2009-12-04 15:30:00,110.64,72.17,30.17,27.58,0.00\n2009-12-04 15:40:00,97.79,46.27,29.27,15.91,0.00\n2009-12-04 15:50:00,114.35,61.85,29.55,22.45,0.00\n2009-12-04 16:00:00,110.82,72.38,30.67,30.70,0.00\n2009-12-04 16:10:00,99.05,49.70,29.35,18.56,0.00\n2009-12-04 16:20:00,109.68,50.87,25.74,16.55,0.00\n2009-12-04 16:30:00,103.92,58.70,22.09,22.58,0.00\n2009-12-04 16:40:00,97.24,56.29,31.71,18.78,0.00\n2009-12-04 16:50:00,119.25,65.72,27.80,22.56,0.00\n2009-12-04 17:00:00,118.31,78.34,34.18,30.43,0.00\n2009-12-04 17:10:00,91.06,47.59,29.66,16.60,0.00\n2009-12-04 17:20:00,117.81,59.82,28.94,21.40,0.00\n2009-12-04 17:30:00,109.79,71.37,27.62,27.64,0.00\n2009-12-04 17:40:00,101.07,40.51,29.57,14.18,0.00\n2009-12-04 17:50:00,117.66,61.18,28.31,22.44,0.00\n2009-12-04 18:00:00,115.48,72.82,32.12,28.74,0.00\n2009-12-04 18:10:00,106.51,54.34,32.96,20.37,0.00\n2009-12-04 18:20:00,111.34,65.11,27.60,22.60,0.00\n2009-12-04 18:30:00,109.66,52.25,29.47,18.47,0.00\n2009-12-04 18:40:00,113.63,55.16,28.67,19.98,0.00\n2009-12-04 18:50:00,103.72,51.86,24.67,18.08,0.00\n2009-12-04 19:00:00,111.27,55.78,26.66,22.62,0.00\n2009-12-04 19:10:00,105.73,63.16,35.37,25.88,0.00\n2009-12-04 19:20:00,130.50,60.07,29.82,22.99,0.00\n2009-12-04 19:30:00,110.41,62.64,30.35,24.46,0.00\n2009-12-04 19:40:00,107.05,52.04,27.15,19.22,0.00\n2009-12-04 19:50:00,126.11,57.06,27.12,19.34,0.00\n2009-12-04 20:00:00,111.36,61.40,26.13,25.96,0.00\n2009-12-04 20:10:00,108.82,61.97,29.52,23.49,0.00\n2009-12-04 20:20:00,122.57,59.28,26.03,18.48,0.00\n2009-12-04 20:30:00,109.35,65.42,29.21,23.13,0.00\n2009-12-04 20:40:00,117.19,57.69,28.01,19.24,0.00\n2009-12-04 20:50:00,125.03,61.15,28.95,21.74,0.00\n2009-12-04 21:00:00,118.31,63.11,29.66,23.99,0.00\n2009-12-04 21:10:00,109.98,62.24,38.77,24.02,0.00\n2009-12-04 21:20:00,121.28,57.78,30.48,20.26,0.00\n2009-12-04 21:30:00,113.89,59.62,29.77,23.72,0.00\n2009-12-04 21:40:00,113.93,60.42,29.35,24.71,0.00\n2009-12-04 21:50:00,130.36,55.68,31.94,21.16,0.00\n2009-12-04 22:00:00,114.11,58.93,32.62,24.41,0.00\n2009-12-04 22:10:00,105.52,58.58,26.04,24.47,0.00\n2009-12-04 22:20:00,113.52,57.04,29.26,22.28,0.00\n2009-12-04 22:30:00,132.64,66.22,34.87,26.82,0.00\n2009-12-04 22:40:00,112.08,59.19,27.16,23.73,0.00\n2009-12-04 22:50:00,136.62,59.74,33.41,20.04,0.00\n2009-12-04 23:00:00,119.58,65.67,31.40,24.40,0.00\n2009-12-04 23:10:00,109.12,53.74,28.38,19.60,0.00\n2009-12-04 23:20:00,121.52,60.03,33.16,22.38,0.00\n2009-12-04 23:30:00,123.33,59.59,31.09,22.51,0.00\n2009-12-04 23:40:00,114.75,56.82,25.53,23.43,0.00\n2009-12-04 23:50:00,124.92,51.26,27.86,19.03,0.00\n2009-12-05 00:00:00,88.23,63.21,41.50,18.59,0.00\n2009-12-05 00:10:00,75.10,66.62,29.94,48.01,0.00\n2009-12-05 00:20:00,94.85,49.27,22.05,18.66,0.00\n2009-12-05 00:30:00,80.27,45.21,20.56,17.55,0.00\n2009-12-05 00:40:00,80.96,52.03,21.66,21.14,0.00\n2009-12-05 00:50:00,85.21,57.68,26.57,23.58,0.00\n2009-12-05 01:00:00,88.66,52.56,21.75,20.94,0.00\n2009-12-05 01:10:00,78.66,53.26,22.89,22.36,0.00\n2009-12-05 01:20:00,83.80,50.74,20.65,19.49,0.00\n2009-12-05 01:30:00,82.70,39.99,17.23,15.86,0.00\n2009-12-05 01:40:00,79.66,52.35,23.55,20.94,0.00\n2009-12-05 01:50:00,74.56,47.64,23.05,18.97,0.00\n2009-12-05 02:00:00,81.25,51.35,17.94,21.61,0.00\n2009-12-05 02:10:00,86.83,58.48,25.03,24.95,0.00\n2009-12-05 02:20:00,73.31,41.39,21.09,17.56,0.00\n2009-12-05 02:30:00,82.31,39.70,19.84,16.74,0.00\n2009-12-05 02:40:00,71.19,55.84,19.05,25.97,0.00\n2009-12-05 02:50:00,88.00,44.13,25.32,17.74,0.00\n2009-12-05 03:00:00,84.54,44.78,18.42,18.78,0.00\n2009-12-05 03:10:00,76.92,50.57,17.91,21.81,0.00\n2009-12-05 03:20:00,84.45,49.12,21.54,18.58,0.00\n2009-12-05 03:30:00,89.52,54.20,45.41,22.39,0.00\n2009-12-05 03:40:00,74.15,44.67,26.54,18.04,0.00\n2009-12-05 03:50:00,76.32,44.36,15.02,18.39,0.00\n2009-12-05 04:00:00,83.90,52.30,19.50,22.39,0.00\n2009-12-05 04:10:00,80.26,61.12,20.66,25.68,0.00\n2009-12-05 04:20:00,80.95,42.11,16.95,14.84,0.00\n2009-12-05 04:30:00,93.01,53.36,17.91,21.94,0.00\n2009-12-05 04:40:00,72.70,45.22,16.63,18.16,0.00\n2009-12-05 04:50:00,81.22,44.73,15.41,18.54,0.00\n2009-12-05 05:00:00,80.02,46.60,16.07,21.04,0.00\n2009-12-05 05:10:00,84.33,57.86,21.77,24.71,0.00\n2009-12-05 05:20:00,87.48,60.54,19.66,22.95,0.00\n2009-12-05 05:30:00,86.19,48.62,18.47,17.51,0.00\n2009-12-05 05:40:00,72.92,50.10,18.46,21.00,0.00\n2009-12-05 05:50:00,81.79,48.14,15.75,19.36,0.00\n2009-12-05 06:00:00,83.11,55.70,17.15,23.02,0.00\n2009-12-05 06:10:00,77.98,55.89,18.36,23.16,0.00\n2009-12-05 06:20:00,77.05,45.96,18.06,17.27,0.00\n2009-12-05 06:30:00,87.58,53.76,15.56,20.79,0.00\n2009-12-05 06:40:00,79.33,58.86,21.02,22.84,0.00\n2009-12-05 06:50:00,85.33,49.99,16.23,18.82,0.00\n2009-12-05 07:00:00,87.54,56.70,17.96,23.31,0.00\n2009-12-05 07:10:00,73.33,56.51,17.57,21.74,0.00\n2009-12-05 07:20:00,84.02,52.77,21.21,20.80,0.00\n2009-12-05 07:30:00,88.86,58.34,27.45,23.10,0.00\n2009-12-05 07:40:00,,,,,\n2009-12-05 07:50:00,,,,,\n2009-12-05 08:00:00,,,,,\n2009-12-05 08:10:00,,,,,\n2009-12-05 08:20:00,,,,,\n2009-12-05 08:30:00,,,,,\n2009-12-05 08:40:00,,,,,\n2009-12-05 08:50:00,,,,,\n2009-12-05 09:00:00,,,,,\n2009-12-05 09:10:00,,,,,\n2009-12-05 09:20:00,,,,,\n2009-12-05 09:30:00,,,,,\n2009-12-05 09:40:00,,,,,\n2009-12-05 09:50:00,,,,,\n2009-12-05 10:00:00,,,,,\n2009-12-05 10:10:00,,,,,\n2009-12-05 10:20:00,77.11,55.87,29.31,18.09,0.00\n2009-12-05 10:30:00,85.31,53.70,31.03,18.46,0.00\n2009-12-05 10:40:00,82.72,61.32,26.30,22.98,0.00\n2009-12-05 10:50:00,87.28,46.53,25.04,18.04,0.00\n2009-12-05 11:00:00,92.07,62.62,27.09,25.14,0.00\n2009-12-05 11:10:00,84.59,50.42,25.97,19.96,0.00\n2009-12-05 11:20:00,80.34,49.64,25.75,20.31,0.00\n2009-12-05 11:30:00,103.61,64.36,31.79,24.71,0.00\n2009-12-05 11:40:00,80.03,54.33,33.83,22.37,0.00\n2009-12-05 11:50:00,90.42,48.24,31.26,18.43,0.00\n2009-12-05 12:00:00,92.51,56.34,33.53,22.45,0.00\n2009-12-05 12:10:00,84.34,62.26,22.66,23.31,0.00\n2009-12-05 12:20:00,86.24,51.96,25.69,16.02,0.00\n2009-12-05 12:30:00,92.08,58.61,23.16,19.34,0.00\n2009-12-05 12:40:00,78.48,53.33,22.72,19.51,0.00\n2009-12-05 12:50:00,97.85,57.71,29.80,20.19,0.00\n2009-12-05 13:00:00,89.23,54.84,34.62,20.93,0.00\n2009-12-05 13:10:00,78.92,50.02,22.47,18.19,0.00\n2009-12-05 13:20:00,87.24,50.92,26.37,19.38,0.00\n2009-12-05 13:30:00,96.29,54.78,27.13,20.04,0.00\n2009-12-05 13:40:00,96.39,69.48,30.21,27.19,0.00\n2009-12-05 13:50:00,83.21,55.13,26.48,18.00,0.00\n2009-12-05 14:00:00,97.00,63.61,30.89,22.23,0.00\n2009-12-05 14:10:00,94.65,65.33,33.51,25.04,0.00\n2009-12-05 14:20:00,86.94,58.14,36.10,22.07,0.00\n2009-12-05 14:30:00,91.91,47.25,28.41,14.45,0.00\n2009-12-05 14:40:00,92.55,55.73,29.59,20.69,0.00\n2009-12-05 14:50:00,97.35,58.90,31.63,22.22,0.00\n2009-12-05 15:00:00,97.91,56.09,23.80,21.68,0.00\n",                         { chartDiv:  document.getElementById("chartDiv_DML"), labelsDiv: document.getElementById("labelsDiv_DML") } ); g_DML.resize(400,160);
// ]]&gt;</script></p>
<p>The above chart is generated with <em>dygraphs</em>. Since it is embedded within my Wordpress page, the layout is affected by that of my theme. Take a look at this <a href="http://code.openark.org/blog/wp-content/uploads/2010/03/mycheckpoint_interactive_demo.html"><strong>example page</strong></a> to see similar charts outside this blog site (Internet Explorer users: Maxmimize/minimize button will not work well for now. And, may I suggest <a href="http://www.mozilla.com/en-US/firefox">Mozilla Firefox</a>?)<a href="http://code.openark.org/blog/wp-content/uploads/2010/03/mycheckpoint_interactive_demo.html"></a></p>
<h4>Pros and cons of interactive charts</h4>
<p>Pros</p>
<ul>
<li>Can present you with exact values. No more doubt about the <strong>com_replace_psec</strong> values.</li>
<li>Can allow for zoom in, zoom out.</li>
</ul>
<p>Cons</p>
<ul>
<li>Need supporting platform. The above cannot be viewed by non-JavaScript browsers (cell phones, etc.)</li>
<li>Browser support is also an issue with JavaScript.</li>
<li>Emailing such report will result in mail blocking in many companies: mail filters will not allow for JavaScript code to pass.</li>
<li>Charts are not necessarily self-contained, in terms of the chart entity With Flash charts (e.g. Fusion Charts) this works. But in the above, the legend and scales are outside the image. As such, they cannot be just moved around.</li>
<li>HTML pages which include such charts <em>can be</em> self contained. The HTML page can include all the JavaScript dependencies, in addition to the chart generating code. Flash based charts cannot be self contained.</li>
</ul>
<h4>Summary</h4>
<p>Interactive charts are cool!</p>
<p>I&#8217;m now integrating <a href="http://www.danvk.org/dygraphs/">dygraphs</a> into <em>mycheckpoint</em> (How nice it is to work with BSD &amp; MIT licenses!). Though I may later switch to <a href="http://code.google.com/p/flot/">flot</a>, interactive charts will be the next standard charting way in <em>mycheckpoint</em>. I will continue supporting static Google Charts, as follows from the above pros and cons list.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/static-charts-vs-interactive-charts/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>mycheckpoint (rev. 88): mount points monitoring, improved charting, enhanced auto-deploy</title>
		<link>http://code.openark.org/blog/mysql/mycheckpoint-rev-88-mount-points-monitoring-improved-charting-enhanced-auto-deploy</link>
		<comments>http://code.openark.org/blog/mysql/mycheckpoint-rev-88-mount-points-monitoring-improved-charting-enhanced-auto-deploy#comments</comments>
		<pubDate>Wed, 10 Feb 2010 06:29:47 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[mycheckpoint]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1904</guid>
		<description><![CDATA[Revision #88 of mycheckpoint is released. In this revision:

Disk space monitoring
Improved charting
Enhanced auto-deploy
And more&#8230;

Disk space monitoring
mycheckpoint now monitors (on Linux only) three mount points:

The &#8220;/&#8221; (root) mount point
The datadir mount point
The tmpdir mount point

It may well be the case that two of the above (or perhaps all three of them) share the same mount point. [...]]]></description>
			<content:encoded><![CDATA[<p>Revision <a href="http://code.google.com/p/mycheckpoint/">#88</a> of <a href="http://code.openark.org/forge/mycheckpoint">mycheckpoint</a> is released. In this revision:</p>
<ul>
<li>Disk space monitoring</li>
<li>Improved charting</li>
<li>Enhanced auto-deploy</li>
<li>And more&#8230;</li>
</ul>
<h4>Disk space monitoring</h4>
<p><em>mycheckpoint</em> now monitors (on Linux only) three mount points:</p>
<ol>
<li>The &#8220;<strong>/</strong>&#8221; (root) mount point</li>
<li>The <strong>datadir</strong> mount point</li>
<li>The <strong>tmpdir</strong> mount point</li>
</ol>
<p>It may well be the case that two of the above (or perhaps all three of them) share the same mount point. For example, if there isn&#8217;t any particular partition for &#8220;<strong>/tmp</strong>&#8220;, it is possible that the <strong>tmpdir</strong> (by default &#8220;<strong>/tmp</strong>&#8220;) is on the same mount point as &#8220;<strong>/</strong>&#8220;. <em>mycheckpoint</em> does not care.</p>
<p><em>mycheckpoint</em> monitors and reports the mount point&#8217;s used percent, in a similar algorithm <strong>df</strong> uses.</p>
<p>Disk space monitoring is only possible when monitoring the local machine (i.e. <em>mycheckpoint</em> runs on the same machine as the monitored MySQL server). In the future <em>mycheckpoint</em> may also monitor additional mount points, such as the various logs mount points.</p>
<h4><span id="more-1904"></span>Improved charting</h4>
<p>There has been some extensive work to turn the charts into real time-series based. <a href="http://code.google.com/apis/chart/">Google charts</a> does not support time series charts; when it will, the required URL length would probably be too long to be used. Some SQL tweaks made it possible to display the charts in correct time-scale even if sampling is taken on non constant interval (or fail to be taken for long periods).</p>
<blockquote><p><a href="http://code.openark.org/blog/wp-content/uploads/2010/02/mycheckpoint-dml-chart-sample-88.png"><img class="alignnone size-full wp-image-1951" title="mycheckpoint-dml-chart-sample-88" src="http://code.openark.org/blog/wp-content/uploads/2010/02/mycheckpoint-dml-chart-sample-88.png" alt="" width="400" height="200" /></a></p></blockquote>
<p>For more examples see the link for <em>HTML brief reports</em> sample, below.</p>
<p>I will write more on SQL Google charts generation in the future.</p>
<h4>Enhanced auto-deploy</h4>
<p><em>mycheckpoint</em> will now detect changes to the MySQL version, in addition to changes in <em>mycheckpoint</em>&#8217;s version itself. This means there&#8217;s no need in ever worrying about upgrades to either one of these components. Just use mycheckpoint to take another sample; it will auto-detect if the MySQL version is different, and start sampling all those new variables introduced in the new version (or stop sampling variables no longer used). It works both for MySQL upgrades and downgrades.</p>
<h4>Enhanced localhost detection</h4>
<p>To determine whether it is monitoring the local host, <em>mycheckpoint</em> now considers the hostname for the monitored server, and sees if it is either <strong>&#8216;127.0.0.1&#8242;</strong>, <strong>&#8216;localhost&#8217;</strong>, or the machine&#8217;s <em>hostname</em> or fully qualified <em>hostname.domainname</em> (these last two additions apply for Unix based machines, and have only been tested on Linux so far).</p>
<h4>HTML brief reports</h4>
<p>Getting a full HTML report is time consuming. I&#8217;ve had requests (though not officially submitted through the Issues mechanism) to make it shorter. This is as yet a difficult job. There&#8217;s just too much data to aggregate (up to ~180 days of every-5-minute-samples, in a common scenario).</p>
<p>HTML <em>brief reports</em> were introduced in previous versions, and have now been enhanced to include more data. These only present last 24 hours data, and load fast. See <a href="http://code.openark.org/blog/wp-content/uploads/2010/02/mycheckpoint-brief-report-sample-88.html">HTML brief report sample</a>.</p>
<h4>Get it</h4>
<p>Downloads are available on Google code&#8217;s <a href="http://code.google.com/p/mycheckpoint/">mycheckpoint</a> page. Documentation can be found on the <a href="http://code.openark.org/forge/mycheckpoint/">mycheckpoint home page</a>.</p>
<h4>On the press</h4>
<p>Not so new by now (it&#8217;s two months old), I&#8217;m very happy that mycheckpoint has been noted by <a href="http://jeremy.zawodny.com/blog/"><strong>Jeremy Zawodny</strong></a> in his &#8220;<a href="http://www.linux-mag.com/cache/7639/1.html">My Top Resources of 2009</a>&#8221; column on Linux Magazine.</p>
<h4>Future plans</h4>
<p>Immediate plans for mycheckpoint are:</p>
<ul>
<li>Email alerts notifications; this will allow <em>mycheckpoint</em> to become a real monitoring solution. Following the concept of &#8220;<em>SQL oritented monitoring</em>&#8220;, these will be SQL based as well.</li>
<li>Custom monitoring: allowing user defined queries to be recorded by <em>mycheckpoint</em>; these can then participate in alerts monitoring. This will allow for easy email notifications on program-level errors.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/mycheckpoint-rev-88-mount-points-monitoring-improved-charting-enhanced-auto-deploy/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>mycheckpoint rev. 76: OS monitoring, auto deploy, brief HTML and 24/7 reports</title>
		<link>http://code.openark.org/blog/mysql/mycheckpoint-rev-76-os-monitoring-auto-deploy-brief-html-and-247-reports</link>
		<comments>http://code.openark.org/blog/mysql/mycheckpoint-rev-76-os-monitoring-auto-deploy-brief-html-and-247-reports#comments</comments>
		<pubDate>Tue, 05 Jan 2010 08:55:14 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[mycheckpoint]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[scripts]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1784</guid>
		<description><![CDATA[Revision 76 of mycheckpoint comes with quite a few improvements, including:

OS monitoring (CPU, load average, memory)
Auto-deploy
Improved charting
Brief HTML reports
24/7 charts

OS Monitoring
When monitoring the local machine, mycheckpoint now monitors CPU utilization, load average, memory and swap space.
This only applies to the Linux operating system; there is currently no plan to work this out for other operating [...]]]></description>
			<content:encoded><![CDATA[<p>Revision <strong>76</strong> of <a href="http://code.openark.org/forge/mycheckpoint">mycheckpoint</a> comes with quite a few improvements, including:</p>
<ul>
<li>OS monitoring (CPU, load average, memory)</li>
<li>Auto-deploy</li>
<li>Improved charting</li>
<li>Brief HTML reports</li>
<li>24/7 charts</li>
</ul>
<h4>OS Monitoring</h4>
<p>When monitoring the local machine, <em>mycheckpoint</em> now monitors CPU utilization, load average, memory and swap space.</p>
<p>This only applies to the Linux operating system; there is currently no plan to work this out for other operating systems.</p>
<p>Examples:</p>
<blockquote>
<pre>mysql&gt; SELECT <strong>os_cpu_utilization_percent</strong> FROM sv_report_chart_sample;

<img class="size-full wp-image-1794 alignnone" title="mycheckpoint-chart-cpu-sample" src="http://code.openark.org/blog/wp-content/uploads/2009/12/mycheckpoint-chart-cpu-sample.png" alt="mycheckpoint-chart-cpu-sample" width="400" height="200" /></pre>
</blockquote>
<blockquote>
<pre>mysql&gt; SELECT ts, <strong>os_loadavg</strong> FROM mycheckpoint.sv_report_sample;
+---------------------+------------+
| 2009-12-27 11:45:01 |       1.78 |
| 2009-12-27 11:50:01 |       2.48 |
| 2009-12-27 11:55:01 |       2.35 |
...
+---------------------+------------+</pre>
</blockquote>
<blockquote>
<pre>mysql&gt; SELECT report FROM mycheckpoint.sv_report_human_sample ORDER BY id DESC LIMIT 1 \G
*************************** 1. row ***************************
report:
Report period: 2009-12-27 13:20:01 to 2009-12-27 13:25:01. Period is 5 minutes (0.08 hours)
Uptime: 100.0% (Up: 334 days, 06:37:28 hours)

<strong>OS:
 Load average: 1.67
 CPU utilization: 25.2%
 Memory: 7486.4MB used out of 7985.6484MB (Active: 6685.8906MB)
 Swap: 3835.2MB used out of 8189.3750MB</strong>
...</pre>
</blockquote>
<h4>Auto-deploy</h4>
<p><em>mycheckpoint</em> now has a version recognition mechanism. There is no need to call <em>mycheckpoint</em> with the &#8220;<strong>deploy</strong>&#8221; argument on first install or after upgrade. <em>mycheckpoint</em> will recognize a change of version and will auto-deploy before moving on to monitoring your system.</p>
<p><span id="more-1784"></span>It is still possible, though, to use &#8220;<strong>deploy</strong>&#8220;, in case you just want to make sure an upgrade takes place, without issuing a monitoring action.</p>
<h4>Improved charting</h4>
<p>Further improvements and bug fixes made to the Google charts, including the implementation of missing values charting.</p>
<h4>Brief HTML report</h4>
<p>In contrast with the full blown HTML report (see <a href="http://code.openark.org/forge/wp-content/uploads/2009/12/mycheckpoint_report-72.html">sample</a>), which presents hourly/daily/weekly reports for the many metrics, the new brief report only presents with a few hourly based charts. These include InnoDB performance, DML, OS metrics, and replication status.</p>
<p>To get a brief HTML report, issue:</p>
<blockquote>
<pre>mysql&gt; SELECT html FROM sv_report_html_brief;

<a href="http://code.openark.org/forge/wp-content/uploads/2009/12/mycheckpoint-brief-report.html"><img class="alignnone size-full wp-image-1839" title="mycehckpoint-report-html-brief-screenshot-small" src="http://code.openark.org/blog/wp-content/uploads/2010/01/mycehckpoint-report-html-brief-screenshot-small2.png" alt="" width="611" height="409" /></a>
</pre>
</blockquote>
<p>See <a href="http://code.openark.org/forge/wp-content/uploads/2009/12/mycheckpoint-brief-report.html">sample brief HTML report</a>.</p>
<h4>24/7 charts</h4>
<p>24/7 charts present the various metrics on a 24&#215;7 matrix, which allows for diagnostics of usage throughout the day and week. For example, it makes it easier to see how things slow down on Saturday/Sunday; how load increases on 10:00am every day, etc.</p>
<p>24/7 charts are provided by the <strong>sv_report_chart_24_7</strong> view.</p>
<blockquote>
<pre>DESC sv_report_chart_24_7;
+---------------------------------------+----------+------+-----+---------+-------+
| Field                                 | Type     | Null | Key | Default | Extra |
+---------------------------------------+----------+------+-----+---------+-------+
| innodb_read_hit_percent               | longblob | YES  |     | NULL    |       |
| innodb_buffer_pool_reads_psec         | longblob | YES  |     | NULL    |       |
| innodb_buffer_pool_pages_flushed_psec | longblob | YES  |     | NULL    |       |
| innodb_os_log_written_psec            | longblob | YES  |     | NULL    |       |
| innodb_row_lock_waits_psec            | longblob | YES  |     | NULL    |       |
| mega_bytes_sent_psec                  | longblob | YES  |     | NULL    |       |
| mega_bytes_received_psec              | longblob | YES  |     | NULL    |       |
| key_read_hit_percent                  | longblob | YES  |     | NULL    |       |
| key_write_hit_percent                 | longblob | YES  |     | NULL    |       |
| com_select_psec                       | longblob | YES  |     | NULL    |       |
| com_insert_psec                       | longblob | YES  |     | NULL    |       |
| com_delete_psec                       | longblob | YES  |     | NULL    |       |
| com_update_psec                       | longblob | YES  |     | NULL    |       |
| com_replace_psec                      | longblob | YES  |     | NULL    |       |
| com_set_option_percent                | longblob | YES  |     | NULL    |       |
| com_commit_percent                    | longblob | YES  |     | NULL    |       |
| slow_queries_percent                  | longblob | YES  |     | NULL    |       |
| select_scan_psec                      | longblob | YES  |     | NULL    |       |
| select_full_join_psec                 | longblob | YES  |     | NULL    |       |
| select_range_psec                     | longblob | YES  |     | NULL    |       |
| table_locks_waited_psec               | longblob | YES  |     | NULL    |       |
| opened_tables_psec                    | longblob | YES  |     | NULL    |       |
| created_tmp_tables_psec               | longblob | YES  |     | NULL    |       |
| created_tmp_disk_tables_psec          | longblob | YES  |     | NULL    |       |
| connections_psec                      | longblob | YES  |     | NULL    |       |
| aborted_connects_psec                 | longblob | YES  |     | NULL    |       |
| threads_created_psec                  | longblob | YES  |     | NULL    |       |
| seconds_behind_master                 | longblob | YES  |     | NULL    |       |
| os_loadavg                            | longblob | YES  |     | NULL    |       |
| os_cpu_utilization_percent            | longblob | YES  |     | NULL    |       |
| os_mem_used_mb                        | longblob | YES  |     | NULL    |       |
| os_mem_active_mb                      | longblob | YES  |     | NULL    |       |
| os_swap_used_mb                       | longblob | YES  |     | NULL    |       |
+---------------------------------------+----------+------+-----+---------+-------</pre>
</blockquote>
<p>Example:</p>
<blockquote>
<pre>mysql&gt; SELECT com_select_psec, innodb_buffer_pool_pages_flushed_psec FROM mycheckpoint.<strong>sv_report_chart_24_7</strong> \G
<img class="alignnone size-full wp-image-1798" title="mycheckpoint-chart-247-sample" src="http://code.openark.org/blog/wp-content/uploads/2009/12/mycheckpoint-chart-247-sample1.png" alt="mycheckpoint-chart-247-sample" width="400" height="200" />
<img class="alignnone size-full wp-image-1830" title="mycheckpoint-24-7-chart-sample2" src="http://code.openark.org/blog/wp-content/uploads/2010/01/mycheckpoint-24-7-chart-sample2.png" alt="" width="400" height="200" />
</pre>
</blockquote>
<h4>Trying mycheckpoint</h4>
<ul>
<li>Get <em>mycheckpoint</em> from the <a href="https://code.google.com/p/mycheckpoint/">mycheckpoint Google Code project page</a>.</li>
<li>Read the <a href="http://code.openark.org/forge/mycheckpoint/documentation">documentation</a>.</li>
<li>Report <a href="https://code.google.com/p/mycheckpoint/issues/list">issues</a>!</li>
</ul>
<h4>Future plans</h4>
<p>I haven&#8217;t got any major immediate issues; planning on user customization of charts and HTML reports. Considering thresholds and alerting for the future.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/mycheckpoint-rev-76-os-monitoring-auto-deploy-brief-html-and-247-reports/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>New and noteworthy in mycheckpoint (rev. 57)</title>
		<link>http://code.openark.org/blog/mysql/new-and-noteworthy-in-mycheckpoint-rev-57</link>
		<comments>http://code.openark.org/blog/mysql/new-and-noteworthy-in-mycheckpoint-rev-57#comments</comments>
		<pubDate>Wed, 16 Dec 2009 19:12:07 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[mycheckpoint]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1695</guid>
		<description><![CDATA[Rev. 57 of mycheckpoint has been released and is available for download.
New and updated in this revision:

Remote host monitoring
Improved charting
Flexible charting
Fix to questions vs. queries issues


Remote host monitoring
It is now possible to monitor one host, while writing into another. Either or both could be remote hosts:

mycheckpoint --host=localhost --monitored-host=192.168.10.178

The above monitors the MySQL server on 192.168.10.178, [...]]]></description>
			<content:encoded><![CDATA[<p>Rev. 57 of <a href="http://code.openark.org/forge/mycheckpoint">mycheckpoint</a> has been released and is available for <a href="http://code.google.com/p/mycheckpoint/">download</a>.</p>
<p>New and updated in this revision:</p>
<ul>
<li><a href="#Remote host monitoring">Remote host monitoring</a></li>
<li><a href="#Improved charting">Improved charting</a></li>
<li><a href="#Flexible charting">Flexible charting</a></li>
<li><a href="#Fix to questions vs. queries issues">Fix to questions vs. queries issues</a></li>
</ul>
<p><a name="Remote host monitoring"></a></p>
<h4>Remote host monitoring</h4>
<p>It is now possible to monitor one host, while writing into another. Either or both could be remote hosts:</p>
<blockquote>
<pre>mycheckpoint --host=localhost --monitored-host=192.168.10.178</pre>
</blockquote>
<p>The above monitors the MySQL server on 192.168.10.178, and writes down to localhost (to be queried later)</p>
<blockquote>
<pre>mycheckpoint --monitored-host=127.0.0.1 --host=192.168.10.178</pre>
</blockquote>
<p>The above monitors the MySQL server on 127.0.0.1, and writes down to 192.168.10.178.</p>
<p><span id="more-1695"></span>As result of the above addition, binary logging for monitoring statements are now enabled by default. The previous solution, in which binary logging were disabled by default and the same schema was utilized by different servers was far from being a clean solution. <em>[Read more on <a href="http://code.openark.org/forge/mycheckpoint/documentation/remote-multiple-hosts">Remote &amp; multiple hosts</a> monitoring]</em></p>
<p><a name="Improved charting"></a></p>
<h4>Improved charting</h4>
<p>The google charts reports have been improved significantly. Here&#8217;s how:</p>
<blockquote><p><img class="size-full wp-image-1702" title="Before" src="http://code.openark.org/blog/wp-content/uploads/2009/12/chart-old-sample11.png" alt="Before" width="400" height="200" /><img class="size-full wp-image-1703" title="After" src="http://code.openark.org/blog/wp-content/uploads/2009/12/chart-new-sample1.png" alt="After" width="400" height="200" /><br />
<strong>Before</strong> on left (or above), <strong>After</strong> on right (or below)</p></blockquote>
<p>And</p>
<blockquote><p><img class="size-full wp-image-1708" title="Before" src="http://code.openark.org/blog/wp-content/uploads/2009/12/chart-old-sample2.png" alt="Before" width="400" height="200" /><img class="size-full wp-image-1709" title="After" src="http://code.openark.org/blog/wp-content/uploads/2009/12/chart-new-sample2.png" alt="After" width="400" height="200" /><br />
<strong>Before</strong> on left (or above), <strong>After</strong> on right (or below)</p></blockquote>
<p><em>Recap</em>: the charts are not stored anywhere; they are being calculated and generated by SQL queries &amp; views, based on the raw data. For example, to generate the above, I do:</p>
<blockquote>
<pre>SELECT innodb_estimated_log_mb_written_per_hour FROM sv_report_chart_sample\G
*************************** 1. row ***************************
innodb_estimated_log_mb_written_per_hour: http://chart.apis.google.com/chart?cht=lc&amp;chs=400x200&amp;chts=303030,12&amp;chtt=Latest+24+hours:+Dec+8,+08:20++-++Dec+9,+08:20&amp;chdl=innodb_estimated_log_mb_written_per_hour&amp;chdlp=b&amp;chco=ff8c00&amp;chd=s:OQOTSPPQPSROUQMTRSQTPSURQTQQPSOSRTRRUTRRTSRRRSRSTRRQSURSUVRRXQTVRUTTVUTOYTXSYTTSYUSUTWUPRRPQSQRQTPPSPQMPPQOQMQPMOMOLQORNNPNRNNOPQOOMQQPNRNMNORQMSTQQPTPRUQVQTTVSURUVVVSSSVWQVUSTVSWSSUURWRVRTWdotpQNROPQRPQQMPLRO9PWNRPNNNPXUMLNNPQQPSONPLLNWXZTQROSQMOONQPLJNOOQQKMPSMMPLfPSSSRTUQSORSSRSPNRSQSQ&amp;chxt=x,y&amp;chxr=1,0,2708.0&amp;chxl=0:|09:00||||13:00||||17:00||||21:00||||01:00||||05:00||||&amp;chxs=0,505050,10&amp;chg=4.17,25,1,3,2.78,0&amp;chxp=0,2.78,6.95,11.12,15.29,19.46,23.63,27.80,31.97,36.14,40.31,44.48,48.65,52.82,56.99,61.16,65.33,69.50,73.67,77.84,82.01,86.18,90.35,94.52,98.69</pre>
</blockquote>
<p>If you thought I was going over the edge in <a href="http://code.openark.org/blog/mysql/auto-scaling-scaled-sql-graphs-concluded">Auto scaling, scaled SQL graphs concluded</a> or <a href="http://code.openark.org/blog/mysql/sql-pie-chart">SQL pie chart</a>, allow me to assure you: generation of the above charts using SQL &amp; views is <em>SQL blasphemy</em>. While cool, the Google Charts API does not do too much on its own, and expects the caller to do the math for putting in the grids, scales and labels (and though all of them are tightly related, they must all be explicitly specified). I&#8217;ll write a later post on this.</p>
<p><a name="Flexible charting"></a></p>
<h4>Flexible charting</h4>
<p>A new table, called <strong>charts_api</strong>, is introduced. It contains one row, which lists charting configuration.</p>
<blockquote>
<pre>SELECT * FROM charts_api;
+-------------+--------------+----------------------------------------------------------------+------------------------------------+
| chart_width | chart_height | simple_encoding                                                | service_url                        |
+-------------+--------------+----------------------------------------------------------------+------------------------------------+
|         400 |          200 | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 | http://chart.apis.google.com/chart |
+-------------+--------------+----------------------------------------------------------------+------------------------------------+</pre>
</blockquote>
<p>To change generated charts dimensions, simply update <strong>chart_width</strong> or <strong>chart_height</strong>.</p>
<p>To use other chart APIs, update <strong>service_url</strong>. I&#8217;ve tried out <a href="http://www.jfree.org/eastwood/">Eastwood Charts</a>, a Java-based service, which implements the Google Charts API. There&#8217;s still some stuff missing, such as the positioning of labels and grids; and it&#8217;s slightly less polished (but configurable). But if you&#8217;re concerned about sending requests to Google, it&#8217;s a viable alternative.</p>
<p>So, drop the WAR file into your <a href="http://tomcat.apache.org/">Tomcat</a> / <a href="http://www.jboss.org/">JBoss</a> / other j2ee server, then follow <strong>http://127.0.0.1/eastwood/chart?</strong>&#8230;</p>
<p><a name="Fix to questions vs. queries issues"></a></p>
<h4>Fix to questions vs. queries issues</h4>
<p><em>mycheckpoint</em> now correctly identifies whether to use <a href="http://code.openark.org/blog/mysql/questions-or-queries">questions or queries</a> when analyzing DML queries. If you got some <strong>570%</strong> for <strong>com_update</strong> out of total queries and wondered about it, it should now be resolved to normal values.</p>
<h4>On upgrading your current installation</h4>
<ol>
<li>Download &amp; install via debian or python installers</li>
<li>Issue <em>mycheckpoint</em> once with &#8220;deploy&#8221; as argument</li>
<li>Done</li>
</ol>
<h4>Future plans</h4>
<p>I&#8217;m working on 24&#215;7 charting; minimal operating system monitoring; auto-detection and auto-deploy on version upgrade; and more.</p>
<p>Stay tuned!</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/new-and-noteworthy-in-mycheckpoint-rev-57/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>SQL multi line chart</title>
		<link>http://code.openark.org/blog/mysql/sql-multi-line-chart</link>
		<comments>http://code.openark.org/blog/mysql/sql-multi-line-chart#comments</comments>
		<pubDate>Tue, 03 Nov 2009 10:44:13 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1400</guid>
		<description><![CDATA[Time for another charting SQL query. I wish to present a single-query generated multi-line/area chart. I&#8217;ll walk through some of the steps towards making this happen. By the end of this post I&#8217;ll present some real-data charts, area charts and colored charts.

+---------+-----------------------------------------------------------------------------------+
&#124; y_scale &#124; chart                                                                             &#124;
+---------+-----------------------------------------------------------------------------------+
&#124; 1       &#124; ****---------#######----------------------------------------*******--------###### &#124;
&#124; 0.88    &#124; ----***---###-------##-----------------------------------***-------***---##------ &#124;
&#124; 0.75    [...]]]></description>
			<content:encoded><![CDATA[<p>Time for another charting SQL query. I wish to present a single-query generated multi-line/area chart. I&#8217;ll walk through some of the steps towards making this happen. By the end of this post I&#8217;ll present some real-data charts, area charts and colored charts.</p>
<blockquote>
<pre>+---------+-----------------------------------------------------------------------------------+
| y_scale | chart                                                                             |
+---------+-----------------------------------------------------------------------------------+
| 1       | ****---------#######----------------------------------------*******--------###### |
| 0.88    | ----***---###-------##-----------------------------------***-------***---##------ |
| 0.75    | -------**#------------##-------------------------------**-------------*##-------- |
| 0.63    | ------##-*--------------##----------------------------*--------------##**-------- |
| 0.5     | -@@@@@@@@@@@@@@@----------#-----@@@@@@@@@@@@@@@@----**---------@@@@@@@@@@@@@@@@-- |
| 0.38    | ----#-------*--------------##----------------------*---------------#-------*----- |
| 0.25    | --##---------*---------------#--------------------*--------------##---------*---- |
| 0.13    | -#------------**--------------#-----------------**--------------#------------*--- |
| 0       | @---------------*--------------##--------------*---------------#--------------**- |
| -0.12   | -----------------*---------------#------------*--------------##-----------------* |
| -0.25   | ------------------*---------------#---------**--------------#-------------------- |
| -0.37   | -------------------**--------------#-------*---------------#--------------------- |
| -0.5    | ----------------@@@@@@@@@@@@@@@@----##----*-----@@@@@@@@@@@@@@@----------------@@ |
| -0.62   | ----------------------**--------------#-**--------------#------------------------ |
| -0.75   | ------------------------**------------**#-------------##------------------------- |
| -0.87   | --------------------------**-------***---###-------###--------------------------- |
| -1      | ----------------------------*******---------#######------------------------------ |
|         | v:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 0                                                                               8 |
|         |     # sin(x)                                                                      |
|         |     * cos(x)                                                                      |
|         |     @ sign(tan(x))/2                                                              |
+---------+-----------------------------------------------------------------------------------+</pre>
</blockquote>
<h4><span id="more-1400"></span>Requirements</h4>
<p>We need a generic query, which returns at least these two columns: <strong>ordering_column</strong> and <strong>row_values</strong>, The latter being a comma-delimited list of values. For example, the following query will do:</p>
<blockquote>
<pre>SELECT
 value/10 AS ordering_column,
 CONCAT(SIN(value/10), ',', COS(value/10), ',', SIGN(TAN(value/10))/2) AS row_values,
 @multi_line_chart_values_legend := 'sin(x),cos(x),sign(tan(x))/2' AS legend
FROM tinyint_asc
LIMIT 81;
+-----------------+---------------------------------------------+------------------------------+
| ordering_column | row_values                                  | legend                       |
+-----------------+---------------------------------------------+------------------------------+
|          0.0000 | 0,1,0.0000                                  | sin(x),cos(x),sign(tan(x))/2 |
|          0.1000 | 0.099833416646828,0.99500416527803,0.5000   | sin(x),cos(x),sign(tan(x))/2 |
|          0.2000 | 0.19866933079506,0.98006657784124,0.5000    | sin(x),cos(x),sign(tan(x))/2 |
|          0.3000 | 0.29552020666134,0.95533648912561,0.5000    | sin(x),cos(x),sign(tan(x))/2 |
|          0.4000 | 0.38941834230865,0.92106099400289,0.5000    | sin(x),cos(x),sign(tan(x))/2 |
|          0.5000 | 0.4794255386042,0.87758256189037,0.5000     | sin(x),cos(x),sign(tan(x))/2 |
|          0.6000 | 0.56464247339504,0.82533561490968,0.5000    | sin(x),cos(x),sign(tan(x))/2 |
...</pre>
</blockquote>
<p>Don&#8217;t worry about the <strong>@multi_line_chart_values_legend</strong> variable; it will be used later on for presenting chart legend.</p>
<p>Since I&#8217;ve already presented with how to create line charts in <a href="http://code.openark.org/blog/mysql/auto-scaling-scaled-sql-graphs-concluded">Auto scaling, scaled SQL graphs concluded</a>, we will concentrate on how to present a single chart column, which includes multiple values.</p>
<h4>Step 1</h4>
<p>Let&#8217;s say we wish to present the values 12,5,16. We wish to eventually generate something like:</p>
<blockquote>
<pre>+------------------+
| unwalked_bar     |
+------------------+
| ----Y------X---Z |
+------------------+</pre>
</blockquote>
<p>Where <strong>X</strong> is in position 12, <strong>Y</strong> in position 5, and <strong>Z</strong> in position 16.</p>
<p>We begin with having these values concatenated into one string:</p>
<blockquote>
<pre>SET @values := '12,5,16';
SET @num_values := CHAR_LENGTH(@values)-CHAR_LENGTH(REPLACE(@values,',',''))+1;
SELECT @num_values;
+-------------+
| @num_values |
+-------------+
| 3           |
+-------------+</pre>
</blockquote>
<p>The query above parses the string and correctly identified there are three tokens.</p>
<h4>Step 2</h4>
<p>We now move on to tokenizing the text:</p>
<blockquote>
<pre>SELECT
 *,
 SUBSTRING_INDEX(SUBSTRING_INDEX(@row_values, ',', tinyint_asc.value), ',', -1) AS row_value
FROM
 tinyint_asc,
 (SELECT @row_values := '12,5,16' AS row_values) AS sel_values,
 (SELECT @num_values := CHAR_LENGTH(@row_values)-CHAR_LENGTH(REPLACE(@row_values,',',''))+1 AS num_values) AS sel_num_values
WHERE
 tinyint_asc.value BETWEEN 1 AND @num_values
;
+-------+------------+------------+-----------+
| value | row_values | num_values | row_value |
+-------+------------+------------+-----------+
|     1 | 12,5,16    |          3 | 12        |
|     2 | 12,5,16    |          3 | 5         |
|     3 | 12,5,16    |          3 | 16        |
+-------+------------+------------+-----------+</pre>
</blockquote>
<p>Using a numbers table (<a href="http://code.openark.org/blog/wp-content/uploads/2009/08/tinyint_asc.sql">tinyint_asc</a>), we walk the string and tokenize it. We also maintain an indicator per value.</p>
<h4>Step 3</h4>
<p>We need to recognize the minimum and maximum values:</p>
<blockquote>
<pre>SELECT
  value AS row_value_indicator,
  row_value,
  @min_row_value := LEAST(IFNULL(@min_row_value, row_value), row_value) AS min_row_value,
  @max_row_value := GREATEST(IFNULL(@max_row_value, row_value), row_value) AS max_row_value
FROM (
  SELECT
  *,
  SUBSTRING_INDEX(SUBSTRING_INDEX(@row_values, ',', tinyint_asc.value), ',', -1)+0 AS row_value
    FROM
      tinyint_asc,
      (SELECT @row_values := '12,5,16' AS row_values) AS sel_values,
      (SELECT @num_values := CHAR_LENGTH(@row_values)-CHAR_LENGTH(REPLACE(@row_values,',',''))+1 AS num_values) AS sel_num_values,
      (SELECT @min_row_value := NULL) AS sel_min_row_value,
      (SELECT @max_row_value := NULL) AS sel_max_row_value
    WHERE
      tinyint_asc.value BETWEEN 1 AND @num_values
  ) sel_row_values
;
+---------------------+-----------+---------------+---------------+
| row_value_indicator | row_value | min_row_value | max_row_value |
+---------------------+-----------+---------------+---------------+
|                   1 |        12 |            12 |            12 |
|                   2 |         5 |             5 |            12 |
|                   3 |        16 |             5 |            16 |
+---------------------+-----------+---------------+---------------+</pre>
</blockquote>
<p>The last line presents the correct values: 5 and 16 being min, max values respectively.</p>
<h4>Step 4</h4>
<p>We now iterate from min value to max value, this being the graph range, and, per row value (token in <strong>row_values</strong> column), we indicate whether there&#8217;s a pixel on the graph.</p>
<blockquote>
<pre>SELECT
  * ,
  IF(tinyint_asc.value = row_value, row_value_indicator, '-') AS display_val
FROM
  tinyint_asc,
  (SELECT
    value AS row_value_indicator,
    row_value,
    @min_row_value := LEAST(IFNULL(@min_row_value, row_value), row_value) AS min_row_value,
    @max_row_value := GREATEST(IFNULL(@max_row_value, row_value), row_value) AS max_row_value
    FROM (
    SELECT
      *,
      SUBSTRING_INDEX(SUBSTRING_INDEX(@row_values, ',', tinyint_asc.value), ',', -1)+0 AS row_value
    FROM
      tinyint_asc,
      (SELECT @row_values := '12,5,16' AS row_values) AS sel_values,
      (SELECT @num_values := CHAR_LENGTH(@row_values)-CHAR_LENGTH(REPLACE(@row_values,',',''))+1 AS num_values) AS sel_num_values,
      (SELECT @min_row_value := NULL) AS sel_min_row_value,
      (SELECT @max_row_value := NULL) AS sel_max_row_value
    WHERE
      tinyint_asc.value BETWEEN 1 AND @num_values
    ) sel_row_values
  ) AS sel_row_values_indicators
WHERE
  tinyint_asc.value BETWEEN 1 AND @max_row_value
;
+-------+---------------------+-----------+---------------+---------------+-------------+
| value | row_value_indicator | row_value | min_row_value | max_row_value | display_val |
+-------+---------------------+-----------+---------------+---------------+-------------+
|     1 |                   1 |        12 |            12 |            12 | -           |
|     1 |                   2 |         5 |             5 |            12 | -           |
|     1 |                   3 |        16 |             5 |            16 | -           |
|     2 |                   1 |        12 |            12 |            12 | -           |
|     2 |                   2 |         5 |             5 |            12 | -           |
|     2 |                   3 |        16 |             5 |            16 | -           |
|     3 |                   1 |        12 |            12 |            12 | -           |
|     3 |                   2 |         5 |             5 |            12 | -           |
|     3 |                   3 |        16 |             5 |            16 | -           |
|     4 |                   1 |        12 |            12 |            12 | -           |
|     4 |                   2 |         5 |             5 |            12 | -           |
|     4 |                   3 |        16 |             5 |            16 | -           |
|     5 |                   1 |        12 |            12 |            12 | -           |
|     5 |                   2 |         5 |             5 |            12 | 2           |
|     5 |                   3 |        16 |             5 |            16 | -           |
|     6 |                   1 |        12 |            12 |            12 | -           |
|     6 |                   2 |         5 |             5 |            12 | -           |
|     6 |                   3 |        16 |             5 |            16 | -           |</pre>
</blockquote>
<p>This table can get very long.</p>
<h4>Step 5</h4>
<p>We now group the marks per graph-row value. If there&#8217;s no mark, we present with an empty space. If only one mark is present, we display that mark. If two lines collide on that point, we need to decide which mark to use. In the following example, we choose by order of appearance in the <strong>row_values</strong> column. Another way (used later on) is to choose the higher value.</p>
<blockquote>
<pre>SELECT
  string_position,
  LEFT(GROUP_CONCAT(bar_string_token ORDER BY bar_string_token DESC separator ''), 1) AS chosen_mark
FROM
  (SELECT
    value AS string_position,
    IF(tinyint_asc.value = row_value, row_value_indicator, ' ') AS bar_string_token
  FROM
    tinyint_asc,
    (SELECT
      value AS row_value_indicator,
      row_value,
      @min_row_value := LEAST(IFNULL(@min_row_value, row_value), row_value) AS min_row_value,
      @max_row_value := GREATEST(IFNULL(@max_row_value, row_value), row_value) AS max_row_value
      FROM (
      SELECT
        *,
        SUBSTRING_INDEX(SUBSTRING_INDEX(@row_values, ',', tinyint_asc.value), ',', -1)+0 AS row_value
      FROM
        tinyint_asc,
        (SELECT @row_values := '12,5,16' AS row_values) AS sel_values,
        (SELECT @num_values := CHAR_LENGTH(@row_values)-CHAR_LENGTH(REPLACE(@row_values,',',''))+1 AS num_values) AS sel_num_values,
        (SELECT @min_row_value := NULL) AS sel_min_row_value,
        (SELECT @max_row_value := NULL) AS sel_max_row_value
      WHERE
        tinyint_asc.value BETWEEN 1 AND @num_values
      ) sel_row_values
    ) AS sel_row_values_indicators
  WHERE
    tinyint_asc.value BETWEEN 1 AND @max_row_value
  ) AS sel_marked_row_values
GROUP BY
  string_position
;
+-----------------+-------------+
| string_position | chosen_mark |
+-----------------+-------------+
|               1 |             |
|               2 |             |
|               3 |             |
|               4 |             |
|               5 | 2           |
|               6 |             |
|               7 |             |
|               8 |             |
|               9 |             |
|              10 |             |
|              11 |             |
|              12 | 1           |
|              13 |             |
|              14 |             |
|              15 |             |
|              16 | 3           |
+-----------------+-------------+</pre>
</blockquote>
<h4>Step 6</h4>
<p>It&#8217;s now just a matter of using GROUP_CONCAT to turn this into the following horizontal bar:</p>
<blockquote>
<pre>+------------------+
| unwalked_bar     |
+------------------+
| ----2------1---3 |
+------------------+</pre>
</blockquote>
<p>(I know I&#8217;m mixing spaces and dashes, it&#8217;s just for display purposes).</p>
<h4>Putting it all together</h4>
<p>From this point on, we use the techniques shown on <a href="../mysql/auto-scaling-scaled-sql-graphs-concluded">Auto scaling, scaled SQL graphs concluded</a> to generate the complete chart. For the curious, here&#8217;s what the complete query looks like. The text in <strong>bold</strong> is the only thing that needs to change; replace this with your own query.</p>
<blockquote>
<pre>SELECT
  y_scale,
  horizontal_bar as chart
  FROM
  (
  SELECT
    @multi_line_chart_row_number := @multi_line_chart_row_number+1,
    CASE @multi_line_chart_row_number
      WHEN 1 THEN ROUND(@multi_line_chart_max_value, @multi_line_chart_value_precision)
      WHEN @multi_line_chart_graph_rows THEN ROUND(@multi_line_chart_min_value, @multi_line_chart_value_precision)
      ELSE ROUND(@multi_line_chart_max_value-(@multi_line_chart_max_value-@multi_line_chart_min_value)*(@multi_line_chart_row_number-1)/(@multi_line_chart_graph_rows-1), @multi_line_chart_value_precision)
    END AS y_scale,
    horizontal_bar,
    @multi_line_chart_bar_length := IFNULL(@multi_line_chart_bar_length, CHAR_LENGTH(horizontal_bar))
  FROM
    (SELECT @multi_line_chart_row_number := 0) AS select_row
    INNER JOIN
    (
    SELECT
      GROUP_CONCAT(SUBSTRING(unwalked_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
    FROM
      tinyint_asc
    INNER JOIN (
      SELECT
        ordering_column,
        GROUP_CONCAT(bar_string_token ORDER BY string_position SEPARATOR '') AS unwalked_bar
      FROM
        (SELECT
          ordering_column,
          string_position,
          scaled_string_position,
          REPLACE(LEFT(GROUP_CONCAT(bar_string_token ORDER BY bar_string_token DESC SEPARATOR ''), 1), ' ', '-') AS bar_string_token
        FROM
          (SELECT
            ordering_column,
            @multi_line_chart_scaled_string_position := CONVERT((row_value-@multi_line_chart_min_value)*(@multi_line_chart_graph_rows-1)/(@multi_line_chart_max_value-@multi_line_chart_min_value), UNSIGNED) AS scaled_string_position,
            value AS string_position,
            IF(tinyint_asc.value = @multi_line_chart_scaled_string_position+1, SUBSTRING(IF(@multi_line_chart_values_legend IS NULL, @multi_line_chart_graph_fallback_colors, @multi_line_chart_graph_colors), row_value_indicator, 1), ' ') AS bar_string_token
          FROM
            tinyint_asc,
            (SELECT
              ordering_column,
              value AS row_value_indicator,
              row_value
            FROM (
              SELECT
                *,
                @multi_line_chart_min_value := LEAST(IFNULL(@multi_line_chart_min_value, row_value), row_value) AS min_value,
                @multi_line_chart_max_value := GREATEST(IFNULL(@multi_line_chart_max_value, row_value), row_value) AS max_value,
                @multi_line_chart_min_range := LEAST(IFNULL(@multi_line_chart_min_range, ordering_column), ordering_column) AS min_range,
                @multi_line_chart_max_range := GREATEST(IFNULL(@multi_line_chart_max_range, ordering_column), ordering_column) AS max_range
              FROM
                (SELECT
                  *,
                  SUBSTRING_INDEX(SUBSTRING_INDEX(row_values, ',', tinyint_asc.value), ',', -1)+0 AS row_value,
                  @multi_line_chart_num_values := CHAR_LENGTH(row_values)-CHAR_LENGTH(REPLACE(row_values,',',''))+1 AS num_values
                FROM
                  (SELECT @multi_line_chart_values_legend := NULL) AS select_nullify_values_legend,
                  (SELECT @multi_line_chart_bar_length := NULL) AS select_nullify_bar_length,
                  tinyint_asc,
                  (
                    <strong>SELECT
                      value/10 AS ordering_column,
                      CONCAT(SIN(value/10), ',', COS(value/10), ',', SIGN(TAN(value/10))/2) AS row_values,
                      @multi_line_chart_values_legend := 'sin(x),cos(x),sign(tan(x))/2'
                    FROM tinyint_asc LIMIT 81</strong>
                  ) AS sel_main_values,
                  (SELECT @multi_line_chart_min_value := NULL) AS select_min,
                  (SELECT @multi_line_chart_max_value := NULL) AS select_max,
                  (SELECT @multi_line_chart_min_range := NULL) AS select_min_range,
                  (SELECT @multi_line_chart_max_range := NULL) AS select_max_range,
                  (SELECT @multi_line_chart_graph_colors := '#*@%o+x;m:') AS select_graph_colors,
                  (SELECT @multi_line_chart_graph_fallback_colors := 'abcdefghij') AS select_graph_fallback_colors,
                  (SELECT @multi_line_chart_value_precision := 2) AS select_value_precision,
                  (SELECT @multi_line_chart_graph_rows := 17) AS select_graph_rows
                ) sel_counted_values_main_values
              WHERE
                value BETWEEN 1 AND @multi_line_chart_num_values
              ) sel_row_values
            ) AS sel_row_values_indicators
          WHERE
            tinyint_asc.value BETWEEN 1 AND @multi_line_chart_graph_rows
          ) AS sel_marked_row_values
        GROUP BY
          ordering_column, string_position
        ) AS sel_walked_bar
      GROUP BY
        ordering_column
    ) AS select_vertical
    WHERE
      tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(unwalked_bar)
    GROUP BY
      tinyint_asc.value
    ORDER BY
      tinyint_asc.value DESC
    ) AS select_horizontal
  ) AS select_horizontal_untitled
UNION ALL
SELECT '', CONCAT('v', REPEAT(':', @multi_line_chart_bar_length-2), 'v')
UNION ALL
SELECT '', CONCAT(@multi_line_chart_min_range, REPEAT(' ', @multi_line_chart_bar_length-CHAR_LENGTH(@multi_line_chart_min_range)-CHAR_LENGTH(@multi_line_chart_max_range)), @multi_line_chart_max_range)
UNION ALL
SELECT
  '', CONCAT('    ', SUBSTRING(@multi_line_chart_graph_colors, value, 1), ' ', SUBSTRING_INDEX(SUBSTRING_INDEX(@multi_line_chart_values_legend, ',', value), ',', -1))
FROM
  tinyint_asc
WHERE
  value BETWEEN 1 AND @multi_line_chart_num_values
  AND @multi_line_chart_values_legend IS NOT NULL
;</pre>
</blockquote>
<h4>Area charts</h4>
<p>Sinus functions curves go nicely with ASCII art. But your everyday chart won&#8217;t have such nice curves. There is some erratic behavior to, say, your number of queries per hour. It doesn&#8217;t display well in the above graph.</p>
<p>Which is why we can choose to present area graphs: not only display the <em>line</em>, but also the <em>area</em> beneath it.</p>
<p>This leads to the problem of hiding: higher values can hide the lower values display. So we need to adjust the above logic and make sure we always bring to front the lower values.</p>
<p>The following example shows the relation between the percentage of slow queries (per hour) and the percentage of full table scans (of total queries, per hour). It&#8217;s really fun to see the unmistakeable connection!</p>
<blockquote>
<pre>+---------+---------------------------------------------------------------+
| y_scale | chart                                                         |
+---------+---------------------------------------------------------------+
| 4       | --------------------------------*--*------------------------- |
| 3       | -----------------------------****-*****------------------*--- |
| 3       | -----------------------------**********-----------------**-*- |
| 3       | ----------*------------------**********--------------*****-** |
| 3       | ------*--**-----------------************-*-----------******** |
| 3       | ------*******---------------****************--------********* |
| 3       | ------*******---------------******************------********* |
| 3       | ------*********-------------******************------********* |
| 3       | -----************--------*-*******************------********* |
| 2       | *----*****************--************************-**-********* |
| 2       | *----******************************************************** |
| 2       | *----******************************************************** |
| 2       | **--********************************************************* |
| 2       | ************************************************************* |
| 2       | ************************************************************* |
| 2       | ************************************************************* |
| 2       | *****************************#******************************* |
| 1       | *****************************##########*****************##*#* |
| 1       | ******#***#*****************###########**************######## |
| 1       | ******#######***************###############*********######### |
| 1       | #*****########**************##################******######### |
| 1       | #****##########*#*******##*###################******######### |
| 1       | #***######################################################### |
| 1       | #***######################################################### |
| 1       | ############################################################# |
|         | v:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 2009-10-22 20:00:00                       2009-10-25 08:00:00 |
|         |     # slow queries percent                                    |
|         |     * select scan percent                                     |
+---------+---------------------------------------------------------------</pre>
</blockquote>
<p>Next we look at the number of <strong>SELECT</strong>, <strong>INSERT</strong>, <strong>UPDATE</strong>, <strong>DELETE</strong> queries per sec &#8211; on a per hour basis.</p>
<blockquote>
<pre>+---------+---------------------------------------------------------------+
| y_scale | chart                                                         |
+---------+---------------------------------------------------------------+
| 135     | --##--------------------------------------------------------- |
| 130     | -###--------------------------------------------------------- |
| 125     | -###----------------------##--------------------------------- |
| 120     | -###-------------------#####--------------------------------- |
| 115     | -###-----------------#######--------------------------------- |
| 110     | -###----------------########----------------------##--------- |
| 104     | -###--------------##########--------------------####--------- |
| 99      | ####*--------#--############------------------######--------- |
| 94      | ####*--------#-#############---------------#########--------- |
| 89      | #####-----##################------------############--------# |
| 84      | ############################-----------#############--------# |
| 79      | ####%########################---####################---###### |
| 74      | ####%######################################################## |
| 69      | ####%*####################################################### |
| 64      | ####%*####################################################### |
| 59      | ##*#%*###############*####################################### |
| 54      | #**#%*#####*#*****###*****##*###########*#####*#####*#####*## |
| 49      | #***%%*#*********************##########****#*********#**##*** |
| 44      | #***%%*************************##**************************** |
| 39      | ****%%******************************************************* |
| 33      | ****%%**********************************************%******** |
| 28      | *%%*%%**********%******%%*%*%*******%***************%******** |
| 23      | %%%%o%**%**%%o%%%%%%%%%%%%%%o******%%*%%%%%%%%%%%%%%%%*****%% |
| 18      | %oooooooo%%oooooooooooooooooo%%oo%oooooooooooooooooooo%o%%ooo |
| 13      | ooooooooo%%oooooooooooooooooo%%%%%%ooooooooooooooooooo%o%%ooo |
|         | v:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 2009-10-22 20:00:00                       2009-10-25 08:00:00 |
|         |     # com_select per second                                   |
|         |     * com_insert per second                                   |
|         |     o com_update per second                                   |
|         |     % com_delete per second                                   |
+---------+---------------------------------------------------------------+</pre>
</blockquote>
<p><a href="http://code.openark.org/blog/wp-content/uploads/2009/10/sql_multi_line_graph2.txt">Here&#8217;s the code</a> to do that.</p>
<h4>Colored charts</h4>
<p>Time to add colors! The previous charts can be easily improved by sending the terminal escape characters to instruct initiating color display (thanks to <strong>TheVoo</strong> for providing the idea and implementation for that on <a href="../mysql/sql-pie-chart">SQL pie chart</a>).</p>
<p>I recommend using the following <strong>pager</strong> if you&#8217;re on unix-like OS:</p>
<blockquote>
<pre>pager awk '{sub(/..$/, "")} {sub(/[ ]+$/, "")} !/^[+][-]/ &amp;&amp; NR &gt; 3 { print }'</pre>
</blockquote>
<p>The following chart is similar to the above, but now utilizes colors. It presents the number of popular DML queries per second, on an hourly basis.</p>
<blockquote><p><a href="http://code.openark.org/blog/wp-content/uploads/2009/10/colored-multi-area-chart-1.png"><img class="size-full wp-image-1411 alignnone" title="Colored multi area chart" src="http://code.openark.org/blog/wp-content/uploads/2009/10/colored-multi-area-chart-1.png" alt="Colored multi area chart" width="525" height="507" /></a></p></blockquote>
<p>Again, comparing slow queries with full table scans:</p>
<blockquote>
<div id="attachment_1444" class="wp-caption alignnone" style="width: 558px"><a href="http://code.openark.org/blog/wp-content/uploads/2009/10/colored-slow-and-scan-queries.png"><img class="size-full wp-image-1444" title="colored-slow-and-scan-queries" src="http://code.openark.org/blog/wp-content/uploads/2009/10/colored-slow-and-scan-queries.png" alt="Slow queries and full scans overlay" width="548" height="319" /></a><p class="wp-caption-text">Slow queries and full scans overlay</p></div></blockquote>
<p>Comparing number of created temporary tables with number of created disk temporary tables:</p>
<blockquote>
<div id="attachment_1446" class="wp-caption alignnone" style="width: 559px"><a href="http://code.openark.org/blog/wp-content/uploads/2009/10/color_multi_line_chart_tmp_tables.png"><img class="size-full wp-image-1446" title="color_multi_line_chart_tmp_tables" src="http://code.openark.org/blog/wp-content/uploads/2009/10/color_multi_line_chart_tmp_tables.png" alt="temp tables vs disk temp tables" width="549" height="485" /></a><p class="wp-caption-text">temp tables vs disk temp tables</p></div></blockquote>
<p>And, once again, the number of popular DML statements, zoomed in.</p>
<blockquote><p><a href="http://code.openark.org/blog/wp-content/uploads/2009/10/color_multi_line_chart_dml.png"><img class="alignnone size-full wp-image-1451" title="color_multi_line_chart_dml" src="http://code.openark.org/blog/wp-content/uploads/2009/10/color_multi_line_chart_dml.png" alt="color_multi_line_chart_dml" width="552" height="517" /></a></p></blockquote>
<p><a href="http://code.openark.org/blog/wp-content/uploads/2009/10/sql_multi_line_graph3.txt">Here&#8217;s the code</a> for colored charts.</p>
<p>Some of the techinuqes used in my <a href="http://code.openark.org/blog/tag/graphs">charting series</a> of blogs can be used, pretty much in the same way, in order to generate <a href="http://code.google.com/apis/chart/">Google charts</a>. But other techniques can be used, as well.</p>
<p>More on this in future posts.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/sql-multi-line-chart/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>SQL pie chart</title>
		<link>http://code.openark.org/blog/mysql/sql-pie-chart</link>
		<comments>http://code.openark.org/blog/mysql/sql-pie-chart#comments</comments>
		<pubDate>Wed, 12 Aug 2009 10:49:41 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1093</guid>
		<description><![CDATA[My other half says I&#8217;m losing it. But I think that as an enthusiast kernel developer she doesn&#8217;t have the right to criticize people. (&#8220;I like user space better!&#8221; &#8211; she exclaims upon reading this).
Shown below is a (single query) SQL-generated pie chart. I will walk through the steps towards making this happen, and conclude [...]]]></description>
			<content:encoded><![CDATA[<p>My other half says I&#8217;m losing it. But I think that as an enthusiast kernel developer she doesn&#8217;t have the right to criticize people. (&#8220;I like user space better!&#8221; &#8211; she exclaims upon reading this).</p>
<p>Shown below is a (single query) SQL-generated pie chart. I will walk through the steps towards making this happen, and conclude with what, I hope you&#8217;ll agree, are real-world, useful usage samples.</p>
<blockquote>
<pre>+----------------------------------------------------------------------+
| pie_chart                                                            |
+----------------------------------------------------------------------+
|                                                                      |
|                         ;;;;;;;;;;;;;;;;;;;;;                        |
|                  oooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;                 |
|             oooooooooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;            |
|          ooooooooooooooooo                 ;;;;;;;;;;;;#####         |
|        oooooooooooooo                           ;#############       |
|       oooooooooooo                                 ############      |
|      oooooooooooo                                   ############     |
|      ooooooooooo                                     ###########     |
|      oooooooooooo                                   ::::::::::::     |
|       oooooooooooo                                 ::::::::::::      |
|        ooooooooo:::::                           ::::::::::::::       |
|          o::::::::::::::::                 :::::::::::::::::         |
|             :::::::::::::::::::::::::::::::::::::::::::::            |
|                  :::::::::::::::::::::::::::::::::::                 |
|                         :::::::::::::::::::::                        |
|                                                                      |
| ##  red: 1 (10%)                                                     |
| ;;  blue: 2 (20%)                                                    |
| oo  orange: 3 (30%)                                                  |
| ::  white: 4 (40%)                                                   |
+----------------------------------------------------------------------+</pre>
</blockquote>
<h4>Requirements</h4>
<p>We need a generic query, which returns at least these two columns: <strong>name_column</strong> and <strong>value_column</strong>. For example, the following query will do:<span id="more-1093"></span></p>
<blockquote>
<pre>SELECT name AS name_column, value AS value_column FROM sample_values2;
+-------------+--------------+
| name_column | value_column |
+-------------+--------------+
| red         |            1 |
| blue        |            2 |
| orange      |            3 |
| white       |            4 |
+-------------+--------------+</pre>
</blockquote>
<p>Find sample data in <a href="http://code.openark.org/blog/wp-content/uploads/2009/08/pie_data.sql">pie_data.sql</a>.</p>
<h4>Part 1: expanding the original query</h4>
<p>We&#8217;re going to need to take the above query&#8217;s results and expand them: how much is the ratio from total, per value? As first step, accumulate values:</p>
<blockquote>
<pre>SELECT
  name_column,
  value_column,
  @accumulating_value := @accumulating_value+value_column AS accumulating_value
FROM (
  SELECT name AS name_column, value AS value_column FROM sample_values2
  ) select_values,
  (SELECT @accumulating_value := 0) select_accumulating_value
;
+-------------+--------------+--------------------+
| name_column | value_column | accumulating_value |
+-------------+--------------+--------------------+
| red         |            1 |                  1 |
| blue        |            2 |                  3 |
| orange      |            3 |                  6 |
| white       |            4 |                 10 |
+-------------+--------------+--------------------+</pre>
</blockquote>
<p>Next, we calculate ratio of accumulating value, and present it both in [0..1] range, as well as in [0..2*PI] (radians):</p>
<blockquote>
<pre>SELECT
  name_order,
  name_column,
  value_column,
  accumulating_value,
  accumulating_value/@accumulating_value AS accumulating_value_ratio,
  2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
FROM (
  SELECT
    name_column,
    value_column,
    @name_order := @name_order+1 AS name_order,
    @accumulating_value := @accumulating_value+value_column AS accumulating_value,
    @aggregated_name_column := CONCAT(@aggregated_name_column, name_column, ',') AS aggregated_name_column
  FROM (
    SELECT name AS name_column, value AS value_column FROM sample_values2
    ) select_values,
    (SELECT @name_order := 0) select_name_order,
    (SELECT @accumulating_value := 0) select_accumulating_value,
    (SELECT @aggregated_name_column := '') select_aggregated_name_column
  ) select_accumulating_values
;
+------------+-------------+--------------+--------------------+--------------------------+----------------------------+
| name_order | name_column | value_column | accumulating_value | accumulating_value_ratio | accumulating_value_radians |
+------------+-------------+--------------+--------------------+--------------------------+----------------------------+
|          1 | red         |            1 |                  1 |                      0.1 |           0.62831853071796 |
|          2 | blue        |            2 |                  3 |                      0.3 |            1.8849555921539 |
|          3 | orange      |            3 |                  6 |                      0.6 |            3.7699111843078 |
|          4 | white       |            4 |                 10 |                        1 |            6.2831853071796 |
+------------+-------------+--------------+--------------------+--------------------------+----------------------------+</pre>
</blockquote>
<p>The radians value will help us decide where in the pie chart lies each value.</p>
<h4>Part 2: behind the scenes of the pie chart</h4>
<p>We now explain how the pie chart works. Later on we combine with <strong>Part 1</strong>, to produce the complete chart.</p>
<p>We first generate a coordinates system (see <a href="http://code.openark.org/blog/mysql/sql-graphics">SQL graphics</a>):</p>
<blockquote>
<pre>SELECT
  GROUP_CONCAT(CONCAT(t2.value,'.',t1.value) order by t1.value separator ' ') as circle
FROM
  tinyint_asc t1,
  tinyint_asc t2,
  (select @size := 10) sel_size,
  (select @radius := (@size/2 - 1)) sel_radius
WHERE
  t1.value &lt; @size
  AND t2.value &lt; @size
GROUP BY t2.value
;
+-----------------------------------------+
| circle                                  |
+-----------------------------------------+
| 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 |
| 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 |
| 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 |
| 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 |
| 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 |
| 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 |
| 6.0 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 |
| 7.0 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 |
| 8.0 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 |
| 9.0 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 9.9 |
+-----------------------------------------+</pre>
</blockquote>
<p>Taking a slightly big step further, we calculate the angle per coordinate, in relation to center of coordinate system. Calculation is in radians, but presented in degrees, since it&#8217;s more readable. Also, we note in which quarter of the graph each point lies.</p>
<blockquote>
<pre>SELECT
  group_concat(
    round(radians*180/PI())
    order by col_number separator ' ') as circle
FROM (
  SELECT
    t1.value AS col_number,
    t2.value AS row_number,
    @dx := (t1.value - (@size-1)/2) AS dx,
    @dy := ((@size-1)/2 - t2.value) AS dy,
    @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
    CASE
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &gt;= 0 THEN @abs_radians
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()-@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()+@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &gt;= 0 THEN 2*PI()-@abs_radians
    END AS radians
  FROM
    tinyint_asc t1,
    tinyint_asc t2,
    (select @size := 15) sel_size,
    (select @radius := (@size/2 - 1)) sel_radius
  WHERE
    t1.value &lt; @size
    AND t2.value &lt; @size) select_combinations
  GROUP BY row_number
;
+-------------------------------------------------------------+
| circle                                                      |
+-------------------------------------------------------------+
| 135 131 126 120 113 106 98 90 82 74 67 60 54 49 45          |
| 139 135 130 124 117 108 99 90 81 72 63 56 50 45 41          |
| 144 140 135 129 121 112 101 90 79 68 59 51 45 40 36         |
| 150 146 141 135 127 117 104 90 76 63 53 45 39 34 30         |
| 157 153 149 143 135 124 108 90 72 56 45 37 31 27 23         |
| 164 162 158 153 146 135 117 90 63 45 34 27 22 18 16         |
| 172 171 169 166 162 153 135 90 45 27 18 14 11 9 8           |
| 180 180 180 180 180 180 180 90 0 0 0 0 0 0 0                |
| 188 189 191 194 198 207 225 270 315 333 342 346 349 351 352 |
| 196 198 202 207 214 225 243 270 297 315 326 333 338 342 344 |
| 203 207 211 217 225 236 252 270 288 304 315 323 329 333 337 |
| 210 214 219 225 233 243 256 270 284 297 307 315 321 326 330 |
| 216 220 225 231 239 248 259 270 281 292 301 309 315 320 324 |
| 221 225 230 236 243 252 261 270 279 288 297 304 310 315 319 |
| 225 229 234 240 247 254 262 270 278 286 293 300 306 311 315 |
+-------------------------------------------------------------+</pre>
</blockquote>
<p>The above needs some formattign to present well, but that&#8217;s not the purpose; I&#8217;m only showing the above to explain the steps taken.</p>
<h4>Part 3: combining the two</h4>
<p>Next step is probably the most significant one: we&#8217;re going to present a rough, square, weird looking pie chart using the original values:</p>
<blockquote>
<pre>SELECT
  group_concat(
    (SELECT name_order FROM
      (
      SELECT
        name_order,
        name_column,
        value_column,
        accumulating_value,
        accumulating_value/@accumulating_value AS accumulating_value_ratio,
        2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
      FROM (
        SELECT
          name_column,
          value_column,
          @name_order := @name_order+1 AS name_order,
          @accumulating_value := @accumulating_value+value_column AS accumulating_value,
          @aggregated_name_column := CONCAT(@aggregated_name_column, name_column, ',') AS aggregated_name_column
        FROM (
          SELECT name AS name_column, value AS value_column FROM sample_values2
          ) select_values,
          (SELECT @name_order := 0) select_name_order,
          (SELECT @accumulating_value := 0) select_accumulating_value,
          (SELECT @aggregated_name_column := '') select_aggregated_name_column
        ) select_accumulating_values
      ) select_for_radians
    WHERE accumulating_value_radians &gt;= radians LIMIT 1
    )
    order by col_number separator ' ') as circle
FROM (
  SELECT
    t1.value AS col_number,
    t2.value AS row_number,
    @dx := (t1.value - (@size-1)/2) AS dx,
    @dy := ((@size-1)/2 - t2.value) AS dy,
    @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
    CASE
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &gt;= 0 THEN @abs_radians
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()-@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()+@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &gt;= 0 THEN 2*PI()-@abs_radians
    END AS radians
  FROM
    tinyint_asc t1,
    tinyint_asc t2,
    (select @size := 21) sel_size,
    (select @radius := (@size/2 - 1)) sel_radius
  WHERE
    t1.value &lt; @size
    AND t2.value &lt; @size) select_combinations
  GROUP BY row_number
;
+-------------------------------------------+
| circle                                    |
+-------------------------------------------+
| 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 |
| 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 |
| 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 |
| 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 |
| 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 |
| 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 |
| 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 1 1 1 1 1 |
| 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 |
| 3 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 1 1 1 1 1 |
| 3 3 3 3 3 3 3 3 3 3 2 2 1 1 1 1 1 1 1 1 1 |
| 3 3 3 3 3 3 3 3 3 3 2 1 1 1 1 1 1 1 1 1 1 |
| 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 |
| 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
| 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
+-------------------------------------------+</pre>
</blockquote>
<p>The interesting SQL trick is that <em>everything goes within the GROUP_CONCAT clause</em>. Instead of presenting the coordinate, we check on the enhanced values table, looking for the first row which has a greater angle (in radians) than the current pixel has. We then display <strong>1</strong>, <strong>2</strong>, etc. to denote the value.</p>
<p>The next step is actually very simple: instead of drawing the full square, limit to a circle!</p>
<blockquote>
<pre>SELECT
  group_concat(
    IF(round(sqrt(pow(col_number-(@size-1)/2, 2) + pow(row_number-(@size-1)/2, 2))) BETWEEN @radius/2 AND @radius,
    (SELECT name_order FROM
      (
      SELECT
        name_order,
        name_column,
        value_column,
        accumulating_value,
        accumulating_value/@accumulating_value AS accumulating_value_ratio,
        2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
      FROM (
        SELECT
          name_column,
          value_column,
          @name_order := @name_order+1 AS name_order,
          @accumulating_value := @accumulating_value+value_column AS accumulating_value,
          @aggregated_name_column := CONCAT(@aggregated_name_column, name_column, ',') AS aggregated_name_column
        FROM (
          SELECT name AS name_column, value AS value_column FROM sample_values2
          ) select_values,
          (SELECT @name_order := 0) select_name_order,
          (SELECT @accumulating_value := 0) select_accumulating_value,
          (SELECT @aggregated_name_column := '') select_aggregated_name_column
        ) select_accumulating_values
      ) select_for_radians
    WHERE accumulating_value_radians &gt;= radians LIMIT 1
    ), '-')
    order by col_number separator ' ') as circle
FROM (
  SELECT
    t1.value AS col_number,
    t2.value AS row_number,
    @dx := (t1.value - (@size-1)/2) AS dx,
    @dy := ((@size-1)/2 - t2.value) AS dy,
    @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
    CASE
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &gt;= 0 THEN @abs_radians
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()-@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()+@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &gt;= 0 THEN 2*PI()-@abs_radians
    END AS radians
  FROM
    tinyint_asc t1,
    tinyint_asc t2,
    (select @size := 21) sel_size,
    (select @radius := 7) sel_radius
  WHERE
    t1.value &lt; @size
    AND t2.value &lt; @size) select_combinations
  GROUP BY row_number
;
+-------------------------------------------+
| circle                                    |
+-------------------------------------------+
| - - - - - - - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - - - - - - - |
| - - - - - - - - 2 2 2 2 2 - - - - - - - - |
| - - - - - - 3 3 3 2 2 2 2 2 2 - - - - - - |
| - - - - - 3 3 3 3 2 2 2 2 2 2 2 - - - - - |
| - - - - 3 3 3 3 3 2 2 2 2 2 2 2 1 - - - - |
| - - - - 3 3 3 3 3 - - - 2 2 2 1 1 - - - - |
| - - - 3 3 3 3 3 - - - - - 1 1 1 1 1 - - - |
| - - - 3 3 3 3 - - - - - - - 1 1 1 1 - - - |
| - - - 3 3 3 3 - - - - - - - 1 1 1 1 - - - |
| - - - 3 3 3 3 - - - - - - - 4 4 4 4 - - - |
| - - - 3 3 3 3 3 - - - - - 4 4 4 4 4 - - - |
| - - - - 3 3 4 4 4 - - - 4 4 4 4 4 - - - - |
| - - - - 3 4 4 4 4 4 4 4 4 4 4 4 4 - - - - |
| - - - - - 4 4 4 4 4 4 4 4 4 4 4 - - - - - |
| - - - - - - 4 4 4 4 4 4 4 4 4 - - - - - - |
| - - - - - - - - 4 4 4 4 4 - - - - - - - - |
| - - - - - - - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - - - - - - - |
+-------------------------------------------+</pre>
</blockquote>
<p>That looks a lot more like a pie chart.</p>
<h4>Part 4: doing the fancy work</h4>
<p>We will now add (in one big step):</p>
<ul>
<li>Stretching along the X-axis.</li>
<li>Condensing the spaces.</li>
<li>Coloring for the pie parts.</li>
<li>A legend.</li>
</ul>
<p>The text in <strong>bold</strong> is the original query, and is the only thing you need to change in order to create your own pie charts.</p>
<blockquote>
<pre>SELECT
  group_concat(
    IF(round(sqrt(pow(col_number/@stretch-0.5-(@size-1)/2, 2) + pow(row_number-(@size-1)/2, 2))) BETWEEN @radius*2/3 AND @radius,
    (SELECT SUBSTRING(@colors, name_order, 1) FROM
      (
      SELECT
        name_order,
        name_column,
        value_column,
        accumulating_value,
        accumulating_value/@accumulating_value AS accumulating_value_ratio,
        @aggregated_data := CONCAT(@aggregated_data, name_column, ': ', value_column, ' (', ROUND(100*value_column/@accumulating_value), '%)', '|') AS aggregated_name_column,
        2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
      FROM (
        SELECT
          name_column,
          value_column,
          @name_order := @name_order+1 AS name_order,
          @accumulating_value := @accumulating_value+value_column AS accumulating_value
        FROM (
          <strong>SELECT name AS name_column, value AS value_column FROM sample_values2 LIMIT 4</strong>
          ) select_values,
          (SELECT @name_order := 0) select_name_order,
          (SELECT @accumulating_value := 0) select_accumulating_value,
          (SELECT @aggregated_data := '') select_aggregated_name_column
        ) select_accumulating_values
      ) select_for_radians
    WHERE accumulating_value_radians &gt;= radians LIMIT 1
    ), ' ')
    order by col_number separator '') as pie
FROM (
  SELECT
    t1.value AS col_number,
    t2.value AS row_number,
    @dx := (t1.value/@stretch - (@size-1)/2) AS dx,
    @dy := ((@size-1)/2 - t2.value) AS dy,
    @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
    CASE
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &gt;= 0 THEN @abs_radians
      WHEN SIGN(@dy) &gt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()-@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &lt;= 0 THEN PI()+@abs_radians
      WHEN SIGN(@dy) &lt;= 0 AND SIGN(@dx) &gt;= 0 THEN 2*PI()-@abs_radians
    END AS radians
  FROM
    tinyint_asc t1,
    tinyint_asc t2,
    (select @size := 23) sel_size,
    (select @radius := (@size/2 - 1)) sel_radius,
    (select @stretch := 4) sel_stretch,
    (select @colors := '#;o:X"@+-=123456789abcdef') sel_colors
  WHERE
    t1.value &lt; @size*@stretch
    AND t2.value &lt; @size) select_combinations
  GROUP BY row_number
UNION ALL
  SELECT
    CONCAT(
      REPEAT(SUBSTRING(@colors, value, 1), 2),
      '  ',
      SUBSTRING_INDEX(SUBSTRING_INDEX(@aggregated_data, '|', value), '|', -1)
    )
  FROM
    tinyint_asc
  WHERE
    value BETWEEN 1 AND @name_order
;
+----------------------------------------------------------------------------------------------+
| pie                                                                                          |
+----------------------------------------------------------------------------------------------+
|                                                                                              |
|                                   ;;;;;;;;;;;;;;;;;;;;;;;;;                                  |
|                          oooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                         |
|                    ooooooooooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                   |
|                oooooooooooooooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;               |
|             oooooooooooooooooooooooo                     ;;;;;;;;;;;;;;;;;;;;;###            |
|           oooooooooooooooooooo                                 ;;;;;;;;;###########          |
|         oooooooooooooooooo                                         ##################        |
|       ooooooooooooooooo                                               #################      |
|      ooooooooooooooooo                                                 #################     |
|      oooooooooooooooo                                                   ################     |
|     oooooooooooooooo                                                     ################    |
|      oooooooooooooooo                                                   ::::::::::::::::     |
|      ooooooooooooooooo                                                 :::::::::::::::::     |
|       ooooooooooooooooo                                               :::::::::::::::::      |
|         oooooooooooooo::::                                         ::::::::::::::::::        |
|           ooooooo:::::::::::::                                 ::::::::::::::::::::          |
|             ::::::::::::::::::::::::                     ::::::::::::::::::::::::            |
|                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::               |
|                    :::::::::::::::::::::::::::::::::::::::::::::::::::::::                   |
|                          :::::::::::::::::::::::::::::::::::::::::::                         |
|                                   :::::::::::::::::::::::::                                  |
|                                                                                              |
| ##  red: 1 (10%)                                                                             |
| ;;  blue: 2 (20%)                                                                            |
| oo  orange: 3 (30%)                                                                          |
| ::  white: 4 (40%)                                                                           |
+----------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>Making the legend is by itself an interesting hack: aggregating an unknown number of rows using a session user variable, then splitting it again. Of course, this works well if we only have a small number of rows (values), as we expect in our case.</p>
<h4>Showcase</h4>
<p>OK. Why? <em>Why?</em></p>
<p>Here are three charts I hope will convince the skeptic reader:</p>
<p><em>Given the <strong>sakila</strong> database, list the 8 largest tables (size in KB):</em></p>
<blockquote>
<pre>+------------------------------------------------------------------------------+
| pie_chart                                                                    |
+------------------------------------------------------------------------------+
|                                                                              |
|                            #######################                           |
|                    #######################################                   |
|               #################################################              |
|            ##################                   ##################           |
|         ###############                               ###############        |
|        ;;###########                                     #############       |
|      ;;;;;;;;;;;;;                                         #############     |
|      ;;;;;;;;;;;;                                           ############     |
|     ;;;;;;;;;;;;;                                           #############    |
|      ;;;;;;;;;;;;                                           @@@@@@@@@@@@     |
|      ;;;;;;;;;;;;;                                         """""""""""""     |
|        ;;;;;;;;;;;;;                                     XX"""""""""""       |
|         ;;;;;;;;;;;;;;;                               XXXXXXXXXXXXX""        |
|            ;;;;;;;;;;;;;;;;;;                   ::::::::::XXXXXXXX           |
|               ;;;;;;;;;;;;;;;;;;;;;;;;;;oooooooooo::::::::::::X              |
|                    ;;;;;;;;;;;;;;;;;;;;;;ooooooooooo::::::                   |
|                            ;;;;;;;;;;;;;;ooooooooo                           |
|                                                                              |
| ##  rental: 2850816 (43%)                                                    |
| ;;  payment: 2228224 (34%)                                                   |
| oo  inventory: 376832 (6%)                                                   |
| ::  film_text: 325440 (5%)                                                   |
| XX  film: 278528 (4%)                                                        |
| ""  film_actor: 278528 (4%)                                                  |
| @@  customer: 131072 (2%)                                                    |
| ++  staff: 98304 (1%)                                                        |
+------------------------------------------------------------------------------+</pre>
</blockquote>
<p><em>How much disk space does each storage engine consume (sum table size per engine)?</em></p>
<blockquote>
<pre>+------------------------------------------------------------------------------+
| pie_chart                                                                    |
+------------------------------------------------------------------------------+
|                                                                              |
|                            #######################                           |
|                    #######################################                   |
|               #################################################              |
|            ##################                   ##################           |
|         ###############                               ###############        |
|        #############                                     #############       |
|      #############                                         #############     |
|      ############                                           ############     |
|     #############                                           #############    |
|      ############                                           oooooooooooo     |
|      #############                                         ;;;;;;;;;;;oo     |
|        #############                                     ;;;;;;;;;;;;;       |
|         ###############                               ;;;;;;;;;;;;;;;        |
|            ##################                   #;;;;;;;;;;;;;;;;;           |
|               #####################################;;;;;;;;;;;;              |
|                    ###################################;;;;                   |
|                            #######################                           |
|                                                                              |
| ##  InnoDB: 1908732 (84%)                                                    |
| ;;  MyISAM: 284074 (12%)                                                     |
| oo  ARCHIVE: 84276 (4%)                                                      |
+------------------------------------------------------------------------------+</pre>
</blockquote>
<p><em>What were the most popular DMLs during the last 10 seconds?</em></p>
<blockquote>
<pre>+------------------------------------------------------------------------------+
| pie_chart                                                                    |
+------------------------------------------------------------------------------+
|                                                                              |
|                            #######################                           |
|                    #######################################                   |
|               #################################################              |
|            ##################                   ##################           |
|         ###############                               ###############        |
|        #############                                     #############       |
|      #############                                         #############     |
|      ############                                           ############     |
|     #############                                           #############    |
|      ############                                           oooo::::::::     |
|      #############                                         ooooooooooooo     |
|        #############                                     ooooooooooooo       |
|         ###############                               ooooooooooooooo        |
|            #################;                   ;;;;;;;;;ooooooooo           |
|               #############;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oo              |
|                    ######;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                   |
|                            ;;;;;;;;;;;;;;;;;;;;;;;                           |
|                                                                              |
| ##  com_select: 1876 (69%)                                                   |
| ;;  com_insert: 514 (19%)                                                    |
| oo  com_delete: 277 (10%)                                                    |
| ::  com_update: 63 (2%)                                                      |
+------------------------------------------------------------------------------+</pre>
</blockquote>
<h4>Conclusion</h4>
<p>ASCII graphics always look funny; some would say the same about <em>top</em>, <em>wget</em>,  <em>cal</em>, etc. (should I even mention <em>lynx</em>?)</p>
<p>I think it is possible to do most common charting with SQL: I&#8217;ve already shown how to do horizontal graphs and pie charts. Multi-column bar charts can also be worked out. These are not meant as a permanent solution; but it&#8217;s good to be able to visualize some values without having to install Nagios (along with <em>apache</em>, <em>php</em>, drivers, etc.), or otherwise exporting table, copying to desktop machines, loading into OpenOffice impress, generating graphs.</p>
<p>Sometimes you just need an immediate overlook. This is where I find SQL charting to be useful.</p>
<p>Sure, there are Perl and Python solutions for that; that&#8217;s easily achieved as well. But doing it from with the MySQL client gives, in my opinion, a level of confidence: you&#8217;ll always be able to produce the graph; <em>perl-DBD-MySQL</em> or no <em>perl-DBD-MySQL</em>; Linux or Windows.</p>
<p>Besides, it was fun doing it.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/sql-pie-chart/feed</wfw:commentRss>
		<slash:comments>71</slash:comments>
		</item>
		<item>
		<title>Auto scaling, scaled SQL graphs concluded</title>
		<link>http://code.openark.org/blog/mysql/auto-scaling-scaled-sql-graphs-concluded</link>
		<comments>http://code.openark.org/blog/mysql/auto-scaling-scaled-sql-graphs-concluded#comments</comments>
		<pubDate>Tue, 28 Jul 2009 12:38:08 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1050</guid>
		<description><![CDATA[I wasn&#8217;t sure I was to go this far. After catching breath the following have been added to Generic, auto scaling, scaled SQL graphs, and these will conclude my current hacks:

Displaying X-axis min/max values.
Support for Y-axis values precision.
Support for pre-defined scale range.

The addition of the above makes for presentable, usable graphs. See also sample graphs [...]]]></description>
			<content:encoded><![CDATA[<p>I wasn&#8217;t sure I was to go this far. After catching breath the following have been added to <a href="http://code.openark.org/blog/mysql/generic-auto-scaling-scaled-sql-graphs">Generic, auto scaling, scaled SQL graphs</a>, and these will conclude my current hacks:</p>
<ul>
<li>Displaying X-axis min/max values.</li>
<li>Support for Y-axis values precision.</li>
<li>Support for pre-defined scale range.</li>
</ul>
<p>The addition of the above makes for presentable, usable graphs. See also sample graphs at the end of this post.</p>
<h4>Step 8: adding X-axis values</h4>
<p>I add minimum/maximum X-scale values to the graph. What was just <strong>ordering_column</strong> before, now turns to be the <strong>x</strong> in the <strong>y = f(x)</strong> function.<span id="more-1050"></span></p>
<blockquote>
<pre>SELECT
  y_scale,
  horizontal_bar
  FROM
  (
  SELECT
    @row_number := @row_number+1,
    CASE @row_number
      WHEN 1 THEN ROUND(max_value)
      WHEN (@graph_rows+1)/2 THEN ROUND((max_value+min_value)/2)
      WHEN @graph_rows THEN ROUND(min_value)
      ELSE '~'
    END AS y_scale,
    horizontal_bar,
    <strong>@bar_length := IFNULL(@bar_length, CHAR_LENGTH(horizontal_bar))</strong>
  FROM
    (SELECT @row_number := 0) AS select_row
    INNER JOIN
    (
    SELECT
      min_value,
      max_value,
      value_column,
      GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
    FROM
      (SELECT @row_number := 0) AS select_row,
      tinyint_asc
    INNER JOIN (
      SELECT
        ordering_column,
        @min_value AS min_value,
        @max_value AS max_value,
        value_column,
        @scaled_value := CONVERT((value_column-@min_value)*(@graph_rows-1)/(@max_value-@min_value), UNSIGNED) AS scaled_value,
        CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',(@graph_rows-1)-@scaled_value)) AS graph_bar
      FROM
        (
        SELECT
          @min_value := LEAST(IFNULL(@min_value, value_column), value_column) AS min_value,
          @max_value := GREATEST(IFNULL(@max_value, value_column), value_column) AS max_value,
          <strong>@min_range := LEAST(IFNULL(@min_range, ordering_column), ordering_column) AS min_range</strong>,
          <strong>@max_range := GREATEST(IFNULL(@max_range, ordering_column), ordering_column) AS max_range</strong>,
          ordering_column,
          value_column
        FROM
          (
            SELECT id AS ordering_column, val AS value_column
            FROM sample_values LIMIT 100
          ) AS value_select,
          (SELECT @min_value := NULL) AS select_min,
          (SELECT @max_value := NULL) AS select_max,
          <strong>(SELECT @min_range := NULL) AS select_min_range</strong>,
          <strong>(SELECT @max_range := NULL) AS select_max_range</strong>,
          <strong>(SELECT @bar_length := NULL) AS select_bar_length</strong>,
          (SELECT @graph_rows := 15) AS select_graph_rows
        ) AS select_range
      ) AS select_vertical
    WHERE
      tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)
    GROUP BY
      tinyint_asc.value
    ORDER BY
      tinyint_asc.value DESC
    ) AS select_horizontal
  ) AS select_horizontal_untitled
<strong>UNION ALL</strong>
<strong>SELECT '', CONCAT('v', REPEAT(':', @bar_length-2), 'v')</strong>
<strong>UNION ALL</strong>
<strong>SELECT '', CONCAT(@min_range, REPEAT(' ', @bar_length-CHAR_LENGTH(@min_range)-CHAR_LENGTH(@max_range)), @max_range)</strong>
;
+---------+------------------------------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                                       |
+---------+------------------------------------------------------------------------------------------------------+
| 2       | ------------########-------------------------------------------------------########----------------- |
| ~       | ----------##--------###-------------------------------------------------###--------###-------------- |
| ~       | -------###-------------##---------------------------------------------##--------------##------------ |
| ~       | ------#------------------##------------------------------------------#------------------##---------- |
| ~       | ----##---------------------#---------------------------------------##---------------------#--------- |
| ~       | ---#------------------------##-----------------------------------##------------------------##------- |
| ~       | -##---------------------------#---------------------------------#----------------------------#------ |
| 1       | #------------------------------##------------------------------#------------------------------#----- |
| ~       | ---------------------------------#---------------------------##--------------------------------##--- |
| ~       | ----------------------------------##------------------------#------------------------------------#-- |
| ~       | ------------------------------------#---------------------##--------------------------------------## |
| ~       | -------------------------------------##-----------------##------------------------------------------ |
| ~       | ---------------------------------------##-------------##-------------------------------------------- |
| ~       | -----------------------------------------###-------###---------------------------------------------- |
| 0       | --------------------------------------------#######------------------------------------------------- |
|         | v::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 0                                                                                                 99 |
+---------+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>The X-axis range is <strong>[0..99]</strong>, displayed below. The code does not limit us to <strong>INTEGER</strong>. We may as well have <strong>FLOAT</strong>, <strong>DATE</strong>, <strong>TIMESTAMP</strong> etc. (examples follow).</p>
<h4>Step 9: adding precision</h4>
<p>The <strong>0,1,2</strong> Y-axis values play nicely here. But if my generic query is:</p>
<blockquote>
<pre>SELECT id AS ordering_column, val/3 AS value_column FROM sample_values LIMIT 100</pre>
</blockquote>
<p>The resulting graph looks like:</p>
<blockquote>
<pre>+---------+------------------------------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                                       |
+---------+------------------------------------------------------------------------------------------------------+
| 1       | ------------#########------------------------------------------------------#########---------------- |
| ~       | --------####---------###-----------------------------------------------####---------###------------- |
| ~       | ------##----------------###------------------------------------------##----------------###---------- |
| ~       | ----##---------------------##-------------------------------------###---------------------##-------- |
| ~       | -###-------------------------##---------------------------------##--------------------------##------ |
| 0       | #------------------------------##-----------------------------##------------------------------##---- |
| ~       | ---------------------------------##-------------------------##----------------------------------##-- |
| ~       | -----------------------------------##---------------------##--------------------------------------## |
| ~       | -------------------------------------###----------------##------------------------------------------ |
| ~       | ----------------------------------------###---------####-------------------------------------------- |
| 0       | -------------------------------------------#########------------------------------------------------ |
|         | v::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 0                                                                                                 99 |
+---------+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>And the <strong>0,0,1</strong> values are completely inappropriate. We will now introduce precision:</p>
<blockquote>
<pre>SELECT
  y_scale,
  horizontal_bar
  FROM
  (
  SELECT
    @row_number := @row_number+1,
    CASE @row_number
      WHEN 1 THEN <strong>ROUND(max_value * POW(10, @value_precision))/POW(10, @value_precision)</strong>
      WHEN (@graph_rows+1)/2 THEN <strong>ROUND((max_value+min_value)*POW(10, @value_precision)/2)/POW(10, @value_precision)</strong>
      WHEN @graph_rows THEN <strong>ROUND(min_value*POW(10, @value_precision))/POW(10, @value_precision)</strong>
      ELSE '~'
    END AS y_scale,
    horizontal_bar,
    @bar_length := IFNULL(@bar_length, CHAR_LENGTH(horizontal_bar))
  FROM
    (SELECT @row_number := 0) AS select_row
    INNER JOIN
    (
    SELECT
      min_value,
      max_value,
      value_column,
      GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
    FROM
      (SELECT @row_number := 0) AS select_row,
      tinyint_asc
    INNER JOIN (
      SELECT
        ordering_column,
        @min_value AS min_value,
        @max_value AS max_value,
        value_column,
        @scaled_value := CONVERT((value_column-@min_value)*(@graph_rows-1)/(@max_value-@min_value), UNSIGNED) AS scaled_value,
        CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',(@graph_rows-1)-@scaled_value)) AS graph_bar
      FROM
        (
        SELECT
          @min_value := LEAST(IFNULL(@min_value, value_column), value_column) AS min_value,
          @max_value := GREATEST(IFNULL(@max_value, value_column), value_column) AS max_value,
          @min_range := LEAST(IFNULL(@min_range, ordering_column), ordering_column) AS min_range,
          @max_range := GREATEST(IFNULL(@max_range, ordering_column), ordering_column) AS max_range,
          ordering_column,
          value_column
        FROM
          (
            SELECT id AS ordering_column, val/3 AS value_column
            FROM sample_values LIMIT 100
          ) AS value_select,
          (SELECT @min_value := NULL) AS select_min,
          (SELECT @max_value := NULL) AS select_max,
          (SELECT @min_range := NULL) AS select_min_range,
          (SELECT @max_range := NULL) AS select_max_range,
          (SELECT @bar_length := NULL) AS select_bar_length,
          <strong>(SELECT @value_precision := 2) AS select_value_precision,</strong>
          (SELECT @graph_rows := 15) AS select_graph_rows
        ) AS select_range
      ) AS select_vertical
    WHERE
      tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)
    GROUP BY
      tinyint_asc.value
    ORDER BY
      tinyint_asc.value DESC
    ) AS select_horizontal
  ) AS select_horizontal_untitled
UNION ALL
SELECT '', CONCAT('v', REPEAT(':', @bar_length-2), 'v')
UNION ALL
SELECT '', CONCAT(@min_range, REPEAT(' ', @bar_length-CHAR_LENGTH(@min_range)-CHAR_LENGTH(@max_range)), @max_range)
;
+---------+------------------------------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                                       |
+---------+------------------------------------------------------------------------------------------------------+
| 0.67    | ------------########-------------------------------------------------------########----------------- |
| ~       | ----------##--------###-------------------------------------------------###--------###-------------- |
| ~       | -------###-------------##---------------------------------------------##--------------##------------ |
| ~       | ------#------------------##------------------------------------------#------------------##---------- |
| ~       | ----##---------------------#---------------------------------------##---------------------#--------- |
| ~       | ---#------------------------##-----------------------------------##------------------------##------- |
| ~       | -##---------------------------#---------------------------------#----------------------------#------ |
| 0.33    | #------------------------------##------------------------------#------------------------------#----- |
| ~       | ---------------------------------#---------------------------##--------------------------------##--- |
| ~       | ----------------------------------##------------------------#------------------------------------#-- |
| ~       | ------------------------------------#---------------------##--------------------------------------## |
| ~       | -------------------------------------##-----------------##------------------------------------------ |
| ~       | ---------------------------------------##-------------##-------------------------------------------- |
| ~       | -----------------------------------------###-------###---------------------------------------------- |
| 0       | --------------------------------------------#######------------------------------------------------- |
|         | v::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 0                                                                                                 99 |
+---------+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>Watch the <strong>0.33, 0.67</strong> values generated here. This is much better.</p>
<h4>Step 10: setting fixed scale range</h4>
<p>When we compare two graphs, it&#8217;s best if they are scaled the same way. So far, our graphs have scaled automatically according to min/max values. What we add now is a way to predefine the range. For example, dealing with <strong>per-cent</strong> values, I may always wish to scale <strong>[0..100]</strong>, regardless of min/max values.</p>
<p>The code below defines a scale of <strong>[-1, 1]</strong>. It <em>automatically extends</em> if the range is actually insufficient, so it&#8217;s safe to use. Let&#8217;s see the same last graph again:</p>
<blockquote>
<pre>SELECT
  y_scale,
  horizontal_bar
  FROM
  (
  SELECT
    @row_number := @row_number+1,
    CASE @row_number
      WHEN 1 THEN ROUND(max_value * POW(10, @value_precision))/POW(10, @value_precision)
      WHEN (@graph_rows+1)/2 THEN ROUND((max_value+min_value)*POW(10, @value_precision)/2)/POW(10, @value_precision)
      WHEN @graph_rows THEN ROUND(min_value*POW(10, @value_precision))/POW(10, @value_precision)
      ELSE '~'
    END AS y_scale,
    horizontal_bar,
    @bar_length := IFNULL(@bar_length, CHAR_LENGTH(horizontal_bar))
  FROM
    (SELECT @row_number := 0) AS select_row
    INNER JOIN
    (
    SELECT
      min_value,
      max_value,
      value_column,
      GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
    FROM
      (SELECT @row_number := 0) AS select_row,
      tinyint_asc
    INNER JOIN (
      SELECT
        ordering_column,
        @min_value AS min_value,
        @max_value AS max_value,
        value_column,
        @scaled_value := CONVERT((value_column-@min_value)*(@graph_rows-1)/(@max_value-@min_value), UNSIGNED) AS scaled_value,
        CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',(@graph_rows-1)-@scaled_value)) AS graph_bar
      FROM
        (
        SELECT
          @min_value := LEAST(<strong>IFNULL(@min_scale_value, value_column),</strong> IFNULL(@min_value, value_column), value_column) AS min_value,
          @max_value := GREATEST(<strong>IFNULL(@max_scale_value, value_column),</strong> IFNULL(@max_value, value_column), value_column) AS max_value,
          @min_range := LEAST(IFNULL(@min_range, ordering_column), ordering_column) AS min_range,
          @max_range := GREATEST(IFNULL(@max_range, ordering_column), ordering_column) AS max_range,
          ordering_column,
          value_column
        FROM
          (
            SELECT id AS ordering_column, val/3 AS value_column FROM sample_values LIMIT 100
          ) AS value_select,
          (SELECT @min_value := NULL) AS select_min,
          (SELECT @max_value := NULL) AS select_max,
          (SELECT @min_range := NULL) AS select_min_range,
          (SELECT @max_range := NULL) AS select_max_range,
          (SELECT @bar_length := NULL) AS select_bar_length,
          <strong>(SELECT @min_scale_value := -1) AS select_min_scale,</strong>
          <strong>(SELECT @max_scale_value := 1) AS select_max_scale,</strong>
          (SELECT @value_precision := 2) AS select_value_precision,
          (SELECT @graph_rows := 15) AS select_graph_rows
        ) AS select_range
      ) AS select_vertical
    WHERE
      tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)
    GROUP BY
      tinyint_asc.value
    ORDER BY
      tinyint_asc.value DESC
    ) AS select_horizontal
  ) AS select_horizontal_untitled
UNION ALL
SELECT '', CONCAT('v', REPEAT(':', @bar_length-2), 'v')
UNION ALL
SELECT '', CONCAT(@min_range, REPEAT(' ', @bar_length-CHAR_LENGTH(@min_range)-CHAR_LENGTH(@max_range)), @max_range)
;
+---------+------------------------------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                                       |
+---------+------------------------------------------------------------------------------------------------------+
| 1       | ---------------------------------------------------------------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| ~       | ------------########-------------------------------------------------------########----------------- |
| ~       | ------######--------#######------------------------------------------######--------#######---------- |
| ~       | -#####---------------------####---------------------------------#####---------------------####------ |
| ~       | #------------------------------#####------------------------####------------------------------####-- |
| ~       | ------------------------------------#####-------------######--------------------------------------## |
| 0       | -----------------------------------------#############---------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| ~       | ---------------------------------------------------------------------------------------------------- |
| -1      | ---------------------------------------------------------------------------------------------------- |
|         | v::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 0                                                                                                 99 |
+---------+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>Values are still in range <strong>[0..0.667]</strong>, but are presented on a wider scale.</p>
<h4>Showcase</h4>
<p>Here are some nice graphs generated, to show off.</p>
<blockquote>
<pre>+---------+----------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                   |
+---------+----------------------------------------------------------------------------------+
| 14.17   | ----------------------------------------------------------------------###------- |
| ~       | ---------------------------------------------------------------------#---#------ |
| ~       | --------------------------------------------------------------------#-----#----- |
| ~       | -------------------------------------------------------##----------------------- |
| ~       | -----------------------------------------------------##--#---------#-------#---- |
| ~       | ----------------------------------------------------#-----#--------------------- |
| ~       | ---------------------------------------###-----------------#------#------------- |
| 7.09    | -------------------------------------##---#--------#------------------------#--- |
| ~       | ------------------------------------#------#----------------#------------------- |
| ~       | -----------------------####--------#--------#-----#--------------#-----------#-- |
| ~       | ---------------------##----#-----------------#---#-----------#------------------ |
| ~       | --------------------#-------##----#-----------------------------#--------------- |
| ~       | --------#####-----##----------#--#------------#-#-------------#---------------#- |
| ~       | ----####-----##--#-------------##----------------------------------------------# |
| 0       | ####-----------##------------------------------#---------------#---------------- |
|         | v::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 2009-07-25 21:43:27                                          2009-07-29 06:02:27 |
+---------+----------------------------------------------------------------------------------+</pre>
</blockquote>
<blockquote>
<pre>+---------+---------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                  |
+---------+---------------------------------------------------------------------------------+
| 0.99    | ##----------------------------------------------------------------------------- |
| ~       | --##--------------------------------------------------------------------------- |
| ~       | ----#-------------------------------------------------------------------------- |
| ~       | -----##------------------------------------------------------------------------ |
| ~       | -------#----------------------------------------------------------------------- |
| ~       | --------#---------------------------------------------------------------------- |
| ~       | ---------#--------------------------------------------------------------------- |
| 0.39    | ----------#-------------------------------------------------------------------- |
| ~       | -----------#------------------------------------------------------------------- |
| ~       | ------------#------------------------------------------------------------------ |
| ~       | -------------#--------------------########------------------------------------- |
| ~       | --------------#----------------###--------#####---------------################- |
| ~       | ---------------##-----------###----------------#####----######----------------# |
| ~       | -----------------##------###------------------------####----------------------- |
| -0.22   | -------------------######------------------------------------------------------ |
|         | v:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 2009-07-25 21:43:27                                         2009-07-29 06:02:27 |
+---------+---------------------------------------------------------------------------------+</pre>
</blockquote>
<blockquote>
<pre>+---------+---------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                  |
+---------+---------------------------------------------------------------------------------+
| -0      | --------------------######################------------------------------------- |
| ~       | ------------########----------------------#######--------------------------#### |
| ~       | --------####-------------------------------------#####-----------------####---- |
| ~       | ------##----------------------------------------------##------------###-------- |
| ~       | ----##--------------------------------------------------##---------#----------- |
| ~       | ---#------------------------------------------------------#------##------------ |
| ~       | --#--------------------------------------------------------#------------------- |
| -2.39   | -#----------------------------------------------------------#---#-------------- |
| ~       | ---------------------------------------------------------------#--------------- |
| ~       | #------------------------------------------------------------#----------------- |
| ~       | ------------------------------------------------------------------------------- |
| ~       | ------------------------------------------------------------------------------- |
| ~       | ------------------------------------------------------------------------------- |
| ~       | ------------------------------------------------------------------------------- |
| -4.78   | --------------------------------------------------------------#---------------- |
|         | v:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 0                                                                          3.95 |
+---------+---------------------------------------------------------------------------------+</pre>
</blockquote>
<blockquote>
<pre>+---------+----------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                   |
+---------+----------------------------------------------------------------------------------+
| 110.82  | ---------------------------------------------------oooooo----------------------- |
| ~       | ------------------------------------------------ooooooooooo--------------------- |
| ~       | ----------------------------------------------oooooooooooooo-------------------- |
| ~       | --------------------------------------------oooooooooooooooooo------------------ |
| ~       | ----------------------------------oooo----ooooooooooooooooooooo----------------- |
| ~       | o------------------------------ooooooooooooooooooooooooooooooooo---------------- |
| ~       | o----------------------------ooooooooooooooooooooooooooooooooooo---------------- |
| ~       | oo--------------------------ooooooooooooooooooooooooooooooooooooo--------------- |
| ~       | oo-------------------------ooooooooooooooooooooooooooooooooooooooo-------------- |
| ~       | ooo-----------------------ooooooooooooooooooooooooooooooooooooooooo------------- |
| ~       | oooo--------------------oooooooooooooooooooooooooooooooooooooooooooo------------ |
| ~       | oooo-------------------ooooooooooooooooooooooooooooooooooooooooooooo------------ |
| 85.93   | ooooo-----------------ooooooooooooooooooooooooooooooooooooooooooooooo----------- |
| ~       | oooooo---------------ooooooooooooooooooooooooooooooooooooooooooooooooo---------- |
| ~       | ooooooo-------------oooooooooooooooooooooooooooooooooooooooooooooooooo---------- |
| ~       | oooooooo-----------oooooooooooooooooooooooooooooooooooooooooooooooooooo--------- |
| ~       | ooooooooo--------ooooooooooooooooooooooooooooooooooooooooooooooooooooooo-------- |
| ~       | ooooooooooo----oooooooooooooooooooooooooooooooooooooooooooooooooooooooooo------- |
| ~       | ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo------- |
| ~       | oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo------ |
| ~       | ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo----- |
| ~       | oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo---- |
| ~       | ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo--- |
| ~       | oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo-- |
| 61.04   | oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo |
|         | v::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::v |
|         | 93                                                                           172 |
+---------+----------------------------------------------------------------------------------+</pre>
</blockquote>
<h4>Why?</h4>
<p>Why go for all this trouble? Isn&#8217;t this nothing but a cool hack?</p>
<p>Is this stuff useful? Practical?</p>
<p>I have some agenda with this, which I&#8217;ll be happy to share when comes into being. I think there are very practical uses for SQL-based graphs. I&#8217;m sure many readers can think of interesting uses, other than the ones I have in mind.</p>
<h4>Conclusion</h4>
<p>There are many more possibilities and featured which can be built into the graphs. I&#8217;m going to stop here, before this becomes too picky. I hope I have shown that some very nice graphs can be produced with SQL alone.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/auto-scaling-scaled-sql-graphs-concluded/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Generic, auto scaling, scaled SQL graphs</title>
		<link>http://code.openark.org/blog/mysql/generic-auto-scaling-scaled-sql-graphs</link>
		<comments>http://code.openark.org/blog/mysql/generic-auto-scaling-scaled-sql-graphs#comments</comments>
		<pubDate>Mon, 27 Jul 2009 10:17:16 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=1009</guid>
		<description><![CDATA[In Rotating SQL graphs horizontally, I have shown how to rotate an ASCII SQL graph into horizontal position.
I was dissatisfied with some parts of the solution, which I will show now how to fix:

I had to manually scale the graph values so as to fit nicely into screen.
I had to rely on hard coded scaling [...]]]></description>
			<content:encoded><![CDATA[<p>In <a href="http://code.openark.org/blog/mysql/rotating-sql-graphs-horizontally">Rotating SQL graphs horizontally</a>, I have shown how to rotate an ASCII SQL graph into horizontal position.</p>
<p>I was dissatisfied with some parts of the solution, which I will show now how to fix:</p>
<ul>
<li>I had to manually scale the graph values so as to fit nicely into screen.</li>
<li>I had to rely on hard coded scaling schemes.</li>
<li>I had to rely on hard coded column names.</li>
<li>I had no y-axis legend.</li>
</ul>
<p>I will now present an SQL query which allows for <em>pluggable queries</em>, which creates self, <em>auto scaling graphs</em>, along with <em>y-axis scales</em>.</p>
<p>Using deeply nested subqueries, we will evolve a simple SELECT query into an elaborate graph. I will present the many steps required, followed by explanations and sample results. But in the end &#8211; the steps are unimportant. I&#8217;ll present a <em>generic query</em>, into which your own SELECT can be embedded, and which will provide you with the graph.</p>
<p>We&#8217;ll use the same example, found in <a href="http://code.openark.org/blog/wp-content/uploads/2009/07/graph2.sql">graph.sql</a>.</p>
<h4><span id="more-1009"></span>Recap</h4>
<p>Following is the table data we want to use. Values represent a sinus function.</p>
<blockquote>
<pre>SELECT * FROM sample_values;
+-----+----------------------+
| id  | val                  |
+-----+----------------------+
|   0 |                    1 |
|   1 |     1.09983341664683 |
|   2 |     1.19866933079506 |
|   3 |     1.29552020666134 |
|   4 |     1.38941834230865 |
|   5 |      1.4794255386042 |
|   6 |     1.56464247339504 |
|   7 |     1.64421768723769 |
|   8 |     1.71735609089952 |
|   9 |     1.78332690962748 |
...
| 246 |    0.492103409609378 |
| 247 |    0.580639083926769 |
| 248 |    0.673364873895278 |
| 249 |    0.769354294072604 |
| 250 |    0.867648249902227 |
| 251 |    0.967264620669155 |
| 252 |     1.06720807252547 |
| 253 |     1.16648000353716 |
| 254 |     1.26408852138447 |
| 255 |     1.35905835402217 |
+-----+----------------------+
256 rows in set (0.00 sec)</pre>
</blockquote>
<h4>Step 1: Generic query</h4>
<p>The only requirements from your query is for it to be generic. It need to provide tow columns:</p>
<ul>
<li><strong>ordering_column</strong>: A column which orders the values, e.g. an <strong>AUTO_INCREMENT</strong>, some <strong>TIMESTAMP</strong> etc.</li>
<li><strong>value_column</strong>: The values themselves.</li>
</ul>
<p>In the above example, the generic query will be:</p>
<blockquote>
<pre>SELECT id AS <strong>ordering_column</strong>, val AS <strong>value_column</strong> FROM sample_values LIMIT 100</pre>
</blockquote>
<p>From this point on, <em>everything else</em> is self-computed. The above is the only thing that needs to change if you wish to provide your own graphs.</p>
<h4>Problem: hard coded scaling</h4>
<p>In the following example I generate a vertical graph, but I need to hard-code the scaling:</p>
<blockquote>
<pre>SELECT
  ordering_column,
  CONCAT(REPEAT('-',(value_column*10+1)-1),'#',REPEAT('-',22-(value_column*10+1))) AS graph_bar
FROM
  (
  SELECT id AS ordering_column, val AS value_column FROM sample_values LIMIT 100
  ) AS value_select
;
+-----------------+------------------------+
| ordering_column | graph_bar              |
+-----------------+------------------------+
|               0 | ----------#----------- |
|               1 | -----------#---------- |
|               2 | ------------#--------- |
|               3 | -------------#-------- |
|               4 | --------------#------- |
|               5 | ---------------#------ |
|               6 | ----------------#----- |
|               7 | ----------------#----- |
|               8 | -----------------#---- |
|               9 | ------------------#--- |
|              10 | ------------------#--- |
|              11 | -------------------#-- |
|              12 | -------------------#-- |
|              13 | --------------------#- |
|              14 | --------------------#- |
|              15 | --------------------#- |
|              16 | --------------------#- |</pre>
</blockquote>
<h4>Step 2: towards auto-scaling</h4>
<p>In this step we will find the minimum/maximum values for <strong>value_column</strong>. We will as yet do nothing with these.</p>
<blockquote>
<pre>SELECT
  <strong>@min_value := LEAST(@min_value, value_column) AS min_value</strong>,
  <strong>@max_value := GREATEST(@max_value, value_column) AS max_value</strong>,
  ordering_column,
  CONCAT(REPEAT('-',(value_column*10+1)-1),'#',REPEAT('-',22-(value_column*10+1))) AS graph_bar
FROM
  (
  SELECT id AS ordering_column, val AS value_column FROM sample_values LIMIT 100
  ) AS value_select,
  <strong>(SELECT @min_value := 1000000) AS select_min</strong>,
  <strong>(SELECT @max_value := -1000000) AS select_max</strong>
;
+---------------------+-----------------+-----------------+------------------------+
| min_value           | max_value       | ordering_column | graph_bar              |
+---------------------+-----------------+-----------------+------------------------+
|                   1 |               1 |               0 | ----------#----------- |
|                   1 | 1.0998334166468 |               1 | -----------#---------- |
|                   1 | 1.1986693307951 |               2 | ------------#--------- |
|                   1 | 1.2955202066613 |               3 | -------------#-------- |
|                   1 | 1.3894183423086 |               4 | --------------#------- |
|                   1 | 1.4794255386042 |               5 | ---------------#------ |
|                   1 |  1.564642473395 |               6 | ----------------#----- |
|                   1 | 1.6442176872377 |               7 | ----------------#----- |
|                   1 | 1.7173560908995 |               8 | -----------------#---- |
|                   1 | 1.7833269096275 |               9 | ------------------#--- |
|                   1 | 1.8414709848079 |              10 | ------------------#--- |
|                   1 | 1.8912073600614 |              11 | -------------------#-- |
|                   1 | 1.9320390859672 |              12 | -------------------#-- |
|                   1 | 1.9635581854172 |              13 | --------------------#- |
|                   1 | 1.9854497299885 |              14 | --------------------#- |
|                   1 |  1.997494986604 |              15 | --------------------#- |
|                   1 | 1.9995736030415 |              16 | --------------------#- |
|                   1 | 1.9995736030415 |              17 | --------------------#- |
|                   1 | 1.9995736030415 |              18 | --------------------#- |
|                   1 | 1.9995736030415 |              19 | -------------------#-- |
|                   1 | 1.9995736030415 |              20 | -------------------#-- |
|                   1 | 1.9995736030415 |              21 | -------------------#-- |
|                   1 | 1.9995736030415 |              22 | ------------------#--- |
|                   1 | 1.9995736030415 |              23 | -----------------#---- |
|                   1 | 1.9995736030415 |              24 | -----------------#---- |
|                   1 | 1.9995736030415 |              25 | ----------------#----- |
|                   1 | 1.9995736030415 |              26 | ---------------#------ |
|                   1 | 1.9995736030415 |              27 | --------------#------- |
|                   1 | 1.9995736030415 |              28 | -------------#-------- |
|                   1 | 1.9995736030415 |              29 | ------------#--------- |
|                   1 | 1.9995736030415 |              30 | -----------#---------- |
|                   1 | 1.9995736030415 |              31 | ----------#----------- |
|    0.94162585657242 | 1.9995736030415 |              32 | ---------#------------ |
|    0.84225430585675 | 1.9995736030415 |              33 | --------#------------- |
|    0.74445889797317 | 1.9995736030415 |              34 | -------#-------------- |
...</pre>
</blockquote>
<p>I&#8217;m using ugly values for initial min/max comparison (-100000, 1000000). I&#8217;ll soon get rid of them, don&#8217;t worry!</p>
<h4>Step 3: formalizing min/max values</h4>
<p>Using another sub-query, we will isolate the minimum/maximum values, and forget about the graph for the moment.</p>
<blockquote>
<pre>SELECT
  <strong>@min_value</strong>,
  <strong>@max_value</strong>,
  ordering_column,
  value_column
FROM
  (
  SELECT
    @min_value := LEAST(@min_value, value_column) AS min_value,
    @max_value := GREATEST(@max_value, value_column) AS max_value,
    ordering_column,
    value_column
  FROM
    (
    SELECT id AS ordering_column, val AS value_column FROM sample_values LIMIT 100
    ) AS value_select,
    (SELECT @min_value := 1000000) AS select_min,
    (SELECT @max_value := -1000000) AS select_max
  ) AS select_range
;
+---------------------+-----------------+-----------------+----------------------+
| @min_value          | @max_value      | ordering_column | value_column         |
+---------------------+-----------------+-----------------+----------------------+
| 7.6742435899169e-05 | 1.9995736030415 |               0 |                    1 |
| 7.6742435899169e-05 | 1.9995736030415 |               1 |     1.09983341664683 |
| 7.6742435899169e-05 | 1.9995736030415 |               2 |     1.19866933079506 |
| 7.6742435899169e-05 | 1.9995736030415 |               3 |     1.29552020666134 |
| 7.6742435899169e-05 | 1.9995736030415 |               4 |     1.38941834230865 |
| 7.6742435899169e-05 | 1.9995736030415 |               5 |      1.4794255386042 |
| 7.6742435899169e-05 | 1.9995736030415 |               6 |     1.56464247339504 |
| 7.6742435899169e-05 | 1.9995736030415 |               7 |     1.64421768723769 |
| 7.6742435899169e-05 | 1.9995736030415 |               8 |     1.71735609089952 |
| 7.6742435899169e-05 | 1.9995736030415 |               9 |     1.78332690962748 |
| 7.6742435899169e-05 | 1.9995736030415 |              10 |      1.8414709848079 |
| 7.6742435899169e-05 | 1.9995736030415 |              11 |     1.89120736006144 |
...</pre>
</blockquote>
<h4>Step 4: scaling the values</h4>
<p>In this step I&#8217;ll do two things:</p>
<ul>
<li>Get rid of ugly hard coded values for min/max comparison. I&#8217;ll use the <strong>IFNULL</strong> function to check for initial conditions.</li>
<li>Introduce self scaling: I will assume a 21-rows high graph is desired, and will auto-scale the values to fit in. Don&#8217;t worry, I&#8217;ll get rid of those values later on!</li>
</ul>
<blockquote>
<pre>SELECT
  @min_value AS min_value,
  @max_value AS max_value,
  value_column,
  <strong>@scaled_value := CONVERT((value_column-@min_value)*20/(@max_value-@min_value), UNSIGNED) AS scaled_value</strong>,
  <strong>CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',20-@scaled_value)) AS graph_bar</strong>
FROM
  (
  SELECT
    @min_value := LEAST(<strong>IFNULL(@min_value, value_column)</strong>, value_column) AS min_value,
    @max_value := GREATEST(<strong>IFNULL(@max_value, value_column)</strong>, value_column) AS max_value,
    ordering_column,
    value_column
  FROM
    (
    SELECT id AS ordering_column, val AS value_column FROM sample_values LIMIT 100
    ) AS value_select,
    (SELECT @min_value := <strong>NULL</strong>) AS select_min,
    (SELECT @max_value := <strong>NULL</strong>) AS select_max
  ) AS select_range
;
+---------------------+-----------------+----------------------+--------------+-----------------------+
| min_value           | max_value       | value_column         | scaled_value | graph_bar             |
+---------------------+-----------------+----------------------+--------------+-----------------------+
| 7.6742435899169e-05 | 1.9995736030415 |                    1 |           10 | ----------#---------- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.09983341664683 |           11 | -----------#--------- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.19866933079506 |           12 | ------------#-------- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.29552020666134 |           13 | -------------#------- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.38941834230865 |           14 | --------------#------ |
| 7.6742435899169e-05 | 1.9995736030415 |      1.4794255386042 |           15 | ---------------#----- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.56464247339504 |           16 | ----------------#---- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.64421768723769 |           16 | ----------------#---- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.71735609089952 |           17 | -----------------#--- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.78332690962748 |           18 | ------------------#-- |
| 7.6742435899169e-05 | 1.9995736030415 |      1.8414709848079 |           18 | ------------------#-- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.89120736006144 |           19 | -------------------#- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.93203908596723 |           19 | -------------------#- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.96355818541719 |           20 | --------------------# |
| 7.6742435899169e-05 | 1.9995736030415 |     1.98544972998846 |           20 | --------------------# |
| 7.6742435899169e-05 | 1.9995736030415 |     1.99749498660405 |           20 | --------------------# |
| 7.6742435899169e-05 | 1.9995736030415 |      1.9995736030415 |           20 | --------------------# |
| 7.6742435899169e-05 | 1.9995736030415 |     1.99166481045247 |           20 | --------------------# |
| 7.6742435899169e-05 | 1.9995736030415 |      1.9738476308782 |           20 | --------------------# |
| 7.6742435899169e-05 | 1.9995736030415 |     1.94630008768741 |           19 | -------------------#- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.90929742682568 |           19 | -------------------#- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.86320936664887 |           19 | -------------------#- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.80849640381959 |           18 | ------------------#-- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.74570521217672 |           17 | -----------------#--- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.67546318055115 |           17 | -----------------#--- |
| 7.6742435899169e-05 | 1.9995736030415 |     1.59847214410396 |           16 | ----------------#---- |</pre>
</blockquote>
<p>I have now re-introduced the graph. Note that while values are in the range 0..2, the SQL query <em>automatically scales</em> to a 1-21 long graph bar.</p>
<h4>Step 5: rotation</h4>
<p>Shall we now present this as a horizontal graph?</p>
<blockquote>
<pre>SELECT
  <strong>GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS `result`</strong>
FROM
  <strong>tinyint_asc</strong>
<strong>INNER JOIN</strong> (
  SELECT
    ordering_column,
    @min_value AS min_value,
    @max_value AS max_value,
    value_column,
    @scaled_value := CONVERT((value_column-@min_value)*20/(@max_value-@min_value), UNSIGNED) AS scaled_value,
    CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',20-@scaled_value)) AS graph_bar
  FROM
    (
    SELECT
      @min_value := LEAST(IFNULL(@min_value, value_column), value_column) AS min_value,
      @max_value := GREATEST(IFNULL(@max_value, value_column), value_column) AS max_value,
      ordering_column,
      value_column
    FROM
      (
      SELECT id AS ordering_column, val AS value_column FROM sample_values LIMIT 100
      ) AS value_select,
      (SELECT @min_value := NULL) AS select_min,
      (SELECT @max_value := NULL) AS select_max
    ) AS select_range
  ) AS select_vertical
WHERE
  <strong>tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)</strong>
<strong>GROUP BY</strong>
  <strong>tinyint_asc.value</strong>
<strong>ORDER BY</strong>
  <strong>tinyint_asc.value DESC;</strong>
;
+------------------------------------------------------------------------------------------------------+
| result                                                                                               |
+------------------------------------------------------------------------------------------------------+
| -------------######---------------------------------------------------------######------------------ |
| -----------##------###---------------------------------------------------###------###--------------- |
| ---------##-----------#-------------------------------------------------#------------#-------------- |
| --------#--------------##---------------------------------------------##--------------##------------ |
| ------##-----------------#-------------------------------------------#------------------#----------- |
| -----#--------------------#-----------------------------------------#--------------------#---------- |
| ----#----------------------#---------------------------------------#----------------------#--------- |
| ---#------------------------#-------------------------------------#------------------------#-------- |
| --#--------------------------#-----------------------------------#--------------------------#------- |
| -#----------------------------#---------------------------------#----------------------------#------ |
| #------------------------------#-------------------------------#------------------------------#----- |
| --------------------------------#-----------------------------#--------------------------------#---- |
| ---------------------------------#---------------------------#----------------------------------#--- |
| ----------------------------------#-------------------------#------------------------------------#-- |
| -----------------------------------##----------------------#--------------------------------------#- |
| -------------------------------------#--------------------#----------------------------------------# |
| --------------------------------------#-----------------##------------------------------------------ |
| ---------------------------------------#---------------#-------------------------------------------- |
| ----------------------------------------##-----------##--------------------------------------------- |
| ------------------------------------------##-------##----------------------------------------------- |
| --------------------------------------------#######------------------------------------------------- |
+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>We now have a <em>self-scaling</em> horizontal graph! Phew!</p>
<p>But I&#8217;m not satisfied yet.</p>
<h4>Step 6: towards Y-axis scaling</h4>
<p>We will now introduce min/max Y-scale values into the graph. Note the following:</p>
<ul>
<li>I&#8217;ll be using a counter to count rows.</li>
<li>I&#8217;ll rely on previous knowledge of hard-coded 21 rows value (I promise, this will be fixed soon).</li>
<li>We already have min/max values. Using the two, I&#8217;ll also provide the mid-value.</li>
<li>I chose to use <strong>ROUND()</strong>. This may not be the best idea when your values are very small fractions. Remove the<strong> ROUND()</strong> if it does not fit in with your values.</li>
</ul>
<blockquote>
<pre>SELECT
  <strong>@row_number := @row_number+1,</strong>
  <strong>CASE @row_number</strong>
    <strong>WHEN 1  THEN ROUND(max_value)</strong>
    <strong>WHEN 11 THEN ROUND((max_value+min_value)/2)</strong>
    <strong>WHEN 21 THEN ROUND(min_value)</strong>
    <strong>ELSE ''</strong>
  <strong>END AS y_scale</strong>,
  horizontal_bar
FROM
  <strong>(SELECT @row_number := 0) AS select_row</strong>
  INNER JOIN
  (
  SELECT
    min_value,
    max_value,
    value_column,
    GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
  FROM
    (SELECT @row_number := 0) AS select_row,
    tinyint_asc
  INNER JOIN (
    SELECT
      ordering_column,
      @min_value AS min_value,
      @max_value AS max_value,
      value_column,
      @scaled_value := CONVERT((value_column-@min_value)*20/(@max_value-@min_value), UNSIGNED) AS scaled_value,
      CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',20-@scaled_value)) AS graph_bar
    FROM
      (
      SELECT
        @min_value := LEAST(IFNULL(@min_value, value_column), value_column) AS min_value,
        @max_value := GREATEST(IFNULL(@max_value, value_column), value_column) AS max_value,
        ordering_column,
        value_column
      FROM
        (
        SELECT id AS ordering_column, val AS value_column FROM sample_values LIMIT 100
        ) AS value_select,
        (SELECT @min_value := NULL) AS select_min,
        (SELECT @max_value := NULL) AS select_max
      ) AS select_range
    ) AS select_vertical
  WHERE
    tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)
  GROUP BY
    tinyint_asc.value
  ORDER BY
    tinyint_asc.value DESC
  ) AS select_horizontal
;+------------------------------+---------+------------------------------------------------------------------------------------------------------+
| @row_number := @row_number+1 | y_scale | horizontal_bar                                                                                       |
+------------------------------+---------+------------------------------------------------------------------------------------------------------+
|                            1 | 2       | -------------######---------------------------------------------------------######------------------ |
|                            2 |         | -----------##------###---------------------------------------------------###------###--------------- |
|                            3 |         | ---------##-----------#-------------------------------------------------#------------#-------------- |
|                            4 |         | --------#--------------##---------------------------------------------##--------------##------------ |
|                            5 |         | ------##-----------------#-------------------------------------------#------------------#----------- |
|                            6 |         | -----#--------------------#-----------------------------------------#--------------------#---------- |
|                            7 |         | ----#----------------------#---------------------------------------#----------------------#--------- |
|                            8 |         | ---#------------------------#-------------------------------------#------------------------#-------- |
|                            9 |         | --#--------------------------#-----------------------------------#--------------------------#------- |
|                           10 |         | -#----------------------------#---------------------------------#----------------------------#------ |
|                           11 | 1       | #------------------------------#-------------------------------#------------------------------#----- |
|                           12 |         | --------------------------------#-----------------------------#--------------------------------#---- |
|                           13 |         | ---------------------------------#---------------------------#----------------------------------#--- |
|                           14 |         | ----------------------------------#-------------------------#------------------------------------#-- |
|                           15 |         | -----------------------------------##----------------------#--------------------------------------#- |
|                           16 |         | -------------------------------------#--------------------#----------------------------------------# |
|                           17 |         | --------------------------------------#-----------------##------------------------------------------ |
|                           18 |         | ---------------------------------------#---------------#-------------------------------------------- |
|                           19 |         | ----------------------------------------##-----------##--------------------------------------------- |
|                           20 |         | ------------------------------------------##-------##----------------------------------------------- |
|                           21 | 0       | --------------------------------------------#######------------------------------------------------- |
+------------------------------+---------+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>Well, now we&#8217;re getting somewhere!</p>
<h4>Step 7: cleaning up</h4>
<p>In this step I&#8217;ll do the following:</p>
<ul>
<li>Using yet another subquery (how many do we have already?), I&#8217;ll get rid of the counter column.</li>
<li>I&#8217;ll remove the 21-rows hard coding. There will only be one session variable set to this value; All calculations will scale themselves according to that value.</li>
</ul>
<blockquote>
<pre>SELECT
  y_scale,
  horizontal_bar
  FROM
  (
  SELECT
    @row_number := @row_number+1,
    CASE @row_number
      WHEN 1  THEN ROUND(max_value)
      WHEN <strong>(@graph_rows+1)/2</strong> THEN ROUND((max_value+min_value)/2)
      WHEN <strong>@graph_rows</strong> THEN ROUND(min_value)
      ELSE ''
    END AS y_scale,
    horizontal_bar
  FROM
    (SELECT @row_number := 0) AS select_row
    INNER JOIN
    (
    SELECT
      min_value,
      max_value,
      value_column,
      GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
    FROM
      (SELECT @row_number := 0) AS select_row,
      tinyint_asc
    INNER JOIN (
      SELECT
        ordering_column,
        @min_value AS min_value,
        @max_value AS max_value,
        value_column,
        @scaled_value := CONVERT((value_column-@min_value)*(@graph_rows-1)/(@max_value-@min_value), UNSIGNED) AS scaled_value,
        CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',(@graph_rows-1)-@scaled_value)) AS graph_bar
      FROM
        (
        SELECT
          @min_value := LEAST(IFNULL(@min_value, value_column), value_column) AS min_value,
          @max_value := GREATEST(IFNULL(@max_value, value_column), value_column) AS max_value,
          ordering_column,
          value_column
        FROM
          (
            SELECT id AS ordering_column, val AS value_column
            FROM sample_values LIMIT 100
          ) AS value_select,
          (SELECT @min_value := NULL) AS select_min,
          (SELECT @max_value := NULL) AS select_max,
          <strong>(SELECT @graph_rows := 21) AS select_graph_rows</strong>
        ) AS select_range
      ) AS select_vertical
    WHERE
      tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)
    GROUP BY
      tinyint_asc.value
    ORDER BY
      tinyint_asc.value DESC
    ) AS select_horizontal
  ) AS select_horizontal_untitled
;
+---------+------------------------------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                                       |
+---------+------------------------------------------------------------------------------------------------------+
| 2       | -------------######---------------------------------------------------------######------------------ |
|         | -----------##------###---------------------------------------------------###------###--------------- |
|         | ---------##-----------#-------------------------------------------------#------------#-------------- |
|         | --------#--------------##---------------------------------------------##--------------##------------ |
|         | ------##-----------------#-------------------------------------------#------------------#----------- |
|         | -----#--------------------#-----------------------------------------#--------------------#---------- |
|         | ----#----------------------#---------------------------------------#----------------------#--------- |
|         | ---#------------------------#-------------------------------------#------------------------#-------- |
|         | --#--------------------------#-----------------------------------#--------------------------#------- |
|         | -#----------------------------#---------------------------------#----------------------------#------ |
| 1       | #------------------------------#-------------------------------#------------------------------#----- |
|         | --------------------------------#-----------------------------#--------------------------------#---- |
|         | ---------------------------------#---------------------------#----------------------------------#--- |
|         | ----------------------------------#-------------------------#------------------------------------#-- |
|         | -----------------------------------##----------------------#--------------------------------------#- |
|         | -------------------------------------#--------------------#----------------------------------------# |
|         | --------------------------------------#-----------------##------------------------------------------ |
|         | ---------------------------------------#---------------#-------------------------------------------- |
|         | ----------------------------------------##-----------##--------------------------------------------- |
|         | ------------------------------------------##-------##----------------------------------------------- |
| 0       | --------------------------------------------#######------------------------------------------------- |
+---------+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>And that&#8217;s as far as we go in our example.</p>
<h4>Proof of concept</h4>
<p>Just to prove my point, I&#8217;ll present another SQL graph. This time the function is <strong>x*sin(x)</strong>, which has a nice curve.</p>
<ul>
<li>Take a look at the <strong>bold text</strong> in the query. This is <em>the only thing</em> that changes. Replace this with your generic query, and you&#8217;re done!</li>
<li>I&#8217;ve also set the number of rows to be <strong>11</strong>. Also find bold text in query. Change this to scale the graph!</li>
</ul>
<p>Note how the graph <em>automatically</em> scales to the range [-17,16].</p>
<blockquote>
<pre>SELECT
  y_scale,
  horizontal_bar
  FROM
  (
  SELECT
    @row_number := @row_number+1,
    CASE @row_number
      WHEN 1  THEN ROUND(max_value)
      WHEN (@graph_rows+1)/2 THEN ROUND((max_value+min_value)/2)
      WHEN @graph_rows THEN ROUND(min_value)
      ELSE ''
    END AS y_scale,
    horizontal_bar
  FROM
    (SELECT @row_number := 0) AS select_row
    INNER JOIN
    (
    SELECT
      min_value,
      max_value,
      value_column,
      GROUP_CONCAT(SUBSTRING(graph_bar, tinyint_asc.value, 1) ORDER BY ordering_column SEPARATOR '') AS horizontal_bar
    FROM
      (SELECT @row_number := 0) AS select_row,
      tinyint_asc
    INNER JOIN (
      SELECT
        ordering_column,
        @min_value AS min_value,
        @max_value AS max_value,
        value_column,
        @scaled_value := CONVERT((value_column-@min_value)*(@graph_rows-1)/(@max_value-@min_value), UNSIGNED) AS scaled_value,
        CONCAT(REPEAT('-',@scaled_value),'#',REPEAT('-',(@graph_rows-1)-@scaled_value)) AS graph_bar
      FROM
        (
        SELECT
          @min_value := LEAST(IFNULL(@min_value, value_column), value_column) AS min_value,
          @max_value := GREATEST(IFNULL(@max_value, value_column), value_column) AS max_value,
          ordering_column,
          value_column
        FROM
          (
            <strong>SELECT id AS ordering_column, SIN(id/5)*id/5 AS value_column FROM sample_values LIMIT 100</strong>
          ) AS value_select,
          (SELECT @min_value := NULL) AS select_min,
          (SELECT @max_value := NULL) AS select_max,
          (SELECT @graph_rows := <strong>11</strong>) AS select_graph_rows
        ) AS select_range
      ) AS select_vertical
    WHERE
      tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(graph_bar)
    GROUP BY
      tinyint_asc.value
    ORDER BY
      tinyint_asc.value DESC
    ) AS select_horizontal
  ) AS select_horizontal_untitled
;
+---------+------------------------------------------------------------------------------------------------------+
| y_scale | horizontal_bar                                                                                       |
+---------+------------------------------------------------------------------------------------------------------+
| 16      | ---------------------------------------------------------------------------------------------------# |
|         | --------------------------------------------------------------------#######-----------------------#- |
|         | ---------------------------------------##-------------------------##-------#---------------------#-- |
|         | -----------------------------------####--####--------------------#----------##------------------#--- |
|         | ------########-------------------##----------##-----------------#-------------#----------------#---- |
| -1      | ######--------#####-----------###--------------##-------------##---------------#--------------#----- |
|         | -------------------###########-------------------##----------#------------------#------------#------ |
|         | ---------------------------------------------------##------##--------------------#----------#------- |
|         | -----------------------------------------------------######-----------------------#--------#-------- |
|         | -----------------------------------------------------------------------------------##----##--------- |
| -17     | -------------------------------------------------------------------------------------####----------- |
+---------+------------------------------------------------------------------------------------------------------</pre>
</blockquote>
<h4>Conclusion</h4>
<p>There goes my Saturday afternoon nap.</p>
<p>To be done:</p>
<ul>
<li>Present X-axis values.</li>
<li>Do better work with value precision.</li>
<li>Present proper titles. This is easy with session variables. I&#8217;ll make a draft.</li>
</ul>
<p>To be continued&#8230; (and concluded, I don&#8217;t mean to drag this much longer).</p>
<p>[Continues on: <a href="http://code.openark.org/blog/mysql/auto-scaling-scaled-sql-graphs-concluded">Auto scaling, scaled SQL graphs concluded</a>]</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/generic-auto-scaling-scaled-sql-graphs/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Rotating SQL graphs horizontally</title>
		<link>http://code.openark.org/blog/mysql/rotating-sql-graphs-horizontally</link>
		<comments>http://code.openark.org/blog/mysql/rotating-sql-graphs-horizontally#comments</comments>
		<pubDate>Fri, 24 Jul 2009 04:00:12 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Graphs]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=984</guid>
		<description><![CDATA[We all love graphs. We all love SQL hacks. We all know the SQL hack which displays a character-based graph (example follows for those unfamiliar).
But we all love horizontal graphs, not vertical ones. We are used to the X axis being horizontal, Y being vertical. Not vice versa.
In this post I&#8217;ll present a SQL hack [...]]]></description>
			<content:encoded><![CDATA[<p>We all love graphs. We all love SQL hacks. We all know the SQL hack which displays a character-based graph (example follows for those unfamiliar).</p>
<p>But we all love <em>horizontal</em> graphs, not vertical ones. We are used to the X axis being horizontal, Y being vertical. Not vice versa.</p>
<p>In this post I&#8217;ll present a SQL hack which rotates a vertical graph to horizontal. In fact, the technique shown will rotate any &#8216;textual image&#8217;; but graphs are a nice example.</p>
<h4>A vertical graph example</h4>
<p>What&#8217;s prettier than a sinus curve? I have prepared a simple table that will serve nicely, and can be found in <a href="http://code.openark.org/blog/wp-content/uploads/2009/07/graph.sql">graph_tables.sql</a>.</p>
<p><span id="more-984"></span></p>
<blockquote>
<pre>SELECT * FROM sample_values;
+-----+----------------------+
| id  | val                  |
+-----+----------------------+
|   0 |                    1 |
|   1 |     1.09983341664683 |
|   2 |     1.19866933079506 |
|   3 |     1.29552020666134 |
|   4 |     1.38941834230865 |
|   5 |      1.4794255386042 |
|   6 |     1.56464247339504 |
|   7 |     1.64421768723769 |
|   8 |     1.71735609089952 |
|   9 |     1.78332690962748 |
...
| 246 |    0.492103409609378 |
| 247 |    0.580639083926769 |
| 248 |    0.673364873895278 |
| 249 |    0.769354294072604 |
| 250 |    0.867648249902227 |
| 251 |    0.967264620669155 |
| 252 |     1.06720807252547 |
| 253 |     1.16648000353716 |
| 254 |     1.26408852138447 |
| 255 |     1.35905835402217 |
+-----+----------------------+
256 rows in set (0.00 sec)</pre>
</blockquote>
<p>The common trick for displaying a graph is something like:</p>
<blockquote>
<pre>SELECT
  id,
  CONCAT(REPEAT('-',(val*10+1)-1),'#',REPEAT('-',22-(val*10+1))) AS bar
FROM
  sample_values
LIMIT 100
+----+------------------------+
| id | bar                    |
+----+------------------------+
|  0 | ----------#----------- |
|  1 | -----------#---------- |
|  2 | ------------#--------- |
|  3 | -------------#-------- |
|  4 | --------------#------- |
|  5 | ---------------#------ |
|  6 | ----------------#----- |
|  7 | ----------------#----- |
|  8 | -----------------#---- |
|  9 | ------------------#--- |
| 10 | ------------------#--- |
| 11 | -------------------#-- |
| 12 | -------------------#-- |
| 13 | --------------------#- |
| 14 | --------------------#- |
| 15 | --------------------#- |
| 16 | --------------------#- |
| 17 | --------------------#- |
| 18 | --------------------#- |
| 19 | -------------------#-- |
| 20 | -------------------#-- |
| 21 | -------------------#-- |
| 22 | ------------------#--- |
| 23 | -----------------#---- |
| 24 | -----------------#---- |
| 25 | ----------------#----- |
| 26 | ---------------#------ |
| 27 | --------------#------- |
...
+----+------------------------+
100 rows in set (0.00 sec)</pre>
</blockquote>
<blockquote><p>In the above I&#8217;ve explicitly stretched values for them to be presentable.</p></blockquote>
<p>So that&#8217;s the vertical graph. It&#8217;s not easy to read, plus you need to scroll down on your terminal to find out what&#8217;s going on. Can we rotate it?</p>
<h4>Rotating the graph</h4>
<p>We will combine the String Walking technique with <a title="Unwalking a string with GROUP_CONCAT" href="http://code.openark.org/blog/mysql/unwalking-a-string-with-group_concat">String Unwalking</a>. What we need and assume is as follows:</p>
<ul>
<li> The above graph pads the bars up to some fixed length.</li>
<li>Said length is not too high, so as to fit nicely within our screen.</li>
<li>We have an integers table, used for string walking.</li>
</ul>
<p>The idea is to iterate the textual column character by character <em>from end to start</em> (done by string walking), and convert each such character column to row (done by string unwalking).</p>
<blockquote>
<pre>SELECT
  GROUP_CONCAT(SUBSTRING(bar,tinyint_asc.value,1) ORDER BY id SEPARATOR '') AS `Sinus graph`
FROM
  tinyint_asc
INNER JOIN (
  SELECT
    id,
    CONCAT(REPEAT('-',(val*10+1)-1),'#',REPEAT('-',22-(val*10+1))) AS bar
  FROM
    sample_values
  LIMIT 100) sel_graph
WHERE
  tinyint_asc.value BETWEEN 1 AND CHAR_LENGTH(bar)
GROUP BY
  tinyint_asc.value
ORDER BY
  tinyint_asc.value DESC;

+------------------------------------------------------------------------------------------------------+
| Sinus graph                                                                                          |
+------------------------------------------------------------------------------------------------------+
| ---------------------------------------------------------------------------------------------------- |
| -------------######---------------------------------------------------------######------------------ |
| -----------##------###---------------------------------------------------###------###--------------- |
| ---------##-----------#-------------------------------------------------#------------#-------------- |
| --------#--------------##---------------------------------------------##--------------##------------ |
| ------##-----------------#-------------------------------------------#------------------#----------- |
| -----#--------------------#-----------------------------------------#--------------------#---------- |
| ----#----------------------#---------------------------------------#----------------------#--------- |
| ---#------------------------#-------------------------------------#------------------------#-------- |
| --#--------------------------#-----------------------------------#--------------------------#------- |
| -#----------------------------#---------------------------------#----------------------------#------ |
| #------------------------------#-------------------------------#------------------------------#----- |
| --------------------------------#-----------------------------#--------------------------------#---- |
| ---------------------------------#---------------------------#----------------------------------#--- |
| ----------------------------------#-------------------------#------------------------------------#-- |
| -----------------------------------##----------------------#--------------------------------------#- |
| -------------------------------------#--------------------#----------------------------------------# |
| --------------------------------------#-----------------##------------------------------------------ |
| ---------------------------------------#---------------#-------------------------------------------- |
| ----------------------------------------##-----------##--------------------------------------------- |
| ------------------------------------------##-------##----------------------------------------------- |
| --------------------------------------------#######------------------------------------------------- |
+------------------------------------------------------------------------------------------------------+</pre>
</blockquote>
<p>Now that&#8217;s much prettier, isn&#8217;t it?</p>
<h4>Crazy stuff challenge</h4>
<p>For those eager to improve upon this, I raise a few challenges (no prize but fame, though). Use the given data (<a href="http://code.openark.org/blog/wp-content/uploads/2009/07/graph.sql">graph_tables.sql</a>), to produce:</p>
<ul>
<li>(Vertical) values on the X-axis for all graph points.</li>
<li>Min/max values for Y-axis, on separate column.</li>
<li>As I understand it, full, in place, values for Y-axis is not possible; please prove me wrong!</li>
<li>Other crazy stuff?</li>
</ul>
<p>Please add solutions in comments!</p>
<p>[Continues on: <a href="http://code.openark.org/blog/mysql/generic-auto-scaling-scaled-sql-graphs">Generic, auto scaling, scaled SQL graphs</a>]</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/rotating-sql-graphs-horizontally/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>
