• Jump To … +
    ./demo/canvas-001.js ./demo/canvas-002.js ./demo/canvas-003.js ./demo/canvas-004.js ./demo/canvas-005.js ./demo/canvas-006.js ./demo/canvas-007.js ./demo/canvas-008.js ./demo/canvas-009.js ./demo/canvas-010.js ./demo/canvas-011.js ./demo/canvas-012.js ./demo/canvas-013.js ./demo/canvas-014.js ./demo/canvas-015.js ./demo/canvas-016.js ./demo/canvas-017.js ./demo/canvas-018.js ./demo/canvas-019.js ./demo/canvas-020.js ./demo/canvas-021.js ./demo/canvas-022.js ./demo/canvas-023.js ./demo/canvas-024.js ./demo/canvas-025.js ./demo/canvas-026.js ./demo/canvas-027.js ./demo/canvas-028.js ./demo/canvas-029.js ./demo/canvas-030.js ./demo/canvas-031.js ./demo/canvas-032.js ./demo/canvas-033.js ./demo/canvas-034.js ./demo/canvas-035.js ./demo/canvas-036.js ./demo/canvas-037.js ./demo/canvas-038.js ./demo/canvas-039.js ./demo/canvas-040.js ./demo/canvas-041.js ./demo/canvas-042.js ./demo/canvas-043.js ./demo/canvas-044.js ./demo/canvas-045.js ./demo/canvas-046.js ./demo/canvas-047.js ./demo/canvas-048.js ./demo/canvas-049.js ./demo/canvas-050.js ./demo/canvas-051.js ./demo/canvas-052.js ./demo/canvas-053.js ./demo/canvas-054.js ./demo/canvas-055.js ./demo/canvas-056.js ./demo/canvas-057.js ./demo/canvas-058.js ./demo/canvas-059.js ./demo/canvas-060.js ./demo/canvas-061.js ./demo/canvas-062.js ./demo/canvas-063.js ./demo/canvas-064.js ./demo/canvas-065.js ./demo/canvas-066.js ./demo/canvas-067.js ./demo/canvas-068.js ./demo/canvas-069.js ./demo/canvas-070.js ./demo/canvas-071.js ./demo/canvas-072.js ./demo/canvas-073.js ./demo/canvas-201.js ./demo/canvas-202.js ./demo/canvas-203.js ./demo/canvas-204.js ./demo/canvas-205.js ./demo/canvas-206.js ./demo/canvas-207.js ./demo/canvas-208.js ./demo/canvas-209.js ./demo/canvas-210.js ./demo/canvas-211.js ./demo/canvas-212.js ./demo/delaunator-001.js ./demo/delaunator-002.js ./demo/dom-001.js ./demo/dom-002.js ./demo/dom-003.js ./demo/dom-004.js ./demo/dom-005.js ./demo/dom-006.js ./demo/dom-007.js ./demo/dom-008.js ./demo/dom-009.js ./demo/dom-010.js ./demo/dom-011.js ./demo/dom-012.js ./demo/dom-013.js ./demo/dom-015.js ./demo/dom-016.js ./demo/dom-017.js ./demo/dom-018.js ./demo/dom-019.js ./demo/dom-020.js ./demo/dom-021.js ./demo/filters-001.js ./demo/filters-002.js ./demo/filters-003.js ./demo/filters-004.js ./demo/filters-005.js ./demo/filters-006.js ./demo/filters-007.js ./demo/filters-008.js ./demo/filters-009.js ./demo/filters-010.js ./demo/filters-011.js ./demo/filters-012.js ./demo/filters-013.js ./demo/filters-014.js ./demo/filters-015.js ./demo/filters-016.js ./demo/filters-017.js ./demo/filters-018.js ./demo/filters-019.js ./demo/filters-020.js ./demo/filters-021.js ./demo/filters-022.js ./demo/filters-023.js ./demo/filters-024.js ./demo/filters-025.js ./demo/filters-026.js ./demo/filters-027.js ./demo/filters-028.js ./demo/filters-029.js ./demo/filters-030.js ./demo/filters-031.js ./demo/filters-032.js ./demo/filters-033.js ./demo/filters-034.js ./demo/filters-035.js ./demo/filters-036.js ./demo/filters-037.js ./demo/filters-101.js ./demo/filters-102.js ./demo/filters-103.js ./demo/filters-104.js ./demo/filters-105.js ./demo/filters-501.js ./demo/filters-502.js ./demo/filters-503.js ./demo/filters-504.js ./demo/filters-505.js ./demo/js/mediapipe/tasks-vision/vision-bundle.js ./demo/js/mediapipe/tasks-vision/wasm/vision_wasm_internal.js ./demo/js/mediapipe/tasks-vision/wasm/vision_wasm_nosimd_internal.js ./demo/mediapipe-001.js ./demo/mediapipe-002.js ./demo/mediapipe-003.js ./demo/modules-001.js ./demo/modules-002.js ./demo/modules-003.js ./demo/modules-004.js ./demo/modules-005.js ./demo/modules-006.js ./demo/modules/canvas-minimap.js ./demo/modules/canvas-scene-editor.js ./demo/modules/demo-m006-c1.js ./demo/modules/demo-m006-c2.js ./demo/modules/demo-m006-c3.js ./demo/modules/demo-m006-c4.js ./demo/modules/demo-m006-utils.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-lines.js ./demo/modules/london-crime-stacked-bars.js ./demo/modules/lottie-loader.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/packets-001.js ./demo/packets-002.js ./demo/particles-001.js ./demo/particles-002.js ./demo/particles-003.js ./demo/particles-004.js ./demo/particles-005.js ./demo/particles-006.js ./demo/particles-007.js ./demo/particles-008.js ./demo/particles-009.js ./demo/particles-010.js ./demo/particles-011.js ./demo/particles-012.js ./demo/particles-013.js ./demo/particles-014.js ./demo/particles-015.js ./demo/particles-016.js ./demo/particles-017.js ./demo/snippets-001.js ./demo/snippets-002.js ./demo/snippets-003.js ./demo/snippets-004.js ./demo/snippets-005.js ./demo/snippets-006.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/word-highlighter-snippet.js ./demo/snippets/worley-text-gradient-snippet.js ./demo/temp-001.js ./demo/temp-shape-scale-investigation.js ./demo/tensorflow-001.js ./demo/tensorflow-002.js ./demo/utilities.js
  • §

    Demo Modules 002

    Spiral charts

    Related files:

    • Spiral charts - main module

    We export a function which issues the fetch call and then builds a chart in a Cell. The function requires an object with the following arguments:

    • page (required) - String value of the Wikipedia page, whose views statistics are to be charted: https://en.wikipedia.org/wiki/__Cat__
    • canvas (required) - a Scrawl-canvas Canvas wrapper object
    • assetName (required) - a namespace String
    • scrawl (required) - the SC object
    • size (optional) - Number - the size of the (square) generated chart. Default: 400
    • backgroundColor (optional) - CSS Color String - the background color of the generated chart. Default: ‘transparent’
    • minimumColor (optional) - CSS Color String - color used to display low daily views datapoints. Default: ‘azure’
    • medialColor (optional) - CSS Color String - color used to display average daily views datapoints. Default: ‘blue’
    • maximumColor (optional) - CSS Color String - color used to display high daily views datapoints. Default: ‘red’
  • §

    Exported function

    export default function (items) {
    
        const { assetName, page, canvas, scrawl, namespace } = items;
  • §

    Check we can proceed

        if (assetName && page && canvas && scrawl && namespace) {
    
            const {
                size = 400,
                backgroundColor = 'rgb(0 0 0 / 0)',
                minimumColor = 'azure',
                medialColor = 'blue',
                maximumColor = 'red',
            } = items;
    
            const name = n => `${namespace}-${n}`;
  • §

    We will create a new Cell for the chart, fabricate the required entitys, render the Cell, then capture the output into an asset

            const cell = canvas.buildCell({
                name: name('cell'),
                width: size,
                height: size,
                cleared: false,
                compiled: false,
                shown: false,
                backgroundColor,
            });
    
            const chartGroup = cell.get('group');
  • §

    Use Color objects to determine the appropriate color for each charted view value.

            const lowViewsFactory = scrawl.makeColor({
                name: name('low-views'),
                minimumColor,
                maximumColor: medialColor,
                colorSpace: 'OKLAB',
            });
    
            const highViewsFactory = scrawl.makeColor({
                name: name('high-views'),
                minimumColor: medialColor,
                maximumColor,
                colorSpace: 'OKLAB',
            });
  • §

    We start by retrieving the data from Wikipedia

            getData(page)
            .then(data => {
  • §

    Some initial calculations

                const maxViews = data.max,
                    minViews = data.min,
                    medianViews = (maxViews - minViews) / 2;
    
                const maxDataLen = Math.max(data.mon.length, data.tue.length, data.wed.length, data.thu.length, data.fri.length, data.sat.length, data.sun.length);
  • §

    Each day series is a spiral of Line entitys. We’ll cut down on the code by creating a factory to generate each day’s spiral

                const buildDayChart = function (dayData, offsetVal, dayName, missFirst) {
    
                    let currentWeek, currentYear, nextWeek, nextYear;
    
                    for (let i = 0, iz = dayData.length - 2; i < iz; i++) {
    
                        if (missFirst) {
    
                            currentWeek = (i + 1) % 52;
                            currentYear = Math.floor((i + 1) / 52);
    
                            nextWeek = (i + 2) % 52;
                            nextYear = Math.floor((i + 2) / 52);
    
                            nextWeek = nextWeek % 52;
                        }
                        else {
    
                            currentWeek = i % 52;
                            currentYear = Math.floor(i / 52);
    
                            nextWeek = (i + 1) % 52;
                            nextYear = Math.floor((i + 1) / 52);
    
                            nextWeek = nextWeek % 52;
                        }
    
                        const views = dayData[i] - minViews;
    
                        let dataColor;
  • §

    Get the appropriate color for this data point’s value

                        if (views < medianViews) dataColor = lowViewsFactory.getRangeColor(views / medianViews);
                        else dataColor = highViewsFactory.getRangeColor((views - medianViews) / medianViews);
  • §

    Each line represents a single data point, positioned in its appropriate place on the spiral by reference to the chart’s spoke lines

                        scrawl.makeLine({
    
                            name: name(`${dayName}-line-${i}`),
                            group: chartGroup,
    
                            lineWidth: 5,
                            strokeStyle: dataColor,
    
                            path: name(`week-line-${currentWeek}`),
                            pathPosition: (currentYear * 0.33) + offsetVal,
                            lockTo: 'path',
    
                            endPath: name(`week-line-${nextWeek}`),
                            endPathPosition: (nextYear * 0.33) + offsetVal,
                            endLockTo: 'path',
    
                            method: 'draw',
                        });
                    }
                }
  • §

    A year is made up of 52 weeks. We create 52 Line entity spokes around a central point to act as the frame for our chart. By setting an appropriate value for each Line’s handleY and roll attributes means that when it comes to building the day spirals we just need to pivot those Lines to our spokes - positioning made easy!

                for (let i = 0; i < 52; i++) {
    
                    scrawl.makeLine({
    
                        name: name(`week-line-${i}`),
                        group: chartGroup,
    
                        start: [200, 220],
                        end: [200, (220 - 120)],
                        handle: [0, ((i / 52) * (120 / 3)) + 30],
                        roll: (i / 52) * 360,
                        useAsPath: true,
  • §

    We don’t need to see the spokes in the finished chart

                        method: 'none',
                    });
                }
  • §

    Build each day’s spiral

                buildDayChart(data.mon, 0, 'mon', (data.mon.length < maxDataLen) ? true : false);
                buildDayChart(data.tue, 0.042, 'tue', (data.tue.length < maxDataLen) ? true : false);
                buildDayChart(data.wed, 0.084, 'wed', (data.wed.length < maxDataLen) ? true : false);
                buildDayChart(data.thu, 0.126, 'thu', (data.thu.length < maxDataLen) ? true : false);
                buildDayChart(data.fri, 0.168, 'fri', (data.fri.length < maxDataLen) ? true : false);
                buildDayChart(data.sat, 0.21, 'sat', (data.sat.length < maxDataLen) ? true : false);
                buildDayChart(data.sun, 0.252, 'sun', (data.sun.length < maxDataLen) ? true : false);
  • §

    We will render this Cell manually, outside of the Display cycle animation loop

                cell.clear();
                scrawl.createImageFromCell(cell, assetName);
                cell.compile();
  • §

    Clean up our mess

                scrawl.purge(namespace);
            })
            .catch(e => console.log('buildChart error', e));
        }
    }
  • §

    Helper function

    Function to fetch and parse Wikipedia page view timeseries data

    • Wikipedia implements the Mediawiki Action API, meaning we can retrieve page views statistics by calling the appropriate endpoint URL
    • The fetch call is asynchronous, thus all calls get wrapped in Javascript Promises
    • Function default action is to retrieve data for views of the Wikipedia Cat page for the past three years
    const getData = (page = 'Cat') => {
    
        return new Promise((resolve, reject) => {
    
            const data = {
                page: '',
                max: 0,
                min: 0,
                fromdate: '20200101',
                todate: '20200101',
                sunday: [],
                monday: [],
                tuesday: [],
                wednesday: [],
                thursday: [],
                friday: [],
                saturday: [],
            };
    
            const t = new Date(),
                f = new Date();
    
            t.setDate(t.getDate() - 1);
            f.setFullYear(f.getFullYear() - 3);
    
            const fromdate = f.toISOString().split('T')[0].replace(/-/g, ''),
                todate = t.toISOString().split('T')[0].replace(/-/g, '');
    
            let dayCounter = f.getDay(),
                maxViews = 0,
                minViews = -1;
    
            const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/user/${page}/daily/${fromdate}/${todate}`;
    
            fetch(url)
            .then(response => response.json())
            .then(dataObject => {
    
                const dataArray = dataObject.items;
    
                const results = [[],[],[],[],[],[],[]];
    
                dataArray.forEach(d => {
    
                    const views = d.views;
    
                    maxViews = (views > maxViews) ? views : maxViews;
    
                    if (minViews < 0) minViews = maxViews;
                    else minViews = (views < minViews) ? views : minViews;
    
                    results[dayCounter].push(views);
    
                    dayCounter++;
                    dayCounter = dayCounter % 7;
                });
    
                data.page = page;
                data.max = maxViews;
                data.min = minViews;
                data.fromdate = fromdate;
                data.todate = todate;
                data.sun = results[0];
                data.mon = results[1];
                data.tue = results[2];
                data.wed = results[3];
                data.thu = results[4];
                data.fri = results[5];
                data.sat = results[6];
    
                resolve(data);
            })
            .catch(e => {
    
                console.log(e);
                reject(data);
            });
        });
    };