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.
{
"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
| map(select(.status != "canceled"))
| sort_by(.carrier)
| group_by(.carrier)
| map({
carrier: .[0].carrier,
shipments: length
})
| sort_by(-.shipments).[] | .carrier.[] | .shipmentsCarry 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.
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.