From 029cd56b955e367ff941ebf64028dfef6d7db11b Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Tue, 3 Jan 2023 11:16:06 +0100 Subject: [PATCH] minify wordcloud --- layouts/partials/head.html | 4 +- static/plugins/wordcloud/wordcloud2.js | 1241 -------------------- static/plugins/wordcloud/wordcloud2.min.js | 8 + 3 files changed, 10 insertions(+), 1243 deletions(-) delete mode 100644 static/plugins/wordcloud/wordcloud2.js create mode 100644 static/plugins/wordcloud/wordcloud2.min.js diff --git a/layouts/partials/head.html b/layouts/partials/head.html index b8edd23e..4bef2809 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -16,8 +16,8 @@ - - + + diff --git a/static/plugins/wordcloud/wordcloud2.js b/static/plugins/wordcloud/wordcloud2.js deleted file mode 100644 index c4a607bd..00000000 --- a/static/plugins/wordcloud/wordcloud2.js +++ /dev/null @@ -1,1241 +0,0 @@ -/*! - * wordcloud2.js - * http://timdream.org/wordcloud2.js/ - * - * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors. - * Released under the MIT license - */ - -'use strict' - -// setImmediate -if (!window.setImmediate) { - window.setImmediate = (function setupSetImmediate () { - return window.msSetImmediate || - window.webkitSetImmediate || - window.mozSetImmediate || - window.oSetImmediate || - (function setupSetZeroTimeout () { - if (!window.postMessage || !window.addEventListener) { - return null - } - - var callbacks = [undefined] - var message = 'zero-timeout-message' - - // Like setTimeout, but only takes a function argument. There's - // no time argument (always zero) and no arguments (you have to - // use a closure). - var setZeroTimeout = function setZeroTimeout (callback) { - var id = callbacks.length - callbacks.push(callback) - window.postMessage(message + id.toString(36), '*') - - return id - } - - window.addEventListener('message', function setZeroTimeoutMessage (evt) { - // Skipping checking event source, retarded IE confused this window - // object with another in the presence of iframe - if (typeof evt.data !== 'string' || - evt.data.substr(0, message.length) !== message/* || - evt.source !== window */) { - return - } - - evt.stopImmediatePropagation() - - var id = parseInt(evt.data.substr(message.length), 36) - if (!callbacks[id]) { - return - } - - callbacks[id]() - callbacks[id] = undefined - }, true) - - /* specify clearImmediate() here since we need the scope */ - window.clearImmediate = function clearZeroTimeout (id) { - if (!callbacks[id]) { - return - } - - callbacks[id] = undefined - } - - return setZeroTimeout - })() || - // fallback - function setImmediateFallback (fn) { - window.setTimeout(fn, 0) - } - })() -} - -if (!window.clearImmediate) { - window.clearImmediate = (function setupClearImmediate () { - return window.msClearImmediate || - window.webkitClearImmediate || - window.mozClearImmediate || - window.oClearImmediate || - // "clearZeroTimeout" is implement on the previous block || - // fallback - function clearImmediateFallback (timer) { - window.clearTimeout(timer) - } - })() -} - -(function (global) { - // Check if WordCloud can run on this browser - var isSupported = (function isSupported () { - var canvas = document.createElement('canvas') - if (!canvas || !canvas.getContext) { - return false - } - - var ctx = canvas.getContext('2d') - if (!ctx) { - return false - } - if (!ctx.getImageData) { - return false - } - if (!ctx.fillText) { - return false - } - - if (!Array.prototype.some) { - return false - } - if (!Array.prototype.push) { - return false - } - - return true - }()) - - // Find out if the browser impose minium font size by - // drawing small texts on a canvas and measure it's width. - var minFontSize = (function getMinFontSize () { - if (!isSupported) { - return - } - - var ctx = document.createElement('canvas').getContext('2d') - - // start from 20 - var size = 20 - - // two sizes to measure - var hanWidth, mWidth - - while (size) { - ctx.font = size.toString(10) + 'px sans-serif' - if ((ctx.measureText('\uFF37').width === hanWidth) && - (ctx.measureText('m').width) === mWidth) { - return (size + 1) - } - - hanWidth = ctx.measureText('\uFF37').width - mWidth = ctx.measureText('m').width - - size-- - } - - return 0 - })() - - var getItemExtraData = function (item) { - if (Array.isArray(item)) { - var itemCopy = item.slice() - // remove data we already have (word and weight) - itemCopy.splice(0, 2) - return itemCopy - } else { - return [] - } - } - - // Based on http://jsfromhell.com/array/shuffle - var shuffleArray = function shuffleArray (arr) { - for (var j, x, i = arr.length; i;) { - j = Math.floor(Math.random() * i) - x = arr[--i] - arr[i] = arr[j] - arr[j] = x - } - return arr - } - - var timer = {}; - var WordCloud = function WordCloud (elements, options) { - if (!isSupported) { - return - } - - var timerId = Math.floor(Math.random() * Date.now()) - - if (!Array.isArray(elements)) { - elements = [elements] - } - - elements.forEach(function (el, i) { - if (typeof el === 'string') { - elements[i] = document.getElementById(el) - if (!elements[i]) { - throw new Error('The element id specified is not found.') - } - } else if (!el.tagName && !el.appendChild) { - throw new Error('You must pass valid HTML elements, or ID of the element.') - } - }) - - /* Default values to be overwritten by options object */ - var settings = { - list: [], - fontFamily: '"Trebuchet MS", "Heiti TC", "微軟正黑體", ' + - '"Arial Unicode MS", "Droid Fallback Sans", sans-serif', - fontWeight: 'normal', - color: 'random-dark', - minSize: 0, // 0 to disable - weightFactor: 1, - clearCanvas: true, - backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1) - - gridSize: 8, - drawOutOfBound: false, - shrinkToFit: false, - origin: null, - - drawMask: false, - maskColor: 'rgba(255,0,0,0.3)', - maskGapWidth: 0.3, - - wait: 0, - abortThreshold: 0, // disabled - abort: function noop () {}, - - minRotation: -Math.PI / 2, - maxRotation: Math.PI / 2, - rotationSteps: 0, - - shuffle: true, - rotateRatio: 0.1, - - shape: 'circle', - ellipticity: 0.65, - - classes: null, - - hover: null, - click: null - } - - if (options) { - for (var key in options) { - if (key in settings) { - settings[key] = options[key] - } - } - } - - /* Convert weightFactor into a function */ - if (typeof settings.weightFactor !== 'function') { - var factor = settings.weightFactor - settings.weightFactor = function weightFactor (pt) { - return pt * factor // in px - } - } - - /* Convert shape into a function */ - if (typeof settings.shape !== 'function') { - switch (settings.shape) { - case 'circle': - /* falls through */ - default: - // 'circle' is the default and a shortcut in the code loop. - settings.shape = 'circle' - break - - case 'cardioid': - settings.shape = function shapeCardioid (theta) { - return 1 - Math.sin(theta) - } - break - - /* - To work out an X-gon, one has to calculate "m", - where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0)) - http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28 - 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29 - Copy the solution into polar equation r = 1/(cos(t') + m*sin(t')) - where t' equals to mod(t, 2PI/X) - */ - - case 'diamond': - // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+ - // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D - // +0+..+2*PI - settings.shape = function shapeSquare (theta) { - var thetaPrime = theta % (2 * Math.PI / 4) - return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime)) - } - break - - case 'square': - // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t - // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI - settings.shape = function shapeSquare (theta) { - return Math.min( - 1 / Math.abs(Math.cos(theta)), - 1 / Math.abs(Math.sin(theta)) - ) - } - break - - case 'triangle-forward': - // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+ - // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29 - // %29%29%2C+t+%3D+0+..+2*PI - settings.shape = function shapeTriangle (theta) { - var thetaPrime = theta % (2 * Math.PI / 3) - return 1 / (Math.cos(thetaPrime) + - Math.sqrt(3) * Math.sin(thetaPrime)) - } - break - - case 'triangle': - case 'triangle-upright': - settings.shape = function shapeTriangle (theta) { - var thetaPrime = (theta + Math.PI * 3 / 2) % (2 * Math.PI / 3) - return 1 / (Math.cos(thetaPrime) + - Math.sqrt(3) * Math.sin(thetaPrime)) - } - break - - case 'pentagon': - settings.shape = function shapePentagon (theta) { - var thetaPrime = (theta + 0.955) % (2 * Math.PI / 5) - return 1 / (Math.cos(thetaPrime) + - 0.726543 * Math.sin(thetaPrime)) - } - break - - case 'star': - settings.shape = function shapeStar (theta) { - var thetaPrime = (theta + 0.955) % (2 * Math.PI / 10) - if ((theta + 0.955) % (2 * Math.PI / 5) - (2 * Math.PI / 10) >= 0) { - return 1 / (Math.cos((2 * Math.PI / 10) - thetaPrime) + - 3.07768 * Math.sin((2 * Math.PI / 10) - thetaPrime)) - } else { - return 1 / (Math.cos(thetaPrime) + - 3.07768 * Math.sin(thetaPrime)) - } - } - break - } - } - - /* Make sure gridSize is a whole number and is not smaller than 4px */ - settings.gridSize = Math.max(Math.floor(settings.gridSize), 4) - - /* shorthand */ - var g = settings.gridSize - var maskRectWidth = g - settings.maskGapWidth - - /* normalize rotation settings */ - var rotationRange = Math.abs(settings.maxRotation - settings.minRotation) - var rotationSteps = Math.abs(Math.floor(settings.rotationSteps)) - var minRotation = Math.min(settings.maxRotation, settings.minRotation) - - /* information/object available to all functions, set when start() */ - var grid, // 2d array containing filling information - ngx, ngy, // width and height of the grid - center, // position of the center of the cloud - maxRadius - - /* timestamp for measuring each putWord() action */ - var escapeTime - - /* function for getting the color of the text */ - var getTextColor - function randomHslColor (min, max) { - return 'hsl(' + - (Math.random() * 360).toFixed() + ',' + - (Math.random() * 30 + 70).toFixed() + '%,' + - (Math.random() * (max - min) + min).toFixed() + '%)' - } - switch (settings.color) { - case 'random-dark': - getTextColor = function getRandomDarkColor () { - return randomHslColor(10, 50) - } - break - - case 'random-light': - getTextColor = function getRandomLightColor () { - return randomHslColor(50, 90) - } - break - - default: - if (typeof settings.color === 'function') { - getTextColor = settings.color - } - break - } - - /* function for getting the font-weight of the text */ - var getTextFontWeight - if (typeof settings.fontWeight === 'function') { - getTextFontWeight = settings.fontWeight - } - - /* function for getting the classes of the text */ - var getTextClasses = null - if (typeof settings.classes === 'function') { - getTextClasses = settings.classes - } - - /* Interactive */ - var interactive = false - var infoGrid = [] - var hovered - - var getInfoGridFromMouseTouchEvent = - function getInfoGridFromMouseTouchEvent (evt) { - var canvas = evt.currentTarget - var rect = canvas.getBoundingClientRect() - var clientX - var clientY - /** Detect if touches are available */ - if (evt.touches) { - clientX = evt.touches[0].clientX - clientY = evt.touches[0].clientY - } else { - clientX = evt.clientX - clientY = evt.clientY - } - var eventX = clientX - rect.left - var eventY = clientY - rect.top - - var x = Math.floor(eventX * ((canvas.width / rect.width) || 1) / g) - var y = Math.floor(eventY * ((canvas.height / rect.height) || 1) / g) - - if (!infoGrid[x]) { - return null - } - - return infoGrid[x][y] - } - - var wordcloudhover = function wordcloudhover (evt) { - var info = getInfoGridFromMouseTouchEvent(evt) - - if (hovered === info) { - return - } - - hovered = info - if (!info) { - settings.hover(undefined, undefined, evt) - - return - } - - settings.hover(info.item, info.dimension, evt) - } - - var wordcloudclick = function wordcloudclick (evt) { - var info = getInfoGridFromMouseTouchEvent(evt) - if (!info) { - return - } - - settings.click(info.item, info.dimension, evt) - evt.preventDefault() - } - - /* Get points on the grid for a given radius away from the center */ - var pointsAtRadius = [] - var getPointsAtRadius = function getPointsAtRadius (radius) { - if (pointsAtRadius[radius]) { - return pointsAtRadius[radius] - } - - // Look for these number of points on each radius - var T = radius * 8 - - // Getting all the points at this radius - var t = T - var points = [] - - if (radius === 0) { - points.push([center[0], center[1], 0]) - } - - while (t--) { - // distort the radius to put the cloud in shape - var rx = 1 - if (settings.shape !== 'circle') { - rx = settings.shape(t / T * 2 * Math.PI) // 0 to 1 - } - - // Push [x, y, t] t is used solely for getTextColor() - points.push([ - center[0] + radius * rx * Math.cos(-t / T * 2 * Math.PI), - center[1] + radius * rx * Math.sin(-t / T * 2 * Math.PI) * - settings.ellipticity, - t / T * 2 * Math.PI]) - } - - pointsAtRadius[radius] = points - return points - } - - /* Return true if we had spent too much time */ - var exceedTime = function exceedTime () { - return ((settings.abortThreshold > 0) && - ((new Date()).getTime() - escapeTime > settings.abortThreshold)) - } - - /* Get the deg of rotation according to settings, and luck. */ - var getRotateDeg = function getRotateDeg () { - if (settings.rotateRatio === 0) { - return 0 - } - - if (Math.random() > settings.rotateRatio) { - return 0 - } - - if (rotationRange === 0) { - return minRotation - } - - if (rotationSteps > 0) { - // Min rotation + zero or more steps * span of one step - return minRotation + - Math.floor(Math.random() * rotationSteps) * - rotationRange / (rotationSteps - 1) - } else { - return minRotation + Math.random() * rotationRange - } - } - - var getTextInfo = function getTextInfo (word, weight, rotateDeg, extraDataArray) { - // calculate the acutal font size - // fontSize === 0 means weightFactor function wants the text skipped, - // and size < minSize means we cannot draw the text. - var debug = false - var fontSize = settings.weightFactor(weight) - if (fontSize <= settings.minSize) { - return false - } - - // Scale factor here is to make sure fillText is not limited by - // the minium font size set by browser. - // It will always be 1 or 2n. - var mu = 1 - if (fontSize < minFontSize) { - mu = (function calculateScaleFactor () { - var mu = 2 - while (mu * fontSize < minFontSize) { - mu += 2 - } - return mu - })() - } - - // Get fontWeight that will be used to set fctx.font - var fontWeight - if (getTextFontWeight) { - fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray) - } else { - fontWeight = settings.fontWeight - } - - var fcanvas = document.createElement('canvas') - var fctx = fcanvas.getContext('2d', { willReadFrequently: true }) - - fctx.font = fontWeight + ' ' + - (fontSize * mu).toString(10) + 'px ' + settings.fontFamily - - // Estimate the dimension of the text with measureText(). - var fw = fctx.measureText(word).width / mu - var fh = Math.max(fontSize * mu, - fctx.measureText('m').width, - fctx.measureText('\uFF37').width - ) / mu - - // Create a boundary box that is larger than our estimates, - // so text don't get cut of (it sill might) - var boxWidth = fw + fh * 2 - var boxHeight = fh * 3 - var fgw = Math.ceil(boxWidth / g) - var fgh = Math.ceil(boxHeight / g) - boxWidth = fgw * g - boxHeight = fgh * g - - // Calculate the proper offsets to make the text centered at - // the preferred position. - - // This is simply half of the width. - var fillTextOffsetX = -fw / 2 - // Instead of moving the box to the exact middle of the preferred - // position, for Y-offset we move 0.4 instead, so Latin alphabets look - // vertical centered. - var fillTextOffsetY = -fh * 0.4 - - // Calculate the actual dimension of the canvas, considering the rotation. - var cgh = Math.ceil((boxWidth * Math.abs(Math.sin(rotateDeg)) + - boxHeight * Math.abs(Math.cos(rotateDeg))) / g) - var cgw = Math.ceil((boxWidth * Math.abs(Math.cos(rotateDeg)) + - boxHeight * Math.abs(Math.sin(rotateDeg))) / g) - var width = cgw * g - var height = cgh * g - - fcanvas.setAttribute('width', width) - fcanvas.setAttribute('height', height) - - if (debug) { - // Attach fcanvas to the DOM - document.body.appendChild(fcanvas) - // Save it's state so that we could restore and draw the grid correctly. - fctx.save() - } - - // Scale the canvas with |mu|. - fctx.scale(1 / mu, 1 / mu) - fctx.translate(width * mu / 2, height * mu / 2) - fctx.rotate(-rotateDeg) - - // Once the width/height is set, ctx info will be reset. - // Set it again here. - fctx.font = fontWeight + ' ' + - (fontSize * mu).toString(10) + 'px ' + settings.fontFamily - - // Fill the text into the fcanvas. - // XXX: We cannot because textBaseline = 'top' here because - // Firefox and Chrome uses different default line-height for canvas. - // Please read https://bugzil.la/737852#c6. - // Here, we use textBaseline = 'middle' and draw the text at exactly - // 0.5 * fontSize lower. - fctx.fillStyle = '#000' - fctx.textBaseline = 'middle' - fctx.fillText( - word, fillTextOffsetX * mu, - (fillTextOffsetY + fontSize * 0.5) * mu - ) - - // Get the pixels of the text - var imageData = fctx.getImageData(0, 0, width, height).data - - if (exceedTime()) { - return false - } - - if (debug) { - // Draw the box of the original estimation - fctx.strokeRect( - fillTextOffsetX * mu, - fillTextOffsetY, fw * mu, fh * mu - ) - fctx.restore() - } - - // Read the pixels and save the information to the occupied array - var occupied = [] - var gx = cgw - var gy, x, y - var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2] - while (gx--) { - gy = cgh - while (gy--) { - y = g - /* eslint no-labels: ["error", { "allowLoop": true }] */ - singleGridLoop: while (y--) { - x = g - while (x--) { - if (imageData[((gy * g + y) * width + - (gx * g + x)) * 4 + 3]) { - occupied.push([gx, gy]) - - if (gx < bounds[3]) { - bounds[3] = gx - } - if (gx > bounds[1]) { - bounds[1] = gx - } - if (gy < bounds[0]) { - bounds[0] = gy - } - if (gy > bounds[2]) { - bounds[2] = gy - } - - if (debug) { - fctx.fillStyle = 'rgba(255, 0, 0, 0.5)' - fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5) - } - break singleGridLoop - } - } - } - if (debug) { - fctx.fillStyle = 'rgba(0, 0, 255, 0.5)' - fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5) - } - } - } - - if (debug) { - fctx.fillStyle = 'rgba(0, 255, 0, 0.5)' - fctx.fillRect( - bounds[3] * g, - bounds[0] * g, - (bounds[1] - bounds[3] + 1) * g, - (bounds[2] - bounds[0] + 1) * g - ) - } - - // Return information needed to create the text on the real canvas - return { - mu: mu, - occupied: occupied, - bounds: bounds, - gw: cgw, - gh: cgh, - fillTextOffsetX: fillTextOffsetX, - fillTextOffsetY: fillTextOffsetY, - fillTextWidth: fw, - fillTextHeight: fh, - fontSize: fontSize - } - } - - /* Determine if there is room available in the given dimension */ - var canFitText = function canFitText (gx, gy, gw, gh, occupied) { - // Go through the occupied points, - // return false if the space is not available. - var i = occupied.length - while (i--) { - var px = gx + occupied[i][0] - var py = gy + occupied[i][1] - - if (px >= ngx || py >= ngy || px < 0 || py < 0) { - if (!settings.drawOutOfBound) { - return false - } - continue - } - - if (!grid[px][py]) { - return false - } - } - return true - } - - /* Actually draw the text on the grid */ - var drawText = function drawText (gx, gy, info, word, weight, distance, theta, rotateDeg, attributes, extraDataArray) { - var fontSize = info.fontSize - var color - if (getTextColor) { - color = getTextColor(word, weight, fontSize, distance, theta, extraDataArray) - } else { - color = settings.color - } - - // get fontWeight that will be used to set ctx.font and font style rule - var fontWeight - if (getTextFontWeight) { - fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray) - } else { - fontWeight = settings.fontWeight - } - - var classes - if (getTextClasses) { - classes = getTextClasses(word, weight, fontSize, extraDataArray) - } else { - classes = settings.classes - } - - elements.forEach(function (el) { - if (el.getContext) { - var ctx = el.getContext('2d') - var mu = info.mu - - // Save the current state before messing it - ctx.save() - ctx.scale(1 / mu, 1 / mu) - - ctx.font = fontWeight + ' ' + - (fontSize * mu).toString(10) + 'px ' + settings.fontFamily - ctx.fillStyle = color - - // Translate the canvas position to the origin coordinate of where - // the text should be put. - ctx.translate( - (gx + info.gw / 2) * g * mu, - (gy + info.gh / 2) * g * mu - ) - - if (rotateDeg !== 0) { - ctx.rotate(-rotateDeg) - } - - // Finally, fill the text. - - // XXX: We cannot because textBaseline = 'top' here because - // Firefox and Chrome uses different default line-height for canvas. - // Please read https://bugzil.la/737852#c6. - // Here, we use textBaseline = 'middle' and draw the text at exactly - // 0.5 * fontSize lower. - ctx.textBaseline = 'middle' - ctx.fillText( - word, info.fillTextOffsetX * mu, - (info.fillTextOffsetY + fontSize * 0.5) * mu - ) - - // The below box is always matches how s are positioned - /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY, - info.fillTextWidth, info.fillTextHeight) */ - - // Restore the state. - ctx.restore() - } else { - // drawText on DIV element - var span = document.createElement('span') - var transformRule = '' - transformRule = 'rotate(' + (-rotateDeg / Math.PI * 180) + 'deg) ' - if (info.mu !== 1) { - transformRule += - 'translateX(-' + (info.fillTextWidth / 4) + 'px) ' + - 'scale(' + (1 / info.mu) + ')' - } - var styleRules = { - position: 'absolute', - display: 'block', - font: fontWeight + ' ' + - (fontSize * info.mu) + 'px ' + settings.fontFamily, - left: ((gx + info.gw / 2) * g + info.fillTextOffsetX) + 'px', - top: ((gy + info.gh / 2) * g + info.fillTextOffsetY) + 'px', - width: info.fillTextWidth + 'px', - height: info.fillTextHeight + 'px', - lineHeight: fontSize + 'px', - whiteSpace: 'nowrap', - transform: transformRule, - webkitTransform: transformRule, - msTransform: transformRule, - transformOrigin: '50% 40%', - webkitTransformOrigin: '50% 40%', - msTransformOrigin: '50% 40%' - } - if (color) { - styleRules.color = color - } - span.textContent = word - for (var cssProp in styleRules) { - span.style[cssProp] = styleRules[cssProp] - } - if (attributes) { - for (var attribute in attributes) { - span.setAttribute(attribute, attributes[attribute]) - } - } - if (classes) { - span.className += classes - } - el.appendChild(span) - } - }) - } - - /* Help function to updateGrid */ - var fillGridAt = function fillGridAt (x, y, drawMask, dimension, item) { - if (x >= ngx || y >= ngy || x < 0 || y < 0) { - return - } - - grid[x][y] = false - - if (drawMask) { - var ctx = elements[0].getContext('2d') - ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth) - } - - if (interactive) { - infoGrid[x][y] = { item: item, dimension: dimension } - } - } - - /* Update the filling information of the given space with occupied points. - Draw the mask on the canvas if necessary. */ - var updateGrid = function updateGrid (gx, gy, gw, gh, info, item) { - var occupied = info.occupied - var drawMask = settings.drawMask - var ctx - if (drawMask) { - ctx = elements[0].getContext('2d') - ctx.save() - ctx.fillStyle = settings.maskColor - } - - var dimension - if (interactive) { - var bounds = info.bounds - dimension = { - x: (gx + bounds[3]) * g, - y: (gy + bounds[0]) * g, - w: (bounds[1] - bounds[3] + 1) * g, - h: (bounds[2] - bounds[0] + 1) * g - } - } - - var i = occupied.length - while (i--) { - var px = gx + occupied[i][0] - var py = gy + occupied[i][1] - - if (px >= ngx || py >= ngy || px < 0 || py < 0) { - continue - } - - fillGridAt(px, py, drawMask, dimension, item) - } - - if (drawMask) { - ctx.restore() - } - } - - /* putWord() processes each item on the list, - calculate it's size and determine it's position, and actually - put it on the canvas. */ - var putWord = function putWord (item) { - var word, weight, attributes - if (Array.isArray(item)) { - word = item[0] - weight = item[1] - } else { - word = item.word - weight = item.weight - attributes = item.attributes - } - var rotateDeg = getRotateDeg() - - var extraDataArray = getItemExtraData(item) - - // get info needed to put the text onto the canvas - var info = getTextInfo(word, weight, rotateDeg, extraDataArray) - - // not getting the info means we shouldn't be drawing this one. - if (!info) { - return false - } - - if (exceedTime()) { - return false - } - - // If drawOutOfBound is set to false, - // skip the loop if we have already know the bounding box of - // word is larger than the canvas. - if (!settings.drawOutOfBound && !settings.shrinkToFit) { - var bounds = info.bounds; - if ((bounds[1] - bounds[3] + 1) > ngx || - (bounds[2] - bounds[0] + 1) > ngy) { - return false - } - } - - // Determine the position to put the text by - // start looking for the nearest points - var r = maxRadius + 1 - - var tryToPutWordAtPoint = function (gxy) { - var gx = Math.floor(gxy[0] - info.gw / 2) - var gy = Math.floor(gxy[1] - info.gh / 2) - var gw = info.gw - var gh = info.gh - - // If we cannot fit the text at this position, return false - // and go to the next position. - if (!canFitText(gx, gy, gw, gh, info.occupied)) { - return false - } - - // Actually put the text on the canvas - drawText(gx, gy, info, word, weight, - (maxRadius - r), gxy[2], rotateDeg, attributes, extraDataArray) - - // Mark the spaces on the grid as filled - updateGrid(gx, gy, gw, gh, info, item) - - // Return true so some() will stop and also return true. - return true - } - - while (r--) { - var points = getPointsAtRadius(maxRadius - r) - - if (settings.shuffle) { - points = [].concat(points) - shuffleArray(points) - } - - // Try to fit the words by looking at each point. - // array.some() will stop and return true - // when putWordAtPoint() returns true. - // If all the points returns false, array.some() returns false. - var drawn = points.some(tryToPutWordAtPoint) - - if (drawn) { - // leave putWord() and return true - return true - } - } - if (settings.shrinkToFit) { - if (Array.isArray(item)) { - item[1] = item[1] * 3 / 4 - } else { - item.weight = item.weight * 3 / 4 - } - return putWord(item) - } - // we tried all distances but text won't fit, return false - return false - } - - /* Send DOM event to all elements. Will stop sending event and return - if the previous one is canceled (for cancelable events). */ - var sendEvent = function sendEvent (type, cancelable, details) { - if (cancelable) { - return !elements.some(function (el) { - var event = new CustomEvent(type, { - detail: details || {} - }) - return !el.dispatchEvent(event) - }, this) - } else { - elements.forEach(function (el) { - var event = new CustomEvent(type, { - detail: details || {} - }) - el.dispatchEvent(event) - }, this) - } - } - - /* Start drawing on a canvas */ - var start = function start () { - // For dimensions, clearCanvas etc., - // we only care about the first element. - var canvas = elements[0] - - if (canvas.getContext) { - ngx = Math.ceil(canvas.width / g) - ngy = Math.ceil(canvas.height / g) - } else { - var rect = canvas.getBoundingClientRect() - ngx = Math.ceil(rect.width / g) - ngy = Math.ceil(rect.height / g) - } - - // Sending a wordcloudstart event which cause the previous loop to stop. - // Do nothing if the event is canceled. - if (!sendEvent('wordcloudstart', true)) { - return - } - - // Determine the center of the word cloud - center = (settings.origin) - ? [settings.origin[0] / g, settings.origin[1] / g] - : [ngx / 2, ngy / 2] - - // Maxium radius to look for space - maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy)) - - /* Clear the canvas only if the clearCanvas is set, - if not, update the grid to the current canvas state */ - grid = [] - - var gx, gy, i - if (!canvas.getContext || settings.clearCanvas) { - elements.forEach(function (el) { - if (el.getContext) { - var ctx = el.getContext('2d') - ctx.fillStyle = settings.backgroundColor - ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1)) - ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1)) - } else { - el.textContent = '' - el.style.backgroundColor = settings.backgroundColor - el.style.position = 'relative' - } - }) - - /* fill the grid with empty state */ - gx = ngx - while (gx--) { - grid[gx] = [] - gy = ngy - while (gy--) { - grid[gx][gy] = true - } - } - } else { - /* Determine bgPixel by creating - another canvas and fill the specified background color. */ - var bctx = document.createElement('canvas').getContext('2d') - - bctx.fillStyle = settings.backgroundColor - bctx.fillRect(0, 0, 1, 1) - var bgPixel = bctx.getImageData(0, 0, 1, 1).data - - /* Read back the pixels of the canvas we got to tell which part of the - canvas is empty. - (no clearCanvas only works with a canvas, not divs) */ - var imageData = - canvas.getContext('2d').getImageData(0, 0, ngx * g, ngy * g).data - - gx = ngx - var x, y - while (gx--) { - grid[gx] = [] - gy = ngy - while (gy--) { - y = g - /* eslint no-labels: ["error", { "allowLoop": true }] */ - singleGridLoop: while (y--) { - x = g - while (x--) { - i = 4 - while (i--) { - if (imageData[((gy * g + y) * ngx * g + - (gx * g + x)) * 4 + i] !== bgPixel[i]) { - grid[gx][gy] = false - break singleGridLoop - } - } - } - } - if (grid[gx][gy] !== false) { - grid[gx][gy] = true - } - } - } - - imageData = bctx = bgPixel = undefined - } - - // fill the infoGrid with empty state if we need it - if (settings.hover || settings.click) { - interactive = true - - /* fill the grid with empty state */ - gx = ngx + 1 - while (gx--) { - infoGrid[gx] = [] - } - - if (settings.hover) { - canvas.addEventListener('mousemove', wordcloudhover) - } - - if (settings.click) { - canvas.addEventListener('click', wordcloudclick) - canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)' - } - - canvas.addEventListener('wordcloudstart', function stopInteraction () { - canvas.removeEventListener('wordcloudstart', stopInteraction) - canvas.removeEventListener('mousemove', wordcloudhover) - canvas.removeEventListener('click', wordcloudclick) - hovered = undefined - }) - } - - i = 0 - var loopingFunction, stoppingFunction - if (settings.wait !== 0) { - loopingFunction = window.setTimeout - stoppingFunction = window.clearTimeout - } else { - loopingFunction = window.setImmediate - stoppingFunction = window.clearImmediate - } - - var addEventListener = function addEventListener (type, listener) { - elements.forEach(function (el) { - el.addEventListener(type, listener) - }, this) - } - - var removeEventListener = function removeEventListener (type, listener) { - elements.forEach(function (el) { - el.removeEventListener(type, listener) - }, this) - } - - var anotherWordCloudStart = function anotherWordCloudStart () { - removeEventListener('wordcloudstart', anotherWordCloudStart) - stoppingFunction(timer[timerId]) - } - - addEventListener('wordcloudstart', anotherWordCloudStart) - timer[timerId] = loopingFunction(function loop () { - if (i >= settings.list.length) { - stoppingFunction(timer[timerId]) - sendEvent('wordcloudstop', false) - removeEventListener('wordcloudstart', anotherWordCloudStart) - delete timer[timerId]; - return - } - escapeTime = (new Date()).getTime() - var drawn = putWord(settings.list[i]) - var canceled = !sendEvent('wordclouddrawn', true, { - item: settings.list[i], - drawn: drawn - }) - if (exceedTime() || canceled) { - stoppingFunction(timer[timerId]) - settings.abort() - sendEvent('wordcloudabort', false) - sendEvent('wordcloudstop', false) - removeEventListener('wordcloudstart', anotherWordCloudStart) - delete timer[timerId] - return - } - i++ - timer[timerId] = loopingFunction(loop, settings.wait) - }, settings.wait) - } - - // All set, start the drawing - start() - } - - WordCloud.isSupported = isSupported - WordCloud.minFontSize = minFontSize - WordCloud.stop = function stop () { - if (timer) { - for (var timerId in timer) { - window.clearImmediate(timer[timerId]) - } - } - } - - // Expose the library as an AMD module - if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef - global.WordCloud = WordCloud - define('wordcloud', [], function () { return WordCloud }) // eslint-disable-line no-undef - } else if (typeof module !== 'undefined' && module.exports) { // eslint-disable-line no-undef - module.exports = WordCloud // eslint-disable-line no-undef - } else { - global.WordCloud = WordCloud - } -})(this) // jshint ignore:line diff --git a/static/plugins/wordcloud/wordcloud2.min.js b/static/plugins/wordcloud/wordcloud2.min.js new file mode 100644 index 00000000..21378350 --- /dev/null +++ b/static/plugins/wordcloud/wordcloud2.min.js @@ -0,0 +1,8 @@ +/*! + * wordcloud2.js + * http://timdream.org/wordcloud2.js/ + * + * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors. + * Released under the MIT license + */ +"use strict";window.setImmediate||(window.setImmediate=window.msSetImmediate||window.webkitSetImmediate||window.mozSetImmediate||window.oSetImmediate||function(){if(!window.postMessage||!window.addEventListener)return null;var t=[void 0],e="zero-timeout-message";return window.addEventListener("message",(function(a){if("string"==typeof a.data&&a.data.substr(0,e.length)===e){a.stopImmediatePropagation();var r=parseInt(a.data.substr(e.length),36);t[r]&&(t[r](),t[r]=void 0)}}),!0),window.clearImmediate=function(e){t[e]&&(t[e]=void 0)},function(a){var r=t.length;return t.push(a),window.postMessage(e+r.toString(36),"*"),r}}()||function(t){window.setTimeout(t,0)}),window.clearImmediate||(window.clearImmediate=window.msClearImmediate||window.webkitClearImmediate||window.mozClearImmediate||window.oClearImmediate||function(t){window.clearTimeout(t)}),function(t){var e=function(){var t=document.createElement("canvas");if(!t||!t.getContext)return!1;var e=t.getContext("2d");return!!e&&(!!e.getImageData&&(!!e.fillText&&(!!Array.prototype.some&&!!Array.prototype.push)))}(),a=function(){if(e){for(var t,a,r=document.createElement("canvas").getContext("2d"),i=20;i;){if(r.font=i.toString(10)+"px sans-serif",r.measureText("W").width===t&&r.measureText("m").width===a)return i+1;t=r.measureText("W").width,a=r.measureText("m").width,i--}return 0}}(),r=function(t){for(var e,a,r=t.length;r;)e=Math.floor(Math.random()*r),a=t[--r],t[r]=t[e],t[e]=a;return t},i={},o=function(t,o){if(e){var n=Math.floor(Math.random()*Date.now());Array.isArray(t)||(t=[t]),t.forEach((function(e,a){if("string"==typeof e){if(t[a]=document.getElementById(e),!t[a])throw new Error("The element id specified is not found.")}else if(!e.tagName&&!e.appendChild)throw new Error("You must pass valid HTML elements, or ID of the element.")}));var s={list:[],fontFamily:'"Trebuchet MS", "Heiti TC", "微軟正黑體", "Arial Unicode MS", "Droid Fallback Sans", sans-serif',fontWeight:"normal",color:"random-dark",minSize:0,weightFactor:1,clearCanvas:!0,backgroundColor:"#fff",gridSize:8,drawOutOfBound:!1,shrinkToFit:!1,origin:null,drawMask:!1,maskColor:"rgba(255,0,0,0.3)",maskGapWidth:.3,wait:0,abortThreshold:0,abort:function(){},minRotation:-Math.PI/2,maxRotation:Math.PI/2,rotationSteps:0,shuffle:!0,rotateRatio:.1,shape:"circle",ellipticity:.65,classes:null,hover:null,click:null};if(o)for(var l in o)l in s&&(s[l]=o[l]);if("function"!=typeof s.weightFactor){var f=s.weightFactor;s.weightFactor=function(t){return t*f}}if("function"!=typeof s.shape)switch(s.shape){case"circle":default:s.shape="circle";break;case"cardioid":s.shape=function(t){return 1-Math.sin(t)};break;case"diamond":s.shape=function(t){var e=t%(2*Math.PI/4);return 1/(Math.cos(e)+Math.sin(e))};break;case"square":s.shape=function(t){return Math.min(1/Math.abs(Math.cos(t)),1/Math.abs(Math.sin(t)))};break;case"triangle-forward":s.shape=function(t){var e=t%(2*Math.PI/3);return 1/(Math.cos(e)+Math.sqrt(3)*Math.sin(e))};break;case"triangle":case"triangle-upright":s.shape=function(t){var e=(t+3*Math.PI/2)%(2*Math.PI/3);return 1/(Math.cos(e)+Math.sqrt(3)*Math.sin(e))};break;case"pentagon":s.shape=function(t){var e=(t+.955)%(2*Math.PI/5);return 1/(Math.cos(e)+.726543*Math.sin(e))};break;case"star":s.shape=function(t){var e=(t+.955)%(2*Math.PI/10);return(t+.955)%(2*Math.PI/5)-2*Math.PI/10>=0?1/(Math.cos(2*Math.PI/10-e)+3.07768*Math.sin(2*Math.PI/10-e)):1/(Math.cos(e)+3.07768*Math.sin(e))}}s.gridSize=Math.max(Math.floor(s.gridSize),4);var d,c,h,u,m,w,g,v,M=s.gridSize,p=M-s.maskGapWidth,x=Math.abs(s.maxRotation-s.minRotation),b=Math.abs(Math.floor(s.rotationSteps)),T=Math.min(s.maxRotation,s.minRotation);switch(s.color){case"random-dark":g=function(){return L(10,50)};break;case"random-light":g=function(){return L(50,90)};break;default:"function"==typeof s.color&&(g=s.color)}"function"==typeof s.fontWeight&&(v=s.fontWeight);var y=null;"function"==typeof s.classes&&(y=s.classes);var I,k=!1,C=[],S=function(t){var e,a,r=t.currentTarget,i=r.getBoundingClientRect();t.touches?(e=t.touches[0].clientX,a=t.touches[0].clientY):(e=t.clientX,a=t.clientY);var o=e-i.left,n=a-i.top,s=Math.floor(o*(r.width/i.width||1)/M),l=Math.floor(n*(r.height/i.height||1)/M);return C[s]?C[s][l]:null},E=function(t){var e=S(t);I!==e&&(I=e,e?s.hover(e.item,e.dimension,t):s.hover(void 0,void 0,t))},F=function(t){var e=S(t);e&&(s.click(e.item,e.dimension,t),t.preventDefault())},P=[],R=function(t){if(P[t])return P[t];var e=8*t,a=e,r=[];for(0===t&&r.push([u[0],u[1],0]);a--;){var i=1;"circle"!==s.shape&&(i=s.shape(a/e*2*Math.PI)),r.push([u[0]+t*i*Math.cos(-a/e*2*Math.PI),u[1]+t*i*Math.sin(-a/e*2*Math.PI)*s.ellipticity,a/e*2*Math.PI])}return P[t]=r,r},O=function(){return s.abortThreshold>0&&(new Date).getTime()-w>s.abortThreshold},A=function(e,a,r,i,o){if(!(e>=c||a>=h||e<0||a<0)){if(d[e][a]=!1,r)t[0].getContext("2d").fillRect(e*M,a*M,p,p);k&&(C[e][a]={item:o,dimension:i})}},z=function e(i){var o,n,l;Array.isArray(i)?(o=i[0],n=i[1]):(o=i.word,n=i.weight,l=i.attributes);var f=0===s.rotateRatio||Math.random()>s.rotateRatio?0:0===x?T:b>0?T+Math.floor(Math.random()*b)*x/(b-1):T+Math.random()*x,u=function(t){if(Array.isArray(t)){var e=t.slice();return e.splice(0,2),e}return[]}(i),w=function(t,e,r,i){var o=s.weightFactor(e);if(o<=s.minSize)return!1;var n,l=1;oR[1]&&(R[1]=P),CR[2]&&(R[2]=C);break t}}return{mu:l,occupied:F,bounds:R,gw:T,gh:b,fillTextOffsetX:p,fillTextOffsetY:x,fillTextWidth:c,fillTextHeight:h,fontSize:o}}(o,n,f,u);if(!w)return!1;if(O())return!1;if(!s.drawOutOfBound&&!s.shrinkToFit){var p=w.bounds;if(p[1]-p[3]+1>c||p[2]-p[0]+1>h)return!1}for(var I=m+1,C=function(e){var a=Math.floor(e[0]-w.gw/2),r=Math.floor(e[1]-w.gh/2);w.gw,w.gh;return!!function(t,e,a,r,i){for(var o=i.length;o--;){var n=t+i[o][0],l=e+i[o][1];if(n>=c||l>=h||n<0||l<0){if(!s.drawOutOfBound)return!1}else if(!d[n][l])return!1}return!0}(a,r,0,0,w.occupied)&&(function(e,a,r,i,o,n,l,f,d,c){var h,u,m,w=r.fontSize;h=g?g(i,o,w,n,l,c):s.color,u=v?v(i,o,w,c):s.fontWeight,m=y?y(i,o,w,c):s.classes,t.forEach((function(t){if(t.getContext){var o=t.getContext("2d"),n=r.mu;o.save(),o.scale(1/n,1/n),o.font=u+" "+(w*n).toString(10)+"px "+s.fontFamily,o.fillStyle=h,o.translate((e+r.gw/2)*M*n,(a+r.gh/2)*M*n),0!==f&&o.rotate(-f),o.textBaseline="middle",o.fillText(i,r.fillTextOffsetX*n,(r.fillTextOffsetY+.5*w)*n),o.restore()}else{var l=document.createElement("span"),c="";c="rotate("+-f/Math.PI*180+"deg) ",1!==r.mu&&(c+="translateX(-"+r.fillTextWidth/4+"px) scale("+1/r.mu+")");var g={position:"absolute",display:"block",font:u+" "+w*r.mu+"px "+s.fontFamily,left:(e+r.gw/2)*M+r.fillTextOffsetX+"px",top:(a+r.gh/2)*M+r.fillTextOffsetY+"px",width:r.fillTextWidth+"px",height:r.fillTextHeight+"px",lineHeight:w+"px",whiteSpace:"nowrap",transform:c,webkitTransform:c,msTransform:c,transformOrigin:"50% 40%",webkitTransformOrigin:"50% 40%",msTransformOrigin:"50% 40%"};for(var v in h&&(g.color=h),l.textContent=i,g)l.style[v]=g[v];if(d)for(var p in d)l.setAttribute(p,d[p]);m&&(l.className+=m),t.appendChild(l)}}))}(a,r,w,o,n,m-I,e[2],f,l,u),function(e,a,r,i,o,n){var l,f,d=o.occupied,u=s.drawMask;if(u&&((l=t[0].getContext("2d")).save(),l.fillStyle=s.maskColor),k){var m=o.bounds;f={x:(e+m[3])*M,y:(a+m[0])*M,w:(m[1]-m[3]+1)*M,h:(m[2]-m[0]+1)*M}}for(var w=d.length;w--;){var g=e+d[w][0],v=a+d[w][1];g>=c||v>=h||g<0||v<0||A(g,v,u,f,n)}u&&l.restore()}(a,r,0,0,w,i),!0)};I--;){var S=R(m-I);if(s.shuffle&&(S=[].concat(S),r(S)),S.some(C))return!0}return!!s.shrinkToFit&&(Array.isArray(i)?i[1]=3*i[1]/4:i.weight=3*i.weight/4,e(i))},W=function(e,a,r){if(a)return!t.some((function(t){var a=new CustomEvent(e,{detail:r||{}});return!t.dispatchEvent(a)}),this);t.forEach((function(t){var a=new CustomEvent(e,{detail:r||{}});t.dispatchEvent(a)}),this)};!function(){var e=t[0];if(e.getContext)c=Math.ceil(e.width/M),h=Math.ceil(e.height/M);else{var a=e.getBoundingClientRect();c=Math.ceil(a.width/M),h=Math.ceil(a.height/M)}if(W("wordcloudstart",!0)){var r,o,l,f,g;if(u=s.origin?[s.origin[0]/M,s.origin[1]/M]:[c/2,h/2],m=Math.floor(Math.sqrt(c*c+h*h)),d=[],!e.getContext||s.clearCanvas)for(t.forEach((function(t){if(t.getContext){var e=t.getContext("2d");e.fillStyle=s.backgroundColor,e.clearRect(0,0,c*(M+1),h*(M+1)),e.fillRect(0,0,c*(M+1),h*(M+1))}else t.textContent="",t.style.backgroundColor=s.backgroundColor,t.style.position="relative"})),r=c;r--;)for(d[r]=[],o=h;o--;)d[r][o]=!0;else{var v=document.createElement("canvas").getContext("2d");v.fillStyle=s.backgroundColor,v.fillRect(0,0,1,1);var p,x,b=v.getImageData(0,0,1,1).data,T=e.getContext("2d").getImageData(0,0,c*M,h*M).data;for(r=c;r--;)for(d[r]=[],o=h;o--;){x=M;t:for(;x--;)for(p=M;p--;)for(l=4;l--;)if(T[4*((o*M+x)*c*M+(r*M+p))+l]!==b[l]){d[r][o]=!1;break t}!1!==d[r][o]&&(d[r][o]=!0)}T=v=b=void 0}if(s.hover||s.click){for(k=!0,r=c+1;r--;)C[r]=[];s.hover&&e.addEventListener("mousemove",E),s.click&&(e.addEventListener("click",F),e.style.webkitTapHighlightColor="rgba(0, 0, 0, 0)"),e.addEventListener("wordcloudstart",(function t(){e.removeEventListener("wordcloudstart",t),e.removeEventListener("mousemove",E),e.removeEventListener("click",F),I=void 0}))}l=0,0!==s.wait?(f=window.setTimeout,g=window.clearTimeout):(f=window.setImmediate,g=window.clearImmediate);var y=function(e,a){t.forEach((function(t){t.removeEventListener(e,a)}),this)},S=function t(){y("wordcloudstart",t),g(i[n])};!function(e,a){t.forEach((function(t){t.addEventListener(e,a)}),this)}("wordcloudstart",S),i[n]=f((function t(){if(l>=s.list.length)return g(i[n]),W("wordcloudstop",!1),y("wordcloudstart",S),void delete i[n];w=(new Date).getTime();var e=z(s.list[l]),a=!W("wordclouddrawn",!0,{item:s.list[l],drawn:e});if(O()||a)return g(i[n]),s.abort(),W("wordcloudabort",!1),W("wordcloudstop",!1),y("wordcloudstart",S),void delete i[n];l++,i[n]=f(t,s.wait)}),s.wait)}}()}function L(t,e){return"hsl("+(360*Math.random()).toFixed()+","+(30*Math.random()+70).toFixed()+"%,"+(Math.random()*(e-t)+t).toFixed()+"%)"}};o.isSupported=e,o.minFontSize=a,o.stop=function(){if(i)for(var t in i)window.clearImmediate(i[t])},"function"==typeof define&&define.amd?(t.WordCloud=o,define("wordcloud",[],(function(){return o}))):"undefined"!=typeof module&&module.exports?module.exports=o:t.WordCloud=o}(this); \ No newline at end of file