diff --git a/flatfront-astro/src/lib/components/PlotSection.tsx b/flatfront-astro/src/lib/components/PlotSection.tsx
index fa87352..16a919f 100644
--- a/flatfront-astro/src/lib/components/PlotSection.tsx
+++ b/flatfront-astro/src/lib/components/PlotSection.tsx
@@ -1,4 +1,5 @@
import type { PlotID, PlotType } from "../types";
+import * as d3 from "d3";
import { useMergeState } from "../contexts/AppStateContext";
import {
PlotIDProvider,
@@ -36,17 +37,13 @@ export default function PlotSection({ id }: { id: PlotID }) {
);
}
+const plot_wrappers = d3.sort(Object.values(Plots), (d) => d.order);
+
function PlotTypeSelect() {
const plot_id = usePlotID();
const plot_type = usePlotType();
- const plot_type_options: { key: PlotType; label: string }[] = [
- { key: Plots.Histogram.key, label: Plots.Histogram.label },
- { key: Plots.Heatmap.key, label: Plots.Heatmap.label },
- { key: Plots.HeatmapObservable.key, label: Plots.HeatmapObservable.label },
- { key: Plots.BoxPlot.key, label: Plots.BoxPlot.label },
- { key: Plots.Scatterplot.key, label: Plots.Scatterplot.label },
- { key: Plots.Scatterplot3D.key, label: Plots.Scatterplot3D.label }
- ];
+ const plot_type_options: { key: PlotType; label: string }[] =
+ plot_wrappers.map(({ key, label }) => ({ key, label }));
const value = plot_type_options.find((d) => d.key === plot_type);
const merge_state = useMergeState();
return (
@@ -71,40 +68,16 @@ function PlotTypeSelect() {
function PlotComponent() {
const plot_type = usePlotType();
- switch (plot_type) {
- case Plots.Histogram.key:
- return ;
- case Plots.Heatmap.key:
- return ;
- case Plots.HeatmapObservable.key:
- return ;
- case Plots.BoxPlot.key:
- return ;
- case Plots.Scatterplot.key:
- return ;
- case Plots.Scatterplot3D.key:
- return ;
- default:
- return Choose a plot type;
+ for (const { key, Plot } of plot_wrappers) {
+ if (plot_type === key) return ;
}
+ return Choose a plot type;
}
function PlotControls() {
const plot_type = usePlotType();
- switch (plot_type) {
- case Plots.Histogram.key:
- return ;
- case Plots.Heatmap.key:
- return ;
- case Plots.HeatmapObservable.key:
- return ;
- case Plots.BoxPlot.key:
- return ;
- case Plots.Scatterplot.key:
- return ;
- case Plots.Scatterplot3D.key:
- return ;
- default:
- return null;
+ for (const { key, Controls } of plot_wrappers) {
+ if (plot_type === key) return ;
}
+ return null;
}
diff --git a/flatfront-astro/src/lib/components/Plots.tsx b/flatfront-astro/src/lib/components/Plots.tsx
index 2eb45c8..fe2b4e4 100644
--- a/flatfront-astro/src/lib/components/Plots.tsx
+++ b/flatfront-astro/src/lib/components/Plots.tsx
@@ -33,6 +33,7 @@ import ObservablePlot from "./ObservablePlot";
export const Histogram: PlotWrapper = {
key: `histogram`,
label: `Histogram`,
+ order: 0,
Plot() {
const catalog_id = useCatalogID();
const filters = useFilterValues();
@@ -61,10 +62,14 @@ export const Histogram: PlotWrapper = {
enabled: enable_request
});
+ const sizes = query.data?.sizes ?? [0, 0];
+
const data_munged = (() => {
if (!query.data) return [];
- return query.data.buckets.map(({ key: [value], count }) => {
- return [value, count];
+ return query.data.buckets.map(({ key: [x], count }) => {
+ const x1 = +x;
+ const x2 = field_config.log_mode ? x1 * sizes[0] : x1 + sizes[0];
+ return { x1, x2, count };
});
})();
@@ -80,33 +85,46 @@ export const Histogram: PlotWrapper = {
}
})();
+ const aspect = 640 / 400;
+
+ const width = 900;
+
+ const plot_options: Plot.PlotOptions = {
+ width,
+ height: width / aspect,
+ style: {
+ background: `transparent`,
+ width: `100%`
+ },
+ x: {
+ label: field_config.field_id,
+ type: field_config.log_mode ? `log` : `linear`,
+ tickFormat: field_config.log_mode ? `.3~f` : undefined,
+ grid: true
+ },
+ y: {
+ label: `Count`,
+ type: count_config.log_mode ? `log` : `linear`,
+ tickFormat: count_config.log_mode ? `.3~f` : undefined,
+ grid: true
+ },
+ marks: [
+ Plot.rectY(data_munged, {
+ x1: `x1`,
+ x2: `x2`,
+ y: `count`,
+ insetRight: 1,
+ insetLeft: 1,
+ tip: true
+ })
+ ]
+ };
+
+ const plot = Plot.plot(plot_options);
+
return (
-
+
);
},
@@ -128,104 +146,7 @@ export const Histogram: PlotWrapper = {
export const Heatmap: PlotWrapper = {
key: `heatmap`,
label: `Heatmap`,
- Plot() {
- const catalog_id = useCatalogID();
- const filters = useFilterValues();
- const random_config = useRandomConfig();
-
- const x_axis = useAxisConfig(`x_axis`);
- const y_axis = useAxisConfig(`y_axis`);
-
- const enable_request =
- Boolean(catalog_id) &&
- x_axis.ready_for_request &&
- y_axis.ready_for_request;
-
- const query = usePlotQuery({
- path: `/${catalog_id}/histogram`,
- body: {
- fields: [
- { field: x_axis.field_id, size: 20, log: x_axis.log_mode },
- { field: y_axis.field_id, size: 20, log: y_axis.log_mode }
- ] as any,
- ...filters,
- ...random_config
- },
- label: `Heatmap`,
- enabled: enable_request
- });
-
- const data_munged = (() => {
- if (!query.data) return [];
- return query.data.buckets.map(({ key: [x, y], count }) => {
- return [x, y, count];
- });
- })();
-
- const status = (() => {
- if (x_axis.log_mode_error_message) {
- return x_axis.log_mode_error_message;
- } else if (y_axis.log_mode_error_message) {
- return y_axis.log_mode_error_message;
- } else if (query.isFetching) {
- return ;
- } else if (!(data_munged.length > 0)) {
- return `No data.`;
- } else {
- return null;
- }
- })();
-
- const is_dark_mode = useIsDarkMode();
-
- return (
-
-
-
- );
- },
- Controls() {
- return (
- <>
-
-
- >
- );
- }
-};
-
-export const HeatmapObservable: PlotWrapper = {
- key: `heatmap_observable`,
- label: `Heatmap v2`,
+ order: 2,
Plot() {
const catalog_id = useCatalogID();
const filters = useFilterValues();
@@ -304,13 +225,13 @@ export const HeatmapObservable: PlotWrapper = {
label: x_axis.field_id,
type: x_axis.log_mode ? `log` : `linear`,
tickFormat: x_axis.log_mode ? `.3~f` : undefined,
- grid: true,
+ grid: true
},
y: {
label: y_axis.field_id,
type: y_axis.log_mode ? `log` : `linear`,
tickFormat: y_axis.log_mode ? `.3~f` : undefined,
- grid: true,
+ grid: true
},
marks: [
Plot.rect(data_munged, {
@@ -352,6 +273,7 @@ export const HeatmapObservable: PlotWrapper = {
export const BoxPlot: PlotWrapper = {
key: `boxplot`,
label: `Box Plot`,
+ order: 3.1,
Plot() {
const catalog_id = useCatalogID();
const filters = useFilterValues();
@@ -382,11 +304,28 @@ export const BoxPlot: PlotWrapper = {
const data_munged = (() => {
if (!query.data) return [];
return query.data.buckets.map(({ key: [x, y], count, quartiles }) => {
- // x, low, q1, median, q3, high
- return [x, ...quartiles];
+ const [low, q1, median, q3, high] = quartiles;
+ return {
+ x,
+ y,
+ count,
+ low,
+ q1,
+ median,
+ q3,
+ high
+ };
});
})();
+ const rect_width = (() => {
+ const [first, second] = data_munged;
+ if (!first) return 0;
+ if (!second) return 0;
+ const distance = Math.abs(+first?.x - +second?.x);
+ return distance * 0.25;
+ })();
+
const status = (() => {
if (x_axis.log_mode_error_message) {
return x_axis.log_mode_error_message;
@@ -401,35 +340,79 @@ export const BoxPlot: PlotWrapper = {
}
})();
+ const aspect = 640 / 400;
+
+ const width = 700;
+
+ const is_dark_mode = useIsDarkMode();
+
+ const plot_options: Plot.PlotOptions = {
+ width,
+ height: width / aspect,
+ insetLeft: 10,
+ insetRight: 10,
+ insetBottom: 20,
+ style: {
+ background: `transparent`,
+ width: `100%`
+ },
+ x: {
+ label: x_axis.field_id,
+ type: x_axis.log_mode ? `log` : `linear`,
+ tickFormat: x_axis.log_mode ? `.3~f` : undefined,
+ grid: true
+ },
+ y: {
+ label: y_axis.field_id,
+ type: y_axis.log_mode ? `log` : `linear`,
+ tickFormat: y_axis.log_mode ? `.3~f` : undefined,
+ grid: true,
+ ticks: 5
+ },
+ marks: [
+ Plot.ruleX(data_munged, {
+ x: `x`,
+ y1: `low`,
+ y2: `high`,
+ marker: `dot`,
+ opacity: 0.5
+ }),
+ Plot.rect(data_munged, {
+ x1: (d) => d.x - rect_width,
+ x2: (d) => d.x + rect_width,
+ y1: `q1`,
+ y2: `q3`,
+ fill: is_dark_mode ? `black` : `white`,
+ stroke: `currentColor`,
+ strokeOpacity: 0.5
+ }),
+ Plot.dot(data_munged, {
+ x: `x`,
+ y: `median`,
+ tip: true
+ }),
+ Plot.line(data_munged, {
+ x: `x`,
+ y: `median`
+ })
+ // Plot.rect(data_munged, {
+ // x1: `x`,
+ // x2: (d) => d.x + 1,
+ // y1: `low`,
+ // y2: `high`,
+ // fill: `currentColor`,
+ // stroke: `red`,
+ // strokeOpacity: 0.2,
+ // tip: true
+ // })
+ ]
+ };
+
+ const plot = Plot.plot(plot_options);
+
return (
- [d[0], d[3]])
- }
- ]
- })}
- />
+
);
},
@@ -446,6 +429,7 @@ export const BoxPlot: PlotWrapper = {
export const Scatterplot: PlotWrapper = {
key: `scatterplot`,
label: `Scatterplot`,
+ order: 5,
Plot() {
const catalog_id = useCatalogID();
const filters = useFilterValues();
@@ -484,7 +468,7 @@ export const Scatterplot: PlotWrapper = {
if (!y_axis.field_id) return [];
if (!query.data) return [];
return query.data.map((datum) => {
- return [+datum[x_axis.field_id], +datum[y_axis.field_id]];
+ return { x: +datum[x_axis.field_id], y: +datum[y_axis.field_id] };
});
})();
@@ -502,35 +486,48 @@ export const Scatterplot: PlotWrapper = {
}
})();
+ const aspect = 640 / 400;
+
+ const width = 700;
+
+ const plot_options: Plot.PlotOptions = {
+ width,
+ height: width / aspect,
+ style: {
+ background: `transparent`,
+ width: `100%`
+ },
+ x: {
+ label: x_axis.field_id,
+ type: x_axis.log_mode ? `log` : `linear`,
+ tickFormat: x_axis.log_mode ? `.3~f` : undefined,
+ grid: true
+ },
+ y: {
+ label: y_axis.field_id,
+ type: y_axis.log_mode ? `log` : `linear`,
+ tickFormat: y_axis.log_mode ? `.3~f` : undefined,
+ grid: true,
+ ticks: 5
+ },
+ marks: [
+ Plot.dot(data_munged, {
+ x: `x`,
+ y: `y`,
+ r: 1,
+ fill: `currentColor`,
+ stroke: `currentColor`,
+ strokeOpacity: 0.2,
+ tip: true
+ })
+ ]
+ };
+
+ const plot = Plot.plot(plot_options);
+
return (
-
+
);
},
@@ -547,6 +544,7 @@ export const Scatterplot: PlotWrapper = {
export const Scatterplot3D: PlotWrapper = {
key: `scatterplot_3d`,
label: `3D Scatterplot`,
+ order: 6,
Plot() {
const catalog_id = useCatalogID();
const filters = useFilterValues();
diff --git a/flatfront-astro/src/lib/types.ts b/flatfront-astro/src/lib/types.ts
index fd6cec1..d2a1d7a 100644
--- a/flatfront-astro/src/lib/types.ts
+++ b/flatfront-astro/src/lib/types.ts
@@ -64,17 +64,12 @@ export type CatalogHierarchyNode = d3.HierarchyNode;
export type PlotWrapper = {
key: PlotType;
label: string;
+ order: number;
Plot: React.FC;
Controls: React.FC;
};
-export type PlotType =
- | `histogram`
- | `heatmap`
- | `heatmap_observable`
- | `boxplot`
- | `scatterplot`
- | `scatterplot_3d`;
+export type PlotType = string;
export type PlotID = `plot_${number}`;
export type FieldID = string;
export type CatalogID = string;