# Fancy Chart Widget

> Interactive charts (bar / line / area / pie / doughnut / radar / polar) powered by Chart.js, with data from either manual entry or a Google Sheet.

**Class file:** `includes/Elements/Fancy_Chart.php` (3,098 lines)
**Slug:** `fancy-chart` (widget id `eael-fancy-chart`)
**Public docs:** <https://essential-addons.com/elementor/docs/fancy-chart/>
**Pro-shared:** Pro-only. **External integration: Google Sheets API**.

## Overview

Renders interactive charts via Chart.js (`assets/front-end/js/lib-view/chart/chart.js`). User picks chart style (bar, line, area, pie, doughnut, radar, polar), provides datasets either via manual repeater entries or by linking a Google Sheet (API key + sheet ID + range). Styled with full Pro typography / color controls. Responsive — chart type can switch per breakpoint (`eael_fancy_chart_chart_type_mobile` / `_tablet`).

## Pro vs Lite

Pro-only. No Lite counterpart, no upsell teaser.

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Fancy_Chart.php` | Widget class — controls + render + Google Sheets fetcher |
| `src/js/view/fancy-chart.js` → `assets/front-end/js/view/fancy-chart.min.js` | Chart.js init from per-element data attribute |
| `assets/front-end/js/lib-view/chart/chart.js` | Chart.js library (lib) |
| `config.php` entry `'fancy-chart'` | One lib JS + one self JS dep |

No SCSS source — chart styling is JS-driven via Chart.js options.

## Architecture

- **Data source split** — `eael_fancy_chart_data_option_type` toggles between `'manual'` (repeater of labels + values) and `'google_sheet'` (API key + sheet id + range). Render branches at `Fancy_Chart.php:2906` based on `$fancy_chart_settings['eael_get_data_type']`.
- **Google Sheets fetch** — see [`_patterns.md § External-API widget pattern`](_patterns.md#external-api-widget-pattern). Endpoint: `https://sheets.googleapis.com/v4/spreadsheets/{sheet_id}?key={api_key}&ranges={range}&includeGridData=true`. **70s `timeout`** — flagged as too long in [`docs/architecture/external-api-integrations.md`](../architecture/external-api-integrations.md). Transient cache key: `eael_fancy_chart_source_google_sheet_<md5(args)>`.
- **Per-breakpoint chart type** — Chart.js supports orientation switching; responsive controls let user pick a different chart type for tablet / mobile (`Fancy_Chart.php:2029-2042`).
- **No `set_default_values()` skin** — fancy-chart is just a slug in Pro's `Core.php` defaults; no skin classes.
- **API key + sheet id concatenated into URL** without `urlencode` — both user-supplied. Low practical risk (Google rejects malformed sheet IDs) but a `urlencode` would tighten. Flagged in [`external-api-integrations.md`](../architecture/external-api-integrations.md).

## Render Output

```html
<div class="eael-fancy-chart-wrap">
  <canvas id="eael-fancy-chart-{element-id}"
          data-chart-config='{...JSON...}'
          width="..."
          height="..."></canvas>
</div>
```

The `data-chart-config` JSON contains the full Chart.js config object (labels, datasets, options). Frontend JS reads it and instantiates `new Chart(canvas, config)`.

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| `eael_fancy_chart_chart_type` | Content → Chart | SELECT | `bar` / `line` / `area` / `pie` / `doughnut` / `radar` / `polarArea` |
| `eael_fancy_chart_data_option_type` | Content → Data | SELECT | `manual` / `google_sheet` |
| `eael_fancy_chart_api_key` | Content → Data | TEXT | Google Sheets API key (when type = google_sheet) |
| `eael_fancy_chart_sheet_id` | Content → Data | TEXT | Sheet ID |
| `eael_fancy_chart_table_range` | Content → Data | TEXT | Range (e.g. `A1:E10`) |
| `eael_fancy_chart_data_cache_limit` | Content → Data | NUMBER | Transient TTL in hours (default 1) |
| `eael_fancy_chart_data` | Content → Data | REPEATER | Manual data rows (when type = manual) |
| `eael_fancy_chart_chart_type_tablet` / `_mobile` | Responsive | SELECT | Per-breakpoint chart type |
| Color / typography controls | Style | various | Chart colors, axis labels, legend |

## Conditional Dependencies

```text
eael_fancy_chart_data_option_type = 'manual'
  └── shows eael_fancy_chart_data repeater
  └── hides Google Sheets controls

eael_fancy_chart_data_option_type = 'google_sheet'
  └── shows eael_fancy_chart_api_key + sheet_id + table_range
  └── shows cache TTL
  └── hides manual repeater

eael_fancy_chart_chart_type = 'bar' | 'area'
  └── shows bar-specific controls (bar_width, etc.)
```

## JavaScript Lifecycle

`src/js/view/fancy-chart.js`:

```js
var FancyChartHandler = function( $scope, $ ) {
    var $canvas = $scope.find( 'canvas' );
    if ( ! $canvas.length ) return;

    var config = JSON.parse( $canvas.data( 'chart-config' ) );
    new Chart( $canvas[0].getContext( '2d' ), config );
};

jQuery( window ).on( 'elementor/frontend/init', function() {
    if ( eael.elementStatusCheck( 'eaelFancyChartLoad' ) ) return false;
    elementorFrontend.hooks.addAction(
        'frontend/element_ready/eael-fancy-chart.default',
        FancyChartHandler
    );
} );
```

Chart.js is loaded via `config.php` lib dep — available globally as `Chart` when the widget script runs.

## Hooks & Filters

### Elementor hooks consumed

Standard widget render. No extension-style hook registration.

### Pro / EA hooks emitted

None visible.

## Common Issues

| Symptom | Likely cause | Diagnose | Fix |
| --- | --- | --- | --- |
| Empty chart, page render slow | Google Sheets API timeout (70s) | Network tab: check duration on `sheets.googleapis.com` request | Verify sheet is shared with "Anyone with link", verify API key has Sheets API enabled. Reduce timeout to 15s as part of a hardening PR. |
| "Failed to load chart" / JS console error | Chart.js not loaded | Verify `chart.js` in `Asset_Builder` enqueue list | Confirm `config.php` entry intact; rebuild assets |
| Chart shows manual data even after switching to Google Sheets | Transient cache from earlier sheet content | Delete transient `eael_fancy_chart_source_google_sheet_*` | Drop cache TTL; reload |
| Chart colors wrong on dark mode | No theme-aware default | — | Set chart colors explicitly; do not rely on user theme |
| Chart not responsive | Chart.js `responsive: true` missing | Inspect `data-chart-config` JSON | Ensure render emits `responsive: true` in config |

## Known Limitations

- **70s timeout on Sheets fetch** — see [`external-api-integrations.md`](../architecture/external-api-integrations.md). Slow Google response stalls the entire page render.
- **API key concatenated into URL** without `urlencode`; user-supplied range likewise.
- **API key visible in saved page HTML** if rendering is debug-logged (it's a query-string param). Google Sheets API keys are referrer-restrictable; document this for users.
- **No fallback to last-good cache on API failure** — if Sheets returns 500, the chart renders empty rather than serving stale data.
- **Chart.js version** is pinned via vendored copy. Upgrading Chart.js may break existing chart configurations.
- **No batch optimization for multiple charts on one page** — each chart makes its own Sheets call. With 5+ charts the page is heavy.

## Cross-References

- Architecture: [`docs/architecture/external-api-integrations.md`](../architecture/external-api-integrations.md) — Sheets endpoint, timeout flagged
- Shared patterns: [`_patterns.md`](_patterns.md) — External-API widget pattern
- Rule: [`.claude/rules/external-api-integrations.md`](../../.claude/rules/external-api-integrations.md)
- Audit skill: [`/external-api-audit`](../../.claude/skills/external-api-audit/SKILL.md)
