61
$\begingroup$

The display forms for objects like ClassifierFunction are nice clickable summary boxes

enter image description here

I like this, and now I'm trying to create my a custom version of this for my functions, so I dissected the code in the output cell and trimmed it down to this:

CellPrint@
     Cell[BoxData[
       InterpretationBox[
        RowBox[{DynamicModuleBox[{Typeset`open$$ = True}, 
           PanelBox[
            PaneSelectorBox[{False -> 
               GridBox[{{PaneBox[
                   ButtonBox[
                    DynamicBox[
                     FEPrivate`FrontEndResource["FEBitmaps", 
                      "SquarePlusIconMedium"], 
                     ImageSizeCache -> {12., {0., 12.}}], 
                    Appearance -> None, 
                    ButtonFunction :> (Typeset`open$$ = True), 
                    Evaluator -> Automatic, Method -> "Preemptive"], 
                   Alignment -> {Center, Center}, 
                   ImageSize -> 
                    Dynamic[{Automatic, 
                      3.5 (CurrentValue["FontCapHeight"]/
                        AbsoluteCurrentValue[Magnification])}]], 
                  GridBox[{{RowBox[{TagBox["\<\"Collapsed Form!\"\>", 
                        "SummaryItemAnnotation"]}]}}, 
                   BaseStyle -> {ShowStringCharacters -> False, 
                     NumberMarks -> False, PrintPrecision -> 3, 
                     ShowSyntaxStyles -> False}, 
                   GridBoxAlignment -> {"Columns" -> {{Left}}, 
                     "Rows" -> {{Automatic}}}, 
                   GridBoxItemSize -> {"Columns" -> {{Automatic}}, 
                     "Rows" -> {{Automatic}}}, 
                   GridBoxSpacings -> {"Columns" -> {{2}}, 
                     "Rows" -> {{Automatic}}}]}}, AutoDelete -> False, 
                BaselinePosition -> {1, 1}, 
                GridBoxAlignment -> {"Rows" -> {{Center}}}, 
                GridBoxItemSize -> {"Columns" -> {{Automatic}}, 
                  "Rows" -> {{Automatic}}}], 
              True -> GridBox[{{PaneBox[
                   ButtonBox[
                    DynamicBox[
                     FEPrivate`FrontEndResource["FEBitmaps", 
                      "SquareMinusIconMedium"], 
                     ImageSizeCache -> {12., {0., 12.}}], 
                    Appearance -> None, 
                    ButtonFunction :> (Typeset`open$$ = False), 
                    Evaluator -> Automatic, Method -> "Preemptive"], 
                   Alignment -> {Center, Center}, 
                   ImageSize -> 
                    Dynamic[{Automatic, 
                      3.5 (CurrentValue["FontCapHeight"]/
                        AbsoluteCurrentValue[Magnification])}]], 
                  GridBox[{{RowBox[{TagBox["\<\"Open Form\"\>", 
                        "SummaryItemAnnotation"]}]}, {RowBox[{TagBox[
                        "\<\"Open Form\"\>", 
                        "SummaryItemAnnotation"]}]}, {RowBox[{TagBox[
                        "\<\"Open Form\"\>", "SummaryItemAnnotation"]}]}},
                    BaseStyle -> {ShowStringCharacters -> False, 
                     NumberMarks -> False, PrintPrecision -> 3, 
                     ShowSyntaxStyles -> False}, 
                   GridBoxAlignment -> {"Columns" -> {{Left}}, 
                     "Rows" -> {{Automatic}}}, 
                   GridBoxItemSize -> {"Columns" -> {{Automatic}}, 
                     "Rows" -> {{Automatic}}}, 
                   GridBoxSpacings -> {"Columns" -> {{2}}, 
                     "Rows" -> {{Automatic}}}]}}, AutoDelete -> False, 
                BaselinePosition -> {1, 1}, 
                GridBoxAlignment -> {"Rows" -> {{Center}}}, 
                GridBoxItemSize -> {"Columns" -> {{Automatic}}, 
                  "Rows" -> {{Automatic}}}]}, Dynamic[Typeset`open$$], 
             ImageSize -> Automatic], BaselinePosition -> Baseline], 
           DynamicModuleValues :> {}]}], Missing[]]], "Output", 
      ImageSize -> {350, 47}, ImageMargins -> {{0, 0}, {0, 0}}, 
      ImageRegion -> {{0, 1}, {0, 1}}]

This code is a bit confusing to me and sadly many of the functions used have no documentation like DynamicBox, PanelBox, PaneSelectorBox... Perhaps there is a more convenient way of doing this than resorting to esoteric boxes?

$\endgroup$
2

4 Answers 4

58
+100
$\begingroup$

Mathematica does it internally by using BoxForm`ArrangeSummaryBox, which is quite straightforward to figure out.

Example

ClearAll[MyObject];

MyObject /: MakeBoxes[obj : MyObject[asc_? myObjectAscQ], form : (StandardForm | TraditionalForm)] := 
    Module[{above, below},
        above = { (* example grid *)
          {BoxForm`SummaryItem[{"Name: ", asc["Name"]}], SpanFromLeft},
          {BoxForm`SummaryItem[{"Variables: ", asc["Variables"]}], BoxForm`SummaryItem[{"Length: ", asc["Length"]}]}
        };
        below = { (* example column *)
          BoxForm`SummaryItem[{"Date: ", asc["Date"]}],
          BoxForm`SummaryItem[{"Metadata: ", asc[MetaInformation]}]
        };
        BoxForm`ArrangeSummaryBox[
           MyObject, (* head *)
           obj,      (* interpretation *)
           $icon,    (* icon, use None if not needed *)
           (* above and below must be in a format suitable for Grid or Column *)
           above,    (* always shown content *)
           below,    (* expandable content *)
           form,
           "Interpretable" -> Automatic
        ]
    ];

It is useful to define a function to test whether MyObject is in the correct format (and whether a summary box can be generated with no errors).

myObjectAscQ[asc_?AssociationQ] := AllTrue[{"Name", "Variables", "Date", "Length", MetaInformation}, KeyExistsQ[asc, #]&]
myObjectAscQ[_] = False;

Summary boxes typically have icons of a certain size:

$icon = 
  Graphics[{Red,Disk[]},
     ImageSize -> Dynamic[{ (* this seems to be the standard icon size *)
         Automatic, 
         3.5 CurrentValue["FontCapHeight"]/AbsoluteCurrentValue[Magnification]
       }]
  ];

Let us test it:

MyObject[<|
  "Name" -> "My particular object",
  "Length" -> 10,
  "Variables" -> {a,b,c},
  "Date" -> Today,
  MetaInformation -> "more info..." |>]

Mathematica graphics

In its expanded form it looks like this:

Mathematica graphics

The "Interpretable" option

If "Interpretable" is set to True, the formatted object can be used directly as input, and will be interpreted as the second argument of ArrangeSummaryBox.

If "Interpretable" is set to Automatic, Mathematica 11.2 and later will decide whether to embed the data into the displayed form of the object based on $SummaryBoxDataSizeLimit. When this size is exceeded, there will be a button that can be used to embed the data.

Mathematica graphics

Usage

Let us define a property retrieval interface, so out MyObject actually does something:

MyObject[asc_?AssociationQ][prop_] := Lookup[asc, prop]

Let's copy-paste the formatted object from above as new input:

Mathematica graphics

$\endgroup$
5
  • $\begingroup$ Is this a v10 only feature? $\endgroup$
    – QuantumDot
    Commented Dec 26, 2015 at 22:13
  • $\begingroup$ @Quantum I don't think so, as this type of object-representation is certainly older than v8. However, I cannot test it as I only have v10; perhaps someone cares to add this information. $\endgroup$ Commented Jan 23, 2016 at 16:57
  • 3
    $\begingroup$ I modernized the answer. Everything shown here should work in versions 10.0+ and is current as of 11.2. This is partly based on the implementation found in built-in packages, so this method of defining summary boxes should be safe. $\endgroup$
    – Szabolcs
    Commented Sep 21, 2017 at 15:10
  • $\begingroup$ Thanks a lot @Szabolcs, this greatly enhanced the post! $\endgroup$ Commented Sep 22, 2017 at 9:28
  • $\begingroup$ It appears that "Interpretable"-> Automatic is not supported on older versions of Mathematica (although "Interpretable" -> True is). For example, with 10.4.1 on macOS I get extra symbols EllisionsDumptypedHead, EllisionsDumpleftBracket, and EllisionsDump`rightBracket in the output along with the expected formatted output. The default with 12.1 is "Interpretable" -> Automatic anyway, so it is best for backwards compatibility to not specify this option. $\endgroup$ Commented May 13, 2020 at 14:56
28
$\begingroup$

The following shows a way to emulate the summary boxes using only documented constructs:

grid[g_] := 
 Column[Row /@ MapAt[Style[#, Gray] &, g, Table[{i, 1}, {i, Length[g]}]]]

foo /: MakeBoxes[c : foo[___], form : (StandardForm | TraditionalForm)] := 
 With[{boxes = 
    RowBox[{"foo", "[", 
      ToBoxes[Panel[
        OpenerView[
         {grid[{{"Something: ", 1}, {"Other: ", 2}}], 
          grid[{{"Extra: ", 3}}]}
        ], 
        BaselinePosition -> Baseline], 
       form], "]"}]
    }, 
    InterpretationBox[boxes, c]
 ]

foo[]

Mathematica graphics

This is a simplified version of something I used in practice.

It only makes sense to use this if you have some sort of requirement to only use documented functions.


If you do this, do not forget to also provide a nice OutputForm, which will be automatically used when running Mathematica in command line mode. It can be done like this:

Format[foo[___], OutputForm] := "foo[<>]"
$\endgroup$
0
18
$\begingroup$

Perhaps there is a more convenient way of doing this than resorting to esoteric boxes.

The following uses the code you posted to define a function:

summaryDisplay = DynamicModule[{open = True, 
  sqrplus = RawBoxes@FrontEndResource["FEBitmaps", "SquarePlusIconMedium"],
  sqrminus = RawBoxes@FrontEndResource["FEBitmaps", "SquareMinusIconMedium"],
  paneF = Pane[Button[Dynamic[#, ImageSizeCache -> {12., {0., 12.}}], 
      open = #2, Appearance -> None, Evaluator -> Automatic,  Method -> "Preemptive"], 
     Alignment -> {Center, Center}, 
     ImageSize -> Dynamic[{Automatic, 3.5 (CurrentValue["FontCapHeight"]/
           AbsoluteCurrentValue[Magnification])}]] &,
  gridF1 = Grid[#, BaseStyle -> {ShowStringCharacters -> False, 
       NumberMarks -> False, PrintPrecision -> 3, ShowSyntaxStyles -> False}, 
     Alignment -> {{{Left}}, {{Automatic}}}, 
     ItemSize -> {{{Automatic}}, {{Automatic}}}, 
     Spacings -> {{{2}}, {{Automatic}}}] &,
  gridF0 = Grid[{{##}}, AutoDelete -> False, BaselinePosition -> {1, 1}, 
     Alignment -> {{{Center}}}, ItemSize -> {{{Automatic}}, {{Automatic}}}] &}, 
 Panel[PaneSelector[{False -> 
   gridF0[paneF[sqrplus, True], gridF1[{Row[{#}]} & /@ #]], 
  True -> gridF0[paneF[sqrminus, False], 
    gridF1[{Row[{#}]} & /@ #2]]}, Dynamic[open], 
 ImageSize -> Automatic], BaselinePosition -> Baseline, 
ImageSize -> {200, Automatic}, ImageMargins -> {{0, 0}, {0, 0}}], 
DynamicModuleValues :> {}] &;

Example:

summaryDisplay[{"ClosedForm"}, {"OpenForm", "OpenForm", "OpenForm"}]

enter image description here

$\endgroup$
14
$\begingroup$

The following solution is slightly more general than @Istvàn's answer, and also fixes the icon-size to what seems to be the standard.

First, we define

ClearAll[summaryForm];
summaryForm[name_, icon_, infos_, maxInfos_, form_, expr_] := RawBoxes@Quiet@Check[
  Module[{shown, hidden},
    (* convert info-table into summary items *)
    {shown, hidden} = Map[BoxForm`MakeSummaryItem[#, form]&, TakeDrop[infos, maxInfos], {2}];
    (* arrange the summary and make sure the label is gray (therefore StyleBox) *)
    StyleBox[BoxForm`ArrangeSummaryBox[
      name, 
      expr,
      Deploy@Show[icon, 
        (* avoid gray background before mouse-over *)
        Background -> White, 
        (* set standard image size *)
        ImageSize -> Dynamic[{
          Automatic, 
          3.5 CurrentValue["FontCapHeight"]/AbsoluteCurrentValue[Magnification]
        }]
      ],
      shown,
      hidden,
      form,
      (* formatted object should be uasable as input *)
      "Interpretable" -> True
    ], "NonInterpretableSummary"]
  ],
  (* in case something goes wrong *)
  ToBoxes@Panel@Short[expr]
]

Now you can specify automatic formatting for your own function, using

Format[testfunction[x_], StandardForm] := summaryForm[
  "testfunction",
   Graphics[{Circle[], Disk[{0,0},1,{0,x}]}],
  {{"Even: ",EvenQ[x]},{"Value: ",x},{"Prime: ",PrimeQ[x]}},2,
  StandardForm,
  x
]

The last argument x is how the summary should be interpreted, so in the more general case you may want to replace it by something like testfunction[x].

Then testfunction is displayed as

testfunction[2]

testfunction summary panel

And of course you can use it as input, and the opener works as expected:

testfunction summary panel in equation

(* -15 *)

Using summaryForm it does not matter how your object is structured internally, and, once defined, you can use it to create many different kinds of formatted objects with very little additional code.

Since this Q&A is a bit hard to find if you don't know how this feature is called, I mention a few more functions which have these summary panels: InterpolatingFunction, TimeSeries, EventSeries, DataDistribution.

$\endgroup$
1
  • 1
    $\begingroup$ Thanks for this. One problem I ran into when defining a Format for a function with a ::usage message and info that uses Part is that ? throws an Part::partd -- Part specification StandardForm[Short[Shallow[HoldForm[in[[1]]], {10, 50}], 5]] is longer than depth of object. error. In this case, extra Quiets in the SummaryForm call fixed it. Just putting this here in case someone else runs into this issue. $\endgroup$
    – Chris K
    Commented Jun 10, 2023 at 18:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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