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 / heat-map / index.js

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

183 lines 11.8 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, ToggleControl, TextControl, TextareaControl, Button } = window.wp.components;
6 const { __ } = window.wp.i18n;
7
8 /* ── Typography lazy getters ──────────────────────────────── */
9 var _ttTC, _ttTV;
10 function _tc() { return _ttTC || (_ttTC = window.bkbgTypographyControl); }
11 function _tv() { return _ttTV || (_ttTV = window.bkbgTypoCssVars); }
12
13 // ── Colour lerp helper ────────────────────────────────────────────────────
14 function hexToRgb( hex ) {
15 const h = hex.replace( '#', '' );
16 return [ parseInt( h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16) ];
17 }
18 function lerpColor( hexA, hexB, t ) {
19 const a = hexToRgb( hexA ), b = hexToRgb( hexB );
20 const r = Math.round( a[0] + ( b[0] - a[0] ) * t );
21 const g = Math.round( a[1] + ( b[1] - a[1] ) * t );
22 const bl = Math.round( a[2] + ( b[2] - a[2] ) * t );
23 return `rgb(${ r },${ g },${ bl })`;
24 }
25 function cellColor( v, minV, maxV, colorLow, colorMid, colorHigh ) {
26 const t = maxV === minV ? 0.5 : ( v - minV ) / ( maxV - minV );
27 if ( t <= 0.5 ) return lerpColor( colorLow, colorMid, t * 2 );
28 return lerpColor( colorMid, colorHigh, ( t - 0.5 ) * 2 );
29 }
30 function textOnBg( hex ) {
31 const [ r, g, b ] = hexToRgb( hex );
32 const lum = 0.299 * r + 0.587 * g + 0.114 * b;
33 return lum > 160 ? '#111827' : '#ffffff';
34 }
35
36 // ── Renderer ──────────────────────────────────────────────────────────────
37 function renderHeatMap( a ) {
38 const rows = a.rowLabels || [];
39 const cols = a.colLabels || [];
40 const vals = a.cellValues || [];
41 const nR = rows.length;
42 const nC = cols.length;
43 if ( ! nR || ! nC ) return el( 'svg', { viewBox: '0 0 400 200', width: '100%' } );
44
45 const CS = a.cellSize;
46 const GAP = a.cellGap;
47 const PT = a.padTop;
48 const PL = a.padLeft;
49 const PB = a.padBottom;
50 const PR = a.padRight;
51 const W = PL + nC * ( CS + GAP ) - GAP + PR;
52 const legendH = a.showLegend ? 30 : 0;
53 const H = PT + nR * ( CS + GAP ) - GAP + PB + legendH;
54
55 const minV = Math.min( ...vals );
56 const maxV = Math.max( ...vals );
57
58 const svgEls = [];
59 svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: H, fill: a.bgColor || '#ffffff', rx: 8 } ) );
60
61 // Column headers
62 cols.forEach( ( c, ci ) => {
63 const cx = PL + ci * ( CS + GAP ) + CS / 2;
64 svgEls.push( el( 'text', { key: `ch${ ci }`, x: cx, y: PT - 8, textAnchor: 'middle', fill: a.textColor, fontSize: a.labelFontSize, fontFamily: 'inherit', transform: `rotate(-40,${ cx },${ PT - 8 })` }, c ) );
65 } );
66
67 // Row headers + cells
68 rows.forEach( ( r, ri ) => {
69 const cy = PT + ri * ( CS + GAP ) + CS / 2;
70 svgEls.push( el( 'text', { key: `rh${ ri }`, x: PL - 8, y: cy, textAnchor: 'end', dominantBaseline: 'middle', fill: a.textColor, fontSize: a.labelFontSize, fontFamily: 'inherit' }, r ) );
71
72 cols.forEach( ( _, ci ) => {
73 const v = vals[ ri * nC + ci ] !== undefined ? vals[ ri * nC + ci ] : 0;
74 const bg = cellColor( v, minV, maxV, a.colorLow || '#dbeafe', a.colorMid || '#93c5fd', a.colorHigh || '#1d4ed8' );
75 const cx = PL + ci * ( CS + GAP );
76 const cy2 = PT + ri * ( CS + GAP );
77 svgEls.push( el( 'rect', { key: `cell${ ri }${ ci }`, x: cx, y: cy2, width: CS, height: CS, fill: bg, rx: 4 } ) );
78 if ( a.showValues ) {
79 const fgColor = textOnBg( bg.startsWith('rgb') ? '#888888' : bg );
80 svgEls.push( el( 'text', { key: `cv${ ri }${ ci }`, x: cx + CS / 2, y: cy2 + CS / 2, textAnchor: 'middle', dominantBaseline: 'middle', fill: '#333333', fontSize: a.valueFontSize, fontFamily: 'inherit', fontWeight: 600 }, parseFloat( v.toFixed(2) ) ) );
81 }
82 } );
83 } );
84
85 // Legend
86 if ( a.showLegend ) {
87 const legY = PT + nR * ( CS + GAP ) - GAP + 12;
88 const legW = Math.min( 200, nC * ( CS + GAP ) );
89 const legX = PL;
90 const steps = 20;
91 for ( let s = 0; s < steps; s++ ) {
92 const t = s / ( steps - 1 );
93 const lc = cellColor( minV + t * ( maxV - minV ), minV, maxV, a.colorLow || '#dbeafe', a.colorMid || '#93c5fd', a.colorHigh || '#1d4ed8' );
94 svgEls.push( el( 'rect', { key: `leg${ s }`, x: legX + ( s / steps ) * legW, y: legY, width: legW / steps + 1, height: 12, fill: lc } ) );
95 }
96 svgEls.push( el( 'text', { key: 'legMin', x: legX, y: legY + 24, fill: a.textColor, fontSize: 10, fontFamily: 'inherit' }, parseFloat( minV.toFixed(2) ) ) );
97 svgEls.push( el( 'text', { key: 'legMax', x: legX + legW, y: legY + 24, textAnchor: 'end', fill: a.textColor, fontSize: 10, fontFamily: 'inherit' }, parseFloat( maxV.toFixed(2) ) ) );
98 }
99
100 return el( 'svg', { viewBox: `0 0 ${ W } ${ H }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
101 }
102
103 registerBlockType( 'blockenberg/heat-map', {
104 edit: function ( props ) {
105 const { attributes: a, setAttributes } = props;
106 const blockProps = useBlockProps( { className: 'bkbg-heatmap-wrap', style: Object.assign( {}, _tv() && _tv()( a.typoTitle, '--bkbg-hm-tt-' ) ) } );
107
108 return el( 'div', blockProps,
109 el( InspectorControls, {},
110
111 el( PanelBody, { title: __( 'Chart Settings', 'blockenberg' ), initialOpen: true },
112 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
113 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
114 el( ToggleControl, { label: __( 'Show Values', 'blockenberg' ), checked: a.showValues, onChange: v => setAttributes( { showValues: v } ) } ),
115 el( ToggleControl, { label: __( 'Show Legend', 'blockenberg' ), checked: a.showLegend, onChange: v => setAttributes( { showLegend: v } ) } ),
116 ),
117
118 el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false },
119 el( RangeControl, { label: __( 'Cell Size (px)', 'blockenberg' ), value: a.cellSize, onChange: v => setAttributes( { cellSize: v } ), min: 20, max: 120 } ),
120 el( RangeControl, { label: __( 'Cell Gap (px)', 'blockenberg' ), value: a.cellGap, onChange: v => setAttributes( { cellGap: v } ), min: 0, max: 12 } ),
121 el( RangeControl, { label: __( 'Row Label Width', 'blockenberg' ), value: a.padLeft, onChange: v => setAttributes( { padLeft: v } ), min: 40, max: 240 } ),
122 el( RangeControl, { label: __( 'Column Header Height', 'blockenberg' ), value: a.padTop, onChange: v => setAttributes( { padTop: v } ), min: 30, max: 160 } ),
123 ),
124
125 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
126 _tc() && _tc()({ label: __('Title','blockenberg'), value: a.typoTitle, onChange: v => setAttributes( { typoTitle: v } ) }),
127 el( RangeControl, { label: __( 'Label Font Size', 'blockenberg' ), value: a.labelFontSize, onChange: v => setAttributes( { labelFontSize: v } ), min: 8, max: 20 } ),
128 el( RangeControl, { label: __( 'Value Font Size', 'blockenberg' ), value: a.valueFontSize, onChange: v => setAttributes( { valueFontSize: v } ), min: 7, max: 18 } )
129 ),
130 el( PanelColorSettings, {
131 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
132 colorSettings: [
133 { label: __( 'Low value', 'blockenberg' ), value: a.colorLow, onChange: v => setAttributes( { colorLow: v || '#dbeafe' } ) },
134 { label: __( 'Mid value', 'blockenberg' ), value: a.colorMid, onChange: v => setAttributes( { colorMid: v || '#93c5fd' } ) },
135 { label: __( 'High value', 'blockenberg' ), value: a.colorHigh, onChange: v => setAttributes( { colorHigh: v || '#1d4ed8' } ) },
136 { label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
137 { label: __( 'Title', 'blockenberg' ), value: a.titleColor,onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
138 { label: __( 'Labels/Text', 'blockenberg' ), value: a.textColor, onChange: v => setAttributes( { textColor: v || '#374151' } ) },
139 ]
140 } ),
141
142 el( PanelBody, { title: __( 'Row Labels', 'blockenberg' ), initialOpen: false },
143 el( TextareaControl, {
144 label: __( 'One label per line', 'blockenberg' ),
145 value: ( a.rowLabels || [] ).join( '\n' ),
146 onChange: v => setAttributes( { rowLabels: v.split( '\n' ).map( s => s.trim() ).filter( Boolean ) } ),
147 } ),
148 ),
149
150 el( PanelBody, { title: __( 'Column Labels', 'blockenberg' ), initialOpen: false },
151 el( TextareaControl, {
152 label: __( 'One label per line', 'blockenberg' ),
153 value: ( a.colLabels || [] ).join( '\n' ),
154 onChange: v => setAttributes( { colLabels: v.split( '\n' ).map( s => s.trim() ).filter( Boolean ) } ),
155 } ),
156 ),
157
158 el( PanelBody, { title: __( 'Cell Values', 'blockenberg' ), initialOpen: false },
159 el( 'p', { style: { fontSize: 12, color: '#6b7280', marginBottom: 8 } }, __( 'Row-major order, comma-separated. Rows × Columns values.', 'blockenberg' ) ),
160 el( TextareaControl, {
161 label: __( 'Values', 'blockenberg' ),
162 value: ( a.cellValues || [] ).join( ', ' ),
163 onChange: v => setAttributes( { cellValues: v.split( ',' ).map( n => parseFloat( n.trim() ) || 0 ) } ),
164 rows: 6,
165 } ),
166 ),
167 ),
168
169 a.showTitle && a.title && el( 'h3', { className: 'bkbg-heatmap-title', style: { color: a.titleColor } }, a.title ),
170 el( 'div', { className: 'bkbg-heatmap-svg' }, renderHeatMap( a ) ),
171 );
172 },
173
174 save: function ( { attributes: a } ) {
175 const blockProps = useBlockProps.save( { className: 'bkbg-heatmap-wrap', style: Object.assign( {}, _tv() && _tv()( a.typoTitle, '--bkbg-hm-tt-' ) ) } );
176 return el( 'div', blockProps,
177 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-heatmap-title', style: { color: a.titleColor } }, a.title ) : null,
178 el( 'div', { className: 'bkbg-heatmap-svg' }, renderHeatMap( a ) ),
179 );
180 },
181 } );
182 }() );
183