4
$\begingroup$

I'm looking to use BarChart3D (in "Grid" mode) to show the elements of two real & positive matrices simultaneously. Since this will become a function in a library, I want to make it simple and extensible.

I plot a single matrix via:

BarChart3D[
    matrix1,

    ChartLayout -> "Grid", 
    Method -> {"Canvas" -> False}, 
    BarSpacing -> {.1, .1},
    ChartElementFunction -> Function[{xyz, z}, {
        Cuboid @@ Transpose @ xyz}]
 ]

which renders (as desired) like:

enter image description here

I now aim to plot the second matrix as semi-transparent cuboids stacked above the first matrix cuboids (showing only their difference; as if the second matrix were "under" the first, showing only its values greater than the first matrix's). This is similar to this question, but with some important differences:

  • In that question, the height of the transparent cuboids was fixed. Here, their height is element-dependent
  • The location of my cuboids must be precisely centered at (1,1), (1,2) ... etc for strict x and y labeling.

Something simple like Show doesn't work...

style = {ChartLayout -> "Grid", Method -> {"Canvas" -> False}, BarSpacing -> {.1, .1}}

Show[
    BarChart3D[
        matrix1,
        style,

        ChartElementFunction -> Function[{xyz, z},
            {Cuboid @@ Transpose@xyz}]
    ],
    BarChart3D[
        matrix2,
        style, 

        ChartElementFunction -> Function[{xyz, z},
            {Opacity[.2], Cuboid @@ Transpose@xyz}]
    ]
 ]

because the faces clip eachother:

enter image description here

The next obvious solution is to force the Cuboids on a strict apriori-known grid (by BarSpacing -> {0,0}) and draw the transparent stacked cuboid myself.

(* will actually grab this from OptionValue[BarSpacing] *)
space  = .1;

BarChart3D[

    (* informs only (x,y) vals passed to ChartElementFuncion *)
    matrix1,

    ChartLayout -> "Grid",
    Method -> {"Canvas" -> False},

    (* force no spacing to keep (x,y) data on integer-grid *)
    BarSpacing -> {0, 0},

    ChartElementFunction -> Function[xyz,

        (*  take max (x,y) corner as center (cx,cy)=(i,j) *)
        With[
            {cx = xyz[[1, 2]], cy = xyz[[2, 2]], offset = -1/2 + space/2},
            {lx = cx + offset, rx = cx - offset,
             ly = cy + offset, ry = cy - offset, 
             z1 = matrix1[[ Round@cx, Round@cy]], 
             z2 = matrix2[[ Round@cx, Round@cy]]},

             {
                 (* draw matrix1 *)
                 Opacity[1], 
                 Cuboid[{lx, ly, 0}, {rx, ry, z1}],

                 (* draw matrix2 *)
                 If[z2 > z1, {
                     Opacity[.3], 
                     Cuboid[{lx, ly, 1.01 z1}, {rx, ry, z2}]}
                 ]
            }
        ]
    ]
]

The result appears as desired... enter image description here but this makes customisation (e.g. of the colours) a bit awkward for the user. E.g. if they specify ColorFunction -> "SolarColors", they'll see enter image description here that the matrix1 colors are being determined by the matrix2 values (when matrix2 > matrix1). Furthermore, I totally disregarded the z value that BarChart3D passed to ChartElementFunction, and looked up a different one in the matrices directly (which incidentally, doesn't match). This seems quite hacky, and I don't know whether it will bite me later.

Is there a better way to approach this?

$\endgroup$
  • 1
    $\begingroup$ So we can reproduce, can you please add matrix1? If it is large, do this: url = CloudPut[matrix1, Permissions->"Public"] and paste in CloudGet @ url $\endgroup$ – M.R. May 13 at 0:37
3
$\begingroup$

As a variant of your first approach, try just randomly shifting the positions of the bars a tiny bit that will be invisible to the human eye but will prevent the rendering collision effect. The following is just your code (with lines to generate fake data) and just the second Cuboid@@ slightly perturbed

matrix2 = DiagonalMatrix[Table[RandomReal[{0.8, 0.95}], {8}]];
matrix1 = 0.3*matrix2 + Table[RandomReal[{0, 0.2}], {8}, {8}];

style = {ChartLayout -> "Grid", Method -> {"Canvas" -> False}, BarSpacing -> {.1, .1}}

Show[
    BarChart3D[
        matrix1,
        style,

        ChartElementFunction -> Function[{xyz, z},
            {Cuboid @@ Transpose@xyz}]
    ],
    BarChart3D[
        matrix2,
        style, 

        ChartElementFunction -> Function[{xyz, z},
            {Opacity[.2], Cuboid @@ Transpose@(0.0015223 + 0.999123 xyz)}]
    ]
 ]

enter image description here

| improve this answer | |
$\endgroup$
  • $\begingroup$ This is the only solution which worked without my library unexpectedly changing the user's frontend settings. Note in lieu of randomly perturbing the second BarChart's cuboids, one can just place them "slightly inside" the first BarChart's by a tiny margin $\endgroup$ – Anti Earth May 18 at 12:56
6
$\begingroup$

The "face-clipping" artifacts in your second example are a common issue when rendering coplanar faces:

enter image description here

This problem is called Z-Fighting, and luckily, it has an easy fix:

Barchart[..., Method -> {"RelieveDPZFighting" -> True}]

Here's your code and with a few small changes:

matrix1 = Rescale @ Table[i+j,{i,8},{j,8}]//N;
matrix2 = matrix1+RandomReal[1,{8,8}]/2;
SetOptions[$FrontEnd, RenderingOptions -> {"Graphics3DRenderingEngine" -> "BSPTree"}]; (* set this for best rendering *)
style = {Method -> {"RelieveDPZFighting"->True}, ChartLayout -> "Grid", BarSpacing -> {.1, .1}};
Show[
    BarChart3D[matrix1, ChartElementFunction -> Function[{xyz, z},
        {Cuboid @@ Transpose@xyz}], Sequence @@ style
    ],
    BarChart3D[matrix2, ChartElementFunction -> Function[{xyz, z},
        {Opacity[.2], Cuboid @@ Transpose@xyz}], Sequence @@ style
    ],
    ViewPoint -> {-2,-3,1}, ViewAngle -> 16\[Degree], ImageSize -> {800,500}, BoxRatios -> {1, 1, 1/1.8}, PlotRangePadding -> 0
]

enter image description here

| improve this answer | |
$\endgroup$
  • $\begingroup$ Method -> {"RelieveDPZFighting"->True} has no effect on the clipping I see (I'm on MacOS if this matters). It is resolved by modifying the front-end rendering options (regardless of RelieveDPZFighting or not), but I feel I should avoid this in a library under the principle of least astonishment $\endgroup$ – Anti Earth May 13 at 15:25
  • $\begingroup$ I should point out too that this doesn't achieve the result of "matrix2 cuboids stacked above matrix1 since even when transparent here, the 'top' cuboids effect the colouring of the matrix1 cuboids beanath them (evident when coloured different, e.g. by respective ColorFunctions. $\endgroup$ – Anti Earth May 13 at 16:41
  • $\begingroup$ (my ability to edit expired, hmph): I can avoid this by slightly shrinking the x-y size of the matrix2 cuboids (so they hide within the matrix1 cuboids) in ChartEllementFunction, though it's a small inelegance $\endgroup$ – Anti Earth May 13 at 16:47
2
$\begingroup$

If matrix1 and matrix2 have non-negative entries, you can also construct WeightedData objects and use Histogram:

SeedRandom[1]
matrix1 = Rescale@Table[i + j, {i, 8}, {j, 8}] // N;
matrix2 = matrix1 + RandomReal[1, {8, 8}]/2;

{wd1, wd2} = WeightedData[Join @@ Array[List, Dimensions @ #], 
     Join @@ #] & /@ {matrix1, matrix2};

Histogram3D[{wd1, wd2},  ChartStyle -> {Opacity[1], Opacity[.3]}, 
 Ticks -> {Transpose[{Range[8], CharacterRange["A", "Z"][[;; 8]]}], Range[8], Automatic}, 
 ChartElementFunction -> ({EdgeForm[{Thin, Gray}], 
      Blend[System`PlotThemeDump`$ThemeDefaultGradient, #2[[1, 1]]/8],
       ChartElementData["Cube"][{{.1, -.1}, {.1, -.1}, {0, 0}} + #, #2]}&), 
 ImageSize -> Large, BoxRatios -> {1, 1, 1/2}]

enter image description here

| improve this answer | |
$\endgroup$
  • $\begingroup$ This seems the most natural way to achieve the plot, and cleanly separate the styles of the two matrices. However, it has the same face-clipping problem that M. R. addresses in his solution (also ineffective here) $\endgroup$ – Anti Earth May 13 at 15:28
  • $\begingroup$ Ah and by virtue of being the 'right' way, has the limitation of being able to specify a single ColorFunction, whereas I can 'split up' a user's ColorFunction -> {f1, f2} myself if I use a Show solution $\endgroup$ – Anti Earth May 13 at 15:48
  • 1
    $\begingroup$ Note the user must supply the number-of-bins arg to Histogram3D, or suddenly (for >=16-by-16 matrices) bars will stack $\endgroup$ – Anti Earth May 17 at 18:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.