|
| 1 | +# ADR: Display UX Facade |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Accepted. |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +The current user-facing display API mixes presentation actions, analysis |
| 10 | +reports, and renderer configuration: |
| 11 | + |
| 12 | +```python |
| 13 | +project.display.plotter.plot_meas(expt_name='hrpt') |
| 14 | +project.display.plotter.plot_calc(expt_name='hrpt') |
| 15 | +project.display.plotter.plot_meas_vs_calc(expt_name='hrpt') |
| 16 | +project.display.plotter.plot_param_correlations() |
| 17 | +project.display.plotter.plot_posterior_pairs() |
| 18 | +project.display.plotter.plot_param_distribution(param) |
| 19 | +project.display.plotter.plot_posterior_predictive(expt_name='hrpt') |
| 20 | + |
| 21 | +project.analysis.display.free_params() |
| 22 | +project.analysis.display.fit_results() |
| 23 | +``` |
| 24 | + |
| 25 | +This has several UX problems: |
| 26 | + |
| 27 | +- `plot_` is redundant below any display or chart object. |
| 28 | +- `plotter` and `tabler` expose backend implementation language to |
| 29 | + scientists using notebooks. |
| 30 | +- `project.display` and `project.analysis.display` overlap without a |
| 31 | + clear user-facing rule. |
| 32 | +- `plot_meas`, `plot_calc`, and `plot_meas_vs_calc` force users to |
| 33 | + choose a plot state that the project can often infer. |
| 34 | +- Bayesian and deterministic chart names are not systematic. |
| 35 | +- The existing `project.display` category is serialized to CIF, so it |
| 36 | + should not also become a broad transient display facade. |
| 37 | + |
| 38 | +EasyDiffraction is aimed at scientists, often non-programmers, so the |
| 39 | +display API should prioritize discoverability, clear names, and safe |
| 40 | +defaults. |
| 41 | + |
| 42 | +## Decision |
| 43 | + |
| 44 | +Use `project.display` as the user-facing facade for display actions. |
| 45 | +Move serialized renderer settings out of that facade and into a separate |
| 46 | +project category named `project.rendering`. |
| 47 | + |
| 48 | +Renderer settings: |
| 49 | + |
| 50 | +```python |
| 51 | +project.rendering.chart_engine = 'plotly' |
| 52 | +project.rendering.table_engine = 'pandas' |
| 53 | +project.rendering.show_chart_engines() |
| 54 | +project.rendering.show_table_engines() |
| 55 | +project.rendering.show_config() |
| 56 | +``` |
| 57 | + |
| 58 | +Suggested CIF names: |
| 59 | + |
| 60 | +- `_rendering.chart_engine` |
| 61 | +- `_rendering.table_engine` |
| 62 | + |
| 63 | +No legacy loader is required for `_display.plotter_type` or |
| 64 | +`_display.tabler_type`. The project is in beta, so this cleanup may |
| 65 | +break old project files rather than carrying compatibility code. |
| 66 | + |
| 67 | +The selected display API is grouped: |
| 68 | + |
| 69 | +```python |
| 70 | +project.display.pattern(expt_name='hrpt') |
| 71 | + |
| 72 | +project.display.parameters.free() |
| 73 | +project.display.parameters.fittable() |
| 74 | +project.display.parameters.all() |
| 75 | +project.display.parameters.access() |
| 76 | +project.display.parameters.cif_uids() |
| 77 | + |
| 78 | +project.display.fit.results() |
| 79 | +project.display.fit.correlations() |
| 80 | +project.display.fit.series(param, versus=temperature) |
| 81 | + |
| 82 | +project.display.posterior.pairs() |
| 83 | +project.display.posterior.distribution(param) |
| 84 | +project.display.posterior.predictive(expt_name='hrpt') |
| 85 | + |
| 86 | +project.display.show_pattern_options(expt_name='hrpt') |
| 87 | +``` |
| 88 | + |
| 89 | +`project.analysis.display` is removed from the primary public API. Its |
| 90 | +current responsibilities move to clearer homes: |
| 91 | + |
| 92 | +| Current method | New home | |
| 93 | +| ---------------------------- | -------------------------------------------------------------- | |
| 94 | +| `all_params()` | `project.display.parameters.all()` | |
| 95 | +| `fittable_params()` | `project.display.parameters.fittable()` | |
| 96 | +| `free_params()` | `project.display.parameters.free()` | |
| 97 | +| `how_to_access_parameters()` | `project.display.parameters.access()` | |
| 98 | +| `parameter_cif_uids()` | `project.display.parameters.cif_uids()` | |
| 99 | +| `fit_results()` | `project.display.fit.results()` | |
| 100 | +| `constraints()` | `project.analysis.constraints.show()` | |
| 101 | +| `as_cif()` | `project.analysis.as_cif` and `project.analysis.show_as_cif()` | |
| 102 | + |
| 103 | +`project.analysis` and `project.info` should follow the same CIF display |
| 104 | +pattern as structures and experiments: |
| 105 | + |
| 106 | +- `as_cif` is a read-only property returning CIF text as a string. |
| 107 | +- `show_as_cif()` pretty-prints the CIF text with a header. |
| 108 | + |
| 109 | +## Pattern Display |
| 110 | + |
| 111 | +Use `pattern()` as the main experiment chart: |
| 112 | + |
| 113 | +```python |
| 114 | +project.display.pattern(expt_name='hrpt') |
| 115 | +project.display.pattern(expt_name='hrpt', x_min=40, x_max=55) |
| 116 | +``` |
| 117 | + |
| 118 | +By default, `pattern()` uses `include='auto'` and displays as much |
| 119 | +useful information as the project state supports: |
| 120 | + |
| 121 | +- measured data if present |
| 122 | +- calculated data if a model/calculation is available |
| 123 | +- background if defined and relevant |
| 124 | +- Bragg ticks if phases/reflections are available |
| 125 | +- residual if both measured and calculated data are available and the |
| 126 | + experiment type supports a residual panel |
| 127 | +- excluded regions if available |
| 128 | +- uncertainty bands where posterior predictive data exists |
| 129 | + |
| 130 | +Specific subsets are selected with `include`: |
| 131 | + |
| 132 | +```python |
| 133 | +project.display.pattern(expt_name='hrpt', include='auto') |
| 134 | +project.display.pattern(expt_name='hrpt', include='measured') |
| 135 | +project.display.pattern(expt_name='hrpt', include='calculated') |
| 136 | +project.display.pattern( |
| 137 | + expt_name='hrpt', |
| 138 | + include=('measured', 'calculated', 'background', 'residual', 'bragg'), |
| 139 | +) |
| 140 | +``` |
| 141 | + |
| 142 | +`include` was chosen over alternatives: |
| 143 | + |
| 144 | +| Name | Reason not selected | |
| 145 | +| ------------- | ----------------------------------------------- | |
| 146 | +| `layers` | Sounds graphical rather than user intent. | |
| 147 | +| `components` | Precise, but longer. | |
| 148 | +| `content` | Too broad. | |
| 149 | +| `view` | Better for presets than arbitrary combinations. | |
| 150 | +| `series` | Does not fit residual rows or Bragg ticks well. | |
| 151 | +| boolean flags | Explicit, but scales poorly. | |
| 152 | + |
| 153 | +Add discovery for supported pattern content: |
| 154 | + |
| 155 | +```python |
| 156 | +project.display.show_pattern_options(expt_name='hrpt') |
| 157 | +``` |
| 158 | + |
| 159 | +The table should show option name, description, availability for the |
| 160 | +experiment, whether `include='auto'` includes it, and the reason an |
| 161 | +option is unavailable. |
| 162 | + |
| 163 | +Initial option names: |
| 164 | + |
| 165 | +- `auto` |
| 166 | +- `measured` |
| 167 | +- `calculated` |
| 168 | +- `background` |
| 169 | +- `residual` |
| 170 | +- `bragg` |
| 171 | +- `excluded` |
| 172 | +- `uncertainty` |
| 173 | + |
| 174 | +`uncertainty` should be implemented immediately where posterior |
| 175 | +predictive data exists. It should be unavailable, with a clear reason, |
| 176 | +when no posterior predictive data is present. |
| 177 | + |
| 178 | +## Deterministic And Bayesian Consistency |
| 179 | + |
| 180 | +Use these naming rules: |
| 181 | + |
| 182 | +- `pattern()` shows the current point-estimate experiment view. |
| 183 | +- `fit.results()` reports the latest fit result. |
| 184 | +- `fit.correlations()` shows parameter relationships from the latest |
| 185 | + fit. |
| 186 | +- `fit.series(param, versus=...)` shows fitted parameter values across a |
| 187 | + sequence of fit results or experiments. |
| 188 | +- `posterior.*` names are used only when posterior samples are required. |
| 189 | + |
| 190 | +## Rejected Alternatives |
| 191 | + |
| 192 | +Flat display facade: |
| 193 | + |
| 194 | +```python |
| 195 | +project.display.pattern(expt_name='hrpt') |
| 196 | +project.display.parameters(scope='free') |
| 197 | +project.display.fit_results() |
| 198 | +project.display.correlations() |
| 199 | +project.display.parameter_series(param, versus=temperature) |
| 200 | +project.display.posterior_pairs() |
| 201 | +project.display.posterior_distribution(param) |
| 202 | +project.display.posterior_predictive(expt_name='hrpt') |
| 203 | +``` |
| 204 | + |
| 205 | +This is shorter but would make `project.display` grow into a long flat |
| 206 | +list. |
| 207 | + |
| 208 | +Separate `charts` and `tables` namespaces were also rejected because |
| 209 | +users should not need to decide the output type before asking for |
| 210 | +information. Some outputs may render as a chart or a table depending on |
| 211 | +backend and state. |
| 212 | + |
| 213 | +Separate `measured()` and `calculated()` methods were rejected because |
| 214 | +they duplicate `pattern(..., include=...)`. |
| 215 | + |
| 216 | +## Consequences |
| 217 | + |
| 218 | +- The main display workflow becomes more discoverable through grouped |
| 219 | + namespaces and tab completion. |
| 220 | +- Renderer configuration becomes clearly separate from display actions. |
| 221 | +- Existing tutorials and public API docs must be updated to the selected |
| 222 | + API. |
| 223 | +- Constraints remain owned by the analysis constraints category. |
| 224 | +- There is no legacy CIF compatibility path for `_display.plotter_type` |
| 225 | + or `_display.tabler_type`. |
| 226 | +- `project.analysis` and `project.info` need CIF access cleanup for |
| 227 | + consistency with structure and experiment objects. |
0 commit comments