▸   Home

Documentation

Declarative Usage

1000words.js allows interactive graphical views to be built declaratively. This means the application only needs to specify what it wants to achieve and the library is tasked with how to achieve it.

Application responsibilities:

The library does the heavy lifting of:


1. Concepts
2. Shape Types
3. Shape Position
4. Shape Detail Level
5. Layers
6. Creating Views
7. Events
8. Modifying Shape Attributes
9. Modifying Shape Position
10. Programmatic Zoom and Pan
11. Drilldown
12. App Skeleton
13. API
14. Misc


  1. Concepts
  2. Interactive scenes (Views) are instantiated by 1000Words.js in a DOM element specified by the application.

    Views contain Shapes.

    Shapes are instances of Shape Types.

    The application specifies each shape's position (x, y) expressed as a percentage of the view's width and height.

    The definition of what a shape looks like is defined once per Shape Type in the form of SVG (string snippets).

    Views can be configured to publish events when a shape is clicked or comes in focus, the viewport changes (zoom or pan), view changes etc. The application can subscribe to these events.

    Shape Groups contain one or more shape instances, of one or more shape types, and are used to configure:

    Drilldown into shapes is supported. When a user dives into (zooms in) a shape configred for drilldown, a new sub-view is opened up. One or more shapes in the new view can, in turn, be configured for drilldown (recursive).

    Infinite Canvas can be used for very large screens. A summary or Overview is provide to accompany the Primary View. The overview provides a quick look at what part of the main view is currently visible. It comes with a Viewport Controller which users can drag to quickly pan across the primary view as desired.

    ▸   Home

  3. Shape Types
  4. A shape type (e.g. Car, Circle, House, Hospital) is expressed as a class that extends the library's built-in shape class

    Shape Class
    
                const DomainShape = $1000Words.import('DomainShape');
                export default class Car extends DomainShape {
                    renderContents(node, detailLevel) {...} // shape svg string injection
                }
                        

    Shape SVG
    
                // The standardized definition of a shape should render inside a box with
                // 0x0 and 100x100 as the top-left and bottom-right corners.
    
                // NOTE: The shape definition is independent of how 'large' shape
                // instances are or 'where' they are placed on the scene.
    
                // square
                <rect x="0" y="0" width="100" height="100" stroke="red"></rect>
    
                // circle
                <circle cx="50" cy="50" r="50" stroke="blue"></circle>
    
                // shape with a bunch of paths
                <g>
                    <path d="m22.16 ..." stroke="green"></path>
                    <path ...></path>
                    <path ...></path>
                </g>
                    

    Three shape definitions in the form of a square, circle and collection of paths.


  5. Shape Position
  6. The position is specified in percentage terms when the shape is created.

    Specifying position
    
                $1000Words.createShape(Car, "car-fred", view, options, 10, 8);
                $1000Words.createShape(Car, "car-jimmy", view, options, 85, 90);
                $1000Words.createShape(Truck, "company-truck", view, options, 50.21, 60.07);
                        

    Two cars and a truck.


    Home

  7. Shape Detail Level (configured via Shape Groups)
  8. For graphical scenes, things typically get more detailed as the user zooms in. When using 1000Words.js, it isn't strictly necessary to show shapes in greater level of detail as the user zooms in. However the UX design of the scene can often involve showing shapes at different amounts of detail, depending on the zoom level.

    Shape Groups
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const configs = new ShapeConfigurations( {
                shapeTypes: [], // optional - use when svg template elements are used
                groups: [{
                        name: 'trees',
                        existences: {1 : 0.5, 20 : 0}, // covered in the 'layers' section
                        resolutions: {1 : 'low', 3: 'medium', '8': 'high' },
                        // show the high fidelity version of tree when zoom level goes to 8
                        // and above and medium fidelity version when zoom level goes below 8
                    ...
                }]
            })
            ...
            $1000Words.createView('Main View', container, configs, onEvent, eventsOfInterest)
            .then( view => {...} )
    
                        

    Note: The zoom levels at which shape resolutions can change isn't fixed and is determined by the application. The application needs to provide the rendering of the svg shape at different fidelity levels.
    Configurable combinations of zoom and detail level
    
            resolutions: {1 : 'low', 2: 'medium', '8': 'high'}
            resolutions: {1 : 'low', 3: 'medium', '4': 'high', '12': 'superhigh'}
            resolutions: {1 : 'far away', 9': 'clear sight', '14': 'rubbing noses'}
            resolutions: {1 : 'standard'} // fixed resolution, independent of zoom level
            // 1000Words.js will determine when it is time to display a shape at a different
            // detail level and ask the shape for the SVG that corresponds to that level
            // (e.g. 'clear sight')
                        

    If a house shape is displayed on a scene, the application could decide to show different house details at different levels of zoom, say 'low', 'medium' and 'high'.

    low       medium     high
     Shape Definitions (100x100) for different zoom levels



     Scene at a low level of zoom



     Scene at a medium level of zoom



     Scene with a high level of zoom


    Home

  9. Layers (configured via Shape Groups)
  10. Layers allow groups of shapes to come into or go out of existence. Scene designers can find many creative uses for layers. Two, out of potentially many, potential uses are outlined below:

    1. Depth. When the scene naturally contains depth or the application designer is able to creatively visualize a complex technical domain using depth
    2. Marker Shapes. When a group of shapes with their natural screen size are too small to be seen without zooming in, and users need a hint of what lies there

    1. Layers - Depth
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const configs = new ShapeConfigurations( {
                shapeTypes: [], // optional - use when svg template elements are used
                groups: [{
                        name: 'big',
                        existences: {1: 20, 10 : 0}, // the shapes included in the
                        // 'big' group will be visible in the beginning (zoom = 1) with a size
                        // of 20% of the view and will be hidden when the user zooms past 10
                        resolutions: {1 : 'standard'}
                    ...
                },
                {
                        name: 'medium',
                        existences: {12 : 10, 15: 0}, // 10% of viewport when zoom is 12
                        resolutions: {4 : 'standard' }
                    ...
                },
                {
                        name: 'small',
                        existences: {150 : 7, 30: 0},
                        resolutions: {20 : 'standard' }
                },
                {name: 'tiny ...}, {name: 'miniscule ...}, {microscopic ...}
            })
            ...
            $1000Words.createView('Main View', container, configs, onEvent, eventsOfInterest)
            .then( view => {...} )
                        


    Fishes arranged from top to bottom, arranged by size
    (100s of fish that are too small to be seen, are hidden at first)

     Zoom level 1



     Zoom level 12



     Zoom level 150

    2. Layers - Marker Shapes
    
                const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
                const configs = new ShapeConfigurations( {
                    shapeTypes: [], // optional - use when svg template elements are used
                    groups: [{
                            name: 'settings-marker',
                            existences: {1 : 1, 20 : 0},
                            resolutions: {1 : 'standard'}
                        ...
                    },
                    {
                            name: 'settings',
                            existences: {20 : 8},
                            resolutions: {20 : 'standard' }
                        ...
                    }]
                })
                ...
                $1000Words.createView('Main View', container, configs, onEvent, eventsOfInterest)
                .then( view => {...} )
                        


    Individual Settings Icons are too small to be seen so a marker shape is used

     Zoom level 1


     Zoom level 8


     Zoom level 16


     Zoom level 20


    Home

  11. Creating Views
  12. Views are created inside a placeholder DOM element created by the application. Views need to be created prior to creating individual shapes and the view instance is a parameter for the createShape call.
    View creation takes the following inputs:

    view creation
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const EventType = $1000Words.import('EventType');
            const ShapeEventType = $1000Words.import('ShapeEventType');
    
            const onEvent = (event) => console.log(event);
            const container = document.getElementById("my-view-container");
    
            // see 'Layers' & 'Shape Detail Level' for details on shape configs
            const configs = new ShapeConfigurations( {...} );
    
            const events = [ShapeEventType.IN_FOCUS, ShapeEventType.SELECT, EventType.VIEWPORT];
            $1000Words.createView(viewName, container, configs, onEvent, events)
            .then( view => {
                const shape1 = $1000Words.createShape(type1, id1, view, options, x1, y1);
                ...
                view.initialize();
            });
    
                        



  13. Events
  14. 1000Words views adhere to the "data in, events out" bidirectional pattern. Events of interest are published to the Application, which can then perform other UI tasks.

    1000Words components, just like any other HTML components, are used to compose the App. The responsibility, for the overall end-to-end UI interactions, rests with the App.

    The "data in" portion of component interactions is covered in the "Modifying Shape Attributes" and "modifying Shape Position" sections. This section covers the "events out" portion.

    Event Types
    
            EventType.VIEWPORT
                -percentLeft
                -percentTop
                -percentRight
                -percentBottom
            EventType.ViewChanged
                -view
            ShapeEventType.IN_FOCUS
                -shape
            ShapeEventType.CLICK
                -shapeId
            ShapeEventType.SELECT
                -shapeId
            ShapeEventType.DESELECT
                -shapeId
            EventType.CORNER_ACTIVATED
                -shape
                -corner
            EventType.CORNER_DEACTIVATED
                -shape
                -corner
                        

    Viewport event. This is emitted whenever the user zooms in or out, and when the user pans the view in any direction.

    Shape in-focus event. This is emitted when the user closes in, on a shape and the shape is covering the center of the viewport.
    On potential use case for this event is to populate an HTML 'properties panel' with the details of the in-focus shape. Textual, boolean and enumeration properties of the shape can be displayed or changed here.

    View changed event. This can happen when the user drills down into a shape or jumps out from a child view back onto the parent view.

    Shape click event. The user clicks a shape.

    Shape select event. The user shift-clicks on a shape which results in the shape being 'selected'. An enclosing golden rectangle is displayed.

    Corner Activated event. 1000Words allows explicit two step "select + are you sure?" interactions to be developed. Shapes need to be configured to have one or more "activated corners".
    The configuration can be done by specifying the name ('NW', 'NE', 'SE', 'SW') of the desired corner.
    Users would shift-click a shape to select it. A selection rectangle is displayed but additionally a larger "confirm" circle is displayed in the configured corner. Clicking on the activated corner serves as a "yes, i'm sure" action and a check mark is displayed to indicate this.

    Intercepting Events
    
        const EventType = $1000Words.import('EventType');
        const ShapeEventType = $1000Words.import('ShapeEventType');
        const ViewportEvent = $1000Words.import('ViewportEvent');
        const container = ...;
        const configs = ...;
        const onEvent = (event) => ...; // business ui logic
        const events = [ShapeEventType.IN_FOCUS, ShapeEventType.SELECT, ViewportEvent];
        $1000Words.createView('main-view', container, configs, onEvent, events).then( view => {
            ...
        }
                        

  15. Modifying Shape Attributes
  16. Applications need to render different shape instances of the same shape type differently, based on their characteristics and maybe even change the apperance dynamically as the attributes change.

    1000Words provides a way to create attributes on shapes, through the setAttr call. The call takes an attribute name and value.

    The render logic in Shape classes can then access these attributes to conditionally render shapes.

    NOTE: If a shape's attributes are dynamically changed after initial creation, say as part of some user interaction or event, 1000Words will automatically re-render (just) that shape.

    Shape Attributes and Rendering
    
        -- Shape Classes --
        const DomainShape = $1000Words.import('DomainShape');
        export default class House extends DomainShape {
            getShapeRenderString(svgComplexity) {
                return `<g>
                    <path d="..." style="fill: ${this.attrs.doorColor}"></path>
                    <path d="..." style="fill: ${this.attrs.roofColor}"></path>
                </g>`;
            }
        }
        export default class Automobile extends DomainShape {
            getTruckSVG(color) {...}
            getCarSVG(color) {...}
            getShapeRenderString(svgComplexity) {
                return this.attrs.type === 'Truck'
                  ? getTruckSVG(this.attrs.color)
                  : getCarSVG(this.attrs.color);
            }
        }
    
        -- App Code --
        $1000Words.createView(viewName, container, configs, onEvent, events)
        .then( view => {
            const house = $1000Words.createShape(House, 'white-house', view, options, x1, y1);
            house.setAttr('doorColor', 'white');
            house.setAttr('roofColor', 'slategrey');
            const auto = $1000Words.createShape(Automobile, 'my-ride', view, options, x2, y2);
            auto.setAttr('type', 'Car');
            auto.setAttr('color', 'red');
            ...
            view.initialize();
        });
    
        // elsewhere in the app...
        // 1000Words will take care of immediately re-rendering shapes after setAttr is called.
        house.setAttr('doorColor', 'red');
        house.setAttr('roofColor', 'lightcoral');
        auto.setAttr('type', 'Truck');
                            


  17. Modifying Shape Position
  18. The ability to move shapes adds a layer of dynamism on top of view interactivity. 1000Words provides a movePercent call on shapes for this.

    NOTE: When moving a large number of shapes over and over (multiple steps to get to the destination instead of one) performance can become a consideration. 1000Words provides a recalibrateShapes call on the view to prevent or reduce degradation.
    The current design requires the application to keep track of the shapes that are being moved and call recalibrateShapes after the move is done.

    Moving Shapes
    
            const fruits = [];
            anApple.movePercent(43.24, 27.6);
            fruits.push(anApple);
            aBanana.movePercent(64.1, 75);
            fruits.push(aBanana);
            anOrange.movePercent(13.7, 20);
            anOrange.movePercent(14.0, 20);
            anOrange.movePercent(14.3, 20);
            view.recalibrateShapes(fruits);
                            


  19. Programmatic Zoom and Pan
  20. Programmatic movement in a view adds a dynamic, somewhat movie-like facet to the user's experience because they are not always manually panning or zooming the view.

    A partial list of when this movement could be initiated ...



    Applications can design interesting and useful functions based on this feature. A few possibilities to jumpstart the creativity ...



    Both, zooming in and out as well as panning in all four directions is accomplished by the same two API calls, createViewport and setViewport.
    setViewport
    
        // viewport coordinates and zoom scale
        const viewport = $1000Words.createViewport(x1, y1, x2, y2, scale);
        // programmatic move to specified viewport over a specified time
        view.setViewport(viewport, seconds);
                            


    Note 1: The createViewport currently requires x1, y1, x2, y2 and scale parameters to create a Viewport. This could potentially be more streamlined in the future.

    Note 2: The second parameter in the setViewport call is the approximate number of seconds over which the animation will occur. Based on the specifics of the viewport change and the shapes that are present, as well as the underlying browser renders, this time will not be exactly honored. Pleasing animation effects can be achieved by experimentation for each given situation and tweaking the number of seconds so that the viewport change is smooth and not too fast or slow.



    Top Viewport (entire view): (0, 0)-(1000, 500) scale: 1
    Current viewport: (200, 100) - (800, 400) scale: 1.66667
    Target Viewport-1: (0, 0) - (600, 300) scale: 1.666666667
    Target Viewport-2: (350, 175) - (650, 400) scale: 3.33333333


    setViewport examples
    
        const blueViewport = $1000Words.createViewport(0, 0, 600, 300, 1.666666667);
        const redViewport = $1000Words.createViewport(350, 175, 650, 400, 3.33333333);
    
        // moving from green viewport to red viewport in 4 seconds
        view.setViewport(redViewport, 4);
        // moving from green viewport to blue in 5 seconds
        view.setViewport(blueViewport, 5);
        // moving from anywhere to blue viewport (source viewport doesn't matter)
        view.setViewport(blueViewport, 5);
                            


    Additional Programmatic Movement APIs
    
        // zoom and pan to a shape and then continue zooming in until drilldown into a new
        // view (requires the shape to have been configured as drilldownable)
        view.drilldownToShape(aShape, 6));  // movement in 6 seconds
    
        // same as drilldownToShape but using the shape's id instead of the actual instance
        view.drilldownToShapeId('a-shape-id', 6));
    
        // zoom out until the top of the view (biggest viewport) is reached
        view.zoomOutToTopOfView(6);
    
        // zoom out to the top of the view and a bit more to jump out to parent view
        view.jumpOutToParentView(); // no time duration parameter needed
                        


    Home

  21. Drilldown
  22. Drilldown happens when the user zooms in and gets close to a shape that has been configured for drilldown.

    The library creates the child view in the same place in the DOM as the parent view, ties the parent and child view together. It initializes the child view and creates the shapes in it.

    Since the library is doing all of this, the app does not need to call any of the library API. It just needs to provide the shape metadata.

    configuring shapes for drilldown
    
        const options = { drilldown: true, clickable: false, selectable: undefined };
        // or
        const options = { drilldown: true };
    
        const shape = $1000Words.createShape(ShapeType, 'shape-id', view, options, x, y);
                        

    providing drilldown data for child view
    
        const domainObjects = getObjectsFromDatabaseOrNetwork(...);
        const shape1s = [];
        domainObjects.forEach( do => {
            shape1s.push({
                typeName: 'ShapeType1',
                type: ShapeType1,
                x: ...,  y: ... // figure out x, y placements based on biz rules
                id: do.id,
                attrs: { attr1: do.attr1, attr2: do.attr2 },
                options: {}
            })
        })
        return ({
            name: 'ChildViewName',
            shapeTypes: [...], // optional - use if svg template elements are needed
            groups: [
                {
                    name: 'shapes-type-1',
                    existences: {...},
                    resolutions: {...},
                    shapes: shape1s
                },
                {
                    ...
                    shapes: shape2s
                }
            ]
        })
                        

  23. App Skeleton
  24. This section combines all the necessary bits of code in one place so the user knows all the parts that are needed. However, the preceeding sections will contain more details and explanations on each individual topic.

    app skeleton
    
        // Shape Class 1
        const DomainShape = $1000Words.import('DomainShape');
        export default class Car extends DomainShape {
            renderContents(detailLevel) {
                return `<g> <path d="m22.16 ..." stroke="green"></path>  ... </g>`;
            }
        }
        // Shape Class 2
        const DomainShape = $1000Words.import('DomainShape');
        export default class Truck extends DomainShape {
            renderContents(detailLevel) {
                return `<g> <rect x="25" ... ></rect> <path d="..."></path> </g>`;
            }
        }
    
        // View Class
        const EventType = $1000Words.import('EventType');
        const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
        const ShapeEventType = $1000Words.import('ShapeEventType');
    
        import Car from '<shapes-dir>/car';
        import Tree from '<shapes-dir>/tree';
        import Truck from '<shapes-dir>/truck';
    
        const onEvent = (event) => console.log(event);
        const events = [ShapeEventType.IN_FOCUS, ShapeEventType.SELECT, EventType.VIEWPORT_EVENT]
        const container = document.getElementById("my-view-container");
    
        const configs = new ShapeConfigurations( {
            shapeTypes: [...],
            groups: [{
                name: 'trees',
                existences: {1 : 0.5, 20 : 0},
                resolutions: {1 : 'low', 3: 'medium', '8': 'high'}
            },
            {
                name: 'autos'
                ...
            }]
        })
        const appCallbacks = {
            getDrilldownMetadata: (shape) => {
                return {name: 'ChildView', shapeTypes: [...], groups: [{}, ...]};
            }
        }
        $1000Words.createView(viewName, container, configs, onEvent, events, appCallbacks)
        .then( view => {
            const options = {drilldown: false, clickable: true, selectable: {'nw'}};
            const car1 = $1000Words.createShape(Car, 'car-1', view, options, x1, y1);
            const truck3 = $1000Words.createShape(Truck, 'truck-3', view, options, x2, y2);
            const aTree = $1000Words.createShape(Tree, 'a-tree', view, options, x3, y3);
            ...
            configs.setShapes({
                'autos': [car1, truck3],
                'trees': [aTree]
            })
            view.initialize();
        });
                        


  25. API

  26. Import 1000Words artifacts
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const DomainShape = $1000Words.import('DomainShape');
            const ShapeEventType = $1000Words.import('ShapeEventType');
                        

    Shape Classes and rendering
    
            const DomainShape = $1000Words.import('DomainShape');
            export default class House extends DomainShape {
                getShapeRenderString(svgComplexity) {
                    return `<g>
                        <path d="..." style="fill: ${this.attrs.doorColor}"></path>
                        <path d="..." style="fill: ${this.attrs.roofColor}"></path>
                    </g>`;
                }
            }
            export default class Automobile extends DomainShape {
                getTruckSVG(color) {...}
                getCarSVG(color) {...}
                getShapeRenderString(svgComplexity) {
                    return this.attrs.type === 'Truck'
                      ? getTruckSVG(this.attrs.color)
                      : getCarSVG(this.attrs.color);
                }
            }
                        

    Create View
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const EventType = $1000Words.import('EventType');
            const ShapeEventType = $1000Words.import('ShapeEventType');
    
            const onEvent = (event) => console.log(event);
            const appCallbacks = ...;
            const container = document.getElementById("my-view-container");
    
            // see 'Layers' & 'Shape Detail Level' for details on shape configs
            const configs = new ShapeConfigurations( {...} );
            const events = [ShapeEventType.IN_FOCUS, ShapeEventType.SELECT, EventType.VIEWPORT];
            $1000Words.createView('view-name', container, configs, onEvent, events, appCallbacks)
            .then( view => {
                ...
                ...
                view.initialize();
            });
                        

    Configuring Shape Detail Level
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const configs = new ShapeConfigurations( {
                shapeTypes: [], // optional - use when svg template elements are used
                groups: [{
                        name: 'trees',
                        existences: {1 : 2},
                        // a tree = 2% of overall view width when view is displayed
                        resolutions: {1 :'low', 3:'medium', '8':'high' } // detail level config
                        // zoom level (top of view)= 1, show the 'low' res version of trees
                        // zoom level > 1 and <= 3, show the 'low' res version of trees
                        // zoom level > 3 and <= 8, show the 'medium' res version of trees
                        // zoom level > 8, show the 'high' resolution version of trees
                    ...
                }]
            })
            ...
            $1000Words.createView('Main View', container, configs, onEvent, eventsOfInterest)
            .then( view => {...} )
                        

    Configuring Layers
    
            const ShapeConfigurations = $1000Words.import('ShapeConfigurations');
            const configs = new ShapeConfigurations( {
                shapeTypes: [], // optional - use when svg template elements are used
                groups: [{
                        name: 'big',
                        existences: {1: 20, 10 : 0}, // the shapes included in the
                        // 'big' group will be visible in the beginning (zoom = 1) with a size
                        // of 20% of the view and will be hidden when the user zooms past 10
                        resolutions: {1 : 'standard'}
                    ...
                },
                {
                        name: 'medium',
                        existences: {12 : 10, 15: 0}, // 10% of viewport when zoom is 12
                        resolutions: {4 : 'standard' }
                    ...
                },
                {
                        name: 'small',
                        existences: {150 : 7, 30: 0},
                        resolutions: {20 : 'standard' }
                }
            })
            ...
            $1000Words.createView('Main View', container, configs, onEvent, eventsOfInterest)
            .then( view => {...} )
    
                        

    Shape Instances
    
        import Car from '/car';
        import Tree from '/tree';
        import Truck from '/truck';
        const options = {drilldown: false, clickable: true, selectable: {'nw'}};
        $1000Words.createView(viewName, container, configs, onEvent, events, appCallbacks)
        .then( view => {
            $1000Words.createShape(Car, "car-fred", view, options, x1, y1);
            $1000Words.createShape(Car, "car-jimmy", view, options, x2, y2);
            $1000Words.createShape(Truck, "company-truck", view, options, x3, y3);
            ...
            ...
            view.initialize();
        });
                        

    Modifying Shape Attributes
    
        house.setAttr('doorColor', 'white');
        house.setAttr('roofColor', 'slategrey');
        auto.setAttr('color', 'red');
                        

    Modifying Shape Position
    
        const fruits = [];
        anApple.movePercent(43.24, 27.6);
        fruits.push(anApple);
        aBanana.movePercent(64.1, 75);
        fruits.push(aBanana);
        anOrange.movePercent(13.7, 20);
        anOrange.movePercent(14.0, 20);
        anOrange.movePercent(14.3, 20);
        view.recalibrateShapes(fruits);
                        

    Programmatic movement - create and set Viewport
    
        // viewport coordinates and zoom scale
        const viewport = $1000Words.createViewport(x1, y1, x2, y2, scale);
        // programmatic move to specified viewport over a specified time
        view.setViewport(viewport, seconds);
                        

    Programmatic movement - zoom to top of view
    
        // viewport coordinates and zoom scale
        const viewport = $1000Words.createViewport(x1, y1, x2, y2, scale);
        // programmatic move to specified viewport over a specified time
        view.setViewport(viewport, seconds);
                        

    Programmatic movement - jump out to parent view
    
        // zoom out until the top of the view (biggest viewport) is reached
        view.zoomOutToTopOfView(6);
                        

    Programmatic movement - drilldown into shape
    
        // zoom and pan to a shape and then continue zooming in until drilldown into a new
        // view (requires the shape to have been configured as drilldownable)
        view.drilldownToShape(aShape, 6));  // movement in 6 seconds
    
        // same as drilldownToShape but using the shape's id instead of the actual instance
        view.drilldownToShapeId('a-shape-id', 6));
    
                        

    Create child view - metadata
    
    
        // Unlike the main view, child views (from drilldown) are created by the library.
        // This also includes the shapes that need to go on the child view.
        // Therefore the app only needs to provide the metadata for the child view and not
        // explicitly call 'createView' and 'createShape'.
    
        // configuring shapes to be drilldownable, while creating the parent view.
        const options = { drilldown: true, clickable: false, selectable: undefined };
        // or
        const options = { drilldown: true };
    
    
        // providing drilldown or child view metadata
        getDrilldownMetadata: (parentShape) => {
            // figure out what needs to go into the child view for parentShape
            const trees = [
                {
                    // tree-1 metadata
                    typeName: 'Tree',
                    type: Tree,
                    x: ...,  y: ... // figure out x, y placements based on biz rules
                    id: 'tree-1',
                    attrs: { attr1: val1, attr2: va2 },
                    options: {}
                },
                { tree-2 ...},
                { tree-3 ...}
            ];
            const bushes = ...;
            const rocks = ...;
            return ({
                name: 'LandscapeView',
                shapeTypes: [...], // optional - use if svg template elements are needed
                groups: [
                    {
                        name: 'trees',
                        existences: {...},
                        resolutions: {...},
                        shapes: trees
                    },
                    {
                        name: 'bushes'
                        shapes: bushes,
                        ...
                    },
                    {
                        name: 'rocks',
                        shapes: rocks
                        ...
                    }
                ]
            })
        }
                        

  27. Miscellaneous