马一丁

Add HTML Support for Word Clouds

@@ -329,6 +329,7 @@ class HTMLRenderer: @@ -329,6 +329,7 @@ class HTMLRenderer:
329 html2canvas = self._load_lib("html2canvas.min.js") 329 html2canvas = self._load_lib("html2canvas.min.js")
330 jspdf = self._load_lib("jspdf.umd.min.js") 330 jspdf = self._load_lib("jspdf.umd.min.js")
331 mathjax = self._load_lib("mathjax.js") 331 mathjax = self._load_lib("mathjax.js")
  332 + wordcloud2 = self._load_lib("wordcloud2.min.js")
332 333
333 # 生成嵌入式script标签,并为每个库添加CDN fallback机制 334 # 生成嵌入式script标签,并为每个库添加CDN fallback机制
334 # Chart.js - 主要图表库 335 # Chart.js - 主要图表库
@@ -347,6 +348,14 @@ class HTMLRenderer: @@ -347,6 +348,14 @@ class HTMLRenderer:
347 lib_name="chartjs-chart-sankey" 348 lib_name="chartjs-chart-sankey"
348 ) 349 )
349 350
  351 + # wordcloud2 - 词云渲染
  352 + wordcloud_tag = self._build_script_with_fallback(
  353 + inline_code=wordcloud2,
  354 + cdn_url="https://cdnjs.cloudflare.com/ajax/libs/wordcloud2.js/1.2.2/wordcloud2.min.js",
  355 + check_expression="typeof WordCloud !== 'undefined'",
  356 + lib_name="wordcloud2"
  357 + )
  358 +
350 # html2canvas - 用于截图 359 # html2canvas - 用于截图
351 html2canvas_tag = self._build_script_with_fallback( 360 html2canvas_tag = self._build_script_with_fallback(
352 inline_code=html2canvas, 361 inline_code=html2canvas,
@@ -383,6 +392,7 @@ class HTMLRenderer: @@ -383,6 +392,7 @@ class HTMLRenderer:
383 <title>{self._escape_html(title)}</title> 392 <title>{self._escape_html(title)}</title>
384 {chartjs_tag} 393 {chartjs_tag}
385 {sankey_tag} 394 {sankey_tag}
  395 + {wordcloud_tag}
386 {html2canvas_tag} 396 {html2canvas_tag}
387 {jspdf_tag} 397 {jspdf_tag}
388 <script> 398 <script>
@@ -1526,6 +1536,7 @@ class HTMLRenderer: @@ -1526,6 +1536,7 @@ class HTMLRenderer:
1526 # 统计 1536 # 统计
1527 widget_type = block.get('widgetType', '') 1537 widget_type = block.get('widgetType', '')
1528 is_chart = isinstance(widget_type, str) and widget_type.startswith('chart.js') 1538 is_chart = isinstance(widget_type, str) and widget_type.startswith('chart.js')
  1539 + is_wordcloud = isinstance(widget_type, str) and 'wordcloud' in widget_type.lower()
1529 1540
1530 if is_chart: 1541 if is_chart:
1531 self.chart_validation_stats['total'] += 1 1542 self.chart_validation_stats['total'] += 1
@@ -1590,9 +1601,13 @@ class HTMLRenderer: @@ -1590,9 +1601,13 @@ class HTMLRenderer:
1590 1601
1591 title = props.get("title") 1602 title = props.get("title")
1592 title_html = f'<div class="chart-title">{self._escape_html(title)}</div>' if title else "" 1603 title_html = f'<div class="chart-title">{self._escape_html(title)}</div>' if title else ""
1593 - fallback_html = self._render_widget_fallback(normalized_data, block.get("widgetId")) 1604 + fallback_html = (
  1605 + self._render_wordcloud_fallback(props, block.get("widgetId"))
  1606 + if is_wordcloud
  1607 + else self._render_widget_fallback(normalized_data, block.get("widgetId"))
  1608 + )
1594 return f""" 1609 return f"""
1595 - <div class="chart-card"> 1610 + <div class="chart-card{' wordcloud-card' if is_wordcloud else ''}">
1596 {title_html} 1611 {title_html}
1597 <div class="chart-container"> 1612 <div class="chart-container">
1598 <canvas id="{canvas_id}" data-config-id="{config_id}"></canvas> 1613 <canvas id="{canvas_id}" data-config-id="{config_id}"></canvas>
@@ -1637,6 +1652,51 @@ class HTMLRenderer: @@ -1637,6 +1652,51 @@ class HTMLRenderer:
1637 """ 1652 """
1638 return table_html 1653 return table_html
1639 1654
  1655 + def _render_wordcloud_fallback(self, props: Dict[str, Any] | None, widget_id: str | None = None) -> str:
  1656 + """为词云提供表格兜底,避免WordCloud渲染失败后页面空白"""
  1657 + words = []
  1658 + if isinstance(props, dict):
  1659 + raw = props.get("data")
  1660 + if isinstance(raw, list):
  1661 + for item in raw:
  1662 + if not isinstance(item, dict):
  1663 + continue
  1664 + text = item.get("word") or item.get("text") or item.get("label")
  1665 + weight = item.get("weight")
  1666 + category = item.get("category") or ""
  1667 + if text:
  1668 + words.append({"word": str(text), "weight": weight, "category": str(category)})
  1669 +
  1670 + if not words:
  1671 + return ""
  1672 +
  1673 + def _format_weight(value: Any) -> str:
  1674 + if isinstance(value, (int, float)) and not isinstance(value, bool):
  1675 + if 0 <= value <= 1.5:
  1676 + return f"{value * 100:.1f}%"
  1677 + return f"{value:.2f}".rstrip("0").rstrip(".")
  1678 + return str(value)
  1679 +
  1680 + widget_attr = f' data-widget-id="{self._escape_attr(widget_id)}"' if widget_id else ""
  1681 + rows = "".join(
  1682 + f"<tr><td>{self._escape_html(item['word'])}</td>"
  1683 + f"<td>{self._escape_html(_format_weight(item['weight']))}</td>"
  1684 + f"<td>{self._escape_html(item['category'] or '-')}</td></tr>"
  1685 + for item in words
  1686 + )
  1687 + return f"""
  1688 + <div class="chart-fallback" data-prebuilt="true"{widget_attr}>
  1689 + <table>
  1690 + <thead>
  1691 + <tr><th>关键词</th><th>权重</th><th>类别</th></tr>
  1692 + </thead>
  1693 + <tbody>
  1694 + {rows}
  1695 + </tbody>
  1696 + </table>
  1697 + </div>
  1698 + """
  1699 +
1640 def _log_chart_validation_stats(self): 1700 def _log_chart_validation_stats(self):
1641 """输出图表验证统计信息""" 1701 """输出图表验证统计信息"""
1642 stats = self.chart_validation_stats 1702 stats = self.chart_validation_stats
@@ -2498,6 +2558,9 @@ table th {{ @@ -2498,6 +2558,9 @@ table th {{
2498 border-radius: 12px; 2558 border-radius: 12px;
2499 background: rgba(0,0,0,0.01); 2559 background: rgba(0,0,0,0.01);
2500 }} 2560 }}
  2561 +.chart-card.wordcloud-card .chart-container {{
  2562 + min-height: 260px;
  2563 +}}
2501 .chart-container {{ 2564 .chart-container {{
2502 position: relative; 2565 position: relative;
2503 min-height: 320px; 2566 min-height: 320px;
@@ -2652,6 +2715,7 @@ document.documentElement.classList.remove('no-js'); @@ -2652,6 +2715,7 @@ document.documentElement.classList.remove('no-js');
2652 document.documentElement.classList.add('js-ready'); 2715 document.documentElement.classList.add('js-ready');
2653 2716
2654 const chartRegistry = []; 2717 const chartRegistry = [];
  2718 +const wordCloudRegistry = new Map();
2655 const STABLE_CHART_TYPES = ['line', 'bar']; 2719 const STABLE_CHART_TYPES = ['line', 'bar'];
2656 const CHART_TYPE_LABELS = { 2720 const CHART_TYPE_LABELS = {
2657 line: '折线图', 2721 line: '折线图',
@@ -2684,6 +2748,12 @@ const CSS_VAR_COLOR_MAP = { @@ -2684,6 +2748,12 @@ const CSS_VAR_COLOR_MAP = {
2684 'var(--color-primary)': '#3498DB', 2748 'var(--color-primary)': '#3498DB',
2685 'var(--color-secondary)': '#95A5A6' 2749 'var(--color-secondary)': '#95A5A6'
2686 }; 2750 };
  2751 +const WORDCLOUD_CATEGORY_COLORS = {
  2752 + positive: '#10b981',
  2753 + negative: '#ef4444',
  2754 + neutral: '#6b7280',
  2755 + controversial: '#f59e0b'
  2756 +};
2687 2757
2688 function normalizeColorToken(color) { 2758 function normalizeColorToken(color) {
2689 if (typeof color !== 'string') return color; 2759 if (typeof color !== 'string') return color;
@@ -2939,6 +3009,170 @@ function clearChartDegradeNote(card) { @@ -2939,6 +3009,170 @@ function clearChartDegradeNote(card) {
2939 } 3009 }
2940 } 3010 }
2941 3011
  3012 +function isWordCloudWidget(payload) {
  3013 + const type = payload && payload.widgetType;
  3014 + return typeof type === 'string' && type.toLowerCase().includes('wordcloud');
  3015 +}
  3016 +
  3017 +function normalizeWordcloudItems(payload) {
  3018 + const source = payload && payload.props && payload.props.data;
  3019 + if (!Array.isArray(source)) return [];
  3020 + return source.map(item => {
  3021 + if (!item || typeof item !== 'object') return null;
  3022 + const word = item.word || item.text || item.label;
  3023 + if (!word) return null;
  3024 + const rawWeight = item.weight;
  3025 + let weight = 0;
  3026 + if (typeof rawWeight === 'number' && !Number.isNaN(rawWeight)) {
  3027 + weight = rawWeight;
  3028 + } else if (typeof rawWeight === 'string') {
  3029 + const parsed = parseFloat(rawWeight);
  3030 + weight = Number.isNaN(parsed) ? 0 : parsed;
  3031 + }
  3032 + const category = (item.category || '').toString().toLowerCase();
  3033 + return { word: String(word), weight, category };
  3034 + }).filter(Boolean);
  3035 +}
  3036 +
  3037 +function wordcloudColor(category) {
  3038 + const key = typeof category === 'string' ? category.toLowerCase() : '';
  3039 + return WORDCLOUD_CATEGORY_COLORS[key] || '#334155';
  3040 +}
  3041 +
  3042 +function renderWordCloudFallback(canvas, items, reason) {
  3043 + const card = canvas.closest('.chart-card') || canvas.parentElement;
  3044 + if (!card) return;
  3045 + const wrapper = canvas.parentElement && canvas.parentElement.classList && canvas.parentElement.classList.contains('chart-container')
  3046 + ? canvas.parentElement
  3047 + : null;
  3048 + if (wrapper) {
  3049 + wrapper.style.display = 'none';
  3050 + } else {
  3051 + canvas.style.display = 'none';
  3052 + }
  3053 + let fallback = card.querySelector('.chart-fallback');
  3054 + if (!fallback) {
  3055 + fallback = document.createElement('div');
  3056 + fallback.className = 'chart-fallback wordcloud-fallback';
  3057 + fallback.setAttribute('data-dynamic', 'true');
  3058 + card.appendChild(fallback);
  3059 + }
  3060 + fallback.style.display = 'block';
  3061 + card.setAttribute('data-chart-state', 'fallback');
  3062 + if (reason) {
  3063 + let notice = fallback.querySelector('.chart-fallback__notice');
  3064 + if (!notice) {
  3065 + notice = document.createElement('p');
  3066 + notice.className = 'chart-fallback__notice';
  3067 + fallback.insertBefore(notice, fallback.firstChild || null);
  3068 + }
  3069 + notice.textContent = `词云未能渲染${reason ? `(${reason})` : ''},已展示数据表。`;
  3070 + }
  3071 + if (fallback.querySelector('table')) {
  3072 + return;
  3073 + }
  3074 + if (!items || !items.length) {
  3075 + const empty = document.createElement('p');
  3076 + empty.textContent = '暂无可用数据。';
  3077 + fallback.appendChild(empty);
  3078 + return;
  3079 + }
  3080 + const table = document.createElement('table');
  3081 + const thead = document.createElement('thead');
  3082 + const headRow = document.createElement('tr');
  3083 + ['关键词', '权重', '类别'].forEach(text => {
  3084 + const th = document.createElement('th');
  3085 + th.textContent = text;
  3086 + headRow.appendChild(th);
  3087 + });
  3088 + thead.appendChild(headRow);
  3089 + table.appendChild(thead);
  3090 + const tbody = document.createElement('tbody');
  3091 + items.forEach(item => {
  3092 + const row = document.createElement('tr');
  3093 + const wordCell = document.createElement('td');
  3094 + wordCell.textContent = item.word;
  3095 + const weightCell = document.createElement('td');
  3096 + if (typeof item.weight === 'number' && !Number.isNaN(item.weight)) {
  3097 + weightCell.textContent = item.weight >= 0 && item.weight <= 1.5
  3098 + ? `${(item.weight * 100).toFixed(1)}%`
  3099 + : item.weight.toFixed(2).replace(/\.0+$/, '').replace(/0+$/, '').replace(/\.$/, '');
  3100 + } else {
  3101 + weightCell.textContent = item.weight !== undefined && item.weight !== null ? String(item.weight) : '—';
  3102 + }
  3103 + const categoryCell = document.createElement('td');
  3104 + categoryCell.textContent = item.category || '—';
  3105 + categoryCell.style.color = wordcloudColor(item.category);
  3106 + row.appendChild(wordCell);
  3107 + row.appendChild(weightCell);
  3108 + row.appendChild(categoryCell);
  3109 + tbody.appendChild(row);
  3110 + });
  3111 + table.appendChild(tbody);
  3112 + fallback.appendChild(table);
  3113 +}
  3114 +
  3115 +function renderWordCloud(canvas, payload, skipRegistry) {
  3116 + const items = normalizeWordcloudItems(payload);
  3117 + const card = canvas.closest('.chart-card') || canvas.parentElement;
  3118 + const container = canvas.parentElement && canvas.parentElement.classList && canvas.parentElement.classList.contains('chart-container')
  3119 + ? canvas.parentElement
  3120 + : null;
  3121 + if (!items.length) {
  3122 + renderWordCloudFallback(canvas, items, '无有效数据');
  3123 + return;
  3124 + }
  3125 + if (typeof WordCloud === 'undefined') {
  3126 + renderWordCloudFallback(canvas, items, '词云依赖未加载');
  3127 + return;
  3128 + }
  3129 + const dpr = Math.max(1, window.devicePixelRatio || 1);
  3130 + const width = Math.max(240, (container ? container.clientWidth : canvas.clientWidth || canvas.width || 320));
  3131 + const height = Math.max(180, Math.round(width * 0.62));
  3132 + canvas.width = Math.round(width * dpr);
  3133 + canvas.height = Math.round(height * dpr);
  3134 + canvas.style.width = `${width}px`;
  3135 + canvas.style.height = `${height}px`;
  3136 +
  3137 + const maxWeight = items.reduce((max, item) => Math.max(max, item.weight || 0), 0) || 1;
  3138 + const list = items.map(item => [item.word, item.weight && item.weight > 0 ? item.weight : 1]);
  3139 + try {
  3140 + WordCloud(canvas, {
  3141 + list,
  3142 + gridSize: Math.max(8, Math.floor(Math.sqrt(canvas.width * canvas.height) / 80)),
  3143 + weightFactor: (val) => {
  3144 + const normalized = Math.max(0, val) / maxWeight;
  3145 + const size = 16 + normalized * 32;
  3146 + return size * dpr;
  3147 + },
  3148 + color: (word) => {
  3149 + const found = items.find(entry => entry.word === word);
  3150 + return lightenColor(wordcloudColor(found && found.category), 0.05);
  3151 + },
  3152 + rotateRatio: 0.15,
  3153 + shuffle: false,
  3154 + shrinkToFit: true,
  3155 + drawOutOfBound: false,
  3156 + backgroundColor: getComputedStyle(document.body).getPropertyValue('--card-bg').trim() || '#fff'
  3157 + });
  3158 + if (container) {
  3159 + container.style.display = '';
  3160 + container.style.minHeight = `${height}px`;
  3161 + }
  3162 + const fallback = card && card.querySelector('.chart-fallback');
  3163 + if (fallback) {
  3164 + fallback.style.display = 'none';
  3165 + }
  3166 + card && card.removeAttribute('data-chart-state');
  3167 + if (!skipRegistry) {
  3168 + wordCloudRegistry.set(canvas, () => renderWordCloud(canvas, payload, true));
  3169 + }
  3170 + } catch (err) {
  3171 + console.error('WordCloud 渲染失败', err);
  3172 + renderWordCloudFallback(canvas, items, err && err.message ? err.message : '');
  3173 + }
  3174 +}
  3175 +
2942 function createFallbackTable(labels, datasets) { 3176 function createFallbackTable(labels, datasets) {
2943 if (!Array.isArray(datasets) || !datasets.length) { 3177 if (!Array.isArray(datasets) || !datasets.length) {
2944 return null; 3178 return null;
@@ -3168,6 +3402,14 @@ function instantiateChart(ctx, payload, optionsTemplate, type) { @@ -3168,6 +3402,14 @@ function instantiateChart(ctx, payload, optionsTemplate, type) {
3168 return new Chart(ctx, config); 3402 return new Chart(ctx, config);
3169 } 3403 }
3170 3404
  3405 +function debounce(fn, wait) {
  3406 + let timer;
  3407 + return (...args) => {
  3408 + clearTimeout(timer);
  3409 + timer = setTimeout(() => fn.apply(null, args), wait || 200);
  3410 + };
  3411 +}
  3412 +
3171 function hydrateCharts() { 3413 function hydrateCharts() {
3172 document.querySelectorAll('canvas[data-config-id]').forEach(canvas => { 3414 document.querySelectorAll('canvas[data-config-id]').forEach(canvas => {
3173 const configScript = document.getElementById(canvas.dataset.configId); 3415 const configScript = document.getElementById(canvas.dataset.configId);
@@ -3180,6 +3422,10 @@ function hydrateCharts() { @@ -3180,6 +3422,10 @@ function hydrateCharts() {
3180 renderChartFallback(canvas, { widgetId: canvas.dataset.configId }, '配置解析失败'); 3422 renderChartFallback(canvas, { widgetId: canvas.dataset.configId }, '配置解析失败');
3181 return; 3423 return;
3182 } 3424 }
  3425 + if (isWordCloudWidget(payload)) {
  3426 + renderWordCloud(canvas, payload);
  3427 + return;
  3428 + }
3183 if (typeof Chart === 'undefined') { 3429 if (typeof Chart === 'undefined') {
3184 renderChartFallback(canvas, payload, 'Chart.js 未加载'); 3430 renderChartFallback(canvas, payload, 'Chart.js 未加载');
3185 return; 3431 return;
@@ -3326,6 +3572,15 @@ function exportPdf() { @@ -3326,6 +3572,15 @@ function exportPdf() {
3326 chart.resize(); 3572 chart.resize();
3327 } 3573 }
3328 }); 3574 });
  3575 + wordCloudRegistry.forEach(fn => {
  3576 + if (typeof fn === 'function') {
  3577 + try {
  3578 + fn();
  3579 + } catch (err) {
  3580 + console.error('词云重新渲染失败', err);
  3581 + }
  3582 + }
  3583 + });
3329 renderTask = pdf.html(target, { 3584 renderTask = pdf.html(target, {
3330 x: 8, 3585 x: 8,
3331 y: 12, 3586 y: 12,
@@ -3398,6 +3653,14 @@ document.addEventListener('DOMContentLoaded', () => { @@ -3398,6 +3653,14 @@ document.addEventListener('DOMContentLoaded', () => {
3398 if (exportBtn) { 3653 if (exportBtn) {
3399 exportBtn.addEventListener('click', exportPdf); 3654 exportBtn.addEventListener('click', exportPdf);
3400 } 3655 }
  3656 + const rerenderWordclouds = debounce(() => {
  3657 + wordCloudRegistry.forEach(fn => {
  3658 + if (typeof fn === 'function') {
  3659 + fn();
  3660 + }
  3661 + });
  3662 + }, 260);
  3663 + window.addEventListener('resize', rerenderWordclouds);
3401 hydrateCharts(); 3664 hydrateCharts();
3402 }); 3665 });
3403 </script> 3666 </script>
  1 +"use strict";window.setImmediate||(window.setImmediate=window.msSetImmediate||window.webkitSetImmediate||window.mozSetImmediate||window.oSetImmediate||function(){if(!window.postMessage||!window.addEventListener)return null;var a=[void 0],r="zero-timeout-message";return window.addEventListener("message",function(t){"string"==typeof t.data&&t.data.substr(0,r.length)===r&&(t.stopImmediatePropagation(),t=parseInt(t.data.substr(r.length),36),a[t]&&(a[t](),a[t]=void 0))},!0),window.clearImmediate=function(t){a[t]&&(a[t]=void 0)},function(t){var e=a.length;return a.push(t),window.postMessage(r+e.toString(36),"*"),e}}()||function(t){window.setTimeout(t,0)}),window.clearImmediate||(window.clearImmediate=window.msClearImmediate||window.webkitClearImmediate||window.mozClearImmediate||window.oClearImmediate||function(t){window.clearTimeout(t)}),function(t){function e(I,t){if(d){var u=Math.floor(Math.random()*Date.now());(I=!Array.isArray(I)?[I]:I).forEach(function(t,e){if("string"==typeof t){if(I[e]=document.getElementById(t),!I[e])throw new Error("The element id specified is not found.")}else if(!t.tagName&&!t.appendChild)throw new Error("You must pass valid HTML elements, or ID of the element.")});var e,k={list:[],fontFamily:'"Trebuchet MS", "Heiti TC", "微軟正黑體", "Arial Unicode MS", "Droid Fallback Sans", sans-serif',fontWeight:"normal",color:"random-dark",minSize:0,weightFactor:1,clearCanvas:!0,backgroundColor:"#fff",gridSize:8,drawOutOfBound:!1,shrinkToFit:!1,origin:null,drawMask:!1,maskColor:"rgba(255,0,0,0.3)",maskGapWidth:.3,wait:0,abortThreshold:0,abort:function(){},minRotation:-Math.PI/2,maxRotation:Math.PI/2,rotationSteps:0,shuffle:!0,rotateRatio:.1,shape:"circle",ellipticity:.65,classes:null,hover:null,click:null};if(t)for(var a in t)a in k&&(k[a]=t[a]);if("function"!=typeof k.weightFactor&&(e=k.weightFactor,k.weightFactor=function(t){return t*e}),"function"!=typeof k.shape)switch(k.shape){case"circle":default:k.shape="circle";break;case"cardioid":k.shape=function(t){return 1-Math.sin(t)};break;case"diamond":k.shape=function(t){t%=2*Math.PI/4;return 1/(Math.cos(t)+Math.sin(t))};break;case"square":k.shape=function(t){return Math.min(1/Math.abs(Math.cos(t)),1/Math.abs(Math.sin(t)))};break;case"triangle-forward":k.shape=function(t){t%=2*Math.PI/3;return 1/(Math.cos(t)+Math.sqrt(3)*Math.sin(t))};break;case"triangle":case"triangle-upright":k.shape=function(t){t=(t+3*Math.PI/2)%(2*Math.PI/3);return 1/(Math.cos(t)+Math.sqrt(3)*Math.sin(t))};break;case"pentagon":k.shape=function(t){t=(t+.955)%(2*Math.PI/5);return 1/(Math.cos(t)+.726543*Math.sin(t))};break;case"star":k.shape=function(t){var e=(t+.955)%(2*Math.PI/10);return 0<=(t+.955)%(2*Math.PI/5)-2*Math.PI/10?1/(Math.cos(2*Math.PI/10-e)+3.07768*Math.sin(2*Math.PI/10-e)):1/(Math.cos(e)+3.07768*Math.sin(e))}}k.gridSize=Math.max(Math.floor(k.gridSize),4);var C,S,E,m,F,w,P,R,O=k.gridSize,g=O-k.maskGapWidth,i=Math.abs(k.maxRotation-k.minRotation),o=Math.abs(Math.floor(k.rotationSteps)),s=Math.min(k.maxRotation,k.minRotation);switch(k.color){case"random-dark":P=function(){return n(10,50)};break;case"random-light":P=function(){return n(50,90)};break;default:"function"==typeof k.color&&(P=k.color)}"function"==typeof k.fontWeight&&(R=k.fontWeight);var A=null;"function"==typeof k.classes&&(A=k.classes);var v,M=!1,p=[],r=function(t){var e=t.currentTarget,a=e.getBoundingClientRect(),t=t.touches?(r=t.touches[0].clientX,t.touches[0].clientY):(r=t.clientX,t.clientY),r=r-a.left,t=t-a.top,r=Math.floor(r*(e.width/a.width||1)/O),a=Math.floor(t*(e.height/a.height||1)/O);return p[r][a]},x=function(t){var e=r(t);v!==e&&((v=e)?k.hover(e.item,e.dimension,t):k.hover(void 0,void 0,t))},b=function(t){var e=r(t);e&&(k.click(e.item,e.dimension,t),t.preventDefault())},l=[],z=function(){return 0<k.abortThreshold&&(new Date).getTime()-w>k.abortThreshold},W=function(t,e,a,r,i,o){var n,s,l=i.occupied,d=k.drawMask;d&&((n=I[0].getContext("2d")).save(),n.fillStyle=k.maskColor),M&&(s={x:(t+(i=i.bounds)[3])*O,y:(e+i[0])*O,w:(i[1]-i[3]+1)*O,h:(i[2]-i[0]+1)*O});for(var f,c,h,u=l.length;u--;){var m=t+l[u][0],w=e+l[u][1];S<=m||E<=w||m<0||w<0||(f=w,c=d,h=s,w=o,S<=(m=m)||E<=f||m<0||f<0||(C[m][f]=!1,c&&I[0].getContext("2d").fillRect(m*O,f*O,g,g),M&&(p[m][f]={item:w,dimension:h})))}d&&n.restore()},T=function t(n){var v,M,p;Array.isArray(n)?(v=n[0],M=n[1]):(v=n.word,M=n.weight,p=n.attributes);var x=0===k.rotateRatio||Math.random()>k.rotateRatio?0:0===i?s:0<o?s+Math.floor(Math.random()*o)*i/(o-1):s+Math.random()*i,b=function(t){if(Array.isArray(t)){t=t.slice();return t.splice(0,2),t}return[]}(n),T=function(t,e,a,r){var i=k.weightFactor(e);if(i<=k.minSize)return!1;var o=1;i<L&&(o=function(){for(var t=2;t*i<L;)t+=2;return t}());var n=R?R(t,e,i,r):k.fontWeight,s=document.createElement("canvas"),l=s.getContext("2d",{willReadFrequently:!0});l.font=n+" "+(i*o).toString(10)+"px "+k.fontFamily;var d=l.measureText(t).width/o,f=Math.max(i*o,l.measureText("m").width,l.measureText("W").width)/o,c=d+2*f,h=3*f,e=Math.ceil(c/O),r=Math.ceil(h/O),c=e*O,h=r*O,e=-d/2,r=.4*-f,u=Math.ceil((c*Math.abs(Math.sin(a))+h*Math.abs(Math.cos(a)))/O),c=Math.ceil((c*Math.abs(Math.cos(a))+h*Math.abs(Math.sin(a)))/O),m=c*O,h=u*O;s.setAttribute("width",m),s.setAttribute("height",h),l.scale(1/o,1/o),l.translate(m*o/2,h*o/2),l.rotate(-a),l.font=n+" "+(i*o).toString(10)+"px "+k.fontFamily,l.fillStyle="#000",l.textBaseline="middle",l.fillText(t,e*o,(r+.5*i)*o);var w=l.getImageData(0,0,m,h).data;if(z())return!1;for(var g,v,M,p=[],x=c,b=[u/2,c/2,u/2,c/2];x--;)for(g=u;g--;){M=O;t:for(;M--;)for(v=O;v--;)if(w[4*((g*O+M)*m+(x*O+v))+3]){p.push([x,g]),x<b[3]&&(b[3]=x),x>b[1]&&(b[1]=x),g<b[0]&&(b[0]=g),g>b[2]&&(b[2]=g);break t}0}return{mu:o,occupied:p,bounds:b,gw:c,gh:u,fillTextOffsetX:e,fillTextOffsetY:r,fillTextWidth:d,fillTextHeight:f,fontSize:i}}(v,M,x,b);if(!T)return!1;if(z())return!1;if(!k.drawOutOfBound&&!k.shrinkToFit){var e=T.bounds;if(e[1]-e[3]+1>S||e[2]-e[0]+1>E)return!1}for(var y=F+1,a=function(t){var s,l,d,f,e,a,r,c,h,u,m,w,g,i=Math.floor(t[0]-T.gw/2),o=Math.floor(t[1]-T.gh/2);T.gw,T.gh;return!!function(t,e,a){for(var r=a.length;r--;){var i=t+a[r][0],o=e+a[r][1];if(S<=i||E<=o||i<0||o<0){if(!k.drawOutOfBound)return!1}else if(!C[i][o])return!1}return!0}(i,o,T.occupied)&&(s=i,l=o,d=T,f=v,e=M,a=F-y,r=t[2],c=x,h=p,t=b,u=d.fontSize,m=P?P(f,e,u,a,r,t):k.color,w=R?R(f,e,u,t):k.fontWeight,g=A?A(f,e,u,t):k.classes,I.forEach(function(t){if(t.getContext){var e=t.getContext("2d"),a=d.mu;e.save(),e.scale(1/a,1/a),e.font=w+" "+(u*a).toString(10)+"px "+k.fontFamily,e.fillStyle=m,e.translate((s+d.gw/2)*O*a,(l+d.gh/2)*O*a),0!==c&&e.rotate(-c),e.textBaseline="middle",e.fillText(f,d.fillTextOffsetX*a,(d.fillTextOffsetY+.5*u)*a),e.restore()}else{var r=document.createElement("span"),e="",e="rotate("+-c/Math.PI*180+"deg) ";1!==d.mu&&(e+="translateX(-"+d.fillTextWidth/4+"px) scale("+1/d.mu+")");var i,o={position:"absolute",display:"block",font:w+" "+u*d.mu+"px "+k.fontFamily,left:(s+d.gw/2)*O+d.fillTextOffsetX+"px",top:(l+d.gh/2)*O+d.fillTextOffsetY+"px",width:d.fillTextWidth+"px",height:d.fillTextHeight+"px",lineHeight:u+"px",whiteSpace:"nowrap",transform:e,webkitTransform:e,msTransform:e,transformOrigin:"50% 40%",webkitTransformOrigin:"50% 40%",msTransformOrigin:"50% 40%"};for(i in m&&(o.color=m),r.textContent=f,o)r.style[i]=o[i];if(h)for(var n in h)r.setAttribute(n,h[n]);g&&(r.className+=g),t.appendChild(r)}}),W(i,o,0,0,T,n),!0)};y--;){var r=function(t){if(l[t])return l[t];var e=8*t,a=e,r=[];for(0===t&&r.push([m[0],m[1],0]);a--;){var i=1;"circle"!==k.shape&&(i=k.shape(a/e*2*Math.PI)),r.push([m[0]+t*i*Math.cos(-a/e*2*Math.PI),m[1]+t*i*Math.sin(-a/e*2*Math.PI)*k.ellipticity,a/e*2*Math.PI])}return l[t]=r}(F-y);if(k.shuffle&&function(t){for(var e,a,r=t.length;r;)e=Math.floor(Math.random()*r),a=t[--r],t[r]=t[e],t[e]=a}(r=[].concat(r)),r.some(a))return!0}return!!k.shrinkToFit&&(Array.isArray(n)?n[1]=3*n[1]/4:n.weight=3*n.weight/4,t(n))},y=function(a,t,r){if(t)return!I.some(function(t){var e=new CustomEvent(a,{detail:r||{}});return!t.dispatchEvent(e)},this);I.forEach(function(t){var e=new CustomEvent(a,{detail:r||{}});t.dispatchEvent(e)},this)};!function(){var t,a,e=I[0];if(E=e.getContext?(S=Math.ceil(e.width/O),Math.ceil(e.height/O)):(r=e.getBoundingClientRect(),S=Math.ceil(r.width/O),Math.ceil(r.height/O)),y("wordcloudstart",!0)){if(m=k.origin?[k.origin[0]/O,k.origin[1]/O]:[S/2,E/2],F=Math.floor(Math.sqrt(S*S+E*E)),C=[],!e.getContext||k.clearCanvas)for(I.forEach(function(t){var e;t.getContext?((e=t.getContext("2d")).fillStyle=k.backgroundColor,e.clearRect(0,0,S*(O+1),E*(O+1)),e.fillRect(0,0,S*(O+1),E*(O+1))):(t.textContent="",t.style.backgroundColor=k.backgroundColor,t.style.position="relative")}),l=S;l--;)for(C[l]=[],t=E;t--;)C[l][t]=!0;else{var r=document.createElement("canvas").getContext("2d");r.fillStyle=k.backgroundColor,r.fillRect(0,0,1,1);for(var i,o,n=r.getImageData(0,0,1,1).data,s=e.getContext("2d").getImageData(0,0,S*O,E*O).data,l=S;l--;)for(C[l]=[],t=E;t--;){o=O;t:for(;o--;)for(i=O;i--;)for(d=4;d--;)if(s[4*((t*O+o)*S*O+(l*O+i))+d]!==n[d]){C[l][t]=!1;break t}!1!==C[l][t]&&(C[l][t]=!0)}s=r=n=void 0}if(k.hover||k.click){for(M=!0,l=S+1;l--;)p[l]=[];k.hover&&e.addEventListener("mousemove",x),k.click&&(e.addEventListener("click",b),e.style.webkitTapHighlightColor="rgba(0, 0, 0, 0)"),e.addEventListener("wordcloudstart",function t(){e.removeEventListener("wordcloudstart",t),e.removeEventListener("mousemove",x),e.removeEventListener("click",b),v=void 0})}var d=0,f=0!==k.wait?(a=window.setTimeout,window.clearTimeout):(a=window.setImmediate,window.clearImmediate),c=function(e,a){I.forEach(function(t){t.removeEventListener(e,a)},this)},h=function t(){c("wordcloudstart",t),f(D[u])};!function(e,a){I.forEach(function(t){t.addEventListener(e,a)},this)}("wordcloudstart",h),D[u]=a(function t(){if(d>=k.list.length)return f(D[u]),y("wordcloudstop",!1),c("wordcloudstart",h),void delete D[u];w=(new Date).getTime();var e=T(k.list[d]),e=!y("wordclouddrawn",!0,{item:k.list[d],drawn:e});if(z()||e)return f(D[u]),k.abort(),y("wordcloudabort",!1),y("wordcloudstop",!1),c("wordcloudstart",h),void delete D[u];d++,D[u]=a(t,k.wait)},k.wait)}}()}function n(t,e){return"hsl("+(360*Math.random()).toFixed()+","+(30*Math.random()+70).toFixed()+"%,"+(Math.random()*(e-t)+t).toFixed()+"%)"}}var d=function(){var t=document.createElement("canvas");if(!t||!t.getContext)return!1;t=t.getContext("2d");return!!t&&(!!t.getImageData&&(!!t.fillText&&(!!Array.prototype.some&&!!Array.prototype.push)))}(),L=function(){if(d){for(var t,e,a=document.createElement("canvas").getContext("2d"),r=20;r;){if(a.font=r.toString(10)+"px sans-serif",a.measureText("W").width===t&&a.measureText("m").width===e)return r+1;t=a.measureText("W").width,e=a.measureText("m").width,r--}return 0}}(),D={};e.isSupported=d,e.minFontSize=L,e.stop=function(){if(D)for(var t in D)window.clearImmediate(D[t])},"function"==typeof define&&define.amd?(t.WordCloud=e,define("wordcloud",[],function(){return e})):"undefined"!=typeof module&&module.exports?module.exports=e:t.WordCloud=e}(this);