最近遇到一個(gè)關(guān)于彩票中獎(jiǎng)號(hào)碼展示的項(xiàng)目,里面有很多技術(shù)難點(diǎn),其中一個(gè)就是把各開(kāi)獎(jiǎng)號(hào)碼之間用線段連接起來(lái),這是一個(gè)典型網(wǎng)頁(yè)圖形繪制問(wèn)題。本文嘗試講解如何利用SVG技術(shù)實(shí)現(xiàn)折線圖形的繪制。
我們知道,在網(wǎng)頁(yè)中繪制圖形有很多種方式,如canvas、svg、vml等都可以繪制優(yōu)秀的矢量圖形。關(guān)于它們的區(qū)別和適用性比較,可以參考這篇文章,本文只介紹如何使用SVG的基礎(chǔ)知識(shí)實(shí)現(xiàn)我的工作目的。
首先分析下問(wèn)題和解決方案
彩票號(hào)碼一般都顯示為圓形的小球,上面疊加一個(gè)阿拉伯?dāng)?shù)字。要把一系列小球用直線連接,需要知道每個(gè)小球的位置信息,然后用SVG的line方法畫出來(lái)。
那么問(wèn)題來(lái)了,我們?cè)趺床拍艿玫叫∏虻奈恢眯畔⒛兀?/strong>
一般計(jì)算機(jī)的坐標(biāo)系都是以左上角為原點(diǎn),橫向?yàn)閄軸,縱向?yàn)閅軸。SVG坐標(biāo)系也不例外。
此外,我們把小球都放表格中,并規(guī)定好單元格的寬高。以表格的左上角為原點(diǎn),就能輕松得到每個(gè)單元格的XY軸信息,進(jìn)而得到圓心的坐標(biāo)。very nice!

但實(shí)際上我們只得到圓心的坐標(biāo)是不行的,因?yàn)镾VG層疊加在表格上面,如果用Line方法把各個(gè)圓心連接起來(lái),會(huì)把小球上面的文字蓋住。所以Line的起止坐標(biāo)必須在小球的邊緣上。而且,除了第一個(gè)和最后一個(gè)小球外,每個(gè)小球都要有一個(gè)畫入點(diǎn)和一個(gè)畫出點(diǎn),分別指向它的上一個(gè)小球和下一個(gè)小球。
那么問(wèn)題又來(lái)了,怎樣才能得到這些出入點(diǎn)的坐標(biāo)呢?
思前想后,終于把這個(gè)問(wèn)題抽象為一道數(shù)學(xué)題:已知一個(gè)圓的圓心坐標(biāo)和半徑,有一條從圓心發(fā)出的直線,假設(shè)直線與X軸的夾角為a,求該直線與圓相交的點(diǎn)的坐標(biāo)。
Oh My Dog!這特么解析幾何??!哥中學(xué)課本早燒香了,咋辦?
zzz...
沒(méi)辦法,自己畫吧。在紙上算啊算,電腦上測(cè)啊測(cè),終于憋出一個(gè)公式!

寫成函數(shù)如下:
<code>
function calcPointCoor(cx,cy,r,alpha){
//cx,cy: coordination of the circle
//r: radius
//alpha: angle of line and X axis
return {x:cx + r * Math.cos(alpha),y:cy + r * Math.sin(alpha)};
}
</code>
其中cx,cy是圓心坐標(biāo),r是半徑,alpha為角度。
然而如何計(jì)算兩個(gè)小球之間的角度呢?
還解析幾何?。∠氲侥X細(xì)胞會(huì)燒壞一坨,還是算了。我考慮用Snap SVG組件的angle方法來(lái)做,這個(gè)方法只要兩個(gè)點(diǎn)的x,y坐標(biāo),就能返回他們之間的夾角。
要注意的是,給angle方法傳入的參數(shù)順序不同,得出的角度會(huì)有差別。經(jīng)過(guò)測(cè)試,angle方法得出的角度值是自X軸正向順時(shí)針增加的。這樣,如果要按解析幾何平面坐標(biāo)系來(lái)計(jì)算的話,就需要加入或減去一個(gè)π周期來(lái)修正。
另外,還需要把角度值轉(zhuǎn)換為弧度值。因?yàn)镴S中Math對(duì)象的正余弦函數(shù)只接受弧度值。
經(jīng)過(guò)上帝測(cè)試,最終得出以下方法:
<pre><code>
//radius , coordinations of current cicle and next circle
var r,cur_cx,cur_cy,next_cx,next_cy;
//.....
var ang = Snap.angle(next_cx,next_cy,cur_cx,cur_cy) * Math.PI / 180;
var coor1 = calcPointCoor(cur_cx,cur_cy,r, ang);
var coor2 = calcPointCoor(next_cx,next_cy,r, ang + Math.PI);
var c = svg.path("M" + coor1.x + " " + coor1.y + "L" + coor2.x + " " + coor2.y).attr({stroke:stroke_color,strokeWidth:1.5});</code></pre>
這里面的coor1是當(dāng)前小球到下一個(gè)小球的畫出點(diǎn)坐標(biāo),coor2是下一個(gè)小球的畫入點(diǎn)坐標(biāo)。
再往下就簡(jiǎn)單了,SVG中有Line方法,只要循環(huán)一下某一組中所有中獎(jiǎng)號(hào)碼的小球圓心坐標(biāo),就可以搞定一切。
但實(shí)際應(yīng)用中,Path方法比Line方法更高效。因?yàn)楫嬕欢尉€條就得用一個(gè)Line,而Path方法只要給出d屬性的M和L值,就可以在一個(gè)標(biāo)簽中實(shí)現(xiàn)N段線條,然后你就能方便地切換顯示了。哈哈!

總結(jié):
- SVG可以像使用其他HTML標(biāo)簽一樣來(lái)靈活生成,不管你是用PHP還是JavaScript。
- 對(duì)CSS有效的標(biāo)簽同樣適用于SVG標(biāo)簽。
- 能用組件或庫(kù)的盡量用,否則死很多腦細(xì)胞
文中涉及的技術(shù)和組件哥也是現(xiàn)學(xué)現(xiàn)賣,歡迎批評(píng)指正。