From 038dfb8c5d825a661da6c8fad312afdf3afea62e Mon Sep 17 00:00:00 2001 From: Dipsy Kapoor Date: Tue, 27 Jan 2015 13:55:33 -0800 Subject: [PATCH 1/9] Added wordle component that generate wordle for first page --- client/app/app.less | 1 + client/app/search/search.html | 4 + .../components/wordle/scripts/wordcloud2.js | 1108 +++++++++++++++++ client/components/wordle/scripts/wordfreq.js | 195 +++ .../wordle/scripts/wordfreq.worker.js | 346 +++++ .../wordle/wordle-chart.directive.js | 87 ++ .../wordle/wordle-chart.directive.spec.js | 21 + client/components/wordle/wordle-chart.html | 6 + client/components/wordle/wordle-chart.less | 37 + client/index.html | 5 + 10 files changed, 1810 insertions(+) create mode 100644 client/components/wordle/scripts/wordcloud2.js create mode 100644 client/components/wordle/scripts/wordfreq.js create mode 100644 client/components/wordle/scripts/wordfreq.worker.js create mode 100644 client/components/wordle/wordle-chart.directive.js create mode 100644 client/components/wordle/wordle-chart.directive.spec.js create mode 100644 client/components/wordle/wordle-chart.html create mode 100644 client/components/wordle/wordle-chart.less diff --git a/client/app/app.less b/client/app/app.less index 6530250..ae5956d 100644 --- a/client/app/app.less +++ b/client/app/app.less @@ -388,4 +388,5 @@ label{ @import 'collapse-filter/collapse-filter.less'; @import 'date-histogram-aggregation/date-histogram-aggregation.less'; @import 'modal/modal.less'; +@import 'wordle/wordle-chart.less'; // endinjector \ No newline at end of file diff --git a/client/app/search/search.html b/client/app/search/search.html index c0f24a2..5475a0f 100644 --- a/client/app/search/search.html +++ b/client/app/search/search.html @@ -80,6 +80,10 @@
{{facets.simFilter.title}} --> + +
diff --git a/client/components/wordle/scripts/wordcloud2.js b/client/components/wordle/scripts/wordcloud2.js new file mode 100644 index 0000000..cd12623 --- /dev/null +++ b/client/components/wordle/scripts/wordcloud2.js @@ -0,0 +1,1108 @@ +/*! + * wordcloud2.js + * http://timdream.org/wordcloud2.js/ + * + * Copyright 2011 - 2013 Tim Chien + * 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.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 miniumFontSize = (function getMiniumFontSize() { + 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; + })(); + + // 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 WordCloud = function WordCloud(elements, options) { + if (!isSupported) { + return; + } + + if (!Array.isArray(elements)) { + elements = [elements]; + } + + elements.forEach(function(el, i) { + if (typeof el === 'string') { + elements[i] = document.getElementById(el); + if (!elements[i]) { + throw 'The element id specified is not found.'; + } + } else if (!el.tagName && !el.appendChild) { + throw '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, + 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, + + shuffle: true, + rotateRatio: 0.1, + + shape: 'circle', + ellipticity: 0.65, + + 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': + case 'square': + // 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 '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 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; + switch (settings.color) { + case 'random-dark': + getTextColor = function getRandomDarkColor() { + return 'rgb(' + + Math.floor(Math.random() * 128).toString(10) + ',' + + Math.floor(Math.random() * 128).toString(10) + ',' + + Math.floor(Math.random() * 128).toString(10) + ')'; + }; + break; + + case 'random-light': + getTextColor = function getRandomLightColor() { + return 'rgb(' + + Math.floor(Math.random() * 128 + 128).toString(10) + ',' + + Math.floor(Math.random() * 128 + 128).toString(10) + ',' + + Math.floor(Math.random() * 128 + 128).toString(10) + ')'; + }; + break; + + default: + if (typeof settings.color === 'function') { + getTextColor = settings.color; + } + break; + } + + /* Interactive */ + var interactive = false; + var infoGrid = []; + var hovered; + + var getInfoGridFromMouseEvent = function getInfoGridFromMouseEvent(evt) { + var canvas = evt.currentTarget; + var rect = canvas.getBoundingClientRect(); + var eventX = evt.clientX - rect.left; + var eventY = evt.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); + + return infoGrid[x][y]; + }; + + var wordcloudhover = function wordcloudhover(evt) { + var info = getInfoGridFromMouseEvent(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 = getInfoGridFromMouseEvent(evt); + if (!info) { + return; + } + + settings.click(info.item, info.dimension, evt); + }; + + /* 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; + } + + return minRotation + Math.random() * rotationRange; + }; + + var getTextInfo = function getTextInfo(word, weight, rotateDeg) { + // 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 < miniumFontSize) { + mu = (function calculateScaleFactor() { + var mu = 2; + while (mu * fontSize < miniumFontSize) { + mu += 2; + } + return mu; + })(); + } + + var fcanvas = document.createElement('canvas'); + var fctx = fcanvas.getContext('2d', { willReadFrequently: true }); + + fctx.font = settings.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 = settings.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, gy, x, y; + var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2]; + while (gx--) { + gy = cgh; + while (gy--) { + y = g; + 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 || !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) { + + var fontSize = info.fontSize; + var color; + if (getTextColor) { + color = getTextColor(word, weight, fontSize, distance, theta); + } else { + color = settings.color; + } + + var dimension; + 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 + }; + + 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 = settings.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': settings.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', + 'color': color, + 'lineHeight': fontSize + 'px', + 'whiteSpace': 'nowrap', + 'transform': transformRule, + 'webkitTransform': transformRule, + 'msTransform': transformRule, + 'transformOrigin': '50% 40%', + 'webkitTransformOrigin': '50% 40%', + 'msTransformOrigin': '50% 40%' + }; + 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]); + } + } + 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--) { + fillGridAt(gx + occupied[i][0], gy + occupied[i][1], + 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(); + + // get info needed to put the text onto the canvas + var info = getTextInfo(word, weight, rotateDeg); + + // not getting the info means we shouldn't be drawing this one. + if (!info) { + return false; + } + + if (exceedTime()) { + return false; + } + + // Skip the loop if we have already know the bounding box of + // word is larger than the canvas. + 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); + + // 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; + } + } + // 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, detail) { + if (cancelable) { + return !elements.some(function(el) { + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(type, true, cancelable, detail || {}); + return !el.dispatchEvent(evt); + }, this); + } else { + elements.forEach(function(el) { + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(type, true, cancelable, detail || {}); + el.dispatchEvent(evt); + }, 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.floor(canvas.width / g); + ngy = Math.floor(canvas.height / g); + } else { + var rect = canvas.getBoundingClientRect(); + ngx = Math.floor(rect.width / g); + ngy = Math.floor(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; + } + }); + + /* 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; + 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.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); + }; + + addEventListener('wordcloudstart', anotherWordCloudStart); + + var timer = loopingFunction(function loop() { + if (i >= settings.list.length) { + stoppingFunction(timer); + sendEvent('wordcloudstop', false); + removeEventListener('wordcloudstart', anotherWordCloudStart); + + 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); + settings.abort(); + sendEvent('wordcloudabort', false); + sendEvent('wordcloudstop', false); + removeEventListener('wordcloudstart', anotherWordCloudStart); + return; + } + i++; + timer = loopingFunction(loop, settings.wait); + }, settings.wait); + }; + + // All set, start the drawing + start(); + }; + + WordCloud.isSupported = isSupported; + WordCloud.miniumFontSize = miniumFontSize; + + // Expose the library as an AMD module + if (typeof global.define === 'function' && global.define.amd) { + global.define('wordcloud', [], function() { return WordCloud; }); + } else { + global.WordCloud = WordCloud; + } + +})(window); diff --git a/client/components/wordle/scripts/wordfreq.js b/client/components/wordle/scripts/wordfreq.js new file mode 100644 index 0000000..f00c7b7 --- /dev/null +++ b/client/components/wordle/scripts/wordfreq.js @@ -0,0 +1,195 @@ +/*! wordfreq - Text corpus calculation in Javascript. + + Author: timdream + +*/ + +'use strict'; + +(function (global) { + +var WordFreq = function WordFreq(options) { + // Public API object + var wordfreq = {}; + + // options: here, we only worry about workerUrl + options = options || {}; + options.workerUrl = options.workerUrl || 'wordfreq.worker.js'; + + // worker reference will be put here by init(); + var worker; + + // message queue + // Note: since Javascript Web Workers itself is a single threaded + // first-in-first-out event queue, the management here + // (send the next message only till the current one is processed) + // seems to be an overkill. This should be written if we are sure of + // the behavior is reliable. + var messageQueue = []; + var message = null; + + // closed flag means the worker is terminated. + var closed = false; + + // add message to queue; if there is no ongoing message, + // start sending the message. + var addQueue = function addQueue(msg) { + messageQueue.push(msg); + + if (!message) + sendMessage(); + }; + // send message to worker + var sendMessage = function sendMessage() { + message = messageQueue.shift(); + + worker.postMessage({ + method: message.method, + params: message.params + }); + + // Remove thing that is not needed anymore. + delete message.method; + delete message.params; + }; + + // process message received from worker + var gotMessage = function gotMessage(evt) { + // unset message reference first + // so callback cannot be called twice in stop(), + // and doing init() in the callback() will not result overwritting + // message. + var callback = message.callback; + message = null; + + if (callback) + callback.call(wordfreq, evt.data); + // Set the message to null, since we have finished processing + if (messageQueue.length) + sendMessage(); + }; + + var gotError = function gotError(evt) { + // Detach callback with global message reference + // so it cannot be called twice in stop() + var callback = message.callback; + delete message.callback; + + if (callback) + callback.call(wordfreq, evt.data); + // Set the message to null, since we have finished processing + message = null; + if (messageQueue.length) + sendMessage(); + }; + + var methods = ['process', 'empty', 'getList', 'getLength', 'getVolume']; + methods.forEach(function buildAPI(method) { + wordfreq[method] = function addMessage() { + if (closed) + return; + + var argLength = arguments.length; + + var callback; + if (typeof arguments[arguments.length - 1] === 'function') { + callback = arguments[arguments.length - 1]; + // exclude the callback from being put into params. + argLength--; + } + + var params = []; + var i = 0; + while (i < argLength) { + params[i] = arguments[i]; + i++; + } + + addQueue({ + method: method, + params: params, + callback: callback + }); + + return wordfreq; + }; + }); + + // uninit + var uninit = function uninit() { + // set closed flag + closed = true; + + // terminate the worker + worker.terminate(); + + // unattach functions + worker.onmessage = null; + worker.onerror = null; + worker = null; + }; + + // stop + wordfreq.stop = function stop(triggerCallbacks) { + if (closed) + return; + + uninit(); + + if (!triggerCallbacks) { + message = null; + messageQueue = []; + + return wordfreq; + } + + // tell all pending callbacks that the work has stopped + if (message && message.callback) + message.callback.call(wordfreq); + message = null; + while (messageQueue.length) { + var msg = messageQueue.shift(); + if (msg.callback) + msg.callback.call(wordfreq); + } + + return wordfreq; + }; + + // initialize the web workers + var init = function init() { + // start the worker + worker = new Worker(options.workerUrl); + + // Attach the handlers + worker.onmessage = gotMessage; + worker.onerror = gotError; + + // init the worker with option data + addQueue({ + method: 'init', + params: [options] + }); + }; + + // all set, initialize! + init(); + + return wordfreq; +}; + +WordFreq.isSupported = !!(global.Worker && + Array.prototype.push && + Array.prototype.indexOf && + Array.prototype.forEach && + Array.isArray && + Object.create); + +// Expose the library as an AMD module +if (typeof define === 'function' && define.amd) { + define('wordfreq', [], function() { return WordFreq; }); +} else { + global.WordFreq = WordFreq; +} + +})(this); diff --git a/client/components/wordle/scripts/wordfreq.worker.js b/client/components/wordle/scripts/wordfreq.worker.js new file mode 100644 index 0000000..54daeb3 --- /dev/null +++ b/client/components/wordle/scripts/wordfreq.worker.js @@ -0,0 +1,346 @@ +/*! wordfreq - Text corpus calculation in Javascript. + + Author: timdream + + This file contains the following library (unmodified but minified): + http://tartarus.org/~martin/PorterStemmer/js.txt + // Porter stemmer in Javascript + // Release 1 be 'andargor', Jul 2004 + // Release 2 (substantially revised) by Christopher McKenzie, Aug 2009 + +*/ + +'use strict'; + +(function (global) { + +// http://tartarus.org/~martin/PorterStemmer/js.txt +// Porter stemmer in Javascript +// Release 1 be 'andargor', Jul 2004 +// Release 2 (substantially revised) by Christopher McKenzie, Aug 2009 +var stemmer=function(){var g={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},h={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""};return function(a){var d,b,e,c,f;if(a.length<3)return a;e=a.substr(0,1);if(e=="y")a=e.toUpperCase()+a.substr(1);c=/^(.+?)(ss|i)es$/;b=/^(.+?)([^s])s$/; if(c.test(a))a=a.replace(c,"$1$2");else if(b.test(a))a=a.replace(b,"$1$2");c=/^(.+?)eed$/;b=/^(.+?)(ed|ing)$/;if(c.test(a)){b=c.exec(a);c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(b[1])){c=/.$/;a=a.replace(c,"")}}else if(b.test(a)){b=b.exec(a);d=b[1];b=/^([^aeiou][^aeiouy]*)?[aeiouy]/;if(b.test(d)){a=d;b=/(at|bl|iz)$/;f=/([^aeiouylsz])\1$/;d=/^[^aeiou][^aeiouy]*[aeiouy][^aeiouwxy]$/;if(b.test(a))a+="e";else if(f.test(a)){c=/.$/;a=a.replace(c,"")}else if(d.test(a))a+="e"}}c= /^(.+?)y$/;if(c.test(a)){b=c.exec(a);d=b[1];c=/^([^aeiou][^aeiouy]*)?[aeiouy]/;if(c.test(d))a=d+"i"}c=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;if(c.test(a)){b=c.exec(a);d=b[1];b=b[2];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(d))a=d+g[b]}c=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;if(c.test(a)){b=c.exec(a);d=b[1];b=b[2];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*/; if(c.test(d))a=d+h[b]}c=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;b=/^(.+?)(s|t)(ion)$/;if(c.test(a)){b=c.exec(a);d=b[1];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(d))a=d}else if(b.test(a)){b=b.exec(a);d=b[1]+b[2];b=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(b.test(d))a=d}c=/^(.+?)e$/;if(c.test(a)){b=c.exec(a);d=b[1];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/; b=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*([aeiouy][aeiou]*)?$/;f=/^[^aeiou][^aeiouy]*[aeiouy][^aeiouwxy]$/;if(c.test(d)||b.test(d)&&!f.test(d))a=d}c=/ll$/;b=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(a)&&b.test(a)){c=/.$/;a=a.replace(c,"")}if(e=="y")a=e.toLowerCase()+a.substr(1);return a}}(); + +var WordFreqSync = function WordFreqSync(options) { + var list = []; + var terms = Object.create(null); + + options = options || {}; + + // fill some defaults + options.languages = options.languages || ['chinese', 'english']; + options.stopWordSets = options.stopWordSets || ['cjk', 'english1', 'english2']; + + if (!Array.isArray(options.stopWords)) { + options.stopWords = []; + } + + options.stopWordSets.forEach(function (stopWordSet) { + switch (stopWordSet) { + case 'cjk': + options.stopWords = options.stopWords.concat([ + '\u3092', /* Japanese wo */ + '\u3067\u3059', /* Japanese desu */ + '\u3059\u308b', /* Japanese suru */ + '\u306e', /* Japanese no */ + '\u308c\u3089' /* Japanese rera */]); + + break; + + case 'english1': + options.stopWords = options.stopWords.concat([ + 'i','a','about', 'an','and','are','as','at', + 'be', 'been','by','com','for', 'from','how','in', + 'is','it','not', 'of','on','or','that', + 'the','this','to','was', 'what','when','where', 'which', + 'who','will','with', 'www','the','
','br']); + break; + + case 'english2': + options.stopWords = options.stopWords.concat([ + 'we', 'us', 'our', 'ours', + 'they', 'them', 'their', 'he', 'him', 'his', + 'she', 'her', 'hers', 'it', 'its', 'you', 'yours', 'your', + 'has', 'have', 'would', 'could', 'should', 'shall', + 'can', 'may', 'if', 'then', 'else', 'but', + 'there', 'these', 'those']); + break; + } + }); + + if (typeof options.filterSubstring !== 'boolean') + options.filterSubstring = true; + + options.maxiumPhraseLength = options.maxiumPhraseLength || 8; + options.minimumCount = options.minimumCount || 2; + + + // Return all possible substrings of a give string in an array + // If there is no maxLength is unrestricted, array will contain + // (2 * str.length) substrings. + var getAllSubStrings = function getAllSubStrings(str, maxLength) { + if (!str.length) + return []; + + var result = []; + var i = Math.min(str.length, maxLength); + do { + result.push(str.substr(0,i)); + } while (--i); + + if (str.length > 1) + result = result.concat(getAllSubStrings(str.substr(1), maxLength)); + + return result; + } + + return { + process: function process(text) { + if (typeof text !== 'string') + throw 'You need to supply text for processing.' + + // English + if (options.languages.indexOf('english') !== -1) { + // For English, we count "stems" instead of words, + // and decide how to represent that stem at the end + // according to the counts. + var stems = Object.create(null); + + // say bye bye to characters that is not belongs to a word + var words = text.split(/[^A-Za-zéÉ'’_\-0-9@\.]+/); + + var stopWords = options.stopWords; + + words.forEach(function (word) { + word = word + .replace(/\.+/g, '.') // replace multiple full stops + .replace(/(.{3,})\.$/g, '$1') // replace single trailing stop + .replace(/n[\'’]t\b/ig, '') // get rid of ~n't + .replace(/[\'’](s|ll|d|ve)?\b/ig, ''); // get rid of ’ and ' + + // skip if the word is shorter than two characters + // (i.e. exactly one letter) + if (!word || word.length < 2) + return; + + // that's not a word unless it contains at least an alphabet + if (/^[0-9\.@\-]+$/.test(word)) + return; + + // skip if this is a stop word + if (stopWords.indexOf(word.toLowerCase()) !== -1) + return; + + var stem = stemmer(word).toLowerCase(); + + // count++ for the stem + if (!(stem in stems)) + stems[stem] = { count: 0, word: word}; + stems[stem].count++; + + // if the current word representing the stem is longer than + // this one, use this word instead (booking -> book) + if (word.length < stems[stem].word.length) + stems[stem].word = word; + + // if the current word representing the stem is of the same + // length but with different form, + // use the lower-case representation (Book -> book) + if (word.length === stems[stem].word.length && + word !== stems[stem].word) + stems[stem].word = word.toLowerCase(); + }); + + // Push each "stem" into terms as word + for (var stem in stems) { + var term = stems[stem].word; + if (!(term in terms)) { + terms[term] = stems[stem].count; + } else { + terms[term] += stems[stem].count; + } + } + + stems = undefined; + } + + // Chinese + if (options.languages.indexOf('chinese') !== -1) { + // Chinese is a language without word boundary. + // We must use N-gram here to extract meaningful terms. + + // say good bye to non-Chinese (Kanji) characters + // TBD: Cannot match CJK characters beyond BMP, + // e.g. \u20000-\u2A6DF at plane B. + + // Han: \u4E00-\u9FFF\u3400-\u4DBF + // Kana: \u3041-\u309f\u30a0-\u30ff + var regexp = /[^\u4E00-\u9FFF\u3400-\u4DBF]+/g; + text = text.replace(regexp, '\n'); + + // Use the stop words as separators -- replace them. + options.stopWords.forEach(function (stopWord) { + // Not handling that stop word if it's not a Chinese word. + if (!(/^[\u4E00-\u9FFF\u3400-\u4DBF]+$/).test(stopWord)) + return; + + text = text.replace(new RegExp(stopWord, 'g'), '$1\n'); + }); + + var chunks = text.split(/\n+/); + + var pendingTerms = Object.create(null); + + // counts all the chunks (and it's substrings) in pendingTerms + chunks.forEach(function processChunk(chunk) { + if (chunk.length <= 1) + return; + + var substrings = getAllSubStrings(chunk, options.maxiumPhraseLength); + substrings.forEach(function (substr) { + if (substr.length <= 1) + return; + + if (!(substr in pendingTerms)) + pendingTerms[substr] = 0; + + pendingTerms[substr]++; + }); + }); + + // if filterSubstring is true, remove the substrings with the exact + // same count as the longer term (implying they are only present in + // the longer terms) + if (options.filterSubstring) { + for (var term in pendingTerms) { + var substrings = getAllSubStrings(term, options.maxiumPhraseLength); + substrings.forEach(function (substr) { + if (term === substr) + return; + + if ((substr in pendingTerms) && + pendingTerms[substr] === pendingTerms[term]) { + delete pendingTerms[substr]; + } + }); + } + } + + // add the pendingTerms into terms + for (var term in pendingTerms) { + if (!(term in terms)) + terms[term] = 0; + + terms[term] += pendingTerms[term]; + } + + pendingTerms = undefined; + } + + // Update the list + list = []; + for (var term in terms) { + if (terms[term] < options.minimumCount) + continue; + + list.push([term, terms[term]]); + } + list = list.sort(function (a, b) { + if (a[1] > b[1]) return -1; + if (a[1] < b[1]) return 1; + if (a[0] === b[0]) + return 0; + var t = ([a[0], b[0]]).sort(); + if (t[0] !== a[0]) return 1; + return -1; + }); + + return list; + }, + + empty: function empty() { + list = []; + terms = Object.create(null); + return true; + }, + + getList: function getList() { + return list; + }, + + getLength: function getLength() { + return list.length; + }, + + getVolume: function getVolume() { + var volume = 0; + list.forEach(function eachListItem(item) { + volume += item[0].length * Math.pow(item[1], 2); + }); + + return volume; + }, + + stop: function stop() { /* Nothing to do. */ } + }; +}; + +WordFreqSync.isSupported = !!(Array.prototype.push && + Array.prototype.indexOf && + Array.prototype.forEach && + Array.isArray && + Object.create); + +if (typeof define === 'function' && define.amd) { + // Expose the library as an AMD module + define('wordfreqsync', [], function() { return WordFreqSync; }); +} else if (typeof module === 'object' && global === module.exports) { + // export WordFreqSync in node.js + // note that |global| here refer to |this| of the script execution context, + // not |global| in node.js + module.exports = WordFreqSync; + + if (require.main === module) { + console.error('Error: WordFreqSync is not meant to be executed directly.'); + } +} else { + // expose WordFreqSync as a global interface. + global.WordFreqSync = WordFreqSync; + + /* If those conditions are met, assume we are in Web Workers. + * Web Workers script that importScripts() us must have their onmessage + * handler set-up first. + * 1. the global object must have a self property. + * 2. the global object must NOT have an window property that is assigned to + * itself. + * 3. there are two Web Workers method exists under 'self'. + * + * XXX: I would love to check (self === global) but it would fail on IE10. + */ + if (('self' in global) && + !(('window' in global) && window === global) && + !self.onmessage && self.postMessage && self.importScripts) { + var wordfreqsync; + self.onmessage = function gotMessage(evt) { + var msg = evt.data; + var method = msg.method; + var params = msg.params; + + if (method === 'init') { + wordfreqsync = WordFreqSync.apply(global, params); + self.postMessage(true); + return; + } + + if (!wordfreqsync) + throw 'You must init to create an instance first'; + + if (!method || !wordfreqsync[method]) + throw 'No such method'; + + var result = wordfreqsync[method].apply(wordfreqsync, params); + self.postMessage(result); + }; + } +} + +})(this); diff --git a/client/components/wordle/wordle-chart.directive.js b/client/components/wordle/wordle-chart.directive.js new file mode 100644 index 0000000..8811b5e --- /dev/null +++ b/client/components/wordle/wordle-chart.directive.js @@ -0,0 +1,87 @@ +'use strict'; + +angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeout) { + return { + templateUrl: 'components/wordle/wordle-chart.html', + restrict: 'E', + scope: { + chartData: '@', + indexVM: '=indexvm', + ejs: '=', + filters: '=' + }, + link: function ($scope, element) { + //var margin = {top: 0, right: 0, bottom: 0, left: 0}; + // var x = d3.scale. + $scope.chartEl = $(element).find('.wordle-chart')[0]; + $scope.chart = null; + $scope.htmlCanvas = null; + + $scope.drawWordle = function(options) { + var htmlCanvas = $("
"); + $($scope.chartEl).empty(); + $($scope.chartEl).append(htmlCanvas); + $scope.htmlCanvas = $($scope.chartEl).find('.wordle-html-canvas')[0]; + WordCloud([$scope.chartEl, $scope.htmlCanvas], options); + $scope.chart = $($scope.chartEl); + }; + + $scope.render = function(data) { + if(data) { + var bodytext=''; + data.forEach(function (r){ + if(r._source) { + if(r._source.hasAbstractPart) { + bodytext += r._source.hasAbstractPart.text; + } + } + }); + console.log("Got bodyText:" + bodytext); + var wordFrequency = { + workerUrl: 'components/wordle/scripts/wordfreq.worker.js' + }; + wordFrequency.languages=["english"]; + wordFrequency.stopWordSets =["english1"]; + wordFrequency.maxiumPhraseLength = 15; + wordFrequency.minimumCount=1; + // Initialize and run process() function + var wordfreq = WordFreq(wordFrequency).process(bodytext, function (list) { + // console.log the list returned in this callback. + //console.log(list); + var options={}; + options.list = list; + options.gridSize = 20; + options.weightFactor=8; + options.fontFamily = 'Times, serif'; + //options.color = function (word, weight) { + // return (weight === 12) ? "#f0222" : "#c09292"; + // } + options.color = 'random-dark'; //random-light'; + options.backgroundColor = "None"; + options.rotateRatio=0.5; + //options.origin= [90, 0]; + //console.log("options2:" +options2); + + $scope.drawWordle(options); + }); + } + }; + + $scope.$watch('indexVM.results.hits', function() { + var data = $scope.$eval('indexVM.results.hits.' + $scope.chartData); + $scope.render(data); + $timeout(function() { + // TODO: Consider replacing with something more element. + // Defer a resize on initial render to later in the update cycle to force the chart size to + // update after the page has rendered. This avoids sizing issues on initial render since + // most of the page elements have not been built and the area available to fill is of + // size 0 on the initial render or in flux. + if ($scope.chart) { + $scope.chart.resize({height:100}); + } + }); + }, true); + + } + }; +}]); \ No newline at end of file diff --git a/client/components/wordle/wordle-chart.directive.spec.js b/client/components/wordle/wordle-chart.directive.spec.js new file mode 100644 index 0000000..d8b8792 --- /dev/null +++ b/client/components/wordle/wordle-chart.directive.spec.js @@ -0,0 +1,21 @@ +'use strict'; + +describe('Directive: wordleChart', function () { + + // load the directive's module and view + beforeEach(module('digApp')); + beforeEach(module('components/wordle/wordle-chart.html')); + + var element, scope; + + beforeEach(inject(function ($rootScope) { + scope = $rootScope.$new(); + })); + + it('should make hidden element visible', inject(function ($compile) { + element = angular.element(''); + element = $compile(element)(scope); + scope.$apply(); + expect(element.text()).toBe('this is the wordleChart directive'); + })); +}); \ No newline at end of file diff --git a/client/components/wordle/wordle-chart.html b/client/components/wordle/wordle-chart.html new file mode 100644 index 0000000..e4633b7 --- /dev/null +++ b/client/components/wordle/wordle-chart.html @@ -0,0 +1,6 @@ +
+
+
+
+
\ No newline at end of file diff --git a/client/components/wordle/wordle-chart.less b/client/components/wordle/wordle-chart.less new file mode 100644 index 0000000..42ca2f4 --- /dev/null +++ b/client/components/wordle/wordle-chart.less @@ -0,0 +1,37 @@ +.wordle-chart .tick:first-of-type text { + text-anchor: start !important; +} + +.wordle-chart .tick:nth-of-type(5) text { + text-anchor: end !important; +} + +.wordle-chart { + overflow-x: visible; + overflow-y: visible; + position: relative; + margin-top: 20px; + margin-bottom: 20px; + min-height: 150px; +} + +.wordle-canvas { + display: block; + position: relative; + overflow: hidden; + } + + .wordle-canvas.hide { + display: none; + } + + .wordle-html-canvas > span { + transition: text-shadow 1s ease, opacity 1s ease; + -webkit-transition: text-shadow 1s ease, opacity 1s ease; + -ms-transition: text-shadow 1s ease, opacity 1s ease; + } + + .wordle-html-canvas > span:hover { + text-shadow: 0 0 10px, 0 0 10px #fff, 0 0 10px #fff, 0 0 10px #fff; + opacity: 0.5; + } \ No newline at end of file diff --git a/client/index.html b/client/index.html index 213605a..677d95d 100644 --- a/client/index.html +++ b/client/index.html @@ -83,6 +83,11 @@ + + + + + From 61b2a856d3425ec8276e9bc2195dbace3de1729f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 29 Jan 2015 18:15:27 +0000 Subject: [PATCH 2/9] Connect to dig-mrs-latest --- client/app/search/search.list.details.html | 2 +- client/app/search/search.list.html | 14 +++++++------- client/index.html | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/client/app/search/search.list.details.html b/client/app/search/search.list.details.html index cfc5e47..0ce5cc0 100644 --- a/client/app/search/search.list.details.html +++ b/client/app/search/search.list.details.html @@ -76,4 +76,4 @@

{{doc._sou

- \ No newline at end of file + diff --git a/client/app/search/search.list.html b/client/app/search/search.list.html index 025d304..4eb2c3c 100644 --- a/client/app/search/search.list.html +++ b/client/app/search/search.list.html @@ -30,11 +30,11 @@

  • --> -

    diff --git a/client/components/wordle/scripts/wordfreq.js b/client/components/wordle/scripts/wordfreq.js deleted file mode 100644 index f00c7b7..0000000 --- a/client/components/wordle/scripts/wordfreq.js +++ /dev/null @@ -1,195 +0,0 @@ -/*! wordfreq - Text corpus calculation in Javascript. - - Author: timdream - -*/ - -'use strict'; - -(function (global) { - -var WordFreq = function WordFreq(options) { - // Public API object - var wordfreq = {}; - - // options: here, we only worry about workerUrl - options = options || {}; - options.workerUrl = options.workerUrl || 'wordfreq.worker.js'; - - // worker reference will be put here by init(); - var worker; - - // message queue - // Note: since Javascript Web Workers itself is a single threaded - // first-in-first-out event queue, the management here - // (send the next message only till the current one is processed) - // seems to be an overkill. This should be written if we are sure of - // the behavior is reliable. - var messageQueue = []; - var message = null; - - // closed flag means the worker is terminated. - var closed = false; - - // add message to queue; if there is no ongoing message, - // start sending the message. - var addQueue = function addQueue(msg) { - messageQueue.push(msg); - - if (!message) - sendMessage(); - }; - // send message to worker - var sendMessage = function sendMessage() { - message = messageQueue.shift(); - - worker.postMessage({ - method: message.method, - params: message.params - }); - - // Remove thing that is not needed anymore. - delete message.method; - delete message.params; - }; - - // process message received from worker - var gotMessage = function gotMessage(evt) { - // unset message reference first - // so callback cannot be called twice in stop(), - // and doing init() in the callback() will not result overwritting - // message. - var callback = message.callback; - message = null; - - if (callback) - callback.call(wordfreq, evt.data); - // Set the message to null, since we have finished processing - if (messageQueue.length) - sendMessage(); - }; - - var gotError = function gotError(evt) { - // Detach callback with global message reference - // so it cannot be called twice in stop() - var callback = message.callback; - delete message.callback; - - if (callback) - callback.call(wordfreq, evt.data); - // Set the message to null, since we have finished processing - message = null; - if (messageQueue.length) - sendMessage(); - }; - - var methods = ['process', 'empty', 'getList', 'getLength', 'getVolume']; - methods.forEach(function buildAPI(method) { - wordfreq[method] = function addMessage() { - if (closed) - return; - - var argLength = arguments.length; - - var callback; - if (typeof arguments[arguments.length - 1] === 'function') { - callback = arguments[arguments.length - 1]; - // exclude the callback from being put into params. - argLength--; - } - - var params = []; - var i = 0; - while (i < argLength) { - params[i] = arguments[i]; - i++; - } - - addQueue({ - method: method, - params: params, - callback: callback - }); - - return wordfreq; - }; - }); - - // uninit - var uninit = function uninit() { - // set closed flag - closed = true; - - // terminate the worker - worker.terminate(); - - // unattach functions - worker.onmessage = null; - worker.onerror = null; - worker = null; - }; - - // stop - wordfreq.stop = function stop(triggerCallbacks) { - if (closed) - return; - - uninit(); - - if (!triggerCallbacks) { - message = null; - messageQueue = []; - - return wordfreq; - } - - // tell all pending callbacks that the work has stopped - if (message && message.callback) - message.callback.call(wordfreq); - message = null; - while (messageQueue.length) { - var msg = messageQueue.shift(); - if (msg.callback) - msg.callback.call(wordfreq); - } - - return wordfreq; - }; - - // initialize the web workers - var init = function init() { - // start the worker - worker = new Worker(options.workerUrl); - - // Attach the handlers - worker.onmessage = gotMessage; - worker.onerror = gotError; - - // init the worker with option data - addQueue({ - method: 'init', - params: [options] - }); - }; - - // all set, initialize! - init(); - - return wordfreq; -}; - -WordFreq.isSupported = !!(global.Worker && - Array.prototype.push && - Array.prototype.indexOf && - Array.prototype.forEach && - Array.isArray && - Object.create); - -// Expose the library as an AMD module -if (typeof define === 'function' && define.amd) { - define('wordfreq', [], function() { return WordFreq; }); -} else { - global.WordFreq = WordFreq; -} - -})(this); diff --git a/client/components/wordle/scripts/wordfreq.worker.js b/client/components/wordle/scripts/wordfreq.worker.js deleted file mode 100644 index 54daeb3..0000000 --- a/client/components/wordle/scripts/wordfreq.worker.js +++ /dev/null @@ -1,346 +0,0 @@ -/*! wordfreq - Text corpus calculation in Javascript. - - Author: timdream - - This file contains the following library (unmodified but minified): - http://tartarus.org/~martin/PorterStemmer/js.txt - // Porter stemmer in Javascript - // Release 1 be 'andargor', Jul 2004 - // Release 2 (substantially revised) by Christopher McKenzie, Aug 2009 - -*/ - -'use strict'; - -(function (global) { - -// http://tartarus.org/~martin/PorterStemmer/js.txt -// Porter stemmer in Javascript -// Release 1 be 'andargor', Jul 2004 -// Release 2 (substantially revised) by Christopher McKenzie, Aug 2009 -var stemmer=function(){var g={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},h={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""};return function(a){var d,b,e,c,f;if(a.length<3)return a;e=a.substr(0,1);if(e=="y")a=e.toUpperCase()+a.substr(1);c=/^(.+?)(ss|i)es$/;b=/^(.+?)([^s])s$/; if(c.test(a))a=a.replace(c,"$1$2");else if(b.test(a))a=a.replace(b,"$1$2");c=/^(.+?)eed$/;b=/^(.+?)(ed|ing)$/;if(c.test(a)){b=c.exec(a);c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(b[1])){c=/.$/;a=a.replace(c,"")}}else if(b.test(a)){b=b.exec(a);d=b[1];b=/^([^aeiou][^aeiouy]*)?[aeiouy]/;if(b.test(d)){a=d;b=/(at|bl|iz)$/;f=/([^aeiouylsz])\1$/;d=/^[^aeiou][^aeiouy]*[aeiouy][^aeiouwxy]$/;if(b.test(a))a+="e";else if(f.test(a)){c=/.$/;a=a.replace(c,"")}else if(d.test(a))a+="e"}}c= /^(.+?)y$/;if(c.test(a)){b=c.exec(a);d=b[1];c=/^([^aeiou][^aeiouy]*)?[aeiouy]/;if(c.test(d))a=d+"i"}c=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;if(c.test(a)){b=c.exec(a);d=b[1];b=b[2];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(d))a=d+g[b]}c=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;if(c.test(a)){b=c.exec(a);d=b[1];b=b[2];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*/; if(c.test(d))a=d+h[b]}c=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;b=/^(.+?)(s|t)(ion)$/;if(c.test(a)){b=c.exec(a);d=b[1];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(d))a=d}else if(b.test(a)){b=b.exec(a);d=b[1]+b[2];b=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(b.test(d))a=d}c=/^(.+?)e$/;if(c.test(a)){b=c.exec(a);d=b[1];c=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/; b=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*([aeiouy][aeiou]*)?$/;f=/^[^aeiou][^aeiouy]*[aeiouy][^aeiouwxy]$/;if(c.test(d)||b.test(d)&&!f.test(d))a=d}c=/ll$/;b=/^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*/;if(c.test(a)&&b.test(a)){c=/.$/;a=a.replace(c,"")}if(e=="y")a=e.toLowerCase()+a.substr(1);return a}}(); - -var WordFreqSync = function WordFreqSync(options) { - var list = []; - var terms = Object.create(null); - - options = options || {}; - - // fill some defaults - options.languages = options.languages || ['chinese', 'english']; - options.stopWordSets = options.stopWordSets || ['cjk', 'english1', 'english2']; - - if (!Array.isArray(options.stopWords)) { - options.stopWords = []; - } - - options.stopWordSets.forEach(function (stopWordSet) { - switch (stopWordSet) { - case 'cjk': - options.stopWords = options.stopWords.concat([ - '\u3092', /* Japanese wo */ - '\u3067\u3059', /* Japanese desu */ - '\u3059\u308b', /* Japanese suru */ - '\u306e', /* Japanese no */ - '\u308c\u3089' /* Japanese rera */]); - - break; - - case 'english1': - options.stopWords = options.stopWords.concat([ - 'i','a','about', 'an','and','are','as','at', - 'be', 'been','by','com','for', 'from','how','in', - 'is','it','not', 'of','on','or','that', - 'the','this','to','was', 'what','when','where', 'which', - 'who','will','with', 'www','the','
    ','br']); - break; - - case 'english2': - options.stopWords = options.stopWords.concat([ - 'we', 'us', 'our', 'ours', - 'they', 'them', 'their', 'he', 'him', 'his', - 'she', 'her', 'hers', 'it', 'its', 'you', 'yours', 'your', - 'has', 'have', 'would', 'could', 'should', 'shall', - 'can', 'may', 'if', 'then', 'else', 'but', - 'there', 'these', 'those']); - break; - } - }); - - if (typeof options.filterSubstring !== 'boolean') - options.filterSubstring = true; - - options.maxiumPhraseLength = options.maxiumPhraseLength || 8; - options.minimumCount = options.minimumCount || 2; - - - // Return all possible substrings of a give string in an array - // If there is no maxLength is unrestricted, array will contain - // (2 * str.length) substrings. - var getAllSubStrings = function getAllSubStrings(str, maxLength) { - if (!str.length) - return []; - - var result = []; - var i = Math.min(str.length, maxLength); - do { - result.push(str.substr(0,i)); - } while (--i); - - if (str.length > 1) - result = result.concat(getAllSubStrings(str.substr(1), maxLength)); - - return result; - } - - return { - process: function process(text) { - if (typeof text !== 'string') - throw 'You need to supply text for processing.' - - // English - if (options.languages.indexOf('english') !== -1) { - // For English, we count "stems" instead of words, - // and decide how to represent that stem at the end - // according to the counts. - var stems = Object.create(null); - - // say bye bye to characters that is not belongs to a word - var words = text.split(/[^A-Za-zéÉ'’_\-0-9@\.]+/); - - var stopWords = options.stopWords; - - words.forEach(function (word) { - word = word - .replace(/\.+/g, '.') // replace multiple full stops - .replace(/(.{3,})\.$/g, '$1') // replace single trailing stop - .replace(/n[\'’]t\b/ig, '') // get rid of ~n't - .replace(/[\'’](s|ll|d|ve)?\b/ig, ''); // get rid of ’ and ' - - // skip if the word is shorter than two characters - // (i.e. exactly one letter) - if (!word || word.length < 2) - return; - - // that's not a word unless it contains at least an alphabet - if (/^[0-9\.@\-]+$/.test(word)) - return; - - // skip if this is a stop word - if (stopWords.indexOf(word.toLowerCase()) !== -1) - return; - - var stem = stemmer(word).toLowerCase(); - - // count++ for the stem - if (!(stem in stems)) - stems[stem] = { count: 0, word: word}; - stems[stem].count++; - - // if the current word representing the stem is longer than - // this one, use this word instead (booking -> book) - if (word.length < stems[stem].word.length) - stems[stem].word = word; - - // if the current word representing the stem is of the same - // length but with different form, - // use the lower-case representation (Book -> book) - if (word.length === stems[stem].word.length && - word !== stems[stem].word) - stems[stem].word = word.toLowerCase(); - }); - - // Push each "stem" into terms as word - for (var stem in stems) { - var term = stems[stem].word; - if (!(term in terms)) { - terms[term] = stems[stem].count; - } else { - terms[term] += stems[stem].count; - } - } - - stems = undefined; - } - - // Chinese - if (options.languages.indexOf('chinese') !== -1) { - // Chinese is a language without word boundary. - // We must use N-gram here to extract meaningful terms. - - // say good bye to non-Chinese (Kanji) characters - // TBD: Cannot match CJK characters beyond BMP, - // e.g. \u20000-\u2A6DF at plane B. - - // Han: \u4E00-\u9FFF\u3400-\u4DBF - // Kana: \u3041-\u309f\u30a0-\u30ff - var regexp = /[^\u4E00-\u9FFF\u3400-\u4DBF]+/g; - text = text.replace(regexp, '\n'); - - // Use the stop words as separators -- replace them. - options.stopWords.forEach(function (stopWord) { - // Not handling that stop word if it's not a Chinese word. - if (!(/^[\u4E00-\u9FFF\u3400-\u4DBF]+$/).test(stopWord)) - return; - - text = text.replace(new RegExp(stopWord, 'g'), '$1\n'); - }); - - var chunks = text.split(/\n+/); - - var pendingTerms = Object.create(null); - - // counts all the chunks (and it's substrings) in pendingTerms - chunks.forEach(function processChunk(chunk) { - if (chunk.length <= 1) - return; - - var substrings = getAllSubStrings(chunk, options.maxiumPhraseLength); - substrings.forEach(function (substr) { - if (substr.length <= 1) - return; - - if (!(substr in pendingTerms)) - pendingTerms[substr] = 0; - - pendingTerms[substr]++; - }); - }); - - // if filterSubstring is true, remove the substrings with the exact - // same count as the longer term (implying they are only present in - // the longer terms) - if (options.filterSubstring) { - for (var term in pendingTerms) { - var substrings = getAllSubStrings(term, options.maxiumPhraseLength); - substrings.forEach(function (substr) { - if (term === substr) - return; - - if ((substr in pendingTerms) && - pendingTerms[substr] === pendingTerms[term]) { - delete pendingTerms[substr]; - } - }); - } - } - - // add the pendingTerms into terms - for (var term in pendingTerms) { - if (!(term in terms)) - terms[term] = 0; - - terms[term] += pendingTerms[term]; - } - - pendingTerms = undefined; - } - - // Update the list - list = []; - for (var term in terms) { - if (terms[term] < options.minimumCount) - continue; - - list.push([term, terms[term]]); - } - list = list.sort(function (a, b) { - if (a[1] > b[1]) return -1; - if (a[1] < b[1]) return 1; - if (a[0] === b[0]) - return 0; - var t = ([a[0], b[0]]).sort(); - if (t[0] !== a[0]) return 1; - return -1; - }); - - return list; - }, - - empty: function empty() { - list = []; - terms = Object.create(null); - return true; - }, - - getList: function getList() { - return list; - }, - - getLength: function getLength() { - return list.length; - }, - - getVolume: function getVolume() { - var volume = 0; - list.forEach(function eachListItem(item) { - volume += item[0].length * Math.pow(item[1], 2); - }); - - return volume; - }, - - stop: function stop() { /* Nothing to do. */ } - }; -}; - -WordFreqSync.isSupported = !!(Array.prototype.push && - Array.prototype.indexOf && - Array.prototype.forEach && - Array.isArray && - Object.create); - -if (typeof define === 'function' && define.amd) { - // Expose the library as an AMD module - define('wordfreqsync', [], function() { return WordFreqSync; }); -} else if (typeof module === 'object' && global === module.exports) { - // export WordFreqSync in node.js - // note that |global| here refer to |this| of the script execution context, - // not |global| in node.js - module.exports = WordFreqSync; - - if (require.main === module) { - console.error('Error: WordFreqSync is not meant to be executed directly.'); - } -} else { - // expose WordFreqSync as a global interface. - global.WordFreqSync = WordFreqSync; - - /* If those conditions are met, assume we are in Web Workers. - * Web Workers script that importScripts() us must have their onmessage - * handler set-up first. - * 1. the global object must have a self property. - * 2. the global object must NOT have an window property that is assigned to - * itself. - * 3. there are two Web Workers method exists under 'self'. - * - * XXX: I would love to check (self === global) but it would fail on IE10. - */ - if (('self' in global) && - !(('window' in global) && window === global) && - !self.onmessage && self.postMessage && self.importScripts) { - var wordfreqsync; - self.onmessage = function gotMessage(evt) { - var msg = evt.data; - var method = msg.method; - var params = msg.params; - - if (method === 'init') { - wordfreqsync = WordFreqSync.apply(global, params); - self.postMessage(true); - return; - } - - if (!wordfreqsync) - throw 'You must init to create an instance first'; - - if (!method || !wordfreqsync[method]) - throw 'No such method'; - - var result = wordfreqsync[method].apply(wordfreqsync, params); - self.postMessage(result); - }; - } -} - -})(this); diff --git a/client/components/wordle/wordle-chart.directive.js b/client/components/wordle/wordle-chart.directive.js index 8811b5e..b69c04d 100644 --- a/client/components/wordle/wordle-chart.directive.js +++ b/client/components/wordle/wordle-chart.directive.js @@ -5,14 +5,14 @@ angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeou templateUrl: 'components/wordle/wordle-chart.html', restrict: 'E', scope: { - chartData: '@', + aggregationName: '@', + aggregationSize: '@', + aggregationKey: '@', indexVM: '=indexvm', ejs: '=', filters: '=' }, link: function ($scope, element) { - //var margin = {top: 0, right: 0, bottom: 0, left: 0}; - // var x = d3.scale. $scope.chartEl = $(element).find('.wordle-chart')[0]; $scope.chart = null; $scope.htmlCanvas = null; @@ -22,53 +22,70 @@ angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeou $($scope.chartEl).empty(); $($scope.chartEl).append(htmlCanvas); $scope.htmlCanvas = $($scope.chartEl).find('.wordle-html-canvas')[0]; - WordCloud([$scope.chartEl, $scope.htmlCanvas], options); - $scope.chart = $($scope.chartEl); + $timeout(function() { + WordCloud([$scope.chartEl, $scope.htmlCanvas], options); + $scope.chart = $($scope.chartEl); + }); }; - $scope.render = function(data) { - if(data) { - var bodytext=''; - data.forEach(function (r){ - if(r._source) { - if(r._source.hasAbstractPart) { - bodytext += r._source.hasAbstractPart.text; - } + var formatData = function(data) { + var list = []; + var stopwords = ['i','a','about', 'an','and','are','as','at', + 'be', 'been','by','com','for', 'from','how','in', + 'is','it','not', 'of','on','or','that', + 'the','this','to','was', 'what','when','where', 'which', + 'who','will','with', 'www','the','
    ','br', + 'u','me','you','your','my','we', 'have','am', 'can', 'were', 'than','also'] + + if(data.buckets.length > 0) { + + data.buckets.forEach(function(item) { + var row = []; + if($.inArray(item.key, stopwords) == -1) { + row.push(item.key); + row.push(item.doc_count); + list.push(row); + //console.log(row[0] + ":" + row[1]); + } }); - console.log("Got bodyText:" + bodytext); - var wordFrequency = { - workerUrl: 'components/wordle/scripts/wordfreq.worker.js' + } + return list; + }; + + $scope.render = function(data) { + if(data) { + var list = formatData(data); + + var options={}; + options.list = list; + options.gridSize = 15; + + var multiplier = 15; + if(list.length > 0) { + multiplier = Math.min(multiplier, 20 / Math.log(list[0][1])) + } + options.weightFactor=function(size) { + return multiplier * Math.sqrt(size); }; - wordFrequency.languages=["english"]; - wordFrequency.stopWordSets =["english1"]; - wordFrequency.maxiumPhraseLength = 15; - wordFrequency.minimumCount=1; - // Initialize and run process() function - var wordfreq = WordFreq(wordFrequency).process(bodytext, function (list) { - // console.log the list returned in this callback. - //console.log(list); - var options={}; - options.list = list; - options.gridSize = 20; - options.weightFactor=8; - options.fontFamily = 'Times, serif'; - //options.color = function (word, weight) { - // return (weight === 12) ? "#f0222" : "#c09292"; - // } - options.color = 'random-dark'; //random-light'; - options.backgroundColor = "None"; - options.rotateRatio=0.5; - //options.origin= [90, 0]; - //console.log("options2:" +options2); - - $scope.drawWordle(options); - }); + + options.fontFamily = 'Times, serif'; + options.color = 'random-dark'; //random-light'; + options.backgroundColor = "None"; + options.rotateRatio=0.5; + //options.wait = 2; + options.abortThreshold = 500; + options.abort = function() { + console.log("Wordle draw aborted..was taking too long"); + } + //options.origin= [90, 0]; + $scope.drawWordle(options); } }; - $scope.$watch('indexVM.results.hits', function() { - var data = $scope.$eval('indexVM.results.hits.' + $scope.chartData); + $scope.$watch('indexVM.results.aggregations', function() { + var data = $scope.$eval('indexVM.results.aggregations.' + $scope.aggregationName + + ' || indexVM.results.aggregations.filtered_' + $scope.aggregationName + '.' + $scope.aggregationName); $scope.render(data); $timeout(function() { // TODO: Consider replacing with something more element. diff --git a/client/components/wordle/wordle-chart.html b/client/components/wordle/wordle-chart.html index e4633b7..bd7bf5c 100644 --- a/client/components/wordle/wordle-chart.html +++ b/client/components/wordle/wordle-chart.html @@ -1,5 +1,5 @@
    + eui-aggregation="ejs.TermsAggregation(aggregationName).field(aggregationKey).size(aggregationSize)" eui-filter-self="false">
    diff --git a/client/index.html b/client/index.html index b196aa6..da798b8 100644 --- a/client/index.html +++ b/client/index.html @@ -84,8 +84,6 @@ - - From e9e22fbddb957de637ef460b07d76d0c7eda20e5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 2 Feb 2015 18:07:29 +0000 Subject: [PATCH 5/9] Modifier multiplier to have a max value, connect to karma-dig --- client/components/wordle/wordle-chart.directive.js | 11 +++++++++-- server/config/environment/index.js | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/components/wordle/wordle-chart.directive.js b/client/components/wordle/wordle-chart.directive.js index b69c04d..2eda3a1 100644 --- a/client/components/wordle/wordle-chart.directive.js +++ b/client/components/wordle/wordle-chart.directive.js @@ -63,9 +63,16 @@ angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeou var multiplier = 15; if(list.length > 0) { - multiplier = Math.min(multiplier, 20 / Math.log(list[0][1])) + var size = list[0][1]; + multiplier = Math.min(multiplier, 20 / Math.log(size)) + + var sqrt = Math.sqrt(size); + var first = multiplier * sqrt; + if(first > 80) + multiplier = 80/sqrt; } options.weightFactor=function(size) { + //console.log("mult:" + multiplier + ", sqrt(size):" + Math.sqrt(size) + "=" + multiplier * Math.sqrt(size)); return multiplier * Math.sqrt(size); }; @@ -101,4 +108,4 @@ angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeou } }; -}]); \ No newline at end of file +}]); diff --git a/server/config/environment/index.js b/server/config/environment/index.js index e18d303..8ffd981 100644 --- a/server/config/environment/index.js +++ b/server/config/environment/index.js @@ -41,8 +41,8 @@ var all = { } }, - euiServerUrl: process.env.EUI_SERVER_URL || 'http://localhost', - euiServerPort: process.env.EUI_SERVER_PORT || 9200, + euiServerUrl: process.env.EUI_SERVER_URL || 'karma-dig-service.cloudapp.net', //'http://localhost', + euiServerPort: process.env.EUI_SERVER_PORT || 55310, //9200, euiSearchIndex: process.env.EUI_SEARCH_INDEX || 'dig-mrs-latest', imageSimUrl: process.env.IMAGE_SIM_URL || 'http://localhost', From e9a449e5fd673345360318bb609a995eaa54bb3f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 3 Feb 2015 17:40:12 +0000 Subject: [PATCH 6/9] Change max size to 70 --- client/components/wordle/wordle-chart.directive.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/components/wordle/wordle-chart.directive.js b/client/components/wordle/wordle-chart.directive.js index 2eda3a1..0781dd3 100644 --- a/client/components/wordle/wordle-chart.directive.js +++ b/client/components/wordle/wordle-chart.directive.js @@ -68,8 +68,9 @@ angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeou var sqrt = Math.sqrt(size); var first = multiplier * sqrt; - if(first > 80) - multiplier = 80/sqrt; + if(first > 70) + multiplier = 70/sqrt; + console.log("mult:" + multiplier + ", sqrt(size):" + sqrt + "=" + multiplier * sqrt); } options.weightFactor=function(size) { //console.log("mult:" + multiplier + ", sqrt(size):" + Math.sqrt(size) + "=" + multiplier * Math.sqrt(size)); From e1d0c67240099f2c4149561ff76613c13702a9e0 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 27 Feb 2015 20:38:48 +0000 Subject: [PATCH 7/9] Add DOI to dig-mrs-latest --- server/config/environment/index.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/server/config/environment/index.js b/server/config/environment/index.js index 8ffd981..0588449 100644 --- a/server/config/environment/index.js +++ b/server/config/environment/index.js @@ -412,11 +412,19 @@ var all = { title: 'Affiliation', field: 'doc._source.hasFeatureCollection.affiliation_country_feature.affiliation_country || doc._source.hasFeatureCollection.affiliation_country_feature[0].affiliation_country', classes: 'location' - }], + }, { + title: 'DOI', + field: 'doc._source.digitalObjectIdentifier', + classes: 'location' + }], "full": { "1": { classes: 'listing-details', - fields: [{ + fields: [ + { + title: 'DOI', + field: 'doc._source.digitalObjectIdentifier' + },{ title: 'Authors(s)', field: 'doc._source.hasFeatureCollection.author_feature.author', featureArray: 'doc._source.hasFeatureCollection.author_feature', @@ -434,7 +442,8 @@ var all = { },{ title: 'Abstract', field: "doc['_source']['hasAbstractPart']['text']" - }] + } + ] } } }, @@ -446,7 +455,10 @@ var all = { title: 'Date', field: "doc._source.dateCreated | date:'MM/dd/yyyy HH:mm:ss'", classes: 'date' - },{ + }, { + title: 'DOI', + field: "doc._source.digitalObjectIdentifier", + }, { title: 'Authors(s)', field: 'doc._source.hasFeatureCollection.author_feature.author', featureArray: 'doc._source.hasFeatureCollection.author_feature', From ec0866eb40a2f19164f1ca149073ba3038266238 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 10 Apr 2015 18:16:55 +0000 Subject: [PATCH 8/9] updated config for mrs-patents --- server/config/environment/index.js | 85 +++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/server/config/environment/index.js b/server/config/environment/index.js index 0588449..0e5ea5c 100644 --- a/server/config/environment/index.js +++ b/server/config/environment/index.js @@ -367,7 +367,12 @@ var all = { euiFilters :[], //simFilter: {}, aggFilters: [{ - title: 'Author', + title: 'Provider', + type: 'eui-aggregation', + field: 'provider_agg', + terms: 'hasFeatureCollection.provider_name_feature.provider_name', + }, { + title: 'Author', type: 'eui-aggregation', field: 'author_agg', terms: 'hasFeatureCollection.author_feature.author', @@ -390,7 +395,23 @@ var all = { field: 'compound_agg', terms: 'hasFeatureCollection.compound_feature.compound', count: 20 - }] + }, { + title: 'Organization', + type: 'eui-aggregation', + field: 'organization_agg', + terms: 'hasFeatureCollection.organization_feature.organization', + }, { + title: 'Country', + type: 'eui-aggregation', + field: 'country_agg', + terms: 'hasFeatureCollection.wipo_country_feature.wipo_country_code', + },{ + title: 'Keywords', + type: 'eui-aggregation', + field: 'keywords_agg', + terms: 'hasFeatureCollection.keywords_feature.keywords', + } + ] }, listFields: { @@ -402,8 +423,8 @@ var all = { }], "short": [{ title: 'Date', - field: "doc._source.dateCreated | date:'MM/dd/yyyy HH:mm:ss'", - classes: 'date' + field: "doc._source.dateCreated || doc._source.datePublished | date:'MM/dd/yyyy HH:mm:ss'", + classes: 'date', },{ title: 'Author', field: 'doc._source.hasFeatureCollection.author_feature.author || doc._source.hasFeatureCollection.author_feature[0].author', @@ -416,7 +437,22 @@ var all = { title: 'DOI', field: 'doc._source.digitalObjectIdentifier', classes: 'location' - }], + }, { + title: 'Provider', + field: 'doc._source.hasFeatureCollection.provider_name_feature.provider_name', + classes: 'location' + }, { + title: 'Country', + field: 'doc._source.hasFeatureCollection.wipo_country_feature.wipo_country_code', + classes: 'location', + hideIfMissing: true + },{ + title: 'Organization', + field: 'doc._source.hasFeatureCollection.organization_feature.organization || doc._source.hasFeatureCollection.organization_feature[0].organization', + classes: 'location', + hideIfMissing: true + } + ], "full": { "1": { classes: 'listing-details', @@ -473,10 +509,45 @@ var all = { field: 'doc._source.hasFeatureCollection.compound_feature.compound', featureArray: 'doc._source.hasFeatureCollection.compound_feature', featureValue: 'compound' - },{ + }, { + title: 'Country', + field: "doc._source.hasFeatureCollection.wipo_country_feature.wipo_country_code", + hideIfMissing: true + }, { + title: 'Organization(s)', + field: 'doc._source.hasFeatureCollection.organization_feature.organization', + featureArray: 'doc._source.hasFeatureCollection.organization_feature', + featureValue: 'organization', + hideIfMissing: true + }, { title: 'Abstract', field: "doc['_source']['hasAbstractPart']['text']" - }] + }, { + title: 'Keyword(s)', + field: 'doc._source.hasFeatureCollection.keywords_feature.keywords', + featureArray: 'doc._source.hasFeatureCollection.keywords_feature', + featureValue: 'keywords', + hideIfMissing: true + }, { + title: 'Reference(s)', + field: 'doc._source.isCitationOf.hasIdentifier.label', + featureArray: 'doc._source.isCitationOf', + featureValue: 'hasIdentifier.label', + hideIfMissing: true + }, { + title: 'Citation(s)', + field: 'doc._source.citation.hasIdentifier.label', + featureArray: 'doc._source.citation', + featureValue: 'hasIdentifier.label', + hideIfMissing: true + },{ + title: 'Full Text', + field: 'doc._source.hasBodyPart.text', + featureArray: 'doc._source.hasBodyPart', + featureValue: 'text', + hideIfMissing: true + } + ] } } } From 20c72062ab2bdb1c8a755a7f44bf0e097e48c011 Mon Sep 17 00:00:00 2001 From: Dipsy Kapoor Date: Fri, 10 Apr 2015 12:17:19 -0700 Subject: [PATCH 9/9] fix issues from the pull request --- .../search-results.partial.html | 4 + client/app/search/search.html | 14 +- .../wordle/wordle-chart.directive.js | 10 +- server/config/environment/index.js | 121 +++++++++--------- 4 files changed, 72 insertions(+), 77 deletions(-) diff --git a/client/app/search/search-results/search-results.partial.html b/client/app/search/search-results/search-results.partial.html index b580fd3..5ee8168 100644 --- a/client/app/search/search-results/search-results.partial.html +++ b/client/app/search/search-results/search-results.partial.html @@ -50,6 +50,10 @@
    {{facets.simFilter.title}} --> + +
    -
    -
    - - - -
    + +
    -
    diff --git a/client/components/wordle/wordle-chart.directive.js b/client/components/wordle/wordle-chart.directive.js index 0781dd3..9cc1d40 100644 --- a/client/components/wordle/wordle-chart.directive.js +++ b/client/components/wordle/wordle-chart.directive.js @@ -31,17 +31,17 @@ angular.module('digApp').directive('wordleChart', ['$timeout', function ($timeou var formatData = function(data) { var list = []; var stopwords = ['i','a','about', 'an','and','are','as','at', - 'be', 'been','by','com','for', 'from','how','in', - 'is','it','not', 'of','on','or','that', - 'the','this','to','was', 'what','when','where', 'which', - 'who','will','with', 'www','the','
    ','br', + 'be', 'been','by','com','for', 'from','how','in', 'so','use', + 'is','it','not', 'of','on','or','that', 'self', 'their', 'such', + 'the','this','to','was', 'what','when','where', 'which', 'has', + 'who','will','with', 'www','the','
    ','br', 'no', 'both', 'due', 'up', 'more', 'u','me','you','your','my','we', 'have','am', 'can', 'were', 'than','also'] if(data.buckets.length > 0) { data.buckets.forEach(function(item) { var row = []; - if($.inArray(item.key, stopwords) == -1) { + if($.inArray(item.key, stopwords) == -1 && item.key.length > 1) { row.push(item.key); row.push(item.doc_count); list.push(row); diff --git a/server/config/environment/index.js b/server/config/environment/index.js index 245b31e..57d2c53 100644 --- a/server/config/environment/index.js +++ b/server/config/environment/index.js @@ -43,8 +43,8 @@ var all = { }, appVersion: pjson.version, - euiServerUrl: process.env.EUI_SERVER_URL || 'karma-dig-service.cloudapp.net', //'http://localhost', - euiServerPort: process.env.EUI_SERVER_PORT || 55310, //9200, + euiServerUrl: process.env.EUI_SERVER_URL || 'karma-dig-service.cloudapp.net', + euiServerPort: process.env.EUI_SERVER_PORT || 55310, euiSearchIndex: process.env.EUI_SEARCH_INDEX || 'dig-mrs-latest', imageSimUrl: process.env.IMAGE_SIM_URL || 'http://localhost', @@ -434,8 +434,8 @@ var all = { type: 'eui-aggregation', field: 'provider_agg', terms: 'hasFeatureCollection.provider_name_feature.provider_name', - }, { - title: 'Author', + }, { + title: 'Author', type: 'eui-aggregation', field: 'author_agg', terms: 'hasFeatureCollection.author_feature.author', @@ -463,18 +463,17 @@ var all = { type: 'eui-aggregation', field: 'organization_agg', terms: 'hasFeatureCollection.organization_feature.organization', - }, { + }, { title: 'Country', type: 'eui-aggregation', field: 'country_agg', terms: 'hasFeatureCollection.wipo_country_feature.wipo_country_code', - },{ + },{ title: 'Keywords', type: 'eui-aggregation', field: 'keywords_agg', terms: 'hasFeatureCollection.keywords_feature.keywords', - } - ] + }] }, listFields: { @@ -487,7 +486,7 @@ var all = { short: [{ title: 'Date', field: "doc._source.dateCreated || doc._source.datePublished | date:'MM/dd/yyyy HH:mm:ss UTC'", - classes: 'date', + classes: 'date' },{ title: 'Author', field: 'doc._source.hasFeatureCollection.author_feature.author || doc._source.hasFeatureCollection.author_feature[0].author', @@ -496,34 +495,11 @@ var all = { title: 'Affiliation', field: 'doc._source.hasFeatureCollection.affiliation_country_feature.affiliation_country || doc._source.hasFeatureCollection.affiliation_country_feature[0].affiliation_country', classes: 'location' - }, { - title: 'DOI', - field: 'doc._source.digitalObjectIdentifier', - classes: 'location' - }, { - title: 'Provider', - field: 'doc._source.hasFeatureCollection.provider_name_feature.provider_name', - classes: 'location' - }, { - title: 'Country', - field: 'doc._source.hasFeatureCollection.wipo_country_feature.wipo_country_code', - classes: 'location', - hideIfMissing: true - },{ - title: 'Organization', - field: 'doc._source.hasFeatureCollection.organization_feature.organization || doc._source.hasFeatureCollection.organization_feature[0].organization', - classes: 'location', - hideIfMissing: true - } - ], - "full": { + }], + full: { "1": { classes: 'listing-details', - fields: [ - { - title: 'DOI', - field: 'doc._source.digitalObjectIdentifier' - },{ + fields: [{ title: 'Authors(s)', field: 'doc._source.hasFeatureCollection.author_feature.author', featureArray: 'doc._source.hasFeatureCollection.author_feature', @@ -538,9 +514,31 @@ var all = { field: 'doc._source.hasFeatureCollection.compound_feature.compound', featureArray: 'doc._source.hasFeatureCollection.compound_feature', featureValue: 'compound' + }, { + title: 'DOI', + field: 'doc._source.digitalObjectIdentifier', + classes: 'location' + }, { + title: 'Provider', + field: 'doc._source.hasFeatureCollection.provider_name_feature.provider_name', + classes: 'location' + }, { + title: 'Country', + field: 'doc._source.hasFeatureCollection.wipo_country_feature.wipo_country_code', + classes: 'location', + hideIfMissing: true + },{ + title: 'Organization', + field: 'doc._source.hasFeatureCollection.organization_feature.organization || doc._source.hasFeatureCollection.organization_feature[0].organization', + classes: 'location', + hideIfMissing: true },{ title: 'Abstract', field: "doc['_source']['hasAbstractPart']['text']" + },{ + title: 'Date', + field: "doc._source.dateCreated || doc._source.datePublished | date:'MM/dd/yyyy HH:mm:ss UTC'" + }] } } }, @@ -550,12 +548,9 @@ var all = { classes: 'listing-details', fields: [{ title: 'Date', - field: "doc._source.dateCreated | date:'MM/dd/yyyy HH:mm:ss UTC'", + field: "doc._source.dateCreated || doc._source.datePublished | date:'MM/dd/yyyy HH:mm:ss UTC'", classes: 'date' - }, { - title: 'DOI', - field: "doc._source.digitalObjectIdentifier", - }, { + },{ title: 'Authors(s)', field: 'doc._source.hasFeatureCollection.author_feature.author', featureArray: 'doc._source.hasFeatureCollection.author_feature', @@ -570,45 +565,51 @@ var all = { field: 'doc._source.hasFeatureCollection.compound_feature.compound', featureArray: 'doc._source.hasFeatureCollection.compound_feature', featureValue: 'compound' + }, { + title: 'DOI', + field: 'doc._source.digitalObjectIdentifier', + classes: 'location' }, { - title: 'Country', - field: "doc._source.hasFeatureCollection.wipo_country_feature.wipo_country_code", - hideIfMissing: true - }, { - title: 'Organization(s)', - field: 'doc._source.hasFeatureCollection.organization_feature.organization', - featureArray: 'doc._source.hasFeatureCollection.organization_feature', - featureValue: 'organization', - hideIfMissing: true - }, { - title: 'Abstract', - field: "doc['_source']['hasAbstractPart']['text']" + title: 'Provider', + field: 'doc._source.hasFeatureCollection.provider_name_feature.provider_name', + classes: 'location' + }, { + title: 'Country', + field: 'doc._source.hasFeatureCollection.wipo_country_feature.wipo_country_code', + classes: 'location', + hideIfMissing: true + },{ + title: 'Organization', + field: 'doc._source.hasFeatureCollection.organization_feature.organization || doc._source.hasFeatureCollection.organization_feature[0].organization', + classes: 'location', + hideIfMissing: true }, { title: 'Keyword(s)', field: 'doc._source.hasFeatureCollection.keywords_feature.keywords', featureArray: 'doc._source.hasFeatureCollection.keywords_feature', featureValue: 'keywords', - hideIfMissing: true + hideIfMissing: true }, { title: 'Reference(s)', field: 'doc._source.isCitationOf.hasIdentifier.label', featureArray: 'doc._source.isCitationOf', featureValue: 'hasIdentifier.label', - hideIfMissing: true + hideIfMissing: true }, { title: 'Citation(s)', field: 'doc._source.citation.hasIdentifier.label', featureArray: 'doc._source.citation', featureValue: 'hasIdentifier.label', - hideIfMissing: true - },{ + hideIfMissing: true + },{ + title: 'Abstract', + field: "doc['_source']['hasAbstractPart']['text']" + }, { title: 'Full Text', field: 'doc._source.hasBodyPart.text', featureArray: 'doc._source.hasBodyPart', featureValue: 'text', - hideIfMissing: true - } - ] + }] } } } @@ -619,4 +620,4 @@ var all = { // ============================================== module.exports = _.merge( all, - require('./' + process.env.NODE_ENV + '.js') || {}); + require('./' + process.env.NODE_ENV + '.js') || {}); \ No newline at end of file