Showing
2 changed files
with
266 additions
and
2 deletions
| @@ -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); |
-
Please register or login to post a comment