PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / waterfall-chart / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/waterfall-chart/frontend.js

321 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* Waterfall Chart — frontend.js (Chart.js floating bars) */
2 (function () {
3 'use strict';
4
5 var CHARTJS_CDN = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js';
6
7 function loadChartJs(cb) {
8 if (window.Chart) { cb(); return; }
9 var s = document.createElement('script');
10 s.src = CHARTJS_CDN;
11 s.onload = cb;
12 document.head.appendChild(s);
13 }
14
15 function hexToRgb(hex) {
16 var r = parseInt((hex || '#000000').slice(1, 3), 16);
17 var g = parseInt((hex || '#000000').slice(3, 5), 16);
18 var b = parseInt((hex || '#000000').slice(5, 7), 16);
19 return r + ',' + g + ',' + b;
20 }
21
22 function abbr(n, prefix, suffix, doAbbr) {
23 var abs = Math.abs(n);
24 var sign = n < 0 ? '-' : '';
25 var str;
26 if (!doAbbr || abs < 1000) {
27 str = abs.toLocaleString();
28 } else if (abs < 1000000) {
29 str = (abs / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
30 } else if (abs < 1000000000) {
31 str = (abs / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
32 } else {
33 str = (abs / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
34 }
35 return sign + prefix + str + suffix;
36 }
37
38 function typeColor(type, o) {
39 if (type === 'start') return o.startColor;
40 if (type === 'increase') return o.increaseColor;
41 if (type === 'decrease') return o.decreaseColor;
42 return o.totalColor;
43 }
44
45 /* Compute [base, top] for each bar */
46 function computeFloats(items, o) {
47 var running = 0;
48 return items.map(function (item) {
49 var v = parseFloat(item.value) || 0;
50 var type = item.type || 'increase';
51 var base, top;
52
53 if (type === 'start') {
54 base = 0; top = v; running = v;
55 } else if (type === 'total') {
56 base = 0; top = running;
57 } else if (type === 'increase') {
58 base = running; top = running + v; running = top;
59 } else { /* decrease */
60 top = running; base = running + v; running = base;
61 }
62
63 return {
64 label: item.label,
65 type: type,
66 floatY: [Math.min(base, top), Math.max(base, top)],
67 displayValue: v,
68 barColor: typeColor(type, o),
69 running: running
70 };
71 });
72 }
73
74 function initChart(appEl, o) {
75 var wrap = document.createElement('div');
76 wrap.className = 'bkbg-wfc-wrap';
77 wrap.style.background = o.bgColor;
78 wrap.style.borderRadius = '10px';
79 wrap.style.padding = (o.paddingTop || 24) + 'px 24px ' + (o.paddingBottom || 24) + 'px';
80 wrap.style.maxWidth = o.maxWidth + 'px';
81 wrap.style.margin = '0 auto';
82
83 if (o.showTitle && o.title) {
84 var titleEl = document.createElement('div');
85 titleEl.className = 'bkbg-wfc-title';
86 titleEl.textContent = o.title;
87 titleEl.style.color = o.titleColor;
88 wrap.appendChild(titleEl);
89 }
90
91 var canvasWrap = document.createElement('div');
92 canvasWrap.className = 'bkbg-wfc-canvas-wrap';
93 canvasWrap.style.height = o.height + 'px';
94
95 var canvas = document.createElement('canvas');
96 canvas.style.width = '100%';
97 canvas.style.height = '100%';
98 canvasWrap.appendChild(canvas);
99 wrap.appendChild(canvasWrap);
100
101 /* tooltip */
102 var tooltip = document.createElement('div');
103 tooltip.className = 'bkbg-wfc-tooltip';
104 canvasWrap.appendChild(tooltip);
105
106 /* legend */
107 if (o.showLegend) {
108 var legend = document.createElement('div');
109 legend.className = 'bkbg-wfc-legend';
110
111 var legendItems = [
112 { type: 'start', label: 'Start', color: o.startColor },
113 { type: 'increase', label: 'Increase', color: o.increaseColor },
114 { type: 'decrease', label: 'Decrease', color: o.decreaseColor },
115 { type: 'total', label: 'Total', color: o.totalColor }
116 ];
117 legendItems.forEach(function (li) {
118 var item = document.createElement('div');
119 item.className = 'bkbg-wfc-legend-item';
120 item.style.color = o.labelColor;
121
122 var dot = document.createElement('span');
123 dot.className = 'bkbg-wfc-legend-dot';
124 dot.style.background = li.color;
125
126 var lbl = document.createElement('span');
127 lbl.textContent = li.label;
128
129 item.appendChild(dot);
130 item.appendChild(lbl);
131 legend.appendChild(item);
132 });
133 wrap.appendChild(legend);
134 }
135
136 appEl.parentNode.insertBefore(wrap, appEl);
137 appEl.style.display = 'none';
138
139 loadChartJs(function () {
140 var segs = computeFloats(o.items || [], o);
141
142 var labels = segs.map(function (s) { return s.label; });
143 var floatData = segs.map(function (s) { return s.floatY; });
144 var barColors = segs.map(function (s) { return 'rgba(' + hexToRgb(s.barColor) + ',0.88)'; });
145 var barColorsBorder = segs.map(function (s) { return s.barColor; });
146 var displayValues = segs.map(function (s) { return s.displayValue; });
147
148 /* Connector lines via custom plugin */
149 var bR = o.borderRadius || 0;
150
151 var connectorPlugin = {
152 id: 'bkbg-wfc-connectors',
153 afterDatasetsDraw: function (chart) {
154 if (!o.showConnectors) return;
155 var ctx = chart.ctx;
156 var meta = chart.getDatasetMeta(0);
157 ctx.save();
158 ctx.strokeStyle = o.connectorColor || '#9ca3af';
159 ctx.lineWidth = 1.5;
160 ctx.setLineDash([5, 4]);
161
162 for (var i = 0; i < meta.data.length - 1; i++) {
163 var curr = meta.data[i];
164 var next = meta.data[i + 1];
165 if (!curr || !next) continue;
166 var seg = segs[i];
167 /* connect from top of current to top of next start */
168 var connY = seg.type === 'decrease'
169 ? chart.scales.y.getPixelForValue(seg.floatY[1])
170 : chart.scales.y.getPixelForValue(seg.floatY[1]);
171
172 ctx.beginPath();
173 ctx.moveTo(curr.x + curr.width / 2, connY);
174 ctx.lineTo(next.x - next.width / 2, connY);
175 ctx.stroke();
176 }
177 ctx.restore();
178 }
179 };
180
181 new window.Chart(canvas, {
182 type: 'bar',
183 plugins: [connectorPlugin],
184 data: {
185 labels: labels,
186 datasets: [{
187 data: floatData,
188 backgroundColor: barColors,
189 borderColor: barColorsBorder,
190 borderWidth: 0,
191 borderRadius: bR,
192 borderSkipped: false,
193 barThickness: o.barThickness || 48
194 }]
195 },
196 options: {
197 responsive: true,
198 maintainAspectRatio: false,
199 plugins: {
200 legend: { display: false },
201 tooltip: {
202 enabled: false,
203 external: function (context) {
204 var tooltipModel = context.tooltip;
205 if (!tooltipModel || tooltipModel.opacity === 0) {
206 tooltip.classList.remove('is-visible');
207 return;
208 }
209 var idx = tooltipModel.dataPoints[0].dataIndex;
210 var seg = segs[idx];
211 tooltip.innerHTML =
212 '<div class="bkbg-wfc-tooltip-label">' + (seg.label || '') + '</div>' +
213 '<div class="bkbg-wfc-tooltip-value">' + abbr(seg.displayValue, o.valuePrefix || '', o.valueSuffix || '', o.abbreviate !== false) + '</div>';
214 tooltip.style.left = tooltipModel.caretX + 'px';
215 tooltip.style.top = (tooltipModel.caretY - 60) + 'px';
216 tooltip.classList.add('is-visible');
217 }
218 },
219 datalabels: { display: false }
220 },
221 scales: {
222 x: {
223 grid: { display: false },
224 ticks: {
225 color: o.labelColor || '#6b7280',
226 font: { size: o.labelSize || 12 }
227 },
228 border: { display: false }
229 },
230 y: {
231 grid: {
232 display: o.showGrid !== false,
233 color: o.gridColor || '#e5e7eb'
234 },
235 ticks: {
236 color: o.labelColor || '#6b7280',
237 font: { size: o.labelSize || 12 },
238 callback: function (v) {
239 return abbr(v, o.valuePrefix || '', o.valueSuffix || '', o.abbreviate !== false);
240 }
241 },
242 border: { display: false }
243 }
244 }
245 }
246 });
247
248 /* Data labels on top of bars when showDataLabels is enabled */
249 if (o.showDataLabels) {
250 var chartInstance = window.Chart.getChart(canvas);
251 if (!chartInstance) return;
252 var originalDraw = chartInstance.draw.bind(chartInstance);
253 chartInstance.draw = function () {
254 originalDraw();
255 var ctx2 = canvas.getContext('2d');
256 var meta2 = chartInstance.getDatasetMeta(0);
257 ctx2.save();
258 ctx2.font = 'bold ' + (o.valueSize || 11) + 'px system-ui, sans-serif';
259 ctx2.textAlign = 'center';
260 ctx2.textBaseline = 'bottom';
261 meta2.data.forEach(function (bar, i) {
262 var seg = segs[i];
263 var txt = abbr(seg.displayValue, o.valuePrefix || '', o.valueSuffix || '', o.abbreviate !== false);
264 ctx2.fillStyle = seg.type === 'decrease' ? o.decreaseColor : seg.barColor;
265 var topY = chartInstance.scales.y.getPixelForValue(seg.floatY[1]);
266 ctx2.fillText(txt, bar.x, topY - 4);
267 });
268 ctx2.restore();
269 };
270 chartInstance.draw();
271 }
272 });
273 }
274
275 function init() {
276 document.querySelectorAll('.bkbg-wfc-app').forEach(function (appEl) {
277 if (appEl.dataset.rendered) return;
278 appEl.dataset.rendered = '1';
279
280 var opts;
281 try { opts = JSON.parse(appEl.dataset.opts || '{}'); } catch (e) { opts = {}; }
282
283 var o = Object.assign({
284 title: 'Revenue Waterfall',
285 showTitle: true,
286 items: [],
287 valuePrefix: '$',
288 valueSuffix: '',
289 abbreviate: true,
290 height: 380,
291 barThickness: 48,
292 showConnectors: true,
293 showDataLabels: true,
294 showGrid: true,
295 showLegend: true,
296 borderRadius: 4,
297 maxWidth: 800,
298 paddingTop: 24,
299 paddingBottom: 24,
300 titleSize: 20,
301 labelSize: 12,
302 valueSize: 11,
303 startColor: '#6366f1',
304 increaseColor: '#10b981',
305 decreaseColor: '#ef4444',
306 totalColor: '#8b5cf6',
307 connectorColor: '#9ca3af',
308 bgColor: '#ffffff',
309 titleColor: '#111827',
310 labelColor: '#6b7280',
311 gridColor: '#e5e7eb'
312 }, opts);
313
314 initChart(appEl, o);
315 });
316 }
317
318 if (document.readyState !== 'loading') { init(); }
319 else { document.addEventListener('DOMContentLoaded', init); }
320 })();
321