# ai-builder/2.2.3/assets/js/src/editor-blocks/stats/index.js

AI Builder – Generate pages, blocks, images &amp; translate with AI, version 2.2.3. 518 lines.

- Page: https://pluginprobe.com/plugins/ai-builder/2.2.3/code/assets/js/src/editor-blocks/stats/index.js
- Raw: https://pluginprobe.com/plugins/ai-builder/2.2.3/raw/assets/js/src/editor-blocks/stats/index.js
- Modified: 2025-09-23T10:34:44+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/ai-builder/2.2.3/code/assets/js/src/editor-blocks/stats/index.js#L10-L20`.

```javascript
const { registerBlockType } = wp.blocks;
const { __ } = wp.i18n;
const {
  PanelBody,
  TextControl,
  SelectControl,
  Button,
  Notice,
  TextareaControl,
} = wp.components;
const { InspectorControls, useBlockProps } = wp.blockEditor || wp.editor;

import "./stats.css";

// Simple, common schemas
// bar/line: { labels:["Jan","Feb"], series:[{name:"A",data:[10,20]},{name:"B",data:[5,15]}] }
// pie: { labels:["A","B","C"], data:[30,50,20] }
const DEFAULT_DATA = {
  labels: ["A", "B", "C"],
  series: [{ name: "Series 1", data: [30, 50, 20] }],
};

registerBlockType("ai-builder/aibui-stats", {
  title: __("Stats (AI Builder)", "ai-builder"),
  icon: "chart-bar",
  category: "widgets",
  supports: { align: ["wide", "full"], html: false },
  attributes: {
    chartType: { type: "string", default: "bar" }, // bar | line | pie
    title: { type: "string", default: "" },
    xLabel: { type: "string", default: "" },
    yLabel: { type: "string", default: "" },
    dataset: { type: "string", default: JSON.stringify(DEFAULT_DATA) }, // JSON string for simplicity
    color: { type: "string", default: "#4f46e5" },
  },
  edit: (props) => {
    const { attributes, setAttributes } = props;
    const { chartType, title, xLabel, yLabel, dataset, color } = attributes;
    const blockProps = useBlockProps({ className: "aibui-stats" });

    let parsed = DEFAULT_DATA;
    try {
      parsed = JSON.parse(dataset || "{}");
      if (!parsed || typeof parsed !== "object") parsed = DEFAULT_DATA;
    } catch (e) {
      parsed = DEFAULT_DATA;
    }

    return (
      <div {...blockProps}>
        <InspectorControls>
          <PanelBody title={__("Settings", "ai-builder")} initialOpen>
            <SelectControl
              label={__("Chart type", "ai-builder")}
              value={chartType}
              options={[
                { label: "Bar", value: "bar" },
                { label: "Line", value: "line" },
                { label: "Pie", value: "pie" },
              ]}
              onChange={(val) => setAttributes({ chartType: val })}
            />
            <TextControl
              label={__("Title", "ai-builder")}
              value={title}
              onChange={(val) => setAttributes({ title: val })}
            />
            <TextControl
              label={__("X axis label", "ai-builder")}
              value={xLabel}
              onChange={(val) => setAttributes({ xLabel: val })}
            />
            <TextControl
              label={__("Y axis label", "ai-builder")}
              value={yLabel}
              onChange={(val) => setAttributes({ yLabel: val })}
            />
            <TextControl
              label={__("Primary color", "ai-builder")}
              type="color"
              value={color}
              onChange={(val) => setAttributes({ color: val })}
            />
            <TextareaControl
              label={__("Data (JSON)", "ai-builder")}
              help={
                chartType === "pie"
                  ? '{"labels":["A","B"],"data":[30,50]}'
                  : '{"labels":["Jan","Feb"],"series":[{"name":"A","data":[10,20]},{"name":"B","data":[5,15]}]}'
              }
              value={dataset}
              onChange={(val) => setAttributes({ dataset: val })}
            />
          </PanelBody>
        </InspectorControls>

        <div className="aibui-stats-preview">
          {title ? <h3 className="aibui-stats-title">{title}</h3> : null}
          <StatsSVG
            type={chartType}
            data={parsed}
            color={color}
            xLabel={xLabel}
            yLabel={yLabel}
          />
          {chartType !== "pie" &&
          Array.isArray(parsed.series) &&
          parsed.series.some((s) => s && s.name) ? (
            <div className="aibui-stats-legend">
              {parsed.series.map((s, si) =>
                s && s.name ? (
                  <span key={si} className="aibui-stats-legend-item">
                    <span
                      className="aibui-stats-legend-dot"
                      style={{
                        backgroundColor: shade(color, si, parsed.series.length),
                      }}
                    ></span>
                    {s.name}
                  </span>
                ) : null
              )}
            </div>
          ) : null}
        </div>
      </div>
    );
  },
  save: (props) => {
    const { attributes } = props;
    const { chartType, title, xLabel, yLabel, dataset, color } = attributes;
    let parsed = DEFAULT_DATA;
    try {
      parsed = JSON.parse(dataset || "{}");
      if (!parsed || typeof parsed !== "object") parsed = DEFAULT_DATA;
    } catch (e) {
      parsed = DEFAULT_DATA;
    }
    const blockProps = wp.blockEditor
      ? wp.blockEditor.useBlockProps.save({ className: "aibui-stats" })
      : {};
    return (
      <div {...blockProps}>
        <div className="aibui-stats-preview">
          {title ? <h3 className="aibui-stats-title">{title}</h3> : null}
          <StatsSVG
            type={chartType}
            data={parsed}
            color={color}
            xLabel={xLabel}
            yLabel={yLabel}
          />
          {chartType !== "pie" &&
          Array.isArray(parsed.series) &&
          parsed.series.some((s) => s && s.name) ? (
            <div className="aibui-stats-legend">
              {parsed.series.map((s, si) =>
                s && s.name ? (
                  <span key={si} className="aibui-stats-legend-item">
                    <span
                      className="aibui-stats-legend-dot"
                      style={{
                        backgroundColor: shade(color, si, parsed.series.length),
                      }}
                    ></span>
                    {s.name}
                  </span>
                ) : null
              )}
            </div>
          ) : null}
        </div>
      </div>
    );
  },
});

function StatsSVG({ type, data, color, xLabel, yLabel }) {
  const PAD_LEFT = 56; // wider to avoid clipping large y tick labels
  const PAD_BOTTOM = 24;
  const BASE_W = 200;
  const H = 200;
  const labelsCommon = Array.isArray(data.labels) ? data.labels : [];
  const W =
    type === "pie"
      ? BASE_W
      : Math.max(BASE_W, PAD_LEFT + 8 + labelsCommon.length * 48);

  if (type === "pie") {
    const labels = data.labels || [];
    let values = (data.data || []).map((v) => Number(v) || 0);
    // Fallback: if user switched from bar/line and kept previous schema
    if (!values.length && Array.isArray(data.series) && data.series.length) {
      values = (data.series[0].data || []).map((v) => Number(v) || 0);
    }
    // Still empty: render nothing but keep container
    if (!values.length) {
      return <svg viewBox={`0 0 ${W} ${H}`} className="aibui-stats-svg" />;
    }
    const total = values.reduce((s, v) => s + v, 0) || 1;
    let acc = 0;
    return (
      <svg viewBox={`0 0 ${W} ${H}`} className="aibui-stats-svg">
        {values.map((val, i) => {
          const angle = (val / total) * Math.PI * 2;
          const x1 = W / 2 + (W / 2) * Math.cos(acc);
          const y1 = H / 2 + (H / 2) * Math.sin(acc);
          acc += angle;
          const x2 = W / 2 + (W / 2) * Math.cos(acc);
          const y2 = H / 2 + (H / 2) * Math.sin(acc);
          const large = angle > Math.PI ? 1 : 0;
          const path = `M${W / 2},${H / 2} L${x1},${y1} A${W / 2},${
            H / 2
          } 0 ${large} 1 ${x2},${y2} z`;
          return (
            <path
              key={i}
              d={path}
              fill={shade(color, i, values.length)}
              data-label={labels[i] != null ? labels[i] : ""}
              data-value={val}
              data-series={""}
            />
          );
        })}
        {(() => {
          let angleAcc = 0;
          return values.map((val, i) => {
            const angle = (val / total) * Math.PI * 2;
            const mid = angleAcc + angle / 2;
            angleAcc += angle;
            const lx = W / 2 + (W / 3) * Math.cos(mid);
            const ly = H / 2 + (H / 3) * Math.sin(mid);
            const label = labels[i] != null ? labels[i] : "";
            return (
              <text
                key={`t${i}`}
                x={lx}
                y={ly}
                fontSize="10"
                textAnchor="middle"
                dominantBaseline="middle"
              >
                {label}
              </text>
            );
          });
        })()}
      </svg>
    );
  }

  const labels = labelsCommon;
  const series = Array.isArray(data.series) ? data.series : [];
  const maxVal = Math.max(
    1,
    ...series.flatMap((s) => s.data.map((v) => Number(v) || 0))
  );
  // Compute "nice" step and max (1/2/5 * 10^n) similar to d3.ticks
  function niceStep(maxValue, tickCount) {
    const raw = maxValue / Math.max(1, tickCount);
    const power = Math.pow(10, Math.floor(Math.log10(raw)));
    const fraction = raw / power;
    let niceFraction;
    if (fraction <= 1) niceFraction = 1;
    else if (fraction <= 2) niceFraction = 2;
    else if (fraction <= 5) niceFraction = 5;
    else niceFraction = 10;
    return niceFraction * power;
  }
  const yTicks = 4;
  const step = niceStep(maxVal, yTicks);
  const niceMax = step * yTicks;

  if (type === "line") {
    const xStep = (W - PAD_LEFT - 8) / Math.max(1, labels.length - 1);
    return (
      <svg viewBox={`0 0 ${W} ${H}`} className="aibui-stats-svg">
        <line
          x1={PAD_LEFT}
          y1={H - PAD_BOTTOM}
          x2={W - 8}
          y2={H - PAD_BOTTOM}
          stroke="#e5e7eb"
        />
        <line
          x1={PAD_LEFT}
          y1={8}
          x2={PAD_LEFT}
          y2={H - PAD_BOTTOM}
          stroke="#e5e7eb"
        />
        {[...Array(yTicks + 1)].map((_, i) => {
          const val = i * step;
          const y = H - PAD_BOTTOM - (val / niceMax) * (H - PAD_BOTTOM - 20);
          return (
            <g key={`yt${i}`}>
              <line
                x1={PAD_LEFT - 3}
                y1={y}
                x2={PAD_LEFT}
                y2={y}
                stroke="#9ca3af"
              />
              <text
                x={PAD_LEFT - 6}
                y={y}
                fontSize="9"
                textAnchor="end"
                dominantBaseline="middle"
              >
                {val}
              </text>
            </g>
          );
        })}
        {labels.map((lab, i) => {
          const x = PAD_LEFT + i * xStep;
          return (
            <text
              key={`xl${i}`}
              x={x}
              y={H - PAD_BOTTOM + 12}
              fontSize="9"
              textAnchor="middle"
            >
              {lab}
            </text>
          );
        })}
        {xLabel ? (
          <text
            x={(W + PAD_LEFT) / 2}
            y={H - 4}
            fontSize="10"
            textAnchor="middle"
          >
            {xLabel}
          </text>
        ) : null}
        {yLabel ? (
          <text
            transform={`translate(12,${H / 2}) rotate(-90)`}
            fontSize="10"
            textAnchor="middle"
          >
            {yLabel}
          </text>
        ) : null}
        {series.map((s, si) => {
          const pts = (s.data || []).map((v, i) => {
            const x =
              PAD_LEFT +
              (i / Math.max(1, labels.length - 1)) * (W - PAD_LEFT - 8);
            const y =
              H -
              PAD_BOTTOM -
              ((Number(v) || 0) / niceMax) * (H - PAD_BOTTOM - 20);
            return `${x},${y}`;
          });
          return (
            <g key={si}>
              <polyline
                fill="none"
                stroke={shade(color, si, series.length)}
                strokeWidth="2"
                points={pts.join(" ")}
              />
              {(s.data || []).map((v, i) => {
                const x =
                  PAD_LEFT +
                  (i / Math.max(1, labels.length - 1)) * (W - PAD_LEFT - 8);
                const y =
                  H -
                  PAD_BOTTOM -
                  ((Number(v) || 0) / niceMax) * (H - PAD_BOTTOM - 20);
                return (
                  <circle
                    key={`pt-${si}-${i}`}
                    cx={x}
                    cy={y}
                    r="3"
                    fill={shade(color, si, series.length)}
                    data-label={labels[i] != null ? labels[i] : ""}
                    data-value={Number(v) || 0}
                    data-series={s && s.name ? s.name : ""}
                  />
                );
              })}
            </g>
          );
        })}
      </svg>
    );
  }

  // bar (grouped)
  const groupWidth = (W - PAD_LEFT - 8) / Math.max(1, labels.length);
  const barWidth = Math.max(2, (groupWidth - 8) / Math.max(1, series.length));
  return (
    <svg viewBox={`0 0 ${W} ${H}`} className="aibui-stats-svg">
      <line
        x1={PAD_LEFT}
        y1={H - PAD_BOTTOM}
        x2={W - 8}
        y2={H - PAD_BOTTOM}
        stroke="#e5e7eb"
      />
      <line
        x1={PAD_LEFT}
        y1={8}
        x2={PAD_LEFT}
        y2={H - PAD_BOTTOM}
        stroke="#e5e7eb"
      />
      {[...Array(5)].map((_, i) => {
        const val = i * step;
        const y = H - PAD_BOTTOM - (val / niceMax) * (H - PAD_BOTTOM - 20);
        return (
          <g key={`ybt${i}`}>
            <line
              x1={PAD_LEFT - 3}
              y1={y}
              x2={PAD_LEFT}
              y2={y}
              stroke="#9ca3af"
            />
            <text
              x={PAD_LEFT - 6}
              y={y}
              fontSize="9"
              textAnchor="end"
              dominantBaseline="middle"
            >
              {val}
            </text>
          </g>
        );
      })}
      {labels.map((lab, li) => {
        const x = PAD_LEFT + li * groupWidth + groupWidth / 2;
        return (
          <text
            key={`xbl${li}`}
            x={x}
            y={H - PAD_BOTTOM + 12}
            fontSize="9"
            textAnchor="middle"
          >
            {lab}
          </text>
        );
      })}
      {xLabel ? (
        <text
          x={(W + PAD_LEFT) / 2}
          y={H - 4}
          fontSize="10"
          textAnchor="middle"
        >
          {xLabel}
        </text>
      ) : null}
      {yLabel ? (
        <text
          transform={`translate(12,${H / 2}) rotate(-90)`}
          fontSize="10"
          textAnchor="middle"
        >
          {yLabel}
        </text>
      ) : null}
      {labels.map((lab, li) => {
        return series.map((s, si) => {
          const val = Number((s.data || [])[li]) || 0;
          const h = (val / niceMax) * (H - PAD_BOTTOM - 20);
          const x = PAD_LEFT + li * groupWidth + 4 + si * barWidth;
          const y = H - PAD_BOTTOM - h;
          return (
            <rect
              key={`${li}-${si}`}
              x={x}
              y={y}
              width={barWidth}
              height={h}
              fill={shade(color, si, series.length)}
              rx="3"
              data-label={lab}
              data-value={val}
              data-series={s && s.name ? s.name : ""}
            />
          );
        });
      })}
    </svg>
  );
}

function shade(hex, index, total) {
  try {
    const amt = Math.round(((index / Math.max(1, total - 1)) * 2 - 1) * 20);
    let col = hex.replace("#", "");
    if (col.length === 3)
      col = col
        .split("")
        .map((c) => c + c)
        .join("");
    let r = Math.max(0, Math.min(255, parseInt(col.substring(0, 2), 16) + amt));
    let g = Math.max(0, Math.min(255, parseInt(col.substring(2, 4), 16) + amt));
    let b = Math.max(0, Math.min(255, parseInt(col.substring(4, 6), 16) + amt));
    return `#${r.toString(16).padStart(2, "0")}${g
      .toString(16)
      .padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
  } catch (e) {
    return hex;
  }
}

```
