├── LICENSE ├── README.md ├── readme-imgs ├── in-usage.png └── poles-algo.png └── src └── LabelPoint.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 George Gardiner 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LabelPoint 2 | Javascript class to find the optimum placement of a label inside an irregular polygon using Poles of Inaccessibility algorithm. Handy if you're working with maps. 3 | 4 | # How It Works 5 | The Poles of Inaccessibility algorithm works by finding a point inside the polygon that is furthest from the perimeter. 6 | 7 | 8 | 9 | # Usage 10 | Just pass in an array of points and a desired precision (defaults to 1 - pixel perfect. Increase for speed at reduced accuracy) 11 | ``` 12 | var points = [ {x:0, y:0}, {x:5, y:0}, {x:5, y:5}, {x:0, y:5} ]; 13 | var precision = 1; 14 | var labelPoint = LabelPoint.find(points, precision); 15 | console.log(labelPoint.x, labelPoint.y); 16 | ``` 17 | 18 | Originally created for Ticketlights' seating map editor. Great partner for the excellent hull.js from https://github.com/AndriiHeonia/hull 19 | 20 | 21 | 22 | George Gardiner @ http://www.commonmode.co.uk 23 | -------------------------------------------------------------------------------- /readme-imgs/in-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgeGardiner/LabelPoint/48f9e4d62d450a3c1aad6c4e356cbb3c065e900a/readme-imgs/in-usage.png -------------------------------------------------------------------------------- /readme-imgs/poles-algo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgeGardiner/LabelPoint/48f9e4d62d450a3c1aad6c4e356cbb3c065e900a/readme-imgs/poles-algo.png -------------------------------------------------------------------------------- /src/LabelPoint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LabelPoint.js 3 | * 4 | * Class to find the optimum placement for a label inside an irregular polygon using 5 | * Poles of Inaccessibility algorithm. 6 | * 7 | * George Gardiner 8 | * www.commonmode.co.uk 9 | */ 10 | 11 | (function (window, undefined) { 12 | var LabelPoint = { 13 | find: function (points, precision) { 14 | if (precision == undefined) { 15 | precision = 1; 16 | } 17 | var x_min, y_min, x_max, y_max; 18 | for (var i = 0; i < points.length; i++) { 19 | if (i == 0) { 20 | x_min = x_max = points[i].x; 21 | y_min = y_max = points[i].y; 22 | } 23 | else { 24 | x_min = Math.min(x_min, points[i].x); 25 | x_max = Math.max(x_max, points[i].x); 26 | y_min = Math.min(y_min, points[i].y); 27 | y_max = Math.max(y_max, points[i].y); 28 | } 29 | } 30 | var lp = this.poleScan(x_min, y_min, x_max, y_max, points); 31 | if (precision > 0) { 32 | var r = ((x_max - x_min) * (y_max - y_min)); 33 | var dx, dy; 34 | while (r > precision) { 35 | lp = this.poleScan(x_min, y_min, x_max, y_max, points); 36 | dx = (x_max - x_min) / 24; 37 | dy = (y_max - y_min) / 24; 38 | x_min = lp.x - dx; 39 | x_max = lp.x + dx; 40 | y_min = lp.y - dy; 41 | y_max = lp.y + dy; 42 | r = dx * dy; 43 | } 44 | } 45 | return lp; 46 | }, 47 | pointToLineDistance: function (x, y, x1, y1, x2, y2) { 48 | var A = x - x1; 49 | var B = y - y1; 50 | var C = x2 - x1; 51 | var D = y2 - y1; 52 | var dot = A * C + B * D; 53 | var len_sq = C * C + D * D; 54 | var param = -1; 55 | if (len_sq != 0) { 56 | param = dot / len_sq; 57 | } 58 | var xx, yy; 59 | if (param < 0) { 60 | xx = x1; 61 | yy = y1; 62 | } 63 | else if (param > 1) { 64 | xx = x2; 65 | yy = y2; 66 | } 67 | else { 68 | xx = x1 + param * C; 69 | yy = y1 + param * D; 70 | } 71 | var dx = x - xx; 72 | var dy = y - yy; 73 | return Math.sqrt(dx * dx + dy * dy); 74 | }, 75 | pointToPerimeterDistance: function (x, y, points) { 76 | var d, p1, p2, minDistance; 77 | for (var i = 0; i < points.length; i++) { 78 | p1 = points[i]; 79 | if ((i + 1) < points.length) { 80 | p2 = points[i + 1]; 81 | } 82 | else { 83 | p2 = points[0]; 84 | } 85 | d = this.pointToLineDistance(x, y, p1.x, p1.y, p2.x, p2.y); 86 | if (i == 0) { 87 | minDistance = d; 88 | } 89 | else { 90 | if (d < minDistance) { 91 | minDistance = d; 92 | } 93 | } 94 | } 95 | return minDistance; 96 | }, 97 | isInside: function (x, y, points) { 98 | for (var c = false, i = -1, l = points.length, j = l - 1; ++i < l; j = i) 99 | ((points[i].y <= y && y < points[j].y) || (points[j].y <= y && y < points[i].y)) 100 | && (x < (points[j].x - points[i].x) * (y - points[i].y) / (points[j].y - points[i].y) + points[i].x) 101 | && (c = !c); 102 | return c; 103 | }, 104 | poleScan: function (x_min, y_min, x_max, y_max, points) { 105 | var px, py, pd, maxDistance = 0; 106 | for (var y = y_min; y < y_max; y += ((y_max - y_min) / 24)) { 107 | for (var x = x_min; x < x_max; x += ((x_max - x_min) / 24)) { 108 | if (this.isInside(x, y, points)) { 109 | pd = this.pointToPerimeterDistance(x, y, points); 110 | if (pd > maxDistance) { 111 | maxDistance = pd; 112 | px = x; 113 | py = y; 114 | } 115 | } 116 | } 117 | } 118 | return { 119 | x: px, 120 | y: py 121 | }; 122 | } 123 | }; 124 | window.LabelPoint = LabelPoint; 125 | })(window); 126 | --------------------------------------------------------------------------------