UNPKG

scrawl-canvas

Version:
335 lines (253 loc) 8.74 kB
// # Demo Modules 001 // Scrawl-canvas modularized code - London crime charts // // Related files: // + [London crime charts - main module](../modules-001.html) // + [London crime graphic module](./london-crime-graphic.html) // + [London crime lines module](./london-crime-lines.html) // + [London crime stacked bar module](./london-crime-stacked-bars.html) // + [Simple chart frame module](./simple-chart-frame.html) // + [Simple chart frame tests module](./simple-chart-frame-tests.html) // + [Simple graph lines module](./simple-graph-lines.html) // + [Simple graph stacked bars module](./simple-graph-stacked-bars.html) /* 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, }