Variable widths on BoxWhiskerChart
This cannot be done easily with BoxWhiskerChart
as the method suboption "BoxWidth"
only accepts "Scaled"
and "Fixed"
, but for such specialized cases you can always fall back to ChartElementFunction
. Here I recreate the default chart element from scratch with all the necessary statistical measures. Note, that the highlighting and tooltip on mouseover are still there!
I rescale the duration length for the i
-th data set by dividing it with the max duration value, to yield w
.
data = {{1, 4, 3, 5, 1, 2}, {1, 5, 4, 3, 3, 4, 4, 2, 3, 2, 8}};
durations = {180, 60};
f[{{xmin_, xmax_}, {ymin_, ymax_}}, data_, meta_, i_] := Module[
{w = durations[[i]]/(2 Max@durations), mid = xmin + (xmax - xmin)/2.,
median, q75, q25},
{median, q75, q25} = Rescale[{Median@data, Quantile[data, 3/4],
Quantile[data, 1/4]}, {Min@data, Max@data}, {ymin, ymax}];
{
{Antialiasing -> False, [email protected], [email protected],
Line@{{mid, ymin}, {mid, ymax}}},
{RGBColor[.798, .825, .968],
Rectangle[{mid - w, q25}, {mid + w, q75}]},
{Antialiasing -> False, White,
Line@{{mid - w, median}, {mid + w, median}}},
{Antialiasing -> False, [email protected], [email protected],
Line@{{mid - w, ymin}, {mid + w, ymin}},
Line@{{mid - w, ymax}, {mid + w, ymax}}}
}];
And now compare the default with the modified chart:
{
BoxWhiskerChart[data, ImageSize -> 300],
i = 1;
BoxWhiskerChart[data, ChartElementFunction -> (f[##, i++] &), ImageSize -> 300]
}
A custom ChartElementFunction
that modifies built-in ChartElementFunctions
s to make box widths to depend on metadata:
ClearAll[ceF, preP]
ceF[f_:"BoxWhisker"] := ChartElementData[f][{#3[[1,1]] + #3[[1,2]]{-1, 1}/2, #[[2]]}, ##2]&
And a data preparation function that attaches width information as metadata to input data:
preP = Thread[# -> MapIndexed[{#2[[1]], #} &, Normalize[#2, Max]]] &;
Examples:
data = {{1, 4, 3, 5, 1, 2}, {1, 5, 4, 3, 3, 4, 4, 2, 3, 2, 8}};
durations = {180, 60};
BoxWhiskerChart[preP[data, durations], ChartStyle -> 63,
ChartLabels -> (Style[#, 16] & /@ {"A", "B", "C", "D"}),
ChartElementFunction -> ceF[]]
data2 = {{1, 4, 3, 5, 1, 2}, {1, 5, 4, 3, 3}, {4, 2, 3, 2, 8}, {1, 5,
4, 3, 3, 4, 4, 2, 3, 2, 8}};
durations2 = {180, 60, 120, 90};
BoxWhiskerChart[preP[data2, durations2], ChartStyle -> 63,
ChartLabels -> (Style[#, 16] & /@ {"A", "B", "C", "D"}),
ChartElementFunction -> ceF["GlassBoxWhisker"]]
Update: It turns out that this approach has an advantage over the others posted in that it can handle different settings for BarOrigin
without modification.
BoxWhiskerChart[preP2[data2, durations2],
ChartLabels -> (Style[#, 20] & /@ {"A", "B", "C", "D"}),
ChartStyle -> 63, BarSpacing -> 0, ImageSize -> 400,
Method -> {"BoxWidth" -> "Scaled", "EqualSpacing" -> False},
ChartElementFunction -> ceF2["GlassBoxWhisker"],
BarOrigin -> #] & /@ {Bottom, Top, Left, Right} // Partition[#, 2] & // Grid
Original answer:
For practical purposes this question is solved by the two answers above. This answer, out of curiosity, takes up the puzzle
Is it possible to apply a list of durations to BoxWidth somehow?
That is, can we somehow use "BoxWidth"
to reflect durations rather than sample sizes.
A simple trick to achieve this is to use a fake data set with (1) sample sizes that depend on durations
so that using the "BoxWidth" -> "Scaled"
does give the desired bar widths, (2) actual data used as metadata so that its quantiles can be used inside the ChartElementFunction
to set the correct "BoxRange"
and produce the correct primitives. We also set (i) BarSpacing -> 0
to avoid more complicated sample size calculations, (ii) "EqualSpacing" -> False
to avoid complications with tick/ label positions.
ClearAll[ceF2, preP2]
ceF2 [f_: "BoxWhisker"] := (Module[{br = Quantile[#3[[1, 1]], {0., .25, .5, .75, 1.}]},
Charting`ChartStyleInformation["BoxRange"] = br;
ChartElementDataFunction[f][{#[[1]] + #3[[1, 2]] - Mean[#[[1]]], #[[2]]}, ##2]] &)
preP2 = ConstantArray[1, #2^2] -> {#, #3} & @@@ Transpose[{#, 5 Normalize[#2, GCD @@ # &],
Range[Length@#] - .5}] &;
Examples:
data = {{1, 4, 3, 5, 1, 2}, {1, 5, 4, 3, 3, 4, 4, 2, 3, 2, 8}};
durations = {180, 60};
BoxWhiskerChart[preP2[data, durations],
ChartLabels -> (Style[#, 20] & /@ {"A", "B"}), ChartStyle -> 63,
BarSpacing -> 0, Method -> {"BoxWidth" -> "Scaled", "EqualSpacing" -> False},
ChartElementFunction -> ceF2["GlassBoxWhisker"]]
data2 = {{1, 4, 3, 5, 1, 2}, {1, 5, 4, 3, 3}, {4, 2, 3, 2, 8},
{1, 5, 4, 3, 3, 4, 4, 2, 3, 2, 8}};
durations2 = {180, 60, 120, 90};
BoxWhiskerChart[preP2[data2, durations2],
ChartLabels -> (Style[#, 20] & /@ {"A", "B", "C", "D"}), ChartStyle -> 63,
BarSpacing -> 0, Method -> {"BoxWidth" -> "Scaled", "EqualSpacing" -> False},
ChartElementFunction -> ceF2["GlassBoxWhisker"]]