How to Build an Interactive Vega-Lite Pareto Chart: Free Plug-and-Play Template 📊

Looking for a sleek, production-ready Vega-Lite Pareto chart template to elevate your Power BI (Deneb) or web dashboards? You are in the right place!

A Pareto Chart is based on the famous 80/20 Rule (the Pareto Principle): roughly 80% of consequences come from 20% of causes. This custom visual combines KPI summary cards, horizontal distribution bars, and a cumulative share curve into one seamless, high-impact design.

Pareto Chart

🎯 When Should You Use a Pareto Chart? (Top Use Cases)

A Vega-Lite Pareto chart is ideal whenever you need to separate the „vital few” from the „trivial many”:

  • 💰 Sales & Revenue (ABC Analysis): Identify the 20% of products or clients that generate 80% of your total revenue.
  • 🐛 Bug & Support Ticket Prioritization: Discover which recurring software bugs account for the vast majority of customer complaints.
  • 📦 Supply Chain & Inventory: Pinpoint high-cost inventory items eating up most of your warehouse budget.
  • 👥 Marketing & Website Traffic: Uncover the few top-performing landing pages or campaigns driving the majority of leads.

🛠️ How to Implement This Template (3 Quick Steps)

💡 Universal Code Notice: This JSON template is 100% plug-and-play. It works with any dataset — you just need two fields: a text/category column and a numeric value column.

Follow these steps to plug this Vega-Lite Pareto chart into your project:

  • First, prepare your data fields: Ensure your visual dataset has two columns:
    • Category (e.g., Product Name, Country, Issue Type)
    • Value (e.g., Sales Amount, Tickets Count, Cost)(If your data columns have different names, either rename them in your model or update the first two lines of the transform block).
  • Second, copy the JSON specification: Grab the complete JSON code provided at the bottom of this article.
  • Third, paste into your visual container: Drop the code into your Power BI Deneb visual or any Vega-Lite 5 renderer and watch it automatically rank, calculate cumulative percentages, and flag the 80% threshold!

🎨 How to Customize the Parameters

You don’t need to be a Vega-Lite expert to tweak this chart. Here are the two most common adjustments:

1. 🌈 Changing the Bar & Accent Colors

To change the primary bar color (items within the top 80%) and the muted bar color (items outside the 80%), look for the "BARS" layer around line 280:

JSON

 
"color": {
  "field": "IsTop80",
  "type": "nominal",
  "scale": {
    "domain": [1, 0],
    "range": [
      "#2a78d6",   // 🔵 COLOR FOR TOP 80% BARS (Change this hex code)
      "#dbe3ec"    // ⚪ COLOR FOR REMAINING BARS (Change this hex code)
    ]
  },
  "legend": null
}
  • To change the orange milestone highlight (the ring on the 80% cutoff point), change #eb6834 in the PARETO_CROSS mark and KPI_VAL_3.

2. 📏 Controlling How Many Bars Are Displayed

By default, the chart dynamically limits the list to prevent overcrowding and rolls up extra categories into a summary "Other / Pozostałe" bar.

Find this calculation in the transform section (around line 43):

JSON

 
{
  "calculate": "datum.TotalCategories <= 20 ? datum.TotalCategories : min(max(datum.CatsTo80, 5), 20)",
  "as": "RowsShown"
}
  • To show more/fewer bars: Replace 20 with your preferred maximum limit (e.g., 10 or 15).
  • To show ALL categories without rollup: Simply set:
    JSON"calculate": "datum.TotalCategories", "as": "RowsShown"

3. 🌐 Language / Label Localization (Optional)

The template includes built-in KPI titles and summary labels in Polish. You can quickly replace them with English (or any other language):

  • Change "TOP KATEGORIA""TOP CATEGORY" (in KPI_TITLE_1)
  • Change "UDZIAŁ TOP 1""TOP 1 SHARE" (in KPI_TITLE_2)
  • Change "KATEGORIE DO 80%""CATS TO 80%" (in KPI_TITLE_3)
  • Change "WARTOŚĆ ŁĄCZNIE""TOTAL VALUE" (in KPI_TITLE_4)
  • Change "Pozostałe""Other" (in IsOther label transform)

💻 Full Ready-to-Use Vega-Lite JSON Code

JSON

 
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {
    "name": "dataset"
  },
  "transform": [
    {
      "calculate": "datum['Category']",
      "as": "_cat"
    },
    {
      "calculate": "toNumber(datum['Value'])",
      "as": "_val"
    },
    {
      "filter": "isValid(datum._cat) && isValid(datum._val) && datum._val > 0"
    },
    {
      "aggregate": [
        {
          "op": "sum",
          "field": "_val",
          "as": "_val"
        }
      ],
      "groupby": [
        "_cat"
      ]
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "_val",
          "as": "TotalValue"
        },
        {
          "op": "count",
          "field": "_cat",
          "as": "TotalCategories"
        }
      ]
    },
    {
      "calculate": "datum._val / datum.TotalValue",
      "as": "Share0"
    },
    {
      "window": [
        {
          "op": "row_number",
          "as": "Rank0"
        },
        {
          "op": "sum",
          "field": "Share0",
          "as": "CumShare0"
        }
      ],
      "sort": [
        {
          "field": "_val",
          "order": "descending"
        }
      ]
    },
    {
      "calculate": "datum.CumShare0 - datum.Share0",
      "as": "Prev0"
    },
    {
      "calculate": "datum.Prev0 < 0.8 ? 1 : 0",
      "as": "Top80_0"
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Top80_0",
          "as": "CatsTo80"
        }
      ]
    },
    {
      "calculate": "datum.TotalCategories <= 20 ? datum.TotalCategories : min(max(datum.CatsTo80, 5), 20)",
      "as": "RowsShown"
    },
    {
      "calculate": "datum.Rank0 <= datum.RowsShown ? 0 : 1",
      "as": "IsOther"
    },
    {
      "calculate": "datum.IsOther === 1 ? 'Pozostałe (' + (datum.TotalCategories - datum.RowsShown) + ')' : datum._cat",
      "as": "_disp"
    },
    {
      "aggregate": [
        {
          "op": "sum",
          "field": "_val",
          "as": "_val"
        },
        {
          "op": "max",
          "field": "CumShare0",
          "as": "CumShare"
        },
        {
          "op": "min",
          "field": "Rank0",
          "as": "Rank"
        },
        {
          "op": "max",
          "field": "Top80_0",
          "as": "_t80"
        },
        {
          "op": "max",
          "field": "IsOther",
          "as": "IsOther"
        }
      ],
      "groupby": [
        "_disp",
        "TotalValue",
        "TotalCategories",
        "CatsTo80",
        "RowsShown"
      ]
    },
    {
      "calculate": "datum.IsOther === 1 ? 0 : datum._t80",
      "as": "IsTop80"
    },
    {
      "calculate": "datum._val / datum.TotalValue",
      "as": "Share"
    },
    {
      "joinaggregate": [
        {
          "op": "max",
          "field": "Share",
          "as": "MaxShare"
        }
      ]
    },
    {
      "calculate": "datum.Share / datum.MaxShare * 0.44",
      "as": "BarX"
    },
    {
      "calculate": "0.58",
      "as": "CumBase"
    },
    {
      "calculate": "0.58 + datum.CumShare * 0.36",
      "as": "CumX"
    },
    {
      "calculate": "datum.IsTop80 === 1 ? datum.Rank : 0",
      "as": "_t80rank"
    },
    {
      "joinaggregate": [
        {
          "op": "max",
          "field": "_t80rank",
          "as": "LastTop80"
        }
      ]
    },
    {
      "calculate": "datum.CatsTo80 + ' z ' + datum.TotalCategories",
      "as": "TopNLabel"
    },
    {
      "calculate": "length(datum._disp) <= 20 ? -1 : lastindexof(slice(datum._disp, 0, 21), ' ')",
      "as": "_wrapAt"
    },
    {
      "calculate": "datum._wrapAt < 1 ? datum._disp : slice(datum._disp, 0, datum._wrapAt) + '|' + slice(datum._disp, datum._wrapAt + 1)",
      "as": "_dispWrap"
    }
  ],
  "encoding": {
    "y": {
      "field": "_disp",
      "type": "nominal",
      "sort": {
        "op": "min",
        "field": "Rank",
        "order": "ascending"
      },
      "axis": {
        "title": null,
        "ticks": false,
        "domain": false,
        "labelFontSize": 12,
        "labelFontWeight": 600,
        "labelColor": "#334155",
        "labelPadding": 28,
        "labelLimit": 220
      }
    }
  },
  "layer": [
    {
      "name": "KPI_TITLE_1",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "bottom",
        "dy": -76,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#94a3b8",
        "text": "TOP KATEGORIA"
      },
      "encoding": {
        "x": {
          "datum": 0,
          "type": "quantitative",
          "scale": {
            "domain": [
              0,
              1.06
            ]
          },
          "axis": null
        }
      }
    },
    {
      "name": "KPI_VAL_1",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "top",
        "dy": -72,
        "fontSize": 15,
        "fontWeight": "normal",
        "fill": "#2a78d6",
        "limit": 180,
        "lineBreak": "|",
        "lineHeight": 18
      },
      "encoding": {
        "x": {
          "datum": 0,
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "_dispWrap",
          "type": "nominal"
        }
      }
    },
    {
      "name": "KPI_TITLE_2",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "bottom",
        "dy": -76,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#94a3b8",
        "text": "UDZIAŁ TOP 1"
      },
      "encoding": {
        "x": {
          "datum": 0.42,
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "KPI_VAL_2",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "top",
        "dy": -72,
        "fontSize": 15,
        "fontWeight": "bold",
        "fill": "#0f172a"
      },
      "encoding": {
        "x": {
          "datum": 0.42,
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "Share",
          "type": "quantitative",
          "format": ".1%"
        }
      }
    },
    {
      "name": "KPI_TITLE_3",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "bottom",
        "dy": -76,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#94a3b8",
        "text": "KATEGORIE DO 80%"
      },
      "encoding": {
        "x": {
          "datum": 0.63,
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "KPI_VAL_3",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "top",
        "dy": -72,
        "fontSize": 15,
        "fontWeight": "bold",
        "fill": "#eb6834"
      },
      "encoding": {
        "x": {
          "datum": 0.63,
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "TopNLabel",
          "type": "nominal"
        }
      }
    },
    {
      "name": "KPI_TITLE_4",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "right",
        "baseline": "bottom",
        "dy": -76,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#94a3b8",
        "text": "WARTOŚĆ ŁĄCZNIE"
      },
      "encoding": {
        "x": {
          "datum": 1.06,
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "KPI_VAL_4",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "right",
        "baseline": "top",
        "dy": -72,
        "fontSize": 15,
        "fontWeight": "bold",
        "fill": "#475569"
      },
      "encoding": {
        "x": {
          "datum": 1.06,
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "TotalValue",
          "type": "quantitative",
          "format": ",.0f"
        }
      }
    },
    {
      "name": "ZONE_TITLE_BARS",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "bottom",
        "dy": -14,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#b4bfcc",
        "text": "UDZIAŁ"
      },
      "encoding": {
        "x": {
          "datum": 0,
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "ZONE_TITLE_CUM",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "bottom",
        "dy": -14,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#b4bfcc",
        "text": "NARASTAJĄCO"
      },
      "encoding": {
        "x": {
          "datum": 0.58,
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "RAIL",
      "mark": {
        "type": "bar",
        "height": {
          "band": 0.6
        },
        "cornerRadius": 7,
        "fill": "#f1f5f9"
      },
      "encoding": {
        "x": {
          "datum": 0,
          "type": "quantitative",
          "axis": null
        },
        "x2": {
          "datum": 0.44
        }
      }
    },
    {
      "name": "BARS",
      "mark": {
        "type": "bar",
        "cornerRadiusEnd": 7,
        "height": {
          "band": 0.6
        },
        "cursor": "pointer"
      },
      "encoding": {
        "x": {
          "field": "BarX",
          "type": "quantitative",
          "axis": null
        },
        "color": {
          "field": "IsTop80",
          "type": "nominal",
          "scale": {
            "domain": [
              1,
              0
            ],
            "range": [
              "#2a78d6",
              "#dbe3ec"
            ]
          },
          "legend": null
        },
        "opacity": {
          "condition": {
            "test": "isValid(datum.__selected__) && datum.__selected__ === 'off'",
            "value": 0.3
          },
          "value": 1
        },
        "tooltip": [
          {
            "field": "_disp",
            "type": "nominal",
            "title": "Kategoria"
          },
          {
            "field": "_val",
            "type": "quantitative",
            "title": "Wartość",
            "format": ",.0f"
          },
          {
            "field": "Share",
            "type": "quantitative",
            "title": "Udział",
            "format": ".1%"
          },
          {
            "field": "CumShare",
            "type": "quantitative",
            "title": "Narastająco",
            "format": ".1%"
          },
          {
            "field": "Rank",
            "type": "quantitative",
            "title": "Pozycja"
          }
        ]
      }
    },
    {
      "name": "RANK_GUTTER",
      "transform": [
        {
          "filter": "datum.IsOther === 0"
        }
      ],
      "mark": {
        "type": "text",
        "align": "right",
        "baseline": "middle",
        "dx": -10,
        "fontSize": 10,
        "fontWeight": "bold"
      },
      "encoding": {
        "x": {
          "datum": 0,
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "Rank",
          "type": "quantitative"
        },
        "color": {
          "condition": {
            "test": "datum.IsTop80 === 1",
            "value": "#94a3b8"
          },
          "value": "#cbd5e1"
        }
      }
    },
    {
      "name": "BAR_LABELS",
      "mark": {
        "type": "text",
        "align": "left",
        "dx": 9,
        "baseline": "middle",
        "fontSize": 11,
        "fontWeight": "bold"
      },
      "encoding": {
        "x": {
          "field": "BarX",
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "Share",
          "type": "quantitative",
          "format": ".1%"
        },
        "color": {
          "condition": {
            "test": "datum.IsTop80 === 1",
            "value": "#0f172a"
          },
          "value": "#64748b"
        }
      }
    },
    {
      "name": "PARETO_80_LINE",
      "mark": {
        "type": "rule",
        "strokeDash": [
          3,
          4
        ],
        "color": "#cbd5e1",
        "strokeWidth": 1.5
      },
      "encoding": {
        "x": {
          "datum": 0.868,
          "type": "quantitative",
          "axis": null
        },
        "y": null
      }
    },
    {
      "name": "PARETO_80_LABEL",
      "transform": [
        {
          "filter": "datum.Rank === 1"
        }
      ],
      "mark": {
        "type": "text",
        "align": "center",
        "baseline": "bottom",
        "dy": -14,
        "fontSize": 9,
        "fontWeight": "bold",
        "fill": "#94a3b8",
        "text": "80%"
      },
      "encoding": {
        "x": {
          "datum": 0.868,
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "CUM_STEMS",
      "mark": {
        "type": "rule",
        "stroke": "#dce8f6",
        "strokeWidth": 5,
        "strokeCap": "round"
      },
      "encoding": {
        "x": {
          "field": "CumBase",
          "type": "quantitative",
          "axis": null
        },
        "x2": {
          "field": "CumX"
        }
      }
    },
    {
      "name": "PARETO_LINE",
      "mark": {
        "type": "line",
        "color": "#1e293b",
        "strokeWidth": 2,
        "interpolate": "monotone"
      },
      "encoding": {
        "x": {
          "field": "CumX",
          "type": "quantitative",
          "axis": null
        },
        "order": {
          "field": "Rank",
          "type": "quantitative"
        }
      }
    },
    {
      "name": "PARETO_CROSS",
      "transform": [
        {
          "filter": "datum.CatsTo80 <= datum.RowsShown && datum.Rank === datum.LastTop80"
        }
      ],
      "mark": {
        "type": "point",
        "size": 190,
        "fill": null,
        "stroke": "#eb6834",
        "strokeWidth": 2
      },
      "encoding": {
        "x": {
          "field": "CumX",
          "type": "quantitative",
          "axis": null
        }
      }
    },
    {
      "name": "PARETO_POINTS",
      "mark": {
        "type": "point",
        "filled": true,
        "size": 70,
        "stroke": "#ffffff",
        "strokeWidth": 2,
        "cursor": "pointer"
      },
      "encoding": {
        "x": {
          "field": "CumX",
          "type": "quantitative",
          "axis": null
        },
        "color": {
          "condition": {
            "test": "datum.IsTop80 === 1",
            "value": "#1e293b"
          },
          "value": "#94a3b8"
        },
        "opacity": {
          "condition": {
            "test": "isValid(datum.__selected__) && datum.__selected__ === 'off'",
            "value": 0.3
          },
          "value": 1
        },
        "tooltip": [
          {
            "field": "_disp",
            "type": "nominal",
            "title": "Kategoria"
          },
          {
            "field": "CumShare",
            "type": "quantitative",
            "title": "Narastająco",
            "format": ".1%"
          },
          {
            "field": "Share",
            "type": "quantitative",
            "title": "Udział",
            "format": ".1%"
          }
        ]
      }
    },
    {
      "name": "CUM_LABELS",
      "mark": {
        "type": "text",
        "align": "left",
        "baseline": "middle",
        "dx": 6,
        "fontSize": 11,
        "fontWeight": "bold"
      },
      "encoding": {
        "x": {
          "datum": 0.955,
          "type": "quantitative",
          "axis": null
        },
        "text": {
          "field": "CumShare",
          "type": "quantitative",
          "format": ".0%"
        },
        "color": {
          "condition": {
            "test": "datum.IsTop80 === 1",
            "value": "#0f172a"
          },
          "value": "#94a3b8"
        }
      }
    }
  ],
  "config": {
    "view": {
      "stroke": "transparent"
    },
    "font": "Segoe UI, -apple-system, BlinkMacSystemFont, Roboto, sans-serif",
    "padding": {
      "top": 92,
      "left": 12,
      "right": 30,
      "bottom": 12
    }
  }
}

🚀 Final Thoughts

With this Vega-Lite Pareto chart template, you get a modern, executive-ready dashboard component without writing hundreds of lines of calculations from scratch.

Have you tried using this template with your own data? Drop a comment below with your use case or any color combinations you've customized! 💬👇

Ready to eliminate static sorting bugs and elevate your dashboard design? You can grab the complete, copy-paste-ready Vega-Lite JSON specification and drop it straight into your Deneb visual.

📥 Download the Vega-Lite JSON Code (click below link)

Feel free to adapt the colors, label thresholds, and category counts to fit your organization's design system!