私はJSの研究を続けていますが、同時に実用的な問題を解決していますが、いくつかの解決策では、少なくとも少しは戻りますが、コミュニティと共有したいという抵抗できない欲求があります。
前回の記事では、d3ライブラリを使用して単純なグラフを作成することについて話しましたが、それを使用してマップを描画することも計画しましたが、d3を試した後、Raphaelとpaper.jsは自転車の建設を避けられないことを認識し、HTML Canvasのレンダリングをやり直しましたこの記事で伝えたい。
実際、カード-これはもちろんあまりにも大声で言われます、なぜなら カード自体は存在しません。 タスクは、座標を使用してポイントオブジェクトと輪郭を描くことです。 輪郭の描画はこの記事の範囲を超えて残し、一般的なタスク(以前は思っていた)、つまり座標によるポイントの描画について説明します。
もちろん、最初に考えたのは、既製の地図作成ライブラリを使用することでした(d3に精通する前から実験していました)。 2つの困難がありました。1つ目-条件付き座標と2つ目-多数のオブジェクト(地図上で1万点まで)。 また、leaflet.jsが条件付き座標に対応している場合、1つのマップのフレームワーク内に膨大な数のポイントを表示すると、他の方向を見る必要があることがわかりました。
次のライブラリはd3でしたが、もう一度地図作成に関連する例を検討した結果、最初に直面するのは速度であることがわかりました。
次は、優れたグラフィックスライブラリであるRaphael.jsで、同じd3と比較して、非常にシンプルで簡単です(使いやすさの点でシンプルです)。 ラファエルでは、私が必要とするほぼすべてのものを実装し、ライブラリ自体が膨大な数のグッズとアメニティを提供しました。少し違うタスクがあれば、ラファエルはそれを使って幸せになりました。 しかし、再び、制限につまずいてpaper.jsを簡単に試したので、純粋なHTMLキャンバスに切り替えました。 確かに、この頃にはすでにほとんどすべてを書き直しており、Raphaelからcanvasに切り替えるために、コード内の最大で12行を置き換える必要がありました。
はじめに、実装に移ります。 マップを効果的に表示するために最初に必要なことは、独自のビューポートを持つことです( ウィキペディアはこの用語をビューポートとして翻訳します。 この用語は3Dに由来し、2Dゲームでも積極的に使用されます。この場合、マップの表示したい部分のみを表示することを意味します。
繰り返しますが、私は車輪を再発明せず、2Dビューポートの既製の抽象的な実装を採用して、それをマップに固定することにしました。 半日かけて積極的に検索を行ったが、適切なものは見つかりませんでした。 しかし今、私は2Dビューポートの完全に抽象的な実装を独自に持っています-フレームワークを使用せずに突然自分のおもちゃを書き始めた場合、それは最初のものです。
これがビューポートの外観です(記事を煩雑にしないために、記事の最後にあるリンクを介してGithubで完全に表示できるコード抽出物を提供します)。
class Viewport { constructor(param) { this.updateCallback = param.update; this.size = param.size; this.map = param.map; ... }; set Center(koordXY) { ... }; get Center() { ... }; set Zoom(zoomXY) { ... }; get Zoom() { ... }; set Size(sizeWH) { ... }; get Size() { ... }; show() { this.vp = { x1: this.vX, x2: this.vX + this.size.w / this.zoom.x, y1: this.vY, y2: this.vY + this.size.h / this.zoom.y, zX: this.zoom.x, zY: this.zoom.y }; this.updateCallback(this.vp); }; caclViewPort() { ... }; calcCenter() { ... }; calcMaxZoom() { ... }; };
原則として、実装は非常にシンプルです。ビューポートクラスのインスタンスが作成され、その中に、そのサイズ、マップの境界座標、コールバック関数が転送され、ビューポートをレンダリングするために呼び出されます。 境界座標はこの関数に転送されます。この関数内では、オブジェクトを表示する必要があり、座標を変換するために各軸の係数によって変換されます。 マップの場合、軸のスケールは一致し、これらの係数は等しくなります。
さて、最も重要で複雑なものが実装されました-それ以外はすべてシンプルです:
class XyMap { constructor(container) { this.container = (typeof container === 'string') ? document.getElementById(container) : container; ... this.objects = []; //{ id: 1, caption: 'Obj1', type: 'circe', x: 0, y: 0, r: 5, color: 'red' } this.viewPort = null; }; //add object for draw to array add(obj) { ... }; init() { let id = this.container.id +'_canvas'; this.container.innerHTML = `<canvas id="${id}" width="${this.container.offsetWidth-1}" height="${this.container.offsetHeight-1}"></canvas>`; this.canvas = document.getElementById(id); this.viewPort = new Viewport({ update: (vp) => { this.drawViewport(vp); }, size: { w: this.container.offsetWidth-1, h: this.container.offsetHeight-1}, map: this.limit, oneZoom: true }); this.handleEvent = function(ev) { switch(ev.type) { case 'mousedown': ... case 'mousemove': ... case 'mouseup': ... case 'wheel': ... } }; ... }; scroll(x, y) { ... }; show() { this.viewPort.show(); }; zoomIn(value) { ... this.viewPort.Zoom = z; this.viewPort.show(); }; zoomOut(value) { ... this.viewPort.Zoom = z; this.viewPort.show(); }; //callback for viewport vp = { x1, x2, y1, y2, zX, zY } drawViewport(vp) { let x,y,obj,objT; let other = this; let ctx = this.canvas.getContext('2d'); let pi2 = Math.PI*2; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.objects.filter(d => dx>=vp.x1 && dx<=vp.x2 && dy>=vp.y1 && dy<=vp.y2).forEach(function(d) { x = (dx - vp.x1) * vp.zX; y = (dy - vp.y1) * vp.zY; if (d.type === 'circe') { ctx.beginPath(); ctx.arc(x,y,dr,0,pi2); ctx.fillStyle = d.color; ctx.fill(); ctx.lineWidth = 0.5; ctx.strokeStyle = 'black'; ctx.stroke(); ctx.fillStyle = 'black'; ctx.font = '8pt arial'; ctx.fillText(d.caption, x-20, y-9); }; }); }; };
XyMapクラスのインスタンスを作成し、addメソッドを使用してオブジェクトを描画用に転送します。その後、ビューポートが作成される初期化を呼び出します。 その後、showメソッドを呼び出します。そして、マップが画面に表示されます。
それだけです。記事の長い紹介とくしゃくしゃの本文をおaびします。 一つの希望は、結果のコードがまだ読みやすく、それ自体を物語っていることです。
プログラム自体と使用例は、 Githubで見ることができます。
UPD。 : JSサンドボックスでのオンライン例。
UPD 2。:コンポーネントを変更しました:レイヤーが表示され、クリックするか、IDによってコンポーネントの外側からオブジェクトを選択できるようになりました。Rツリーは空間検索に使用されます( Jush RBushライブラリ)。