Practical guide

Create bar and pie charts from JSON categories

Categorical charts usually need a transformation before they need a chart. A shipment feed contains individual records; the chart needs one row per carrier, warehouse, or status. That grouping rule is part of the result and should remain visible.

This walkthrough uses synthetic parcel operations data. The records are realistic enough to expose cancellation, exception, and denominator choices, but they are not customer evidence or a performance claim about any carrier.

Define the population once

The sample covers one fixed reporting week. Each shipment has exactly one current status. Canceled records remain in the source, but the delivery-status chart excludes them because they no longer belong to the active fulfillment population.

Make that decision before grouping. If one chart includes canceled shipments and another silently drops them, their totals and percentages cannot be compared.

shipment excerptjson
{
  "window": { "from": "2026-09-07", "to": "2026-09-13" },
  "shipments": [
    { "orderId": "ORD-8401", "warehouse": "ATL-1", "carrier": "NorthLine", "status": "delivered", "deliveryDays": 2, "units": 4 },
    { "orderId": "ORD-8402", "warehouse": "ATL-1", "carrier": "ParcelJet", "status": "exception", "deliveryDays": 5, "units": 1 },
    { "orderId": "ORD-8403", "warehouse": "DAL-2", "carrier": "NorthLine", "status": "in_transit", "deliveryDays": null, "units": 3 },
    { "orderId": "ORD-8404", "warehouse": "SEA-3", "carrier": "BlueRoute", "status": "delivered", "deliveryDays": 3, "units": 2 },
    { "orderId": "ORD-8405", "warehouse": "DAL-2", "carrier": "ParcelJet", "status": "canceled", "deliveryDays": null, "units": 6 },
    { "orderId": "ORD-8406", "warehouse": "SEA-3", "carrier": "BlueRoute", "status": "delivered", "deliveryDays": 2, "units": 5 }
  ]
}

Group records for a bar chart

For a carrier workload comparison, count non-canceled shipments by carrier. Sorting before group_by keeps equal carrier names together; the final descending sort makes the largest bar easy to find without changing the metric.

Run the transformation first and inspect its compact array. The chart expressions then become simple and the aggregate remains available for review or export.

shipments by carrierjq
.shipments
| map(select(.status != "canceled"))
| sort_by(.carrier)
| group_by(.carrier)
| map({
    carrier: .[0].carrier,
    shipments: length
  })
| sort_by(-.shipments)
bar labelsjq
.[] | .carrier
bar valuesjq
.[] | .shipments

Use pie or doughnut for a checked whole

A status-share chart uses the same non-canceled population and one current status per shipment. The values must add back to the number of active shipments. If a record can belong to more than one category, a pie or doughnut would overstate the whole.

Keep the denominator in the transformed result while reviewing it, even though only category and count are needed by the chart. The explicit total makes a percentage error easier to catch.

active shipment status totalsjq
.shipments
| map(select(.status != "canceled"))
| sort_by(.status)
| group_by(.status)
| map({
    status: .[0].status,
    shipments: length
  })
| . as $groups
| {
    total: ($groups | map(.shipments) | add),
    groups: $groups
  }

Carry one source through several views

The full walkthrough expands the fixture to a synthetic week of shipment events. It filters one reporting population, builds a carrier bar chart, then reuses the source for a status doughnut and a warehouse exception comparison. Each chart gets its own transformed array rather than borrowing labels from another view.

The sequence is intentionally closer to an operations review than a toy array. Watch the count before and after each filter, keep the jq readable, and return to the source record when a category looks surprising.

A multi-chart shipment workflow: filter a realistic weekly source, group it with readable JQ, build a carrier bar chart and status doughnut, then verify totals and exception records against the source.Watch on YouTube

Reconcile totals before sharing

Add the category values and compare the sum with the filtered population. Then inspect at least one record from the smallest and largest categories. A typo in a carrier name can create a second bar; a status with different casing can create a second slice.

Use a bar chart when precise comparison matters more than part-to-whole emphasis. Use pie or doughnut only for a small set of exclusive categories with a clear denominator. Neither view turns synthetic shipment counts into a claim about real logistics performance.