马一丁

Add HTML Support for Word Clouds

... ... @@ -329,6 +329,7 @@ class HTMLRenderer:
html2canvas = self._load_lib("html2canvas.min.js")
jspdf = self._load_lib("jspdf.umd.min.js")
mathjax = self._load_lib("mathjax.js")
wordcloud2 = self._load_lib("wordcloud2.min.js")
# 生成嵌入式script标签,并为每个库添加CDN fallback机制
# Chart.js - 主要图表库
... ... @@ -347,6 +348,14 @@ class HTMLRenderer:
lib_name="chartjs-chart-sankey"
)
# wordcloud2 - 词云渲染
wordcloud_tag = self._build_script_with_fallback(
inline_code=wordcloud2,
cdn_url="https://cdnjs.cloudflare.com/ajax/libs/wordcloud2.js/1.2.2/wordcloud2.min.js",
check_expression="typeof WordCloud !== 'undefined'",
lib_name="wordcloud2"
)
# html2canvas - 用于截图
html2canvas_tag = self._build_script_with_fallback(
inline_code=html2canvas,
... ... @@ -383,6 +392,7 @@ class HTMLRenderer:
<title>{self._escape_html(title)}</title>
{chartjs_tag}
{sankey_tag}
{wordcloud_tag}
{html2canvas_tag}
{jspdf_tag}
<script>
... ... @@ -1526,6 +1536,7 @@ class HTMLRenderer:
# 统计
widget_type = block.get('widgetType', '')
is_chart = isinstance(widget_type, str) and widget_type.startswith('chart.js')
is_wordcloud = isinstance(widget_type, str) and 'wordcloud' in widget_type.lower()
if is_chart:
self.chart_validation_stats['total'] += 1
... ... @@ -1590,9 +1601,13 @@ class HTMLRenderer:
title = props.get("title")
title_html = f'<div class="chart-title">{self._escape_html(title)}</div>' if title else ""
fallback_html = self._render_widget_fallback(normalized_data, block.get("widgetId"))
fallback_html = (
self._render_wordcloud_fallback(props, block.get("widgetId"))
if is_wordcloud
else self._render_widget_fallback(normalized_data, block.get("widgetId"))
)
return f"""
<div class="chart-card">
<div class="chart-card{' wordcloud-card' if is_wordcloud else ''}">
{title_html}
<div class="chart-container">
<canvas id="{canvas_id}" data-config-id="{config_id}"></canvas>
... ... @@ -1637,6 +1652,51 @@ class HTMLRenderer:
"""
return table_html
def _render_wordcloud_fallback(self, props: Dict[str, Any] | None, widget_id: str | None = None) -> str:
"""为词云提供表格兜底,避免WordCloud渲染失败后页面空白"""
words = []
if isinstance(props, dict):
raw = props.get("data")
if isinstance(raw, list):
for item in raw:
if not isinstance(item, dict):
continue
text = item.get("word") or item.get("text") or item.get("label")
weight = item.get("weight")
category = item.get("category") or ""
if text:
words.append({"word": str(text), "weight": weight, "category": str(category)})
if not words:
return ""
def _format_weight(value: Any) -> str:
if isinstance(value, (int, float)) and not isinstance(value, bool):
if 0 <= value <= 1.5:
return f"{value * 100:.1f}%"
return f"{value:.2f}".rstrip("0").rstrip(".")
return str(value)
widget_attr = f' data-widget-id="{self._escape_attr(widget_id)}"' if widget_id else ""
rows = "".join(
f"<tr><td>{self._escape_html(item['word'])}</td>"
f"<td>{self._escape_html(_format_weight(item['weight']))}</td>"
f"<td>{self._escape_html(item['category'] or '-')}</td></tr>"
for item in words
)
return f"""
<div class="chart-fallback" data-prebuilt="true"{widget_attr}>
<table>
<thead>
<tr><th>关键词</th><th>权重</th><th>类别</th></tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
"""
def _log_chart_validation_stats(self):
"""输出图表验证统计信息"""
stats = self.chart_validation_stats
... ... @@ -2498,6 +2558,9 @@ table th {{
border-radius: 12px;
background: rgba(0,0,0,0.01);
}}
.chart-card.wordcloud-card .chart-container {{
min-height: 260px;
}}
.chart-container {{
position: relative;
min-height: 320px;
... ... @@ -2652,6 +2715,7 @@ document.documentElement.classList.remove('no-js');
document.documentElement.classList.add('js-ready');
const chartRegistry = [];
const wordCloudRegistry = new Map();
const STABLE_CHART_TYPES = ['line', 'bar'];
const CHART_TYPE_LABELS = {
line: '折线图',
... ... @@ -2684,6 +2748,12 @@ const CSS_VAR_COLOR_MAP = {
'var(--color-primary)': '#3498DB',
'var(--color-secondary)': '#95A5A6'
};
const WORDCLOUD_CATEGORY_COLORS = {
positive: '#10b981',
negative: '#ef4444',
neutral: '#6b7280',
controversial: '#f59e0b'
};
function normalizeColorToken(color) {
if (typeof color !== 'string') return color;
... ... @@ -2939,6 +3009,170 @@ function clearChartDegradeNote(card) {
}
}
function isWordCloudWidget(payload) {
const type = payload && payload.widgetType;
return typeof type === 'string' && type.toLowerCase().includes('wordcloud');
}
function normalizeWordcloudItems(payload) {
const source = payload && payload.props && payload.props.data;
if (!Array.isArray(source)) return [];
return source.map(item => {
if (!item || typeof item !== 'object') return null;
const word = item.word || item.text || item.label;
if (!word) return null;
const rawWeight = item.weight;
let weight = 0;
if (typeof rawWeight === 'number' && !Number.isNaN(rawWeight)) {
weight = rawWeight;
} else if (typeof rawWeight === 'string') {
const parsed = parseFloat(rawWeight);
weight = Number.isNaN(parsed) ? 0 : parsed;
}
const category = (item.category || '').toString().toLowerCase();
return { word: String(word), weight, category };
}).filter(Boolean);
}
function wordcloudColor(category) {
const key = typeof category === 'string' ? category.toLowerCase() : '';
return WORDCLOUD_CATEGORY_COLORS[key] || '#334155';
}
function renderWordCloudFallback(canvas, items, reason) {
const card = canvas.closest('.chart-card') || canvas.parentElement;
if (!card) return;
const wrapper = canvas.parentElement && canvas.parentElement.classList && canvas.parentElement.classList.contains('chart-container')
? canvas.parentElement
: null;
if (wrapper) {
wrapper.style.display = 'none';
} else {
canvas.style.display = 'none';
}
let fallback = card.querySelector('.chart-fallback');
if (!fallback) {
fallback = document.createElement('div');
fallback.className = 'chart-fallback wordcloud-fallback';
fallback.setAttribute('data-dynamic', 'true');
card.appendChild(fallback);
}
fallback.style.display = 'block';
card.setAttribute('data-chart-state', 'fallback');
if (reason) {
let notice = fallback.querySelector('.chart-fallback__notice');
if (!notice) {
notice = document.createElement('p');
notice.className = 'chart-fallback__notice';
fallback.insertBefore(notice, fallback.firstChild || null);
}
notice.textContent = `词云未能渲染${reason ? `(${reason})` : ''},已展示数据表。`;
}
if (fallback.querySelector('table')) {
return;
}
if (!items || !items.length) {
const empty = document.createElement('p');
empty.textContent = '暂无可用数据。';
fallback.appendChild(empty);
return;
}
const table = document.createElement('table');
const thead = document.createElement('thead');
const headRow = document.createElement('tr');
['关键词', '权重', '类别'].forEach(text => {
const th = document.createElement('th');
th.textContent = text;
headRow.appendChild(th);
});
thead.appendChild(headRow);
table.appendChild(thead);
const tbody = document.createElement('tbody');
items.forEach(item => {
const row = document.createElement('tr');
const wordCell = document.createElement('td');
wordCell.textContent = item.word;
const weightCell = document.createElement('td');
if (typeof item.weight === 'number' && !Number.isNaN(item.weight)) {
weightCell.textContent = item.weight >= 0 && item.weight <= 1.5
? `${(item.weight * 100).toFixed(1)}%`
: item.weight.toFixed(2).replace(/\.0+$/, '').replace(/0+$/, '').replace(/\.$/, '');
} else {
weightCell.textContent = item.weight !== undefined && item.weight !== null ? String(item.weight) : '—';
}
const categoryCell = document.createElement('td');
categoryCell.textContent = item.category || '—';
categoryCell.style.color = wordcloudColor(item.category);
row.appendChild(wordCell);
row.appendChild(weightCell);
row.appendChild(categoryCell);
tbody.appendChild(row);
});
table.appendChild(tbody);
fallback.appendChild(table);
}
function renderWordCloud(canvas, payload, skipRegistry) {
const items = normalizeWordcloudItems(payload);
const card = canvas.closest('.chart-card') || canvas.parentElement;
const container = canvas.parentElement && canvas.parentElement.classList && canvas.parentElement.classList.contains('chart-container')
? canvas.parentElement
: null;
if (!items.length) {
renderWordCloudFallback(canvas, items, '无有效数据');
return;
}
if (typeof WordCloud === 'undefined') {
renderWordCloudFallback(canvas, items, '词云依赖未加载');
return;
}
const dpr = Math.max(1, window.devicePixelRatio || 1);
const width = Math.max(240, (container ? container.clientWidth : canvas.clientWidth || canvas.width || 320));
const height = Math.max(180, Math.round(width * 0.62));
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const maxWeight = items.reduce((max, item) => Math.max(max, item.weight || 0), 0) || 1;
const list = items.map(item => [item.word, item.weight && item.weight > 0 ? item.weight : 1]);
try {
WordCloud(canvas, {
list,
gridSize: Math.max(8, Math.floor(Math.sqrt(canvas.width * canvas.height) / 80)),
weightFactor: (val) => {
const normalized = Math.max(0, val) / maxWeight;
const size = 16 + normalized * 32;
return size * dpr;
},
color: (word) => {
const found = items.find(entry => entry.word === word);
return lightenColor(wordcloudColor(found && found.category), 0.05);
},
rotateRatio: 0.15,
shuffle: false,
shrinkToFit: true,
drawOutOfBound: false,
backgroundColor: getComputedStyle(document.body).getPropertyValue('--card-bg').trim() || '#fff'
});
if (container) {
container.style.display = '';
container.style.minHeight = `${height}px`;
}
const fallback = card && card.querySelector('.chart-fallback');
if (fallback) {
fallback.style.display = 'none';
}
card && card.removeAttribute('data-chart-state');
if (!skipRegistry) {
wordCloudRegistry.set(canvas, () => renderWordCloud(canvas, payload, true));
}
} catch (err) {
console.error('WordCloud 渲染失败', err);
renderWordCloudFallback(canvas, items, err && err.message ? err.message : '');
}
}
function createFallbackTable(labels, datasets) {
if (!Array.isArray(datasets) || !datasets.length) {
return null;
... ... @@ -3168,6 +3402,14 @@ function instantiateChart(ctx, payload, optionsTemplate, type) {
return new Chart(ctx, config);
}
function debounce(fn, wait) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(null, args), wait || 200);
};
}
function hydrateCharts() {
document.querySelectorAll('canvas[data-config-id]').forEach(canvas => {
const configScript = document.getElementById(canvas.dataset.configId);
... ... @@ -3180,6 +3422,10 @@ function hydrateCharts() {
renderChartFallback(canvas, { widgetId: canvas.dataset.configId }, '配置解析失败');
return;
}
if (isWordCloudWidget(payload)) {
renderWordCloud(canvas, payload);
return;
}
if (typeof Chart === 'undefined') {
renderChartFallback(canvas, payload, 'Chart.js 未加载');
return;
... ... @@ -3326,6 +3572,15 @@ function exportPdf() {
chart.resize();
}
});
wordCloudRegistry.forEach(fn => {
if (typeof fn === 'function') {
try {
fn();
} catch (err) {
console.error('词云重新渲染失败', err);
}
}
});
renderTask = pdf.html(target, {
x: 8,
y: 12,
... ... @@ -3398,6 +3653,14 @@ document.addEventListener('DOMContentLoaded', () => {
if (exportBtn) {
exportBtn.addEventListener('click', exportPdf);
}
const rerenderWordclouds = debounce(() => {
wordCloudRegistry.forEach(fn => {
if (typeof fn === 'function') {
fn();
}
});
}, 260);
window.addEventListener('resize', rerenderWordclouds);
hydrateCharts();
});
</script>
... ...
"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);
\ No newline at end of file
... ...