MediaWiki:DataMaps.js: Difference between revisions

From Deez Nuts' Sword Coast Campaign
Jump to navigation Jump to search
Per-marker label direction override via id suffix (--l/--r/--t/--b)
Clickable, labelled region polygons from custom.regions; per-map minor-label groups
Line 1: Line 1:
/**
/**
  * Interactive maps (Extension:DataMaps) site script.
  * Interactive maps (Extension:DataMaps) site script.
  * Binds a permanent name label to every marker so the maps read like the printed ones,
  * - Binds a permanent name label to every marker so the maps read like the printed ones,
  * and hides labels of the busier groups (towns, ruins, wilds, rumours) while zoomed far out.
  *   hiding labels of the busier groups while zoomed far out.
  * Styling lives in MediaWiki:DataMaps.css (.dn-map-label).
*  - Draws clickable, labelled region outlines from a map's `custom.regions`
*    ([{ name, article, points: [[x,y], …] }]) — used by Map:Faerun to open the regional maps.
  * Styling lives in MediaWiki:DataMaps.css.
  *
  *
  * Label placement: by group (DIRECTION below), overridable per marker by ending its id with
  * Label placement: by group (DIRECTION), overridable per marker by ending its id with
  * --l, --r, --t or --b (left / right / top / bottom), e.g. "sc-elturel--l".
  * --l, --r, --t or --b. A map can widen the set of "minor" (hidden-when-far) groups with
* `custom.labelMinorGroups`.
  */
  */
mw.loader.using( 'ext.datamaps.bootstrap' ).then( function () {
mw.loader.using( [ 'ext.datamaps.bootstrap', 'ext.datamaps.leaflet', 'mediawiki.util' ] ).then( function ( require ) {
var MINOR_GROUPS = [ 'town', 'ruin', 'wilds', 'rumour' ],
var Leaflet = require( 'ext.datamaps.leaflet' ),
MINOR_GROUPS = [ 'town', 'ruin', 'wilds', 'rumour' ],
DIRECTION = { stronghold: 'bottom', skarhald: 'bottom', party: 'top' },
DIRECTION = { stronghold: 'bottom', skarhald: 'bottom', party: 'top' },
SUFFIX = { l: 'left', r: 'right', t: 'top', b: 'bottom' },
SUFFIX = { l: 'left', r: 'right', t: 'top', b: 'bottom' },
Line 17: Line 21:


mw.dataMaps.registerMapAddedHandler( function ( map ) {
mw.dataMaps.registerMapAddedHandler( function ( map ) {
var isMini = map.rootElement.classList.contains( 'ext-datamaps-mini-layout' );
var isMini = map.rootElement.classList.contains( 'ext-datamaps-mini-layout' ),
custom = map.config.custom || {},
minorGroups = custom.labelMinorGroups || MINOR_GROUPS;


map.on( 'markerReady', function ( marker ) {
map.on( 'markerReady', function ( marker ) {
Line 39: Line 45:
cls += ' dn-map-label-mini';
cls += ' dn-map-label-mini';
}
}
if ( MINOR_GROUPS.indexOf( group ) !== -1 ) {
if ( minorGroups.indexOf( group ) !== -1 ) {
cls += ' dn-map-label-minor';
cls += ' dn-map-label-minor';
}
}
Line 52: Line 58:
} );
} );


// Zoom tier → data attribute the CSS keys off. "far" = at or near the fitted zoom.
map.on( 'leafletLoaded', function () {
map.on( 'leafletLoaded', function () {
var leafletMap = map.viewport && map.viewport.getLeafletMap(),
var leafletMap = map.viewport && map.viewport.getLeafletMap(),
Line 59: Line 64:
return;
return;
}
}
// Zoom tier → data attribute the CSS keys off. "far" = at or near the fitted zoom.
function updateTier() {
function updateTier() {
var z = leafletMap.getZoom();
var z = leafletMap.getZoom();
Line 70: Line 77:
leafletMap.on( 'zoomend', updateTier );
leafletMap.on( 'zoomend', updateTier );
leafletMap.whenReady( updateTier );
leafletMap.whenReady( updateTier );
// Clickable region outlines (overview map).
( custom.regions || [] ).forEach( function ( region ) {
var latlngs = region.points.map( function ( p ) {
return map.crs.fromPoint( [ p[ 1 ], p[ 0 ] ] ); // crs takes [y, x]
} ),
idle = { color: region.color || '#3b2a12', weight: 2, opacity: 0.55, fillColor: '#ffffff', fillOpacity: 0.03 },
hover = { opacity: 0.95, fillOpacity: 0.28 },
poly = new Leaflet.Polygon( latlngs, Object.assign( { className: 'dn-region', interactive: true }, idle ) );
poly.addTo( leafletMap );
poly.bindTooltip( region.name, { permanent: true, direction: 'center', interactive: false, opacity: 1, className: 'dn-region-label' } );
poly.on( 'mouseover', function () { poly.setStyle( hover ); } );
poly.on( 'mouseout', function () { poly.setStyle( idle ); } );
poly.on( 'click', function () {
if ( region.article ) {
window.location.href = mw.util.getUrl( region.article );
}
} );
} );
} );
} );
} );
} );
} );
} );

Revision as of 19:19, 29 August 2026

/**
 * Interactive maps (Extension:DataMaps) site script.
 *  - Binds a permanent name label to every marker so the maps read like the printed ones,
 *    hiding labels of the busier groups while zoomed far out.
 *  - Draws clickable, labelled region outlines from a map's `custom.regions`
 *    ([{ name, article, points: [[x,y], …] }]) — used by Map:Faerun to open the regional maps.
 * Styling lives in MediaWiki:DataMaps.css.
 *
 * Label placement: by group (DIRECTION), overridable per marker by ending its id with
 * --l, --r, --t or --b. A map can widen the set of "minor" (hidden-when-far) groups with
 * `custom.labelMinorGroups`.
 */
mw.loader.using( [ 'ext.datamaps.bootstrap', 'ext.datamaps.leaflet', 'mediawiki.util' ] ).then( function ( require ) {
	var Leaflet = require( 'ext.datamaps.leaflet' ),
		MINOR_GROUPS = [ 'town', 'ruin', 'wilds', 'rumour' ],
		DIRECTION = { stronghold: 'bottom', skarhald: 'bottom', party: 'top' },
		SUFFIX = { l: 'left', r: 'right', t: 'top', b: 'bottom' },
		// Minor labels appear once the map is zoomed in this many levels past its fitted (initial)
		// zoom. The zoom buttons step 0.25, so 0.45 = two clicks.
		NEAR_DELTA = 0.45;

	mw.dataMaps.registerMapAddedHandler( function ( map ) {
		var isMini = map.rootElement.classList.contains( 'ext-datamaps-mini-layout' ),
			custom = map.config.custom || {},
			minorGroups = custom.labelMinorGroups || MINOR_GROUPS;

		map.on( 'markerReady', function ( marker ) {
			var state = marker.apiInstance && marker.apiInstance[ 2 ],
				group = marker.attachedLayers && marker.attachedLayers[ 0 ],
				uid = state && state.uid ? String( state.uid ) : '',
				suffix = /--([lrtb])$/.exec( uid ),
				direction = ( suffix && SUFFIX[ suffix[ 1 ] ] ) || DIRECTION[ group ] || 'right',
				size, half, offset, cls;
			if ( !state || !state.label || typeof marker.bindTooltip !== 'function' ) {
				return;
			}
			// Icons are anchored at their centre; push the label just clear of the icon edge.
			size = marker.options && marker.options.icon && marker.options.icon.options.iconSize;
			half = ( size ? ( Array.isArray( size ) ? size[ 0 ] : size.x || 40 ) : 24 ) / 2;
			offset = direction === 'right' ? [ half + 2, 0 ] :
				direction === 'left' ? [ -( half + 2 ), 0 ] :
				direction === 'top' ? [ 0, -( half - 2 ) ] : [ 0, half - 2 ];
			cls = 'dn-map-label dn-map-label-' + direction + ' dn-map-label-g-' + ( group || 'none' );
			if ( isMini ) {
				cls += ' dn-map-label-mini';
			}
			if ( minorGroups.indexOf( group ) !== -1 ) {
				cls += ' dn-map-label-minor';
			}
			marker.bindTooltip( state.label, {
				permanent: true,
				direction: direction,
				offset: offset,
				interactive: false,
				opacity: 1,
				className: cls
			} );
		} );

		map.on( 'leafletLoaded', function () {
			var leafletMap = map.viewport && map.viewport.getLeafletMap(),
				baseZoom = null;
			if ( !leafletMap ) {
				return;
			}

			// Zoom tier → data attribute the CSS keys off. "far" = at or near the fitted zoom.
			function updateTier() {
				var z = leafletMap.getZoom();
				if ( baseZoom === null || z < baseZoom ) {
					baseZoom = z; // the most zoomed-out level seen is the fitted view
				}
				map.rootElement.dataset.dnZoom = z.toFixed( 2 );
				map.rootElement.dataset.dnBaseZoom = baseZoom.toFixed( 2 );
				map.rootElement.dataset.dnLabelTier = ( z - baseZoom ) < NEAR_DELTA ? 'far' : 'near';
			}
			leafletMap.on( 'zoomend', updateTier );
			leafletMap.whenReady( updateTier );

			// Clickable region outlines (overview map).
			( custom.regions || [] ).forEach( function ( region ) {
				var latlngs = region.points.map( function ( p ) {
						return map.crs.fromPoint( [ p[ 1 ], p[ 0 ] ] ); // crs takes [y, x]
					} ),
					idle = { color: region.color || '#3b2a12', weight: 2, opacity: 0.55, fillColor: '#ffffff', fillOpacity: 0.03 },
					hover = { opacity: 0.95, fillOpacity: 0.28 },
					poly = new Leaflet.Polygon( latlngs, Object.assign( { className: 'dn-region', interactive: true }, idle ) );
				poly.addTo( leafletMap );
				poly.bindTooltip( region.name, { permanent: true, direction: 'center', interactive: false, opacity: 1, className: 'dn-region-label' } );
				poly.on( 'mouseover', function () { poly.setStyle( hover ); } );
				poly.on( 'mouseout', function () { poly.setStyle( idle ); } );
				poly.on( 'click', function () {
					if ( region.article ) {
						window.location.href = mw.util.getUrl( region.article );
					}
				} );
			} );
		} );
	} );
} );