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 / scatter-chart / index.js

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

307 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = window.wp.element.createElement;
3 var Fragment = window.wp.element.Fragment;
4 var useState = window.wp.element.useState;
5 var useEffect = window.wp.element.useEffect;
6 var useRef = window.wp.element.useRef;
7 var registerBlockType = window.wp.blocks.registerBlockType;
8 var InspectorControls = window.wp.blockEditor.InspectorControls;
9 var useBlockProps = window.wp.blockEditor.useBlockProps;
10 var PanelBody = window.wp.components.PanelBody;
11 var PanelColorSettings = window.wp.blockEditor.PanelColorSettings;
12 var RangeControl = window.wp.components.RangeControl;
13 var SelectControl = window.wp.components.SelectControl;
14 var TextControl = window.wp.components.TextControl;
15 var TextareaControl = window.wp.components.TextareaControl;
16 var ToggleControl = window.wp.components.ToggleControl;
17 var Button = window.wp.components.Button;
18 var ColorPicker = window.wp.components.ColorPicker;
19 var Popover = window.wp.components.Popover;
20 var __ = window.wp.i18n.__;
21
22 var LEG_POS = [
23 { label: 'Top', value: 'top' },
24 { label: 'Bottom', value: 'bottom' },
25 { label: 'Left', value: 'left' },
26 { label: 'Right', value: 'right' },
27 ];
28
29 var POINT_STYLES = [
30 { label: 'Circle', value: 'circle' },
31 { label: 'Cross', value: 'cross' },
32 { label: 'Triangle', value: 'triangle' },
33 { label: 'Rect', value: 'rect' },
34 { label: 'Star', value: 'star' },
35 ];
36
37 var CDN = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js';
38
39 function parseHex(hex, alpha) {
40 hex = (hex || '#6c3fb5').replace('#', '');
41 if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
42 var r = parseInt(hex.substring(0,2),16);
43 var g = parseInt(hex.substring(2,4),16);
44 var b = parseInt(hex.substring(4,6),16);
45 return 'rgba('+r+','+g+','+b+','+(alpha/100).toFixed(2)+')';
46 }
47
48 function parsePoints(text) {
49 return (text || '').split('\n').filter(Boolean).map(function (line) {
50 var parts = line.split(',').map(function (s) { return parseFloat(s.trim()) || 0; });
51 return { x: parts[0] || 0, y: parts[1] || 0 };
52 });
53 }
54
55 function loadChart(cb) {
56 if (window.Chart) { cb(window.Chart); return; }
57 var s = document.createElement('script');
58 s.src = CDN;
59 s.onload = function () { cb(window.Chart); };
60 document.head.appendChild(s);
61 }
62
63 /* ── Editor chart preview ─────────────────────────────────────────── */
64 function ScatterPreview(a) {
65 var canvasRef = useRef(null);
66 var chartRef = useRef(null);
67
68 useEffect(function () {
69 var canvas = canvasRef.current;
70 if (!canvas) return;
71
72 function render(ChartJS) {
73 if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; }
74
75 var datasets;
76 try { datasets = JSON.parse(a.datasetsJson || '[]'); } catch (e) { datasets = []; }
77
78 var dsData = datasets.map(function (ds) {
79 return {
80 label: ds.label || '',
81 data: parsePoints(ds.points),
82 backgroundColor: parseHex(ds.color || '#6c3fb5', a.fillAlpha),
83 borderColor: ds.color || '#6c3fb5',
84 borderWidth: 1.5,
85 pointRadius: a.pointSize,
86 pointStyle: a.pointStyle,
87 pointHoverRadius: a.pointSize + 2,
88 };
89 });
90
91 var xMin = a.xMin !== '' ? parseFloat(a.xMin) : undefined;
92 var xMax = a.xMax !== '' ? parseFloat(a.xMax) : undefined;
93 var yMin = a.yMin !== '' ? parseFloat(a.yMin) : undefined;
94 var yMax = a.yMax !== '' ? parseFloat(a.yMax) : undefined;
95
96 canvas.style.height = a.chartHeight + 'px';
97 chartRef.current = new ChartJS(canvas, {
98 type: 'scatter',
99 data: { datasets: dsData },
100 options: {
101 responsive: true,
102 maintainAspectRatio: false,
103 animation: false,
104 plugins: {
105 legend: { display: !!a.showLegend, position: a.legendPos, labels: { font: { size: a.labelFontSize } } },
106 title: { display: !!(a.showTitle && a.chartTitle), text: a.chartTitle, font: { size: a.titleFontSize } },
107 subtitle: { display: !!(a.showSubtitle && a.chartSubtitle), text: a.chartSubtitle, padding: { bottom: 10 }, font: { size: a.labelFontSize } },
108 },
109 scales: {
110 x: {
111 type: 'linear',
112 grid: { display: !!a.showGrid },
113 min: xMin, max: xMax,
114 title: { display: !!a.xLabel, text: a.xLabel, font: { size: a.labelFontSize } },
115 ticks: { font: { size: a.labelFontSize } },
116 },
117 y: {
118 grid: { display: !!a.showGrid },
119 min: yMin, max: yMax,
120 title: { display: !!a.yLabel, text: a.yLabel, font: { size: a.labelFontSize } },
121 ticks: { font: { size: a.labelFontSize } },
122 },
123 },
124 },
125 });
126 }
127
128 loadChart(render);
129 return function () {
130 if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; }
131 };
132 }, [
133 a.datasetsJson, a.fillAlpha, a.chartHeight, a.showLegend, a.legendPos,
134 a.showTitle, a.chartTitle, a.showSubtitle, a.chartSubtitle, a.showGrid,
135 a.pointSize, a.pointStyle, a.xLabel, a.yLabel, a.xMin, a.xMax, a.yMin, a.yMax,
136 a.titleFontSize, a.labelFontSize,
137 ]);
138
139 return el('div', { style: { background: a.bgColor, borderRadius: a.borderRadius + 'px', padding: '24px' } },
140 el('div', { style: { position: 'relative', height: a.chartHeight + 'px' } },
141 el('canvas', { ref: canvasRef, style: { height: a.chartHeight + 'px' } })
142 )
143 );
144 }
145
146 /* ── Datasets panel ───────────────────────────────────────────────── */
147 function DatasetEditor(a, set) {
148 var datasets;
149 try { datasets = JSON.parse(a.datasetsJson || '[]'); } catch (e) { datasets = []; }
150
151 function save(ds) { set({ datasetsJson: JSON.stringify(ds) }); }
152
153 return el('div', null,
154 datasets.map(function (ds, i) {
155 return el('div', { key: i, style: { border: '1px solid #e5e7eb', borderRadius: '8px', padding: '12px', marginBottom: '12px' } },
156 el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' } },
157 el('strong', null, ds.label || ('Series ' + (i + 1))),
158 el(Button, { isDestructive: true, isSmall: true, onClick: function () { var d = datasets.slice(); d.splice(i, 1); save(d); } }, __('Remove', 'blockenberg'))
159 ),
160 el(TextControl, { label: __('Label', 'blockenberg'), value: ds.label || '', onChange: function (v) { var d = datasets.slice(); d[i] = Object.assign({}, d[i], { label: v }); save(d); } }),
161 el(BkbgColorSwatch, { label: __('Color', 'blockenberg'), value: ds.color || '#6c3fb5', onChange: function (v) { var d = datasets.slice(); d[i] = Object.assign({}, d[i], { color: v }); save(d); } }),
162 el('p', { style: { margin: '0 0 4px', fontSize: '11px', color: '#757575' } }, __('Points: x,y — one per line', 'blockenberg')),
163 el(TextareaControl, {
164 label: __('Data points (x,y)', 'blockenberg'),
165 value: ds.points || '',
166 rows: 6,
167 onChange: function (v) { var d = datasets.slice(); d[i] = Object.assign({}, d[i], { points: v }); save(d); },
168 })
169 );
170 }),
171 el(Button, { variant: 'secondary', onClick: function () {
172 var d = datasets.concat([{ label: 'Series ' + (datasets.length + 1), color: '#10b981', points: '5,10\n20,35\n40,25\n60,50\n80,40' }]);
173 save(d);
174 }}, __('+ Add Series', 'blockenberg'))
175 );
176 }
177
178 /* ── colour-swatch + popover ── */
179 function BkbgColorSwatch(p) {
180 var st = useState(false), open = st[0], setOpen = st[1];
181 return el('div', { style:{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 0', gap:'8px' } },
182 el('span', { style:{ fontSize:'12px', color:'#1e1e1e', flex:1, lineHeight:1.4 } }, p.label),
183 el('div', { style:{ position:'relative', flexShrink:0 } },
184 el('button', { type:'button', title: p.value||'none', onClick: function(){ setOpen(!open); },
185 style:{ width:'28px', height:'28px', borderRadius:'4px', border: open ? '2px solid #007cba' : '2px solid #ddd', cursor:'pointer', padding:0, display:'block', background: p.value||'#ffffff', flexShrink:0 } }),
186 open && el(Popover, { position:'bottom left', onClose: function(){ setOpen(false); } },
187 el('div', { style:{ padding:'8px' }, onMouseDown: function(e){ e.stopPropagation(); } },
188 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'6px' } },
189 el('strong', { style:{ fontSize:'12px' } }, p.label),
190 el(Button, { icon:'no-alt', isSmall:true, onClick: function(){ setOpen(false); } })
191 ),
192 el(ColorPicker, { color: p.value, enableAlpha:true, onChange: p.onChange })
193 )
194 )
195 )
196 );
197 }
198
199 registerBlockType('blockenberg/scatter-chart', {
200 icon: el('svg', { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', width: 24, height: 24 },
201 el('path', { d: 'M3 3v18h18', stroke: 'currentColor', strokeWidth: 1.5, fill: 'none' }),
202 el('circle', { cx: 7, cy: 15, r: 1.5, fill: 'currentColor' }),
203 el('circle', { cx: 10, cy: 10, r: 1.5, fill: 'currentColor' }),
204 el('circle', { cx: 14, cy: 13, r: 1.5, fill: 'currentColor' }),
205 el('circle', { cx: 13, cy: 7, r: 1.5, fill: 'currentColor' }),
206 el('circle', { cx: 17, cy: 5, r: 1.5, fill: 'currentColor' }),
207 el('circle', { cx: 18, cy: 11, r: 1.5, fill: 'currentColor' })
208 ),
209 edit: function (props) {
210 var a = props.attributes;
211 var set = props.setAttributes;
212 var blockProps = useBlockProps({ style: { paddingTop: a.paddingTop + 'px', paddingBottom: a.paddingBottom + 'px', backgroundColor: a.bgColor || undefined } });
213
214 return el(Fragment, null,
215 el(InspectorControls, null,
216 el(PanelBody, { title: __('Datasets', 'blockenberg'), initialOpen: true },
217 DatasetEditor(a, set)
218 ),
219 el(PanelBody, { title: __('Chart Options', 'blockenberg'), initialOpen: false },
220 el(ToggleControl, { label: __('Show Title', 'blockenberg'), checked: a.showTitle, onChange: function (v) { set({ showTitle: v }); }, __nextHasNoMarginBottom: true }),
221 a.showTitle && el(TextControl, { label: __('Title', 'blockenberg'), value: a.chartTitle, onChange: function (v) { set({ chartTitle: v }); } }),
222 el(ToggleControl, { label: __('Show Subtitle', 'blockenberg'), checked: a.showSubtitle, onChange: function (v) { set({ showSubtitle: v }); }, __nextHasNoMarginBottom: true }),
223 a.showSubtitle && el(TextControl, { label: __('Subtitle', 'blockenberg'), value: a.chartSubtitle, onChange: function (v) { set({ chartSubtitle: v }); } }),
224 el(RangeControl, { label: __('Chart Height (px)', 'blockenberg'), value: a.chartHeight, min: 200, max: 700, onChange: function (v) { set({ chartHeight: v }); } }),
225 el(RangeControl, { label: __('Point Size', 'blockenberg'), value: a.pointSize, min: 2, max: 20, onChange: function (v) { set({ pointSize: v }); } }),
226 el(RangeControl, { label: __('Fill Opacity %', 'blockenberg'), value: a.fillAlpha, min: 10, max: 100, onChange: function (v) { set({ fillAlpha: v }); } }),
227 el(SelectControl, { label: __('Point Style', 'blockenberg'), value: a.pointStyle, options: POINT_STYLES, onChange: function (v) { set({ pointStyle: v }); } }),
228 el(ToggleControl, { label: __('Show Grid', 'blockenberg'), checked: a.showGrid, onChange: function (v) { set({ showGrid: v }); }, __nextHasNoMarginBottom: true }),
229 el(ToggleControl, { label: __('Show Legend', 'blockenberg'), checked: a.showLegend, onChange: function (v) { set({ showLegend: v }); }, __nextHasNoMarginBottom: true }),
230 a.showLegend && el(SelectControl, { label: __('Legend Position', 'blockenberg'), value: a.legendPos, options: LEG_POS, onChange: function (v) { set({ legendPos: v }); } }),
231 el(ToggleControl, { label: __('Animate on load', 'blockenberg'), checked: a.animate, onChange: function (v) { set({ animate: v }); }, __nextHasNoMarginBottom: true })
232 ),
233 el(PanelBody, { title: __('Axes', 'blockenberg'), initialOpen: false },
234 el(TextControl, { label: __('X Axis Label', 'blockenberg'), value: a.xLabel, onChange: function (v) { set({ xLabel: v }); } }),
235 el(TextControl, { label: __('Y Axis Label', 'blockenberg'), value: a.yLabel, onChange: function (v) { set({ yLabel: v }); } }),
236 el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' } },
237 el(TextControl, { label: __('X Min', 'blockenberg'), type: 'number', value: a.xMin, onChange: function (v) { set({ xMin: v }); } }),
238 el(TextControl, { label: __('X Max', 'blockenberg'), type: 'number', value: a.xMax, onChange: function (v) { set({ xMax: v }); } }),
239 el(TextControl, { label: __('Y Min', 'blockenberg'), type: 'number', value: a.yMin, onChange: function (v) { set({ yMin: v }); } }),
240 el(TextControl, { label: __('Y Max', 'blockenberg'), type: 'number', value: a.yMax, onChange: function (v) { set({ yMax: v }); } })
241 )
242 ),
243 el(PanelBody, { title: __('Card Style', 'blockenberg'), initialOpen: false },
244 el(RangeControl, { label: __('Border Radius (px)', 'blockenberg'), value: a.borderRadius, min: 0, max: 32, onChange: function (v) { set({ borderRadius: v }); } })
245 ),
246 el(PanelColorSettings, { title: __('Colors', 'blockenberg'), initialOpen: false, colorSettings: [
247 { label: __('Card Background', 'blockenberg'), value: a.bgColor, onChange: function (v) { set({ bgColor: v || '#ffffff' }); } },
248 ]}),
249 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
250 el(RangeControl, { label: __('Title Font Size (px)', 'blockenberg'), value: a.titleFontSize, min: 10, max: 32, onChange: function (v) { set({ titleFontSize: v }); }, __nextHasNoMarginBottom: true }),
251 el(RangeControl, { label: __('Label / Axis Font Size (px)', 'blockenberg'), value: a.labelFontSize, min: 8, max: 24, onChange: function (v) { set({ labelFontSize: v }); }, __nextHasNoMarginBottom: true })
252 ),
253 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
254 el(RangeControl, { label: __('Padding Top', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { set({ paddingTop: v }); } }),
255 el(RangeControl, { label: __('Padding Bottom', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { set({ paddingBottom: v }); } })
256 )
257 ),
258 el('div', blockProps,
259 el(ScatterPreview, a)
260 )
261 );
262 },
263
264 save: function (props) {
265 var a = props.attributes;
266 var chartData = {
267 datasetsJson: a.datasetsJson,
268 fillAlpha: a.fillAlpha,
269 showLegend: a.showLegend,
270 legendPos: a.legendPos,
271 showTitle: a.showTitle,
272 chartTitle: a.chartTitle,
273 showSubtitle: a.showSubtitle,
274 chartSubtitle: a.chartSubtitle,
275 showGrid: a.showGrid,
276 pointSize: a.pointSize,
277 pointStyle: a.pointStyle,
278 animate: a.animate,
279 xLabel: a.xLabel,
280 yLabel: a.yLabel,
281 xMin: a.xMin,
282 xMax: a.xMax,
283 yMin: a.yMin,
284 yMax: a.yMax,
285 titleFontSize: a.titleFontSize,
286 labelFontSize: a.labelFontSize,
287 };
288 return el(
289 window.wp.blockEditor.useBlockProps.save({
290 className: 'bkbg-sc-wrapper',
291 style: { paddingTop: a.paddingTop + 'px', paddingBottom: a.paddingBottom + 'px', backgroundColor: a.bgColor || undefined },
292 }),
293 el('div', {
294 className: 'bkbg-sc-card',
295 'data-chart': JSON.stringify(chartData),
296 'data-height': a.chartHeight,
297 style: { background: a.bgColor, borderRadius: a.borderRadius + 'px', padding: '24px' },
298 },
299 el('div', { style: { position: 'relative', height: a.chartHeight + 'px' } },
300 el('canvas', { className: 'bkbg-sc-canvas', style: { height: a.chartHeight + 'px' } })
301 )
302 )
303 );
304 },
305 });
306 }() );
307