Add a tutorial showing how to make a text feature.
This also checks in a script used to generate the thumbnails and allows
tutorials to accept a query parameter to start on a specific step.
ADD
scripts/make_thumbnails.py
+#!/usr/bin/env python
+
+# NOTE: If this doesn't work, it may be related to a policy in
+# /etc/ImageMagick-6/policy.xml
+# Specifically, disable
+# <policy domain="coder" rights="none" pattern="PS" />
+# by removing it or commenting it out.
+
+import json
+import os
+import psutil
+import signal
+import six
+import subprocess
+import sys
+import time
+
+OriginalSize = (1200, 900)
+ExtraSpace = 1 # 1, otherwise we get a black border on the bottom and right
+NavbarHeight = 60
+FinalSize = (800, 600)
+InitialDelay = 15 # in seconds
+MaxDelay = 30 # in seconds
+Quality = 90
+OutputFile = 'thumb.jpg'
+InputList = ["examples", "tutorials"]
+BrowserCommand = [
+ 'xvfb-run', '-s', '-ac -screen 0 %dx%dx24' % (
+ OriginalSize[0] + ExtraSpace, OriginalSize[1] + ExtraSpace + NavbarHeight),
+ 'google-chrome', '--kiosk', '--no-pings', '--device-scale-factor=1',
+ '--incognito', '--start-fullscreen', '--no-default-browser-check',
+ '--user-data-dir=/tmp/chrome_geojs_thumbnails', '--no-first-run',
+ '--disable-default-apps', '--disable-popup-blocking',
+ '--disable-translate', '--disable-background-timer-throttling',
+ '--disable-renderer-backgrounding',
+ '--disable-device-discovery-notifications',
+ '--window-position=0,0',
+]
+BrowserCommandSize = [
+ '--window-size=%d,%d' % (OriginalSize[0] + ExtraSpace, OriginalSize[1] + ExtraSpace),
+]
+BrowserCommandSizeIgnoreNavbar = [
+ '--window-size=%d,%d' % (
+ OriginalSize[0] + ExtraSpace, OriginalSize[1] + ExtraSpace + NavbarHeight),
+]
+BrowserUrl = 'http://127.0.0.1:30100/%s'
+ImageCommand = (
+ 'DISPLAY=:99.0 import -window root -crop %dx%d+0+0 +repage - | '
+ 'convert - -resize %dx%d -quality %d ' % (
+ OriginalSize[0], OriginalSize[1], FinalSize[0], FinalSize[1], Quality))
+ImageCommandIgnoreNavbar = (
+ 'DISPLAY=:99.0 import -window root -crop %dx%d+0+%d +repage - | '
+ 'convert - -resize %dx%d -quality %d ' % (
+ OriginalSize[0], OriginalSize[1], NavbarHeight, FinalSize[0], FinalSize[1], Quality))
+
+
+def process_item(path, opts):
+ output = (open('/tmp/thumbnail.out', 'ab')
+ if opts.get('verbose', 0) >= 1 else open(os.devnull, 'w'))
+ data = json.load(open(path))
+ if data.get('disabled') and not opts.get('all'):
+ return
+ dest = os.path.join(os.path.dirname(path), OutputFile)
+ if os.path.exists(dest) and not opts.get('force'):
+ return
+ originalSize = 0
+ if os.path.exists(dest):
+ originalSize = os.path.getsize(dest)
+ sys.stdout.write('\r%s %d' % (path, originalSize))
+ sys.stdout.flush()
+ if opts.get('simulate'):
+ dest = os.path.join('/tmp', os.path.basename(os.path.dirname(
+ os.path.dirname(path))) + '_' + os.path.basename(os.path.dirname(
+ path)) + '_' + OutputFile)
+ if os.path.exists(dest):
+ os.unlink(dest)
+ cmd = list(BrowserCommand)
+ imgcmd = ImageCommand
+ if 'example.json' in path and not data.get('hideNavbar'):
+ cmd.extend(BrowserCommandSizeIgnoreNavbar)
+ imgcmd = ImageCommandIgnoreNavbar
+ else:
+ cmd.extend(BrowserCommandSize)
+ url = BrowserUrl % os.path.dirname(path)
+ if data.get('thumbquery'):
+ url += '?' + data['thumbquery']
+ cmd.append(url)
+ output.write('--> %r\n' % (cmd, ))
+ output.write(' %s\n' % (' '.join([six.moves.shlex_quote(arg) for arg in cmd])))
+ proc = subprocess.Popen(cmd, shell=False, stdout=output, stderr=output)
+ delay = opts.get('delay', InitialDelay)
+ startTime = time.time()
+ time.sleep(delay)
+ lastSize = 0
+ while True:
+ output.write('--> %r\n' % (imgcmd + six.moves.shlex_quote(dest), ))
+ subprocess.Popen(
+ imgcmd + six.moves.shlex_quote(dest), shell=True,
+ stdout=output, stderr=output).wait()
+ newSize = os.path.getsize(dest)
+ if newSize and newSize == lastSize:
+ break
+ if time.time() - startTime > opts.get('maxdelay', MaxDelay):
+ break
+ lastSize = newSize
+ sys.stdout.write('\r%s %d %d ' % (path, originalSize, newSize))
+ sys.stdout.flush()
+ time.sleep(0.5)
+ for child in psutil.Process(proc.pid).children(recursive=True):
+ try:
+ child.send_signal(signal.SIGINT)
+ except psutil.NoSuchProcess:
+ pass
+ os.kill(proc.pid, signal.SIGINT)
+ proc.wait()
+ sys.stdout.write('\n')
+
+
+if __name__ == '__main__': # noqa
+ opts = {'force': False, 'verbose': 0}
+ for arg in sys.argv[1:]:
+ if arg in ('-a', '--all'):
+ opts['all'] = True
+ elif arg.startswith('--delay='):
+ opts['delay'] = float(arg.split('=', 1)[1])
+ elif arg == '--force':
+ opts['force'] = True
+ elif arg.startswith('--maxdelay='):
+ opts['maxdelay'] = float(arg.split('=', 1)[1])
+ elif arg.startswith('--only='):
+ opts['only'] = arg.split('=', 1)[1]
+ elif arg in ('-s', '--simulate'):
+ opts['simulate'] = True
+ elif arg in ('-v', '--verbose'):
+ opts['verbose'] += 1
+ else:
+ opts['help'] = True
+ if opts.get('help'):
+ print """
+Regenerate thumbnails for examples and tutorials.
+
+Syntax: make_thumbnails.py --force --simulate --only=(substr) --all
+ --delay=(seconds) --maxdelay=(seconds)
+
+Run in the root geojs directory.
+--all or -a generates thumbnails for disabled examples, too.
+--delay is the duration after the web browser is started before a thumbnail
+ snapshot might be taken. The thumbnail is only taken after the webpage hasn't
+ changed for a short duration.
+--force regenerates all thumbnails. Otherwise, only missing thumbnails are
+ created.
+--maxdelay is the longest to wait before taking the snapshot. This will happen
+ even if the webpage is still changing.
+--only will only process examples or tutorials whose name contains the
+ specified substring.
+--simulate or -s determines the size of thumbnails that would be created but
+ doesn't make them.
+"""
+ sys.exit(0)
+ for inputdir in InputList:
+ for root, dirs, files in os.walk(inputdir):
+ dirs.sort()
+ for dir in dirs:
+ for name in ['example.json', 'tutorial.json']:
+ path = os.path.join(root, dir, name)
+ if opts.get('only') and not opts['only'] in path:
+ continue
+ if os.path.exists(path):
+ process_item(path, opts)

MODIFY
src/textFeature.js
src/textFeature.js
@@ -51,7 +51,7 @@ var feature = require('./feature');
* text when the map rotates. Otherwise, the text is always in the same
* orientation.
* @property {number|function} [textScaled] If defined, the text is scaled when
- * the map zoomes and this is the basis zoom for the fontSize.
+ * the map zooms and this is the basis zoom for the fontSize.
* @property {geo.screenPosition|function} [offset] Offset from the default
* position for the text. This is applied before rotation.
* @property {geo.geoColor|function} [shadowColor='black'] Text shadow color.

MODIFY
tutorials/common/tutorials.js
tutorials/common/tutorials.js
@@ -202,11 +202,14 @@ function process_block_debounce(selector, debounce) {
/**
* Run any default code blocks, then start listening for changes in code
* blocks.
+ *
+ * @param {string} [initialStep] If specified, run this step instead of the
+ * step(s) marked as initial.
*/
-function run_tutorial() {
+function run_tutorial(initialStep) {
/* If any of the codeblocks is marked 'default', run them. Do this in a
* timeout so that other start up scripts can run */
- $('.codeblock[initial="true"]').each(function (idx, elem) {
+ $(!initialStep ? '.codeblock[initial="true"]' : '.codeblock[step="' + initialStep + '"]').each(function (idx, elem) {
run_block(elem);
});
/* Whenever a code block changes, run it with its parents */
@@ -356,7 +359,7 @@ function start_tutorial(useCodeMirror, alwaysKeep) {
processBlockInfo.srcdocSupport = false;
}
start_keeper(alwaysKeep);
- run_tutorial();
+ run_tutorial(utils.getQuery().initial);
}
module.exports = start_tutorial;

ADD
tutorials/text/index.pug
+extends ../common/index.pug
+
+block mainTutorial
+ :markdown-it
+ # Tutorial - Text and Points
+ Create a map and define some data. Create a feature layer for the points
+ and add the points to it.
+
+ +codeblock('javascript', 1).
+ var map = geo.map({
+ node: "#map",
+ center: { x: -97.67, y: 31.80 },
+ zoom: 4
+ });
+ map.createLayer('osm');
+
+ var cities = [
+ {lon: -74.0059413, lat: 40.7127837, name: "New York", population: 8405837},
+ {lon: -118.2436849, lat: 34.0522342, name: "Los Angeles", population: 3884307},
+ {lon: -87.6297982, lat: 41.8781136, name: "Chicago", population: 2718782},
+ {lon: -95.3698028, lat: 29.7604267, name: "Houston", population: 2195914},
+ {lon: -75.1652215, lat: 39.9525839, name: "Philadelphia", population: 1553165},
+ {lon: -112.0740373, lat: 33.4483771, name: "Phoenix", population: 1513367}
+ ];
+
+ var pointLayer = map.createLayer('feature', {features: ['point']});
+ var pointFeature = pointLayer.createFeature('point')
+ .data(cities)
+ .position(function (city) {
+ return {
+ x: city.lon,
+ y: city.lat
+ };
+ })
+ .draw();
+
+ :markdown-it
+ Create another layer for the text feature. We need a separate layer as the
+ text feature won't use the same renderer as the point feature.
+
+ Add a text feature to the layer.
+
+ +codeblock('javascript', 2, undefined, true).
+ var textLayer = map.createLayer('feature', {features: ['text']});
+ var textFeature = textLayer.createFeature('text')
+ .data(cities)
+ .position(function (city) {
+ return {
+ x: city.lon,
+ y: city.lat
+ };
+ })
+ .text(function (city) {
+ return city.name;
+ })
+ .draw();
+ +codeblock_test('text feature exists', [
+ 'map.layers()[2].features()[0] instanceof geo.textFeature',
+ ])
+
+ :markdown-it
+ Many text styles can be changed, including adding an offset so the text
+ won't overlap the points.
+
+ +codeblock('javascript', 3).
+ textFeature.style({
+ fontSize: '20px',
+ fontFamily: 'serif',
+ textAlign: 'left',
+ textBaseline: 'middle',
+ color: 'blue',
+ offset: {x: 10, y: 0}
+ }).draw();
+ +codeblock_test('text color changed', [
+ 'map.layers()[2].features()[0].style().color === "blue"',
+ ])
+
+ :markdown-it
+ Multiline and word-wrapping aren't directly supported, but can be simulated
+ by adding additional text features.
+
+ This also shows that the text value can be set as a style property.
+
+ +codeblock('javascript', 4).
+ textLayer.createFeature('text')
+ .data(cities)
+ .position(function (city) {
+ return {
+ x: city.lon,
+ y: city.lat
+ };
+ })
+ .style({
+ text: function (city) {
+ return city.population;
+ },
+ fontSize: '16px',
+ fontFamily: 'serif',
+ textAlign: 'left',
+ textBaseline: 'middle',
+ color: 'green',
+ offset: {x: 10, y: 16}
+ })
+ .draw();

ADD
tutorials/text/thumb.jpg
tutorials/text/thumb.jpg
Binary files /dev/null and b/tutorials/text/thumb.jpg differ

ADD
tutorials/text/tutorial.json
+{
+ "title": "Text and Points",
+ "hideNavbar": true,
+ "level": 0,
+ "thumbquery": "initial=4",
+ "about": {
+ "text": "Add text labels and points to a map"
+ }
+}

Add a tutorial showing how to make a text feature.

This also checks in a script used to generate the thumbnails and allows
tutorials to accept a query parameter to start on a specific step.