├── docs
├── res
│ ├── example1.png
│ └── gerbs
│ │ ├── RU-AD.png
│ │ ├── RU-AL.png
│ │ ├── RU-BA.png
│ │ ├── RU-BU.png
│ │ ├── RU-CE.png
│ │ ├── RU-CU.png
│ │ ├── RU-DA.png
│ │ ├── RU-IN.png
│ │ ├── RU-KB.png
│ │ ├── RU-KC.png
│ │ ├── RU-KK.png
│ │ ├── RU-KL.png
│ │ ├── RU-KO.png
│ │ ├── RU-KR.png
│ │ ├── RU-ME.png
│ │ ├── RU-MO.png
│ │ ├── RU-SA.png
│ │ ├── RU-SE.png
│ │ ├── RU-TA.png
│ │ ├── RU-TY.png
│ │ ├── RU-UD.png
│ │ ├── UA-40.png
│ │ ├── UA-43.png
│ │ ├── RU-ALT.png
│ │ ├── RU-AMU.png
│ │ ├── RU-ARK.png
│ │ ├── RU-AST.png
│ │ ├── RU-BEL.png
│ │ ├── RU-BRY.png
│ │ ├── RU-CHE.png
│ │ ├── RU-CHU.png
│ │ ├── RU-IRK.png
│ │ ├── RU-IVA.png
│ │ ├── RU-KAM.png
│ │ ├── RU-KDA.png
│ │ ├── RU-KEM.png
│ │ ├── RU-KGD.png
│ │ ├── RU-KGN.png
│ │ ├── RU-KHA.png
│ │ ├── RU-KHM.png
│ │ ├── RU-KIR.png
│ │ ├── RU-KLU.png
│ │ ├── RU-KOS.png
│ │ ├── RU-KRS.png
│ │ ├── RU-KRY.png
│ │ ├── RU-KYA.png
│ │ ├── RU-LEN.png
│ │ ├── RU-LIP.png
│ │ ├── RU-MAG.png
│ │ ├── RU-MOS.png
│ │ ├── RU-MOW.png
│ │ ├── RU-MUR.png
│ │ ├── RU-NEN.png
│ │ ├── RU-NGR.png
│ │ ├── RU-NIZ.png
│ │ ├── RU-NVS.png
│ │ ├── RU-OMS.png
│ │ ├── RU-ORE.png
│ │ ├── RU-ORL.png
│ │ ├── RU-PER.png
│ │ ├── RU-PNZ.png
│ │ ├── RU-PRI.png
│ │ ├── RU-PSK.png
│ │ ├── RU-ROS.png
│ │ ├── RU-RYA.png
│ │ ├── RU-SAK.png
│ │ ├── RU-SAM.png
│ │ ├── RU-SAR.png
│ │ ├── RU-SEV.png
│ │ ├── RU-SMO.png
│ │ ├── RU-SPE.png
│ │ ├── RU-STA.png
│ │ ├── RU-SVE.png
│ │ ├── RU-TAM.png
│ │ ├── RU-TOM.png
│ │ ├── RU-TUL.png
│ │ ├── RU-TVE.png
│ │ ├── RU-TYU.png
│ │ ├── RU-ULY.png
│ │ ├── RU-VGG.png
│ │ ├── RU-VLA.png
│ │ ├── RU-VLG.png
│ │ ├── RU-VOR.png
│ │ ├── RU-YAN.png
│ │ ├── RU-YAR.png
│ │ ├── RU-YEV.png
│ │ └── RU-ZAB.png
├── example-text
│ └── index.html
└── example-img
│ └── index.html
├── .eslintignore
├── .gitignore
├── src
├── util
│ ├── templateFilterStorage
│ │ └── dotColor.js
│ ├── transformHexToRGB.js
│ ├── getPolygonWithMaxArea.js
│ ├── State.js
│ ├── zoom
│ │ ├── setZoomVisibility.js
│ │ ├── parseZoomData.js
│ │ └── getFirstZoomInside.js
│ ├── transformPolygonCoords.js
│ ├── checkPointPosition.js
│ └── getPolesOfInaccessibility.js
├── label
│ ├── util
│ │ ├── getLayoutSize.js
│ │ ├── layoutTemplates
│ │ │ ├── getBaseLayoutTemplates.js
│ │ │ ├── dotLayoutTemplate.js
│ │ │ ├── labelLayoutTemplate.js
│ │ │ └── createLayoutTemplates.js
│ │ ├── classHelper.js
│ │ ├── LabelPlacemarkOverlay.js
│ │ └── LabelData.js
│ ├── LabelBase.js
│ ├── GeoObjectCollection
│ │ └── Label.js
│ └── ObjectManager
│ │ └── Label.js
├── config.js
├── create.js
└── polylabel
│ ├── PolylabelBase.js
│ ├── PolylabelCollection.js
│ └── PolylabelObjectManager.js
├── package.json
├── .eslintrc.js
├── LICENSE.md
├── Gruntfile.js
├── .babelrc
└── README.md
/docs/res/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/example1.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-AD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-AD.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-AL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-AL.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-BA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-BA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-BU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-BU.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-CE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-CE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-CU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-CU.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-DA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-DA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-IN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-IN.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KB.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KC.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KK.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KL.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KO.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ME.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ME.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-MO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-MO.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TY.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-UD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-UD.png
--------------------------------------------------------------------------------
/docs/res/gerbs/UA-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/UA-40.png
--------------------------------------------------------------------------------
/docs/res/gerbs/UA-43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/UA-43.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ALT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ALT.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-AMU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-AMU.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ARK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ARK.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-AST.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-AST.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-BEL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-BEL.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-BRY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-BRY.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-CHE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-CHE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-CHU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-CHU.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-IRK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-IRK.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-IVA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-IVA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KAM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KAM.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KDA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KDA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KEM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KEM.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KGD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KGD.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KGN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KGN.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KHA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KHA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KHM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KHM.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KIR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KIR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KLU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KLU.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KOS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KOS.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KRS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KRS.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KRY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KRY.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-KYA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-KYA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-LEN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-LEN.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-LIP.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-LIP.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-MAG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-MAG.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-MOS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-MOS.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-MOW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-MOW.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-MUR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-MUR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-NEN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-NEN.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-NGR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-NGR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-NIZ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-NIZ.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-NVS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-NVS.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-OMS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-OMS.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ORE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ORE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ORL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ORL.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-PER.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-PER.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-PNZ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-PNZ.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-PRI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-PRI.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-PSK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-PSK.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ROS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ROS.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-RYA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-RYA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SAK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SAK.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SAM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SAM.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SAR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SAR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SEV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SEV.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SMO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SMO.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SPE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SPE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-STA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-STA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-SVE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-SVE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TAM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TAM.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TOM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TOM.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TUL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TUL.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TVE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TVE.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-TYU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-TYU.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ULY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ULY.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-VGG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-VGG.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-VLA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-VLA.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-VLG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-VLG.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-VOR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-VOR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-YAN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-YAN.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-YAR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-YAR.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-YEV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-YEV.png
--------------------------------------------------------------------------------
/docs/res/gerbs/RU-ZAB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandex/mapsapi-polylabeler/master/docs/res/gerbs/RU-ZAB.png
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | cases/
3 | node_modules/
4 | Gruntfile.js
5 | src/calculateArea.min.js
6 | karma.conf.js
7 | builder/
8 | test/
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build
3 | cases/
4 | .idea
5 | polylabel/
6 | mapsapi-polylabeler/
7 | npm-debug.log
8 | ._.DS_Store
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/src/util/templateFilterStorage/dotColor.js:
--------------------------------------------------------------------------------
1 | import transformHexToRGB from '../transformHexToRGB';
2 | import CONFIG from '../../config';
3 |
4 | export default function (data, dateString) {
5 | return transformHexToRGB(dateString || CONFIG.DEFAULT_POLYGON_FILL_COLOR, 0.9);
6 | }
7 |
--------------------------------------------------------------------------------
/src/label/util/getLayoutSize.js:
--------------------------------------------------------------------------------
1 | export default function (layout) {
2 | let el = layout && layout.getElement();
3 | if (!el) return;
4 |
5 | el = el.getElementsByClassName('ymaps-polylabel-view')[0];
6 | if (!el) return;
7 |
8 | const {width, height} = el.getBoundingClientRect();
9 | return {width, height};
10 | }
11 |
--------------------------------------------------------------------------------
/src/label/util/layoutTemplates/getBaseLayoutTemplates.js:
--------------------------------------------------------------------------------
1 | import labelLayoutTemplate from './labelLayoutTemplate';
2 | import dotLayoutTemplate from './dotLayoutTemplate';
3 |
4 | /**
5 | * Returns basic wrappers over templates
6 | */
7 | export default function () {
8 | return {
9 | label: labelLayoutTemplate,
10 | dot: dotLayoutTemplate
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/label/util/layoutTemplates/dotLayoutTemplate.js:
--------------------------------------------------------------------------------
1 | import templateLayoutFactory from 'api/templateLayoutFactory';
2 |
3 | const template = templateLayoutFactory.createClass(
4 | `
6 | {% include options.labelDotTemplateLayout %}
7 |
`
8 | );
9 |
10 | export default template;
11 |
--------------------------------------------------------------------------------
/src/util/transformHexToRGB.js:
--------------------------------------------------------------------------------
1 | export default function (hex, opacity) {
2 | if (!hex) return;
3 | if (hex.indexOf('rgb') !== -1) return hex;
4 | hex = hex[0] !== '#' ? `#${hex}` : hex;
5 | hex = hex.slice(0, 7);
6 | let c;
7 | if (!/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) return;
8 |
9 | c = hex.substring(1).split('');
10 | if (c.length === 3) {
11 | c = [c[0], c[0], c[1], c[1], c[2], c[2]];
12 | }
13 | c = '0x' + c.join('');
14 | return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')}, ${opacity || 1})`;
15 | }
16 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | MIN_ZOOM: 0,
3 | MAX_ZOOM: 23,
4 | DEFAULT_POLYGON_FILL_COLOR: '0066ff99',
5 | options: [
6 | 'labelLayout',
7 | 'labelDotLayout',
8 | 'labelDotVisible',
9 | 'labelDefaults',
10 | 'labelCursor',
11 | 'labelDotCursor'
12 | ],
13 | zoomRangeOptions: [
14 | 'labelClassName',
15 | 'labelForceVisible',
16 | 'labelTextColor',
17 | 'labelTextSize',
18 | 'labelCenterCoords',
19 | 'labelOffset',
20 | 'labelPermissibleInaccuracyOfVisibility'
21 | ],
22 | properties: []
23 | };
24 |
--------------------------------------------------------------------------------
/src/label/util/layoutTemplates/labelLayoutTemplate.js:
--------------------------------------------------------------------------------
1 | import templateLayoutFactory from 'api/templateLayoutFactory';
2 |
3 | const template = templateLayoutFactory.createClass(`
4 |
6 |
9 | {% include options.labelTemplateLayout %}
10 |
11 |
`
12 | );
13 |
14 | export default template;
15 |
--------------------------------------------------------------------------------
/src/label/util/layoutTemplates/createLayoutTemplates.js:
--------------------------------------------------------------------------------
1 | import templateLayoutFactory from 'api/templateLayoutFactory';
2 |
3 | /**
4 | * Creates custom templates
5 | */
6 | export default function (labelLayout, labelDotLayout) {
7 | const dotDefault = ``;
10 | return {
11 | label: {
12 | iconLabelTemplateLayout: templateLayoutFactory.createClass(labelLayout)
13 | },
14 | dot: {
15 | iconLabelDotTemplateLayout: templateLayoutFactory.createClass(labelDotLayout || dotDefault)
16 | }
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mapsapi-polylabeler",
3 | "version": "1.0.2",
4 | "description": "",
5 | "scripts": {
6 | "build": "grunt build",
7 | "lint": "eslint ."
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {},
12 | "devDependencies": {
13 | "babel-core": "^6.26.0",
14 | "babel-plugin-transform-class-properties": "^6.24.1",
15 | "babel-plugin-transform-es2015-modules-ym": "^0.3.0",
16 | "babel-preset-env": "^1.6.0",
17 | "eslint": "^4.5.0",
18 | "glob": "^7.1.2",
19 | "grunt": "1.0.1",
20 | "grunt-babel": "^7.0.0",
21 | "grunt-contrib-concat": "^1.0.1",
22 | "grunt-contrib-uglify": "3.0.1",
23 | "grunt-contrib-watch": "1.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "loris/es5",
3 | "root": true,
4 | "env": {
5 | "browser": true,
6 | "es6": true,
7 | "mocha": true
8 | },
9 | "rules": {
10 | "strict": ["error", "never"],
11 | "curly": [0, "multi"],
12 | "no-console": ["error", { allow: ["error"] }],
13 | "no-implicit-globals": 0,
14 | "no-cond-assign": 0
15 | },
16 | "globals": {
17 | "modules": true,
18 | "module": true,
19 | "ymaps": true,
20 | "chai": true,
21 | "describe": true,
22 | "modules": true,
23 | "expect": true,
24 | "it": true,
25 | "before": true,
26 | "after": true
27 | },
28 | "parserOptions": {
29 | "sourceType": "module"
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/src/util/getPolygonWithMaxArea.js:
--------------------------------------------------------------------------------
1 | import calculateArea from 'api/util.calculateArea';
2 | import GeoObject from 'api/GeoObject';
3 |
4 | export default function (polygonCoords) {
5 | if (typeof calculateArea === 'undefined') {
6 | throw new Error('Didn\'t find calculateArea module');
7 | }
8 | let maxArea = Number.MIN_VALUE;
9 | let indexOfMaxArea = 0;
10 | for (let i = 0; i < polygonCoords.length; i++) {
11 | let polygon = new GeoObject({
12 | geometry: {
13 | type: 'Polygon', coordinates: [polygonCoords[i]]
14 | }
15 | });
16 | let area = Math.round(calculateArea(polygon));
17 | if (maxArea < area) {
18 | maxArea = area;
19 | indexOfMaxArea = i;
20 | }
21 | }
22 | return polygonCoords[indexOfMaxArea];
23 | }
24 |
--------------------------------------------------------------------------------
/src/util/State.js:
--------------------------------------------------------------------------------
1 | import DataManager from 'api/data.Manager';
2 |
3 | export default class State {
4 | constructor() {
5 | this._state = new WeakMap();
6 | }
7 |
8 | _getDataManager(polygon) {
9 | let dataManager = this._state.get(polygon);
10 | if (!dataManager) {
11 | dataManager = new DataManager();
12 | this._state.set(polygon, dataManager);
13 | }
14 | return dataManager;
15 | }
16 |
17 | set(polygon, key, value) {
18 | const dataManager = this._getDataManager(polygon);
19 | dataManager.set(key, value);
20 | }
21 |
22 | get(polygon, key) {
23 | const dataManager = this._getDataManager(polygon);
24 | if (dataManager) {
25 | return dataManager.get(key);
26 | }
27 | }
28 |
29 | getState(polygon) {
30 | return this._getDataManager(polygon);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/label/util/classHelper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {*} layout
3 | * @param {*} className - class by which we find the element
4 | * @param {*} newClassName - new class
5 | */
6 | function add(layout, className, newClassName) {
7 | const el = getElemByClass(layout, className);
8 | if (!el) return;
9 |
10 | el.classList.add(newClassName);
11 | }
12 |
13 | /**
14 | * @param {*} layout
15 | * @param {*} className - class by which we find the element
16 | * @param {*} removeClassName - class to remove
17 | */
18 | function remove(layout, className, removeClassName) {
19 | const el = getElemByClass(layout, className);
20 | if (!el) return;
21 |
22 | el.classList.remove(removeClassName);
23 | }
24 |
25 | function getElemByClass(layout, className) {
26 | if (!layout) return;
27 | let el = layout.getElement();
28 | if (!el) return;
29 |
30 | return el.getElementsByClassName(className)[0];
31 | }
32 |
33 | export default {
34 | add,
35 | remove
36 | };
37 |
--------------------------------------------------------------------------------
/src/label/util/LabelPlacemarkOverlay.js:
--------------------------------------------------------------------------------
1 | import overlayPlacemark from 'api/overlay.Placemark';
2 | import GeoObject from 'api/GeoObject';
3 |
4 | /**
5 | * Puts the data into a label from the polygon
6 | */
7 | export default class LabelPlacemarkOverlay extends overlayPlacemark {
8 | constructor(geometry, properties, options) {
9 | super(geometry, properties, options);
10 | }
11 |
12 | getData() {
13 | const polygon = this._data.geoObject instanceof GeoObject ?
14 | this._data.geoObject.properties.get('polygon') :
15 | this._data.properties.polygon;
16 |
17 | return {
18 | geoObject: polygon,
19 | properties: polygon.properties,
20 | //options: polygon.options, TODO невозможно переопределить опции, потому что https://github.yandex-team.ru/mapsapi/jsapi-v2/blob/master/src/overlay/view/abstract/baseWithLayout/overlay.view.BaseWithLayout.js#L99
21 | state: polygon.state
22 | };
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 YANDEX LLC
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are
4 | permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of
7 | conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list
10 | of conditions and the following disclaimer in the documentation and/or other materials
11 | provided with the distribution.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/src/util/zoom/setZoomVisibility.js:
--------------------------------------------------------------------------------
1 | import getFirstZoomInside from './getFirstZoomInside';
2 | import getLayoutSize from '../../label/util/getLayoutSize';
3 |
4 | export default function (
5 | labelType, map, zoom, currVisible, layout, center,
6 | polygonCoordinates, labelOffset, permissibleInaccuracyOfVisibility
7 | ) {
8 | return analyze(
9 | labelType,
10 | map,
11 | zoom,
12 | currVisible,
13 | layout,
14 | center,
15 | polygonCoordinates,
16 | labelOffset,
17 | permissibleInaccuracyOfVisibility
18 | );
19 | }
20 |
21 | function getVisible(currentType, newType, newIsVisible) {
22 | let types = ['none', 'dot', 'label'];
23 | let result = currentType;
24 | if (newIsVisible) {
25 | result = types.indexOf(newType) > types.indexOf(currentType) ? newType : currentType;
26 | }
27 | return result;
28 | }
29 |
30 | function analyze(type, map, zoom, currVisible, layout, center,
31 | polygonCoordinates, labelOffset, permissibleInaccuracyOfVisibility) {
32 | const size = getLayoutSize(layout);
33 | if (!size || size.width === 0 || size.height === 0) return;
34 |
35 | const firstZoomInside = getFirstZoomInside(
36 | map,
37 | center,
38 | polygonCoordinates,
39 | size,
40 | labelOffset,
41 | type === 'dot' ? 0 : permissibleInaccuracyOfVisibility
42 | );
43 |
44 | return {
45 | visible: getVisible(currVisible, type, zoom >= firstZoomInside),
46 | size
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | const glob = require("glob");
2 | module.exports = function (grunt) {
3 | const fileNames = glob.sync("src/**/*.js");
4 | const babelFiles = fillFiles(fileNames);
5 | grunt.initConfig({
6 | pkg: grunt.file.readJSON('package.json'),
7 | babel: {
8 | options: {
9 | sourceMap: true
10 | },
11 | dist: {
12 | files: babelFiles
13 | }
14 | },
15 | concat: {
16 | build: {
17 | src: [
18 | 'build/pre/**/*.js'
19 | ],
20 | dest: 'build/polylabel.js'
21 | }
22 | },
23 | uglify: {
24 | build: {
25 | src: 'build/polylabel.js',
26 | dest: 'build/polylabel.min.js'
27 | }
28 | },
29 | watch: {
30 | files: ['src/**/*.js'],
31 | tasks: 'default'
32 | }
33 | });
34 | grunt.loadNpmTasks('grunt-babel');
35 | grunt.loadNpmTasks('grunt-contrib-concat');
36 | grunt.loadNpmTasks('grunt-contrib-uglify');
37 | grunt.loadNpmTasks('grunt-contrib-watch');
38 |
39 | grunt.registerTask('build', ['babel', 'concat', 'uglify']);
40 | grunt.registerTask('default', ['build', 'watch']);
41 | };
42 |
43 | function fillFiles(names) {
44 | names = names.map(name => {
45 | return name.split('/').slice(1).join('/');
46 | });
47 | let result = {};
48 | names.forEach((fileName) => {
49 | result[`build/pre/${fileName}`] = `src/${fileName}`;
50 | });
51 | return result;
52 | }
53 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "sourceMaps": true,
3 | "sourceRoot": "src/",
4 | "presets": [
5 | [
6 | "env",
7 | {
8 | "targets": [
9 | "last 5 version",
10 | "ie >= 8",
11 | "safari >= 7"
12 | ],
13 | "loose": true,
14 | "spec": false,
15 | "strict": false,
16 | "noInterop": true,
17 | "modules": false
18 | }
19 | ]
20 | ],
21 | "plugins": [
22 | [
23 | "transform-class-properties",
24 | {
25 | "spec": false
26 | }
27 | ],
28 | [
29 | "./builder/plugin-transform-ym-helpers",
30 | {
31 | "overrideHelpers": {
32 | "inheritsLoose": "util.defineClass",
33 | "inherits": "util.defineClass",
34 | "extends": "util.extend"
35 | },
36 | "replaceGlobals": {
37 | "Object.assign": "util.extend",
38 | "Array.isArray": [
39 | "util.array",
40 | "isArray"
41 | ],
42 | "WeakMap": "util.WeakMap",
43 | "Promise": [
44 | "vow",
45 | "Promise"
46 | ]
47 | }
48 | }
49 | ],
50 | [
51 | "babel-plugin-transform-es2015-modules-ym",
52 | {
53 | "moduleBase": "polylabel",
54 | "sourceDir": "src",
55 | "ymGlobal": "ymaps",
56 | "sourceMappings": {
57 | "api": ""
58 | }
59 | }
60 | ]
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/src/util/transformPolygonCoords.js:
--------------------------------------------------------------------------------
1 | // these functions are needed to transform the coordinates of the polygon,
2 | // because there are transitions from 180 to -180,
3 | // that violates the function of calculating the center
4 | const methods = {
5 | polygon: polygon,
6 | point: point
7 | };
8 |
9 | function point(coords, isPositivePart) {
10 | const key = getKey(coords);
11 | return transformPoint(isPositivePart, key, coords);
12 | }
13 |
14 | function polygon(coords) {
15 | let result = [];
16 | let isPositivePart = true; // true = positive, false = negative
17 | for (let i = 0; i < coords.length; i++) {
18 | const pointCoords = coords[i];
19 | if (i === 0) {
20 | isPositivePart = pointCoords[1] >= 0 ? true : false;
21 | }
22 | const key = getKey(pointCoords);
23 | result.push(transformPoint(isPositivePart, key, pointCoords));
24 | }
25 | return {
26 | coords: result,
27 | isPositivePart
28 | };
29 | }
30 |
31 | function transformPoint(isPositivePart, key, pointCoords) {
32 | return isPositivePart ? transformPositive(key, pointCoords) : transformNegative(key, pointCoords);
33 | }
34 |
35 | /**
36 | * Gets the key, which says which of the seams is closer
37 | */
38 | function getKey(p) {
39 | const arr = [
40 | {
41 | key: '180',
42 | distance: 180 - p[1]
43 | },
44 | {
45 | key: '-180',
46 | distance: Math.abs(-180 - p[1])
47 | },
48 | {
49 | key: '0',
50 | distance: Math.abs(p[1])
51 | }
52 | ];
53 | return arr.sort(comparator)[0].key;
54 | }
55 |
56 | function transformPositive(key, point) {
57 | return (key === '-180') ? [point[0], 360 + point[1]] : point;
58 | }
59 |
60 | function transformNegative(key, point) {
61 | return (key === '180') ? [point[0], -360 + point[1]] : point;
62 | }
63 |
64 | function comparator(a, b) {
65 | return a.distance - b.distance;
66 | }
67 |
68 | export default methods;
69 |
--------------------------------------------------------------------------------
/src/util/zoom/parseZoomData.js:
--------------------------------------------------------------------------------
1 | import CONFIG from '../../config';
2 | const {MIN_ZOOM, MAX_ZOOM} = CONFIG;
3 |
4 | /**
5 | * Parse data about zoom.
6 | * @param {Object|primitive} zoomData
7 | * Supported object properties view: number, string
8 | * @return {Object} - Returned object with zoom, where the parsed values.
9 | * @example
10 | * zoomData = {1: 'value1', '3_5': 'value2'}
11 | * return {1: 'value1', 2: undefined ... 3: 'value2', 4: 'value2', 5: 'value2', 6: undefined ...}
12 | * zoomData = 'value123'
13 | * return {1: 'value123' ... 19: 'value123'}
14 | */
15 | export default function parseZoomData(zoomData) {
16 | const valid = ['number', 'string', 'boolean', 'object'];
17 | if (zoomData && !Array.isArray(zoomData) && typeof zoomData === 'object') {
18 | return Object.keys(zoomData).reduce((result, key) => {
19 | if (typeof key === 'string') {
20 | parseString(result, key, zoomData[key]);
21 | } else if (typeof key === 'number') {
22 | parseNumber(result, key, zoomData[key]);
23 | }
24 | return result;
25 | }, createDefZoomObj());
26 | } else if (valid.indexOf(typeof zoomData) !== -1) {
27 | return createDefZoomObj(zoomData);
28 | }
29 | }
30 |
31 | function parseNumber(target, zoom, value) {
32 | target[zoom] = value;
33 | }
34 |
35 | function parseString(target, zoom, value) {
36 | if (!isNaN(Number(zoom))) {
37 | target[Number(zoom)] = value;
38 | return;
39 | }
40 | const zoomRange = zoom.split('_').map(Number);
41 | if (isNaN(zoomRange[0]) || isNaN(zoomRange[1])) {
42 | return;
43 | }
44 | let bottom = zoomRange[0] < MIN_ZOOM ? MIN_ZOOM : zoomRange[0];
45 | const top = zoomRange[1] > MAX_ZOOM ? MAX_ZOOM : zoomRange[1];
46 | while (bottom <= top) {
47 | target[bottom] = value;
48 | bottom++;
49 | }
50 | }
51 |
52 | function createDefZoomObj(val) {
53 | let result = {};
54 | for (let i = MIN_ZOOM; i <= MAX_ZOOM; i++) {
55 | result[i] = val;
56 | }
57 | return result;
58 | }
59 |
--------------------------------------------------------------------------------
/src/create.js:
--------------------------------------------------------------------------------
1 | import PCollection from './polylabel/PolylabelCollection';
2 | import PObjectManager from './polylabel/PolylabelObjectManager';
3 | import ObjectManager from 'api/ObjectManager';
4 |
5 | export default function (map, data) {
6 | initStyles();
7 | return data instanceof ObjectManager ?
8 | new PObjectManager(map, data) :
9 | new PCollection(map, data);
10 | }
11 |
12 | function initStyles() {
13 | const style = document.createElement('style');
14 | style.innerText = `
15 | .ymaps-polylabel-dot-default {
16 | border: 1px solid rgba(255, 255, 255, 0.8);
17 | height: 4px;
18 | width: 4px;
19 | border-radius: 3px;
20 | }
21 | .ymaps-polylabel-dot-default_hover {
22 | border: 1px solid rgba(255, 255, 255, 1);
23 | }
24 | .ymaps-polylabel-label-default {
25 | font-family: Arial, Helvetica, sans-serif;
26 | text-align: center;
27 | }
28 | .ymaps-polylabel-dark-label {
29 | color: #06264f;
30 | -webkit-font-smoothing: subpixel-antialiased;
31 | text-shadow:
32 | 0 -1px 0 rgba( 255, 255, 255, 0.4 ),
33 | 0 -1px 0 rgba( 255, 255, 255, 0.4 ),
34 | 0 1px 0 rgba( 255, 255, 255, 0.4 ),
35 | 0 1px 0 rgba( 255, 255, 255, 0.4 ),
36 | -1px 0 0 rgba( 255, 255, 255, 0.4 ),
37 | 1px 0 0 rgba( 255, 255, 255, 0.4 ),
38 | -1px 0 0 rgba( 255, 255, 255, 0.4 ),
39 | 1px 0 0 rgba( 255, 255, 255, 0.4 ),
40 | -1px -1px 0 rgba( 255, 255, 255, 0.4 ),
41 | 1px -1px 0 rgba( 255, 255, 255, 0.4 ),
42 | -1px 1px 0 rgba( 255, 255, 255, 0.4 ),
43 | 1px 1px 0 rgba( 255, 255, 255, 0.4 ),
44 | -1px -1px 0 rgba( 255, 255, 255, 0.4 ),
45 | 1px -1px 0 rgba( 255, 255, 255, 0.4 ),
46 | -1px 1px 0 rgba( 255, 255, 255, 0.4 ),
47 | 1px 1px 0 rgba( 255, 255, 255, 0.4 );
48 | opacity: 0.9;
49 | }
50 | .ymaps-polylabel-light-label {
51 | color: rgba(255, 255, 255, 1);
52 | text-shadow: 0 0 2px rgba(0, 0, 0, 1), 0 0 2px rgba(0, 0, 0, 1);
53 | }
54 | `.replace(/\n+\s+/gi, '');
55 | document.head.appendChild(style);
56 | }
57 |
--------------------------------------------------------------------------------
/src/util/checkPointPosition.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check if the point is inside the polygon.
3 | * @param {Array[2]} point
4 | * @param {Array} coords - Polygon coords.
5 | */
6 | export default function isInside(point, coords) {
7 | let parity = 0;
8 | for (let i = 0; i < coords.length - 1; i++) {
9 | let e = [coords[i], coords[i + 1]];
10 | switch (edgeType(point, e)) {
11 | case 'TOUCHING':
12 | return 'BOUNDARY';
13 | case 'CROSSING':
14 | parity = 1 - parity;
15 | }
16 | }
17 | return (parity ? 'INSIDE' : 'OUTSIDE');
18 | }
19 |
20 | /**
21 | * Determines the position of the point relative to the edge.
22 | * @param {Array[2]} p - The investigated point.
23 | * @param {Array[2]} p0 - The first point of the edge.
24 | * @param {Array[2]} p1 - The second point of the edge.
25 | */
26 | function pointClassify(p, p0, p1) {
27 | const a = pointMinus(p1, p0);
28 | const b = pointMinus(p, p0);
29 | const sa = a[0] * b[1] - b[0] * a[1];
30 | if (sa > 0) {
31 | return 'LEFT';
32 | }
33 | if (sa < 0) {
34 | return 'RIGHT';
35 | }
36 | if ((a[0] * b[0] < 0) || (a[1] * b[1] < 0)) {
37 | return 'BEHIND';
38 | }
39 | if (pointLength(a) < pointLength(b)) {
40 | return 'BEYOND';
41 | }
42 | if (pointEquals(p0, p)) {
43 | return 'ORIGIN';
44 | }
45 | if (pointEquals(p1, p)) {
46 | return 'DESTINATION';
47 | }
48 | return 'BETWEEN';
49 | }
50 |
51 | function pointMinus(p1, p2) {
52 | return [p1[0] - p2[0], p1[1] - p2[1]];
53 | }
54 |
55 | function pointLength(p) {
56 | return Math.sqrt(Math.pow(p[0], 2) + Math.pow(p[1], 2));
57 | }
58 |
59 | function pointEquals(p1, p2) {
60 | return p1[0] === p2[0] && p1[1] === p2[1];
61 | }
62 |
63 | /**
64 | * Determines the positions of the ray released from the point relative to the edge (Crosses, Affects, Neutral).
65 | * @param {Arrya[2]} point - The investigated point.
66 | * @param {Array} edge - Edge.
67 | */
68 | function edgeType(point, edge) {
69 | const v = edge[0];
70 | const w = edge[1];
71 | switch (pointClassify(point, v, w)) {
72 | case 'LEFT': {
73 | return ((v[1] < point[1]) && (point[1] <= w[1])) ? 'CROSSING' : 'INESSENTIAL';
74 | }
75 | case 'RIGHT': {
76 | return ((w[1] < point[1]) && (point[1] <= v[1])) ? 'CROSSING' : 'INESSENTIAL';
77 | }
78 | case 'BETWEEN':
79 | case 'ORIGIN':
80 | case 'DESTINATION':
81 | return 'TOUCHING';
82 | default: {
83 | return 'INESSENTIAL';
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/util/zoom/getFirstZoomInside.js:
--------------------------------------------------------------------------------
1 | import isInside from '../checkPointPosition';
2 | import CONFIG from '../../config';
3 |
4 | export default function (map, center, coords, size, offset, resolvedInaccuracy) {
5 | let {MIN_ZOOM: i, MAX_ZOOM: j} = CONFIG;
6 | let zoom;
7 | while (i < j) {
8 | zoom = Math.floor((i + j) / 2);
9 | const ri = isNaN(Number(resolvedInaccuracy)) ? 0 : Number(resolvedInaccuracy);
10 | const elemPoints = getElemPoints(map, center, zoom, size, offset || [0, 0], ri);
11 | if (checkIsInside(map, coords, elemPoints.normal, zoom) ||
12 | (ri !== 0 && checkIsInside(map, coords, elemPoints.withInaccuracy, zoom))) {
13 | j = zoom;
14 | } else {
15 | i = zoom + 1;
16 | }
17 | }
18 | return i;
19 | }
20 |
21 | function getElemPoints(map, center, zoom, size, offset, ri) {
22 | const centerProj = map.options.get('projection').toGlobalPixels(center, zoom);
23 | let {width: w, height: h} = size;
24 |
25 | centerProj[0] += offset[0];
26 | centerProj[1] += offset[1];
27 |
28 | let elemPoints = [];
29 | let elemPointsWithInaccuracy = [];
30 | elemPoints.push(
31 | [centerProj[0] - w / 2, centerProj[1] - h / 2],
32 | [centerProj[0] - w / 2, centerProj[1] + h / 2],
33 | [centerProj[0] + w / 2, centerProj[1] - h / 2],
34 | [centerProj[0] + w / 2, centerProj[1] + h / 2]
35 | );
36 | elemPointsWithInaccuracy.push(
37 | [
38 | elemPoints[0][0] + ri > centerProj[0] ? centerProj[0] : elemPoints[0][0] + ri,
39 | elemPoints[0][1] + ri > centerProj[1] ? centerProj[1] : elemPoints[0][1] + ri
40 | ],
41 | [
42 | elemPoints[1][0] + ri > centerProj[0] ? centerProj[0] : elemPoints[1][0] + ri,
43 | elemPoints[1][1] - ri < centerProj[1] ? centerProj[1] : elemPoints[1][1] - ri
44 | ],
45 | [
46 | elemPoints[2][0] - ri < centerProj[0] ? centerProj[0] : elemPoints[2][0] - ri,
47 | elemPoints[2][1] + ri > centerProj[1] ? centerProj[1] : elemPoints[2][1] + ri
48 | ],
49 | [
50 | elemPoints[3][0] - ri < centerProj[0] ? centerProj[0] : elemPoints[3][0] - ri,
51 | elemPoints[3][1] - ri < centerProj[1] ? centerProj[1] : elemPoints[3][1] - ri
52 | ]
53 | );
54 | return {
55 | normal: elemPoints,
56 | withInaccuracy: elemPointsWithInaccuracy
57 | };
58 | }
59 |
60 | function checkIsInside(map, coords, elemPoints, zoom) {
61 | for (let i = 0; i < elemPoints.length; i++) {
62 | const point = map.options.get('projection').fromGlobalPixels(elemPoints[i], zoom);
63 | if (isInside(point, coords) !== 'INSIDE') {
64 | return false;
65 | }
66 | }
67 | return true;
68 | }
69 |
--------------------------------------------------------------------------------
/src/polylabel/PolylabelBase.js:
--------------------------------------------------------------------------------
1 | import templateFiltersStorage from 'api/template.filtersStorage';
2 |
3 | import CONFIG from '../config';
4 | import dotColorFilterStorage from '../util/templateFilterStorage/dotColor';
5 |
6 | export default class PolylabelBased {
7 | constructor(map) {
8 | this._map = map;
9 | templateFiltersStorage.add('dot-color', dotColorFilterStorage);
10 | }
11 |
12 | getPolylabelType() {
13 | return this._polylabelType;
14 | }
15 |
16 | initMapListeners(callback) {
17 | this._mapCallback = callback;
18 | this._map.events.add(['boundschange', 'actionbegin', 'actionend'], this._mapEventsHandler, this);
19 | }
20 |
21 | destroyMapListeners() {
22 | this._map.events.remove(['boundschange', 'actionbegin', 'actionend'], this._mapEventsHandler, this);
23 | }
24 |
25 | _mapEventsHandler(event) {
26 | switch (event.get('type')) {
27 | case 'actionbegin': {
28 | const action = event.originalEvent.action;
29 | action.events.add('tick', this._actionTickHandler, this);
30 | break;
31 | }
32 | case 'actionend': {
33 | const action = event.originalEvent.action;
34 | action.events.remove('tick', this._actionTickHandler, this);
35 |
36 | if (this._zoomed) {
37 | this._mapCallback('actionendzoomchange');
38 | this._zoomed = false;
39 | }
40 | break;
41 | }
42 | }
43 | }
44 |
45 | _actionTickHandler(event) {
46 | const tick = event.get('tick');
47 | if (tick.zoom !== this._map.getZoom()) {
48 | if (!this._zoomed) this._mapCallback('actionbeginzoomchange');
49 | this._zoomed = true;
50 | }
51 | }
52 |
53 | /**
54 | * Returns all polygon options
55 | */
56 | getOptions(polygon) {
57 | if (this.getPolylabelType() === 'collection') {
58 | return polygon.options.getAll();
59 | } else {
60 | return polygon.options;
61 | }
62 | }
63 |
64 | /**
65 | * Returns the options required to create a label
66 | */
67 | getConfigOptions(polygon) {
68 | return CONFIG.options.reduce((result, key) => {
69 | result[key] = this.getPolylabelType() === 'collection' ? polygon.options.get(key) : polygon.options[key];
70 | return result;
71 | }, {});
72 | }
73 |
74 | /**
75 | * Returns options (type zoomRange) that are required to create a label
76 | */
77 | getConfigZoomRangeOptions(polygon) {
78 | return CONFIG.zoomRangeOptions.reduce((result, key) => {
79 | result[key] = this.getPolylabelType() === 'collection' ? polygon.options.get(key) : polygon.options[key];
80 | return result;
81 | }, {});
82 | }
83 |
84 | /**
85 | * Returns the properties that are required to create a label
86 | */
87 | getConfigProperties(polygon) {
88 | return CONFIG.properties.reduce((result, key) => {
89 | result[key] = this.getPolylabelType() === 'collection' ?
90 | polygon.properties.get(key) :
91 | polygon.properties[key];
92 | return result;
93 | }, {});
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/docs/example-text/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Пример
6 |
7 |
9 |
11 |
15 |
17 |
32 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/docs/example-img/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Пример
6 |
7 |
8 |
9 |
13 |
14 |
29 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/label/LabelBase.js:
--------------------------------------------------------------------------------
1 | import createLayoutTemplates from './util/layoutTemplates/createLayoutTemplates';
2 | import LabelData from './util/LabelData';
3 | import classHelper from './util/classHelper';
4 | import getLayoutSize from './util/getLayoutSize';
5 |
6 | export default class LabelBase {
7 | constructor() {
8 | this._baseLayoutTemplates = null;
9 | this._layoutTemplates = null;
10 | }
11 |
12 | getPolylabelType() {
13 | return this._polylabel.getPolylabelType();
14 | }
15 |
16 | getPlacemark(type) {
17 | return this._placemark[type];
18 | }
19 |
20 | getLayout(type) {
21 | return this._layout[type];
22 | }
23 |
24 | setLayout(type, layout) {
25 | this._layout[type] = layout;
26 | this._initLayoutSizeChangeHandler(layout);
27 | }
28 |
29 | updateLayouts() {
30 | this.getLabelLayout('label').then(layout => {
31 | this.setLayout('label', layout);
32 | });
33 | this.getLabelLayout('dot').then(layout => {
34 | this.setLayout('dot', layout);
35 | });
36 | }
37 |
38 | _initLayoutSizeChangeHandler(layout) {
39 | const el = layout.getElement();
40 | if (!el) return;
41 | let imgs = Array.prototype.slice.call(el.getElementsByTagName('img'));
42 | if (imgs.length > 0) {
43 | const imagesLoaded = Promise.all(imgs.map(img => {
44 | if (img.complete) {
45 | return Promise.resolve();
46 | }
47 | return new Promise(resolve => img.onload = resolve);
48 | }));
49 | imagesLoaded.then(() => {
50 | const size = getLayoutSize(layout);
51 | if (!size) return;
52 | if (size.width > 0 && size.height > 0) {
53 | this._polylabel._setLabelData(this._polygon, this, undefined, ['dot', 'label']);
54 | }
55 | });
56 | }
57 | }
58 |
59 | getPolygonOptions() {
60 | if (this.getPolylabelType() === 'collection') {
61 | return this._polygon.options.getAll();
62 | }
63 | return this._polygon.options;
64 | }
65 |
66 | getLabelOptions() {
67 | const label = this._placemark.label;
68 | return (this.getPolylabelType() === 'collection') ? label.options.getAll() : label.options;
69 | }
70 |
71 | /**
72 | * Forms all the necessary options for the label (from the polygon, the necessary parsed data, etc.)
73 | */
74 | getFormedOptionsForPlacemark(type) {
75 | const cursors = this._data.getLabelCursors();
76 | return Object.assign(
77 | {},
78 | this.getPolygonOptions(),
79 | this._layoutTemplates[type],
80 | cursors[type]
81 | );
82 | }
83 |
84 | /**
85 | * Create templates that will then be included in the base
86 | */
87 | createLayoutTemplates() {
88 | const polygonOptions = this.getPolygonOptions();
89 | const label = polygonOptions.labelLayout;
90 | const dot = polygonOptions.labelDotLayout;
91 |
92 | if (this._prevLayout && this._prevLayout.dot === dot && this._prevLayout.label === label) {
93 | return;
94 | }
95 |
96 | this._layoutTemplates = createLayoutTemplates(label, dot);
97 |
98 | this._prevLayout = {
99 | dot,
100 | label
101 | };
102 | }
103 |
104 | /**
105 | * Returns the current visibility value
106 | */
107 | analyseVisibility(visibleState, visible, dotVisible) {
108 | let currState = visibleState && visibleState !== 'auto' ? visibleState : visible;
109 | if (currState === 'dot' && !dotVisible) currState = 'none';
110 | return currState;
111 | }
112 |
113 | /**
114 | * Sets visibility for a label
115 | */
116 | setVisibility(visibleState, visible, dotVisible) {
117 | const currState = this.analyseVisibility(visibleState, visible, dotVisible);
118 | this.setVisibilityForce(currState);
119 | return currState;
120 | }
121 |
122 | createLabelData(options, zoomRangeOptions) {
123 | this._data = new LabelData(this._polygon, options, zoomRangeOptions, this._map, this);
124 | return this._data;
125 | }
126 |
127 | getLabelData() {
128 | return this._data;
129 | }
130 |
131 | analysePane(type, visibleType) {
132 | return type === visibleType ? 'places' : 'phantom';
133 | }
134 |
135 | analyseOffset(size, offset) {
136 | const h = size.height / 2;
137 | const w = size.width / 2;
138 |
139 | return {
140 | left: -w + offset[0],
141 | top: -h + offset[1]
142 | };
143 | }
144 |
145 | analyseStyles(data) {
146 | const labelOptions = this.getLabelOptions();
147 | let isChange = false;
148 | Object.keys(data).forEach(key => {
149 | if (labelOptions[`iconLabel${key[0].toUpperCase()}${key.slice(1)}`] !== data[key]) {
150 | isChange = true;
151 | }
152 | });
153 | return {
154 | isChange,
155 | styles: {
156 | iconLabelClassName: data.className,
157 | iconLabelTextSize: data.textSize,
158 | iconLabelTextColor: data.textColor
159 | }
160 | };
161 | }
162 |
163 | analyseShape(type, size, offset) {
164 | const h = size.height / 2;
165 | const w = size.width / 2;
166 | const dotDefaultShape = (type === 'dot' && this._data.isDotDefault) ? 2 : 0;
167 |
168 | return {
169 | type: 'Rectangle',
170 | coordinates: [
171 | [-w + offset[0] - dotDefaultShape, -h + offset[1] - dotDefaultShape],
172 | [w + offset[0] + dotDefaultShape, h + offset[1] + dotDefaultShape]
173 | ]
174 | };
175 | }
176 |
177 | addDotClass(className) {
178 | classHelper.add(this._layout.dot, 'ymaps-polylabel-dot-default', className);
179 | }
180 |
181 | removeDotClass(className) {
182 | classHelper.remove(this._layout.dot, 'ymaps-polylabel-dot-default', className);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/label/GeoObjectCollection/Label.js:
--------------------------------------------------------------------------------
1 | import Placemark from 'api/Placemark';
2 | import LabelPlacemarkOverlay from '../util/LabelPlacemarkOverlay';
3 | import getBaseLayoutTemplates from '../util/layoutTemplates/getBaseLayoutTemplates';
4 | import LabelBase from '../LabelBase';
5 |
6 | /**
7 | * The label class for the geo-collection
8 | */
9 | export default class Label extends LabelBase {
10 | constructor(map, polygon, parentCollection, polylabel) {
11 | super();
12 |
13 | this._map = map;
14 | this._polylabel = polylabel;
15 | this._polygon = polygon;
16 | this._parentCollection = parentCollection;
17 | this._placemark = {
18 | label: null,
19 | dot: null
20 | };
21 | this._layout = {
22 | label: null,
23 | dot: null
24 | };
25 | this._init();
26 | }
27 |
28 | removeFromCollection() {
29 | if (!this._parentCollection) {
30 | return;
31 | }
32 | ['label', 'dot'].forEach(type => {
33 | if (this._parentCollection.indexOf(this._placemark[type]) === -1) {
34 | return;
35 | }
36 | this._parentCollection.remove(this._placemark[type]);
37 | });
38 | this._polygon = null;
39 | this._parentCollection = null;
40 | this._placemark = null;
41 | this._layout = null;
42 | this._data = null;
43 | }
44 |
45 | addToCollection() {
46 | if (!this._parentCollection) {
47 | return Promise.reject();
48 | }
49 | const layouts = ['label', 'dot'].map(type => {
50 | if (!this._placemark[type].getParent()) {
51 | this._parentCollection.add(this._placemark[type]);
52 | }
53 |
54 | return this.getLabelLayout(type).then(layout => {
55 | this.setLayout(type, layout);
56 | });
57 | });
58 | return Promise.all(layouts);
59 | }
60 |
61 | getLabelLayout(type) {
62 | return this._placemark[type].getOverlay().then(overlay => overlay.getLayout());
63 | }
64 |
65 | _init() {
66 | this._baseLayoutTemplates = getBaseLayoutTemplates();
67 | }
68 |
69 | createPlacemarks() {
70 | ['label', 'dot'].forEach(type => {
71 | this._placemark[type] = Label._createPlacemark({
72 | properties: Object.assign({}, {
73 | polygon: this._polygon
74 | }, this._polygon.properties.getAll()),
75 | options: this.getFormedOptionsForPlacemark(type)
76 | }, this._baseLayoutTemplates[type], this._data.getCenterCoords(this._map.getZoom()));
77 | });
78 | }
79 |
80 | static _createPlacemark(params, layout, coords) {
81 | const options = Object.assign({}, {
82 | iconLayout: layout,
83 | pointOverlay: LabelPlacemarkOverlay,
84 | iconLabelPosition: 'absolute'
85 | }, params.options);
86 | return new Placemark(coords, params.properties, options);
87 | }
88 |
89 | setDataByZoom(zoom, visibleState) {
90 | ['dot', 'label'].forEach(type => {
91 | if (type === 'label') {
92 | const styles = this._data.getStyles(zoom);
93 | this.setStyles({
94 | className: styles.className,
95 | textSize: styles.textSize,
96 | textColor: styles.textColor
97 | });
98 | }
99 | this.setVisibilityForce('none');
100 | this._data.setVisible(zoom, type, this._layout[type]);
101 | });
102 |
103 | const currentVisibleType = this.setVisibility(
104 | visibleState,
105 | this._data.getVisibility(zoom),
106 | this._data.getData('dotVisible')
107 | );
108 |
109 | const currentCenter = this._data.getCenterCoords(zoom);
110 | if (['label', 'dot'].indexOf(currentVisibleType) !== -1 &&
111 | this._data.getSize(zoom, currentVisibleType)) {
112 | this.setCoordinates(currentCenter);
113 | this.setOffsetAndIconShape(
114 | currentVisibleType,
115 | this._data.getSize(zoom, currentVisibleType),
116 | this._data.getOffset(zoom)
117 | );
118 | }
119 |
120 | return {
121 | currentVisibleType,
122 | currentConfiguredVisibileType: this._data.getVisibility(zoom),
123 | currentCenter
124 | };
125 | }
126 |
127 | setLayoutTemplate() {
128 | this.createLayoutTemplates();
129 | Object.keys(this._layoutTemplates).forEach(type => {
130 | this._placemark[type].options.set(this._layoutTemplates[type]);
131 | });
132 | }
133 |
134 | setNewOptions(newOptions) {
135 | ['dot', 'label'].forEach(type => {
136 | this._placemark[type].options.set(newOptions);
137 | });
138 | }
139 |
140 | /**
141 | * Sets the coordinates for the label
142 | */
143 | setCoordinates(coords) {
144 | if (coords.toString() !== this._placemark.label.geometry.getCoordinates().toString()) {
145 | ['dot', 'label'].forEach(type => {
146 | this._placemark[type].geometry.setCoordinates(coords);
147 | });
148 | }
149 | }
150 |
151 | setVisibilityForce(visibleType) {
152 | Object.keys(this._placemark).forEach(type => {
153 | const pane = this.analysePane(type, visibleType);
154 | if (this._placemark[type].options.get('pane') !== pane) {
155 | this._placemark[type].options.set({pane});
156 | }
157 | });
158 | }
159 |
160 | /**
161 | * Sets the styles for the label
162 | */
163 | setStyles(data) {
164 | const styles = this.analyseStyles(data);
165 | if (styles.isChange) {
166 | this._placemark.label.options.set(styles.styles);
167 | }
168 | }
169 |
170 | /**
171 | * Centers the label and creates the correct iconShape
172 | */
173 | setOffsetAndIconShape(type, size, offset) {
174 | const offsetResult = this.analyseOffset(size, offset);
175 | this._placemark[type].options.set({
176 | iconShape: this.analyseShape(type, size, offset),
177 | iconLabelLeft: offsetResult.left,
178 | iconLabelTop: offsetResult.top
179 | });
180 | }
181 |
182 | destroy() {
183 | this.removeFromCollection();
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/label/ObjectManager/Label.js:
--------------------------------------------------------------------------------
1 | import LabelPlacemarkOverlay from '../util/LabelPlacemarkOverlay';
2 | import getBaseLayoutTemplates from '../util/layoutTemplates/getBaseLayoutTemplates';
3 | import LabelBase from '../LabelBase';
4 |
5 | /**
6 | * The label class for the ObjectManager
7 | */
8 | export default class Label extends LabelBase {
9 | constructor(map, polygon, objectManager, polylabel) {
10 | super();
11 | this._map = map;
12 | this._polylabel = polylabel;
13 | this._polygon = polygon;
14 | this._objectManager = objectManager;
15 | this._placemark = {
16 | label: null,
17 | dot: null
18 | };
19 | this._layout = {
20 | label: null,
21 | dot: null
22 | };
23 | this._init();
24 | }
25 |
26 | destroy() {
27 | this.removeFromObjectManager();
28 | }
29 |
30 | addToObjectManager() {
31 | this.setLayoutTemplate();
32 | ['label', 'dot'].forEach(type => {
33 | this._objectManager.add(this._placemark[type]);
34 | });
35 | }
36 |
37 | removeFromObjectManager() {
38 | ['label', 'dot'].forEach(type => {
39 | this._objectManager.remove(this._placemark[type]);
40 | });
41 | this._polygon = null;
42 | this._objectManager = null;
43 | this._placemark = null;
44 | this._layout = null;
45 | this._data = null;
46 | }
47 |
48 | _init() {
49 | this._baseLayoutTemplates = getBaseLayoutTemplates();
50 | }
51 |
52 | getLabelLayout(type) {
53 | const overlay = this._objectManager.objects.overlays.getById(this._placemark[type].id);
54 | if (!overlay) return Promise.reject();
55 | return overlay.getLayout();
56 | }
57 |
58 | createPlacemarks() {
59 | ['label', 'dot'].forEach(type => {
60 | this._placemark[type] = Label._createPlacemark(`${type}#${this._polygon.id}`, {
61 | properties: Object.assign({}, {
62 | polygon: this._polygon
63 | }, this._polygon.properties),
64 | options: this.getFormedOptionsForPlacemark(type)
65 | }, this._baseLayoutTemplates[type], this._data.getCenterCoords(this._map.getZoom()));
66 | });
67 | }
68 |
69 | static _createPlacemark(id, params, layout, coordinates) {
70 | const options = Object.assign({}, {
71 | iconLayout: layout,
72 | iconLabelPosition: 'absolute',
73 | overlay: LabelPlacemarkOverlay,
74 | pane: 'phantom'
75 | }, params.options);
76 | return {
77 | type: 'Feature',
78 | id,
79 | options,
80 | properties: params.properties,
81 | geometry: {
82 | type: 'Point',
83 | coordinates
84 | }
85 | };
86 | }
87 |
88 | _updateOptions(id, options) {
89 | this._objectManager.objects.setObjectOptions(id, options);
90 | }
91 |
92 | setLayoutTemplate() {
93 | Object.keys(this._layoutTemplates).forEach(type => {
94 | this._updateOptions(this._placemark[type].id, this._layoutTemplates[type]);
95 | });
96 | }
97 |
98 | /**
99 | * Updates all possible options in the label
100 | */
101 | updateOptions() {
102 | ['dot', 'label'].forEach(type => {
103 | Object.assign(this._placemark[type].options, this.getFormedOptionsForPlacemark(type));
104 | this._updateOptions(
105 | this._placemark[type].id,
106 | this._placemark[type].options
107 | );
108 | });
109 | }
110 |
111 | setDataByZoom(zoom, types, visibleState) {
112 | types.forEach(type => {
113 | if (type === 'label') {
114 | const styles = this._data.getStyles(zoom);
115 | this.setStyles({
116 | className: styles.className,
117 | textSize: styles.textSize,
118 | textColor: styles.textColor
119 | });
120 | }
121 | this.setVisibilityForce('none');
122 | this._data.setVisible(zoom, type, this._layout[type]);
123 | });
124 |
125 | const currentVisibleType = this.setVisibility(
126 | visibleState,
127 | this._data.getVisibility(zoom),
128 | this._data.getData('dotVisible')
129 | );
130 |
131 | const currentCenter = this._data.getCenterCoords(zoom);
132 | if (['label', 'dot'].indexOf(currentVisibleType) !== -1 &&
133 | this._data.getSize(zoom, currentVisibleType)) {
134 | this.setCoordinates(currentCenter);
135 | this.setOffsetAndIconShape(
136 | currentVisibleType,
137 | this._data.getSize(zoom, currentVisibleType),
138 | this._data.getOffset(zoom)
139 | );
140 | }
141 |
142 | return {
143 | currentVisibleType,
144 | currentConfiguredVisibileType: this._data.getVisibility(zoom),
145 | currentCenter
146 | };
147 | }
148 |
149 | setOffsetAndIconShape(type, size, offset) {
150 | const offsetResult = this.analyseOffset(size, offset);
151 | this._updateOptions(this._placemark[type].id, {
152 | iconShape: this.analyseShape(type, size, offset),
153 | iconLabelLeft: offsetResult.left,
154 | iconLabelTop: offsetResult.top
155 | });
156 | }
157 |
158 | setCoordinates(coords) {
159 | if (coords.toString() !== this._placemark.label.geometry.coordinates.toString()) {
160 | ['dot', 'label'].forEach(type => {
161 | this._objectManager.remove(this._placemark[type]);
162 | this._generateNewPlacemark(type);
163 | this._placemark[type].geometry.coordinates = coords;
164 | this._objectManager.add(this._placemark[type]);
165 | });
166 | }
167 | }
168 |
169 | setStyles(data) {
170 | const styles = this.analyseStyles(data);
171 | if (styles.isChange) {
172 | this._updateOptions(this._placemark.label.id, styles.styles);
173 | }
174 | }
175 |
176 | setVisibilityForce(visibleType) {
177 | Object.keys(this._placemark).forEach(type => {
178 | const pane = this.analysePane(type, visibleType);
179 | const label = this._objectManager.objects.getById(this._placemark[type].id);
180 | if (label && label.options.pane !== pane) {
181 | this._updateOptions(this._placemark[type].id, {pane});
182 | }
183 | });
184 | }
185 |
186 | _generateNewPlacemark(type) {
187 | this._placemark[type] = Object.assign({}, this._placemark[type]);
188 | const id = this._placemark[type].id;
189 | this._placemark[type].id = id[0] === '_' ? id.slice(1) : `_${id}`;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/util/getPolesOfInaccessibility.js:
--------------------------------------------------------------------------------
1 | export default function (polygonCoords, precision, debug) {
2 | return getPolesOfInaccessibility([polygonCoords], precision, debug);
3 | }
4 |
5 | function TinyQueue(data, compare) {
6 | if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
7 |
8 | this.data = data || [];
9 | this.length = this.data.length;
10 | this.compare = compare || defaultCompare;
11 |
12 | if (this.length > 0) {
13 | for (var i = (this.length >> 1); i >= 0; i--) this._down(i);
14 | }
15 | }
16 |
17 | function defaultCompare(a, b) {
18 | return a < b ? -1 : a > b ? 1 : 0;
19 | }
20 |
21 | TinyQueue.prototype = {
22 |
23 | push: function (item) {
24 | this.data.push(item);
25 | this.length++;
26 | this._up(this.length - 1);
27 | },
28 |
29 | pop: function () {
30 | if (this.length === 0) return undefined;
31 |
32 | var top = this.data[0];
33 | this.length--;
34 |
35 | if (this.length > 0) {
36 | this.data[0] = this.data[this.length];
37 | this._down(0);
38 | }
39 | this.data.pop();
40 |
41 | return top;
42 | },
43 |
44 | peek: function () {
45 | return this.data[0];
46 | },
47 |
48 | _up: function (pos) {
49 | var data = this.data;
50 | var compare = this.compare;
51 | var item = data[pos];
52 |
53 | while (pos > 0) {
54 | var parent = (pos - 1) >> 1;
55 | var current = data[parent];
56 | if (compare(item, current) >= 0) break;
57 | data[pos] = current;
58 | pos = parent;
59 | }
60 |
61 | data[pos] = item;
62 | },
63 |
64 | _down: function (pos) {
65 | var data = this.data;
66 | var compare = this.compare;
67 | var halfLength = this.length >> 1;
68 | var item = data[pos];
69 |
70 | while (pos < halfLength) {
71 | var left = (pos << 1) + 1;
72 | var right = left + 1;
73 | var best = data[left];
74 |
75 | if (right < this.length && compare(data[right], best) < 0) {
76 | left = right;
77 | best = data[right];
78 | }
79 | if (compare(best, item) >= 0) break;
80 |
81 | data[pos] = best;
82 | pos = left;
83 | }
84 |
85 | data[pos] = item;
86 | }
87 | };
88 |
89 | var Queue = TinyQueue;
90 |
91 | function getPolesOfInaccessibility(polygon, precision, debug) {
92 | precision = precision || 1.0;
93 |
94 | // find the bounding box of the outer ring
95 | var minX;
96 | var minY;
97 | var maxX;
98 | var maxY;
99 |
100 | for (let i = 0; i < polygon[0].length; i++) {
101 | const p = polygon[0][i];
102 | if (!i || p[0] < minX) minX = p[0];
103 | if (!i || p[1] < minY) minY = p[1];
104 | if (!i || p[0] > maxX) maxX = p[0];
105 | if (!i || p[1] > maxY) maxY = p[1];
106 | }
107 |
108 | var width = maxX - minX;
109 | var height = maxY - minY;
110 | var cellSize = Math.min(width, height);
111 | var h = cellSize / 2;
112 |
113 | // a priority queue of cells in order of their "potential" (max distance to polygon)
114 | var cellQueue = new Queue(null, compareMax);
115 |
116 | if (cellSize === 0) return [minX, minY];
117 |
118 | // cover polygon with initial cells
119 | for (var x = minX; x < maxX; x += cellSize) {
120 | for (var y = minY; y < maxY; y += cellSize) {
121 | cellQueue.push(new Cell(x + h, y + h, h, polygon));
122 | }
123 | }
124 |
125 | // take centroid as the first best guess
126 | var bestCell = getCentroidCell(polygon);
127 |
128 | // special case for rectangular polygons
129 | var bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon);
130 | if (bboxCell.d > bestCell.d) bestCell = bboxCell;
131 |
132 | var numProbes = cellQueue.length;
133 |
134 | while (cellQueue.length) {
135 | // pick the most promising cell from the queue
136 | var cell = cellQueue.pop();
137 |
138 | // update the best cell if we found a better one
139 | if (cell.d > bestCell.d) {
140 | bestCell = cell;
141 | if (debug) console.error('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
142 | }
143 |
144 | // do not drill down further if there's no chance of a better solution
145 | if (cell.max - bestCell.d <= precision) continue;
146 |
147 | // split the cell into four cells
148 | h = cell.h / 2;
149 | cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
150 | cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
151 | cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
152 | cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
153 | numProbes += 4;
154 | }
155 |
156 | if (debug) {
157 | console.error('num probes: ' + numProbes);
158 | console.error('best distance: ' + bestCell.d);
159 | }
160 |
161 | return [bestCell.x, bestCell.y];
162 | }
163 |
164 | function compareMax(a, b) {
165 | return b.max - a.max;
166 | }
167 |
168 | function Cell(x, y, h, polygon) {
169 | this.x = x; // cell center x
170 | this.y = y; // cell center y
171 | this.h = h; // half the cell size
172 | this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
173 | this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell
174 | }
175 |
176 | // signed distance from point to polygon outline (negative if point is outside)
177 | function pointToPolygonDist(x, y, polygon) {
178 | var inside = false;
179 | var minDistSq = Infinity;
180 |
181 | for (var k = 0; k < polygon.length; k++) {
182 | var ring = polygon[k];
183 |
184 | for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
185 | var a = ring[i];
186 | var b = ring[j];
187 |
188 | if ((a[1] > y !== b[1] > y) &&
189 | (x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0])) inside = !inside;
190 |
191 | minDistSq = Math.min(minDistSq, getSegDistSq(x, y, a, b));
192 | }
193 | }
194 |
195 | return (inside ? 1 : -1) * Math.sqrt(minDistSq);
196 | }
197 |
198 | // get polygon centroid
199 | function getCentroidCell(polygon) {
200 | var area = 0;
201 | var x = 0;
202 | var y = 0;
203 | var points = polygon[0];
204 |
205 | for (var i = 0, len = points.length, j = len - 1; i < len; j = i++) {
206 | var a = points[i];
207 | var b = points[j];
208 | var f = a[0] * b[1] - b[0] * a[1];
209 | x += (a[0] + b[0]) * f;
210 | y += (a[1] + b[1]) * f;
211 | area += f * 3;
212 | }
213 | if (area === 0) return new Cell(points[0][0], points[0][1], 0, polygon);
214 | return new Cell(x / area, y / area, 0, polygon);
215 | }
216 |
217 | // get squared distance from a point to a segment
218 | function getSegDistSq(px, py, a, b) {
219 | var x = a[0];
220 | var y = a[1];
221 | var dx = b[0] - x;
222 | var dy = b[1] - y;
223 |
224 | if (dx !== 0 || dy !== 0) {
225 | var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
226 |
227 | if (t > 1) {
228 | x = b[0];
229 | y = b[1];
230 | } else if (t > 0) {
231 | x += dx * t;
232 | y += dy * t;
233 | }
234 | }
235 |
236 | dx = px - x;
237 | dy = py - y;
238 |
239 | return dx * dx + dy * dy;
240 | }
241 |
--------------------------------------------------------------------------------
/src/label/util/LabelData.js:
--------------------------------------------------------------------------------
1 | import CONFIG from '../../config';
2 | import parseZoomData from '../../util/zoom/parseZoomData';
3 | import getPolylabelCenter from '../../util/getPolesOfInaccessibility';
4 | import setZoomVisibility from '../../util/zoom.setZoomVisibility';
5 | import transformPolygonCoords from '../../util/transformPolygonCoords';
6 | import getPolygonWithMaxArea from '../../util/getPolygonWithMaxArea';
7 | import transformHexToRGB from '../../util/transformHexToRGB';
8 |
9 | const {
10 | MIN_ZOOM,
11 | MAX_ZOOM,
12 | DEFAULT_POLYGON_FILL_COLOR
13 | } = CONFIG;
14 |
15 | const LABEL_CLASS_DEFAULT = 'ymaps-polylabel-label-default';
16 | const LABEL_CLASS_LIGHT = 'ymaps-polylabel-light-label';
17 | const LABEL_CLASS_DARK = 'ymaps-polylabel-dark-label';
18 | const LABEL_SIZE_DEFAULT = parseZoomData({'1_6': 12, '7_18': 14});
19 |
20 | export default class LabelData {
21 | constructor(polygon, options, zoomRangeOptions, map, label) {
22 | this._map = map;
23 | this._label = label;
24 | this._polygon = polygon;
25 | this._data = {
26 | zoomInfo: {}, // Object with information for each zoom
27 | autoCenter: [0, 0],
28 | dotVisible: typeof options.labelDotVisible !== 'boolean' ? true : options.labelDotVisible
29 | };
30 | this.parsedOptions = LabelData._parseOptions(zoomRangeOptions);
31 | this._polygonCoordsWithMaxArea = transformPolygonCoords.polygon(getPolygonWithMaxArea(this.getPolygonCoords()));
32 | this.updateDotDefaultFlag();
33 | this._init();
34 | }
35 |
36 | setData(key, val) {
37 | this._data[key] = val;
38 | }
39 |
40 | getData(key) {
41 | return this._data[key];
42 | }
43 |
44 | setZoomInfo(zoom, key, value) {
45 | zoom = LabelData.zoomRound(zoom);
46 | this._data.zoomInfo[zoom][key] = value;
47 | }
48 |
49 | getZoomInfo(zoom) {
50 | zoom = LabelData.zoomRound(zoom);
51 | if (zoom || typeof zoom === 'number' && zoom === 0) {
52 | return this._data.zoomInfo[zoom];
53 | }
54 | return this._data.zoomInfo;
55 | }
56 |
57 | getPolygonCoords() {
58 | return this._label.getPolylabelType() === 'collection' ?
59 | this._polygon.geometry.getCoordinates() :
60 | this._polygon.geometry.coordinates;
61 | }
62 |
63 | getLabelCursors() {
64 | const DEFAULT = 'grab';
65 | const result = {
66 | dot: {},
67 | label: {}
68 | };
69 | if (this._label.getPolylabelType() === 'collection') {
70 | result.label.cursor = this._polygon.options.get('labelCursor') || DEFAULT;
71 | result.dot.cursor = this._polygon.options.get('labelDotCursor') || DEFAULT;
72 | } else {
73 | result.label.cursor = this._polygon.options.labelCursor || DEFAULT;
74 | result.dot.cursor = this._polygon.options.labelDotCursor || DEFAULT;
75 | }
76 | return result;
77 | }
78 |
79 | getLabelDefaults(zoom) {
80 | const defaults = this._label.getPolylabelType() === 'collection' ?
81 | this._polygon.options.get('labelDefaults') :
82 | this._polygon.options.labelDefaults;
83 | if (!defaults) return;
84 | return {
85 | className: defaults === 'dark' ?
86 | `${LABEL_CLASS_DEFAULT} ${LABEL_CLASS_DARK}` :
87 | `${LABEL_CLASS_DEFAULT} ${LABEL_CLASS_LIGHT}`,
88 | textSize: LABEL_SIZE_DEFAULT[zoom]
89 | };
90 | }
91 |
92 | getCenterCoords(zoom) {
93 | zoom = LabelData.zoomRound(zoom);
94 | return this.parsedOptions.labelCenterCoords &&
95 | this.parsedOptions.labelCenterCoords[zoom] || this._data.autoCenter;
96 | }
97 |
98 | getStyles(zoom) {
99 | zoom = LabelData.zoomRound(zoom);
100 | const {className: defaultClassName, textSize: defaultTextSize} = this.getLabelDefaults(zoom) || {};
101 |
102 | return {
103 | className: this.parsedOptions.labelClassName && this.parsedOptions.labelClassName[zoom] ||
104 | defaultClassName,
105 | textSize: this.parsedOptions.labelTextSize && this.parsedOptions.labelTextSize[zoom] ||
106 | defaultTextSize,
107 | textColor: this.parsedOptions.labelTextColor && this.parsedOptions.labelTextColor[zoom]
108 | };
109 | }
110 |
111 | getPolygonFillColor() {
112 | const color = this._label.getPolylabelType() === 'collection' ?
113 | this._polygon.options.get('fillColor') :
114 | this._polygon.options.fillColor;
115 | return color || DEFAULT_POLYGON_FILL_COLOR;
116 | }
117 |
118 | getDotColorByPolygonColor() {
119 | let color = this.getPolygonFillColor();
120 | let checkColor = transformHexToRGB(color, 0.9);
121 | if (checkColor) color = checkColor;
122 | return color;
123 | }
124 |
125 | getVisibility(zoom) {
126 | zoom = LabelData.zoomRound(zoom);
127 | return this.parsedOptions.labelForceVisible && this.parsedOptions.labelForceVisible[zoom] ||
128 | this._data.zoomInfo[zoom].visible;
129 | }
130 |
131 | getOffset(zoom) {
132 | zoom = LabelData.zoomRound(zoom);
133 | return this.parsedOptions.labelOffset && this.parsedOptions.labelOffset[zoom] || [0, 0];
134 | }
135 |
136 | updateDotDefaultFlag() {
137 | this.isDotDefault = this._label.getPolygonOptions().labelDotLayout ? false : true;
138 | }
139 |
140 | getPermissibleInaccuracyOfVisibility(zoom) {
141 | zoom = LabelData.zoomRound(zoom);
142 | return this.parsedOptions.labelPermissibleInaccuracyOfVisibility &&
143 | this.parsedOptions.labelPermissibleInaccuracyOfVisibility[zoom] || 0;
144 | }
145 |
146 | getSize(zoom, type) {
147 | zoom = LabelData.zoomRound(zoom);
148 | return this._data.zoomInfo[zoom][`${type}Size`];
149 | }
150 |
151 | setSize(zoom, type, size) {
152 | zoom = LabelData.zoomRound(zoom);
153 | if (size.height > 0 && size.width > 0) {
154 | this._data.zoomInfo[zoom][`${type}Size`] = size;
155 | }
156 | }
157 |
158 | static zoomRound(z) {
159 | return Math.round(z);
160 | }
161 |
162 | setVisible(zoom, type, layout) {
163 | zoom = LabelData.zoomRound(zoom);
164 | if (this.getSize(zoom, type)) return;
165 | const zoomData = setZoomVisibility(
166 | type,
167 | this._map,
168 | zoom,
169 | this._data.zoomInfo[zoom].visible,
170 | layout,
171 | transformPolygonCoords.point(this.getCenterCoords(zoom), this._polygonCoordsWithMaxArea.isPositivePart),
172 | this._polygonCoordsWithMaxArea.coords,
173 | this.getOffset(zoom),
174 | this.getPermissibleInaccuracyOfVisibility(zoom)
175 | );
176 | if (!zoomData) return;
177 |
178 | this._data.zoomInfo[zoom].visible = zoomData.visible;
179 | this.setSize(zoom, type, zoomData.size);
180 | }
181 |
182 | _checkParams() {
183 | if (!this._label.getPolygonOptions().labelLayout) {
184 | const errText = 'Не указан шаблон для подписи (labelLayout)';
185 | console.error(errText);
186 | throw new Error(errText);
187 | }
188 | }
189 |
190 | _init() {
191 | this._checkParams();
192 | const autoCenter = getPolylabelCenter(this._polygonCoordsWithMaxArea.coords, 1.0);
193 | this._data.autoCenter = autoCenter;
194 |
195 | for (let z = MIN_ZOOM; z <= MAX_ZOOM; z++) {
196 | this._data.zoomInfo[z] = LabelData._createDefaultZoomInfo(z);
197 | }
198 | }
199 |
200 | static _parseOptions(options) {
201 | let result = {};
202 | Object.keys(options).forEach(key => {
203 | result[key] = parseZoomData(options[key]);
204 | });
205 | return result;
206 | }
207 |
208 | static _createDefaultZoomInfo() {
209 | return {
210 | visible: 'none', // label | dot | none,
211 | dotSize: undefined,
212 | labelSize: undefined
213 | };
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/polylabel/PolylabelCollection.js:
--------------------------------------------------------------------------------
1 | import GeoObjectCollection from 'api/GeoObjectCollection';
2 | import Monitor from 'api/Monitor';
3 | import nextTick from 'api/system.nextTick';
4 | import EventManager from 'api/event.Manager';
5 | import Event from 'api/Event';
6 |
7 | import PBase from './PolylabelBase';
8 | import Label from '../label/GeoObjectCollection/Label';
9 | import State from '../util/State';
10 |
11 | export default class PolylabelCollection extends PBase {
12 | constructor(map, polygonsCollection) {
13 | super(map);
14 |
15 | this._map = map;
16 | this._labelsCollection = new GeoObjectCollection();
17 | this._labelsState = new State();
18 | this._userState = new State(); // everything that needs to be added to the user
19 | this._polygonsCollection = polygonsCollection;
20 | this._isPolygonParentChange = new WeakMap();
21 | this._polylabelType = 'collection';
22 | this._init();
23 | }
24 |
25 | destroy() {
26 | this._deleteLabelStateListeners();
27 | this._deletePolygonsListeners();
28 | this._deletePolygonCollectionListeners();
29 | this._deleteLabelCollection();
30 | this._map.geoObjects.remove(this._labelsCollection);
31 | }
32 |
33 | /**
34 | * Returns the status of the label for the specified polygon
35 | */
36 | getLabelState(polygon) {
37 | return this._userState.getState(polygon);
38 | }
39 |
40 | _init() {
41 | this._map.geoObjects.add(this._labelsCollection);
42 | this._firstCalculatePolygons().then(() => {
43 | this._initMapListeners();
44 | this._initPolygonCollectionListeners();
45 | this._initLabelCollectionListeners();
46 | });
47 | }
48 |
49 | _firstCalculatePolygons() {
50 | this._clearLabelCollection();
51 | this._polygonsCollection.each(polygon => {
52 | this._calculateNewPolygon(polygon).then(label => {
53 | this._setLabelData(polygon, label);
54 | });
55 | });
56 | return Promise.resolve();
57 | }
58 |
59 | _calculatePolygons() {
60 | let promises = [];
61 | this._polygonsCollection.each(polygon => {
62 | if (polygon.geometry.getType() === 'Polygon') {
63 | const label = this._labelsState.get(polygon, 'label');
64 | if (label) promises.push(this._setLabelData(polygon, label));
65 | }
66 | });
67 | return Promise.all(promises);
68 | }
69 |
70 | /**
71 | * Cleaning the collection of labels
72 | */
73 | _clearLabelCollection() {
74 | this._labelsCollection.removeAll();
75 | this._labelsCollection.options.set({
76 | pane: 'phantom'
77 | });
78 | }
79 |
80 | /**
81 | * Destroy each label from all polygons
82 | */
83 | _deleteLabelCollection() {
84 | this._polygonsCollection.each(polygon => {
85 | const label = this._labelsState.get(polygon, 'label');
86 | if (label) label.destroy();
87 | });
88 | this._clearLabelCollection();
89 | }
90 |
91 | /**
92 | * Calculates data for the polygon label
93 | */
94 | _calculatePolygonLabelData(polygon, isLabelCreated) {
95 | const options = this.getConfigOptions(polygon);
96 | const zoomRangeOptions = this.getConfigZoomRangeOptions(polygon);
97 |
98 | const label = (isLabelCreated) ?
99 | this._labelsState.get(polygon, 'label') :
100 | new Label(this._map, polygon, this._labelsCollection, this);
101 |
102 | label.createLabelData(options, zoomRangeOptions);
103 | label.createLayoutTemplates();
104 | return Promise.resolve(label);
105 | }
106 |
107 | /**
108 | * Analyzes data about the label of the polygon and establishes the parameters of the label
109 | */
110 | _setLabelData(polygon, label, visibleState) {
111 | const data = label.setDataByZoom(this._map.getZoom(), visibleState);
112 | this._setCurrentConfiguredVisibility(polygon, data.currentConfiguredVisibileType);
113 | this._setCurrentVisibility(polygon, data.currentVisibleType);
114 | this._setCurrentCenter(polygon, data.currentCenter);
115 | }
116 |
117 | _setCurrentCenter(polygon, center) {
118 | this._userState.set(polygon, 'center', center);
119 | }
120 |
121 | /**
122 | * Sets the current visibility status for the polygon (automatically calculated)
123 | */
124 | _setCurrentConfiguredVisibility(polygon, type) {
125 | this._userState.set(polygon, 'currentConfiguredVisibility', type);
126 | }
127 |
128 | /**
129 | * Sets the current visibility status for the polygon
130 | */
131 | _setCurrentVisibility(polygon, type) {
132 | this._userState.set(polygon, 'currentVisibility', ['dot', 'label'].indexOf(type) !== -1 ? type : 'none');
133 | }
134 |
135 | /**
136 | * Calculates the new polygon added to the collection
137 | */
138 | _calculateNewPolygon(polygon) {
139 | if (polygon.geometry.getType() !== 'Polygon') {
140 | return Promise.reject();
141 | }
142 |
143 | return new Promise(resolve => {
144 | this._calculatePolygonLabelData(polygon).then(label => {
145 | this._labelsState.set(polygon, 'label', label);
146 | this._initUserStateListener(polygon);
147 | this._initPolygonListener(polygon);
148 | label.createPlacemarks();
149 | label.addToCollection().then(() => {
150 | resolve(label);
151 | });
152 | });
153 | });
154 | }
155 |
156 | /**
157 | * Clears the status of visible all label
158 | * (on new zoom it did not conflict with the calculated data, since state priority is higher)
159 | */
160 | _clearVisibilityInLabelsState(value) {
161 | this._polygonsCollection.each(polygon => {
162 | this._userState.set(polygon, 'visible', value);
163 | });
164 | }
165 |
166 | /**
167 | * Listener for changing the visibility state of the label at the polygon
168 | */
169 | _initUserStateListener(polygon) {
170 | const monitor = new Monitor(this._userState.getState(polygon));
171 | this._userState.set(polygon, '_labelMonitor', monitor);
172 | monitor.add('visible', newValue => {
173 | this._setLabelData(
174 | polygon,
175 | this._labelsState.get(polygon, 'label'),
176 | newValue
177 | );
178 | });
179 | }
180 |
181 | /**
182 | * Creates listener events on the polygon
183 | */
184 | _initPolygonListener(polygon) {
185 | if (polygon.geometry.getType() === 'Polygon') {
186 | polygon.events.add(['optionschange', 'propertieschange'], this._onPolygonOptionsChangeHandler, this);
187 | polygon.events.add('parentchange', this._onPolygonParentChangeHandler, this);
188 | }
189 | }
190 |
191 | _onPolygonParentChangeHandler(event) {
192 | this._isPolygonParentChange.set(event.get('target'), true);
193 | }
194 |
195 | _onPolygonOptionsChangeHandler(event) {
196 | nextTick(() => { // тк может произойти удаление объекта и optionschange тоже дернется
197 | // сделан nextTick чтобы до этого проверить был ли parentchange, тк он происходит до optionschange
198 | const polygon = event.get('target');
199 | const label = this._labelsState.get(polygon, 'label');
200 |
201 | const curr = this._isPolygonParentChange.get(polygon);
202 | if (curr || !label) return;
203 |
204 | label.setVisibilityForce('none');
205 | label.setLayoutTemplate();
206 |
207 | this._calculatePolygonLabelData(polygon, true).then(label => {
208 | label.updateLayouts();
209 | this._labelsState.set(polygon, 'label', label);
210 | this._setLabelData(polygon, label);
211 | });
212 | });
213 | }
214 |
215 | /**
216 | * Creates listener events for a collection of polygons
217 | */
218 | _initPolygonCollectionListeners() {
219 | this._polygonsCollection.events.add(['add', 'remove'], this._polygonCollectionEventHandler, this);
220 | }
221 |
222 | _polygonCollectionEventHandler(event) {
223 | switch (event.get('type')) {
224 | case 'add': {
225 | const polygon = event.get('child');
226 | this._calculateNewPolygon(polygon).then(label => {
227 | this._setLabelData(polygon, label);
228 | });
229 | break;
230 | }
231 | case 'remove': {
232 | const polygon = event.get('child');
233 | this._deleteLabelStateListener(polygon);
234 | const label = this._labelsState.get(polygon, 'label');
235 | if (label) label.destroy();
236 | break;
237 | }
238 | }
239 | }
240 |
241 | /**
242 | * Makes the transfer of events from the label to the corresponding polygon
243 | */
244 | _initLabelCollectionListeners() {
245 | const controller = {
246 | onBeforeEventFiring: (events, type, event) => {
247 | if (event.get('target').options.get('pane') === 'phantom') return false;
248 |
249 | let polygon = event.get('target').properties.get('polygon');
250 | if (!polygon) return false;
251 |
252 | if (type === 'mouseenter' || type === 'mouseleave') {
253 | const labelInst = this._labelsState.get(polygon, 'label');
254 | if (labelInst && labelInst.getLabelData().isDotDefault) {
255 | if (type === 'mouseenter') {
256 | labelInst.addDotClass('ymaps-polylabel-dot-default_hover');
257 | } else {
258 | labelInst.removeDotClass('ymaps-polylabel-dot-default_hover');
259 | }
260 | }
261 | }
262 |
263 | let newEvent = new Event({
264 | target: polygon,
265 | type: `label${type}`
266 | }, event);
267 |
268 | polygon.events.fire(`label${type}`, newEvent);
269 | return false;
270 | }
271 | };
272 | const eventManager = new EventManager({
273 | controllers: [controller]
274 | });
275 | this._labelsCollection.events.setParent(eventManager);
276 | }
277 |
278 | _initMapListeners() {
279 | this.initMapListeners((type) => {
280 | if (type === 'actionendzoomchange') {
281 | this._clearVisibilityInLabelsState();
282 | this._calculatePolygons();
283 | } else if (type === 'actionbeginzoomchange') {
284 | this._clearVisibilityInLabelsState('none');
285 | }
286 | });
287 | }
288 |
289 | _deleteLabelStateListeners() {
290 | this._polygonsCollection.each(polygon => {
291 | this._deleteLabelStateListener(polygon);
292 | });
293 | }
294 |
295 | /**
296 | * Removes the listener for changing the visibility state of the label at the polygon
297 | */
298 | _deleteLabelStateListener(polygon) {
299 | const monitor = this._userState.get(polygon, '_labelMonitor');
300 | if (monitor) monitor.removeAll();
301 | }
302 |
303 | /**
304 | * Removes listener from the polygon collection
305 | */
306 | _deletePolygonCollectionListeners() {
307 | this._polygonsCollection.events.remove(['add', 'remove'], this._polygonCollectionEventHandler, this);
308 | this.destroyMapListeners();
309 | }
310 |
311 | _deletePolygonsListeners() {
312 | this._polygonsCollection.each((polygon) => {
313 | this._deletePolygonListener(polygon);
314 | });
315 | }
316 |
317 | /**
318 | * Removes event listeners from a polygon
319 | */
320 | _deletePolygonListener(polygon) {
321 | polygon.events.remove(['optionschange', 'propertieschange'], this._onPolygonOptionsChangeHandler, this);
322 | polygon.events.remove('parentchange', this._onPolygonParentChangeHandler, this);
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/src/polylabel/PolylabelObjectManager.js:
--------------------------------------------------------------------------------
1 | import Monitor from 'api/Monitor';
2 | import ObjectManager from 'api/ObjectManager';
3 | import nextTick from 'api/system.nextTick';
4 | import EventManager from 'api/event.Manager';
5 |
6 | import PBase from './PolylabelBase';
7 | import Label from '../label/ObjectManager/Label';
8 | import State from '../util/State';
9 |
10 | export default class PolylabelObjectManager extends PBase {
11 | constructor(map, objectManager) {
12 | super(map);
13 | this._map = map;
14 | this._polygonsObjectManager = objectManager;
15 | this._labelsObjectManager = new ObjectManager();
16 | this._labelsState = new State();
17 | this._userState = new State(); // everything that needs to be added to the user
18 | this._polylabelType = 'objectmanager';
19 | this._init();
20 | }
21 |
22 | destroy() {
23 | this._deleteLabelsOverlaysListeners();
24 | this._deleteLabelStateListeners();
25 | this._deletePolygonsListeners();
26 | this._deletePolygonsObjectsListeners();
27 | this._deleteLabelsOMListeners();
28 | }
29 |
30 | /**
31 | * Returns the status of the label for the specified polygon
32 | */
33 | getLabelState(polygon) {
34 | return this._userState.getState(polygon);
35 | }
36 |
37 | _init() {
38 | this._map.geoObjects.add(this._labelsObjectManager);
39 | this._initLabelsOverlaysListeners();
40 | this._initPolygonsObjectsListeners();
41 | this._initPolygonsListeners();
42 | this._initLabelsOMListeners();
43 |
44 | this._calculatePolygons().then(() => this._initMapListeners());
45 | }
46 |
47 | /**
48 | * Sets the data for labels for the current zoom
49 | */
50 | _calculatePolygons() {
51 | this._polygonsObjectManager.objects.each(polygon => {
52 | this._calculateNewPolygon(polygon);
53 | });
54 | return Promise.resolve();
55 | }
56 |
57 | /**
58 | * Calculates data for the label signature
59 | * Creates a label
60 | */
61 | _calculatePolygonLabelData(polygon, isLabelCreated) {
62 | const options = this.getConfigOptions(polygon);
63 | const zoomRangeOptions = this.getConfigZoomRangeOptions(polygon);
64 |
65 | const label = (isLabelCreated) ?
66 | this._labelsState.get(polygon, 'label') :
67 | new Label(this._map, polygon, this._labelsObjectManager, this);
68 |
69 | label.createLabelData(options, zoomRangeOptions);
70 | label.createLayoutTemplates();
71 | return Promise.resolve(label);
72 | }
73 |
74 | /**
75 | * Analyzes data about the label of the polygon and establishes the parameters of the label
76 | */
77 | _setLabelData(polygon, label, visibleState, types) {
78 | const data = label.setDataByZoom(this._map.getZoom(), types, visibleState);
79 | this._setCurrentConfiguredVisibility(polygon, data.currentConfiguredVisibileType);
80 | this._setCurrentVisibility(polygon, data.currentVisibleType);
81 | this._setCurrentCenter(polygon, data.currentCenter);
82 | }
83 |
84 | _setCurrentCenter(polygon, center) {
85 | this._userState.set(polygon, 'center', center);
86 | }
87 |
88 | _setCurrentConfiguredVisibility(polygon, type) {
89 | this._userState.set(polygon, 'currentConfiguredVisibility', type);
90 | }
91 |
92 | _setCurrentVisibility(polygon, type) {
93 | this._userState.set(polygon, 'currentVisibility', ['dot', 'label'].indexOf(type) !== -1 ? type : 'none');
94 | }
95 |
96 | /**
97 | * Listener for changing the visibility state of the label at the polygon
98 | */
99 | _initLabelStateListener(polygon) {
100 | const monitor = new Monitor(this._userState.getState(polygon));
101 | this._userState.set(polygon, '_labelMonitor', monitor);
102 | monitor.add('visible', newValue => {
103 | if (!polygon) return;
104 | this._setLabelData(
105 | polygon,
106 | this._labelsState.get(polygon, 'label'),
107 | newValue,
108 | ['dot', 'label']
109 | );
110 | });
111 | }
112 |
113 | _initLabelsOverlaysListeners() {
114 | this._labelsObjectManager.objects.overlays.events.add(
115 | ['add', 'remove'], this._labelsOverlaysEventHandler, this
116 | );
117 | }
118 |
119 | _getLabelType(labelId) {
120 | return labelId.indexOf('label#') !== -1 ? 'label' : 'dot';
121 | }
122 |
123 | _labelOverlaysGeometryChangeHandler(event) {
124 | const overlay = event.get('target');
125 | const labelId = overlay._data.id;
126 |
127 | overlay.getLayout().then(layout => {
128 | const labelType = this._getLabelType(labelId);
129 | const label = this._labelsObjectManager.objects.getById(labelId);
130 | if (!label) return;
131 |
132 | const polygon = label.properties.polygon;
133 | const labelInst = this._labelsState.get(polygon, 'label');
134 | labelInst.setLayout(labelType, layout);
135 | this._setLabelData(polygon, labelInst, undefined, [labelType]);
136 | });
137 | }
138 |
139 | _labelsOverlaysEventHandler(event) {
140 | const labelId = event.get('objectId');
141 | const labelType = this._getLabelType(labelId);
142 | const overlay = event.get('overlay');
143 |
144 | switch (event.get('type')) {
145 | case 'add': {
146 | overlay.events.add('geometrychange', this._labelOverlaysGeometryChangeHandler, this);
147 | nextTick(() => { // overlay добавляется на карту на следующем тике
148 | if (!overlay) return;
149 | overlay.getLayout().then(layout => {
150 | if (!layout) return;
151 | const label = this._labelsObjectManager.objects.getById(labelId);
152 | if (!label) return;
153 |
154 | const polygon = label.properties.polygon;
155 | const labelInst = this._labelsState.get(polygon, 'label');
156 | labelInst.setLayout(labelType, layout);
157 | this._setLabelData(polygon, labelInst, undefined, [labelType]);
158 | });
159 | });
160 | break;
161 | }
162 | case 'remove': {
163 | overlay.events.remove('geometrychange', this._labelOverlaysGeometryChangeHandler, this);
164 | break;
165 | }
166 | }
167 | }
168 |
169 | _clearVisibilityInLabelsState(value) {
170 | this._polygonsObjectManager.objects.each(polygon => {
171 | this._userState.set(polygon, 'visible', value);
172 | });
173 | }
174 |
175 | _initMapListeners() {
176 | this.initMapListeners(type => {
177 | if (type === 'actionendzoomchange') {
178 | this._clearVisibilityInLabelsState();
179 | } else if (type === 'actionbeginzoomchange') {
180 | this._clearVisibilityInLabelsState('none');
181 | }
182 | });
183 | }
184 |
185 | _initPolygonsObjectsListeners() {
186 | this._polygonsObjectManager.objects.events.add(['add', 'remove'], this._polygonCollectionEventHandler, this);
187 | }
188 |
189 | _polygonCollectionEventHandler(event) {
190 | switch (event.get('type')) {
191 | case 'add': {
192 | const polygon = event.get('child');
193 | this._calculateNewPolygon(polygon);
194 | break;
195 | }
196 | case 'remove': {
197 | const polygon = event.get('child');
198 | this._deleteLabelStateListener(polygon);
199 | const label = this._labelsState.get(polygon, 'label');
200 | if (label) label.destroy();
201 | break;
202 | }
203 | }
204 | }
205 |
206 | _calculateNewPolygon(polygon) {
207 | if (polygon.geometry.type === 'Polygon') {
208 | this._calculatePolygonLabelData(polygon).then(label => {
209 | this._labelsState.set(polygon, 'label', label);
210 | this._initLabelStateListener(polygon);
211 | label.createPlacemarks();
212 | label.addToObjectManager();
213 | });
214 | }
215 | }
216 |
217 | _initPolygonsListeners() {
218 | this._polygonsObjectManager.objects.events.add(
219 | ['optionschange', 'objectoptionschange'],
220 | this._onPolygonOptionsChangeHandler,
221 | this
222 | );
223 | }
224 |
225 | _onPolygonOptionsChangeHandler(event) {
226 | const polygon = this._polygonsObjectManager.objects.getById(event.get('objectId'));
227 | if (!polygon) return;
228 |
229 | this._calculatePolygonLabelData(polygon, true).then(label => {
230 | label.setVisibilityForce('none');
231 | this._labelsState.set(polygon, 'label', label);
232 |
233 | label.setLayoutTemplate();
234 | label.updateOptions();
235 | label.updateLayouts();
236 | this._setLabelData(polygon, this._labelsState.get(polygon, 'label'), undefined, ['dot', 'label']);
237 | });
238 | }
239 |
240 | _initLabelsOMListeners() {
241 | let controller = {
242 | onBeforeEventFiring: (events, type, event) => {
243 | const labelId = event.get('objectId');
244 | if (!labelId) return false;
245 |
246 | let polygonId = labelId.split('#')[1];
247 | polygonId = isNaN(Number(polygonId)) ? polygonId : Number(polygonId);
248 |
249 | const polygon = this._polygonsObjectManager.objects.getById(polygonId);
250 | const label = this._labelsObjectManager.objects.getById(labelId);
251 | if (label && label.options.pane === 'phantom' || !polygon) return false;
252 |
253 | if (type === 'mouseenter' || type === 'mouseleave') {
254 | const labelInst = this._labelsState.get(polygon, 'label');
255 | if (labelInst && labelInst.getLabelData().isDotDefault) {
256 | if (type === 'mouseenter') {
257 | labelInst.addDotClass('ymaps-polylabel-dot-default_hover');
258 | } else {
259 | labelInst.removeDotClass('ymaps-polylabel-dot-default_hover');
260 | }
261 | }
262 | }
263 |
264 | this._polygonsObjectManager.events.fire(`label${type}`, {
265 | objectId: polygonId,
266 | type: `label${type}`
267 | });
268 |
269 | return false;
270 | }
271 | };
272 | let eventManager = new EventManager({
273 | controllers: [controller]
274 | });
275 | this._labelsObjectManager.events.setParent(eventManager);
276 | }
277 |
278 | _deleteLabelStateListeners() {
279 | this._polygonsObjectManager.objects.each(polygon => {
280 | if (polygon.geometry.type === 'Polygon') {
281 | this._deleteLabelStateListener(polygon);
282 | }
283 | });
284 | }
285 |
286 | _deleteLabelStateListener(polygon) {
287 | const monitor = this._userState.get(polygon, '_labelMonitor');
288 | if (monitor) monitor.removeAll();
289 | }
290 |
291 | _deleteLabelsOverlaysListeners() {
292 | this._labelsObjectManager.objects.overlays.events.remove(
293 | ['add', 'remove'], this._labelsOverlaysEventHandler, this
294 | );
295 | }
296 |
297 | _deletePolygonsListeners() {
298 | this._polygonsObjectManager.objects.events.remove(['optionschange', 'objectoptionschange'],
299 | this._onPolygonOptionsChangeHandler, this);
300 | }
301 |
302 | _deletePolygonsObjectsListeners() {
303 | this._polygonsObjectManager.objects.events.remove(['add', 'remove'], this._polygonCollectionEventHandler, this);
304 | this.destroyMapListeners();
305 | }
306 |
307 | /**
308 | * Destroy each label from all polygons
309 | */
310 | _deleteLabelsOMListeners() {
311 | this._polygonsObjectManager.objects.each(polygon => {
312 | const label = this._labelsState.get(polygon, 'label');
313 | if (polygon.geometry.type === 'Polygon' && label) {
314 | label.destroy();
315 | }
316 | });
317 | this._clearLabels();
318 | }
319 |
320 | _clearLabels() {
321 | this._labelsObjectManager.removeAll();
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Yandex.Maps API Polylabeler Plugin
2 | ===================
3 |
4 | This module allows to set labels inside polygons on the map choosing the suitable position automatically.
5 | It is created for [Yandex.Maps JS API v2.1](https://tech.yandex.ru/maps/doc/jsapi/2.1/quick-start/tasks/quick-start-docpage/) and based on [Polylabel module](https://github.com/mapbox/polylabel)
6 |
7 | [Demo1](https://yandex.github.io/mapsapi-polylabeler/docs/example-text/)
8 | [Demo2](https://yandex.github.io/mapsapi-polylabeler/docs/example-img/)
9 |
10 | 
11 |
12 | Loading
13 | ============
14 |
15 | 1. Load [Yandex.Maps JS API 2.1](https://tech.yandex.ru/maps/doc/jsapi/2.1/quick-start/tasks/quick-start-docpage/), Polylabeler Plugin and ([Area calculation plugin](https://github.com/yandex/mapsapi-area)) (It is used by the present module) by adding the following code into the **head** section of your page.
16 |
17 | ```html
18 |
19 |
20 |
21 |
22 | ```
23 |
24 | Simple example
25 | ============
26 | #### Example shows, how to create label for one polygon.
27 |
28 | ```js
29 | ymaps.ready(['polylabel.create']).then(function () {
30 | const map = new ymaps.Map('map', {
31 | center: [65, 81],
32 | zoom: 5
33 | });
34 | const objectManager = new ymaps.ObjectManager();
35 |
36 | objectManager.add({
37 | type: 'Feature',
38 | id: 1,
39 | geometry: {
40 | type: 'Polygon',
41 | coordinates: [[
42 | [66 , 74],
43 | [68, 92],
44 | [59, 88],
45 | [62, 80],
46 | [66, 74]
47 | ]]
48 | },
49 | properties: {
50 | name: 'nameOfMyPolygon'
51 | },
52 | options: {
53 | labelDefaults: 'light',
54 | labelLayout: '{{properties.name}}'
55 | }
56 | });
57 | map.geoObjects.add(objectManager);
58 | const polylabel = new ymaps.polylabel.create(map, objectManager);
59 | });
60 | ```
61 |
62 | Launch
63 | ============
64 | It is possible to work with two API entities:
65 | * [GeoObjectCollection](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/GeoObjectCollection-docpage/)
66 | * [ObjectManager](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/ObjectManager-docpage/)
67 |
68 | Get access to the module functions by using [ymaps.modules.require](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/modules.require-docpage/) method:
69 | ```js
70 | ymaps.modules.require(['polylabel.create']).then(function (Polylabel) {
71 | /**
72 | * @param {Map} map - map instance
73 | * @param {GeoObjectCollection | ObjectManager} component -
74 | * instance of a collection or an object manager that contains polygons to be labeled
75 | */
76 | const polyLabeler = new Polylabel(map, component);
77 | });
78 | ```
79 |
80 |
81 | Documentation
82 | ============
83 | The module works according to the following principle:
84 | Two labels are created for the polygon: a small (**dot**) and a main (**label**).
85 | If the main label can't fit into the polygon, the module tries to place a small one;
86 | If both does not fit, nothing is displayed.
87 |
88 | ## Methods
89 | | Name | Returns | Description |
90 | |------|---------|-------------|
91 | | [getLabelState(polygon)](#getlabelstate) | [DataManager](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/data.Manager-docpage/) | label state |
92 |
93 | ### getLabelState
94 | | Parameter | Type | Default value | Description |
95 | |-----------|------|---------------|-------------|
96 | | polygon | [GeoObject](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/GeoObject-docpage/) | - | Object, describing a polygon. Use GeoObject for the [GeoObjectCollection](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/GeoObjectCollection-docpage/) |
97 | | | JSON | - | Use JSON for the [ObjectManager](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/ObjectManager-docpage/) |
98 |
99 |
100 | ```js
101 | const polyLabeler = new Polylabel(map, objectManager);
102 | let state = polyLabeler.getLabelState(polygon);
103 | ```
104 |
105 | ## State
106 |
107 | ### write/read
108 | | Name | Type | Default value | Value | Description |
109 | |------|------|---------------|-------|-------------|
110 | | visible | string\|undefined | undefined | dot | Show small label |
111 | | | | | label | Show main label |
112 | | | | | none | Hide all labels |
113 | | | | | undefined | Automatic calculation |
114 |
115 | With map zoom change the state resets to default.
116 |
117 | In ObjectManager version of polylabeler state resets to default on any change within labels in vieport, i.e. on any zoom and almost on any drag. It's better to set `labelForceVisible` option to hide labels instead of updating state's `visible`.
118 |
119 | ```js
120 | const polyLabeler = new Polylabel(map, objectManager);
121 | let state = polyLabeler.getLabelState(polygon);
122 | state.set('visible', 'dot');
123 | ```
124 |
125 | ### only read
126 | | Name | Type | Description |
127 | |------|------|-------------|
128 | | center | Array[2] | Current center of label |
129 | | currentVisibility | string | Label current visibility |
130 | | currentConfiguredVisibility | string | Label visiblity, which configured by module |
131 |
132 | ```js
133 | const polyLabeler = new Polylabel(map, objectManager);
134 | let state = polyLabeler.getLabelState(polygon);
135 | state.get('center');
136 | ```
137 |
138 |
139 | ## Events
140 | Labels events can be accessed through polygons.
141 | List of events can be found in
142 | [GeoObject](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/GeoObject-docpage/#events-summary).
143 | For events handling add listener for the desired event with the prefix **"label"** to
144 | [EventManager](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/event.Manager-docpage/) of [GeoObjectCollection](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/GeoObjectCollection-docpage/) or [ObjectManager](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/ObjectManager-docpage/).
145 |
146 | ```js
147 | // In this example, we listen to pointing the cursor at the label and
148 | // moving the cursor off the label
149 | geoObjectCollection.events.add(['labelmouseenter', 'labelmouseleave'], event => {
150 | // Get the polygon on which the event occured
151 | var polygon = event.get('target');
152 | // Get label state
153 | var state = polyLabeler.getLabelState(polygon);
154 | // Change the visibility of the label depending on the type of event
155 | state.set('visible', event.get('type') === 'labelmouseleave' ? undefined : 'label');
156 | });
157 | ```
158 |
159 |
160 | ## Polygon options
161 | ### The label is controlled via the polygon options
162 | **\* Mandatory option**
163 |
164 | | Name | Type | Default value |
165 | |------|------|---------------|
166 | | [labelLayout](#labellayout) * | string | - |
167 | | [labelDotLayout](#labeldotlayout) | string | default dot layout |
168 | | [labelDotVisible](#labeldotvisible) | boolean | true |
169 | | [labelDefaults](#labeldefaults) | string | - |
170 | | [labelCursor](#labelcursor) | string | 'grab' |
171 | | [labelDotCursor](#labeldotcursor) | string | 'grab' |
172 | | [labelClassName](#labelclassname) | ZoomRange < string > \|\| string | - |
173 | | [labelForceVisible](#labelforcevisible)| ZoomRange < string > \|\| string | - |
174 | | [labelTextColor](#labeltextcolor) | ZoomRange < string > \|\| string | - |
175 | | [labelTextSize](#labeltextsize) | ZoomRange < number > \|\| number | - |
176 | | [labelCenterCoords](#labelcentercoords)| ZoomRange < Array[2]< number >> \|\| Array[2]< number > | - |
177 | | [labelOffset](#labeloffset) | ZoomRange < Array[2]< number >> \|\| Array[2]< number > | \[0, 0\] |
178 | | [labelPermissibleInaccuracyOfVisibility](#labelpermissibleinaccuracyofvisibility) | ZoomRange < number > \|\| number | 0 |
179 |
180 | Type **ZoomRange< T >** object allows to set zoom level or zoom range to specify values for the certain scales.
181 | **T** - type of value.
182 | ```js
183 | // In this example we specify 'someOptions' value for different zoom levels
184 | // for the 1-st zoom it will be set to 12,
185 | // from 2-nd to 5-th the value is 14, from 6-th to 22-nd it is 16 and on the 23-rd it is 18
186 | someOptions: {
187 | 1: 12,
188 | '2_5': 14,
189 | '6_22': 16,
190 | 23: 18
191 | }
192 | // or you can specify same value for all zoom levels
193 | someOptions: 12
194 | // if you skip some zoom levels, they will have a value calculated automatically or they will be simply ignored
195 | someOptions: {
196 | 1: 12,
197 | '2_3': 13
198 | }
199 | ```
200 |
201 | ### labelLayout
202 | Template that describes the layout of a main label.
203 | [Based on Template](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/Template-docpage/)
204 | ```js
205 | polygon.options.set({
206 | labelLayout: 'bar
'
207 | });
208 | ```
209 |
210 | ### labelDotLayout
211 | Template that describes the layout of a small label (dot).
212 |
213 | **Default behavior:**
214 | If you do not specify this option, the default dot will be drawn.
215 |
216 | ```js
217 | polygon.options.set({
218 | labelDotLayout: ``
220 | });
221 | ```
222 |
223 | ### labelDotVisible
224 | Responsible for small labels displaying.
225 | - ***true*** - show
226 | - ***false*** - don't show
227 |
228 | **Default behavior:**
229 | Small labels are displayed.
230 |
231 | ### labelDefaults
232 | Responsible for the labels default layout
233 |
234 | ***Values:***
235 | - ***dark*** - dark theme
236 | - ***light*** - light theme
237 |
238 | ### labelCursor
239 | Cursor type to be displayed when hovering over the main label.
240 | [Possible Values](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/util.cursor.Manager-docpage/#push-param-key)
241 |
242 | ### labelDotCursor
243 | Cursor type to be displayed when hovering over the small label.
244 | [Possible Values](https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/util.cursor.Manager-docpage/#push-param-key)
245 |
246 | ### labelClassName
247 | CSS-class name to be applied to the label.
248 | **Important:** if you use *labelDefaults* option, *labelClassName* doesn't work.
249 |
250 | ```js
251 | // Apply "foo-class" to the label on the first zoom level,
252 | // for others apply 'bar-class"
253 |
254 | polygon.options.set({
255 | labelClassName: {
256 | 1: 'foo-class',
257 | '2_23': 'bar-class'
258 | }
259 | });
260 | ```
261 |
262 | ### labelForceVisible
263 | Label type to be displayed.
264 | - ***label*** - show main label
265 | - ***dot*** - show small label
266 | - ***none*** - don't display anything
267 |
268 | ```js
269 | // On the first two zoom levels, the main label will be always shown.
270 | // On the others - small label will be always shown.
271 |
272 | polygon.options.set({
273 | labelForceVisible: {
274 | '0_1': 'label',
275 | '2_23': 'dot'
276 | }
277 | });
278 | ```
279 |
280 | **Default behavior:**
281 | Automatic calculation.
282 |
283 | ### labelTextColor
284 | Label font color.
285 |
286 | ```js
287 | //Set '#FCEA00' as labels font color for all zoom levels
288 | polygon.options.set({
289 | labelTextColor: '#FCEA00'
290 | });
291 | ```
292 |
293 | ### labelTextSize
294 | Label font size.
295 |
296 | ```js
297 | // On the first five zoom levels, labels font size is '22'.
298 | // On the others - '11'.
299 | polygon.options.set({
300 | labelTextSize: {
301 | '0_4': 22,
302 | '5_23': 11
303 | }
304 | });
305 | ```
306 |
307 | ### labelCenterCoords
308 | Geographic coordinates where the label will be displayed.
309 |
310 | ```js
311 | // On the first zoom level coords - [37.0192, 61.01210]
312 | // On the next two - [38.123, 62.9182]
313 | // On the others - Automatic calculation
314 | polygon.options.set({
315 | labelCenterCoords: {
316 | 0: [37.0192, 61.01210],
317 | '2_3': [38.123, 62.9182]
318 | }
319 | });
320 | ```
321 |
322 | **Default behavior:**
323 | Automatic calculation.
324 |
325 | ### labelOffset
326 | Offset in pixels from the current position of the label.
327 | - zero element - left offset.
328 | - first element - top offset.
329 |
330 | ```js
331 | //Set labels offset to the left as 10px and down as 20px
332 | polygon.options.set({
333 | labelOffset: [-10, 20]
334 | });
335 | ```
336 |
337 | **Default behavior:**
338 | labelOffset: \[0, 0]
339 |
340 | ### labelPermissibleInaccuracyOfVisibility
341 | The value in pixels describing max area outside of the polygon that could be occupied by the label.
342 |
343 | ```js
344 | // Allow labels to step out of the polygon for 2px at all zoom levels
345 | polygon.options.set({
346 | labelPermissibleInaccuracyOfVisibility: 2
347 | });
348 | ```
349 |
350 | **Default behavior:**
351 | labelPermissibleInaccuracyOfVisibility: 0
352 |
353 |
354 | ## Used libraries
355 | [https://github.com/mapbox/polylabel](https://github.com/mapbox/polylabel)
356 |
357 | ## Third party components
358 |
359 | The views and conclusions contained in the software and documentation are those
360 | of the authors and should not be interpreted as representing official policies,
361 | either expressed or implied, of OpenLayers Contributors.
362 |
--------------------------------------------------------------------------------