Violin Chart of Salary Bands by Level
If you are the HR analyst preparing a pay review, the question is not what the median salary is at each level. It is whether everyone at a level is paid like the others, or whether the level is really two groups sharing a title. A median and a range will not tell you which.
A violin chart does. Each level gets a mirrored shape whose width at any salary is how many people are paid around it, so a single hump reads as one band and a pinch with a second bulge reads as two. The chart above is bound to the grid beneath it, one row per employee, so filtering the rows redraws the chart. Filter the Level column to Senior and Lead and compare them alone.
Why a violin and not a box plot
A box plot reduces each level to a median and a middle half, so a Senior level split into one group near 73,000 and another near 93,000 is drawn as one tidy box sitting between them, over a salary almost nobody earns. The violin keeps the shape, so the gap shows.
The chart over a grid
The chart reads the grid’s rows, so there is nothing to keep in step. A violin takes a category column, x, and a measure column, y, and draws one mirrored density shape per category over the values of the measure. Here the category is the level and the measure is the salary. Here is the example in plain JavaScript, with sample rows inline in place of your own.
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
const pay = {
Associate: [38000, 40000, 41000, 42000, 44000, 46000],
Mid: [52000, 54000, 56000, 57000, 59000, 61000],
Senior: [70000, 72000, 74000, 90000, 93000, 96000],
Lead: [100000, 104000, 106000, 109000, 112000, 115000],
};
const rows = Object.entries(pay)
.flatMap(([level, list]) => list.map((salary) => ({ level, salary })))
.map((r, i) => ({ id: i + 1, ...r }));
const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = '260px';
chartEl.style.height = '320px';
document.body.append(gridEl, chartEl);
const grid = createGrid(gridEl, {
rowKey: 'id',
rows,
columns: [
{ field: 'level', title: 'Level', filter: { type: 'set' } },
{ field: 'salary', title: 'Salary', type: 'number' },
],
});
createChart({ grid, container: chartEl, type: 'violin', x: 'level', y: 'salary',
title: 'Salary by level' });
The same thing in React
The React adapter is a factory, so you build the component once at module scope. The ref’s grid is the real grid, which is what the chart binds to.
import React, { useEffect, useRef } from 'react';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
const LatticeGrid = createLatticeGrid({ React, createGrid });
const rows = [
{ id: 1, level: 'Mid', salary: 52000 }, { id: 2, level: 'Mid', salary: 56000 },
{ id: 3, level: 'Mid', salary: 58000 }, { id: 4, level: 'Mid', salary: 61000 },
{ id: 5, level: 'Senior', salary: 70000 }, { id: 6, level: 'Senior', salary: 73000 },
{ id: 7, level: 'Senior', salary: 92000 }, { id: 8, level: 'Senior', salary: 96000 },
];
const columns = [
{ field: 'level', title: 'Level', filter: { type: 'set' } },
{ field: 'salary', title: 'Salary', type: 'number' },
];
export function SalaryByLevel() {
const gridRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
const chart = createChart({ grid: gridRef.current.grid, container: chartRef.current,
type: 'violin', x: 'level', y: 'salary', title: 'Salary by level' });
return () => chart.destroy();
}, []);
return (<>
<LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 220 }} />
<div ref={chartRef} style={{ height: 280 }} />
</>);
}
The options used
These are the rows of the charts reference for the options in the listings above.
| Option | Type | What it does |
|---|---|---|
grid |
Grid | Required. The grid to read. |
container |
Element or string | Required. Where to draw. |
type |
string | 'violin' here, one of the chart types the charts module draws. |
x / y |
string | Category and measure columns. Here x is the level, which gets one shape each, and y is the salary the shape is drawn over. |
title |
string | Drawn above the plot. |
Reading the shapes
Width is how many people are paid near that salary, and every shape is drawn on the same salary axis, so a shape further up means higher pay. Associate and Mid are single humps. Senior is not: it has a broad lower bulge, a pinch near 80,000 and a second bulge above it, so the level holds a group paid in the low seventies and a group paid in the low nineties under one title. That is a question for the pay review that a median could never raise. The grid still drives it: filter to one department or one location, and every shape is drawn again from the rows that are left.
Next
See the violin beside the other distribution charts in the distribution chart demo, read the full list of chart types on the charts page, or follow the how-to for a chart that follows the grid.