使用canvas和svg繪圖之 ?? image2D

作者:心葉

時(shí)間:2019-05-29 22:31

> 本文檔非最新版本,會(huì)有更新延遲,如果希望獲取最新版本,請(qǐng)直接訪問(wèn)接口文檔

一.簡(jiǎn)介

==========

首先,讓我們來(lái)了解一個(gè)這個(gè)庫(kù)主要解決的問(wèn)題是什么,如何使用以及問(wèn)題反饋等基本信息。


Example | image2D

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容