Skip to content

Instantly share code, notes, and snippets.

@asizer
Created October 28, 2014 15:46
Show Gist options
  • Save asizer/44e68f03679004094262 to your computer and use it in GitHub Desktop.
Save asizer/44e68f03679004094262 to your computer and use it in GitHub Desktop.
Feature Layer with Multi-Line Labels

Multi-line labels on a Feature Layer

This is a workaround to add line breaks to the Label Layer

NOTE: I can't figure out a way to return all the labels for a Feature Layer here, unfortunately. The Label Layer only returns the labels that will fit into their elements, or not conflict with other labels, as calculated based on the original, single-line label. Splitting these labels into multiple lines might reduce conflicts, or allow them to fit properly within their polygons, but because they were conflicted out in the first place, we never see them.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Feature Layer Multi-Line Labels</title>
<link rel="stylesheet" href="//js.arcgis.com/3.10/js/esri/css/esri.css">
<style>
html, body, #map {
height: 100%; width: 100%; margin: 0; padding: 0;
}
</style>
<script src="//js.arcgis.com/3.10/"></script>
<script src="main.js"></script>
</head>
<body>
<div id="map"></div>
</body>
</html>
var map;
require([
'dojo/query',
'dojo/_base/array',
'esri/map',
'esri/layers/FeatureLayer',
'esri/layers/ArcGISDynamicMapServiceLayer',
'esri/symbols/SimpleLineSymbol',
'esri/symbols/SimpleFillSymbol',
'esri/symbols/TextSymbol',
'esri/renderers/SimpleRenderer',
'esri/layers/LabelLayer',
'esri/Color',
'dojo/domReady!'],
function(dojoQuery, arrayUtil, Map, FeatureLayer, DynamicLayer,
SLS, SFS, TextSymbol, SimpleRenderer,
LabelLayer,
Color) {
var config = {
mapCenter: [-117.1963, 34.0571], // longitude, latitude
mapZoom: 23,
basemapUrl: 'http://arcgis-tenone2012-1974758903.us-west-1.elb.amazonaws.com/arcgis/rest/services/Campus/MapServer',
serviceUrl: 'http://arcgis-localgov-61933129.us-west-1.elb.amazonaws.com/arcgis/rest/services/CampusPlaceFinder/BuildingInterior/MapServer/',
roomsLayerId: 1,
bldg: 'M',
flr: '2',
visibleLayers: [0, 3], // lines, buildings. not including rooms layer because it already has a label on it.
defExprFunction: function() {
// defexpr array for map service. here, 'this' is this config object.
return [
'BUILDINGKEY = \'' + this.bldg + '\' AND FLOOR = \'' + this.flr + '\''
];
},
roomDefExpr: function() {
return 'BUILDING = \'' + this.bldg + '\' AND FLOOR = \'' + this.flr + '\'';
},
labelFields: ['SPACEID', 'SPACETYPE', 'SHORTNAME']
};
// be able to zoom in past the regular 0-19 zoom levels.
var lods = [
{ 'level': 0, 'resolution': 156543.033928, 'scale': 591657527.591555 },
{ 'level': 1, 'resolution': 78271.5169639999, 'scale': 295828763.795777 },
{ 'level': 2, 'resolution': 39135.7584820001, 'scale': 147914381.897889 },
{ 'level': 3, 'resolution': 19567.8792409999, 'scale': 73957190.948944 },
{ 'level': 4, 'resolution': 9783.93962049996, 'scale': 36978595.474472 },
{ 'level': 5, 'resolution': 4891.96981024998, 'scale': 18489297.737236 },
{ 'level': 6, 'resolution': 2445.98490512499, 'scale': 9244648.868618 },
{ 'level': 7, 'resolution': 1222.99245256249, 'scale': 4622324.434309 },
{ 'level': 8, 'resolution': 611.49622628138, 'scale': 2311162.217155 },
{ 'level': 9, 'resolution': 305.748113140558, 'scale': 1155581.108577 },
{ 'level': 10, 'resolution': 152.874056570411, 'scale': 577790.554289 },
{ 'level': 11, 'resolution': 76.4370282850732, 'scale': 288895.277144 },
{ 'level': 12, 'resolution': 38.2185141425366, 'scale': 144447.638572 },
{ 'level': 13, 'resolution': 19.1092570712683, 'scale': 72223.819286 },
{ 'level': 14, 'resolution': 9.55462853563415, 'scale': 36111.909643 },
{ 'level': 15, 'resolution': 4.77731426794937, 'scale': 18055.954822 },
{ 'level': 16, 'resolution': 2.38865713397468, 'scale': 9027.977411 },
{ 'level': 17, 'resolution': 1.19432856685505, 'scale': 4513.988705 },
{ 'level': 18, 'resolution': 0.59716428355982, 'scale': 2256.994353 },
{ 'level': 19, 'resolution': 0.29858214164762, 'scale': 1128.497176 },
{ 'level': 20, 'resolution': 0.14929144441622, 'scale': 564.25 },
{ 'level': 21, 'resolution': 0.07464439928880, 'scale': 282.12 },
{ 'level': 22, 'resolution': 0.03747036660734, 'scale': 141.62 },
{ 'level': 23, 'resolution': 0.0187351833037, 'scale': 70.8 }
];
map = new Map('map', {
center: config.mapCenter,
zoom: config.mapZoom,
lods: lods
});
// set up basemaps
esriConfig.defaults.map.basemaps.Campus = {
'title': 'Campus',
'thumbnailUrl': 'src/resources/TopoCampus.png',
'baseMapLayers': [{
url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer'
}, {
url: config.basemapUrl
}]
};
map.setBasemap('Campus');
// add map service layer for building, floor, room
var mapServiceLayer = new DynamicLayer(config.serviceUrl);
mapServiceLayer.setVisibleLayers(config.visibleLayers);
mapServiceLayer.setLayerDefinitions(config.defExprFunction());
map.addLayer(mapServiceLayer);
// create an invisible feature layer for labeling
var roomsColor = new Color('#666');
var roomsSymbol = new SFS('solid', null, null);
var roomsRenderer = new SimpleRenderer(roomsSymbol);
var roomUrl = config.serviceUrl + config.roomsLayerId;
roomLayer = new FeatureLayer(roomUrl, {
id: 'rooms',
outFields: config.labelFields
});
roomLayer.setRenderer(roomsRenderer);
roomLayer.setDefinitionExpression(config.roomDefExpr());
map.addLayer(roomLayer);
// create a text symbol to define the style of labels
var labelSymbol = new TextSymbol().setColor(roomsColor);
labelSymbol.font.setSize('6pt');
labelSymbol.font.setFamily('arial');
roomsLabelRenderer = new SimpleRenderer(labelSymbol);
labelsLayer = new LabelLayer({ id: 'labels' });
// construct the label layer string from the config label fields
var labelStr = '{';
labelStr += config.labelFields.join('}${');
labelStr += '}';
labelsLayer.addFeatureLayer(roomLayer, roomsLabelRenderer, labelStr);
// add the label layer to the map
map.addLayer(labelsLayer);
var labelsLayerNode = labelsLayer.getNode();
// hide the labels so that we don't see the single line string before formatting
// we're doing this on MAP events because the labelsLayer doesn't seem to be firing update events.
map.on('update-start', function() {
console.debug('map update-start');
labelsLayerNode = labelsLayerNode || labelsLayer.getNode();
if (!labelsLayerNode) {
return;
}
// can't just labelsLayer.setVisibility(false) because then the label text isn't actually added.
labelsLayerNode.setAttributeNS(null, 'style', 'display: none');
});
// set up the timer (IE8 doesn't like it when you setTimeout(null, 100));
var endTimer = setTimeout(function() {}, 100);
map.on('update-end', function() {
console.debug('map update-end', arguments);
labelsLayerNode = labelsLayerNode || labelsLayer.getNode();
if (!labelsLayerNode) {
return;
}
// find text labels in the labelsLayer svg element
var textLabels = dojoQuery('text', labelsLayerNode);
// cycle through each label and add a new row on the dollar sign.
// TODO: ignore empty strings so they don't cause unnecessary newlines.
arrayUtil.forEach(textLabels, function(lbl) {
var fieldArr = lbl.innerHTML.split('$');
// only format these labels if there's more than one field here
if (fieldArr.length > 1) {
lbl.innerHTML = fieldArr[0];
for (var i = 1; i < fieldArr.length; i++) {
// create a tspan for each subsequent field
var tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS(null, 'x', lbl.getAttributeNS(null, 'x'));
tspan.setAttributeNS(null, 'dy', '1.5em');
tspan.innerHTML = fieldArr[i];
lbl.appendChild(tspan);
}
}
});
// show the labels again once we're done formatting, but sometimes update-end fires
// a bunch of times in a row, before update has actually ended once and for all, so put a timer on it.
clearTimeout(endTimer);
endTimer = setTimeout(function() {
labelsLayer.getNode().setAttributeNS(null, 'style', 'display: block');
}, 500);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment