<?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/"
	xmlns:georss="http://www.georss.org/georss">

<channel>
	<title>Here comes the Sun &#187; web</title>
	<atom:link href="http://sunng.info/blog/tag/web/feed/" rel="self" type="application/rss+xml" />
	<link>http://sunng.info/blog</link>
	<description>Life ramblings</description>
	<lastBuildDate>Thu, 02 Sep 2010 12:29:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>geb for browser functional testing</title>
		<link>http://sunng.info/blog/2010/08/geb-for-browser-functional-testing/</link>
		<comments>http://sunng.info/blog/2010/08/geb-for-browser-functional-testing/#comments</comments>
		<pubDate>Tue, 17 Aug 2010 14:42:14 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[装备]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://sunng.info/blog/2010/08/geb-for-browser-functional-test/</guid>
		<description><![CDATA[虽然现在不做前段了，但是发现好的工具还是很兴奋。今天在twitter上看到Grails in Action的作者 @pledbrook 转了一个geb 0.4的消息，顺带看了一下这个工具 http://geb.codehaus.org geb项目旨在创造一套groovy dsl帮助人们进行webapp的functional test。它是对selenium的封装，举例： @Grapes&#40;&#91; &#160; &#160; @Grab&#40;'org.seleniumhq.selenium:selenium-firefox-driver:latest.release'&#41;, &#160; &#160; @Grab&#40;'org.codehaus.geb:geb-core:latest.release'&#41; &#93;&#41; import geb.* println &#34;Dependencies downloaded, ready for testing&#34; Browser.drive&#40;'http://sunng.info:8000/Pacajus'&#41;&#123; &#160; &#160; assert title== 'Pacajus' &#160; &#160; assert $&#40;&#34;p&#34;, 3&#41;.text&#40;&#41; == 'Population: 41558' &#125; println &#34;Tested, bye&#34; 打开页面，执行断言。如果断言失败，driver方法会报null： Caught: geb.error.DriveException: null 只要在命令行用groovy执行即可，grapes会搞定依赖关系。 很方便吧，文档上说还可以跟grails / junit等等集成，快去看看吧 http://geb.codehaus.org/manual/latest/index.html The post [...]]]></description>
			<content:encoded><![CDATA[<p>虽然现在不做前段了，但是发现好的工具还是很兴奋。今天在twitter上看到Grails in Action的作者 @pledbrook 转了一个geb 0.4的消息，顺带看了一下这个工具</p>
<p>http://geb.codehaus.org</p>
<p>geb项目旨在创造一套groovy dsl帮助人们进行webapp的functional test。它是对selenium的封装，举例：</p>
<div class="codecolorer-container groovy default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="groovy codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Grapes<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#91;</span><br />
&nbsp; &nbsp; @Grab<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">'org.seleniumhq.selenium:selenium-firefox-driver:latest.release'</span><span style="color: #66cc66;">&#41;</span>,<br />
&nbsp; &nbsp; @Grab<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">'org.codehaus.geb:geb-core:latest.release'</span><span style="color: #66cc66;">&#41;</span><br />
<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span><br />
<a href="http://www.google.de/search?q=site%3Adocs.codehaus.org/%20import"><span style="color: #000000; font-weight: bold;">import</span></a> <span style="color: #a1a100;">geb.*</span><br />
<br />
<a href="http://www.google.de/search?q=site%3Adocs.codehaus.org/%20println"><span style="color: #993399;">println</span></a> <span style="color: #ff0000;">&quot;Dependencies downloaded, ready for testing&quot;</span><br />
Browser.<span style="color: #006600;">drive</span><span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">'http://sunng.info:8000/Pacajus'</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#123;</span><br />
&nbsp; &nbsp; <a href="http://www.google.de/search?q=site%3Adocs.codehaus.org/%20assert"><span style="color: #000000; font-weight: bold;">assert</span></a> title<span style="color: #66cc66;">==</span> <span style="color: #ff0000;">'Pacajus'</span><br />
<br />
&nbsp; &nbsp; <a href="http://www.google.de/search?q=site%3Adocs.codehaus.org/%20assert"><span style="color: #000000; font-weight: bold;">assert</span></a> $<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">&quot;p&quot;</span>, <span style="color: #cc66cc;">3</span><span style="color: #66cc66;">&#41;</span>.<span style="color: #006600;">text</span><span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">==</span> <span style="color: #ff0000;">'Population: 41558'</span><br />
<span style="color: #66cc66;">&#125;</span><br />
<a href="http://www.google.de/search?q=site%3Adocs.codehaus.org/%20println"><span style="color: #993399;">println</span></a> <span style="color: #ff0000;">&quot;Tested, bye&quot;</span></div></div>
<p>打开页面，执行断言。如果断言失败，driver方法会报null：<br />
Caught: geb.error.DriveException: null</p>
<p>只要在命令行用groovy执行即可，grapes会搞定依赖关系。</p>
<p>很方便吧，文档上说还可以跟grails / junit等等集成，快去看看吧<br />
<a href="http://geb.codehaus.org/manual/latest/index.html">http://geb.codehaus.org/manual/latest/index.html</a></p>
<p>The post is brought to you by <a href="http://fedorahosted.org/lekhonee">lekhonee</a> v0.7</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/08/geb-for-browser-functional-testing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>XAuth.org &amp; OpenAddresses.org</title>
		<link>http://sunng.info/blog/2010/04/xauth-org-openaddresses-org/</link>
		<comments>http://sunng.info/blog/2010/04/xauth-org-openaddresses-org/#comments</comments>
		<pubDate>Tue, 20 Apr 2010 12:15:30 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[他山]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://sunng.info/blog/?p=547</guid>
		<description><![CDATA[XAuth.org 提供一个新的轻量级认证机制，他的机制可以参考： http://xauth.org/spec/ OpenAddresses.org是上个月O&#8217;Reilly Where 2.0上发布的开放的Geocoding服务，类似OpenStreetMaps http://openaddresses.org/]]></description>
			<content:encoded><![CDATA[<p>XAuth.org 提供一个新的轻量级认证机制，他的机制可以参考：<br />
<a href="http://xauth.org/spec/">http://xauth.org/spec/</a></p>
<p>OpenAddresses.org是上个月O&#8217;Reilly Where 2.0上发布的开放的Geocoding服务，类似OpenStreetMaps<br />
<a href="http://openaddresses.org/">http://openaddresses.org/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/04/xauth-org-openaddresses-org/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>将！将！将！</title>
		<link>http://sunng.info/blog/2010/03/%e5%b0%86%ef%bc%81%e5%b0%86%ef%bc%81%e5%b0%86%ef%bc%81/</link>
		<comments>http://sunng.info/blog/2010/03/%e5%b0%86%ef%bc%81%e5%b0%86%ef%bc%81%e5%b0%86%ef%bc%81/#comments</comments>
		<pubDate>Sat, 20 Mar 2010 01:47:14 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[自话]]></category>
		<category><![CDATA[Life]]></category>
		<category><![CDATA[snda]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://sunng.info/blog/?p=519</guid>
		<description><![CDATA[汇报一下近况，最近仍然是6*12小时的工作，这是第二个礼拜了。我们的进度还算是顺利，不过架不住产品那里经常会有另他们自己拍案的新点子，这可苦了开发。可怜我们开发在产品面前基本没有什么话语权，连老大都只能说我们尽量支持。因为我负责的部分跟业务关系不是特别紧密，之前也算是有先见之明，坚决解耦，所以策划的变化对我影响不大，只是听见两旁的同事不断地重复“我悲剧了”。 10.04来了，迫不及待了又。结果升级的过程中遇到了无数依赖问题和冲突，apt几乎都无能为力，前天搞到1点多才睡，早晨7点起床又继续折腾。到了昨天中午终于可以进入桌面了，累得够呛，晚上又发烧了。 早晨起床忽然想起winter，现在的4square这么火，昨天又看到了比较mood的微博产品。哎，我们做的时候可是07年啊，也许是太超前了吧。 加班路上哼张楚的《将将将》，我真想再见张楚一回。 看见@dearaprilfool同学抄的《致橡树》，很有共鸣，女生们应该读一下。小时候读不算，这个年纪读才好。 新的一天开始了。]]></description>
			<content:encoded><![CDATA[<ol>
<li>汇报一下近况，最近仍然是6*12小时的工作，这是第二个礼拜了。我们的进度还算是顺利，不过架不住产品那里经常会有另他们自己拍案的新点子，这可苦了开发。可怜我们开发在产品面前基本没有什么话语权，连老大都只能说我们尽量支持。因为我负责的部分跟业务关系不是特别紧密，之前也算是有先见之明，坚决解耦，所以策划的变化对我影响不大，只是听见两旁的同事不断地重复“我悲剧了”。</li>
<li>10.04来了，迫不及待了又。结果升级的过程中遇到了无数依赖问题和冲突，apt几乎都无能为力，前天搞到1点多才睡，早晨7点起床又继续折腾。到了昨天中午终于可以进入桌面了，累得够呛，晚上又发烧了。</li>
<li>早晨起床忽然想起winter，现在的4square这么火，昨天又看到了比较mood的微博产品。哎，我们做的时候可是07年啊，也许是太超前了吧。</li>
<li>加班路上哼张楚的《将将将》，我真想再见张楚一回。</li>
<li>看见@dearaprilfool同学抄的《致橡树》，很有共鸣，女生们应该读一下。小时候读不算，这个年纪读才好。</li>
<li>新的一天开始了。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/03/%e5%b0%86%ef%bc%81%e5%b0%86%ef%bc%81%e5%b0%86%ef%bc%81/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>Sunng.info in Mosaic</title>
		<link>http://sunng.info/blog/2010/03/sunng-info-in-mosaic/</link>
		<comments>http://sunng.info/blog/2010/03/sunng-info-in-mosaic/#comments</comments>
		<pubDate>Wed, 10 Mar 2010 01:17:37 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[留影]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://sunng.info/blog/?p=510</guid>
		<description><![CDATA[Mosaic: http://en.wikipedia.org/wiki/Mosaic_browser 这么一看，什么标准通通扯淡，还是图片管用，什么浏览器都可以显示。]]></description>
			<content:encoded><![CDATA[<p>Mosaic:<br />
<a href="http://en.wikipedia.org/wiki/Mosaic_browser">http://en.wikipedia.org/wiki/Mosaic_browser</a></p>
<p><a href="http://www.flickr.com/photos/40741608@N08/4420674473/" title="mosaic by 贝小塔, on Flickr"><img src="http://farm5.static.flickr.com/4058/4420674473_e14d48020e.jpg" width="457" height="500" alt="mosaic" /></a></p>
<p>这么一看，什么标准通通扯淡，还是图片管用，什么浏览器都可以显示。</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/03/sunng-info-in-mosaic/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>Sunng&#039;s Canvas based Heatmap API</title>
		<link>http://sunng.info/blog/2010/02/sunngs-canvas-based-heatmap-api/</link>
		<comments>http://sunng.info/blog/2010/02/sunngs-canvas-based-heatmap-api/#comments</comments>
		<pubDate>Thu, 11 Feb 2010 07:10:03 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[广告]]></category>
		<category><![CDATA[手艺]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[visualization]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://sunng.info/blog/?p=464</guid>
		<description><![CDATA[Glad to announce my works this morning: A simple heatmap API based on HTML5 canvas. The programming interface is rather simple now. To create such a heatmap, you just new a heatmap object by: &#60;canvas width="300" height="215" id="canv"&#62;&#60;/canvas&#62; heatmap = new HeatMap("canv"); Then read your dataset and push data into the heatmap: heatmap.push(x, y, value); [...]]]></description>
			<content:encoded><![CDATA[<p>Glad to announce my works this morning: A simple heatmap API based on HTML5 canvas.<br />
<a href="http://www.flickr.com/photos/40741608@N08/4347604245/" title="canvas by 贝小塔, on Flickr"><img src="http://farm5.static.flickr.com/4071/4347604245_486bd3a12e_o.jpg" width="300" height="215" alt="canvas" /></a></p>
<p>The programming interface is rather simple now. To create such a heatmap, you just new a heatmap object by:</p>
<pre class="brush:html">
&lt;canvas width="300" height="215" id="canv"&gt;&lt;/canvas&gt;
</pre>
<pre class="brush:javascript">
heatmap = new HeatMap("canv");
</pre>
<p>Then read your dataset and push data into the heatmap:</p>
<pre class="brush:javascript">
heatmap.push(x, y, value);
</pre>
<p>At last, spread the data and get the canvas rendered:</p>
<pre class="brush:javascript">
heatmap.spread();
heatmap.render();
</pre>
<p>Now you got it.</p>
<p>For advanced usage, you can specify the resolution of heatmap by px:</p>
<pre class="brush:javascript">
heatmap = new HeatMap("canv", 2);
</pre>
<p>Large value will gain performance with low image quality.</p>
<p>Also, you can specify a value to define the attenuation value of each px.</p>
<pre class="brush:javascript">
heatmap.spread(5);
</pre>
<p>Finally, there is an option to use your own color schema for heatmap generation.</p>
<pre class="brush:javascript">
heatmap.render(function(value, maxValue){
    var light = value / maxValue * 100;
    return "hsl(20, 75%, "+light+"%)";
});
</pre>
<p>The parameters passed in are current pixel value and max value in whole context.</p>
<p>That&#8217;s all the toolkit. More functionality and options might be added in future. Grab it from my bitbucket page:<br />
<a href="http://bitbucket.org/sunng/daily-coding/src/tip/canvas-heatmap/">http://bitbucket.org/sunng/daily-coding/src/tip/canvas-heatmap/</a></p>
<p>There is a demo page which you can test the api by clicking canvas:<br />
<a href="http://www.flickr.com/photos/40741608@N08/4347650163/" title="canvas-raw by 贝小塔, on Flickr"><img src="http://farm3.static.flickr.com/2733/4347650163_ea740356e4_o.jpg" width="326" height="284" alt="canvas-raw" /></a><br />
then click heatmap button!<br />
<a href="http://www.flickr.com/photos/40741608@N08/4348397874/" title="canvas2 by 贝小塔, on Flickr"><img src="http://farm3.static.flickr.com/2796/4348397874_8ac80b0756_o.jpg" width="314" height="275" alt="canvas2" /></a></p>
<p>This is the new year gift for my readers and my dear friends !</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/02/sunngs-canvas-based-heatmap-api/feed/</wfw:commentRss>
		<slash:comments>21</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>All new SUNNG.INFO</title>
		<link>http://sunng.info/blog/2010/01/all-new-sunng-info/</link>
		<comments>http://sunng.info/blog/2010/01/all-new-sunng-info/#comments</comments>
		<pubDate>Thu, 28 Jan 2010 02:32:03 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[装备]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://sunng.info/blog/?p=432</guid>
		<description><![CDATA[今早短路，在godaddy上注册了我的新域名sunng.info。更换域名主要出于几方面的考虑： 新域名1年的注册费用相当于原先的一半； 众所周知的原因，域名放在国内存在一定的风险； 原先域名过长不方便记忆和交流； .com后缀和我一穷二白地状况不符 classicning.com曾被加入stopbadware.org，至今没有从twitter的malcious数据库里移除 原先的classicning.com会在今年6月到期，如果没有意外情况我不准备续费了，届时这个使用了四年的域名就要废弃了（当时客服大嫂还在电话里娇嗔地问我为啥不续十年呢，你说为啥呢）。 麻烦大家利用这半年的时间逐步改变习惯，逐步用sunng.info了。]]></description>
			<content:encoded><![CDATA[<p>今早短路，在godaddy上注册了我的新域名sunng.info。更换域名主要出于几方面的考虑：</p>
<ol>
<li>新域名1年的注册费用相当于原先的一半；</li>
<li>众所周知的原因，域名放在国内存在一定的风险；</li>
<li>原先域名过长不方便记忆和交流；</li>
<li>.com后缀和我一穷二白地状况不符</li>
<li>classicning.com曾被加入stopbadware.org，至今没有从twitter的malcious数据库里移除</li>
</ol>
<p>原先的classicning.com会在今年6月到期，如果没有意外情况我不准备续费了，届时这个使用了四年的域名就要废弃了（当时客服大嫂还在电话里娇嗔地问我为啥不续十年呢，你说为啥呢）。</p>
<p>麻烦大家利用这半年的时间逐步改变习惯，逐步用sunng.info了。</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/01/all-new-sunng-info/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>OAuth Step by Step</title>
		<link>http://sunng.info/blog/2010/01/oauth-step-by-step/</link>
		<comments>http://sunng.info/blog/2010/01/oauth-step-by-step/#comments</comments>
		<pubDate>Mon, 04 Jan 2010 15:47:25 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[手艺]]></category>
		<category><![CDATA[mashup]]></category>
		<category><![CDATA[oauth]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://www.classicning.com/blog/?p=420</guid>
		<description><![CDATA[最近琢磨OAuth认证方式。OAuth的优点主要在于﻿ 用户不需要直接提供用户名密码给第三方应用，就可以让第三方应用访问受限资源； 资源提供方对第三方应用有更细粒度的控制。 在整个OAuth协议里，生成signature的base string是最容易出错的部分。它由HTTP方法名、URL编码的请求路径和请求的参数表组成。 请求的参数表是除去oauth_signature以外的所有参数，按参数名排序，并进行url转义 def to_signature_key(method, url, data): keys = list(data.keys()) keys.sort() encoded = urllib.quote("&#38;".join([key+"="+data[key] for key in keys])) return "&#38;".join([method, urllib.quote(url, safe="~"), encoded]) 有了这个通用的生成signature base string的方法，以后就可以根据OAuth协议规范按步骤进行。 首先获取Request Token。这一步通常使用资源提供方注册的API Key和API Key Secret def request_token_params(consumer_key, consumer_secret, path, method='GET'): data={} data['oauth_consumer_key']=consumer_key data['oauth_signature_method']='HMAC-SHA1' data['oauth_timestamp']=str(int(time.time())) data['oauth_nonce']=''.join([str(random.randint(0,9)) for i in range(10)]) print data msg = to_signature_key(method, path, data) [...]]]></description>
			<content:encoded><![CDATA[<p>最近琢磨OAuth认证方式。OAuth的优点主要在于﻿</p>
<ul>
<li>用户不需要直接提供用户名密码给第三方应用，就可以让第三方应用访问受限资源；</li>
<li>资源提供方对第三方应用有更细粒度的控制。</li>
</ul>
<p>在整个OAuth协议里，生成signature的base string是最容易出错的部分。它由HTTP方法名、URL编码的请求路径和请求的参数表组成。<br />
请求的参数表是除去oauth_signature以外的所有参数，按参数名排序，并进行url转义</p>
<pre class="brush:python">def to_signature_key(method, url, data):
	keys = list(data.keys())
	keys.sort()
	encoded = urllib.quote("&amp;".join([key+"="+data[key] for key in keys]))
	return "&amp;".join([method, urllib.quote(url, safe="~"), encoded])
</pre>
<p>有了这个通用的生成signature base string的方法，以后就可以根据OAuth协议规范按步骤进行。</p>
<p>首先获取Request Token。这一步通常使用资源提供方注册的API Key和API Key Secret</p>
<pre class="brush:python">def request_token_params(consumer_key, consumer_secret, path, method='GET'):
	data={}
	data['oauth_consumer_key']=consumer_key
	data['oauth_signature_method']='HMAC-SHA1'
	data['oauth_timestamp']=str(int(time.time()))
	data['oauth_nonce']=''.join([str(random.randint(0,9)) for i in range(10)])
	print data

	msg = to_signature_key(method, path, data)
	print msg

	signed = base64.b64encode(hmac.new(consumer_secret+"&amp;", msg, hashlib.sha1).digest())
	print signed
	data['oauth_signature']=signed
	return data

def result2dict(result_string):
	d = {}
	params = res.split('&amp;')
	for p in params:
		d[p.split('=')[0]] = p.split('=')[1]
	return d

conn = httplib.HTTPConnection("www.douban.com", 80)

params = request_token_params(consumer_key, consumer_secret, request_token_path)
conn.request('GET', request_token_path+"?"+urllib.urlencode(params))
res = conn.getresponse().read()
print res
request_token = result2dict(res)
</pre>
<p>这一步可以获得未经认证的Request Token和Request Token Secret。需要注意的细节是在计算hmac签名的时候，即使只有一个Token Secret，仍然需要加上&#8221;&amp;&#8221;</p>
<p>第二步要求用户授权该Request Token，打开浏览器，将用户定向到相应的授权页面，参数为上一步获得的Request Token</p>
<p>第三步，用授权过的Request Token换取Access Token。这一步类似第一步，只是用于签名的token包括API Key Secret和Request Token</p>
<pre class="brush:python">def access_token_params(consumer_key, consumer_secret, oauth_token, oauth_secret, path, method='GET'):
	data={}
	data['oauth_consumer_key']=consumer_key
	data['oauth_signature_method']='HMAC-SHA1'
	data['oauth_timestamp']=str(int(time.time()))
	data['oauth_nonce']=''.join([str(random.randint(0,9)) for i in range(10)])
	data['oauth_token'] = oauth_token

	msg = to_signature_key(method, path, data)
	print msg

	signed = base64.b64encode(hmac.new(consumer_secret+"&amp;"+oauth_secret, msg, hashlib.sha1).digest())
	print signed
	data['oauth_signature']=signed
	return data

params = access_token_params(consumer_key, consumer_secret, request_token['oauth_token'],
	request_token['oauth_token_secret'], access_token_path)
conn.request('GET', access_token_path+"?"+urllib.urlencode(params))
res = conn.getresponse().read()
print res
access_token = result2dict(res)
</pre>
<p>这一步将至少返回Access Token和Access Token Secret，是最终用于访问受限资源的Token。以豆瓣的实现为例，OAuth的相关参数应放在HTTP头里随请求进行发送。</p>
<pre class="brush:python">def oauth_header(consumer_key, consumer_secret, oauth_token, oauth_secret, path, realm):
	data = access_token_params(consumer_key, consumer_secret, oauth_token, oauth_secret, path, method="POST")
	header_string = ','.join([key+'="'+data[key]+'"' for key in data.keys()])
	return 'OAuth realm="'+realm+'",'+header_string

posturl = 'http://api.douban.com/miniblog/saying'

content = """&lt;?xml version='1.0' encoding='UTF-8'?&gt;
&lt;entry xmlns:ns0="http://www.w3.org/2005/Atom" xmlns:db="http://www.douban.com/xmlns/"&gt;
&lt;content&gt;li lei ju le han mei mei&lt;/content&gt;
&lt;/entry&gt;
"""

header = {}
header['Authorization'] = oauth_header(consumer_key, consumer_secret,
		access_token['oauth_token'], access_token['oauth_token_secret'],
		posturl, "http://api.douban.com")
header['Content-Type'] = 'application/atom+xml'
print header

conn.request('POST', posturl, content, header)
res = conn.getresponse().read()

print res

conn.close()
</pre>
<p>在这一步中，用于生成signature base string的url是要访问的受限资源地址，而签名的参数表依然是oauth相关的参数。<br />
生成的Authorization头如下</p>
<pre class="brush:text">Authorization: OAuth realm="http://api.douban.com",
    oauth_nonce="8735717688",
    oauth_timestamp="1262613619",
    oauth_consumer_key="0bc081a01168b263234184e0343a1729",
    oauth_signature_method="HMAC-SHA1",
    oauth_token="5fb836c37543ad691f28a44a5fcb083b",
    oauth_signature="jk6p5qaXVPrGQctSzpO5jjYHfDk="
</pre>
<p>用这个头就可以在一定的时间内访问所有授权范围内的受限资源。</p>
<p>代码是ugly了一些，不过应该相对易于理解吧。</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2010/01/oauth-step-by-step/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>Using Yan in Ruby Web Application</title>
		<link>http://sunng.info/blog/2009/12/use-yan-in-ruby-web-application/</link>
		<comments>http://sunng.info/blog/2009/12/use-yan-in-ruby-web-application/#comments</comments>
		<pubDate>Mon, 21 Dec 2009 15:16:45 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[把戏]]></category>
		<category><![CDATA[captcha]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[Yan]]></category>

		<guid isPermaLink="false">http://www.classicning.com/blog/?p=360</guid>
		<description><![CDATA[I will show you the usage of Yan captcha service. In this tutorial, it&#8217;s based on a simple ruby web application of the Sinatra web framework. Before we start to use the service, it is necesary to get Yan running. Download the code from the project page, then build and run it with maven: mvn [...]]]></description>
			<content:encoded><![CDATA[<p>I will show you the usage of Yan captcha service. In this tutorial, it&#8217;s based on a simple ruby web application of the Sinatra web framework.</p>
<p>Before we start to use the service, it is necesary to get Yan running. Download the code from <a href="http://bitbucket.org/sunng/yan/" target="_blank">the project page</a>, then build and run it with maven:<br />
<em>mvn jetty:run</em></p>
<p>To enable the application to use Yan, we have to register our application to get an API Key. If you use Yan 0.3, there is a secret registration page at <em>http://localhost:8080/yan/reg.jsp</em> The page is protected by HTTP Basic Authentication, the username and password are store in &#8216;realm.properties&#8217; which is considered to locate in the root directory. Open the file you can see the plain text username and password. If you are running the latest development version, there is no long any UI for API Key creation, but restful interface. This won&#8217;t be hard to you, pickup your tools such as curl or poster (a firefox extension) to send a HTTP request. Take curl as example, do it like this:<br />
<em>curl -X PUT &#8220;http://localhost:8080/yan/apikey/&#8221; -d &#8220;SinatraTestApp&#8221; -u &#8220;username:password&#8221;</em></p>
<p>If it works, you will get a line of json:<br />
<em>{&#8220;apikey&#8221;:&#8221;b251b0dc2eed31cac38555b61d4fa6a453923bfd&#8221;,&#8221;appName&#8221;:&#8221;SinatraTestApp&#8221;}</em><br />
Save this apikey.</p>
<p>Sinatra is generally considered to be the world&#8217;s lightest and smallest web framework. And our application is rather simple. Just check the code:</p>
<pre class="brush:ruby">require "rubygems"
require "sinatra"
require "net/http"
require "yaml"

apikey='b251b0dc2eed31cac38555b61d4fa6a453923bfd'

get '/' do
	conn = Net::HTTP.new('localhost', 8080)
	q = "ip=#{@env['REMOTE_ADDR']}&amp;apikey=#{apikey}&amp;alt=yaml&amp;mode=0"
	resp, data = conn.get("/yan/ticket?#{q}")
	@ticket = YAML::load(data)
	haml :sinatra_captcha
end

post '/' do
	conn = Net::HTTP.new('localhost', 8080)
	q = "ip=#{@env['REMOTE_ADDR']}&amp;apikey=#{apikey}&amp;key=#{params['key']}&amp;code=#{params['captcha']}"
	resp, data = conn.get("/yan/validate?#{q}")
	data
end

use_in_file_templates!
__END__

@@ sinatra_captcha
%html
	%head
		%title Yan Captcha on Sinatra
	%body
		%form{:action=&gt;"/", :method=&gt;"post"}
			%p
				Username:
				%input{:name=&gt;"username", :type=&gt;"text"}
			%p
				Password:
				%input{:name=&gt;"password", :type=&gt;"password"}
			%p
				Captcha:
				%img{:src=&gt;@ticket['url']}
				%br
				%input{:name=&gt;"captcha", :type=&gt;"text"}
				%input{:name=&gt;"key", :type=&gt;"hidden", :value=&gt;@ticket['key']}
				%input{:type=&gt;'submit'}
</pre>
<p>There are two parts of this application: ruby code and haml. I just use in-file-template for convenience. We define a get handler and a post handler on the path &#8216;/&#8217;. The get handler will request a ticket from Yan which contains captcha image url and ticket key. The post handler will extract user input and submit the Yan&#8217;s validator and return user the result. And the HAML code is template for page rendering after GET request.</p>
<p>Maybe you need to install sinatra and some dependency:<br />
<em>sudo gem install sinatra haml</em></p>
<p>Run the code with a build-in WEBrick<br />
<em>ruby sinatra-yan.rb</em></p>
<p>Browse to the default url, test it:</p>
<p><img class="alignnone" src="http://farm3.static.flickr.com/2766/4203612734_3237ed066c_o.png" alt="" width="459" height="268" /></p>
<p>For another similar tutorial using python, check Yan&#8217;s wiki page:<br />
<a href="http://bitbucket.org/sunng/yan/wiki/SampleCode" target="_blank">http://bitbucket.org/sunng/yan/wiki/SampleCode</a></p>
<p>Thank you for your support. btw, today is my dear girl friend&#8217;s birthday, I just wish her happy everyday.</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2009/12/use-yan-in-ruby-web-application/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Nginx HTTP Push</title>
		<link>http://sunng.info/blog/2009/11/nginx-http-push/</link>
		<comments>http://sunng.info/blog/2009/11/nginx-http-push/#comments</comments>
		<pubDate>Thu, 26 Nov 2009 10:07:35 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[手艺]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://www.classicning.com/blog/?p=340</guid>
		<description><![CDATA[前些天看到一个Nginx的Module，用来是实现Comet，今天简单试了一下功能。作者名叫Leo Ponomarev，项目地址：http://pushmodule.slact.net/ 安装 Module需要在编译时加入nginx，同时下载nginx和nginx-push-module，在nginx configure时增加一个参数： ./configure &#8211;add-module=path/to/nginx_http_push_module 使用 编写一个非常基本的nginx配置文件： events{ worker_connections 1024; } http{ server { listen 80; server_name localhost; location /publish { set $push_channel_id $arg_id; push_publisher; push_store_messages on; push_message_timeout 2h; push_max_message_buffer_length 10; push_min_message_recipients 0; } location /subscribe{ push_subscriber; push_subscriber_concurrency broadcast; set $push_channel_id $arg_id; default_type text/plain; } } } 一个简单的Server定义了两个路径分别用于publish和subscribe。所有相关的配置项可以在项目主页找到解释，不作赘述。 启动nginx nginx -c /home/sun/nginxpush/nginx-push.conf 打开一个终端访问subscribe [...]]]></description>
			<content:encoded><![CDATA[<p>前些天看到一个Nginx的Module，用来是实现Comet，今天简单试了一下功能。作者名叫Leo Ponomarev，项目地址：http://pushmodule.slact.net/</p>
<h3>安装</h3>
<p>Module需要在编译时加入nginx，同时下载nginx和nginx-push-module，在nginx configure时增加一个参数：<br />
<em>./configure &#8211;add-module=path/to/nginx_http_push_module</em></p>
<h3>使用</h3>
<p>编写一个非常基本的nginx配置文件：</p>
<pre class="brush:plain">events{
	worker_connections 1024;
}
http{
	server {
		listen	80;
		server_name	localhost;

		location /publish {
			set $push_channel_id $arg_id;

			push_publisher;

			push_store_messages on;
			push_message_timeout 2h;
			push_max_message_buffer_length 10;
			push_min_message_recipients 0;
		}

		location /subscribe{
			push_subscriber;

			push_subscriber_concurrency broadcast;
			set $push_channel_id $arg_id;
			default_type text/plain;
		}

	}
}</pre>
<p>一个简单的Server定义了两个路径分别用于publish和subscribe。所有相关的配置项可以在项目主页找到解释，不作赘述。</p>
<p>启动nginx<br />
<em>nginx -c /home/sun/nginxpush/nginx-push.conf</em></p>
<p><em><span style="font-style: normal; background-color: #ffffff;">打开一个终端访问subscribe<br />
<em> curl -X GET http://localhost/subscribe?id=0</em></span></em></p>
<p><span style="background-color: #ffffff;">可以看到HTTP请求被阻塞</span></p>
<p><span style="background-color: #ffffff;">打开另一个终端访问publish<br />
<em> curl -X POST http://localhost/publish?id=0 -d &#8220;Hello World&#8221; </em></span></p>
<p><span style="background-color: #ffffff;">此时subscriber收到字符串&#8221;Hello World&#8221; ，完成HTTP请求。</span></p>
<p><span style="background-color: #ffffff;">subscriber可以通过设置HTTP头来对消息进行过滤，如<br />
<em> curl -X POST http://localhost/publish?id=0 -d &#8220;Hello World&#8221;</em><br />
<em> curl -X GET http://localhost/subscribe?id=0 &#8211;verbose</em></span></p>
<div><em>*</em><span style="background-color: #ffffff;"><em> About to connect() to localhost port 80 (#0)</em></span></div>
<div><span style="background-color: #ffffff;"><em>*   Trying 127.0.0.1&#8230; connected</em></span></div>
<div><span style="background-color: #ffffff;"><em>* Connected to localhost (127.0.0.1) port 80 (#0)</em></span></div>
<div><span style="background-color: #ffffff;"><em>&gt; GET /subscribe?id=0 HTTP/1.1</em></span></div>
<div><span style="background-color: #ffffff;"><em>&gt; User-Agent: curl/7.19.7 (i686-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3.3</em></span></div>
<div><span style="background-color: #ffffff;"><em>&gt; Host: localhost</em></span></div>
<div><span style="background-color: #ffffff;"><em>&gt; Accept: */*</em></span></div>
<div><span style="background-color: #ffffff;"><em>&gt; </em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; HTTP/1.1 200 OK</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Server: nginx/0.8.28</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Date: Thu, 26 Nov 2009 09:45:25 GMT</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Content-Type: application/x-www-form-urlencoded</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Content-Length: 10</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Last-Modified: Thu, 26 Nov 2009 09:44:59 GMT</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Connection: keep-alive</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Etag: 0</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; Vary: If-None-Match, If-Modified-Since</em></span></div>
<div><span style="background-color: #ffffff;"><em>&lt; </em></span></div>
<div><span style="background-color: #ffffff;"><em>* Connection #0 to host localhost left intact</em></span></div>
<div><span style="background-color: #ffffff;"><em>* Closing connection #0</em></span></div>
<p><em>HelloWorld</em></p>
<p>从响应的头部可以看到Last-Modified: Thu, 26 Nov 2009 09:44:59 GMT的时间是上一次publish的时间，并且通过Vary字段提示了两个选项</p>
<ul>
<li>If-None-Match</li>
<li>If-Modified-Since</li>
</ul>
<p>RFC中对Vary头是这样解释的：</p>
<p>The Vary field value indicates the set of request-header fields that fully determines, while the response is fresh, whether a cache is permitted to use the response to reply to a subsequent request without revalidation.</p>
<p>即可以通过发送If-Modified-Since来获取指定时间之后的数据</p>
<p><em>curl -X GET -H &#8220;If-Modified-Since: Thu, 26 Nov 2009 09:44:50 GMT&#8221; http://localhost/subscribe?id=0 &#8211;verbose</em></p>
<p>这时subscribe会重新被阻塞而不是接收上次publish的数据，充分利用了HTTP的语义。</p>
<p>这样用push module来做Web-IM、聊天室的思路就非常清晰了：<span style="background-color: #ffffff;">每个浏览器保持一个subscriber连接，在接收到消息后连接关闭。把消息打印出来，并根据消息响应的头部Last-Modified请求重新subscribe。</span></p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2009/11/nginx-http-push/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	<georss:point>31.203057 121.6174866</georss:point>	</item>
		<item>
		<title>Open Source Charting Toolkits in JavaScript</title>
		<link>http://sunng.info/blog/2009/10/open-source-charting-libraries-in-javascript/</link>
		<comments>http://sunng.info/blog/2009/10/open-source-charting-libraries-in-javascript/#comments</comments>
		<pubDate>Thu, 22 Oct 2009 13:48:23 +0000</pubDate>
		<dc:creator>Sunng</dc:creator>
				<category><![CDATA[手艺]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[dojo]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mootools]]></category>
		<category><![CDATA[svg]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://www.classicning.com/blog/?p=284</guid>
		<description><![CDATA[项目的下一阶段，需要做一些数据展现方面的工作，处于对Adobe产品的抵触情绪，我个人还是倾向于用JavaScript来完成。今天找了几个画Chart的库，比较一下功能和编程接口的使用，为接下来的开发做一些准备。 dojox.charting (BSD License) 没什么悬念，第一个想到的就是曾经用过的dojo。之前实习时候用dojo画chart也算是积累了一些心得。dojox.charting的最主要优点是编程接口完善、全面，可以配置的项目很多，接口易于编程，易于动态地生成、操作Chart。而另一方面最主要的问题就是文档比较匮乏，不了解的人可能问了，doc.dojotoolkit.org好强大，怎么会文档匮乏呢。其实是dojo和mootools类似，都很喜欢用option object来传递可选参数，但是这些option object在文档中没有任何涉及，很多关键的属性都在其中但是却没有办法查到，算是美中一大不足。 &#60;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&#62; &#60;html xmlns="http://www.w3.org/1999/xhtml"&#62; &#60;head&#62; &#60;script src="dojo-release-1.3.2/dojo/dojo.js" djConfig="isDebug: true"&#62;&#60;/script&#62; &#60;title&#62;Dojo&#60;/title&#62; &#60;script type="text/javascript"&#62; dojo.require("dojox.charting.Chart2D"); &#60;/script&#62; &#60;/head&#62; &#60;body&#62; &#60;h1&#62;Dojox.charting&#60;/h1&#62; &#60;div id="charting" style="width:400px; height:250px;"&#62; &#60;/div&#62; &#60;script type="text/javascript"&#62; （function charting(){ dojo.empty("charting"); var data = [2,4,5,7,7,4,4,7]; var chart = new dojox.charting.Chart2D("charting"); chart.addPlot("default", {type: "Lines", markers: true, shadows: [...]]]></description>
			<content:encoded><![CDATA[<p>项目的下一阶段，需要做一些数据展现方面的工作，处于对Adobe产品的抵触情绪，我个人还是倾向于用JavaScript来完成。今天找了几个画Chart的库，比较一下功能和编程接口的使用，为接下来的开发做一些准备。</p>
<h3>dojox.charting</h3>
<p>(BSD License)<br />
没什么悬念，第一个想到的就是曾经用过的<a href="http://dojotoolkit.org/" target="_blank">dojo</a>。之前实习时候用dojo画chart也算是积累了一些心得。dojox.charting的最主要优点是编程接口完善、全面，可以配置的项目很多，接口易于编程，易于动态地生成、操作Chart。而另一方面最主要的问题就是文档比较匮乏，不了解的人可能问了，doc.dojotoolkit.org好强大，怎么会文档匮乏呢。其实是dojo和<a href="http://mootools.net">mootools</a>类似，都很喜欢用option object来传递可选参数，但是这些option object在文档中没有任何涉及，很多关键的属性都在其中但是却没有办法查到，算是美中一大不足。</p>
<pre class="brush:html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;

&lt;head&gt;

	&lt;script src="dojo-release-1.3.2/dojo/dojo.js" djConfig="isDebug: true"&gt;&lt;/script&gt;

	&lt;title&gt;Dojo&lt;/title&gt;

	&lt;script type="text/javascript"&gt;

		dojo.require("dojox.charting.Chart2D");

	&lt;/script&gt;

&lt;/head&gt;

&lt;body&gt;

	&lt;h1&gt;Dojox.charting&lt;/h1&gt;

	&lt;div id="charting" style="width:400px; height:250px;"&gt;

	&lt;/div&gt;

	&lt;script type="text/javascript"&gt;

		（function charting(){

			dojo.empty("charting");

			var data = [2,4,5,7,7,4,4,7];

			var chart = new dojox.charting.Chart2D("charting");

			chart.addPlot("default", {type: "Lines", markers: true, shadows: {dw:2, dy:2, dx:2}});

			chart.addPlot("additional", {type: "Areas"});

			chart.addPlot("other", {type: "ClusteredColumns"})

			chart.addAxis("x");

			chart.addAxis("y", {vertical: true, min:0, max: 10});

			chart.addSeries("Testing Data 1", data);

			chart.addSeries("Average", dojo.map(data, function(it){return (Math.sin(it)+Math.random())*4}), {

				fill: "rgba(145, 213, 100, 0.5)", plot:"additional"

			});

			chart.addSeries("All Random", dojo.map(data, function(it){return (Math.random()*5)}), {

				plot: "other", fill: "#AEC6E2", stroke:{color:"#333", width:1}

			});

			chart.render();

		}）();

	&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;</pre>
<h3>Protovis</h3>
<p>(BSD License)<br />
<a href="http://vis.stanford.edu/protovis/" target="_blank">Protovis</a>，看域名就知道是斯坦福的项目，Protovis是在prototype基础上，不仅有charting的功能，还包括其他可视化方法的实现。看了Demo会发现它真的很不错，不过Protovis的本意似乎只是借助浏览器的图形功能实现可视化，它并没有提供超出可视化范畴的其他东西，甚至看过实例的代码你会发现，作者是不折不扣的函数式编程和链式方法的爱好者，Protovis的代码算是出神入化了，如果你想看看学术的JavaScript是如何写的，那就看看这个发表了论文的JavaScript库吧。<br />
看起来是很出神入化，可是这样的编程接口如果要用在动态的数据展现上，实在是难以想象。况且，pv.Panel居然没有传入dom对象的接口，无从控制chart的生成位置。说白了，Protovis并不是一个满足项目需要的Library，不过它的功能和代码还是值得多看几眼的。</p>
<pre class="brush:html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;

&lt;head&gt;

	&lt;script src="protovis-3.1/protovis-r3.1.js"&gt;&lt;/script&gt;

&lt;/head&gt;

&lt;body&gt;

	&lt;div id="page"&gt;

		&lt;h1&gt;Protovis&lt;/h1&gt;

		&lt;div style="border:1px #CCC solid;"&gt;

			&lt;script type="text/javascript"&gt;

				var width = 300;

				var height = 225;

				var data = [1,2,3,4,6,7,5,8,9];

				var dataMax = data[0];

				for(var i=1; i&lt;data.length; i++){

					dataMax = dataMax &gt; data[i] ? dataMax : data[i];

				}

				var x = pv.Scale.linear(0, dataMax).range(0, width);

				var y = pv.Scale.ordinal(pv.range(10)).splitBanded(0, height, 0.9);

				var charting = new pv.Panel();

				charting.width(width).height(height).left(20).top(20);

				var bar = charting.add(pv.Bar);

				bar.data(data).top(function() y(this.index)).height(y.range().band).left(0).width(x);

				bar.anchor("center").add(pv.Label)

					.textStyle("white")

					.textAlign("center")

					.text(function(d) d.toFixed(1));

				charting.add(pv.Rule)

					.data(x.ticks())

					.left(function(d) Math.round(x(d)) - .5)

					.strokeStyle(function(d) d ? "rgba(255,255,255,.3)" : "#000")

				.add(pv.Rule)

					.bottom(0)

					.height(5)

					.strokeStyle("#C00")

					.anchor("bottom").add(pv.Label)

					.text(function(d) d);

				charting.render();

			&lt;/script&gt;

		&lt;/div&gt;

	&lt;/div&gt;

&lt;/body&gt;

&lt;/html&gt;</pre>
<h3>Flotr</h3>
<p>(MIT License)<br />
不同于上面两个库通过SVG绘图，<a href="http://solutoire.com/category/flotr/" target="_blank">Flotr</a>是用canvas api来完成绘制的。与dojo相比，Flotr的API比较简单，主要只有一个Flotr.draw方法，可以传入三个参数：目标DOM对象，数据和绘图选项。和dojo不同，Flotr的文档里关于这个option object有非常明确的文档。另外强大的是Flotr还提供了一些交互的功能，支持事件绑定。</p>
<pre class="brush:html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;

&lt;head&gt;

	&lt;script src="flotr-0.2.0-alpha/flotr/lib/prototype-1.6.0.2.js"&gt;&lt;/script&gt;

	&lt;script src="flotr-0.2.0-alpha/flotr/flotr-0.2.0-alpha.js"&gt;&lt;/script&gt;

	&lt;title&gt;Flotr&lt;/title&gt;

	&lt;script type="text/javascript"&gt;

	&lt;/script&gt;

&lt;/head&gt;

&lt;body&gt;

	&lt;h1&gt;Flotr&lt;/h1&gt;

	&lt;div id="charting" style="width:400px; height:250px;"&gt;

	&lt;/div&gt;

	&lt;script type="text/javascript"&gt;

		(function d(){

			var series1 = {data: (function(){

				var d = [];

				for(var i=0; i&lt;10; i+=0.5){

					d.push([i, Math.random()-0.5+Math.sin(i)*3]);

				}

				return d;

			})(), label:"d1", lines:{fill:true}};

			var series2 = {data: (function(){

				var d = [];

				for(var i=0; i&lt;10; i+=0.5){

					d.push([i, (Math.random()-0.5)*2+Math.cos(i)*2]);

				}

				return d;

			})(), label: "s2", points:{show:true}, lines:{show:true}};

			Flotr.draw($("charting"), [series1, series2], {

				legend: {position :"se"},

				yaxis: {min: -3.5, max:3.5}

			});

		})();

	&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;</pre>
<p>此外，还有一个与Flotr很类似的<a href="http://www.deensoft.com/lab/protochart/">Protochart</a>，后者的网站上写自己是motivated by Flotr，不过我很难想象功能几乎相同是如何motivated的。</p>
<h3>MilkChart</h3>
<p>(Apache License 2.0)<br />
<a href="http://code.google.com/p/milkchart/">MilkChart</a>是一个建立在Mootools上的Charting库，采用canvas api绘图。它的特点是把HTML Table的数据绘制成Chart，如果场景允许，倒是省去了不少构建数组的麻烦。这个项目目前规模还不大，并且在作者积极的开发中，可以参考它的Wiki获得详细的使用方法。</p>
]]></content:encoded>
			<wfw:commentRss>http://sunng.info/blog/2009/10/open-source-charting-libraries-in-javascript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
