├── 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 |
--------------------------------------------------------------------------------