├── .gitignore ├── LICENSE ├── README.md ├── css ├── app.css └── layer-tree.css ├── dist ├── css │ └── styles.min.css └── js │ └── scripts.min.js ├── gulpfile.js ├── icons └── airplane.svg ├── index.html ├── js ├── app.js ├── jquery-ui-sortable │ ├── AUTHORS.txt │ ├── LICENSE.txt │ ├── external │ │ └── jquery │ │ │ └── jquery.js │ ├── index.html │ ├── jquery-ui.css │ ├── jquery-ui.js │ ├── jquery-ui.min.css │ ├── jquery-ui.min.js │ ├── jquery-ui.structure.css │ ├── jquery-ui.structure.min.css │ └── package.json ├── layer-tree.js └── onClickLoad-example.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Gartrell Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mapbox GL JS Layer Tree for the PORT 2 | #### Allow users to interactively organize and reposition different map layers. 3 | 4 | #### [Demo](http://dev.gartrellgroup.com/layer-tree/) 5 | 6 | ### Install: 7 | - `yarn` to install current set of dependencies 8 | - Additional dependencies can be installed via `yarn add [packagename]` 9 | - `gulp` to develop locally (includes a local server and watchers on index.html, js, and css files) 10 | - `gulp build` will compile/minify required javascript and default layer-tree.css. 11 | 12 | 13 | ### Dependencies: 14 | - jQuery, jQuery UI Sortable Module, and Font-Awesome are all currently required. 15 | - jQuery and jQuery UI-Sortable are both included within the compiled `dist/js/scripts.min.js` 16 | - Font-Awesome CSS should be added in your HTML `
` 17 | 18 | ### Regular Usage: 19 | - #### Layer Config: 20 | - **name** - to be displayed as the layer name in Legend 21 | - **id** - layer id 22 | - **source** - layer source 23 | - **directory** - directory where the layer is apart of 24 | - **icon** (optional) - path to img src and is solely reflected in the Layer Tree legend. It does not modify the symbology of a layer. Additionally, in a group layer object configuration - the icon param can inherit a child layer's default icon style - via the child layer's ID. 25 | - **path** (optional) - references source path for geojson and is *only* used in conjunction with onClickLoad param (see further below) 26 | - **hideLabel** (optional - group layer only) - in a group layer object configuration, this can be used to hide specific child layers from the Layer Tree by using an array of child layer IDs. 27 | 28 | ```javascript 29 | var lyrArray = 30 | [ 31 | { 32 | 'name': 'Geographic Regions', 33 | 'id': 'geo-regions', 34 | 'source': 'geo-regions', 35 | 'directory': 'Misc', 36 | }, 37 | { 38 | 'name': 'Land', 39 | 'id': 'land', 40 | 'source': 'land', 41 | 'directory': 'Natural', 42 | }, 43 | { 44 | 'name': 'Glaciers', 45 | 'id': 'glaciers', 46 | 'source': 'glacial', 47 | 'directory': 'Natural', 48 | 'icon': 'http://external.image.png' 49 | }, 50 | { 51 | 'name': 'Boundary Lines', 52 | 'id': 'boundary-line', 53 | 'source': 'boundary', 54 | 'directory': 'Travel', 55 | }, 56 | { 57 | 'name': 'Points', 58 | 'id': 'travel-group', 59 | 'icon': 'port', 60 | 'hideLabel': ['port', 'airport'], 61 | 'layerGroup' : [ 62 | { 63 | 'id': 'port', 64 | 'source': 'ports', 65 | 'name': 'Major Shipping Ports' 66 | }, 67 | { 68 | 'id': 'airport', 69 | 'source': 'airports', 70 | 'name': 'Airports' 71 | }, 72 | ], 73 | 'directory': 'Travel' 74 | } 75 | ]; 76 | ``` 77 | ##### onClickLoad (optional) 78 | Since the Layer Tree is populated within `map.on('load', function()`, *mapLayers* and *mapSources* are added inside the event listener. Often times, may want to load layers only on 'click'. To account for this (*geojson only*), users will need to initially setup their layers sources with empty featureCollections. The `onClickLoad` param also needs to be added to LayerTree control and must be set to `true`. If a geojson layer has a layout visibility not set to 'none' - the Layer Tree will behave as it normally would - where the layer will be activated and shown on map load. 79 | 80 | Example js setup [here](https://github.com/TheGartrellGroup/Mapbox-GL-JS-Layer-Tree/blob/master/js/onClickLoad-example.js) 81 | 82 | ```javascript 83 | // **** EMPTY GEOJSON PLACEHOLDER **** 84 | var emptyGJ = { 85 | 'type': 'FeatureCollection', 86 | 'features': [] 87 | }; 88 | 89 | map.on('load', function () { 90 | // *** emptyGJ is now the initial data source *** 91 | map.addSource('land', { type: 'geojson', data: emptyGJ }); 92 | map.addLayer({ 93 | "id": "land", 94 | "type": "fill", 95 | "source": "land", 96 | "paint": { 97 | 'fill-color': '#a89b97', 98 | 'fill-opacity': 0.8 99 | } 100 | }); 101 | 102 | var layers = 103 | [ 104 | { 105 | 'name': 'Land', 106 | 'id': 'land', 107 | 'source': 'land', 108 | // **** SOURCE PATH IS NOW HERE **** 109 | 'path': 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_land.geojson', 110 | 'directory': 'Directory 1', 111 | } 112 | ]; 113 | ``` 114 | - #### Layer Directory - Open/Closed (Optional) 115 | - A directory can be set to open or close on load (they'll default to open if no overriding configuration is passed) 116 | ```javascript 117 | var directoryOptions = 118 | [ 119 | { 120 | 'name': 'Natural', 121 | 'open': false 122 | } 123 | ] 124 | ``` 125 | 126 | - #### Instantiate Layer Tree 127 | ```javascript 128 | map.addControl(new LayerTree({ 129 | layers: lyrArray, 130 | directoryOptions: directoryOptions, 131 | }, 'bottom-left') 132 | ``` 133 | 134 | ### Notes: 135 | - Layers within the same directory **must** be configured together 136 | - For example: *Layer A* and *Layer C* can not be of the same directory - if *Layer B* is also **not** within the same directory and has been added as a mapLayer prior to *Layer C* being added. 137 | - If the layer is a geojson and no icon is passed to the layer config, the Layer Tree will automatically add a FontAwesome icon to the legend 138 | - Icon params within the layer config **only** update the legend - not layer symbology on the map 139 | - `onClickLoad` only works with geojson layers and should have layout visibilities set to 'none' 140 | -------------------------------------------------------------------------------- /css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; padding:0; 3 | } 4 | 5 | #map { 6 | position:absolute; 7 | top:0; 8 | bottom:0; 9 | width:100%; 10 | } 11 | 12 | .toggle-directory-open, .toggle-directory-close { 13 | padding-top: 2px; 14 | } 15 | 16 | #airports img{ 17 | height: 15px; 18 | width: 15px; 19 | -webkit-text-stroke: 1px #FFF; 20 | } -------------------------------------------------------------------------------- /css/layer-tree.css: -------------------------------------------------------------------------------- 1 | /* jquery ui required */ 2 | .ui-sortable-handle{ 3 | -ms-touch-action: none; 4 | touch-action: none; 5 | } 6 | 7 | .mapboxgl-ctrl.legend-container { 8 | display: none; 9 | width: auto; 10 | height: auto; 11 | pointer-events: auto; 12 | margin: 0; 13 | float: none; 14 | } 15 | 16 | #mapboxgl-legend { 17 | padding: 5px; 18 | } 19 | 20 | .layer-directory .directory-name { 21 | font-size: 18px; 22 | font-weight: 500; 23 | padding: 5px; 24 | } 25 | 26 | .layer-directory .layer-item { 27 | font-size: 15px; 28 | font-weight: 400; 29 | padding: 2px 6px 2px 15px; 30 | } 31 | 32 | .layer-item input { 33 | margin-right: 5px; 34 | } 35 | 36 | .layer-item.ghost { 37 | color: #D3D3D3; 38 | } 39 | 40 | .layer-item span.name { 41 | margin-left: 5px; 42 | } 43 | 44 | .layer-item > img, .child-layer > img { 45 | height: 15px; 46 | width: 15px; 47 | } 48 | 49 | .child-layer { 50 | padding-left: 30px; 51 | font-size: 13px; 52 | } 53 | 54 | .child-layer span.child-name { 55 | padding-left: 5px; 56 | } 57 | /* .grb is class for all sortable components */ 58 | .grb { 59 | cursor: move; /* fallback if grab cursor is unsupported */ 60 | cursor: grab; 61 | cursor: -moz-grab; 62 | cursor: -webkit-grab; 63 | } 64 | 65 | .grb:active { 66 | cursor: grabbing; 67 | cursor: -moz-grabbing; 68 | cursor: -webkit-grabbing; 69 | } 70 | 71 | /* font awesome classes */ 72 | .geojson-polygon:before { 73 | content: "\f0c8"; 74 | } 75 | 76 | .geojson-circle:before { 77 | content: "\f111"; 78 | } 79 | 80 | .geojson-line-solid:before { 81 | content: "\f068"; 82 | } 83 | 84 | .geojson-line-dashed:before { 85 | content: "\f141"; 86 | } 87 | 88 | .toggle-directory-open, .toggle-directory-close { 89 | float: left; 90 | margin-right: 5px; 91 | } 92 | .toggle-directory-open:before{ 93 | content: "\f07c"; 94 | } 95 | 96 | .toggle-directory-close:before { 97 | content: "\f07b"; 98 | } 99 | -------------------------------------------------------------------------------- /dist/css/styles.min.css: -------------------------------------------------------------------------------- 1 | .ui-sortable-handle{-ms-touch-action:none;touch-action:none}.mapboxgl-ctrl.legend-container{display:none;width:auto;height:auto;pointer-events:auto;margin:0;float:none}#mapboxgl-legend{padding:5px}.layer-directory .directory-name{font-size:18px;font-weight:500;padding:5px}.layer-directory .layer-item{font-size:15px;font-weight:400;padding:2px 6px 2px 15px}.layer-item input{margin-right:5px}.layer-item.ghost{color:#d3d3d3}.layer-item span.name{margin-left:5px}.child-layer>img,.layer-item>img{height:15px;width:15px}.child-layer{padding-left:30px;font-size:13px}.child-layer span.child-name{padding-left:5px}.grb{cursor:move;cursor:grab;cursor:-moz-grab;cursor:-webkit-grab}.grb:active{cursor:grabbing;cursor:-moz-grabbing;cursor:-webkit-grabbing}.geojson-polygon:before{content:"\f0c8"}.geojson-circle:before{content:"\f111"}.geojson-line-solid:before{content:"\f068"}.geojson-line-dashed:before{content:"\f141"}.toggle-directory-close,.toggle-directory-open{float:left;margin-right:5px}.toggle-directory-open:before{content:"\f07c"}.toggle-directory-close:before{content:"\f07b"} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var webserver = require('gulp-webserver'); 3 | var concat = require('gulp-concat'); 4 | var uglify = require('gulp-uglify'); 5 | var cleanCSS = require('gulp-clean-css'); 6 | 7 | //local webserver 8 | gulp.task('webserver', function() { 9 | gulp.src('./') 10 | .pipe(webserver({ 11 | fallback: 'index.html', 12 | directoryListing: false, 13 | livereload: true, 14 | open: true 15 | })); 16 | }); 17 | 18 | //watch task 19 | gulp.task('watch', function(){ 20 | gulp.watch(['index.htm', 'js/app.js', 'js/layer-tree.js', 'css/app.css', 'css/layer-tree.css']); 21 | }); 22 | 23 | //minify js 24 | gulp.task('scripts', function() { 25 | gulp.src(['node_modules/jquery/dist/jquery.min.js', 'js/jquery-ui-sortable/jquery-ui.min.js', 'js/layer-tree.js']) 26 | .pipe(concat('scripts.min.js')) 27 | .pipe(uglify()) 28 | .pipe(gulp.dest('dist/js')) 29 | }); 30 | 31 | //minify css 32 | gulp.task('css', function() { 33 | gulp.src(['css/layer-tree.css']) 34 | .pipe(concat('styles.min.css')) 35 | .pipe(cleanCSS()) 36 | .pipe(gulp.dest('dist/css')) 37 | }); 38 | 39 | //default task 40 | gulp.task('default', ['webserver', 'watch']); 41 | 42 | //build task 43 | gulp.task('build', ['scripts', 'css']); 44 | -------------------------------------------------------------------------------- /icons/airplane.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |