# blockenberg/2.0.7/blocks/waterfall-chart/index.js

Blockenberg — 600+ Advanced Gutenberg Blocks &amp; AI Agent for WordPress Block Editor, version 2.0.7. 542 lines.

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/waterfall-chart/index.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.7/raw/blocks/waterfall-chart/index.js
- Modified: 2026-03-06T12:32:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/waterfall-chart/index.js#L10-L20`.

```javascript
( function () {
    var el = wp.element.createElement;
    var Fragment = wp.element.Fragment;
    var useState = wp.element.useState;
    var __ = wp.i18n.__;
    var registerBlockType = wp.blocks.registerBlockType;
    var InspectorControls = wp.blockEditor.InspectorControls;
    var useBlockProps = wp.blockEditor.useBlockProps;
    var PanelColorSettings = wp.blockEditor.PanelColorSettings;
    var PanelBody = wp.components.PanelBody;
    var ToggleControl = wp.components.ToggleControl;
    var RangeControl = wp.components.RangeControl;
    var SelectControl = wp.components.SelectControl;
    var TextControl = wp.components.TextControl;
    var Button = wp.components.Button;

    var _tc, _tvf;
    Object.defineProperty(window, '_tc',  { get: function () { return _tc  || (_tc  = window.bkbgTypographyControl); } });
    Object.defineProperty(window, '_tvf', { get: function () { return _tvf || (_tvf = window.bkbgTypoCssVars); } });
    function getTypoControl(label, key, attrs, setA) { return _tc(label, key, attrs, setA); }
    function getTypoCssVars(attrs) {
        var v = {};
        _tvf(v, 'titleTypo', attrs, '--bkwfc-tt-');
        return v;
    }

    /* ── updateItem (ES5 safe) ── */
    function updateItem(arr, idx, field, val) {
        return arr.map(function (item, i) {
            if (i !== idx) return item;
            var p = {}; p[field] = val;
            return Object.assign({}, item, p);
        });
    }

    /* ── abbreviate large numbers ── */
    function abbr(n, prefix, suffix, doAbbr) {
        var abs = Math.abs(n);
        var sign = n < 0 ? '-' : '';
        var str;
        if (!doAbbr || abs < 1000) {
            str = abs.toLocaleString();
        } else if (abs < 1000000) {
            str = (abs / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
        } else if (abs < 1000000000) {
            str = (abs / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
        } else {
            str = (abs / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
        }
        return sign + prefix + str + suffix;
    }

    /* ── type label ── */
    var TYPE_LABELS = {
        start:    __('Starting value', 'blockenberg'),
        increase: __('Increase (+)', 'blockenberg'),
        decrease: __('Decrease (−)', 'blockenberg'),
        total:    __('Total / Net', 'blockenberg')
    };
    var TYPE_OPTIONS = [
        { label: __('Starting value', 'blockenberg'), value: 'start' },
        { label: __('Increase (+)', 'blockenberg'),   value: 'increase' },
        { label: __('Decrease (−)', 'blockenberg'),  value: 'decrease' },
        { label: __('Total / Net', 'blockenberg'),    value: 'total' }
    ];

    /* ── color for type ── */
    function typeColor(type, a) {
        if (type === 'start')    return a.startColor;
        if (type === 'increase') return a.increaseColor;
        if (type === 'decrease') return a.decreaseColor;
        return a.totalColor;
    }

    /* ── compute bar segments for preview ── */
    /* Returns array of { label, type, base, top, barColor, displayValue } */
    function computeSegments(items, a) {
        var running = 0;
        return items.map(function (item) {
            var v = parseFloat(item.value) || 0;
            var type = item.type || 'increase';
            var base, top;

            if (type === 'start') {
                base = 0; top = v; running = v;
            } else if (type === 'total') {
                base = 0; top = running; /* running stays same */
            } else if (type === 'increase') {
                base = running; top = running + v; running = top;
            } else { /* decrease */
                top = running; base = running + v; running = base;
            }

            return {
                label: item.label,
                type: type,
                base: base,
                top: top,
                barColor: typeColor(type, a),
                displayValue: v,
                runningAfter: running
            };
        });
    }

    /* ── SVG bar chart preview in editor ── */
    function WaterfallPreview(props) {
        var a = props.attributes;
        var items = a.items;
        if (!items || !items.length) return el('div', {}, 'No data.');

        var segs = computeSegments(items, a);
        var allVals = segs.map(function (s) { return s.top; }).concat(segs.map(function (s) { return s.base; }));
        var maxVal = Math.max.apply(null, allVals);
        var minVal = Math.min.apply(null, allVals.concat([0]));
        var range = maxVal - minVal || 1;

        var svgW = 600, svgH = Math.min(a.height, 320);
        var padLeft = 55, padRight = 16, padTop = 20, padBottom = 36;
        var chartW = svgW - padLeft - padRight;
        var chartH = svgH - padTop - padBottom;
        var barW = Math.min(a.barThickness, Math.floor(chartW / items.length) - 6);
        var step = chartW / items.length;

        function yPx(v) { return padTop + chartH - ((v - minVal) / range) * chartH; }

        var bars = segs.map(function (s, i) {
            var x = padLeft + i * step + (step - barW) / 2;
            var y1 = yPx(Math.max(s.base, s.top));
            var y2 = yPx(Math.min(s.base, s.top));
            var bh = Math.max(y2 - y1, 2);
            return { x: x, y: y1, width: barW, height: bh, seg: s, cx: x + barW / 2 };
        });

        var gridLines = [];
        var gridCount = 5;
        for (var g = 0; g <= gridCount; g++) {
            var gv = minVal + (range / gridCount) * g;
            var gy = yPx(gv);
            gridLines.push(el('line', { key: 'gl' + g, x1: padLeft, x2: svgW - padRight, y1: gy, y2: gy, stroke: a.gridColor, strokeWidth: 1, strokeDasharray: '4,3' }));
            gridLines.push(el('text', { key: 'glt' + g, x: padLeft - 4, y: gy + 4, textAnchor: 'end', fontSize: 9, fill: a.labelColor },
                abbr(gv, a.valuePrefix, a.valueSuffix, a.abbreviate)
            ));
        }

        var connectors = [];
        if (a.showConnectors) {
            for (var ci = 0; ci < bars.length - 1; ci++) {
                var curr = bars[ci];
                var next = bars[ci + 1];
                var connY = yPx(segs[ci].type === 'decrease' ? segs[ci].base : segs[ci].top);
                connectors.push(el('line', {
                    key: 'conn' + ci,
                    x1: curr.x + curr.width, x2: next.x,
                    y1: connY, y2: connY,
                    stroke: a.connectorColor, strokeWidth: 1.5, strokeDasharray: '4,3'
                }));
            }
        }

        return el('div', { style: { background: a.bgColor, borderRadius: '8px' } },
            a.showTitle && a.title ? el('div', {
                className: 'bkbg-wfc-title',
                style: { color: a.titleColor, padding: '0 0 12px' }
            }, a.title) : null,
            el('svg', { width: '100%', viewBox: '0 0 600 ' + svgH, style: { display: 'block', maxWidth: '100%' } },
                a.showGrid ? el('g', {}, gridLines) : null,
                el('line', { x1: padLeft, x2: svgW - padRight, y1: yPx(0), y2: yPx(0), stroke: a.gridColor, strokeWidth: 1.5 }),
                el('g', {}, connectors),
                el('g', {},
                    bars.map(function (b, i) {
                        return el('g', { key: i },
                            el('rect', {
                                x: b.x, y: b.y, width: b.width, height: b.height,
                                fill: b.seg.barColor, rx: a.borderRadius
                            }),
                            a.showDataLabels ? el('text', {
                                x: b.cx, y: b.y - 4, textAnchor: 'middle',
                                fontSize: a.valueSize, fill: b.seg.barColor, fontWeight: 700, fontFamily: 'system-ui,sans-serif'
                            }, abbr(b.seg.displayValue, a.valuePrefix, a.valueSuffix, a.abbreviate)) : null,
                            el('text', {
                                x: b.cx, y: svgH - 4, textAnchor: 'middle',
                                fontSize: a.labelSize, fill: a.labelColor, fontFamily: 'system-ui,sans-serif'
                            }, b.seg.label.length > 10 ? b.seg.label.slice(0, 9) + '…' : b.seg.label)
                        );
                    })
                )
            ),
            a.showLegend ? el('div', {
                style: { display: 'flex', flexWrap: 'wrap', gap: '10px 18px', padding: '10px 0 0', fontFamily: 'system-ui,sans-serif' }
            },
                [['start', 'Start', a.startColor], ['increase', 'Increase', a.increaseColor], ['decrease', 'Decrease', a.decreaseColor], ['total', 'Total', a.totalColor]].map(function (row) {
                    return el('div', { key: row[0], style: { display: 'flex', alignItems: 'center', gap: '5px' } },
                        el('span', { style: { width: '12px', height: '12px', borderRadius: '3px', background: row[2], display: 'inline-block', flexShrink: 0 } }),
                        el('span', { style: { fontSize: '12px', color: a.labelColor } }, row[1])
                    );
                })
            ) : null
        );
    }

    /* ── item list editor ── */
    function ItemEditor(props) {
        var items = props.items;
        var setItems = props.setItems;
        var expanded = props.expanded;
        var setExpanded = props.setExpanded;

        return el('div', {},
            items.map(function (item, i) {
                var isOpen = expanded === i;
                var col = {start:'#6366f1',increase:'#10b981',decrease:'#ef4444',total:'#8b5cf6'}[item.type] || '#6b7280';
                return el('div', {
                    key: i,
                    style: { border: '1px solid #e5e7eb', borderRadius: '6px', marginBottom: '5px', overflow: 'hidden' }
                },
                    el('div', {
                        onClick: function () { setExpanded(isOpen ? -1 : i); },
                        style: {
                            padding: '7px 10px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px',
                            background: isOpen ? '#f9fafb' : '#fff',
                            borderBottom: isOpen ? '1px solid #e5e7eb' : 'none'
                        }
                    },
                        el('span', { style: { width: '10px', height: '10px', borderRadius: '2px', background: col, flexShrink: 0 } }),
                        el('span', { style: { flex: 1, fontSize: '12px', color: '#374151', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
                            item.label || '(empty)'
                        ),
                        el('span', { style: { fontSize: '11px', color: '#9ca3af', marginRight: '4px' } },
                            (item.value > 0 ? '+' : '') + (item.value || 0)
                        ),
                        el('button', {
                            onClick: function (e) {
                                e.stopPropagation();
                                if (items.length <= 2) return;
                                setItems(items.filter(function (_, j) { return j !== i; }));
                                setExpanded(-1);
                            },
                            style: { background: 'none', border: 'none', cursor: 'pointer', color: '#ef4444', fontSize: '14px', padding: '0 2px', lineHeight: 1 }
                        }, '×'),
                        el('span', { style: { color: '#9ca3af', fontSize: '10px' } }, isOpen ? '▲' : '▼')
                    ),
                    isOpen ? el('div', { style: { padding: '10px' } },
                        el(TextControl, {
                            __nextHasNoMarginBottom: true,
                            label: __('Label', 'blockenberg'),
                            value: item.label,
                            onChange: function (v) { setItems(updateItem(items, i, 'label', v)); }
                        }),
                        el('div', { style: { marginTop: '8px' } },
                            el(TextControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Value (negative = decrease)', 'blockenberg'),
                                type: 'number',
                                value: item.value,
                                onChange: function (v) { setItems(updateItem(items, i, 'value', parseFloat(v) || 0)); }
                            })
                        ),
                        el('div', { style: { marginTop: '8px' } },
                            el(SelectControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Type', 'blockenberg'),
                                value: item.type,
                                options: TYPE_OPTIONS,
                                onChange: function (v) { setItems(updateItem(items, i, 'type', v)); }
                            })
                        ),
                        el('div', { style: { display: 'flex', gap: '6px', marginTop: '8px' } },
                            i > 0 ? el(Button, {
                                variant: 'secondary', style: { fontSize: '11px' },
                                onClick: function () {
                                    var arr = items.slice(); var tmp = arr[i - 1]; arr[i - 1] = arr[i]; arr[i] = tmp;
                                    setItems(arr); setExpanded(i - 1);
                                }
                            }, '↑') : null,
                            i < items.length - 1 ? el(Button, {
                                variant: 'secondary', style: { fontSize: '11px' },
                                onClick: function () {
                                    var arr = items.slice(); var tmp = arr[i + 1]; arr[i + 1] = arr[i]; arr[i] = tmp;
                                    setItems(arr); setExpanded(i + 1);
                                }
                            }, '↓') : null,
                            el(Button, {
                                variant: 'secondary', style: { fontSize: '11px' },
                                onClick: function () {
                                    var copy = Object.assign({}, item, { label: item.label + ' (copy)' });
                                    var arr = items.slice(); arr.splice(i + 1, 0, copy);
                                    setItems(arr); setExpanded(i + 1);
                                }
                            }, __('Dup', 'blockenberg'))
                        )
                    ) : null
                );
            }),
            el('div', { style: { marginTop: '8px', display: 'flex', gap: '6px' } },
                el(Button, {
                    variant: 'primary', style: { flex: 1, justifyContent: 'center' },
                    onClick: function () {
                        var arr = items.slice();
                        arr.push({ label: 'New item', value: 0, type: 'increase' });
                        setItems(arr); setExpanded(arr.length - 1);
                    }
                }, __('+ Add Item', 'blockenberg')),
                el(Button, {
                    variant: 'secondary', style: { justifyContent: 'center' },
                    onClick: function () {
                        var arr = items.slice();
                        arr.push({ label: 'Total', value: 0, type: 'total' });
                        setItems(arr); setExpanded(arr.length - 1);
                    }
                }, __('+ Total', 'blockenberg'))
            )
        );
    }

    /* ── register ── */
    registerBlockType('blockenberg/waterfall-chart', {
        edit: function (props) {
            var a = props.attributes;
            var setAttributes = props.setAttributes;

            var expState = useState(-1);
            var expanded = expState[0];
            var setExpanded = expState[1];

            function setItems(v) { setAttributes({ items: v }); }

            var blockProps = useBlockProps({ className: 'bkbg-wfc-editor-wrap' });

            return el(Fragment, {},

                el(InspectorControls, {},

                    /* ── Data ── */
                    el(PanelBody, { title: __('Chart Data', 'blockenberg'), initialOpen: true },
                        el(ItemEditor, {
                            items: a.items,
                            setItems: setItems,
                            expanded: expanded,
                            setExpanded: setExpanded
                        })
                    ),

                    /* ── Labels ── */
                    el(PanelBody, { title: __('Labels & Format', 'blockenberg'), initialOpen: false },
                        el(TextControl, {
                            __nextHasNoMarginBottom: true,
                            label: __('Chart title', 'blockenberg'),
                            value: a.title,
                            onChange: function (v) { setAttributes({ title: v }); }
                        }),
                        el('div', { style: { marginTop: '10px' } },
                            el(ToggleControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Show title', 'blockenberg'),
                                checked: a.showTitle,
                                onChange: function (v) { setAttributes({ showTitle: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '10px', display: 'flex', gap: '8px' } },
                            el('div', { style: { flex: 1 } },
                                el(TextControl, {
                                    __nextHasNoMarginBottom: true,
                                    label: __('Value prefix', 'blockenberg'),
                                    value: a.valuePrefix,
                                    onChange: function (v) { setAttributes({ valuePrefix: v }); }
                                })
                            ),
                            el('div', { style: { flex: 1 } },
                                el(TextControl, {
                                    __nextHasNoMarginBottom: true,
                                    label: __('Value suffix', 'blockenberg'),
                                    value: a.valueSuffix,
                                    onChange: function (v) { setAttributes({ valueSuffix: v }); }
                                })
                            )
                        ),
                        el('div', { style: { marginTop: '10px' } },
                            el(ToggleControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Abbreviate large values (K, M, B)', 'blockenberg'),
                                checked: a.abbreviate,
                                onChange: function (v) { setAttributes({ abbreviate: v }); }
                            })
                        )
                    ),

                    /* ── Appearance ── */
                    el(PanelBody, { title: __('Appearance', 'blockenberg'), initialOpen: false },
                        el(RangeControl, {
                            __nextHasNoMarginBottom: true,
                            label: __('Chart height (px)', 'blockenberg'),
                            value: a.height, min: 200, max: 700,
                            onChange: function (v) { setAttributes({ height: v }); }
                        }),
                        el('div', { style: { marginTop: '10px' } },
                            el(RangeControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Bar max thickness (px)', 'blockenberg'),
                                value: a.barThickness, min: 20, max: 100,
                                onChange: function (v) { setAttributes({ barThickness: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '10px' } },
                            el(RangeControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Bar border radius (px)', 'blockenberg'),
                                value: a.borderRadius, min: 0, max: 20,
                                onChange: function (v) { setAttributes({ borderRadius: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '10px' } },
                            el(RangeControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Max width (px)', 'blockenberg'),
                                value: a.maxWidth, min: 400, max: 1400,
                                onChange: function (v) { setAttributes({ maxWidth: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '10px' } },
                            el(ToggleControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Show connector lines', 'blockenberg'),
                                checked: a.showConnectors,
                                onChange: function (v) { setAttributes({ showConnectors: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '4px' } },
                            el(ToggleControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Show data labels', 'blockenberg'),
                                checked: a.showDataLabels,
                                onChange: function (v) { setAttributes({ showDataLabels: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '4px' } },
                            el(ToggleControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Show grid lines', 'blockenberg'),
                                checked: a.showGrid,
                                onChange: function (v) { setAttributes({ showGrid: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '4px' } },
                            el(ToggleControl, {
                                __nextHasNoMarginBottom: true,
                                label: __('Show legend', 'blockenberg'),
                                checked: a.showLegend,
                                onChange: function (v) { setAttributes({ showLegend: v }); }
                            })
                        ),
                        el('div', { style: { marginTop: '10px', display: 'flex', gap: '8px' } },
                            el('div', { style: { flex: 1 } },
                                el(RangeControl, {
                                    __nextHasNoMarginBottom: true,
                                    label: __('Padding top', 'blockenberg'),
                                    value: a.paddingTop, min: 0, max: 120,
                                    onChange: function (v) { setAttributes({ paddingTop: v }); }
                                })
                            ),
                            el('div', { style: { flex: 1 } },
                                el(RangeControl, {
                                    __nextHasNoMarginBottom: true,
                                    label: __('Padding bottom', 'blockenberg'),
                                    value: a.paddingBottom, min: 0, max: 120,
                                    onChange: function (v) { setAttributes({ paddingBottom: v }); }
                                })
                            )
                        )
                    ),

                    /* ── Colors ── */
                    
                    el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
                        getTypoControl( __( 'Title', 'blockenberg' ), 'titleTypo', a, setAttributes ),
                        el('div', { style: { marginTop: '10px', display: 'flex', gap: '8px' } },
                                                    el('div', { style: { flex: 1 } },
                                                        el(RangeControl, {
                                                            __nextHasNoMarginBottom: true,
                                                            label: __('Label size', 'blockenberg'),
                                                            value: a.labelSize, min: 8, max: 18,
                                                            onChange: function (v) { setAttributes({ labelSize: v }); }
                                                        })
                                                    ),
                                                    el('div', { style: { flex: 1 } },
                                                        el(RangeControl, {
                                                            __nextHasNoMarginBottom: true,
                                                            label: __('Value size', 'blockenberg'),
                                                            value: a.valueSize, min: 8, max: 18,
                                                            onChange: function (v) { setAttributes({ valueSize: v }); }
                                                        })
                                                    )
                                                )
                    ),
el(PanelColorSettings, {
                        title: __('Colors', 'blockenberg'),
                        initialOpen: false,
                        colorSettings: [
                            { label: __('Background', 'blockenberg'),       value: a.bgColor,        onChange: function (v) { setAttributes({ bgColor: v || '#ffffff' }); } },
                            { label: __('Title', 'blockenberg'),            value: a.titleColor,     onChange: function (v) { setAttributes({ titleColor: v || '#111827' }); } },
                            { label: __('Labels / axis', 'blockenberg'),    value: a.labelColor,     onChange: function (v) { setAttributes({ labelColor: v || '#6b7280' }); } },
                            { label: __('Grid lines', 'blockenberg'),       value: a.gridColor,      onChange: function (v) { setAttributes({ gridColor: v || '#e5e7eb' }); } },
                            { label: __('Connectors', 'blockenberg'),       value: a.connectorColor, onChange: function (v) { setAttributes({ connectorColor: v || '#9ca3af' }); } },
                            { label: __('Start bars', 'blockenberg'),       value: a.startColor,     onChange: function (v) { setAttributes({ startColor: v || '#6366f1' }); } },
                            { label: __('Increase bars', 'blockenberg'),    value: a.increaseColor,  onChange: function (v) { setAttributes({ increaseColor: v || '#10b981' }); } },
                            { label: __('Decrease bars', 'blockenberg'),    value: a.decreaseColor,  onChange: function (v) { setAttributes({ decreaseColor: v || '#ef4444' }); } },
                            { label: __('Total bars', 'blockenberg'),       value: a.totalColor,     onChange: function (v) { setAttributes({ totalColor: v || '#8b5cf6' }); } }
                        ]
                    })
                ),

                /* ── canvas ── */
                el('div', blockProps,
                    el('div', {
                        style: {
                            background: a.bgColor,
                            borderRadius: '10px',
                            padding: '24px',
                            paddingTop: a.paddingTop ? a.paddingTop + 'px' : '24px',
                            paddingBottom: a.paddingBottom ? a.paddingBottom + 'px' : '24px',
                            maxWidth: a.maxWidth + 'px',
                            margin: '0 auto',
                            fontFamily: 'system-ui, sans-serif'
                        }
                    },
                        el(WaterfallPreview, { attributes: a })
                    )
                )
            );
        },

        save: function (props) {
            return el('div', useBlockProps.save({ style: getTypoCssVars(props.attributes) }),
                el('div', {
                    className: 'bkbg-wfc-app',
                    'data-opts': JSON.stringify(props.attributes)
                })
            );
        }
    });
}() );

```
