%% tikzviolinplots.sty %% Copyright 2026 Pedro Callil-Soares % % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 % of this license or (at your option) any later version. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is Pedro Callil-Soares. % % This work consists of the files tikzviolinplots.sty and % tikzviolinplots.tex. \NeedsTeXFormat{LaTeX2e}[1994/06/01] \ProvidesPackage{tikzviolinplots}[2026/03/31 v0.11.1 Violin plot creation in pgfplots] \RequirePackage{pgfplots} \RequirePackage{pgfplotstable} \RequirePackage{pgfkeys} \DeclareOption*{\PackageWarning{tikzviolinplots}{Unknown option: ‘\CurrentOption’}} \ProcessOptions\relax \newif\ifviolin@scaled \newif\ifviolin@datapoints \newif\ifviolin@averages \newif\ifviolin@nomirror \newif\ifviolin@reverseaxis \newif\ifviolin@invert \pgfkeys{ /violinplot/.is family, /violinplot/.search also=/pgfplots, /violinplot, % scaled/.is if=violin@scaled, data points/.is if=violin@datapoints, averages/.is if=violin@averages, no mirror/.is if=violin@nomirror, reverse axis/.is if=violin@reverseaxis, invert/.is if=violin@invert, % xmin/.value required, xmax/.value required, ymin/.value required, ymax/.value required, col sep/.value required, kernel/.value required, bandwidth/.value required, index/.value required, indexes/.value required, frequencies/.value required, samples/.value required, relative position/.value required, spacing/.value required, color/.value required, primary color/.value required, secondary color/.value required, fill opacity/.value required, label/.value required, labels/.value required, data label style/.value required, average mark/.value required, average size/.value required, average color/.value required, average opacity/.value required, average fill/.value required, average fill opacity/.value required, dataset mark/.value required, dataset size/.value required, dataset color/.value required, dataset opacity/.value required, dataset fill/.value required, dataset fill opacity/.value required, dataset jitter/.value required, % col sep/.is choice, col sep/space/.code=\def\violin@colsep{space}, col sep/tab/.code=\def\violin@colsep{tab}, col sep/comma/.code=\def\violin@colsep{comma}, col sep/colon/.code=\def\violin@colsep{colon}, col sep/semicolon/.code=\def\violin@colsep{semicolon}, col sep/braces/.code=\def\violin@colsep{braces}, col sep/ampersand/.code=\def\violin@colsep{ampersand}, % kernel/.is choice, kernel/gaussian/.code=\def\violin@kernel{gaussian}, kernel/logistic/.code=\def\violin@kernel{logistic}, kernel/parabolic/.code=\def\violin@kernel{parabolic}, kernel/uniform/.code=\def\violin@kernel{uniform}, kernel/triangular/.code=\def\violin@kernel{triangular}, % xmin/.store in = \violin@xmin, xmax/.store in = \violin@xmax, ymin/.store in = \violin@ymin, ymax/.store in = \violin@ymax, bandwidth/.store in = \violin@bandwidth, index/.store in = \violin@index, indexes/.store in = \violin@indexes, frequencies/.store in = \violin@frequencies, samples/.store in = \violin@samples, relative position/.store in = \violin@delta, spacing/.store in = \violin@spacing, color/.store in = \violin@color, primary color/.store in = \violin@color@primary, secondary color/.store in = \violin@color@secondary, fill opacity/.store in = \violin@fillopacity, label/.store in = \violin@label, labels/.store in = \violin@labels, data label style/.store in = \violin@datalabelstyle, average mark/.store in = \violin@avg@mark, average size/.store in = \violin@avg@size, average color/.store in = \violin@avg@color, average opacity/.store in = \violin@avg@opacity, average fill/.store in = \violin@avg@fillcolor, average fill opacity/.store in = \violin@avg@fillopacity, dataset mark/.store in = \violin@pts@mark, dataset size/.store in = \violin@pts@size, dataset color/.store in = \violin@pts@color, dataset opacity/.store in = \violin@pts@opacity, dataset fill/.store in = \violin@pts@fillcolor, dataset fill opacity/.store in = \violin@pts@fillopacity, dataset jitter/.store in = \violin@pts@jitter, % xmin=-10, xmax=10, ymin=-10, ymax=10, % xmin/.forward to = {/pgfplots/xmin}, xmax/.forward to = {/pgfplots/xmax}, ymin/.forward to = {/pgfplots/ymin}, ymax/.forward to = {/pgfplots/ymax}, % default/.style = { col sep=comma, kernel=gaussian, bandwidth=NONE, index=DATA, indexes={A,B,C}, frequencies=FREQINTERNALVIOLIN, samples=50, relative position=0, spacing=1.0, color=black, primary color=blue, secondary color=black, fill opacity=0.5, label={LABEL}, labels={A,B,C}, data label style={}, invert=false, average mark=x, average size=3pt, average color=black, average opacity=1.0, average fill=white, average fill opacity=0.5, dataset mark=*, dataset size=1pt, dataset color=black, dataset opacity=1.0, dataset fill=black, dataset fill opacity=0.2, dataset jitter=0.0, }, default, } \newcommand{\violinsetoptions}[2][]{% \pgfkeys@spdef\violin@violinargs{#1} \pgfkeys@spdef\violin@pgfplotsargs{#2} \edef\violin@args{ /handler config=only existing, /violinplot, \expandonce{\violin@violinargs}, \expandonce{\violin@pgfplotsargs}, /handler config=all, } \pgfkeysalsofrom{\violin@args} \pgfmathqparse{0} \let\violin@numofplots\pgfmathresult % Needed to distinguish first plot from others in \violinplot } \newcommand{\addviolinplot}[2][]{% \pgfkeys{/violinplot, #1} \edef\violin@filename{#2} \pgfkeys{/pgf/fpu} \pgfplotstableread[% col sep=\violin@colsep, trim cells=true, ]{\violin@filename}\theviolin@dataset@table \pgfplotstablegetrowsof{\theviolin@dataset@table} \pgfmathparse{int(\pgfmathresult-1)} \let\violin@filename@lastindex\pgfmathresult \pgfmathqparse{0} \let\violin@dataset@stddev\pgfmathresult \let\violin@dataset@average\pgfmathresult \let\violin@dataset@weightsum\pgfmathresult \let\violin@weight@row\pgfmathresult % Calculating averages \def\violin@frequencies@default{FREQINTERNALVIOLIN} \pgfplotstableforeachcolumnelement{\violin@index}\of\theviolin@dataset@table\as\xi{% \ifx\violin@frequencies\violin@frequencies@default \pgfmathqparse{1} \let\violin@dataset@weight\pgfmathresult \else \pgfplotstablegetelem{\violin@weight@row}{\violin@frequencies}\of{\theviolin@dataset@table} \let\violin@dataset@weight\pgfplotsretval \pgfmathparse{\violin@weight@row+1} \let\violin@weight@row\pgfmathresult \fi \pgfmathparse{\violin@dataset@weightsum+\violin@dataset@weight} \let\violin@dataset@weightsum\pgfmathresult \pgfmathparse{\violin@dataset@average+\xi*\violin@dataset@weight} \let\violin@dataset@average\pgfmathresult } \pgfmathparse{\violin@dataset@average/\violin@dataset@weightsum} \let\violin@dataset@average\pgfmathresult \ifviolin@datapoints \pgfplotstablecreatecol[ create col/assign/.code={ \let\entry\violin@delta \pgfkeyslet{/pgfplots/table/create col/next content}\entry } ]{deltacol}\theviolin@dataset@table \fi \ifviolin@reverseaxis \def\violin@axis@x{y} \def\violin@axis@y{x} \else \def\violin@axis@x{x} \def\violin@axis@y{y} \fi % Bandwidth calculation \def\violin@bandwidth@default{NONE} \ifx\violin@bandwidth\violin@bandwidth@default \pgfmathqparse{0} \let\violin@weight@row\pgfmathresult % Standard deviation calculation \pgfplotstableforeachcolumnelement{\violin@index}\of\theviolin@dataset@table\as\xi{% \ifx\violin@frequencies\violin@frequencies@default \pgfmathqparse{1} \let\violin@dataset@weight\pgfmathresult \else \pgfplotstablegetelem{\violin@weight@row}{\violin@frequencies}\of{\theviolin@dataset@table} \let\violin@dataset@weight\pgfplotsretval \pgfmathparse{\violin@weight@row+1} \let\violin@weight@row\pgfmathresult \fi \pgfmathparse{\violin@dataset@stddev+\violin@dataset@weight*(\xi-\violin@dataset@average)^2} \let\violin@dataset@stddev\pgfmathresult } \pgfmathparse{sqrt(\violin@dataset@stddev/\violin@dataset@weightsum)} \let\violin@dataset@stddev\pgfmathresult \pgfmathparse{1.06*\violin@dataset@stddev*(\violin@dataset@weightsum^(-0.2))}% \let\violin@bandwidthcalc\pgfmathresult \else \let\violin@bandwidthcalc\violin@bandwidth \fi % Setting limits for curve \pgfplotstablegetelem{0}{\violin@index}\of{\theviolin@dataset@table} \let\violin@dataset@min\pgfplotsretval \let\violin@dataset@max\pgfplotsretval \pgfplotstableforeachcolumnelement{\violin@index}\of\theviolin@dataset@table\as\xi{% \pgfmathparse{\xi > \violin@dataset@min} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfmathresult=0 \let\violin@dataset@min\xi \fi } \pgfmathparse{\violin@dataset@min - 3*\violin@bandwidthcalc} \let\violin@dataset@min\pgfmathresult \pgfplotstableforeachcolumnelement{\violin@index}\of\theviolin@dataset@table\as\xi{% \pgfmathparse{\xi < \violin@dataset@max} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfmathresult=0 \let\violin@dataset@max\xi \fi } \pgfmathparse{\violin@dataset@max + 3*\violin@bandwidthcalc} \let\violin@dataset@max\pgfmathresult % Creating curve domain \pgfplotstableset{ create on use/list/.style={create col/expr={ \violin@dataset@min + (\violin@dataset@max-\violin@dataset@min) *(\pgfplotstablerow/(\violin@samples-1)) }} } % Calculating KDE \pgfplotstablenew[columns={list}]{\violin@samples}\violin@kde \pgfmathqparse{0} \let\violin@kde@max\pgfmathresult \def\violin@kernel@gaussian{gaussian} \def\violin@kernel@logistic{logistic} \def\violin@kernel@parabolic{parabolic} \def\violin@kernel@uniform{uniform} \def\violin@kernel@triangular{triangular} \pgfplotstablecreatecol[ create col/assign/.code={ \pgfmathparse{% 1.0/(\violin@dataset@weightsum*\violin@bandwidthcalc) } \let\violin@kde@factor\pgfmathresult \pgfmathqparse{0} \let\violin@kde@accum\pgfmathresult \let\violin@weight@row\pgfmathresult \pgfplotstableforeachcolumnelement{\violin@index}\of\theviolin@dataset@table\as\xi{% \ifx\violin@frequencies\violin@frequencies@default \pgfmathqparse{1} \let\violin@dataset@weight\pgfmathresult \else \pgfplotstablegetelem{\violin@weight@row}{\violin@frequencies}\of{\theviolin@dataset@table} \let\violin@dataset@weight\pgfplotsretval \pgfmathparse{\violin@weight@row+1} \let\violin@weight@row\pgfmathresult \fi \pgfmathparse{% ((\xi-\thisrow{list})/% \violin@bandwidthcalc)} \let\violin@u\pgfmathresult % \ifx\violin@kernel\violin@kernel@gaussian \pgfmathparse{\violin@kde@accum + % \violin@dataset@weight*e^(-0.5*\violin@u*\violin@u)/sqrt(2*pi)} \let\violin@kde@accum\pgfmathresult \fi \ifx\violin@kernel\violin@kernel@logistic \pgfmathparse{\violin@kde@accum + % \violin@dataset@weight*1/(e^\violin@u + 2 + e^(-\violin@u)) } \let\violin@kde@accum\pgfmathresult \fi \ifx\violin@kernel\violin@kernel@parabolic \pgfmathparse{abs(\violin@u) < 1} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfmathresult=1 \pgfmathparse{\violin@kde@accum + % \violin@dataset@weight*0.75*(1-\violin@u*\violin@u) } \else \pgfmathparse{\violin@kde@accum} \fi \let\violin@kde@accum\pgfmathresult \fi \ifx\violin@kernel\violin@kernel@uniform \pgfmathparse{abs(\violin@u) < 1} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfmathresult=1 \pgfmathparse{\violin@kde@accum + \violin@dataset@weight*0.5} \else \pgfmathparse{\violin@kde@accum} \fi \let\violin@kde@accum\pgfmathresult \fi \ifx\violin@kernel\violin@kernel@triangular \pgfmathparse{abs(\violin@u) < 1} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfmathresult=1 \pgfmathparse{\violin@kde@accum + % \violin@dataset@weight*(1-abs(\violin@u))} \else \pgfmathparse{\violin@kde@accum} \fi \let\violin@kde@accum\pgfmathresult \fi } \pgfmathparse{\violin@kde@accum*\violin@kde@factor} \let\entry\pgfmathresult \pgfkeyslet{/pgfplots/table/create col/next content}\entry } ]{kdecol}\violin@kde \pgfplotstableforeachcolumnelement{kdecol}\of\violin@kde\as\entry{% \pgfmathparse{\entry < \violin@kde@max} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfmathresult=0 \let\violin@kde@max\entry \fi } \pgfplotstablemodifyeachcolumnelement{kdecol}\of\violin@kde\as\cell{% \let\pgfplotstablezero\violin@delta \ifnum\pgfplotstablerow=0 \let\cell\pgfplotstablezero \else \pgfmathparse{int(\violin@samples-1)} \pgfmathfloattoint{\pgfmathresult} \ifnum\pgfplotstablerow=\pgfmathresult \let\cell\pgfplotstablezero \else \ifviolin@nomirror \pgfmathqparse{1.9} \else \pgfmathqparse{1} \fi \let\violin@mirror@factor\pgfmathresult \ifviolin@scaled \pgfmathparse{\violin@delta+\violin@mirror@factor*0.5*\cell/\violin@kde@max} \else \pgfmathparse{\violin@delta+\violin@mirror@factor*\cell} \fi \let\cell\pgfmathresult \fi \fi } \ifviolin@nomirror \ifviolin@invert \pgfplotstablemodifyeachcolumnelement{kdecol}\of\violin@kde\as\cell{% \pgfmathparse{-\cell+2*\violin@delta} \let\cell\pgfmathresult } \fi \else \pgfplotstablecreatecol[ create col/assign/.code={ \pgfmathparse{-\thisrow{kdecol}+2*\violin@delta} \let\entry\pgfmathresult \pgfkeyslet{/pgfplots/table/create col/next content}\entry } ]{kdecolmirror}\violin@kde \pgfplotstablenew[columns={list}]{\violin@samples}\violin@kdewithmirror \pgfplotstablecreatecol[ copy column from table={\violin@kde}{kdecol} ]{kdecol}{\violin@kdewithmirror} \pgfplotstablesort[ sort key=list, sort cmp=float <, ]{\violin@kdewithmirror}{\violin@kdewithmirror} \pgfplotstablenew[columns={list}]{\violin@samples}\violin@kdemirror \pgfplotstablecreatecol[ copy column from table={\violin@kde}{kdecolmirror} ]{kdecol}{\violin@kdemirror} \pgfplotstablesort[ sort key=list, sort cmp=float >, ]{\violin@kdemirror}{\violin@kdemirror} \pgfplotstablevertcat{\violin@kdewithmirror}{\violin@kdemirror} \fi \pgfkeys{/pgf/fpu=false} % Data points must be centered only if plot is mirrored \ifviolin@nomirror \ifviolin@invert \def\randfct{(-rnd)} \else \def\randfct{(rnd)} \fi \else \def\randfct{(rand)} \fi \ifviolin@nomirror \ifviolin@reverseaxis \edef\violin@addplot{ \noexpand\addplot[ no marks, fill=\violin@color, fill opacity=\violin@fillopacity, ] table [ \violin@axis@x=kdecol, \violin@axis@y=list ] {\noexpand\violin@kde}; } \violin@addplot \edef\violin@addplot{ \noexpand\addplot[ no marks, color=black, ] coordinates { (\violin@xmin,\violin@delta)% (\violin@xmax,\violin@delta) }; } \violin@addplot \else \edef\violin@addplot{ \noexpand\addplot[ no marks, fill=\violin@color, fill opacity=\violin@fillopacity, ] table [ \violin@axis@x=kdecol, \violin@axis@y=list ] {\noexpand\violin@kde}; } \violin@addplot \edef\violin@addplot{ \noexpand\addplot[ no marks, color=black, ] coordinates { (\violin@delta,\violin@ymin)% (\violin@delta,\violin@ymax) }; } \violin@addplot \fi \else \edef\violin@addplot{ \noexpand\addplot[ no marks, fill=\violin@color, fill opacity=\violin@fillopacity, ] table [ \violin@axis@x=kdecol, \violin@axis@y=list ] {\noexpand\violin@kdewithmirror}; } \violin@addplot \fi \ifviolin@averages \ifviolin@reverseaxis \edef\violin@addplot{ \noexpand\addplot[ only marks, mark=\violin@avg@mark, mark size=\violin@avg@size, color=\violin@avg@color, opacity=\violin@avg@opacity, fill=\violin@avg@fillcolor, fill opacity=\violin@avg@fillopacity, ] coordinates { (\violin@dataset@average,\violin@delta) }; } \violin@addplot \else \edef\violin@addplot{ \noexpand\addplot[ only marks, mark=\violin@avg@mark, mark size=\violin@avg@size, color=\violin@avg@color, opacity=\violin@avg@opacity, fill=\violin@avg@fillcolor, fill opacity=\violin@avg@fillopacity, ] coordinates { (\violin@delta,\violin@dataset@average) }; } \violin@addplot \fi \fi \ifviolin@datapoints \edef\violin@addplot{ \noexpand\addplot[ only marks, mark=\violin@pts@mark, mark size=\violin@pts@size, color=\violin@pts@color, opacity=\violin@pts@opacity, fill=\violin@pts@fillcolor, fill opacity=\violin@pts@fillopacity, \violin@axis@x\space filter/.expression={\violin@axis@x + \violin@pts@jitter * \randfct} ] table [ \violin@axis@x=deltacol, \violin@axis@y=\violin@index, ] {\noexpand\theviolin@dataset@table}; } \violin@addplot \fi } \newcommand{\violin@getnth}[2]{% \pgfmathqparse{0} \let\violin@getnth@counter\pgfmathresult \let\violin@nthelem\pgfmathresult \@for\violin@listelem:=#1\do{ \ifnum#2=\violin@getnth@counter \let\violin@nthelem\violin@listelem \pgfmathparse{int(\violin@getnth@counter+1)} \let\violin@getnth@counter\pgfmathresult \else \pgfmathparse{int(\violin@getnth@counter+1)} \let\violin@getnth@counter\pgfmathresult \fi } } \newcommand{\violinplot}[2][]{% \pgfkeys{/violinplot, default} \pgfkeysalsofrom{\violin@args} \pgfkeys{/violinplot, #1} \def\violin@filename{#2} \ifviolin@reverseaxis \pgfplotsset{ yticklabels/.expand once={\violin@label}, ytick/.expand once={\violin@delta}, yticklabel style={\violin@datalabelstyle}, } \else \pgfplotsset{ xticklabels/.expand once={\violin@label}, xtick/.expand once={\violin@delta}, xticklabel style={\violin@datalabelstyle}, } \fi \pgfmathparse{\violin@numofplots!=0} % Current plot is not first plot \ifnum\pgfmathresult=1 \ifviolin@reverseaxis \pgfplotsset{hide x axis} \else \pgfplotsset{hide y axis} \fi \pgfplotsset{ title={}, xlabel={}, ylabel={}, axis line style={draw=none}, grid={none}, } \fi \begin{axis}[ axis on top, ] \addviolinplot{\violin@filename} \end{axis} \pgfmathparse{int(\violin@numofplots+1)} \let\violin@numofplots\pgfmathresult } \newcommand{\violinplotwholefile}[2][]{% \pgfkeys{/violinplot, default} \pgfkeysalsofrom{\violin@args} \pgfkeys{/violinplot, #1} \def\violin@filename{#2} \pgfmathqparse{0} \let\violin@numofplots\pgfmathresult \@for\violin@plotindex:=\violin@indexes\do{ \pgfmathparse{int(\violin@numofplots+1)} \let\violin@numofplots\pgfmathresult } \pgfmathqparse{0} \let\violin@counter\pgfmathresult \let\violin@relativepos\pgfmathresult \pgfmathqparse{1} \let\violin@start\pgfmathresult \pgfmathparse{1+\violin@spacing} \let\violin@step\pgfmathresult \pgfmathparse{\violin@spacing*(\violin@numofplots-\violin@start)+\violin@start} \let\violin@stop\pgfmathresult \ifviolin@reverseaxis \pgfplotsset{ yticklabels/.expand once={\violin@labels}, ytick/.expand once={\violin@start, \violin@step, ..., \violin@stop}, yticklabel style={\violin@datalabelstyle}, } \else \pgfplotsset{ xticklabels/.expand once={\violin@labels}, xtick/.expand once={\violin@start, \violin@step, ..., \violin@stop}, xticklabel style={\violin@datalabelstyle}, } \fi \begin{axis}[ axis on top, ] \@for\violin@plotindex:=\violin@indexes\do{ \ifnum\violin@numofplots=1 \pgfmathqparse{50} \else \pgfmathparse{int(\violin@counter*(100/(\violin@numofplots-1)))} \fi \let\violin@color@deg\pgfmathresult \pgfmathparse{1+\violin@counter*\violin@spacing} \let\violin@relativepos\pgfmathresult \addviolinplot[% index=\violin@plotindex, relative position=\violin@relativepos, color={\violin@color@primary!\violin@color@deg!\violin@color@secondary}, ]{\violin@filename} \pgfmathparse{int(\violin@counter+1)} \let\violin@counter\pgfmathresult } \end{axis} } % PGFplots-analogous alias \let\violinplotsset\violinsetoptions