├── .gitignore ├── .npmignore ├── docs ├── img │ ├── intro.png │ ├── miter.png │ ├── round.png │ ├── overdraw.png │ ├── WebGL_Logo.png │ └── stadia-logo.svg ├── overdraw.html ├── basic-demo.html ├── js │ ├── leaflet-hash.js │ ├── L.PixiOverlay.min.js │ └── graph-draw-bundle.min.js ├── data │ └── polyline.json ├── city-mesh.html ├── railways.html ├── polyline.html └── css │ └── leaflet.css ├── LICENSE ├── package.json ├── src ├── tessellator.js └── graph-draw.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | private 3 | dist 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | docs_src 3 | private 4 | -------------------------------------------------------------------------------- /docs/img/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/intro.png -------------------------------------------------------------------------------- /docs/img/miter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/miter.png -------------------------------------------------------------------------------- /docs/img/round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/round.png -------------------------------------------------------------------------------- /docs/img/overdraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/overdraw.png -------------------------------------------------------------------------------- /docs/img/WebGL_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/WebGL_Logo.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Manuel Baclet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/overdraw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Expected overdraw situation 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graph-draw", 3 | "description": "tessellation of planar graphs with convex polygons", 4 | "author": "Manuel Baclet ", 5 | "version": "2.0.4", 6 | "main": "src/graph-draw.js", 7 | "scripts": { 8 | "build:docs": "cp ./node_modules/leaflet-hash/leaflet-hash.js ./docs/js/leaflet-hash.js && cp ./node_modules/leaflet/dist/leaflet.css ./docs/css/leaflet.css && cp ./node_modules/leaflet/dist/leaflet.js ./docs/js/leaflet.js && cp ./node_modules/leaflet-pixi-overlay/L.PixiOverlay.min.js ./docs/js/L.PixiOverlay.min.js && cp ./node_modules/pixi.js/dist/pixi.min.js ./docs/js/pixi.min.js && cp ./dist/graph-draw-bundle.min.js ./docs/js/graph-draw-bundle.min.js", 9 | "build:js": "./node_modules/browserify/bin/cmd.js ./src/graph-draw.js --standalone graphDraw > ./dist/graph-draw-bundle.js && ./node_modules/browserify/bin/cmd.js --external libtess ./src/graph-draw.js --standalone graphDraw > ./dist/graph-draw.js", 10 | "build:uglify": "./node_modules/uglify-js/bin/uglifyjs ./dist/graph-draw-bundle.js -m -c -o ./dist/graph-draw-bundle.min.js && ./node_modules/uglify-js/bin/uglifyjs ./dist/graph-draw.js -m -c -o ./dist/graph-draw.min.js", 11 | "build": "npm run build:js && npm run build:uglify" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/manubb/graph-draw.git" 16 | }, 17 | "keywords": [ 18 | "planar graph", 19 | "tessellation", 20 | "graph", 21 | "polyline" 22 | ], 23 | "license": "MIT", 24 | "homepage": "https://github.com/manubb/graph-draw#readme", 25 | "devDependencies": { 26 | "browserify": "^17.0.1", 27 | "leaflet": "^1.7.1", 28 | "leaflet-hash": "^0.2.1", 29 | "leaflet-pixi-overlay": "^1.5.0", 30 | "pixi.js": "^4.7.3", 31 | "uglify-js": "^3.19.3" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/manubb/graph-draw/issues" 35 | }, 36 | "dependencies": { 37 | "libtess": "^1.2.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/basic-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Basic graph-draw demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/tessellator.js: -------------------------------------------------------------------------------- 1 | // Graph-draw Tesselator 2 | // author: Manuel Baclet 3 | // license: MIT 4 | 5 | 'use strict'; 6 | 7 | var tess; 8 | 9 | try { 10 | tess = require('libtess'); 11 | } catch (e) { 12 | if (typeof window.libtess === 'undefined') { 13 | throw new Error('libtess must be loaded before graph-draw'); 14 | } 15 | tess = window.libtess; 16 | } 17 | 18 | var GluTesselator = tess.GluTesselator, gluEnum = tess.gluEnum, primitiveType = tess.primitiveType; 19 | 20 | var GLU_TESS_BOUNDARY_ONLY = gluEnum.GLU_TESS_BOUNDARY_ONLY; 21 | var GL_LINE_LOOP = tess.primitiveType.GL_LINE_LOOP; 22 | 23 | function Tesselator() { 24 | GluTesselator.call(this); 25 | 26 | this.gluTessNormal(0, 0, 1); 27 | this.gluTessProperty(gluEnum.GLU_TESS_WINDING_RULE, tess.windingRule.GLU_TESS_WINDING_POSITIVE); 28 | 29 | this.gluTessCallback(gluEnum.GLU_TESS_VERTEX_DATA, this._vertex); 30 | this.gluTessCallback(gluEnum.GLU_TESS_BEGIN, this._begin); 31 | this.gluTessCallback(gluEnum.GLU_TESS_END, this._end); 32 | this.gluTessCallback(gluEnum.GLU_TESS_ERROR, this._error); 33 | this.gluTessCallback(gluEnum.GLU_TESS_COMBINE, this._combine); 34 | } 35 | 36 | Tesselator.prototype = Object.create(GluTesselator.prototype); 37 | Tesselator.prototype.constructor = Tesselator; 38 | 39 | Tesselator.prototype.run = function(polygons) { 40 | var self = this; 41 | this.gluTessBeginPolygon(); 42 | polygons.forEach(function(poly) { 43 | self.gluTessBeginContour(); 44 | poly.forEach(function(point) { 45 | self.gluTessVertex([point[0], point[1], 0], point); 46 | }); 47 | self.gluTessEndContour(); 48 | }); 49 | this.gluTessProperty(GLU_TESS_BOUNDARY_ONLY, true); 50 | this._output = []; 51 | this.gluTessEndPolygon(); 52 | 53 | this.gluTessBeginPolygon(); 54 | this._output.forEach(function(poly) { 55 | self.gluTessBeginContour(); 56 | poly.forEach(function(point) { 57 | self.gluTessVertex([point[0], point[1], 0], point); 58 | }); 59 | self.gluTessEndContour(); 60 | }); 61 | this.gluTessProperty(GLU_TESS_BOUNDARY_ONLY, false); 62 | this.gluTessEndPolygon(); 63 | }; 64 | 65 | Tesselator.prototype.clean = function() { 66 | this._accu = []; 67 | this._output = []; 68 | }; 69 | 70 | Tesselator.prototype._begin = function(type) { 71 | this._primitiveType = type; 72 | this._accu = []; 73 | }; 74 | 75 | Tesselator.prototype._vertex = function(v) { 76 | this._accu.push(v); 77 | }; 78 | 79 | Tesselator.prototype._error = function(err) { 80 | console.error('libtess error: ' + err); 81 | }; 82 | 83 | Tesselator.prototype._combine = function(v) { 84 | return [v[0], v[1]]; 85 | }; 86 | 87 | Tesselator.prototype._end = function() { 88 | if (this._primitiveType === GL_LINE_LOOP) { 89 | this._output.push(this._accu); 90 | } else { 91 | for (var i = 0; i < this._accu.length; i += 3) { 92 | this._cb([this._accu[i], this._accu[i+1], this._accu[i+2]]); 93 | } 94 | } 95 | }; 96 | 97 | module.exports = Tesselator; 98 | -------------------------------------------------------------------------------- /docs/img/stadia-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | 11 | 15 | 16 | 17 | 20 | 21 | 22 | 24 | 25 | 26 | 29 | 30 | 31 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | graph-draw 2 | ========== 3 | 4 | A JavaScript library for tessellating undirected planar graphs for Node and browsers. 5 | 6 | [![screenshot](/docs/img/intro.png)](https://manubb.github.io/graph-draw/basic-demo.html) 7 | 8 | The algorithm is designed to avoid local overdraw. A typical non local overdraw (expected) situation: 9 | [![overdraw](/docs/img/overdraw.png)](https://manubb.github.io/graph-draw/overdraw.html) 10 | 11 | The library can be used for example to draw boundaries or polylines on a [Leaflet](http://leafletjs.com) map using [leaflet-pixi-overlay](https://www.npmjs.com/package/leaflet-pixi-overlay) (see the demos). 12 | ## Demo 13 | 14 | A very basic [demo](https://manubb.github.io/graph-draw/basic-demo.html). 15 | 16 | A [polyline](https://manubb.github.io/graph-draw/polyline.html) on a map (173 edges tessellated in 335 triangles). 17 | 18 | French [cities boundaries](https://manubb.github.io/graph-draw/city-mesh.html) (186722 edges tessellated in 769041 triangles). 19 | 20 | A [rail network](https://manubb.github.io/graph-draw/railways.html). 21 | 22 | ## Installation 23 | graph-draw is available as a npm package: 24 | ``` 25 | npm install graph-draw 26 | ``` 27 | In Node: 28 | ```js 29 | const graphDraw = require('graph-draw'); 30 | ``` 31 | 32 | In browsers, include one file from the dist directory. (Files with name that contains "bundle" include libtess.) 33 | 34 | ## Usage 35 | ```js 36 | const vertices = [[0, 0], [100, 0], [100, 100], [0, 100]]; 37 | // coordinates of the vertices 38 | 39 | const edges = [[0, 1], [1, 2], [2, 3], [3, 0], [1, 3]]; 40 | // each edge is specified with the indices of the linked vertices 41 | // each edge must appear exactly once in the list 42 | // ([0, 1] and [1, 0] are the same edge) 43 | 44 | const graph = {vertices, edges}; 45 | const strokeWidth = 10; 46 | const polygons = []; 47 | 48 | const polygonCallBack = convexPolygon => polygons.push(convexPolygon); 49 | 50 | graphDraw(graph, strokeWidth, polygonCallBack); 51 | ``` 52 | The `polygonCallBack` is executed on each polygon of the tessellation. Now, `polygons` contains a list of convex polygons (which can be easily converted into triangle strips or triangle fans): 53 | 54 | ```js 55 | [ 56 | [[x1, y1], [x2, y2], [x3, y3], [x4, y4]], 57 | [[a1, b1], [a2, b2], [a3, b3]], 58 | ... 59 | ] 60 | ``` 61 | 62 | Those convex polygons can have between 3 and 8 edges. 63 | 64 | ## Limiting miters 65 | When the angle between two consecutive edges is close to 2π, long miter situations occur. For example: 66 | 67 | ```js 68 | const vertices = [ 69 | [0, -200], 70 | [100 , -100], 71 | [30, -200] 72 | ]; 73 | 74 | const edges = [ 75 | [0, 1], 76 | [1, 2] 77 | ]; 78 | const graph = {vertices, edges}; 79 | const strokeWidth = 20; 80 | graphDraw(graph, strokeWidth, polygonCallBack); 81 | ``` 82 | produces: 83 | ![miter](/docs/img/miter.png) 84 | 85 | To avoid this, `graphDraw` function accepts a fourth (optional) `maxAngle` parameter which is an angle between π and 2π. If the angle between two consecutive edges is above `maxAngle`, the miter will be replaced by two triangles approximating a round join. For example: 86 | 87 | ```js 88 | graphDraw(graph, strokeWidth, polygonCallBack, Math.PI); 89 | ``` 90 | will produce: 91 | ![round](/docs/img/round.png) 92 | 93 | ## Sponsors 94 | 95 | Thanks to [Stadia Maps](https://stadiamaps.com/) for providing the [Stamen Toner Lite map tiles](https://docs.stadiamaps.com/map-styles/stamen-toner/#lite-variant) in our project demos. 96 | 97 |

98 | 99 |

100 | -------------------------------------------------------------------------------- /docs/js/leaflet-hash.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var HAS_HASHCHANGE = (function() { 3 | var doc_mode = window.documentMode; 4 | return ('onhashchange' in window) && 5 | (doc_mode === undefined || doc_mode > 7); 6 | })(); 7 | 8 | L.Hash = function(map) { 9 | this.onHashChange = L.Util.bind(this.onHashChange, this); 10 | 11 | if (map) { 12 | this.init(map); 13 | } 14 | }; 15 | 16 | L.Hash.parseHash = function(hash) { 17 | if(hash.indexOf('#') === 0) { 18 | hash = hash.substr(1); 19 | } 20 | var args = hash.split("/"); 21 | if (args.length == 3) { 22 | var zoom = parseInt(args[0], 10), 23 | lat = parseFloat(args[1]), 24 | lon = parseFloat(args[2]); 25 | if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { 26 | return false; 27 | } else { 28 | return { 29 | center: new L.LatLng(lat, lon), 30 | zoom: zoom 31 | }; 32 | } 33 | } else { 34 | return false; 35 | } 36 | }; 37 | 38 | L.Hash.formatHash = function(map) { 39 | var center = map.getCenter(), 40 | zoom = map.getZoom(), 41 | precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); 42 | 43 | return "#" + [zoom, 44 | center.lat.toFixed(precision), 45 | center.lng.toFixed(precision) 46 | ].join("/"); 47 | }, 48 | 49 | L.Hash.prototype = { 50 | map: null, 51 | lastHash: null, 52 | 53 | parseHash: L.Hash.parseHash, 54 | formatHash: L.Hash.formatHash, 55 | 56 | init: function(map) { 57 | this.map = map; 58 | 59 | // reset the hash 60 | this.lastHash = null; 61 | this.onHashChange(); 62 | 63 | if (!this.isListening) { 64 | this.startListening(); 65 | } 66 | }, 67 | 68 | removeFrom: function(map) { 69 | if (this.changeTimeout) { 70 | clearTimeout(this.changeTimeout); 71 | } 72 | 73 | if (this.isListening) { 74 | this.stopListening(); 75 | } 76 | 77 | this.map = null; 78 | }, 79 | 80 | onMapMove: function() { 81 | // bail if we're moving the map (updating from a hash), 82 | // or if the map is not yet loaded 83 | 84 | if (this.movingMap || !this.map._loaded) { 85 | return false; 86 | } 87 | 88 | var hash = this.formatHash(this.map); 89 | if (this.lastHash != hash) { 90 | location.replace(hash); 91 | this.lastHash = hash; 92 | } 93 | }, 94 | 95 | movingMap: false, 96 | update: function() { 97 | var hash = location.hash; 98 | if (hash === this.lastHash) { 99 | return; 100 | } 101 | var parsed = this.parseHash(hash); 102 | if (parsed) { 103 | this.movingMap = true; 104 | 105 | this.map.setView(parsed.center, parsed.zoom); 106 | 107 | this.movingMap = false; 108 | } else { 109 | this.onMapMove(this.map); 110 | } 111 | }, 112 | 113 | // defer hash change updates every 100ms 114 | changeDefer: 100, 115 | changeTimeout: null, 116 | onHashChange: function() { 117 | // throttle calls to update() so that they only happen every 118 | // `changeDefer` ms 119 | if (!this.changeTimeout) { 120 | var that = this; 121 | this.changeTimeout = setTimeout(function() { 122 | that.update(); 123 | that.changeTimeout = null; 124 | }, this.changeDefer); 125 | } 126 | }, 127 | 128 | isListening: false, 129 | hashChangeInterval: null, 130 | startListening: function() { 131 | this.map.on("moveend", this.onMapMove, this); 132 | 133 | if (HAS_HASHCHANGE) { 134 | L.DomEvent.addListener(window, "hashchange", this.onHashChange); 135 | } else { 136 | clearInterval(this.hashChangeInterval); 137 | this.hashChangeInterval = setInterval(this.onHashChange, 50); 138 | } 139 | this.isListening = true; 140 | }, 141 | 142 | stopListening: function() { 143 | this.map.off("moveend", this.onMapMove, this); 144 | 145 | if (HAS_HASHCHANGE) { 146 | L.DomEvent.removeListener(window, "hashchange", this.onHashChange); 147 | } else { 148 | clearInterval(this.hashChangeInterval); 149 | } 150 | this.isListening = false; 151 | } 152 | }; 153 | L.hash = function(map) { 154 | return new L.Hash(map); 155 | }; 156 | L.Map.prototype.addHash = function() { 157 | this._hash = L.hash(this); 158 | }; 159 | L.Map.prototype.removeHash = function() { 160 | this._hash.removeFrom(); 161 | }; 162 | })(window); 163 | -------------------------------------------------------------------------------- /docs/data/polyline.json: -------------------------------------------------------------------------------- 1 | {"vertices":[[-77.066104,38.910203],[-77.066106,38.910321],[-77.066112,38.910758],[-77.065249,38.910775],[-77.065178,38.910793],[-77.065139,38.910804],[-77.064904,38.91036],[-77.064855,38.910268],[-77.064621,38.909825],[-77.06459,38.909766],[-77.064342,38.909298],[-77.064281,38.909182],[-77.064201,38.909226],[-77.064175,38.909235],[-77.064149,38.909241],[-77.064122,38.909244],[-77.06336,38.90926],[-77.061442,38.909288],[-77.060801,38.909297],[-77.059237,38.909315],[-77.058186,38.90933],[-77.057113,38.909343],[-77.055623,38.909368],[-77.054762,38.909377],[-77.053951,38.909389],[-77.053904,38.909393],[-77.053858,38.909399],[-77.053813,38.909408],[-77.052833,38.909635],[-77.052799,38.909642],[-77.052772,38.909645],[-77.052692,38.90965],[-77.052443,38.909649],[-77.05096,38.909639],[-77.050327,38.909634],[-77.049545,38.909631],[-77.049533,38.909635],[-77.049341,38.909635],[-77.048903,38.909635],[-77.048797,38.909634],[-77.04773,38.909639],[-77.046632,38.90964],[-77.045758,38.909641],[-77.044877,38.909643],[-77.044578,38.909641],[-77.044493,38.909635],[-77.04442,38.909626],[-77.044345,38.909608],[-77.044283,38.909578],[-77.044249,38.909541],[-77.044212,38.909496],[-77.044199,38.909458],[-77.044182,38.909421],[-77.044162,38.909385],[-77.04414,38.90935],[-77.044113,38.909313],[-77.044083,38.90928],[-77.044051,38.909247],[-77.044018,38.909165],[-77.044018,38.909117],[-77.044029,38.909071],[-77.044037,38.909038],[-77.044058,38.908981],[-77.043996,38.909021],[-77.043919,38.909052],[-77.043854,38.909067],[-77.043798,38.909073],[-77.043755,38.909075],[-77.043689,38.909056],[-77.043656,38.909048],[-77.04359,38.909036],[-77.043527,38.909028],[-77.043486,38.909026],[-77.043444,38.909025],[-77.043402,38.909026],[-77.043344,38.909029],[-77.043286,38.909037],[-77.043263,38.909041],[-77.04323,38.909047],[-77.043194,38.909056],[-77.043022,38.909118],[-77.042995,38.909132],[-77.04296,38.909151],[-77.042938,38.909165],[-77.042915,38.90918],[-77.042875,38.909209],[-77.04284,38.909239],[-77.042823,38.909255],[-77.042791,38.909288],[-77.042774,38.909307],[-77.042712,38.909315],[-77.042637,38.909322],[-77.042551,38.909317],[-77.042422,38.909299],[-77.041676,38.90903],[-77.039935,38.908425],[-77.038503,38.907925],[-77.037656,38.907632],[-77.037477,38.907534],[-77.037317,38.907438],[-77.037238,38.90739],[-77.037121,38.907315],[-77.036997,38.907233],[-77.036875,38.907137],[-77.036833,38.907094],[-77.036817,38.907081],[-77.036795,38.907068],[-77.036774,38.907058],[-77.036767,38.907052],[-77.036712,38.907034],[-77.03669,38.907028],[-77.036643,38.907022],[-77.03661,38.907021],[-77.03653,38.907021],[-77.036474,38.907021],[-77.03643,38.907025],[-77.03639,38.907033],[-77.036295,38.907058],[-77.036005,38.906995],[-77.035439,38.906869],[-77.035142,38.906757],[-77.035049,38.906725],[-77.0348,38.906641],[-77.034673,38.906594],[-77.034568,38.906558],[-77.034059,38.906382],[-77.033931,38.906338],[-77.032623,38.905883],[-77.03129,38.905426],[-77.030031,38.904982],[-77.029623,38.904835],[-77.028082,38.904304],[-77.027679,38.904167],[-77.02705,38.903949],[-77.026409,38.903729],[-77.025984,38.903579],[-77.024321,38.902997],[-77.024139,38.902954],[-77.023952,38.902954],[-77.023188,38.902956],[-77.022699,38.902958],[-77.021917,38.90296],[-77.021918,38.902902],[-77.021918,38.902521],[-77.019907,38.902521],[-77.018928,38.902521],[-77.016176,38.902519],[-77.015177,38.902518],[-77.013722,38.902514],[-77.012171,38.902516],[-77.011237,38.902516],[-77.009846,38.902515],[-77.009117,38.902514],[-77.009126,38.901346],[-77.00912,38.900203],[-77.009062,38.900203],[-77.008975,38.900203],[-77.008004,38.900198],[-77.00631,38.900193],[-77.005531,38.900197],[-77.002929,38.9002],[-77.002038,38.900203],[-77.001892,38.900203],[-77.000571,38.900205],[-76.999507,38.900204],[-76.998442,38.900204],[-76.998369,38.900204],[-76.996167,38.900205],[-76.994961,38.900203],[-76.994962,38.899748],[-76.994961,38.899626],[-76.994961,38.899367],[-76.994961,38.898908],[-76.994962,38.898447]],"edges":[[1,0],[2,1],[3,2],[4,3],[5,4],[6,5],[7,6],[8,7],[9,8],[10,9],[11,10],[12,11],[13,12],[14,13],[15,14],[16,15],[17,16],[18,17],[19,18],[20,19],[21,20],[22,21],[23,22],[24,23],[25,24],[26,25],[27,26],[28,27],[29,28],[30,29],[31,30],[32,31],[33,32],[34,33],[35,34],[36,35],[37,36],[38,37],[39,38],[40,39],[41,40],[42,41],[43,42],[44,43],[45,44],[46,45],[47,46],[48,47],[49,48],[50,49],[51,50],[52,51],[53,52],[54,53],[55,54],[56,55],[57,56],[58,57],[59,58],[60,59],[61,60],[62,61],[63,62],[64,63],[65,64],[66,65],[67,66],[68,67],[69,68],[70,69],[71,70],[72,71],[73,72],[74,73],[75,74],[76,75],[77,76],[78,77],[79,78],[80,79],[81,80],[82,81],[83,82],[84,83],[85,84],[86,85],[87,86],[88,87],[89,88],[90,89],[91,90],[92,91],[93,92],[94,93],[95,94],[96,95],[97,96],[98,97],[99,98],[100,99],[101,100],[102,101],[103,102],[104,103],[105,104],[106,105],[107,106],[108,107],[109,108],[110,109],[111,110],[112,111],[113,112],[114,113],[115,114],[116,115],[117,116],[118,117],[119,118],[120,119],[121,120],[122,121],[123,122],[124,123],[125,124],[126,125],[127,126],[128,127],[129,128],[130,129],[131,130],[132,131],[133,132],[134,133],[135,134],[136,135],[137,136],[138,137],[139,138],[140,139],[141,140],[142,141],[143,142],[144,143],[145,144],[146,145],[147,146],[148,147],[149,148],[150,149],[151,150],[152,151],[153,152],[154,153],[155,154],[156,155],[157,156],[158,157],[159,158],[160,159],[161,160],[162,161],[163,162],[164,163],[165,164],[166,165],[167,166],[168,167],[169,168],[170,169],[171,170],[172,171],[173,172]]} 2 | -------------------------------------------------------------------------------- /docs/js/L.PixiOverlay.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("function"==typeof define&&define.amd)define(["leaflet","pixi.js"],e);else if("undefined"!=typeof module)module.exports=e(require("leaflet"),require("pixi.js"));else{if(void 0===window.L)throw new Error("Leaflet must be loaded first");if(void 0===window.PIXI)throw new Error("Pixi.js must be loaded first");e(window.L,window.PIXI)}}(function(d,o){function e(){return this}var t=d.Point.prototype._round;function r(e,t,i){e=o.VERSION<"7"?e.plugins.interaction:e.events;t?e.destroy():i||(e.autoPreventDefault=!1)}var i={options:{padding:.1,forceCanvas:!1,doubleBuffering:!1,resolution:d.Browser.retina?2:1,projectionZoom:function(e){var t=e.getMaxZoom(),e=e.getMinZoom();return t===1/0?e+8:(t+e)/2},destroyInteractionManager:!1,autoPreventDefault:!0,preserveDrawingBuffer:!1,clearBeforeRender:!0,shouldRedrawOnMove:function(){return!1}},initialize:function(e,t,i){d.setOptions(this,i),d.stamp(this),this._drawCallback=e,this._pixiContainer=t,this._rendererOptions={resolution:this.options.resolution,antialias:!0,forceCanvas:this.options.forceCanvas,preserveDrawingBuffer:this.options.preserveDrawingBuffer,clearBeforeRender:this.options.clearBeforeRender},o.VERSION<"6"?this._rendererOptions.transparent=!0:this._rendererOptions.backgroundAlpha=0,this._doubleBuffering=o.utils.isWebGLSupported()&&!this.options.forceCanvas&&this.options.doubleBuffering},_setMap:function(){},_setContainerStyle:function(){},_addContainer:function(){this.getPane().appendChild(this._container)},_setEvents:function(){},onAdd:function(e){this._setMap(e),this._container||((e=this._container=d.DomUtil.create("div","leaflet-pixi-overlay")).style.position="absolute",this._renderer=o.autoDetectRenderer(this._rendererOptions),r(this._renderer,this.options.destroyInteractionManager,this.options.autoPreventDefault),e.appendChild(this._renderer.view),this._zoomAnimated&&(d.DomUtil.addClass(e,"leaflet-zoom-animated"),this._setContainerStyle()),this._doubleBuffering&&(this._auxRenderer=o.autoDetectRenderer(this._rendererOptions),r(this._auxRenderer,this.options.destroyInteractionManager,this.options.autoPreventDefault),e.appendChild(this._auxRenderer.view),this._renderer.view.style.position="absolute",this._auxRenderer.view.style.position="absolute")),this._addContainer(),this._setEvents();var i=this._map,n=(this._initialZoom=this.options.projectionZoom(i),this._wgsOrigin=d.latLng([0,0]),this._wgsInitialShift=i.project(this._wgsOrigin,this._initialZoom),this._mapInitialZoom=i.getZoom(),this);this.utils={latLngToLayerPoint:function(e,t){return t=void 0===t?n._initialZoom:t,i.project(d.latLng(e),t)},layerPointToLatLng:function(e,t){t=void 0===t?n._initialZoom:t;e=d.point(e);return i.unproject(e,t)},getScale:function(e){return void 0===e?i.getZoomScale(i.getZoom(),n._initialZoom):i.getZoomScale(e,n._initialZoom)},getRenderer:function(){return n._renderer},getContainer:function(){return n._pixiContainer},getMap:function(){return n._map}},this._update({type:"add"})},onRemove:function(){d.DomUtil.remove(this._container)},getEvents:function(){var e={zoom:this._onZoom,move:this._onMove,moveend:this._update};return this._zoomAnimated&&(e.zoomanim=this._onAnimZoom),e},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_onAnimZoom:function(e){this._updateTransform(e.center,e.zoom)},_onMove:function(e){this.options.shouldRedrawOnMove(e)&&this._update(e)},_updateTransform:function(e,t){var i=this._map.getZoomScale(t,this._zoom),n=this._map.getSize().multiplyBy(.5+this.options.padding),o=this._map.project(this._center,t),n=n.multiplyBy(-i).add(o).subtract(this._map._getNewPixelOrigin(e,t));d.Browser.any3d?d.DomUtil.setTransform(this._container,n,i):d.DomUtil.setPosition(this._container,n)},_redraw:function(e,t){this._disableLeafletRounding();var i=this._map.getZoomScale(this._zoom,this._initialZoom),e=this._map.latLngToLayerPoint(this._wgsOrigin)._subtract(this._wgsInitialShift.multiplyBy(i))._subtract(e);this._pixiContainer.scale.set(i),this._pixiContainer.position.set(e.x,e.y),this._drawCallback(this.utils,t),this._enableLeafletRounding()},_update:function(e){var t,i,n,o,r,s,a;this._map._animatingZoom&&this._bounds||(r=this.options.padding,o=this._map.getSize(),s=this._map.containerPointToLayerPoint(o.multiplyBy(-r)).round(),this._bounds=new d.Bounds(s,s.add(o.multiplyBy(1+2*r)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom(),this._doubleBuffering&&(s=this._renderer,this._renderer=this._auxRenderer,this._auxRenderer=s),t=this._renderer.view,i=this._bounds,n=this._container,o=i.getSize(),this._renderer.size&&this._renderer.size.x===o.x&&this._renderer.size.y===o.y||(this._renderer.gl&&(this._renderer.resolution=this.options.resolution,this._renderer.rootRenderTarget)&&(this._renderer.rootRenderTarget.resolution=this.options.resolution),this._renderer.resize(o.x,o.y),t.style.width=o.x+"px",t.style.height=o.y+"px",this._renderer.gl&&(r=this._renderer.gl).drawingBufferWidth!==this._renderer.width&&(s=this.options.resolution*r.drawingBufferWidth/this._renderer.width,this._renderer.resolution=s,this._renderer.rootRenderTarget&&(this._renderer.rootRenderTarget.resolution=s),this._renderer.resize(o.x,o.y)),this._renderer.size=o),this._doubleBuffering?(a=this,requestAnimationFrame(function(){a._redraw(i.min,e),a._renderer.gl.finish(),t.style.visibility="visible",a._auxRenderer.view.style.visibility="hidden",d.DomUtil.setPosition(n,i.min)})):(this._redraw(i.min,e),d.DomUtil.setPosition(n,i.min)))},_disableLeafletRounding:function(){d.Point.prototype._round=e},_enableLeafletRounding:function(){d.Point.prototype._round=t},redraw:function(e){return this._map&&(this._disableLeafletRounding(),this._drawCallback(this.utils,e),this._enableLeafletRounding()),this},_destroy:function(){this._renderer.destroy(!0),this._doubleBuffering&&this._auxRenderer.destroy(!0)},destroy:function(){this.remove(),this._destroy()}};"1"<=d.version?d.PixiOverlay=d.Layer.extend(i):(d.Map.prototype.getZoomScale=function(e,t){var i=this.options.crs;return t=void 0===t?this._zoom:t,i.scale(e)/i.scale(t)},d.DomUtil.setTransform=function(e,t,i){t=t||new d.Point(0,0);e.style[d.DomUtil.TRANSFORM]=(d.Browser.ie3d?"translate("+t.x+"px,"+t.y+"px)":"translate3d("+t.x+"px,"+t.y+"px,0)")+(i?" scale("+i+")":"")},i.includes=d.Mixin.Events,i.addTo=function(e){return e.addLayer(this),this},i._setMap=function(e){this._map=e,this._zoomAnimated=e._zoomAnimated},i._setContainerStyle=function(){var t=this;["-webkit-transform-origin","-ms-transform-origin","transform-origin"].forEach(function(e){t._container.style[e]="0 0"})},i._addContainer=function(){this._map.getPanes()[this.options.pane||"overlayPane"].appendChild(this._container)},i._setEvents=function(){var e,t=this.getEvents();for(e in t)this._map.on(e,t[e],this)},i.onRemove=function(){this._map=null;var e,t=this._container.parentNode,i=(t&&t.removeChild(this._container),this.getEvents());for(e in i)this._map.off(e,i[e],this)},i.destroy=function(){var e=this._map||this._mapToAdd;e&&e.removeLayer(this),this._destroy()},d.PixiOverlay=d.Class.extend(i)),d.pixiOverlay=function(e,t,i){return d.Browser.canvas?new d.PixiOverlay(e,t,i):null}}); -------------------------------------------------------------------------------- /docs/city-mesh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | French cities mesh graph-draw demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /docs/railways.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rail network demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /docs/polyline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Polyline graph-draw demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /src/graph-draw.js: -------------------------------------------------------------------------------- 1 | // Graph-draw 2 | // version: 2.0.3 3 | // author: Manuel Baclet 4 | // license: MIT 5 | 6 | 'use strict'; 7 | 8 | var tess = new (require('./tessellator'))(); 9 | 10 | /* Stack class with no duplicates */ 11 | 12 | function TaskList() { 13 | this.list = []; 14 | this.hash = Object.create(null); 15 | } 16 | TaskList.prototype.push = function(index) { 17 | if (this.hash[index]) return; 18 | this.hash[index] = true; 19 | this.list.push(index); 20 | }; 21 | TaskList.prototype.pop = function() { 22 | if (this.list.length) { 23 | var value = this.list.pop(); 24 | delete this.hash[value]; 25 | return value; 26 | } else return null; 27 | }; 28 | 29 | /* Union-find functions */ 30 | 31 | function unionFindUnion(obj1, obj2) { 32 | var root1 = unionFindFind(obj1); 33 | var root2 = unionFindFind(obj2); 34 | if (root1 !== root2) { 35 | if (root1.rank < root2.rank) { 36 | root1.parent = root2; 37 | } else { 38 | root2.parent = root1; 39 | if (root1.rank === root2.rank) root1.rank += 1; 40 | } 41 | } 42 | } 43 | 44 | function unionFindFind(x) { 45 | if (x.parent !== x) x.parent = unionFindFind(x.parent); 46 | return x.parent; 47 | } 48 | 49 | /* Fast collision test for rectangles*/ 50 | 51 | function subTest(p1, p2 , r) { 52 | var position; 53 | var norm = norm2(p1, p2); 54 | var product, localPosition; 55 | for (var i = 0; i < 4; i++) { 56 | product = innerProduct(p1, p2, p1, r[i]); 57 | if (product <= 0) localPosition = -1; 58 | else if (product >= norm) localPosition = 1; 59 | else return false; 60 | if (i === 0) { 61 | position = localPosition; 62 | } else { 63 | if (localPosition !== position) { 64 | return false; 65 | } 66 | } 67 | } 68 | return true; 69 | } 70 | 71 | function rectanglesCollide(r1, r2) { 72 | return !(subTest(r1[0], r1[1], r2) || subTest(r1[1], r1[2], r2) || subTest(r2[0], r2[1], r1) || subTest(r2[1], r2[2], r1)); 73 | } 74 | 75 | /* Geometry helpers */ 76 | 77 | function innerProduct(p1, p2, p3, p4) { 78 | return (p2[0] - p1[0]) * (p4[0] - p3[0]) + (p2[1] - p1[1]) * (p4[1] - p3[1]); 79 | } 80 | function norm2(p1, p2) { 81 | var dx = p2[0] - p1[0]; 82 | var dy = p2[1] - p1[1]; 83 | return dx * dx + dy * dy; 84 | } 85 | 86 | function segmentIntersection(p1, p2, p3, p4) { 87 | // find intersection point of [p1, p2] and [p3, p4], supposing it exists 88 | var dx = p2[0] - p1[0]; 89 | var dy = p2[1] - p1[1]; 90 | var dx2 = p4[0] - p3[0]; 91 | var dy2 = p4[1] - p3[1]; 92 | var lambda = ((p2[0] - p3[0]) * dy - dx * (p2[1] - p3[1])) / 93 | (dx2 * dy - dx * dy2); 94 | return [p3[0] + lambda * dx2, p3[1] + lambda * dy2]; 95 | } 96 | function rayIntersection(p1, p2, p3, p4) { 97 | // find intersection point of (p1, p2] and (p3, p4] 98 | var dx = p2[0] - p1[0]; 99 | var dy = p2[1] - p1[1]; 100 | var dx2 = p4[0] - p3[0]; 101 | var dy2 = p4[1] - p3[1]; 102 | var denom = dx2 * dy - dx * dy2; 103 | if (denom === 0) return {point: p1, valid: true}; 104 | var lambda = ((p2[0] - p3[0]) * dy - dx * (p2[1] - p3[1])) / denom; 105 | var inter = [p3[0] + lambda * dx2, p3[1] + lambda * dy2]; 106 | if (lambda > 1 || innerProduct(p2, inter, p2, p1) < 0) return {point: inter, valid: false}; 107 | else return {point: inter, valid: true}; 108 | } 109 | 110 | function getData(from, to, w) { 111 | var ux = to[0] - from[0]; 112 | var uy = to[1] - from [1]; 113 | var Nu = Math.sqrt(ux * ux + uy * uy); 114 | var theta = Math.acos(ux / Nu); 115 | if (uy < 0) theta *= -1; 116 | return { 117 | angle: theta, 118 | norm: Nu, 119 | dir: [ux, uy], 120 | ortho: [- w * uy / Nu, w * ux / Nu] 121 | }; 122 | } 123 | 124 | /* main function */ 125 | 126 | function graphDraw(graph, width, cb, maxAngle) { 127 | var w = width / 2; 128 | maxAngle = Math.max(Math.PI, maxAngle || 2 * Math.PI); 129 | 130 | /* Data structures setup */ 131 | 132 | var vertices = graph.vertices.map(function(coords) { 133 | return { 134 | coords: coords, 135 | neighList: [] 136 | }; 137 | }); 138 | var edges = graph.edges.map(function(edge, index) { 139 | var from = edge[0]; 140 | var to = edge[1]; 141 | var vertexFrom = vertices[from]; 142 | var vertexTo = vertices[to]; 143 | var data = getData(vertexFrom.coords, vertexTo.coords, w); 144 | vertexFrom.neighList.push({ 145 | to: to, 146 | angle: data.angle, 147 | dir: data.dir, 148 | ortho: data.ortho, 149 | index: index 150 | }); 151 | vertexTo.neighList.push({ 152 | to: from, 153 | angle: data.angle <= 0 ? data.angle + Math.PI : data.angle - Math.PI, 154 | dir: [-data.dir[0], -data.dir[1]], 155 | ortho: [-data.ortho[0], -data.ortho[1]], 156 | index: index 157 | }); 158 | var obj = { 159 | rank: 0, 160 | edge: edge, 161 | points: {} 162 | }; 163 | obj.points[to] = {}; 164 | obj.points[from] = {}; 165 | obj.parent = obj; 166 | return obj; 167 | }); 168 | 169 | /* Build edges contour points */ 170 | 171 | var toPostProcess = []; 172 | vertices.forEach(function(vertex, vindex) { 173 | var point = vertex.coords; 174 | var prepared = vertex.neighList; 175 | prepared.sort(function(a, b) {return a.angle - b.angle;}); 176 | var n = prepared.length; 177 | if (n === 1) { 178 | var edge = prepared[0]; 179 | var p1 = [point[0] + edge.ortho[0], point[1] + edge.ortho[1]]; 180 | var p2 = [point[0] - edge.ortho[0], point[1] - edge.ortho[1]]; 181 | var edgePoints = edges[edge.index].points; 182 | edgePoints[vindex].first_vertex = edge.index; 183 | edgePoints[vindex].last_vertex = edge.index; 184 | edgePoints[vindex].first = p1; 185 | edgePoints[vindex].remove_middle_first = true; 186 | edgePoints[vindex].remove_middle_last = true; 187 | edgePoints[vindex].last = p2; 188 | } else { 189 | prepared.forEach(function(edge, index) { 190 | var last = (index === n - 1); 191 | var next = prepared[last ? 0 : index + 1]; 192 | var edgePoints = edges[edge.index].points; 193 | var nextPoints = edges[next.index].points; 194 | edgePoints[vindex].first_vertex = next.index; 195 | nextPoints[vindex].last_vertex = edge.index; 196 | var p1 = [point[0] + edge.ortho[0], point[1] + edge.ortho[1]]; 197 | var p2 = [p1[0] + edge.dir[0], p1[1] + edge.dir[1]]; 198 | var p3 = [point[0] - next.ortho[0], point[1] - next.ortho[1]]; 199 | var p4 = [p3[0] + next.dir[0], p3[1] + next.dir[1]]; 200 | var intersection = rayIntersection(p1, p2, p3, p4); 201 | var newPoint = intersection.point; 202 | if (intersection.valid) { 203 | var nextAngle = last ? next.angle + 2 * Math.PI : next.angle; 204 | if (nextAngle - edge.angle > maxAngle) { 205 | edgePoints[vindex].first = p1; 206 | nextPoints[vindex].last = p3; 207 | var vec = [newPoint[0] - point[0], newPoint[1] - point[1]]; 208 | var invNorm = 1 / Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]); 209 | edgePoints[vindex].miter_first = nextPoints[vindex].miter_last = [ 210 | point[0] + w * vec[0] * invNorm, 211 | point[1] + w * vec[1] * invNorm 212 | ]; 213 | } else { 214 | edgePoints[vindex].first = newPoint; 215 | nextPoints[vindex].last = newPoint; 216 | } 217 | if (n === 2) { 218 | edgePoints[vindex].remove_middle_first = true; 219 | nextPoints[vindex].remove_middle_last = true; 220 | } 221 | } else { 222 | var q1 = [newPoint[0] - edge.ortho[0], newPoint[1] - edge.ortho[1]]; 223 | var q3 = [newPoint[0] + next.ortho[0], newPoint[1] + next.ortho[1]]; 224 | 225 | toPostProcess.push({ 226 | done: [edge.index, next.index], 227 | todo: [vindex, edge.to, next.to], 228 | rectangles: [[p1, newPoint, q1, point], [p3, newPoint, q3, point]] 229 | }); 230 | 231 | edgePoints[vindex].first = p1; 232 | nextPoints[vindex].last = p3; 233 | } 234 | }); 235 | } 236 | }); 237 | 238 | /* Build each edge polygon */ 239 | 240 | edges.forEach(function(obj) { 241 | var edge = obj.edge; 242 | var from = edge[0]; 243 | var to = edge[1]; 244 | var obj1 = obj.points[from]; 245 | var obj2 = obj.points[to]; 246 | var fromCoords = vertices[from].coords; 247 | var toCoords = vertices[to].coords; 248 | var newPoly = obj.polygon = []; 249 | 250 | if (innerProduct(obj1.last, obj2.first, fromCoords, toCoords) < 0) { 251 | var i1 = obj1.last_vertex; 252 | var i2 = obj2.first_vertex; 253 | unionFindUnion(edges[i1], edges[i2]); 254 | newPoly.push(segmentIntersection(obj1.miter_last || fromCoords, obj1.last, obj2.first, obj2.miter_first || toCoords)); 255 | } else { 256 | newPoly.push(obj1.last, obj2.first); 257 | } 258 | if (obj2.miter_first) newPoly.push(obj2.miter_first); 259 | if (!(obj2.remove_middle_first && obj2.remove_middle_last)) newPoly.push(toCoords); 260 | if (obj2.miter_last) newPoly.push(obj2.miter_last); 261 | if (innerProduct(obj1.first, obj2.last, fromCoords, toCoords) < 0) { 262 | var i1 = obj1.first_vertex; 263 | var i2 = obj2.last_vertex; 264 | unionFindUnion(edges[i1], edges[i2]); 265 | newPoly.push(segmentIntersection(obj1.first, obj1.miter_first || fromCoords, obj2.miter_last || toCoords, obj2.last)); 266 | } else { 267 | newPoly.push(obj2.last, obj1.first); 268 | } 269 | if (obj1.miter_first) newPoly.push(obj1.miter_first); 270 | if (!(obj1.remove_middle_first && obj1.remove_middle_last)) newPoly.push(fromCoords); 271 | if (obj1.miter_last) newPoly.push(obj1.miter_last); 272 | }); 273 | 274 | /* Find locally overlapping edges */ 275 | 276 | var shapeMemo = Object.create(null); 277 | 278 | toPostProcess.forEach(function(obj) { 279 | var done = Object.create(null); 280 | var i1 = obj.done[0]; 281 | var i2 = obj.done[1]; 282 | var e1 = edges[i1]; 283 | var e2 = edges[i2]; 284 | unionFindUnion(e1, e2); 285 | done[i1] = true; 286 | done[i2] = true; 287 | var todo = new TaskList(); 288 | obj.todo.forEach(function(vertex) { 289 | todo.push(vertex); 290 | }); 291 | var from; 292 | var r1 = obj.rectangles[0]; 293 | var r2 = obj.rectangles[1]; 294 | while((from = todo.pop()) !== null) { 295 | vertices[from].neighList.forEach(function(neigh) { 296 | var index = neigh.index; 297 | if (done[index]) return; 298 | var to = neigh.to; 299 | var rectangle = shapeMemo[index]; 300 | if (!rectangle) { 301 | var fromCoords = vertices[from].coords; 302 | var toCoords = vertices[to].coords; 303 | var p1 = [fromCoords[0] + neigh.ortho[0], fromCoords[1] + neigh.ortho[1]]; 304 | var p2 = [toCoords[0] + neigh.ortho[0], toCoords[1] + neigh.ortho[1]]; 305 | var p3 = [toCoords[0] - neigh.ortho[0], toCoords[1] - neigh.ortho[1]]; 306 | var p4 = [fromCoords[0] - neigh.ortho[0], fromCoords[1] - neigh.ortho[1]]; 307 | rectangle = shapeMemo[index] = [p1, p2, p3, p4]; 308 | } 309 | done[index] = true; 310 | if (rectanglesCollide(rectangle, r1) || rectanglesCollide(rectangle, r2)) { 311 | unionFindUnion(e1, edges[index]); 312 | todo.push(to); 313 | } 314 | }); 315 | } 316 | }); 317 | 318 | /* Execute cb on each polygon */ 319 | 320 | var needUnion = []; 321 | edges.forEach(function(obj, index) { 322 | if (obj.rank > 0 && obj.parent === obj) { 323 | obj.union = obj.union || []; 324 | obj.union.push(obj.polygon); 325 | needUnion.push(index); 326 | } else { 327 | if (obj.parent === obj) { 328 | cb(obj.polygon); 329 | } else { 330 | var root = unionFindFind(obj); 331 | root.union = root.union || []; 332 | root.union.push(obj.polygon); 333 | } 334 | } 335 | }); 336 | 337 | tess._cb = cb; 338 | needUnion.forEach(function(index) { 339 | tess.run(edges[index].union); 340 | }); 341 | delete tess._cb; 342 | } 343 | 344 | module.exports = graphDraw; 345 | -------------------------------------------------------------------------------- /docs/css/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Prevents IE11 from highlighting tiles in blue */ 29 | .leaflet-tile::selection { 30 | background: transparent; 31 | } 32 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 33 | .leaflet-safari .leaflet-tile { 34 | image-rendering: -webkit-optimize-contrast; 35 | } 36 | /* hack that prevents hw layers "stretching" when loading new tiles */ 37 | .leaflet-safari .leaflet-tile-container { 38 | width: 1600px; 39 | height: 1600px; 40 | -webkit-transform-origin: 0 0; 41 | } 42 | .leaflet-marker-icon, 43 | .leaflet-marker-shadow { 44 | display: block; 45 | } 46 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 47 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 48 | .leaflet-container .leaflet-overlay-pane svg, 49 | .leaflet-container .leaflet-marker-pane img, 50 | .leaflet-container .leaflet-shadow-pane img, 51 | .leaflet-container .leaflet-tile-pane img, 52 | .leaflet-container img.leaflet-image-layer, 53 | .leaflet-container .leaflet-tile { 54 | max-width: none !important; 55 | max-height: none !important; 56 | } 57 | 58 | .leaflet-container.leaflet-touch-zoom { 59 | -ms-touch-action: pan-x pan-y; 60 | touch-action: pan-x pan-y; 61 | } 62 | .leaflet-container.leaflet-touch-drag { 63 | -ms-touch-action: pinch-zoom; 64 | /* Fallback for FF which doesn't support pinch-zoom */ 65 | touch-action: none; 66 | touch-action: pinch-zoom; 67 | } 68 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 69 | -ms-touch-action: none; 70 | touch-action: none; 71 | } 72 | .leaflet-container { 73 | -webkit-tap-highlight-color: transparent; 74 | } 75 | .leaflet-container a { 76 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 77 | } 78 | .leaflet-tile { 79 | filter: inherit; 80 | visibility: hidden; 81 | } 82 | .leaflet-tile-loaded { 83 | visibility: inherit; 84 | } 85 | .leaflet-zoom-box { 86 | width: 0; 87 | height: 0; 88 | -moz-box-sizing: border-box; 89 | box-sizing: border-box; 90 | z-index: 800; 91 | } 92 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 93 | .leaflet-overlay-pane svg { 94 | -moz-user-select: none; 95 | } 96 | 97 | .leaflet-pane { z-index: 400; } 98 | 99 | .leaflet-tile-pane { z-index: 200; } 100 | .leaflet-overlay-pane { z-index: 400; } 101 | .leaflet-shadow-pane { z-index: 500; } 102 | .leaflet-marker-pane { z-index: 600; } 103 | .leaflet-tooltip-pane { z-index: 650; } 104 | .leaflet-popup-pane { z-index: 700; } 105 | 106 | .leaflet-map-pane canvas { z-index: 100; } 107 | .leaflet-map-pane svg { z-index: 200; } 108 | 109 | .leaflet-vml-shape { 110 | width: 1px; 111 | height: 1px; 112 | } 113 | .lvml { 114 | behavior: url(#default#VML); 115 | display: inline-block; 116 | position: absolute; 117 | } 118 | 119 | 120 | /* control positioning */ 121 | 122 | .leaflet-control { 123 | position: relative; 124 | z-index: 800; 125 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 126 | pointer-events: auto; 127 | } 128 | .leaflet-top, 129 | .leaflet-bottom { 130 | position: absolute; 131 | z-index: 1000; 132 | pointer-events: none; 133 | } 134 | .leaflet-top { 135 | top: 0; 136 | } 137 | .leaflet-right { 138 | right: 0; 139 | } 140 | .leaflet-bottom { 141 | bottom: 0; 142 | } 143 | .leaflet-left { 144 | left: 0; 145 | } 146 | .leaflet-control { 147 | float: left; 148 | clear: both; 149 | } 150 | .leaflet-right .leaflet-control { 151 | float: right; 152 | } 153 | .leaflet-top .leaflet-control { 154 | margin-top: 10px; 155 | } 156 | .leaflet-bottom .leaflet-control { 157 | margin-bottom: 10px; 158 | } 159 | .leaflet-left .leaflet-control { 160 | margin-left: 10px; 161 | } 162 | .leaflet-right .leaflet-control { 163 | margin-right: 10px; 164 | } 165 | 166 | 167 | /* zoom and fade animations */ 168 | 169 | .leaflet-fade-anim .leaflet-tile { 170 | will-change: opacity; 171 | } 172 | .leaflet-fade-anim .leaflet-popup { 173 | opacity: 0; 174 | -webkit-transition: opacity 0.2s linear; 175 | -moz-transition: opacity 0.2s linear; 176 | transition: opacity 0.2s linear; 177 | } 178 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 179 | opacity: 1; 180 | } 181 | .leaflet-zoom-animated { 182 | -webkit-transform-origin: 0 0; 183 | -ms-transform-origin: 0 0; 184 | transform-origin: 0 0; 185 | } 186 | .leaflet-zoom-anim .leaflet-zoom-animated { 187 | will-change: transform; 188 | } 189 | .leaflet-zoom-anim .leaflet-zoom-animated { 190 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 191 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 192 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 193 | } 194 | .leaflet-zoom-anim .leaflet-tile, 195 | .leaflet-pan-anim .leaflet-tile { 196 | -webkit-transition: none; 197 | -moz-transition: none; 198 | transition: none; 199 | } 200 | 201 | .leaflet-zoom-anim .leaflet-zoom-hide { 202 | visibility: hidden; 203 | } 204 | 205 | 206 | /* cursors */ 207 | 208 | .leaflet-interactive { 209 | cursor: pointer; 210 | } 211 | .leaflet-grab { 212 | cursor: -webkit-grab; 213 | cursor: -moz-grab; 214 | cursor: grab; 215 | } 216 | .leaflet-crosshair, 217 | .leaflet-crosshair .leaflet-interactive { 218 | cursor: crosshair; 219 | } 220 | .leaflet-popup-pane, 221 | .leaflet-control { 222 | cursor: auto; 223 | } 224 | .leaflet-dragging .leaflet-grab, 225 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 226 | .leaflet-dragging .leaflet-marker-draggable { 227 | cursor: move; 228 | cursor: -webkit-grabbing; 229 | cursor: -moz-grabbing; 230 | cursor: grabbing; 231 | } 232 | 233 | /* marker & overlays interactivity */ 234 | .leaflet-marker-icon, 235 | .leaflet-marker-shadow, 236 | .leaflet-image-layer, 237 | .leaflet-pane > svg path, 238 | .leaflet-tile-container { 239 | pointer-events: none; 240 | } 241 | 242 | .leaflet-marker-icon.leaflet-interactive, 243 | .leaflet-image-layer.leaflet-interactive, 244 | .leaflet-pane > svg path.leaflet-interactive, 245 | svg.leaflet-image-layer.leaflet-interactive path { 246 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 247 | pointer-events: auto; 248 | } 249 | 250 | /* visual tweaks */ 251 | 252 | .leaflet-container { 253 | background: #ddd; 254 | outline: 0; 255 | } 256 | .leaflet-container a { 257 | color: #0078A8; 258 | } 259 | .leaflet-container a.leaflet-active { 260 | outline: 2px solid orange; 261 | } 262 | .leaflet-zoom-box { 263 | border: 2px dotted #38f; 264 | background: rgba(255,255,255,0.5); 265 | } 266 | 267 | 268 | /* general typography */ 269 | .leaflet-container { 270 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 271 | } 272 | 273 | 274 | /* general toolbar styles */ 275 | 276 | .leaflet-bar { 277 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 278 | border-radius: 4px; 279 | } 280 | .leaflet-bar a, 281 | .leaflet-bar a:hover { 282 | background-color: #fff; 283 | border-bottom: 1px solid #ccc; 284 | width: 26px; 285 | height: 26px; 286 | line-height: 26px; 287 | display: block; 288 | text-align: center; 289 | text-decoration: none; 290 | color: black; 291 | } 292 | .leaflet-bar a, 293 | .leaflet-control-layers-toggle { 294 | background-position: 50% 50%; 295 | background-repeat: no-repeat; 296 | display: block; 297 | } 298 | .leaflet-bar a:hover { 299 | background-color: #f4f4f4; 300 | } 301 | .leaflet-bar a:first-child { 302 | border-top-left-radius: 4px; 303 | border-top-right-radius: 4px; 304 | } 305 | .leaflet-bar a:last-child { 306 | border-bottom-left-radius: 4px; 307 | border-bottom-right-radius: 4px; 308 | border-bottom: none; 309 | } 310 | .leaflet-bar a.leaflet-disabled { 311 | cursor: default; 312 | background-color: #f4f4f4; 313 | color: #bbb; 314 | } 315 | 316 | .leaflet-touch .leaflet-bar a { 317 | width: 30px; 318 | height: 30px; 319 | line-height: 30px; 320 | } 321 | .leaflet-touch .leaflet-bar a:first-child { 322 | border-top-left-radius: 2px; 323 | border-top-right-radius: 2px; 324 | } 325 | .leaflet-touch .leaflet-bar a:last-child { 326 | border-bottom-left-radius: 2px; 327 | border-bottom-right-radius: 2px; 328 | } 329 | 330 | /* zoom control */ 331 | 332 | .leaflet-control-zoom-in, 333 | .leaflet-control-zoom-out { 334 | font: bold 18px 'Lucida Console', Monaco, monospace; 335 | text-indent: 1px; 336 | } 337 | 338 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 339 | font-size: 22px; 340 | } 341 | 342 | 343 | /* layers control */ 344 | 345 | .leaflet-control-layers { 346 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 347 | background: #fff; 348 | border-radius: 5px; 349 | } 350 | .leaflet-control-layers-toggle { 351 | background-image: url(images/layers.png); 352 | width: 36px; 353 | height: 36px; 354 | } 355 | .leaflet-retina .leaflet-control-layers-toggle { 356 | background-image: url(images/layers-2x.png); 357 | background-size: 26px 26px; 358 | } 359 | .leaflet-touch .leaflet-control-layers-toggle { 360 | width: 44px; 361 | height: 44px; 362 | } 363 | .leaflet-control-layers .leaflet-control-layers-list, 364 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 365 | display: none; 366 | } 367 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 368 | display: block; 369 | position: relative; 370 | } 371 | .leaflet-control-layers-expanded { 372 | padding: 6px 10px 6px 6px; 373 | color: #333; 374 | background: #fff; 375 | } 376 | .leaflet-control-layers-scrollbar { 377 | overflow-y: scroll; 378 | overflow-x: hidden; 379 | padding-right: 5px; 380 | } 381 | .leaflet-control-layers-selector { 382 | margin-top: 2px; 383 | position: relative; 384 | top: 1px; 385 | } 386 | .leaflet-control-layers label { 387 | display: block; 388 | } 389 | .leaflet-control-layers-separator { 390 | height: 0; 391 | border-top: 1px solid #ddd; 392 | margin: 5px -10px 5px -6px; 393 | } 394 | 395 | /* Default icon URLs */ 396 | .leaflet-default-icon-path { 397 | background-image: url(images/marker-icon.png); 398 | } 399 | 400 | 401 | /* attribution and scale controls */ 402 | 403 | .leaflet-container .leaflet-control-attribution { 404 | background: #fff; 405 | background: rgba(255, 255, 255, 0.7); 406 | margin: 0; 407 | } 408 | .leaflet-control-attribution, 409 | .leaflet-control-scale-line { 410 | padding: 0 5px; 411 | color: #333; 412 | } 413 | .leaflet-control-attribution a { 414 | text-decoration: none; 415 | } 416 | .leaflet-control-attribution a:hover { 417 | text-decoration: underline; 418 | } 419 | .leaflet-container .leaflet-control-attribution, 420 | .leaflet-container .leaflet-control-scale { 421 | font-size: 11px; 422 | } 423 | .leaflet-left .leaflet-control-scale { 424 | margin-left: 5px; 425 | } 426 | .leaflet-bottom .leaflet-control-scale { 427 | margin-bottom: 5px; 428 | } 429 | .leaflet-control-scale-line { 430 | border: 2px solid #777; 431 | border-top: none; 432 | line-height: 1.1; 433 | padding: 2px 5px 1px; 434 | font-size: 11px; 435 | white-space: nowrap; 436 | overflow: hidden; 437 | -moz-box-sizing: border-box; 438 | box-sizing: border-box; 439 | 440 | background: #fff; 441 | background: rgba(255, 255, 255, 0.5); 442 | } 443 | .leaflet-control-scale-line:not(:first-child) { 444 | border-top: 2px solid #777; 445 | border-bottom: none; 446 | margin-top: -2px; 447 | } 448 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 449 | border-bottom: 2px solid #777; 450 | } 451 | 452 | .leaflet-touch .leaflet-control-attribution, 453 | .leaflet-touch .leaflet-control-layers, 454 | .leaflet-touch .leaflet-bar { 455 | box-shadow: none; 456 | } 457 | .leaflet-touch .leaflet-control-layers, 458 | .leaflet-touch .leaflet-bar { 459 | border: 2px solid rgba(0,0,0,0.2); 460 | background-clip: padding-box; 461 | } 462 | 463 | 464 | /* popup */ 465 | 466 | .leaflet-popup { 467 | position: absolute; 468 | text-align: center; 469 | margin-bottom: 20px; 470 | } 471 | .leaflet-popup-content-wrapper { 472 | padding: 1px; 473 | text-align: left; 474 | border-radius: 12px; 475 | } 476 | .leaflet-popup-content { 477 | margin: 13px 19px; 478 | line-height: 1.4; 479 | } 480 | .leaflet-popup-content p { 481 | margin: 18px 0; 482 | } 483 | .leaflet-popup-tip-container { 484 | width: 40px; 485 | height: 20px; 486 | position: absolute; 487 | left: 50%; 488 | margin-left: -20px; 489 | overflow: hidden; 490 | pointer-events: none; 491 | } 492 | .leaflet-popup-tip { 493 | width: 17px; 494 | height: 17px; 495 | padding: 1px; 496 | 497 | margin: -10px auto 0; 498 | 499 | -webkit-transform: rotate(45deg); 500 | -moz-transform: rotate(45deg); 501 | -ms-transform: rotate(45deg); 502 | transform: rotate(45deg); 503 | } 504 | .leaflet-popup-content-wrapper, 505 | .leaflet-popup-tip { 506 | background: white; 507 | color: #333; 508 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 509 | } 510 | .leaflet-container a.leaflet-popup-close-button { 511 | position: absolute; 512 | top: 0; 513 | right: 0; 514 | padding: 4px 4px 0 0; 515 | border: none; 516 | text-align: center; 517 | width: 18px; 518 | height: 14px; 519 | font: 16px/14px Tahoma, Verdana, sans-serif; 520 | color: #c3c3c3; 521 | text-decoration: none; 522 | font-weight: bold; 523 | background: transparent; 524 | } 525 | .leaflet-container a.leaflet-popup-close-button:hover { 526 | color: #999; 527 | } 528 | .leaflet-popup-scrolled { 529 | overflow: auto; 530 | border-bottom: 1px solid #ddd; 531 | border-top: 1px solid #ddd; 532 | } 533 | 534 | .leaflet-oldie .leaflet-popup-content-wrapper { 535 | -ms-zoom: 1; 536 | } 537 | .leaflet-oldie .leaflet-popup-tip { 538 | width: 24px; 539 | margin: 0 auto; 540 | 541 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 542 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 543 | } 544 | .leaflet-oldie .leaflet-popup-tip-container { 545 | margin-top: -1px; 546 | } 547 | 548 | .leaflet-oldie .leaflet-control-zoom, 549 | .leaflet-oldie .leaflet-control-layers, 550 | .leaflet-oldie .leaflet-popup-content-wrapper, 551 | .leaflet-oldie .leaflet-popup-tip { 552 | border: 1px solid #999; 553 | } 554 | 555 | 556 | /* div icon */ 557 | 558 | .leaflet-div-icon { 559 | background: #fff; 560 | border: 1px solid #666; 561 | } 562 | 563 | 564 | /* Tooltip */ 565 | /* Base styles for the element that has a tooltip */ 566 | .leaflet-tooltip { 567 | position: absolute; 568 | padding: 6px; 569 | background-color: #fff; 570 | border: 1px solid #fff; 571 | border-radius: 3px; 572 | color: #222; 573 | white-space: nowrap; 574 | -webkit-user-select: none; 575 | -moz-user-select: none; 576 | -ms-user-select: none; 577 | user-select: none; 578 | pointer-events: none; 579 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 580 | } 581 | .leaflet-tooltip.leaflet-clickable { 582 | cursor: pointer; 583 | pointer-events: auto; 584 | } 585 | .leaflet-tooltip-top:before, 586 | .leaflet-tooltip-bottom:before, 587 | .leaflet-tooltip-left:before, 588 | .leaflet-tooltip-right:before { 589 | position: absolute; 590 | pointer-events: none; 591 | border: 6px solid transparent; 592 | background: transparent; 593 | content: ""; 594 | } 595 | 596 | /* Directions */ 597 | 598 | .leaflet-tooltip-bottom { 599 | margin-top: 6px; 600 | } 601 | .leaflet-tooltip-top { 602 | margin-top: -6px; 603 | } 604 | .leaflet-tooltip-bottom:before, 605 | .leaflet-tooltip-top:before { 606 | left: 50%; 607 | margin-left: -6px; 608 | } 609 | .leaflet-tooltip-top:before { 610 | bottom: 0; 611 | margin-bottom: -12px; 612 | border-top-color: #fff; 613 | } 614 | .leaflet-tooltip-bottom:before { 615 | top: 0; 616 | margin-top: -12px; 617 | margin-left: -6px; 618 | border-bottom-color: #fff; 619 | } 620 | .leaflet-tooltip-left { 621 | margin-left: -6px; 622 | } 623 | .leaflet-tooltip-right { 624 | margin-left: 6px; 625 | } 626 | .leaflet-tooltip-left:before, 627 | .leaflet-tooltip-right:before { 628 | top: 50%; 629 | margin-top: -6px; 630 | } 631 | .leaflet-tooltip-left:before { 632 | right: 0; 633 | margin-right: -12px; 634 | border-left-color: #fff; 635 | } 636 | .leaflet-tooltip-right:before { 637 | left: 0; 638 | margin-left: -12px; 639 | border-right-color: #fff; 640 | } 641 | -------------------------------------------------------------------------------- /docs/js/graph-draw-bundle.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).graphDraw=t()}(function(){return function i(r,n,s){function o(a,t){if(!n[a]){if(!r[a]){var e="function"==typeof require&&require;if(!t&&e)return e(a,!0);if(h)return h(a,!0);throw(t=new Error("Cannot find module '"+a+"'")).code="MODULE_NOT_FOUND",t}e=n[a]={exports:{}},r[a][0].call(e.exports,function(t){return o(r[a][1][t]||t)},e,e.exports,i,r,n,s)}return n[a].exports}for(var h="function"==typeof require&&require,t=0;tMath.abs(t[0])&&(a=1),a=Math.abs(t[2])>Math.abs(t[a])?2:a}var I=4e150;function U(t,a){t.f+=a.f,t.b.f+=a.b.f}function f(t,a,e){return t=t.a,a=a.a,e=e.a,a.b.a===t?e.b.a===t?v(a.a,e.a)?E(e.b.a,a.a,e.a)<=0:0<=E(a.b.a,e.a,a.a):E(e.b.a,t,e.a)<=0:e.b.a===t?0<=E(a.b.a,t,a.a):(a=g(a.b.a,t,a.a),(t=g(e.b.a,t,e.a))<=a)}function w(t){t.a.i=null;var a=t.e;a.a.c=a.c,a.c.a=a.a,t.e=null}function O(t,a){_(t.a),t.c=!1,(t.a=a).i=t}function k(t){for(var a=t.a.a;(t=q(t)).a.a===a;);return t.c&&(O(t,a=y(V(t).a.b,t.a.e)),t=q(t)),t}function z(t,a,e){var i=new j;return i.a=e,i.e=d(t.f,a.e,i),e.i=i}function H(t,a){switch(t.s){case 100130:return 0!=(1&a);case 100131:return 0!==a;case 100132:return 0>1]],o[s[c]])?B:bt)(e,c),o[n]=null,h[n]=e.b,e.b=n}else for(e.c[-(n+1)]=null;0Math.max(s.a,h.a))){if(v(n,s)){if(0>1,h=e[o];if(0==o||v(i[h],i[s])){r[e[n]=s]=n;break}r[e[n]=h]=n,n=o}}function j(){this.e=this.a=null,this.f=0,this.c=this.b=this.h=this.d=!1}function V(t){return t.e.c.b}function q(t){return t.e.a.b}(t=a.prototype).x=function(){R(this,0)},t.B=function(t,a){switch(t){case 100142:return;case 100140:switch(a){case 100130:case 100131:case 100132:case 100133:case 100134:return void(this.s=a)}break;case 100141:return void(this.m=!!a);default:return void h(this,100900)}h(this,100901)},t.y=function(t){switch(t){case 100142:return 0;case 100140:return this.s;case 100141:return this.m;default:h(this,100900)}return!1},t.A=function(t,a,e){this.j[0]=t,this.j[1]=a,this.j[2]=e},t.z=function(t,a){var e=a||null;switch(t){case 100100:case 100106:this.h=e;break;case 100104:case 100110:this.l=e;break;case 100101:case 100107:this.k=e;break;case 100102:case 100108:this.i=e;break;case 100103:case 100109:this.p=e;break;case 100105:case 100111:this.o=e;break;case 100112:this.r=e;break;default:h(this,100900)}},t.C=function(t,a){var e=!1,i=[0,0,0];R(this,2);for(var r=0;r<3;++r){var n=t[r];n<-1e150&&(n=-1e150,e=!0),1e150i[c]&&(i[c]=b,r[c]=e)}if(i[e=i[e=i[1]-o[1]>i[e=0]-o[0]?1:e]-o[e]function(t,a){return v(e[t],e[a])?1:-1})(u.c)),u.e=!0;for(var l=u.b,d=l.a;1<=d;--d)B(l,d);for(l.h=!0,this.f=new tt(this),$(this,-I),$(this,I);null!==(s=st(this.e));){for(;;){t:if(0===(e=this.e).a)n=P(e.b);else if(n=e.c[e.d[e.a-1]],0!==e.b.a&&v(e=P(e.b),n)){n=e;break t}if(null===n||!p(n,s))break;n=st(this.e),D(this,s.c,n.c)}!function t(a,e){for(var i,r=(a.a=e).c;null===r.i;)if((r=r.c)===e.c){var r=a,n=e;(b=new j).a=n.c.b;for(var s=(o=r.f).a;null!==(s=s.a).b&&!o.c(o.b,b,s.b););var o,h,c=V(o=s.b),b=o.a,s=c.a;return void(0===E(b.b.a,n,b.a)?p((b=o.a).a,n)||p(b.b.a,n)||(L(b.b),o.c&&(_(b.c),o.c=!1),G(n.c,b),t(r,n)):(h=v(s.b.a,b.b.a)?o:c,c=void 0,o.d||h.c?(c=h===o?y(n.c.b,b.e):y(s.b.c.b,n.c).b,h.c?O(h,c):((o=z(b=r,o,c)).f=q(o).f+o.a.f,o.d=H(b,o.f)),t(r,n)):A(r,o,n.c,n.c,null,!0)))}o=(b=V(r=k(r.i))).a,(b=x(a,b,null)).c===o?(b=(o=b).c,s=V(r),c=r.a,h=s.a,i=!1,c.b.a!==h.b.a&&Z(a,r),p(c.a,a.a)&&(G(b.b.e,c),b=V(r=k(r)).a,x(a,V(r),s),i=!0),p(h.a,a.a)&&(G(o,h.b.e),o=x(a,s,null),i=!0),i?A(a,r,o.c,b,b,!0):(n=v(h.a,c.a)?h.b.e:c,A(a,r,n=y(o.c.b,n),n.c,n.c,!1),n.b.i.c=!0,J(a,r))):A(a,r,b.c,o,o,!0)}(this,s)}for(this.a=this.f.a.a.b.a.a,s=0;null!==(n=this.f.a.a.b);)n.h||++s,w(n);for(this.f=null,(s=this.e).b=null,s.d=null,this.e=s.c=null,s=this.b,e=s.a.b;e!==s.a;e=n)n=e.b,(e=e.a).e.e===e&&(U(e.c,e),_(e));if(!this.n){if(s=this.b,this.m)for(e=s.b.h;e!==s.b;e=n)n=e.h,e.b.d.c!==e.d.c?e.f=e.d.c?1:-1:_(e);else for(e=s.a.b;e!==s.a;e=n)if(n=e.b,e.c){for(e=e.a;v(e.b.a,e.a);e=e.c.b);for(;v(e.a,e.b.a);e=e.e);for(i=e.c.b,r=void 0;e.e!==i;)if(v(e.b.a,i.a)){for(;i.e!==e&&(v((a=i.e).b.a,a.a)||E(i.a,i.b.a,i.e.b.a)<=0);)i=(r=y(i.e,i)).b;i=i.c.b}else{for(;i.e!==e&&(v((t=e.c.b).a,t.b.a)||0<=E(e.b.a,e.a,e.c.b.a));)e=(r=y(e,e.c.b)).b;e=e.e}for(;i.e.e!==e;)r=y(i.e,i),i=r.b}if(this.h||this.i||this.k||this.l)if(this.m){for(n=(s=this.b).a.b;n!==s.a;n=n.b)if(n.c){for(this.h&&this.h(2,this.c),e=n.a;this.k&&this.k(e.a.d,this.c),(e=e.e)!==n.a;);this.i&&this.i(this.c)}}else{for(s=this.b,n=!!this.l,e=!1,i=-1,r=s.a.d;r!==s.a;r=r.d)if(r.c)for(e||(this.h&&this.h(4,this.c),e=!0),h=r.a;n&&(o=h.b.d.c?0:1,i!==o)&&(i=o,this.l)&&this.l(!!i,this.c),this.k&&this.k(h.a.d,this.c),(h=h.e)!==r.a;);e&&this.i&&this.i(this.c)}if(this.r){for(s=this.b,e=s.a.b;e!==s.a;e=n)if(n=e.b,!e.c){for(r=(i=e.a).e,h=void 0;r=(h=r).e,(h.d=null)===h.b.d&&(h.c===h?m(h.a,null):(h.a.c=h.c,N(h,h.b.e)),(o=h.b).c===o?m(o.a,null):(o.a.c=o.c,N(o,o.b.e)),F(h)),h!==i;);i=e.d,((e=e.b).d=i).b=e}return this.r(this.b),void(this.c=this.b=null)}}this.b=this.c=null},this.libtess={GluTesselator:a,windingRule:{GLU_TESS_WINDING_ODD:100130,GLU_TESS_WINDING_NONZERO:100131,GLU_TESS_WINDING_POSITIVE:100132,GLU_TESS_WINDING_NEGATIVE:100133,GLU_TESS_WINDING_ABS_GEQ_TWO:100134},primitiveType:{GL_LINE_LOOP:2,GL_TRIANGLES:4,GL_TRIANGLE_STRIP:5,GL_TRIANGLE_FAN:6},errorType:{GLU_TESS_MISSING_BEGIN_POLYGON:100151,GLU_TESS_MISSING_END_POLYGON:100153,GLU_TESS_MISSING_BEGIN_CONTOUR:100152,GLU_TESS_MISSING_END_CONTOUR:100154,GLU_TESS_COORD_TOO_LARGE:100155,GLU_TESS_NEED_COMBINE_CALLBACK:100156},gluEnum:{GLU_TESS_MESH:100112,GLU_TESS_TOLERANCE:100142,GLU_TESS_WINDING_RULE:100140,GLU_TESS_BOUNDARY_ONLY:100141,GLU_INVALID_ENUM:100900,GLU_INVALID_VALUE:100901,GLU_TESS_BEGIN:100100,GLU_TESS_VERTEX:100101,GLU_TESS_END:100102,GLU_TESS_ERROR:100103,GLU_TESS_EDGE_FLAG:100104,GLU_TESS_COMBINE:100105,GLU_TESS_BEGIN_DATA:100106,GLU_TESS_VERTEX_DATA:100107,GLU_TESS_END_DATA:100108,GLU_TESS_ERROR_DATA:100109,GLU_TESS_EDGE_FLAG_DATA:100110,GLU_TESS_COMBINE_DATA:100111}},a.prototype.gluDeleteTess=a.prototype.x,a.prototype.gluTessProperty=a.prototype.B,a.prototype.gluGetTessProperty=a.prototype.y,a.prototype.gluTessNormal=a.prototype.A,a.prototype.gluTessCallback=a.prototype.z,a.prototype.gluTessVertex=a.prototype.C,a.prototype.gluTessBeginPolygon=a.prototype.u,a.prototype.gluTessBeginContour=a.prototype.t,a.prototype.gluTessEndContour=a.prototype.v,a.prototype.gluTessEndPolygon=a.prototype.w,void 0!==i&&(i.exports=this.libtess)},{}],2:[function(t,a,e){var r=new(t("./tessellator"));function n(){this.list=[],this.hash=Object.create(null)}function p(t,a){t=s(t),a=s(a);t!==a&&(t.rankE?(h[_].first=b,c[_].last=f,r=[d[0]-p[0],d[1]-p[1]],n=1/Math.sqrt(r[0]*r[0]+r[1]*r[1]),h[_].miter_first=c[_].miter_last=[p[0]+T*r[0]*n,p[1]+T*r[1]*n]):(h[_].first=d,c[_].last=d),2===g&&(h[_].remove_middle_first=!0,c[_].remove_middle_last=!0)):(s=[d[0]-t.ortho[0],d[1]-t.ortho[1]],u=[d[0]+a.ortho[0],d[1]+a.ortho[1]],G.push({done:[t.index,a.index],todo:[_,t.to,a.to],rectangles:[[b,d,s,p],[f,d,u,p]]}),h[_].first=b,c[_].last=f)})}),S.forEach(function(t){var a,e,i=t.edge,r=i[0],i=i[1],n=t.points[r],s=t.points[i],r=d[r].coords,i=d[i].coords,t=t.polygon=[];L(n.last,s.first,r,i)<0?(a=n.last_vertex,e=s.first_vertex,p(S[a],S[e]),t.push(o(n.miter_last||r,n.last,s.first,s.miter_first||i))):t.push(n.last,s.first),s.miter_first&&t.push(s.miter_first),s.remove_middle_first&&s.remove_middle_last||t.push(i),s.miter_last&&t.push(s.miter_last),L(n.first,s.last,r,i)<0?(a=n.first_vertex,e=s.last_vertex,p(S[a],S[e]),t.push(o(n.first,n.miter_first||r,s.miter_last||i,s.last))):t.push(s.last,n.first),n.miter_first&&t.push(n.miter_first),n.remove_middle_first&&n.remove_middle_last||t.push(r),n.miter_last&&t.push(n.miter_last)}),Object.create(null)),i=(G.forEach(function(t){for(var h,c=Object.create(null),a=t.done[0],e=t.done[1],b=S[a],i=S[e],u=(p(b,i),c[a]=!0,c[e]=!0,new n),f=(t.todo.forEach(function(t){u.push(t)}),t.rectangles[0]),l=t.rectangles[1];null!==(h=u.pop());)d[h].neighList.forEach(function(t){var a,e,i,r,n,s,o=t.index;!c[o]&&(a=t.to,(s=_[o])||(n=d[h].coords,r=d[a].coords,e=[n[0]+t.ortho[0],n[1]+t.ortho[1]],i=[r[0]+t.ortho[0],r[1]+t.ortho[1]],r=[r[0]-t.ortho[0],r[1]-t.ortho[1]],n=[n[0]-t.ortho[0],n[1]-t.ortho[1]],s=_[o]=[e,i,r,n]),c[o]=!0,v(s,f)||v(s,l))&&(p(b,S[o]),u.push(a))})}),[]);S.forEach(function(t,a){0