• Jump To … +
    ./demo/modules/canvas-minimap.js ./demo/modules/canvas-scene-editor.js ./demo/modules/dom-entity-editor.js ./demo/modules/entity-copy-paste.js ./demo/modules/entity-manipulation-gui.js ./demo/modules/entity-navigation.js ./demo/modules/entity-ring-builder.js ./demo/modules/london-crime-graphic.js ./demo/modules/london-crime-lines.js ./demo/modules/london-crime-stacked-bars.js ./demo/modules/lottie-loader.js ./demo/modules/simple-chart-frame-tests.js ./demo/modules/simple-chart-frame.js ./demo/modules/simple-graph-lines.js ./demo/modules/simple-graph-stacked-bars.js ./demo/modules/wikipedia-views-spiral-chart.js ./demo/snippets/animated-highlight-gradient-text-snippet.js ./demo/snippets/animated-hover-gradient-snippet.js ./demo/snippets/animated-word-gradient-snippet.js ./demo/snippets/before-after-slider-infographic.js ./demo/snippets/bubbles-text-snippet.js ./demo/snippets/green-box-snippet.js ./demo/snippets/jazzy-button-snippet.js ./demo/snippets/page-performance-snippet-test.js ./demo/snippets/page-performance-snippet.js ./demo/snippets/pan-image-snippet.js ./demo/snippets/placeholder-effect-snippet.js ./demo/snippets/ripple-effect-snippet.js ./demo/snippets/risograph-text-gradient-snippet.js ./demo/snippets/spotlight-text-snippet-test.js ./demo/snippets/spotlight-text-snippet.js ./demo/snippets/swirling-stripes-text-snippet.js ./demo/snippets/text-snippet-helper.js ./demo/snippets/word-highlighter-snippet.js ./demo/snippets/worley-text-gradient-snippet.js
  • §

    Demo Modules 001

    Scrawl-canvas modularized code - London crime charts

    Related files:

    • London crime charts - main module
    • London crime graphic module
    • London crime lines module
    • London crime stacked bar module
    • Simple chart frame module
    • Simple chart frame tests module
    • Simple graph lines module
    • Simple graph stacked bars module
    /*
    Data supplied to graph module as Javascript object with structure:
    {
        yearLabels: ['year-1-label', 'year-2-label', ...],
        data:       [year-1-data, year-2-data, ...],
    }
    */
    
    import * as scrawl from '../../source/scrawl.js';
  • §

    We need to adapt the graph frame with data specific to this graph

    import * as frame from './simple-chart-frame.js';
  • §

    Get the Magic Numbers from the chart frame

    let graphWidth = frame.graphWidth,
        graphHeight = frame.graphHeight,
        graphBottom = frame.graphBottom,
        graphLeft = frame.graphLeft;
  • §

    Define the group variables

    let positionGroup,
        lineGroup,
        pinGroup;
  • §

    Accessibility

    let selectedColumn = 0,
        space = '',
        currentData;
  • §

    The exported ‘build’ function

    const build = function (namespace, canvas, dataSet) {
    
        space = namespace;
        currentData = dataSet;
  • §

    Only build the Groups and entitys if they don’t already exist

        if (!positionGroup) {
    
            let yearLabels = dataSet.yearLabels,
                xStep = graphWidth / yearLabels.length;
  • §

    The ‘positions’ Group

    • A set of Block entitys used as pivots by the other entitys
            positionGroup = scrawl.makeGroup({
    
                name: `${namespace}-position-group`,
                host: canvas.base.name,
                order: 1,
                visibility: false,
            });
  • §

    The ‘lines’ Group

    • A set of line Shape entitys
    • Use position entitys for their start and end coordinate pivots
            lineGroup = scrawl.makeGroup({
    
                name: `${namespace}-line-group`,
                host: canvas.base.name,
                order: 2,
                visibility: false,
            });
  • §

    The ‘pins’ Group

    • A set of Wheel entitys to mark the position of each data point
    • Use position entitys as their pivots
    • Interactive
            pinGroup = scrawl.makeGroup({
    
                name: `${namespace}-pin-group`,
                host: canvas.base.name,
                order: 3,
                visibility: false,
            });
  • §

    Build the entitys

            yearLabels.forEach((label, index) => {
  • §

    Hidden position Blocks

                scrawl.makeBlock({
    
                    name: `${namespace}-${index}-position`,
                    group: positionGroup,
    
                    width: 0,
                    height: 0,
                    method: 'none',
    
                    startX: `${graphLeft + (xStep * index) + (xStep / 2)}%`,
                    startY: `${graphBottom}%`,
                });
    
                if (index) {
  • §

    Line Shapes

                    scrawl.makeLine({
    
                        name: `${namespace}-${index}-line`,
                        group: lineGroup,
    
                        pivot: `${namespace}-${index - 1}-position`,
                        lockTo: 'pivot',
    
                        endPivot: `${namespace}-${index}-position`,
                        endLockTo: 'pivot',
    
                        useStartAsControlPoint: true,
    
                        strokeStyle: 'blue',
                        lineWidth: 2,
    
                        method: 'draw',
                    });
                }
  • §

    Visible pin Wheels

                scrawl.makeWheel({
    
                    name: `${namespace}-${index}-pin`,
                    group: pinGroup,
                    order: index,
    
                    radius: 8,
    
                    handleX: 'center',
                    handleY: 'center',
    
                    pivot: `${namespace}-${index}-position`,
                    lockTo: 'pivot',
    
                    fillStyle: 'aliceblue',
                    strokeStyle: 'blue',
                    lineWidth: 4,
    
                    method: 'drawThenFill',
                });
            });
  • §

    All further calculation happens in the ‘update’ function

            update();
  • §

    Accessibility

            frame.setArrowAction('up', () => {});
            frame.setArrowAction('down', () => {});
            frame.setArrowAction('left', () => doNavigation('left'));
            frame.setArrowAction('right', () => doNavigation('right'));
  • §

    Display the graph entitys

            show();
        }
    };
  • §

    Accessibility

    const doNavigation = (direction) => {
    
        const { yearLabels } = currentData;
    
        const len = yearLabels.length;
    
        switch (direction) {
    
            case 'left' : 
                selectedColumn--;
                break;
    
            case 'right' : 
                selectedColumn++;
                break;
        }
    
        if (selectedColumn < 0) selectedColumn = len - 1;
        else if (selectedColumn >= len) selectedColumn = 0;
    
        updateSelected();
    };
  • §

    Processing

    Determine the range batch

    • To make sure the graph covers as much vertical space as possible
    const calculateBatchValue = (val) => {
    
        if (val > 50000) return 10000;
        if (val > 25000) return 5000;
        if (val > 10000) return 2000;
        if (val > 5000) return 1000;
        if (val > 2500) return 500;
        if (val > 1000) return 200;
        if (val > 500) return 100;
        if (val > 250) return 50;
        if (val > 100) return 20;
        if (val > 50) return 10;
        if (val > 25) return 5;
        if (val > 10) return 2;
        return 1;
    }
  • §

    The exported ‘update’ function

    const update = () => {
  • §

    Only update if we have entitys to update

        if (positionGroup) {
  • §

    Initial positioning calculations

            let yearLabels = currentData.yearLabels,
                data = currentData.data,
                max = Math.max(...data),
                min = Math.min(...data),
                batch = calculateBatchValue(max - min);
    
            max = ((Math.floor(max / batch)) * batch) + batch;
            min = (Math.floor(min / batch)) * batch;
    
            let categoryValue = graphHeight / (max - min),
                yDepth = graphBottom - graphHeight;
  • §

    Grab a handle to the ‘entity’ section in the Scrawl-canvas library

            let entity = scrawl.library.entity;
  • §

    Reset any highlighted pin Wheel

            pinGroup.setArtefacts({
                scale: 1,
                fillStyle: 'aliceblue',
            });
  • §

    Final calculations and updates

            yearLabels.forEach((label, index) => {
    
                let pointDepth = (data[index] - min) * categoryValue,
                    yVal = yDepth + (graphHeight - pointDepth),
                    tempName = `${space}-${index}`;
    
                entity[`${tempName}-position`].set({
                    startY: `${yVal}%`,
                });
    
                entity[`${tempName}-pin`].set({
    
                    onEnter: function () {
    
                        selectedColumn = this.get('order');
                        updateSelected();
                    },
                });
            });
  • §

    Update the chart frame

            frame.updateSubtitle('No data selected');
            frame.updateYTop(max.toLocaleString());
            frame.updateYBottom(min.toLocaleString());
            frame.updateXLeft(yearLabels[0]);
            frame.updateXRight(yearLabels[yearLabels.length - 1]);
    
            selectedColumn = 0;
            updateSelected();
        }
    };
  • §

    Handles both mouse hover, and (accessibility) keyboard navigation

    const updateSelected = () => {
    
        const { yearLabels, data } = currentData;
    
            const entity = pinGroup.getArtefact(pinGroup.artefacts[selectedColumn]);
    
            pinGroup.setArtefacts({
                scale: 1,
                fillStyle: 'aliceblue',
            });
    
            frame.updateSubtitle(`${yearLabels[selectedColumn]}: §RED§${data[selectedColumn].toLocaleString()}`);
    
            entity.set({
                scale: 1.5,
                fillStyle: 'red',
            });
    };
  • §

    Exported ‘kill’ function

    const kill = () => {
    
        if (positionGroup) {
    
            positionGroup.kill(true);
            lineGroup.kill(true);
            pinGroup.kill(true);
    
            positionGroup = false;
            lineGroup = false;
            pinGroup = false;
        }
    };
  • §

    Exported ‘hide’ function

    const hide = () => {
    
        if (positionGroup) {
    
            positionGroup.visibility = false;
            lineGroup.visibility = false;
            pinGroup.visibility = false;
        }
    };
  • §

    Exported ‘show’ function

    const show = () => {
    
        if (positionGroup) {
    
            positionGroup.visibility = true;
            lineGroup.visibility = true;
            pinGroup.visibility = true;
        }
    };
    
    export {
        build,
        update,
        kill,
        hide,
        show,
    }