UNPKG

wordcloud

Version:

Tag cloud/Wordle presentation on 2D canvas or HTML

494 lines (425 loc) 19.4 kB
'use strict'; var examples = { 'taiwan': { list: (function() { var names = ['台灣', '台湾', 'Taiwan', '臺灣']; var str = ['40 台灣']; var i = 20; while (--i) { names.forEach(function(name) { str.push(i + ' ' + name); }); } return str.join('\n'); }()), option: '{\n' + ' gridSize: 4,\n' + ' weightFactor: 1,\n' + ' fontFamily: \'Hiragino Mincho Pro, serif\',\n' + ' color: \'random-dark\',\n' + ' backgroundColor: \'#f0f0f0\',\n' + ' rotateRatio: 0.5,\n' + ' rotationSteps: 2\n,' + ' ellipticity: 1,\n' + ' shape: function(theta) {\n' + ' /' + '/ Function for simple shapes can be generated manually with http://timdream.org/wordcloud2.js/shape-generator.html.\n' + ' var max = 1026;\n' + ' var leng = [290,296,299,301,305,309,311,313,315,316,318,321,325,326,327,328,330,330,331,334,335,338,340,343,343,343,346,349,353,356,360,365,378,380,381,381,381,391,394,394,395,396,400,400,408,405,400,400,400,401,401,403,404,405,408,410,413,414,414,415,416,418,420,423,425,430,435,440,446,456,471,486,544,541,533,532,533,537,540,537,535,535,533,546,543,539,531,529,530,533,529,528,529,522,521,520,509,520,520,533,522,523,526,528,527,532,537,539,539,540,539,538,533,532,524,523,513,503,482,467,443,438,435,431,429,427,426,422,422,426,426,423,419,414,410,407,404,401,396,393,393,395,392,389,388,383,379,378,376,375,372,369,368,359,343,335,332,327,323,314,308,300,294,290,288,289,290,282,275,269,263,257,242,244,237,235,235,232,231,225,224,221,219,218,218,217,217,215,215,214,214,214,214,214,215,215,216,213,213,212,211,209,207,205,204,206,205,205,205,205,204,203,203,201,200,199,197,195,193,192,192,190,189,187,186,186,183,183,182,182,181,179,180,179,178,178,177,177,176,176,176,176,175,175,175,175,175,175,174,174,175,175,175,175,176,177,176,177,177,177,180,179,179,180,179,179,179,178,178,178,178,177,178,177,179,179,179,180,180,181,181,181,183,183,184,184,186,187,189,189,192,195,193,194,193,194,194,191,189,196,195,196,199,200,201,200,200,200,200,202,203,204,205,210,210,210,211,210,214,218,219,226,231,233,235,235,235,235,236,238,240,241,243,245,246,249,249,249,255,257,264,271,272,274,275,276,276,278,285,292,294,296,301,304,313,320,330,333,337,342,345,348,352,358,363,376,386,379,386,387,387,399,402,402,410,415,420,425,430,429,436,435,438,442,447,451,454,455,474,477,481,484,492,486,488,501,509,544,553,552,553,564,579,593,600,627,637,644,644,643,641,640,641,641,643,643,648,651,653,659,671,678,685,690,698,705,711,715,722,729,738,760,770,777,780,788,792,796,800,803,806,808,810,809,815,819,821,823,826,828,830,834,838,849,854,861,884,891,909,932,996,1026,1016,1011,1015,1018,999,987,827,806,779,754,734,727,700,690,686,682,677,675,672,668,665,664,658,641,614,610,609,609,608,596,591,583,577,576,570,561,553,547,539,531,526,525,524,519,513,502,484,480,478,470,464,458,453,450,448,448,445,441,435,431,423,420,411,408,405,398,388,385,385,385,383,379,372,370,369,368,366,367,371,370,367,365,345,343,342,340,336,334,331,329,326,323,323,322,321,321,319,318,315,313,312,309,308,307,306,305,304,303,303,302,302,300,299,299,297,296,294,292,291,290,289,290,291,291,289,289,285,285,286,287,287,288,288,288,288,288,289,288,287,279,275,273,272,272,272,274,274,274,275,275,277,281,284,285,286,286,286,283,280,279,279,280,281,283,284,288,291];\n\n' + ' return leng[(theta / (2 * Math.PI)) * leng.length | 0] / max;\n' + ' }\n'+ '}' }, 'web-tech': { list: '26 Web Technologies\n20 HTML\n20 <canvas>\n' + '15 CSS\n15 JavaScript\n12 Document Object Model\n12 <audio>\n12 <video>\n12 Web Workers\n12 XMLHttpRequest\n12 SVG\n' + '9 JSON.parse()\n9 Geolocation\n9 data attribute\n9 transform\n9 transition\n9 animation\n' + '7 setTimeout\n7 @font-face\n7 Typed Arrays\n7 FileReader API\n7 FormData\n7 IndexedDB\n' + '7 getUserMedia()\n7 postMassage()\n7 CORS\n6 strict mode\n6 calc()\n6 supports()\n' + '6 media queries\n6 full screen\n6 notification\n6 orientation\n6 requestAnimationFrame\n' + '5 border-radius\n5 box-sizing\n5 rgba()\n5 text-shadow\n5 box-shadow\n5 flexbox\n5 viewpoint', option: '{\n' + ' gridSize: 18,\n' + ' weightFactor: 3,\n' + ' fontFamily: \'Finger Paint, cursive, sans-serif\',\n' + ' color: \'#f0f0c0\',\n' + ' hover: window.drawBox,\n' + ' click: function(item) {\n' + ' alert(item[0] + \': \' + item[1]);\n' + ' },\n' + ' backgroundColor: \'#001f00\'\n' + '}', fontCSS: 'https://fonts.googleapis.com/css?family=Finger+Paint' }, 'les-miz': { list: '30 Les Misérables\n20 Victor Hugo\n15 Jean Valjean\n15 Javert\n15 Fantine\n' + '15 Cosette\n12 Éponine\n12 Marius\n12 Enjolras\n10 Thénardiers\n10 Gavroche\n' + '10 Bishop Myriel\n10 Patron-Minette\n10 God\n8 ABC Café\n8 Paris\n8 Digne\n' + '8 Elephant of the Bastille\n5 silverware\n5 Bagne of Toulon\n5 loaf of bread\n' + '5 Rue Plumet\n5 revolution\n5 barricade\n4 sewers\n4 Fex urbis lex orbis', option: '{\n' + ' gridSize: 18,\n' + ' weightFactor: 3,\n' + ' fontFamily: \'Average, Times, serif\',\n' + ' color: function() {\n' + ' return ([\'#d0d0d0\', \'#e11\', \'#44f\'])[Math.floor(Math.random() * 3)]\n' + ' },\n' + ' backgroundColor: \'#333\'\n' + '}', fontCSS: 'https://fonts.googleapis.com/css?family=Average' }, 'red-chamber': { list: '6 紅樓夢\n3 賈寶玉\n3 林黛玉\n3 薛寶釵\n3 王熙鳳\n3 李紈\n3 賈元春\n3 賈迎春\n' + '3 賈探春\n3 賈惜春\n3 秦可卿\n3 賈巧姐\n3 史湘雲\n3 妙玉\n2 賈政\n2 賈赦\n' + '2 賈璉\n2 賈珍\n2 賈環\n2 賈母\n2 王夫人\n2 薛姨媽\n2 尤氏\n2 平兒\n2 鴛鴦\n' + '2 襲人\n2 晴雯\n2 香菱\n2 紫鵑\n2 麝月\n2 小紅\n2 金釧\n2 甄士隱\n2 賈雨村', option: '{\n' + ' gridSize: 8,\n' + ' weightFactor: 16,\n' + ' fontFamily: \'Hiragino Mincho Pro, serif\',\n' + ' color: \'random-dark\',\n' + ' backgroundColor: \'#f0f0f0\',\n' + ' rotateRatio: 0\n' + '}' }, 'quick-fox': { list: '2 The\n2 quick\n3 brown\n5 fox\n3 jumps\n3 over\n3.5 the\n3 lazy\n6 dog\n', option: '{\n' + ' gridSize: 16,\n' + ' weightFactor: 16,\n' + ' origin: [90, 0],\n' + ' fontFamily: \'Times, serif\',\n' + ' color: \'random-light\',\n' + ' backgroundColor: \'#000\',\n' + ' shuffle: false,\n' + ' rotateRatio: 0\n' + '}', width: 180, height: 480 }, 'love' : { list: (function generateLoveList() { var list = ['12 Love']; var nums = [5, 4, 3, 2, 2]; // This list of the word "Love" in language of the world was taken from // the Language links of entry "Love" in English Wikipedia, with duplicate // spelling removed. var words = ('Liebe,ፍቅር,Lufu,حب,Aimor,Amor,Heyran,ভালোবাসা,Каханне,Любоў,Любов,བརྩེ་དུང་།,' + 'Ljubav,Karantez,Юрату,Láska,Amore,Cariad,Kærlighed,Armastus,Αγάπη,Amo,Amol,Maitasun,' + 'عشق,Pyar,Amour,Leafde,Gràdh,愛,爱,પ્રેમ,사랑,Սեր,Ihunanya,Cinta,ᑕᑯᑦᓱᒍᓱᑉᐳᖅ,Ást,אהבה,' + 'ಪ್ರೀತಿ,სიყვარული,Махаббат,Pendo,Сүйүү,Mīlestība,Meilė,Leefde,Bolingo,Szerelem,' + 'Љубов,സ്നേഹം,Imħabba,प्रेम,Ái,Хайр,အချစ်,Tlazohtiliztli,Liefde,माया,मतिना,' + 'Kjærlighet,Kjærleik,ପ୍ରେମ,Sevgi,ਪਿਆਰ,پیار,Miłość,Leevde,Dragoste,' + 'Khuyay,Любовь,Таптал,Dashuria,Amuri,ආදරය,Ljubezen,Jaceyl,خۆشەویستی,Љубав,Rakkaus,' + 'Kärlek,Pag-ibig,காதல்,ప్రేమ,ความรัก,Ишқ,Aşk,محبت,Tình yêu,Higugma,ליבע').split(','); nums.forEach(function(n) { words.forEach(function(w) { list.push(n + ' ' + w); }); }); return list.join('\n'); })(), option: '{\n' + ' gridSize: Math.round(16 * $(\'#canvas\').width() / 1024),\n' + ' weightFactor: function (size) {\n' + ' return Math.pow(size, 2.3) * $(\'#canvas\').width() / 1024;\n' + ' },\n' + ' fontFamily: \'Times, serif\',\n' + ' color: function (word, weight) {\n' + ' return (weight === 12) ? \'#f02222\' : \'#c09292\';\n' + ' },\n' + ' rotateRatio: 0.5,\n' + ' rotationSteps: 2,\n' + ' backgroundColor: \'#ffe0e0\'\n' + '}' } }; var maskCanvas; jQuery(function($) { var $form = $('#form'); var $canvas = $('#canvas'); var $htmlCanvas = $('#html-canvas'); var $canvasContainer = $('#canvas-container'); var $loading = $('#loading'); var $list = $('#input-list'); var $options = $('#config-option'); var $width = $('#config-width'); var $height = $('#config-height'); var $mask = $('#config-mask'); var $dppx = $('#config-dppx'); var $css = $('#config-css'); var $webfontLink = $('#link-webfont'); if (!WordCloud.isSupported) { $('#not-supported').prop('hidden', false); $form.find('textarea, input, select, button').prop('disabled', true); return; } var $box = $('<div id="box" hidden />'); $canvasContainer.append($box); window.drawBox = function drawBox(item, dimension) { if (!dimension) { $box.prop('hidden', true); return; } var dppx = $dppx.val(); $box.prop('hidden', false); $box.css({ left: dimension.x / dppx + 'px', top: dimension.y / dppx + 'px', width: dimension.w / dppx + 'px', height: dimension.h / dppx + 'px' }); }; // Update the default value if we are running in a hdppx device if (('devicePixelRatio' in window) && window.devicePixelRatio !== 1) { $dppx.val(window.devicePixelRatio); } $canvas.on('wordcloudstop', function wordcloudstopped(evt) { $loading.prop('hidden', true); }); $form.on('submit', function formSubmit(evt) { evt.preventDefault(); changeHash(''); }); $('#config-mask-clear').on('click', function() { maskCanvas = null; // Hack! $mask.wrap('<form>').closest('form').get(0).reset(); $mask.unwrap(); }); // Load the local image file, read it's pixels and transform it into a // black-and-white mask image on the canvas. $mask.on('change', function() { maskCanvas = null; var file = $mask[0].files[0]; if (!file) { return; } var url = window.URL.createObjectURL(file); var img = new Image(); img.src = url; img.onload = function readPixels() { window.URL.revokeObjectURL(url); maskCanvas = document.createElement('canvas'); maskCanvas.width = img.width; maskCanvas.height = img.height; var ctx = maskCanvas.getContext('2d'); ctx.drawImage(img, 0, 0, img.width, img.height); var imageData = ctx.getImageData( 0, 0, maskCanvas.width, maskCanvas.height); var newImageData = ctx.createImageData(imageData); for (var i = 0; i < imageData.data.length; i += 4) { var tone = imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]; var alpha = imageData.data[i + 3]; if (alpha < 128 || tone > 128 * 3) { // Area not to draw newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 255; newImageData.data[i + 3] = 0; } else { // Area to draw newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 0; newImageData.data[i + 3] = 255; } } ctx.putImageData(newImageData, 0, 0); }; }); if ($mask[0].files.length) { $mask.trigger('change'); } $('#btn-save').on('click', function save(evt) { var url = $canvas[0].toDataURL(); if ('download' in document.createElement('a')) { this.href = url; } else { evt.preventDefault(); alert('Please right click and choose "Save As..." to save the generated image.'); window.open(url, '_blank', 'width=500,height=300,menubar=yes'); } }); $('#btn-canvas').on('click', function showCanvas(evt) { $canvas.removeClass('hide'); $htmlCanvas.addClass('hide'); $('#btn-canvas').prop('disabled', true); $('#btn-html-canvas').prop('disabled', false); }); $('#btn-html-canvas').on('click', function showCanvas(evt) { $canvas.addClass('hide'); $htmlCanvas.removeClass('hide'); $('#btn-canvas').prop('disabled', false); $('#btn-html-canvas').prop('disabled', true); }); $('#btn-canvas').prop('disabled', true); $('#btn-html-canvas').prop('disabled', false); var $examples = $('#examples'); $examples.on('change', function loadExample(evt) { changeHash(this.value); this.selectedIndex = 0; $examples.blur(); }); var run = function run() { $loading.prop('hidden', false); // Load web font $webfontLink.prop('href', $css.val()); // devicePixelRatio var devicePixelRatio = parseFloat($dppx.val()); // Set the width and height var width = $width.val() ? $width.val() : $('#canvas-container').width(); var height = $height.val() ? $height.val() : Math.floor(width * 0.65); var pixelWidth = width; var pixelHeight = height; if (devicePixelRatio !== 1) { $canvas.css({'width': width + 'px', 'height': height + 'px'}); pixelWidth *= devicePixelRatio; pixelHeight *= devicePixelRatio; } else { $canvas.css({'width': '', 'height': '' }); } $canvas.attr('width', pixelWidth); $canvas.attr('height', pixelHeight); $htmlCanvas.css({'width': pixelWidth + 'px', 'height': pixelHeight + 'px'}); // Set the options object var options = {}; if ($options.val()) { options = (function evalOptions() { try { return eval('(' + $options.val() + ')'); } catch (error) { alert('The following Javascript error occurred in the option definition; all option will be ignored: \n\n' + error.toString()); return {}; } })(); } // Set devicePixelRatio options if (devicePixelRatio !== 1) { if (!('gridSize' in options)) { options.gridSize = 8; } options.gridSize *= devicePixelRatio; if (options.origin) { if (typeof options.origin[0] == 'number') options.origin[0] *= devicePixelRatio; if (typeof options.origin[1] == 'number') options.origin[1] *= devicePixelRatio; } if (!('weightFactor' in options)) { options.weightFactor = 1; } if (typeof options.weightFactor == 'function') { var origWeightFactor = options.weightFactor; options.weightFactor = function weightFactorDevicePixelRatioWrap() { return origWeightFactor.apply(this, arguments) * devicePixelRatio; }; } else { options.weightFactor *= devicePixelRatio; } } // Put the word list into options if ($list.val()) { var list = []; $.each($list.val().split('\n'), function each(i, line) { if (!$.trim(line)) return; var lineArr = line.split(' '); var count = parseFloat(lineArr.shift()) || 0; list.push([lineArr.join(' '), count]); }); options.list = list; } if (maskCanvas) { options.clearCanvas = false; /* Determine bgPixel by creating another canvas and fill the specified background color. */ var bctx = document.createElement('canvas').getContext('2d'); bctx.fillStyle = options.backgroundColor || '#fff'; bctx.fillRect(0, 0, 1, 1); var bgPixel = bctx.getImageData(0, 0, 1, 1).data; var maskCanvasScaled = document.createElement('canvas'); maskCanvasScaled.width = $canvas[0].width; maskCanvasScaled.height = $canvas[0].height; var ctx = maskCanvasScaled.getContext('2d'); ctx.drawImage(maskCanvas, 0, 0, maskCanvas.width, maskCanvas.height, 0, 0, maskCanvasScaled.width, maskCanvasScaled.height); var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); var newImageData = ctx.createImageData(imageData); for (var i = 0; i < imageData.data.length; i += 4) { if (imageData.data[i + 3] > 128) { newImageData.data[i] = bgPixel[0]; newImageData.data[i + 1] = bgPixel[1]; newImageData.data[i + 2] = bgPixel[2]; newImageData.data[i + 3] = bgPixel[3]; } else { // This color must not be the same w/ the bgPixel. newImageData.data[i] = bgPixel[0]; newImageData.data[i + 1] = bgPixel[1]; newImageData.data[i + 2] = bgPixel[2]; newImageData.data[i + 3] = bgPixel[3] ? (bgPixel[3] - 1) : 0; } } ctx.putImageData(newImageData, 0, 0); ctx = $canvas[0].getContext('2d'); ctx.drawImage(maskCanvasScaled, 0, 0); maskCanvasScaled = ctx = imageData = newImageData = bctx = bgPixel = undefined; } // Always manually clean up the html output if (!options.clearCanvas) { $htmlCanvas.empty(); $htmlCanvas.css('background-color', options.backgroundColor || '#fff'); } // All set, call the WordCloud() // Order matters here because the HTML canvas might by // set to display: none. WordCloud([$canvas[0], $htmlCanvas[0]], options); }; var loadExampleData = function loadExampleData(name) { var example = examples[name]; $options.val(example.option || ''); $list.val(example.list || ''); $css.val(example.fontCSS || ''); $width.val(example.width || ''); $height.val(example.height || ''); }; var changeHash = function changeHash(name) { if (window.location.hash === '#' + name || (!window.location.hash && !name)) { // We got a same hash, call hashChanged() directly hashChanged(); } else { // Actually change the hash to invoke hashChanged() window.location.hash = '#' + name; } }; var hashChanged = function hashChanged() { var name = window.location.hash.substr(1); if (!name) { // If there is no name, run as-is. run(); } else if (name in examples) { // If the name matches one of the example, load it and run it loadExampleData(name); run(); } else { // If the name doesn't match, reset it. window.location.replace('#'); } } $(window).on('hashchange', hashChanged); if (!window.location.hash || !(window.location.hash.substr(1) in examples)) { // If the initial hash doesn't match to any of the examples, // or it doesn't exist, reset it to #love window.location.replace('#love'); } else { hashChanged(); } });