Skip to content

Commit 7eeeecc

Browse files
Further improvements to distribution plot (#1252)
1 parent c15225f commit 7eeeecc

File tree

5 files changed

+128
-31
lines changed

5 files changed

+128
-31
lines changed

frontend/src/modules/DistributionPlot/interfaces.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
import type { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface";
22

3-
import { numBinsAtom, orientationAtom, plotTypeAtom } from "./settings/atoms/baseAtoms";
4-
import type { PlotType } from "./typesAndEnums";
3+
import {
4+
barSortByAtom,
5+
numBinsAtom,
6+
orientationAtom,
7+
plotTypeAtom,
8+
sharedXAxesAtom,
9+
sharedYAxesAtom,
10+
} from "./settings/atoms/baseAtoms";
11+
import type { BarSortBy, PlotType } from "./typesAndEnums";
512

613
type SettingsToViewInterface = {
714
plotType: PlotType | null;
815
numBins: number;
916
orientation: "h" | "v";
17+
sharedXAxes: boolean;
18+
sharedYAxes: boolean;
19+
barSortBy: BarSortBy;
1020
};
1121

1222
export type Interfaces = {
@@ -17,4 +27,7 @@ export const settingsToViewInterfaceInitialization: InterfaceInitialization<Sett
1727
plotType: (get) => get(plotTypeAtom),
1828
numBins: (get) => get(numBinsAtom),
1929
orientation: (get) => get(orientationAtom),
30+
sharedXAxes: (get) => get(sharedXAxesAtom),
31+
sharedYAxes: (get) => get(sharedYAxesAtom),
32+
barSortBy: (get) => get(barSortByAtom),
2033
};
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { atom } from "jotai";
22

3-
import { PlotType } from "@modules/DistributionPlot/typesAndEnums";
4-
3+
import { BarSortBy, PlotType } from "@modules/DistributionPlot/typesAndEnums";
54

65
export const plotTypeAtom = atom<PlotType | null>(PlotType.Histogram);
76
export const numBinsAtom = atom<number>(10);
87
export const orientationAtom = atom<"h" | "v">("h");
8+
export const sharedXAxesAtom = atom<boolean>(false);
9+
export const sharedYAxesAtom = atom<boolean>(false);
10+
export const barSortByAtom = atom<BarSortBy>(BarSortBy.Value);

frontend/src/modules/DistributionPlot/settings/settings.tsx

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,24 @@ import { useAtom } from "jotai";
44

55
import { useApplyInitialSettingsToState } from "@framework/InitialSettings";
66
import type { ModuleSettingsProps } from "@framework/Module";
7+
import { Checkbox } from "@lib/components/Checkbox";
78
import { CollapsibleGroup } from "@lib/components/CollapsibleGroup";
89
import { Dropdown } from "@lib/components/Dropdown";
910
import { Label } from "@lib/components/Label";
1011
import { RadioGroup } from "@lib/components/RadioGroup";
1112
import { Slider } from "@lib/components/Slider";
1213

1314
import type { Interfaces } from "../interfaces";
14-
import { PlotType } from "../typesAndEnums";
15+
import { BarSortBy, PlotType } from "../typesAndEnums";
1516

16-
import { numBinsAtom, orientationAtom, plotTypeAtom } from "./atoms/baseAtoms";
17+
import {
18+
barSortByAtom,
19+
numBinsAtom,
20+
orientationAtom,
21+
plotTypeAtom,
22+
sharedXAxesAtom,
23+
sharedYAxesAtom,
24+
} from "./atoms/baseAtoms";
1725

1826
const plotTypes = [
1927
{
@@ -39,6 +47,9 @@ export function Settings({ initialSettings }: ModuleSettingsProps<Interfaces>) {
3947
const [plotType, setPlotType] = useAtom(plotTypeAtom);
4048
const [numBins, setNumBins] = useAtom(numBinsAtom);
4149
const [orientation, setOrientation] = useAtom(orientationAtom);
50+
const [sharedXAxes, setSharedXAxes] = useAtom(sharedXAxesAtom);
51+
const [sharedYAxes, setSharedYAxes] = useAtom(sharedYAxesAtom);
52+
const [barSortBy, setBarSortBy] = useAtom(barSortByAtom);
4253

4354
useApplyInitialSettingsToState(initialSettings, "plotType", "string", setPlotType);
4455
useApplyInitialSettingsToState(initialSettings, "numBins", "number", setNumBins);
@@ -64,7 +75,24 @@ export function Settings({ initialSettings }: ModuleSettingsProps<Interfaces>) {
6475
return null;
6576
}
6677
const content: React.ReactNode[] = [];
67-
78+
const axisContent: React.ReactNode = (
79+
<>
80+
<div className="mb-2 text-gray-500">
81+
<Checkbox
82+
label="Shared X Axes"
83+
checked={sharedXAxes}
84+
onChange={(_, checked) => setSharedXAxes(checked)}
85+
/>
86+
</div>
87+
<div className="mb-2">
88+
<Checkbox
89+
label="Shared Y Axes"
90+
checked={sharedYAxes}
91+
onChange={(_, checked) => setSharedYAxes(checked)}
92+
/>
93+
</div>
94+
</>
95+
);
6896
if (plotType === PlotType.Histogram) {
6997
content.push(
7098
<CollapsibleGroup title="Plot settings" expanded>
@@ -77,10 +105,17 @@ export function Settings({ initialSettings }: ModuleSettingsProps<Interfaces>) {
77105
valueLabelDisplay="auto"
78106
/>
79107
</Label>
108+
{axisContent}
109+
</CollapsibleGroup>,
110+
);
111+
}
112+
if (plotType === PlotType.Scatter || plotType === PlotType.ScatterWithColorMapping) {
113+
content.push(
114+
<CollapsibleGroup title="Plot settings" expanded>
115+
{axisContent}
80116
</CollapsibleGroup>,
81117
);
82118
}
83-
84119
if (plotType === PlotType.BarChart) {
85120
content.push(
86121
<CollapsibleGroup title="Plot settings" expanded>
@@ -100,6 +135,22 @@ export function Settings({ initialSettings }: ModuleSettingsProps<Interfaces>) {
100135
value={orientation}
101136
/>
102137
</Label>
138+
<Label text="Sort bars by" key="bar-sort-by">
139+
<RadioGroup
140+
options={[
141+
{
142+
label: "Value",
143+
value: BarSortBy.Value,
144+
},
145+
{
146+
label: "Key",
147+
value: BarSortBy.Key,
148+
},
149+
]}
150+
onChange={(_, value) => setBarSortBy(value as typeof barSortBy)}
151+
value={barSortBy}
152+
/>
153+
</Label>
103154
</CollapsibleGroup>,
104155
);
105156
}

frontend/src/modules/DistributionPlot/typesAndEnums.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ export enum PlotType {
44
Scatter = "scatter",
55
ScatterWithColorMapping = "scatterWithColor",
66
}
7+
export enum BarSortBy {
8+
Value = "value",
9+
Key = "key",
10+
}

frontend/src/modules/DistributionPlot/view.tsx

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import { ContentWarning } from "@modules/_shared/components/ContentMessage/conte
1717
import { Plot } from "@modules/_shared/components/Plot";
1818
import { makeSubplots } from "@modules/_shared/Figure";
1919
import { makeHistogramTrace } from "@modules/_shared/histogram";
20+
import { formatNumber } from "@modules/_shared/utils/numberFormatting";
2021

2122
import type { Interfaces } from "./interfaces";
22-
import { PlotType } from "./typesAndEnums";
23+
import { BarSortBy, PlotType } from "./typesAndEnums";
2324
import { makeHoverText, makeHoverTextWithColor, makeTitleFromChannelContent } from "./utils/stringUtils";
2425
import { calcTextSize } from "./utils/textSize";
2526

@@ -47,10 +48,16 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
4748
const [prevNumBins, setPrevNumBins] = React.useState<number | null>(null);
4849
const [prevOrientation, setPrevOrientation] = React.useState<"v" | "h" | null>(null);
4950
const [prevSize, setPrevSize] = React.useState<Size2D | null>(null);
51+
const [prevSharedXAxes, setPrevSharedXAxes] = React.useState<boolean | null>(null);
52+
const [prevSharedYAxes, setPrevSharedYAxes] = React.useState<boolean | null>(null);
53+
const [prevBarSortBy, setPrevBarSortBy] = React.useState<BarSortBy>(BarSortBy.Value);
5054

5155
const plotType = viewContext.useSettingsToViewInterfaceValue("plotType");
56+
const sharedXAxes = viewContext.useSettingsToViewInterfaceValue("sharedXAxes");
57+
const sharedYAxes = viewContext.useSettingsToViewInterfaceValue("sharedYAxes");
5258
const numBins = viewContext.useSettingsToViewInterfaceValue("numBins");
5359
const orientation = viewContext.useSettingsToViewInterfaceValue("orientation");
60+
const barSortBy = viewContext.useSettingsToViewInterfaceValue("barSortBy");
5461

5562
const statusWriter = useViewStatusWriter(viewContext);
5663

@@ -84,7 +91,10 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
8491
plotType !== prevPlotType ||
8592
numBins !== prevNumBins ||
8693
orientation !== prevOrientation ||
87-
wrapperDivSize !== prevSize
94+
wrapperDivSize !== prevSize ||
95+
sharedXAxes !== prevSharedXAxes ||
96+
sharedYAxes !== prevSharedYAxes ||
97+
barSortBy !== prevBarSortBy
8898
) {
8999
setRevNumberX(receiverX.revisionNumber);
90100
setRevNumberY(receiverY.revisionNumber);
@@ -93,6 +103,9 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
93103
setPrevNumBins(numBins);
94104
setPrevOrientation(orientation);
95105
setPrevSize(wrapperDivSize);
106+
setPrevSharedXAxes(sharedXAxes);
107+
setPrevSharedYAxes(sharedYAxes);
108+
setPrevBarSortBy(barSortBy);
96109

97110
startTransition(function makeContent() {
98111
if (!receiverX.channel) {
@@ -167,8 +180,8 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
167180
numCols,
168181
width: wrapperDivSize.width,
169182
height: wrapperDivSize.height,
170-
sharedXAxes: false,
171-
sharedYAxes: false,
183+
sharedXAxes: sharedXAxes,
184+
sharedYAxes: sharedYAxes,
172185
verticalSpacing: 100 / (wrapperDivSize.height - 50),
173186
horizontalSpacing: 0.2 / numCols,
174187

@@ -255,9 +268,12 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
255268
const data = receiverX.channel.contents[cellIndex];
256269
const keyData = data.dataArray.map((el: any) => el.key);
257270
const valueData = data.dataArray.map((el: any) => el.value);
258-
259271
const dataTitle = makeTitleFromChannelContent(data);
260272
const kindOfKeyTitle = `${receiverX.channel.kindOfKey}`;
273+
const hoverText = data.dataArray.map(
274+
(el) =>
275+
`${kindOfKeyTitle}: <b>${el.key}</b><br>${dataTitle}: <b>${formatNumber(Number(el.value))}</b><extra></extra>`,
276+
);
261277

262278
const trace: Partial<PlotData> = {
263279
x: orientation === "h" ? valueData : keyData,
@@ -269,18 +285,31 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
269285
showlegend: false,
270286
type: "bar",
271287
orientation,
288+
hovertemplate: hoverText,
289+
hoverlabel: {
290+
bgcolor: "white",
291+
font: { size: 12, color: "black" },
292+
},
272293
};
273294

274-
const xAxisTitle = orientation === "h" ? dataTitle : kindOfKeyTitle;
275-
const yAxisTitle = orientation === "h" ? kindOfKeyTitle : dataTitle;
295+
const xAxisTitle = orientation === "h" ? dataTitle : `${kindOfKeyTitle} (hover to see values)`;
296+
const yAxisTitle = orientation === "h" ? `${kindOfKeyTitle} (hover to see values)` : dataTitle;
276297

277298
figure.addTrace(trace, rowIndex + 1, colIndex + 1);
299+
const xBinsInDescendingOrder = orientation === "v" && barSortBy === BarSortBy.Value;
300+
const yBinsInDescendingOrder = orientation === "h" && barSortBy === BarSortBy.Value;
278301
const patch: Partial<Layout> = {
279302
[`xaxis${cellIndex + 1}`]: {
280303
title: { text: xAxisTitle },
304+
type: xBinsInDescendingOrder ? "category" : "linear",
305+
categoryorder: xBinsInDescendingOrder ? "total descending" : "trace",
306+
showticklabels: xBinsInDescendingOrder ? false : true,
281307
},
282308
[`yaxis${cellIndex + 1}`]: {
283309
title: { text: yAxisTitle },
310+
type: yBinsInDescendingOrder ? "category" : "linear",
311+
categoryorder: yBinsInDescendingOrder ? "total descending" : "trace",
312+
showticklabels: yBinsInDescendingOrder ? false : true,
284313
},
285314
};
286315
figure.updateLayout(patch);
@@ -306,8 +335,8 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
306335
numCols: receiverY.channel.contents.length,
307336
width: wrapperDivSize.width,
308337
height: wrapperDivSize.height,
309-
sharedXAxes: true,
310-
sharedYAxes: true,
338+
sharedXAxes: sharedXAxes,
339+
sharedYAxes: sharedYAxes,
311340
verticalSpacing: 20 / (wrapperDivSize.height - 80),
312341
horizontalSpacing: 20 / (wrapperDivSize.width - 80),
313342
margin: {
@@ -336,12 +365,11 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
336365

337366
let cellIndex = 0;
338367

339-
receiverX.channel.contents.forEach((contentX, rowIndex, rowArr) => {
368+
receiverX.channel.contents.forEach((contentX, rowIndex) => {
340369
if (!receiverY.channel) {
341370
return;
342371
}
343372

344-
const numRows = rowArr.length;
345373
receiverY.channel.contents.forEach((contentY, colIndex) => {
346374
cellIndex++;
347375

@@ -405,7 +433,7 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
405433
: undefined,
406434
},
407435
showlegend: false,
408-
type: "scattergl",
436+
type: "scatter",
409437
hovertemplate: realizations.map((real) =>
410438
dataColor
411439
? makeHoverTextWithColor(contentX, contentY, dataColor, real)
@@ -415,22 +443,21 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps<Interfa
415443

416444
figure.addTrace(trace, rowIndex + 1, colIndex + 1);
417445

418-
if (rowIndex === numRows - 1) {
419-
const patch: Partial<Layout> = {
420-
[`xaxis${cellIndex}`]: {
421-
title: {
422-
text: makeTitleFromChannelContent(contentX),
423-
font,
424-
},
446+
const patch: Partial<Layout> = {
447+
[`xaxis${cellIndex}`]: {
448+
title: {
449+
text: makeTitleFromChannelContent(contentY),
450+
font,
425451
},
426-
};
427-
figure.updateLayout(patch);
428-
}
452+
},
453+
};
454+
figure.updateLayout(patch);
455+
429456
if (colIndex === 0) {
430457
const patch: Partial<Layout> = {
431458
[`yaxis${cellIndex}`]: {
432459
title: {
433-
text: makeTitleFromChannelContent(contentY),
460+
text: makeTitleFromChannelContent(contentX),
434461
font,
435462
},
436463
},

0 commit comments

Comments
 (0)