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

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

297 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 const el = window.wp.element.createElement;
3 const { registerBlockType } = window.wp.blocks;
4 const { InspectorControls, useBlockProps, PanelColorSettings } = window.wp.blockEditor;
5 const { PanelBody, RangeControl, SelectControl, ToggleControl, TextControl, Button } = window.wp.components;
6 const { __ } = window.wp.i18n;
7
8 var _tc, _tv;
9 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
10 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
11
12 /* ---- SVG pie math ---- */
13 function buildPieSlices(slices, cx, cy, r) {
14 const total = slices.reduce((s, sl) => s + (Number(sl.value) || 0), 0);
15 if (!total) return [];
16 let ang = -Math.PI / 2;
17 return slices.map(sl => {
18 const frac = (Number(sl.value) || 0) / total;
19 const sweep = frac * 2 * Math.PI;
20 const end = ang + sweep;
21 const x1 = cx + r * Math.cos(ang), y1 = cy + r * Math.sin(ang);
22 const x2 = cx + r * Math.cos(end), y2 = cy + r * Math.sin(end);
23 const large = sweep > Math.PI ? 1 : 0;
24 const mid = ang + sweep / 2;
25 const lx = cx + (r * 0.62) * Math.cos(mid);
26 const ly = cy + (r * 0.62) * Math.sin(mid);
27 const pct = Math.round(frac * 100);
28 const path = `M ${cx} ${cy} L ${x1.toFixed(2)} ${y1.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${x2.toFixed(2)} ${y2.toFixed(2)} Z`;
29 ang = end;
30 return { path, color: sl.color, label: sl.label, pct, lx: lx.toFixed(2), ly: ly.toFixed(2) };
31 });
32 }
33
34 function renderSVG(a) {
35 const half = a.size / 2;
36 const r = half - 4;
37 const slices = buildPieSlices(a.slices, half, half, r);
38 return el('svg', {
39 className: 'bkbg-piec-svg',
40 width: a.size, height: a.size,
41 viewBox: `0 0 ${a.size} ${a.size}`,
42 },
43 slices.map((s, i) =>
44 el('path', {
45 key: i,
46 d: s.path,
47 fill: s.color,
48 stroke: a.strokeColor,
49 strokeWidth: a.strokeWidth,
50 })
51 ),
52 a.showLabels && a.showPercentages && slices.map((s, i) =>
53 s.pct > 5 && el('text', {
54 key: 'lbl-' + i,
55 x: s.lx, y: s.ly,
56 textAnchor: 'middle',
57 dominantBaseline: 'middle',
58 fill: '#ffffff',
59 fontSize: 13,
60 fontWeight: 700,
61 fontFamily: 'inherit',
62 }, s.pct + '%')
63 ),
64 );
65 }
66
67 function renderLegend(a) {
68 return el('ul', { className: 'bkbg-piec-legend' },
69 a.slices.map((s, i) =>
70 el('li', { key: i, className: 'bkbg-piec-legend-item' },
71 el('span', { className: 'bkbg-piec-legend-dot', style: { background: s.color } }),
72 el('span', { className: 'bkbg-piec-legend-label' }, s.label),
73 )
74 )
75 );
76 }
77
78 function wrapStyle(a) {
79 return {
80 '--bkbg-piec-bg': a.bgColor,
81 '--bkbg-piec-title-c': a.titleColor,
82 '--bkbg-piec-sub-c': a.subtitleColor,
83 '--bkbg-piec-legend-c': a.legendColor,
84 '--bkbg-piec-radius': a.borderRadius + 'px',
85 '--bkbg-piec-pt': a.paddingTop + 'px',
86 '--bkbg-piec-pb': a.paddingBottom + 'px',
87 };
88 }
89
90 function wrapStyleLegacy(a) {
91 return {
92 '--bkbg-piec-bg': a.bgColor,
93 '--bkbg-piec-title-c': a.titleColor,
94 '--bkbg-piec-sub-c': a.subtitleColor,
95 '--bkbg-piec-legend-c': a.legendColor,
96 '--bkbg-piec-title-sz': a.titleSize + 'px',
97 '--bkbg-piec-title-w': a.titleWeight,
98 '--bkbg-piec-sub-sz': a.subtitleSize + 'px',
99 '--bkbg-piec-legend-sz': a.legendSize + 'px',
100 '--bkbg-piec-radius': a.borderRadius + 'px',
101 '--bkbg-piec-pt': a.paddingTop + 'px',
102 '--bkbg-piec-pb': a.paddingBottom + 'px',
103 };
104 }
105
106 const LEGEND_POSITIONS = [
107 { label: 'Right', value: 'right' },
108 { label: 'Bottom', value: 'bottom' },
109 { label: 'Left', value: 'left' },
110 { label: 'Top', value: 'top' },
111 ];
112 const LAYOUTS = [
113 { label: 'Side (chart + legend)', value: 'side' },
114 { label: 'Stacked (chart on top)', value: 'stacked' },
115 ];
116 const ALIGNS = [
117 { label: 'Left', value: 'left' },
118 { label: 'Center', value: 'center' },
119 { label: 'Right', value: 'right' },
120 ];
121
122 registerBlockType('blockenberg/pie-chart', {
123 edit: function (props) {
124 const { attributes: a, setAttributes } = props;
125
126 function updateSlice(i, key, val) {
127 const next = a.slices.slice();
128 next[i] = Object.assign({}, next[i], { [key]: val });
129 setAttributes({ slices: next });
130 }
131 function removeSlice(i) { setAttributes({ slices: a.slices.filter((_, x) => x !== i) }); }
132 function addSlice() {
133 const colors = ['#6c3fb5','#3b82f6','#10b981','#f59e0b','#ef4444','#8b5cf6','#06b6d4'];
134 setAttributes({ slices: [...a.slices, { label: 'New Slice', value: 10, color: colors[a.slices.length % colors.length] }] });
135 }
136
137 const blockProps = useBlockProps((function () {
138 var tv = getTypoCssVars();
139 var s = wrapStyle(a);
140 Object.assign(s, tv(a.titleTypo, '--bkbg-piec-tt-'));
141 Object.assign(s, tv(a.subtitleTypo, '--bkbg-piec-st-'));
142 Object.assign(s, tv(a.legendTypo, '--bkbg-piec-lg-'));
143 return { className: 'bkbg-piec-wrap bkbg-piec-layout--' + a.layout + ' bkbg-piec-title-align--' + a.titleAlign, style: s };
144 })());
145
146 return el('div', blockProps,
147 el(InspectorControls, null,
148
149 el(PanelBody, { title: __('Chart Data'), initialOpen: true },
150 a.slices.map((sl, i) =>
151 el('div', { key: i, style: { border: '1px solid #e2e8f0', borderRadius: 8, padding: '10px', marginBottom: 8 } },
152 el(TextControl, { label: __('Label'), value: sl.label, onChange: v => updateSlice(i, 'label', v) }),
153 el(RangeControl, { label: __('Value'), value: sl.value, min: 1, max: 10000, onChange: v => updateSlice(i, 'value', v) }),
154 el('div', { style: { marginBottom: 8 } },
155 el('label', { style: { fontSize: 11, fontWeight: 600, display: 'block', marginBottom: 4 } }, __('Color')),
156 el('input', { type: 'color', value: sl.color, onChange: e => updateSlice(i, 'color', e.target.value), style: { width: '100%', height: 32, border: '1px solid #ccc', borderRadius: 4, cursor: 'pointer' } }),
157 ),
158 a.slices.length > 2 && el(Button, { isDestructive: true, isSmall: true, onClick: () => removeSlice(i) }, __('Remove')),
159 )
160 ),
161 el(Button, { variant: 'secondary', onClick: addSlice, style: { width: '100%', justifyContent: 'center', marginTop: 8 } }, __('+ Add Slice')),
162 ),
163
164 el(PanelBody, { title: __('Title & Labels'), initialOpen: false },
165 el(ToggleControl, { label: __('Show Title'), checked: a.showTitle, onChange: v => setAttributes({ showTitle: v }), __nextHasNoMarginBottom: true }),
166 a.showTitle && el(TextControl, { label: __('Title'), value: a.title, onChange: v => setAttributes({ title: v }) }),
167 el(ToggleControl, { label: __('Show Subtitle'), checked: a.showSubtitle, onChange: v => setAttributes({ showSubtitle: v }), __nextHasNoMarginBottom: true }),
168 a.showSubtitle && el(TextControl, { label: __('Subtitle'), value: a.subtitle, onChange: v => setAttributes({ subtitle: v }) }),
169 el(SelectControl, { label: __('Title Align'), value: a.titleAlign, options: ALIGNS, onChange: v => setAttributes({ titleAlign: v }) }),
170 el(ToggleControl, { label: __('Show % Labels on Slices'), checked: a.showLabels && a.showPercentages, onChange: v => setAttributes({ showLabels: v, showPercentages: v }), __nextHasNoMarginBottom: true }),
171 el(ToggleControl, { label: __('Show Legend'), checked: a.showLegend, onChange: v => setAttributes({ showLegend: v }), __nextHasNoMarginBottom: true }),
172 a.showLegend && el(SelectControl, { label: __('Legend Position'), value: a.legendPosition, options: LEGEND_POSITIONS, onChange: v => setAttributes({ legendPosition: v }) }),
173 ),
174
175 el(PanelBody, { title: __('Chart Dimensions'), initialOpen: false },
176 el(RangeControl, { label: __('Chart Diameter (px)'), value: a.size, min: 140, max: 500, onChange: v => setAttributes({ size: v }) }),
177 el(RangeControl, { label: __('Stroke Width'), value: a.strokeWidth, min: 0, max: 8, onChange: v => setAttributes({ strokeWidth: v }) }),
178 el(SelectControl, { label: __('Layout'), value: a.layout, options: LAYOUTS, onChange: v => setAttributes({ layout: v }) }),
179 ),
180
181 el(PanelBody, { title: __('Spacing & Style'), initialOpen: false },
182 el(RangeControl, { label: __('Border Radius (px)'), value: a.borderRadius, min: 0, max: 32, onChange: v => setAttributes({ borderRadius: v }) }),
183 el(RangeControl, { label: __('Padding Top (px)'), value: a.paddingTop, min: 0, max: 160, onChange: v => setAttributes({ paddingTop: v }) }),
184 el(RangeControl, { label: __('Padding Bottom (px)'), value: a.paddingBottom, min: 0, max: 160, onChange: v => setAttributes({ paddingBottom: v }) }),
185
186 ),
187
188
189 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
190 el(getTypoControl(), { label: __('Title', 'blockenberg'), value: a.titleTypo, onChange: v => setAttributes({ titleTypo: v }) }),
191 el(getTypoControl(), { label: __('Subtitle', 'blockenberg'), value: a.subtitleTypo, onChange: v => setAttributes({ subtitleTypo: v }) }),
192 el(getTypoControl(), { label: __('Legend', 'blockenberg'), value: a.legendTypo, onChange: v => setAttributes({ legendTypo: v }) })
193 ),
194 el(PanelColorSettings, {
195 title: __('Colors'), initialOpen: false,
196 colorSettings: [
197 { label: __('Background'), value: a.bgColor, onChange: v => setAttributes({ bgColor: v || '#ffffff' }) },
198 { label: __('Title'), value: a.titleColor, onChange: v => setAttributes({ titleColor: v || '#0f172a' }) },
199 { label: __('Subtitle'), value: a.subtitleColor, onChange: v => setAttributes({ subtitleColor: v || '#64748b' }) },
200 { label: __('Legend Text'), value: a.legendColor, onChange: v => setAttributes({ legendColor: v || '#374151' }) },
201 { label: __('Slice Stroke'), value: a.strokeColor, onChange: v => setAttributes({ strokeColor: v || '#ffffff' }) },
202 ]
203 }),
204 ),
205
206 /* ---- Canvas ---- */
207 a.showTitle && el('h3', { className: 'bkbg-piec-title' }, a.title),
208 a.showSubtitle && el('p', { className: 'bkbg-piec-subtitle' }, a.subtitle),
209 el('div', { className: 'bkbg-piec-chart-row bkbg-piec-legend-pos--' + a.legendPosition },
210 renderSVG(a),
211 a.showLegend && renderLegend(a),
212 ),
213 );
214 },
215
216 save: function ({ attributes: a }) {
217 var tv = getTypoCssVars();
218 var s = wrapStyle(a);
219 Object.assign(s, tv(a.titleTypo, '--bkbg-piec-tt-'));
220 Object.assign(s, tv(a.subtitleTypo, '--bkbg-piec-st-'));
221 Object.assign(s, tv(a.legendTypo, '--bkbg-piec-lg-'));
222 return el('div', {
223 className: 'bkbg-piec-wrap bkbg-piec-layout--' + a.layout + ' bkbg-piec-title-align--' + a.titleAlign,
224 style: s,
225 },
226 a.showTitle && el('h3', { className: 'bkbg-piec-title' }, a.title),
227 a.showSubtitle && el('p', { className: 'bkbg-piec-subtitle' }, a.subtitle),
228 el('div', { className: 'bkbg-piec-chart-row bkbg-piec-legend-pos--' + a.legendPosition },
229 renderSVG(a),
230 a.showLegend && el('ul', { className: 'bkbg-piec-legend' },
231 a.slices.map((s, i) =>
232 el('li', { key: i, className: 'bkbg-piec-legend-item' },
233 el('span', { className: 'bkbg-piec-legend-dot', style: { background: s.color } }),
234 el('span', { className: 'bkbg-piec-legend-label' }, s.label),
235 )
236 )
237 ),
238 ),
239 );
240 },
241
242 deprecated: [
243 {
244 attributes: {
245 title: { type: 'string', default: 'Revenue by Channel' },
246 showTitle: { type: 'boolean', default: true },
247 subtitle: { type: 'string', default: 'Q1 2025 breakdown' },
248 showSubtitle: { type: 'boolean', default: true },
249 slices: { type: 'array', default: [{label:'Organic Search',value:42,color:'#6c3fb5'},{label:'Direct',value:28,color:'#3b82f6'},{label:'Referral',value:18,color:'#10b981'},{label:'Social Media',value:12,color:'#f59e0b'}] },
250 size: { type: 'integer', default: 280 },
251 showLabels: { type: 'boolean', default: true },
252 showPercentages: { type: 'boolean', default: true },
253 showLegend: { type: 'boolean', default: true },
254 legendPosition: { type: 'string', default: 'right' },
255 strokeWidth: { type: 'integer', default: 2 },
256 strokeColor: { type: 'string', default: '#ffffff' },
257 layout: { type: 'string', default: 'side' },
258 titleAlign: { type: 'string', default: 'center' },
259 borderRadius: { type: 'integer', default: 16 },
260 paddingTop: { type: 'integer', default: 48 },
261 paddingBottom: { type: 'integer', default: 48 },
262 titleSize: { type: 'integer', default: 24 },
263 titleWeight: { type: 'integer', default: 700 },
264 subtitleSize: { type: 'integer', default: 15 },
265 legendSize: { type: 'integer', default: 14 },
266 bgColor: { type: 'string', default: '#ffffff' },
267 titleColor: { type: 'string', default: '#0f172a' },
268 subtitleColor: { type: 'string', default: '#64748b' },
269 legendColor: { type: 'string', default: '#374151' },
270 titleFontWeight: { type: 'string', default: '700' },
271 subtitleFontWeight: { type: 'string', default: '400' },
272 },
273 save: function ({ attributes: a }) {
274 return el('div', {
275 className: 'bkbg-piec-wrap bkbg-piec-layout--' + a.layout + ' bkbg-piec-title-align--' + a.titleAlign,
276 style: wrapStyleLegacy(a),
277 },
278 a.showTitle && el('h3', { className: 'bkbg-piec-title' }, a.title),
279 a.showSubtitle && el('p', { className: 'bkbg-piec-subtitle' }, a.subtitle),
280 el('div', { className: 'bkbg-piec-chart-row bkbg-piec-legend-pos--' + a.legendPosition },
281 renderSVG(a),
282 a.showLegend && el('ul', { className: 'bkbg-piec-legend' },
283 a.slices.map((s, i) =>
284 el('li', { key: i, className: 'bkbg-piec-legend-item' },
285 el('span', { className: 'bkbg-piec-legend-dot', style: { background: s.color } }),
286 el('span', { className: 'bkbg-piec-legend-label', style: { color: a.legendColor, fontSize: a.legendSize + 'px' } }, s.label),
287 )
288 )
289 ),
290 ),
291 );
292 }
293 }
294 ]
295 });
296 }() );
297