Skip to content
Gravity Tables
minor v4.1.23 + 4.1.26 · · 6 min read

Charts and maps, two new shortcodes from the same Gravity Forms entries

Two new top-level shortcodes shipped three versions apart, both reading from the same data source the table shortcode does. `[gravity_chart]` renders bar and donut SVG charts with five aggregation modes; `[gravity_map]` plots Leaflet markers for entries with lat/lng fields. Zero JS runtime on the chart, no API key on the map.

  • `[gravity_chart]`, bar and donut SVG, count / sum / avg / min / max aggregations, zero JS runtime, no charting library dependency
  • `[gravity_map]`, Leaflet markers from lat/lng fields, OpenStreetMap tiles, no Google Maps API key required
  • Same data source as `[gravity_table]`, drop a chart, a map, and a table on one page from one form
  • Both shortcodes respect the same role gates and per-user filtering as tables
  • Mobile-responsive, keyboard-navigable, screen-reader-friendly

Up to v4.1.22, Gravity Tables had exactly one rendering surface: the [gravity_table] shortcode. Tabular row-and-column was the only output. That covered ~90% of “show me my form data” use cases, but two real ones kept landing in support email and getting hand-coded around.

“I want a chart of submissions per category.” Solvable, but it took a separate charting plugin reading the same data, and you had to keep the configurations in sync.

“I want a map of submissions with addresses.” Solvable too, but it took a Leaflet integration plugin or a Google Maps embed with hand-rolled JavaScript reading the GF entries via REST.

Both ended in the same place: two plugins doing what could have been one plugin doing two things. So we shipped two new shortcodes.

v4.1.23, [gravity_chart]#

A bar or donut SVG, rendered server-side, from any Gravity Form’s entries.

[gravity_chart id="42"
  type="bar"
  group_by="category"
  aggregate="sum"
  value="value"
  height="320"]

Five aggregation modes:

  • count, number of entries per group (default)
  • sum, sum of value per group
  • avg, average of value per group
  • min, minimum value per group
  • max, maximum value per group

Two chart types:

  • bar, horizontal or vertical bars (default vertical), one per group, with a numeric label on each
  • donut, donut chart with a centered total, color-cycled segments, hover tooltips

Two intentional decisions worth flagging:

Decision 1: SVG, not Canvas, no charting library#

The output is inline SVG. No <canvas> element, no D3, no Chart.js, no <script> tag at all. The chart loads with the page; the page weight added is the SVG markup itself (~2 KB for a typical chart).

What you give up: no animated drawing-in, no real-time data updates, no fancy interactions. What you get: zero JavaScript dependencies, zero runtime cost, perfect SSR (Google sees the chart, not a placeholder), perfect printability, perfect screen-reader text fallback.

For a marketing dashboard, an admin overview, or a public stat page, the trade-off is right. For a real-time trading app, use a JS chart library, but you probably weren’t building that here anyway.

Decision 2: Same data source as the table#

The chart reads from wp_gf_entry via the same query layer the table shortcode uses. That means:

  • The same filter parameter works (filter="status:approved" shows only approved entries on the chart, same as on the table)
  • The same allowed_roles gate applies, visitors who can’t see the table can’t see the chart
  • The same per-user scoping (filter_user_owns="...") applies, a customer’s chart shows only their own data, exactly like their table view

You can drop a chart and a table on the same page using the same form id, with the same filter, and they’ll show consistent narratives of the same data.

v4.1.26, [gravity_map]#

A Leaflet map with one marker per entry, plotting from lat/lng fields you specify.

[gravity_map id="42"
  lat_field="latitude"
  lng_field="longitude"
  popup_field="address"
  zoom="11"
  height="480"]

Three intentional decisions:

Decision 1: Leaflet + OpenStreetMap, no API key#

Leaflet is a small, mature open-source mapping library; OpenStreetMap supplies the tiles. Together they require zero API key, zero billing setup, zero quota planning.

The Google Maps alternative would have been more familiar to some users, but the API key plumbing, the rate-limit anxiety, the “your map will silently break when the credit card expires” failure mode, all of those go away with the Leaflet/OSM combination.

For sites that genuinely need Google’s tile imagery or Street View integration, a future option will let you swap providers; the default works without you ever touching billing.

Decision 2: Explicit lat/lng fields, not address geocoding#

The shortcode takes lat_field and lng_field parameters that point to numeric fields on the form, not a single “Address” field. Why?

Geocoding a free-text address is a runtime API call (Mapbox, Google, Pelias). Each call has cost. Each call introduces a failure mode (the address didn’t resolve, the API was down, the coordinates were ambiguous). For a map showing 500 entries, that’s 500 API calls every time the page loads.

Forcing entries to carry their own lat/lng coordinates moves the geocoding cost to submission time (one API call per submission, when the user typed the address) instead of render time (500 API calls per page view). It’s a small operational shift that scales infinitely better.

If you need geocoding on form submission, every major Gravity Forms add-on (Gravity Forms Geolocation, GF Geocoding) writes lat/lng fields exactly like the map expects. Plug those in, the map plots the results.

Decision 3: Same permission model as table + chart#

allowed_roles, filter, and filter_user_owns apply to the map exactly the way they apply to the table. If a customer can only see their own table rows, they only see their own map markers. If a filter="status:approved" constrains the table, the same constraint constrains the map.

Three views, one permission model. No surprises.

What this enables#

The most marketing-worthy use case: a property listings page that ships in three lines of shortcode.

## Browse listings

[gravity_table id="properties"
  columns="title,price,beds,baths,city"
  allow_edit=""
  filters="city,beds,price_range"
  per_page="20"
  mobile_layout="cards"]

[gravity_chart id="properties"
  type="bar"
  group_by="city"
  aggregate="avg"
  value="price"
  height="300"]

[gravity_map id="properties"
  lat_field="lat"
  lng_field="lng"
  popup_field="title"
  zoom="9"
  height="480"]

A filterable property table, an “average price by city” bar chart, and a map of every property, three views, one form, one shared filter state. Visitor selects “3+ beds” in the chip filter; the table narrows, the chart recomputes the per-city averages on the narrowed set, the map redraws to only show the matching markers.

Real estate, store locator, event venues, member directory, restaurant guide, same pattern, different fields.

What’s not in scope (yet)#

Both shortcodes ship intentionally narrow:

  • Chart: bar and donut only. No line, area, scatter, sankey, treemap. Those are on the queue but not here yet.
  • Map: marker plotting only. No clustering at the marker level, no polygon overlay, no heat map. Clustering for 1,000+ markers is in 4.3 nightlies.

The principle: ship the version that solves the 80% case cleanly, then iterate. Charts that are markdown-quality “rough idea of the distribution” beat charts that are unshipped because the line-chart implementation isn’t perfect.

Upgrade path#

Both shortcodes are additive. No existing [gravity_table] shortcode changes behaviour. Drop [gravity_chart] or [gravity_map] on any page where you’d previously written a long custom-fields-and-PHP block; uninstall the old plugin you were using for charts/maps; everything else keeps working.

#shortcodes#charts#maps#visualization#leaflet#svg