作者:心葉
時(shí)間:2019-05-29 22:31
> 本文檔非最新版本,會(huì)有更新延遲,如果希望獲取最新版本,請(qǐng)直接訪問(wèn)接口文檔
一.簡(jiǎn)介
==========
首先,讓我們來(lái)了解一個(gè)這個(gè)庫(kù)主要解決的問(wèn)題是什么,如何使用以及問(wèn)題反饋等基本信息。

1.1 關(guān)注的問(wèn)題
----------
本庫(kù)致力于提供更簡(jiǎn)單的Web端二維繪圖接口,主要包括這些方面:畫(huà)筆、輔助計(jì)算、結(jié)點(diǎn)操作和一些零碎的小工具方法。我們希望繪圖是簡(jiǎn)單而有趣的、高效而愉悅的!
主要是在svg和canvas2D上繪圖,雖然有提供比如Maritx4坐標(biāo)變換等三維相關(guān)方法,這是考慮到一些潛在的需求。
1.2 如何使用
-----
如果你開(kāi)發(fā)的是一個(gè)web項(xiàng)目,直接在頁(yè)面引入打包后的文件后即可(在代碼中通過(guò)image2D或$$調(diào)用):
? ? <script src="https://cdn.jsdelivr.net/npm/image2d@1.4.10/build/image2D.min.js"></script>
如果你想通過(guò)npm方式管理,首先你需要通過(guò)命令行安裝image2D,就像這樣:
? ? npm install --save image2d
安裝好了以后,在需要的地方引入即可:
? ? import $$ from 'image2d';
1.3 獲取幫助
----
在使用image2D的時(shí)候,如果遇到任何疑惑或問(wèn)題,包括建議或?qū)ξ磥?lái)版本的想法,請(qǐng)先在 [Github issue][1] 上查找是否存在相似內(nèi)容,然后進(jìn)行補(bǔ)充或追問(wèn),當(dāng)然也可以增加新的話題進(jìn)行交流,除非特殊情況,你會(huì)在48小時(shí)內(nèi)獲得 作者 回復(fù)。
二.結(jié)點(diǎn)操作
====
為了繪圖的方便,我們提供了最基本的結(jié)點(diǎn)相關(guān)操作。因?yàn)檫@些操作是為了繪圖而開(kāi)發(fā)的,可能和純粹的結(jié)點(diǎn)操作方法在設(shè)計(jì)上有所不同,請(qǐng)知悉。
2.1 結(jié)點(diǎn)對(duì)象
---
所有的結(jié)點(diǎn)操作都是由結(jié)點(diǎn)對(duì)象提供的,因此,我們首先來(lái)看看如何創(chuàng)建一個(gè)結(jié)點(diǎn)對(duì)象:
? ? var imageObject=$$(selector[, context]);
如上所示,通過(guò)執(zhí)行$$或image2D方法即可獲取一個(gè)結(jié)點(diǎn)對(duì)象,我們可以傳遞二個(gè)參數(shù)來(lái)確定當(dāng)前結(jié)點(diǎn)對(duì)象維護(hù)的結(jié)點(diǎn)是哪些。
結(jié)點(diǎn)對(duì)象維護(hù)了一些結(jié)點(diǎn),調(diào)用結(jié)點(diǎn)對(duì)象上的方法,就是對(duì)維護(hù)的這些結(jié)點(diǎn)進(jìn)行操作。
第一個(gè)參數(shù)selector(稱(chēng)為選擇器)是必須的,用以確定當(dāng)前維護(hù)的結(jié)點(diǎn)是哪些。
第二個(gè)參數(shù)context是可選的,默認(rèn)選擇器在全局查找,你也可以通過(guò)傳遞一個(gè)dom結(jié)點(diǎn)指定查找上下文(id選擇器會(huì)忽略此參數(shù)直接在全局查找)。
選擇器
任何合法的選擇器都應(yīng)該是下列中的某一種:
- 模板字符串,比如'<g>'、'<canvas>非常抱歉,您的瀏覽器不支持canvas!</canvas>'等。
- ID選擇器,比如'#demo'會(huì)選中id是'demo'的第一個(gè)標(biāo)簽。
- class和標(biāo)簽選擇器,比如'.cls'、'div'、'div.cls'和'g.info.warn'等。
- 全部選擇器,也就是字符串'*',會(huì)選中全部結(jié)點(diǎn)。
- 非查詢選擇器,包括:結(jié)點(diǎn),結(jié)點(diǎn)數(shù)組和結(jié)點(diǎn)對(duì)象。這類(lèi)選擇器不會(huì)進(jìn)行查找,直接把傳遞的結(jié)點(diǎn)作為維護(hù)結(jié)點(diǎn),因此也會(huì)忽略查找上下文。
- 篩選函數(shù),傳遞一個(gè)函數(shù),函數(shù)形參是當(dāng)前面對(duì)的結(jié)點(diǎn),通過(guò)返回true或false來(lái)判斷是否把當(dāng)前面對(duì)的結(jié)點(diǎn)加入結(jié)點(diǎn)對(duì)象中。
創(chuàng)建好了結(jié)點(diǎn)對(duì)象以后,后續(xù)依舊可以對(duì)維護(hù)的結(jié)點(diǎn)進(jìn)行篩選后獲取新的結(jié)點(diǎn)對(duì)象:
? ? var new_imageObject=imageObject.filter(filterback);
返回新的結(jié)點(diǎn)對(duì)象,不會(huì)修改原來(lái)的結(jié)點(diǎn)對(duì)象。其中filterback叫做篩選函數(shù),有二個(gè)形參,分別是當(dāng)前面對(duì)結(jié)點(diǎn)序號(hào)和維護(hù)了當(dāng)前面對(duì)結(jié)點(diǎn)的結(jié)點(diǎn)對(duì)象,通過(guò)返回true或false來(lái)判斷是否把當(dāng)前面對(duì)的結(jié)點(diǎn)加入新創(chuàng)建的結(jié)點(diǎn)對(duì)象中。
2.2 編輯
--
把當(dāng)前維護(hù)的結(jié)點(diǎn)加到目標(biāo)結(jié)點(diǎn)內(nèi)部的結(jié)尾:
? ? imageObject.appendTo(target[, context]);
target是一個(gè)合法的選擇器即可,context是一個(gè)結(jié)點(diǎn),表示目標(biāo)結(jié)點(diǎn)查找上下文,可選,默認(rèn)全局查找,下同。
把當(dāng)前維護(hù)的結(jié)點(diǎn)加到目標(biāo)結(jié)點(diǎn)內(nèi)部的開(kāi)頭:
? ? imageObject.prependTo(target[, context]);
把當(dāng)前維護(hù)的結(jié)點(diǎn)加到目標(biāo)結(jié)點(diǎn)之后:
? ? imageObject.afterTo(target[, context]);
把當(dāng)前維護(hù)的結(jié)點(diǎn)加到目標(biāo)結(jié)點(diǎn)之前:
? ? imageObject.beforeTo(target[, context]);
從頁(yè)面中刪除當(dāng)前維護(hù)的結(jié)點(diǎn):
? ? imageObject.remove();
設(shè)置或獲取結(jié)點(diǎn)中的文本:
? ? imageObject.text([content]);
2.3 樣式和屬性
---
修改或獲取結(jié)點(diǎn)樣式:
? ? imageObject.css();
通過(guò)不同的參數(shù)來(lái)確定是獲取樣式還是設(shè)置樣式,具體有下列參數(shù)選項(xiàng)可選:
- (key):獲取指定樣式。
- (key,value):設(shè)置指定樣式。
- ():獲取全部樣式。
- (json):設(shè)置大量樣式。
設(shè)置或獲取結(jié)點(diǎn)屬性:
? ? imageObject.attr();
和樣式css方法類(lèi)似,也是通過(guò)具體參數(shù)來(lái)確定是獲取還是設(shè)置樣式:
- (attr):獲取屬性。
- (attr,value):設(shè)置指定屬性值。
- (json):設(shè)置大量屬性。
2.4 事件相關(guān)
----
給維護(hù)的結(jié)點(diǎn)綁定事件:
? ? imageObject.bind(eventType, callback);
獲取鼠標(biāo)相對(duì)當(dāng)前維護(hù)的元素左上角位置:
? ? imageObject.position(event);
2.5 數(shù)據(jù)綁定
繪圖就離不開(kāi)數(shù)據(jù),把數(shù)據(jù)和結(jié)點(diǎn)關(guān)聯(lián)起來(lái),會(huì)簡(jiǎn)化結(jié)點(diǎn)管理和數(shù)據(jù)保存問(wèn)題,這里涉及四個(gè)核心方法:data、datum、enter和exit,還有一些相關(guān)方法(因?yàn)榻Y(jié)點(diǎn)對(duì)象的各個(gè)方法之間不完全是獨(dú)立的)。
把數(shù)據(jù)綁定到一組結(jié)點(diǎn)或返回第一個(gè)結(jié)點(diǎn)數(shù)據(jù):
? ? imageObject.datum();
通過(guò)具體的參數(shù)來(lái)判斷是獲取還是綁定,有下列參數(shù)選項(xiàng)可選:
- ():不帶任何參數(shù)表示獲取數(shù)據(jù)。
- (data):帶一個(gè)參數(shù)表示設(shè)置結(jié)點(diǎn)對(duì)象維護(hù)的全部結(jié)點(diǎn)數(shù)據(jù)為data。
- (data, calcback):和帶一個(gè)參數(shù)類(lèi)似,只不過(guò)綁定的數(shù)據(jù)是經(jīng)過(guò)calcback函數(shù)重新計(jì)算后返回的值,該函數(shù)有二個(gè)形參:data和index。
把一組數(shù)據(jù)綁定到一組結(jié)點(diǎn)或返回一組結(jié)點(diǎn)數(shù)據(jù):
? ? var update=imageObject.data();
和上面的datum方法類(lèi)似,只不過(guò)這是對(duì)一組數(shù)據(jù)進(jìn)行操作,也就是data變成了數(shù)組datas,不再贅述了。
不過(guò),你可能已經(jīng)注意到了,data方法因?yàn)椴僮鞯氖菙?shù)組,數(shù)組個(gè)數(shù)和維護(hù)的結(jié)點(diǎn)個(gè)數(shù)不一定一樣多,這樣就存在平衡問(wèn)題。
如右圖所示,我們把數(shù)據(jù)和結(jié)點(diǎn)匹配的部分稱(chēng)為update(剛剛好平衡,已經(jīng)更新好了的意思),數(shù)據(jù)多于結(jié)點(diǎn)的部分稱(chēng)為enter(因?yàn)槔L圖是根據(jù)數(shù)據(jù)來(lái)的,數(shù)據(jù)多了,應(yīng)該添加結(jié)點(diǎn)來(lái)維持平衡),結(jié)點(diǎn)多于數(shù)據(jù)的部分稱(chēng)為exit(多余的結(jié)點(diǎn)刪除即可)。
? ? var enter=update.enter(template);
如果數(shù)據(jù)多于結(jié)點(diǎn),調(diào)用enter方法,傳遞一個(gè)參數(shù)template(模板字符串,類(lèi)似'<path>'、'text'等)來(lái)把多余的數(shù)據(jù)綁定到新建立的結(jié)點(diǎn)上去,后續(xù)通過(guò)之前的常規(guī)結(jié)點(diǎn)操作追加到頁(yè)面的合適位置去即可。
? ? var exit=update.exit();
如果結(jié)點(diǎn)多于數(shù)據(jù),調(diào)用exit方法獲取多余的結(jié)點(diǎn),然后再調(diào)用remove方法刪除即可。
通過(guò)上面的四個(gè)主要方法,可以把數(shù)據(jù)和結(jié)點(diǎn)綁定起來(lái),接下來(lái)需要思考的是如何把數(shù)據(jù)的繪圖方法作用到具體的結(jié)點(diǎn)上去:
? ? imageObject.loop(function(data,index,target){
? ? ? ? // 繪制圖像
? ? ? ? // data是當(dāng)前結(jié)點(diǎn)對(duì)象target維護(hù)的數(shù)據(jù),index是當(dāng)前結(jié)點(diǎn)對(duì)象序號(hào)
? ? });
loop方法會(huì)把傳遞的繪圖方法在當(dāng)前結(jié)點(diǎn)對(duì)象維護(hù)的每一個(gè)結(jié)點(diǎn)上應(yīng)用一次,具體的繪圖方法可以根據(jù)當(dāng)前面對(duì)的結(jié)點(diǎn)綁定的數(shù)據(jù)來(lái)繪制。
三.畫(huà)筆
===
畫(huà)筆是image2D的主體部分,根據(jù)當(dāng)前綁定的結(jié)點(diǎn)不同,獲取的是不同類(lèi)型的畫(huà)筆,目前支持svg和canvas2D畫(huà)筆:
? ? var painter=imageObject.painter();
如果維護(hù)的第一個(gè)結(jié)點(diǎn)是canvas,返回的就是專(zhuān)門(mén)在canvas上繪圖的位圖畫(huà)筆,svg等別的類(lèi)似。
獲取畫(huà)筆后就可以調(diào)用painter上的方法進(jìn)行繪圖了,不過(guò)在這之前,你還可以對(duì)畫(huà)筆進(jìn)行屬性(文字大小,顏色等)配置:
? ? painter.config();
考慮到屬性設(shè)置可能有多個(gè)或單個(gè),為了方便,提供二種參數(shù)類(lèi)型:
- (json):一次配置多個(gè)屬性,鍵值對(duì)的方式。
- (key, value):對(duì)屬性key設(shè)置為value。
不同畫(huà)筆的使用大體和上面的類(lèi)似,差異的部分會(huì)在具體的繪圖工具下說(shuō)明,下面我們來(lái)看看畫(huà)筆可配置屬性有哪些:
- "fillStyle":填充色或圖案,默認(rèn)"#000"。
- "strokeStyle":輪廓色或圖案,默認(rèn)"#000"。
- "lineWidth":線條寬度,默認(rèn)1(單位px,下同)。
- "textAlign":文字水平對(duì)齊方式,默認(rèn)"left"左對(duì)齊(還有"center"居中和"right"右對(duì)齊)。
- "textBaseline":文字垂直對(duì)齊方式,默認(rèn)"middle"垂直居中(還有"top"上對(duì)齊和"bottom"下對(duì)齊)。
- "font-size":文字大小,默認(rèn)16。
- "font-family":字體,默認(rèn)"sans-serif"。
- "arc-start-cap":圓弧開(kāi)始端閉合方式,默認(rèn)'butt'直線閉合(還有'round'圓帽閉合)。
- "arc-end-cap":圓弧結(jié)束端閉合方式,和上一個(gè)類(lèi)似。
3.1 位圖canvas2D
---
除了上面列出的可配置項(xiàng),因?yàn)閏anvas2D的配置是直接連原始畫(huà)筆的(不是全部),因此其自身的2d畫(huà)筆原來(lái)可配置的屬性依舊可以配置,請(qǐng)知悉。這種繪圖方法相對(duì)比較簡(jiǎn)單,下面我們來(lái)看看其特有的一些繪圖方法。
原始畫(huà)筆的意思是2d上下文,不是我們抽象的painter,因此不同的painter如果管理的是同一個(gè)canvas,屬性配置不是完全獨(dú)立的(后面要說(shuō)明的svg就是獨(dú)立的)。
把當(dāng)前繪制的圖形變成base64返回:
? ? var base64=painter.toDataURL();
擦除畫(huà)布上正方形大小是width*height的區(qū)域(正方形左上角坐標(biāo)(x, y)),x和y默認(rèn)0,width和height默認(rèn)就是畫(huà)布的尺寸,都是可選的:
? ? painter.clearn(x, y, width, height);
把圖像、畫(huà)布或視頻繪制到畫(huà)布的指定位置上:
? ? painter.drawImage();
- (img, x, y):在畫(huà)布上定位圖像。
- (img, x, y, width, height):在畫(huà)布上定位圖像,并規(guī)定圖像的寬度和高度。
- (img, sx, sy, swidth, sheight, x, y, width, height):剪切圖像,并在畫(huà)布上定位被剪切的部分。
3.2 矢圖svg
---
這種畫(huà)筆比較特殊,畫(huà)筆是綁定在維護(hù)了svg結(jié)點(diǎn)的結(jié)點(diǎn)對(duì)象上的,不過(guò)具體的繪制(比如文字是text標(biāo)簽)卻需要對(duì)應(yīng)更具體的標(biāo)簽,因此獲取painter方法的時(shí)候可以傳遞一個(gè)選擇器selector來(lái)綁定本次繪制目標(biāo)(可選):
? ? var painter=imageObject.painter(selector);
因?yàn)槔L制文字、圓形和圓弧等對(duì)應(yīng)的目標(biāo)標(biāo)簽不一樣,并且一個(gè)結(jié)點(diǎn)只可以繪制一個(gè)圖形,因此在每次繪制前都需要明確目標(biāo)結(jié)點(diǎn):
? ? painter.bind(selector);
大部分情況下,selector應(yīng)該都是模板字符串,比如"<text>",繪制結(jié)束需要追加到svg中去,我們提供了四種追加方法:
? ? painter.appendTo|prependTo|afterTo|beforeTo(selector);
因?yàn)橄拗屏瞬檎疑舷挛氖谦@取畫(huà)筆的svg,只需要傳遞一個(gè)參數(shù),具體方法和前面常規(guī)的結(jié)點(diǎn)操作一樣。
鑒于svg繪圖的特殊性,下面以繪制文字舉一個(gè)例子來(lái)看看繪制整體代碼:
? ? // 獲取畫(huà)筆
? ? var painter=$$('svg').painter('<text>');
? ? // 配置畫(huà)筆
? ? painter.config({
? ? ? ? "fillStyle":"red",
? ? ? ? "font-size":30
? ? });
? ? // 繪制文字并追加到畫(huà)布
? ? painter.fillText('Step By Step', 100, 100).appendTo('g.text');
3.3 通用繪圖方法
---
上面說(shuō)明的繪圖方法都是具體畫(huà)筆特有的(因?yàn)椴煌漠?huà)筆存在差異),除此之外,大部分方法是通用的。
在點(diǎn)(x, y)處繪制填充的文字text;deg表示文字旋轉(zhuǎn)角度,可選:
? ? painter.fillText(text, x, y[, deg]);
在點(diǎn)(x, y)處繪制輪廓的文字text;deg表示文字旋轉(zhuǎn)角度,可選:
? ? painter.strokeText(text, x, y[, deg]);
以(cx, cy)為圓心,內(nèi)外半徑分別是r1和r2,從弧度beginDeg開(kāi)始,跨越弧度deg,繪制填充圓弧:
? ? painter.fillArc(cx, cy, r1, r2, beginDeg, deg);
除非特別說(shuō)明,角度全部采用弧度值,這是為了方便記憶,別的地方一樣。
和fillArc方法類(lèi)似,只不過(guò)繪制的是輪廓圓弧:
? ? painter.strokeArc(cx, cy, r1, r2, beginDeg, deg);
以(cx, cy)為圓心,半徑r繪制填充圓形:
? ? painter.fillCircle(cx, cy, r);
以(cx, cy)為圓心,半徑r繪制輪廓圓形:
? ? painter.strokeCircle(cx, cy, r);
路徑
基于路徑可以繪制幾乎大部分圖形,這里獨(dú)立一小段來(lái)說(shuō)明。
開(kāi)始一段獨(dú)立的路徑:
? ? painter.beginPath();
閉合當(dāng)前路徑,也就是路徑首尾閉合:
? ? painter.closePath();
畫(huà)筆移動(dòng)到點(diǎn)(x, y),此時(shí)筆離開(kāi)了畫(huà)布:
? ? painter.moveTo(x, y);
畫(huà)筆移動(dòng)到點(diǎn)(x, y),此時(shí)筆沒(méi)有離開(kāi)畫(huà)布:
? ? painter.lineTo(x, y);
把當(dāng)前路徑包裹的區(qū)域填充顏色:
? ? painter.fill();
把當(dāng)前路徑上色(輪廓線):
? ? painter.stroke();
四.計(jì)算
===
繪圖的時(shí)候難免要進(jìn)行一些比較復(fù)雜的計(jì)算,這里根據(jù)使用場(chǎng)景不同,提供了幾種常見(jiàn)的輔助計(jì)算。
4.1 二維簡(jiǎn)單坐標(biāo)變換
---
簡(jiǎn)單坐標(biāo)變換分為二類(lèi):獨(dú)立的變換和變換對(duì)象dot。
- 獨(dú)立的變換
點(diǎn)(x,y)圍繞中心(cx,cy)旋轉(zhuǎn)deg度:
? ? $$.rotate(cx, cy, deg, x, y);
點(diǎn)(x,y)沿著向量(ax,ay)方向移動(dòng)距離d:
? ? $$.move(ax, ay, d, x, y);
點(diǎn)(x,y)圍繞中心(cx,cy)縮放times倍:
? ? $$.scale(cx, cy, times, x, y);
- 變換對(duì)象
dot表示一個(gè)會(huì)移動(dòng)的二維點(diǎn),內(nèi)部維護(hù)著「前進(jìn)方向向量」、「當(dāng)前位置」和「中心坐標(biāo)」。首先,我們來(lái)看看如何獲取一個(gè)dot實(shí)例:
? ? var dot=$$.dot({
? ? ? ? // 前進(jìn)方向、中心坐標(biāo)和當(dāng)前位置(都可選,下列是缺省值)
? ? ? ? d: [1, 1],c: [0, 0],p: [0, 0]
? ? });
下列是一些變換方法,通過(guò)這些方法可以控制點(diǎn)dot的坐標(biāo)改變或獲取當(dāng)前坐標(biāo)。
前進(jìn)方向以當(dāng)前位置為中心,旋轉(zhuǎn)deg度(注意,改變的是前進(jìn)方向,不是當(dāng)前坐標(biāo)):
? ? dot.rotate(deg);
沿著當(dāng)前前進(jìn)方向前進(jìn)d:
? ? dot.move(d);
圍繞中心坐標(biāo)縮放:
? ? dot.scale(times);
返回當(dāng)前位置:
? ? var p=dot.value();
4.2 Matrix4三維坐標(biāo)變換
-----
Matrix4是一個(gè)列主序存儲(chǔ)的4x4矩陣,使用該矩陣對(duì)象的第一步是像下面這樣獲取該對(duì)象,參數(shù)initMatrix4可選,你可以傳遞一個(gè)初始化矩陣或默認(rèn)采用單位矩陣E初始化。
? ? var matrix4=$$.Matrix4(initMatrix4);
和前面的二維坐標(biāo)變換不同的是,變換不是直接作用在具體的點(diǎn)上,而是先求解出一系列變換的變換矩陣,最后應(yīng)用在具體點(diǎn)上。
- 基本運(yùn)算
返回matrix4當(dāng)前記錄的內(nèi)部矩陣:
? ? var val=matrix4.value();
比如采用默認(rèn)值初始化的矩陣對(duì)象,打印結(jié)果如下:
? ? (16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
二個(gè)矩陣相乘:
? ? matrix4.multiply(newMatrix4[, flag]);
第一個(gè)參數(shù)應(yīng)該是一個(gè)和value打印出來(lái)一樣格式的一維數(shù)組,列主序存儲(chǔ)。flag默認(rèn)false,可不傳,表示左乘,即newMatrix4 × matrix4,如果設(shè)置flag為true,表示右乘。
把變換矩陣作用在具體的點(diǎn)上:
? ? var position=matrix4.use(x, y, z, w);
矩陣的目的是對(duì)坐標(biāo)進(jìn)行變換,use方法返回齊次坐標(biāo)(x, y, z, w)經(jīng)過(guò)matrix4矩陣變換后的坐標(biāo)值。其中z和w可以不傳遞,默認(rèn)0和1,返回的坐標(biāo)值是一個(gè)齊次坐標(biāo)。
- 坐標(biāo)變換
沿著向量(a, b, c)方向移動(dòng)距離dis(其中c可以不傳,默認(rèn)0):
? ? matrix4.move(dis, a, b, c);
以點(diǎn)(cx, cy, cz)為中心,分別在x、y和z方向上縮放xTimes、yTimes和zTimes倍(其中cx、cy和cz都可以不傳遞,默認(rèn)0):
? ? matrix4.scale(xTimes, yTimes, zTimes, cx, cy, cz);
圍繞射線(a1, b1, c1) -> (a2, b2, c2)旋轉(zhuǎn)deg度(方向由右手法則確定):
? ? matrix4.rotate(deg, a1, b1, c1, a2, b2, c2);
a1、b1、c1、a2、b2和c2這6個(gè)值在設(shè)置的時(shí)候,不是一定需要全部設(shè)置,還有以下可選:
只設(shè)置了a1和b1,表示在xoy平面圍繞(a1, b1)旋轉(zhuǎn)。
只設(shè)置三個(gè)點(diǎn)(設(shè)置不足六個(gè)點(diǎn)都認(rèn)為只設(shè)置了三個(gè)點(diǎn)),表示圍繞從原點(diǎn)出發(fā)的射線旋轉(zhuǎn)。
4.3 曲線插值
----
給定若干個(gè)不連續(xù)的點(diǎn),在這些點(diǎn)之間插入足夠的點(diǎn),來(lái)使得這些點(diǎn)連接起來(lái)是一個(gè)平滑的曲線。求解在何處插入新的點(diǎn)就是這里需要解決的問(wèn)題。
- Cardinal
用一個(gè)N次多項(xiàng)式函數(shù)求解出若干個(gè)點(diǎn)的插值函數(shù)是一個(gè)可行的方法,不過(guò)在一次插值中,當(dāng)插值點(diǎn)數(shù)量增加的時(shí)候,N越來(lái)越大,很容易帶來(lái)收斂困難,也就是Runge現(xiàn)象。
因此在這里,我們內(nèi)部選擇三次插值Hermite法(N=3)。在需要插值的點(diǎn)的個(gè)數(shù)比較多的時(shí)候,選擇分段求解,也就是Cardinal插值法。
首先,我們需要獲取插值對(duì)象實(shí)例:
? ? var cardinal=$$.cardinal();
設(shè)置張弛系數(shù)(應(yīng)該在點(diǎn)的位置設(shè)置前設(shè)置):
? ? cardinal.setT(t);
該參數(shù)用于調(diào)整曲線走勢(shì),默認(rèn)數(shù)值t=0,分水嶺t=-1,|t-(-1)|的值越大,曲線走勢(shì)調(diào)整的越嚴(yán)重。
設(shè)置點(diǎn)的位置:
? ? cardinal.setP([[x,y],[x,y],...]);
經(jīng)過(guò)上面的設(shè)置,插值對(duì)象就可以求值了。比如x=a,其中a在需要插值的點(diǎn)之間(邊界也可以),你可以這樣求解出y值:
? ? var y=cardinal(a);
4.4 布局
----
在繪制一些常見(jiàn)圖形的時(shí)候,比如關(guān)系圖,單個(gè)結(jié)點(diǎn)或連線并不難,麻煩的是位置的計(jì)算等,和圖形模塊不同,布局就是專(zhuān)門(mén)計(jì)算一些特殊圖形位置的模塊,用一句通俗的話說(shuō)就是:決定什么元素繪制在哪里。因此,布局應(yīng)該和具體的繪圖方法無(wú)關(guān),她只關(guān)心位置的計(jì)算。
- 樹(shù)布局
調(diào)用treeLayout方法,傳遞配置config(后續(xù)也可以提供config方法來(lái)修改配置)就可以獲取樹(shù)布局實(shí)例:
? ? var treeLayout=$$.treeLayout(config);
config是一個(gè)鍵值對(duì)格式的配置json,由于原始數(shù)據(jù)格式不一定,你需要傳遞數(shù)據(jù)格式的配置(必須的,可選部分在后面說(shuō)明):
? ? "root":function(initTree){ /*返回根結(jié)點(diǎn)*/ }
? ? "id":function(treedata){ /*返回id*/ }
? ? "child":function(parentTree, initTree){ /*返回孩子結(jié)點(diǎn)*/ }
你還必須配置繪圖方法,因?yàn)椴季植⒉恢廊绾卫L制:
? ? treeLayout.drawer(function(data){ /*繪制*/ });
data是計(jì)算后帶有結(jié)點(diǎn)坐標(biāo)的數(shù)據(jù),格式如下:
? ? {node: {
? ? ? ? "XXX":{
? ? ? ? ? ? children: []
? ? ? ? ? ? data: any
? ? ? ? ? ? id: string||number
? ? ? ? ? ? left: number
? ? ? ? ? ? pid: any
? ? ? ? ? ? top: number
? ? ? ? },
? ? ? ? ...
? ? }, root: string||number, size: number, deep: number}
node記錄的是每個(gè)結(jié)點(diǎn)的信息,每個(gè)結(jié)點(diǎn)中的left和top就是該結(jié)點(diǎn)應(yīng)該繪制的位置,data是結(jié)點(diǎn)的原始數(shù)據(jù)。
上面說(shuō)明的都配置好以后,就可以啟動(dòng)布局計(jì)算并繪圖了:
? ? treeLayout(data);
- 基本模型
可能你已經(jīng)發(fā)現(xiàn)了,樹(shù)圖分為很多種(圓形樹(shù),倒樹(shù)等),上面并沒(méi)有配置這些信息(有接口提供配置,稍后說(shuō)明)。是的,沒(méi)有配置的時(shí)候,默認(rèn)選擇的是基本模型,那什么是基本模型?
右圖是某個(gè)具體例子的基本模型,其中每個(gè)紅色矩形都是一個(gè)1x1的正方形,坐標(biāo)原心位于左上角綠色頂點(diǎn)。
? ? "油畫(huà)":{
? ? ? ? children: [];
? ? ? ? data: (2) ["油畫(huà)", "手繪"];
? ? ? ? id: "油畫(huà)";
? ? ? ? left: 1.5;
? ? ? ? pid: "手繪";
? ? ? ? show: true;
? ? ? ? top: 1.5
? ? }
主要看看top和left,和右邊的圖對(duì)應(yīng),是不是很清晰了。tree布局的核心位置計(jì)算就是把每個(gè)結(jié)點(diǎn)看成一個(gè)1x1的正方形,別的具體樹(shù)圖都是從此出發(fā)計(jì)算得出的,這就是基本模型。
- 配置模型
雖然從基本模型出發(fā)計(jì)算具體的樹(shù)圖已經(jīng)很容易了,不過(guò)為了方便,依舊對(duì)常見(jiàn)的樹(shù)圖提供了下列配置選項(xiàng):
? ? type:LR|RL|BT|TB|circle,配置樹(shù)圖的類(lèi)型(默認(rèn)原始模型,會(huì)忽略下列全部設(shè)置)。
? ? width,height:number,設(shè)置樹(shù)圖的寬和高(如果類(lèi)型是LR|RL|BT|TB需要設(shè)置)。
? ? cx,cy:number,設(shè)置圓心(如果類(lèi)型是circle需要設(shè)置)。
? ? radius:number,設(shè)置樹(shù)圖半徑(如果類(lèi)型是circle需要設(shè)置)。
? ? begin-deg,deg:number,開(kāi)始和跨越弧度(可選,如果類(lèi)型是circle設(shè)置該參數(shù)有效)。
五.小工具
===
因?yàn)槔L制的時(shí)候,比如canvas2D沒(méi)有圖層,某個(gè)數(shù)據(jù)改變可能就意味著需要全部重新繪制等,基于這些考慮,在這一章,對(duì)前面進(jìn)行必要的補(bǔ)充。
5.1 圖層
---
首先需要明確,圖層服務(wù)的對(duì)象是canvas2D,svg某種意義上天生具有圖層,不需要額外設(shè)計(jì)。讓我們首先看看如何獲取一個(gè)圖層對(duì)象:
? ? var layer=imageObject.layer();
這里的圖層可以類(lèi)比photoshop的圖層去理解,我們提供了幾個(gè)類(lèi)似的方法來(lái)幫助使用圖層對(duì)象。
圖層對(duì)象管理著圖層,通過(guò)傳遞id可以獲取對(duì)應(yīng)圖層的畫(huà)筆,如果該圖層不存在會(huì)自動(dòng)創(chuàng)建(這里的畫(huà)筆就是canvas2D畫(huà)筆):
? ? var painter=layer.painter(id);
刪除指定圖層:
? ? layer.delete(id);
圖層中的內(nèi)容不會(huì)顯示在畫(huà)布上,為了顯示在畫(huà)布上,需要手動(dòng)更新:
? ? layer.update();
隱藏圖層:
? ? layer.hidden(id);
顯示圖層:
? ? layer.show(id);
? [1]: https://github.com/yelloxing/image2D/issues