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

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

228 lines 14.5 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, ColorPicker, Popover } = window.wp.components;
6 const { __ } = window.wp.i18n;
7 const { useState } = window.wp.element;
8
9 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
10 var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
11
12 // ── Renderer ──────────────────────────────────────────────────────────────
13 function renderStepChart( a ) {
14 const series = a.series || [];
15 const xLabels = a.xLabels || [];
16 const W = a.svgWidth;
17 const H = a.svgHeight;
18 const PT = a.padTop;
19 const PL = a.padLeft;
20 const PR = a.padRight;
21 const PB = a.padBottom;
22
23 const allVals = series.flatMap( s => s.values || [] );
24 const maxVal = Math.max( ...allVals, 1 );
25 const minVal = 0;
26 const chartW = W - PL - PR;
27 const chartH = H - PT - PB;
28
29 // x positions evenly spaced at each point index
30 const nPts = Math.max( ...series.map( s => ( s.values || [] ).length ), xLabels.length, 2 );
31 function xAt( i ) { return PL + ( i / ( nPts - 1 ) ) * chartW; }
32 function yAt( v ) { return H - PB - ( ( v - minVal ) / ( maxVal - minVal ) ) * chartH; }
33
34 // Build step path for a series (step-after style: H then V)
35 function stepPath( values ) {
36 if ( !values || values.length === 0 ) return '';
37 let d = `M ${ xAt( 0 ) } ${ yAt( values[0] ) }`;
38 for ( let i = 1; i < values.length; i++ ) {
39 d += ` H ${ xAt( i ) } V ${ yAt( values[i] ) }`;
40 }
41 return d;
42 }
43
44 function fillPath( values ) {
45 if ( !values || values.length === 0 ) return '';
46 const base = H - PB;
47 let d = `M ${ xAt( 0 ) } ${ yAt( values[0] ) }`;
48 for ( let i = 1; i < values.length; i++ ) {
49 d += ` H ${ xAt( i ) } V ${ yAt( values[i] ) }`;
50 }
51 d += ` V ${ base } H ${ xAt( 0 ) } Z`;
52 return d;
53 }
54
55 const svgEls = [];
56 svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: H, fill: a.bgColor || '#ffffff', rx: 8 } ) );
57
58 // Grid lines (horizontal)
59 if ( a.showGridLines ) {
60 const ticks = 5;
61 for ( let i = 0; i <= ticks; i++ ) {
62 const v = minVal + ( maxVal - minVal ) * i / ticks;
63 const gy = yAt( v );
64 svgEls.push( el( 'line', { key: `gy${ i }`, x1: PL, y1: gy, x2: W - PR, y2: gy, stroke: a.gridColor || '#f3f4f6', strokeWidth: 1 } ) );
65 svgEls.push( el( 'text', { key: `gyl${ i }`, x: PL - 6, y: gy, textAnchor: 'end', dominantBaseline: 'middle', fill: '#9ca3af', fontSize: a.labelFontSize, fontFamily: 'inherit' }, String( parseFloat( v.toFixed( 0 ) ) ) ) );
66 }
67 }
68
69 // Axes
70 svgEls.push( el( 'line', { key: 'axX', x1: PL, y1: H - PB, x2: W - PR, y2: H - PB, stroke: '#d1d5db', strokeWidth: 1 } ) );
71 svgEls.push( el( 'line', { key: 'axY', x1: PL, y1: PT, x2: PL, y2: H - PB, stroke: '#d1d5db', strokeWidth: 1 } ) );
72
73 // X-axis labels
74 for ( let i = 0; i < nPts; i++ ) {
75 const lbl = xLabels[i] !== undefined ? String( xLabels[i] ) : String( i + 1 );
76 svgEls.push( el( 'text', { key: `xl${ i }`, x: xAt( i ), y: H - PB + 16, textAnchor: 'middle', fill: '#9ca3af', fontSize: a.labelFontSize, fontFamily: 'inherit' }, lbl ) );
77 }
78
79 // Series
80 series.forEach( ( ser, si ) => {
81 const vals = ser.values || [];
82 const clr = ser.color || '#6366f1';
83
84 // Fill area
85 if ( a.fillArea ) {
86 svgEls.push( el( 'path', { key: `fill${ si }`, d: fillPath( vals ), fill: clr, opacity: ( a.fillOpacity || 20 ) / 100 } ) );
87 }
88
89 // Step line
90 svgEls.push( el( 'path', { key: `line${ si }`, d: stepPath( vals ), fill: 'none', stroke: clr, strokeWidth: a.lineThickness, strokeLinecap: 'round', strokeLinejoin: 'round' } ) );
91
92 // Dots and value labels
93 vals.forEach( ( v, i ) => {
94 const cx = xAt( i );
95 const cy = yAt( v );
96 if ( a.showDots ) {
97 svgEls.push( el( 'circle', { key: `dot${ si }${ i }`, cx, cy, r: a.dotRadius, fill: clr, stroke: '#ffffff', strokeWidth: 1.5 } ) );
98 }
99 if ( a.showValues ) {
100 svgEls.push( el( 'text', { key: `val${ si }${ i }`, x: cx, y: cy - a.dotRadius - 4, textAnchor: 'middle', fill: clr, fontSize: a.valueFontSize, fontFamily: 'inherit', fontWeight: 600 }, String( v ) ) );
101 }
102 } );
103 } );
104
105 // Legend
106 if ( a.showLegend && series.length > 0 ) {
107 series.forEach( ( ser, si ) => {
108 const lx = PL + si * 130;
109 const ly = PT - 14;
110 svgEls.push( el( 'line', { key: `ll${ si }`, x1: lx, y1: ly, x2: lx + 22, y2: ly, stroke: ser.color, strokeWidth: 3 } ) );
111 svgEls.push( el( 'circle', { key: `lc${ si }`, cx: lx + 11, cy: ly, r: 4, fill: ser.color } ) );
112 svgEls.push( el( 'text', { key: `lt${ si }`, x: lx + 26, y: ly, dominantBaseline: 'middle', fill: a.textColor, fontSize: 11, fontFamily: 'inherit' }, ser.label ) );
113 } );
114 }
115
116 return el( 'svg', { viewBox: `0 0 ${ W } ${ H }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
117 }
118
119 function updSer( setAttributes, series, si, field, val ) {
120 setAttributes( { series: series.map( ( s, i ) => i === si ? { ...s, [field]: val } : s ) } );
121 }
122
123 function parseVals( raw ) {
124 return raw.split( /[\s,]+/ ).map( Number ).filter( v => !isNaN( v ) );
125 }
126
127 /* ── colour-swatch + popover ── */
128 function BkbgColorSwatch(p) {
129 var st = useState(false), open = st[0], setOpen = st[1];
130 return el('div', { style:{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 0', gap:'8px' } },
131 el('span', { style:{ fontSize:'12px', color:'#1e1e1e', flex:1, lineHeight:1.4 } }, p.label),
132 el('div', { style:{ position:'relative', flexShrink:0 } },
133 el('button', { type:'button', title: p.value||'none', onClick: function(){ setOpen(!open); },
134 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 } }),
135 open && el(Popover, { position:'bottom left', onClose: function(){ setOpen(false); } },
136 el('div', { style:{ padding:'8px' }, onMouseDown: function(e){ e.stopPropagation(); } },
137 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'6px' } },
138 el('strong', { style:{ fontSize:'12px' } }, p.label),
139 el(Button, { icon:'no-alt', isSmall:true, onClick: function(){ setOpen(false); } })
140 ),
141 el(ColorPicker, { color: p.value, enableAlpha:true, onChange: p.onChange })
142 )
143 )
144 )
145 );
146 }
147
148 registerBlockType( 'blockenberg/step-chart', {
149 edit: function ( props ) {
150 const { attributes: a, setAttributes } = props;
151 const blockProps = useBlockProps( (function () {
152 var _tvf = getTypoCssVars();
153 var s = { '--bkst-title-color': a.titleColor };
154 Object.assign(s, _tvf(a.titleTypo, '--bkst-tt-'));
155 return { className: 'bkbg-step-wrap', style: s };
156 })() );
157
158 return el( 'div', blockProps,
159 el( InspectorControls, {},
160
161 el( PanelBody, { title: __( 'Chart Settings', 'blockenberg' ), initialOpen: true },
162 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
163 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
164 el( ToggleControl, { label: __( 'Fill Area', 'blockenberg' ), checked: a.fillArea, onChange: v => setAttributes( { fillArea: v } ) } ),
165 el( ToggleControl, { label: __( 'Show Grid Lines', 'blockenberg' ), checked: a.showGridLines, onChange: v => setAttributes( { showGridLines: v } ) } ),
166 el( ToggleControl, { label: __( 'Show Dots', 'blockenberg' ), checked: a.showDots, onChange: v => setAttributes( { showDots: v } ) } ),
167 el( ToggleControl, { label: __( 'Show Values', 'blockenberg' ), checked: a.showValues, onChange: v => setAttributes( { showValues: v } ) } ),
168 el( ToggleControl, { label: __( 'Show Legend', 'blockenberg' ), checked: a.showLegend, onChange: v => setAttributes( { showLegend: v } ) } ),
169 el( TextControl, { label: __( 'X-Axis Labels (comma separated)', 'blockenberg' ), value: ( a.xLabels || [] ).join( ', ' ), onChange: v => setAttributes( { xLabels: v.split( /[\s,]+/ ).filter( Boolean ) } ) } ),
170 ),
171
172 el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false },
173 el( RangeControl, { label: __( 'Canvas Width', 'blockenberg' ), value: a.svgWidth, onChange: v => setAttributes( { svgWidth: v } ), min: 300, max: 1200, step: 20 } ),
174 el( RangeControl, { label: __( 'Canvas Height', 'blockenberg' ), value: a.svgHeight, onChange: v => setAttributes( { svgHeight: v } ), min: 120, max: 700 } ),
175 el( RangeControl, { label: __( 'Line Thickness', 'blockenberg' ), value: a.lineThickness, onChange: v => setAttributes( { lineThickness: v } ), min: 1, max: 10 } ),
176 el( RangeControl, { label: __( 'Dot Radius', 'blockenberg' ), value: a.dotRadius, onChange: v => setAttributes( { dotRadius: v } ), min: 2, max: 18 } ),
177 el( RangeControl, { label: __( 'Fill Opacity %', 'blockenberg' ), value: a.fillOpacity, onChange: v => setAttributes( { fillOpacity: v } ), min: 0, max: 70 } ),
178 ),
179
180
181 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
182 getTypoControl() && getTypoControl()({ label: __('Title', 'blockenberg'), value: a.titleTypo, onChange: function (v) { setAttributes({ titleTypo: v }); } }),
183 el( RangeControl, { label: __( 'Label Font Size', 'blockenberg' ), value: a.labelFontSize, onChange: v => setAttributes( { labelFontSize: v } ), min: 8, max: 22 } ),
184 el( RangeControl, { label: __( 'Value Font Size', 'blockenberg' ), value: a.valueFontSize, onChange: v => setAttributes( { valueFontSize: v } ), min: 7, max: 16 } )
185 ),
186 el( PanelColorSettings, {
187 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
188 colorSettings: [
189 { label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
190 { label: __( 'Grid Lines', 'blockenberg' ), value: a.gridColor, onChange: v => setAttributes( { gridColor: v || '#f3f4f6' } ) },
191 { label: __( 'Title Color', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
192 { label: __( 'Text Color', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#374151' } ) },
193 ]
194 } ),
195
196 el( PanelBody, { title: __( 'Series', 'blockenberg' ), initialOpen: false },
197 el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { series: [ ...a.series, { label: 'New Series', color: '#6366f1', values: [10, 20, 20, 40] } ] } ) }, __( '+ Add Series', 'blockenberg' ) ),
198 a.series.map( ( ser, si ) =>
199 el( PanelBody, { key: si, title: ser.label || `Series ${ si + 1 }`, initialOpen: false },
200 el( TextControl, { label: __( 'Label', 'blockenberg' ), value: ser.label, onChange: v => updSer( setAttributes, a.series, si, 'label', v ) } ),
201 el( BkbgColorSwatch, { label: __( 'Color', 'blockenberg' ), value: ser.color, onChange: v => updSer( setAttributes, a.series, si, 'color', v ) } ),
202 el( TextControl, { label: __( 'Values (comma separated)', 'blockenberg' ), value: ( ser.values || [] ).join( ', ' ), onChange: v => updSer( setAttributes, a.series, si, 'values', parseVals( v ) ) } ),
203 el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { series: a.series.filter( ( _, x ) => x !== si ) } ) }, __( 'Remove', 'blockenberg' ) ),
204 )
205 ),
206 ),
207 ),
208
209 a.showTitle && a.title && el( 'h3', { className: 'bkbg-step-title' }, a.title ),
210 el( 'div', { className: 'bkbg-step-svg' }, renderStepChart( a ) ),
211 );
212 },
213
214 save: function ( { attributes: a } ) {
215 const blockProps = useBlockProps.save( (function () {
216 var _tvf = getTypoCssVars();
217 var s = { '--bkst-title-color': a.titleColor };
218 Object.assign(s, _tvf(a.titleTypo, '--bkst-tt-'));
219 return { className: 'bkbg-step-wrap', style: s };
220 })() );
221 return el( 'div', blockProps,
222 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-step-title' }, a.title ) : null,
223 el( 'div', { className: 'bkbg-step-svg' }, renderStepChart( a ) ),
224 );
225 },
226 } );
227 }() );
228