@ocelescope/core 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,14 +27,6 @@ for a complete setup.
27
27
  ```tsx
28
28
  // pages/_app.tsx
29
29
 
30
- // 1. Third-party global styles core relies on. Import these FIRST, in this
31
- // order (@mantine/core must come before its extensions).
32
- import "@mantine/core/styles.css";
33
- import "@mantine/dropzone/styles.css";
34
- import "@mantine/notifications/styles.css";
35
- import "mantine-datatable/styles.css";
36
-
37
- // 2. core's own (scoped) styles — must come AFTER the above so its overrides win.
38
30
  import "@ocelescope/core/styles.css";
39
31
 
40
32
  import { OcelescopeApp } from "@ocelescope/core";
@@ -43,12 +35,13 @@ import config from "../ocelescope.config";
43
35
  export default OcelescopeApp(config);
44
36
  ```
45
37
 
46
- > **Styles.** `@ocelescope/core` ships only its own component styles in
47
- > `@ocelescope/core/styles.css`; it does **not** bundle its dependencies'
48
- > global CSS. Your app must import the Mantine / mantine-datatable stylesheets
49
- > above itself (and add those packages to its `dependencies`), in order, before
50
- > core's stylesheet. Each Ocelescope module documents any additional
51
- > stylesheets it needs.
38
+ > **Styles.** `@ocelescope/core/styles.css` is the only stylesheet an app needs.
39
+ > It inlines the third-party global CSS core and the Ocelescope modules rely on
40
+ > (`@mantine/core` and its `dates` / `charts` / `dropzone` / `notifications`
41
+ > extensions, `mantine-datatable`, `@xyflow/react`, `@r4pm/components`) ahead of
42
+ > core's own component styles, in cascade order — so do not import those
43
+ > stylesheets again. A module that ships its own stylesheet documents it; import
44
+ > those after core's.
52
45
 
53
46
  ```tsx
54
47
  // pages/_document.tsx
@@ -105,6 +98,60 @@ export default defineModule({
105
98
  });
106
99
  ```
107
100
 
101
+ ### Charts
102
+
103
+ `OcelChart` runs DuckDB SQL against the selected OCEL and draws the result. The
104
+ query is usually the whole configuration: the first non-numeric column goes
105
+ across, the numeric ones go up.
106
+
107
+ ```tsx
108
+ import { OcelChart } from "@ocelescope/core";
109
+
110
+ export const EventsByActivity = () => (
111
+ <OcelChart
112
+ title="Events per activity"
113
+ colorScope="activity"
114
+ sql={`SELECT "ocel:activity" AS activity, count(*) AS events
115
+ FROM events GROUP BY 1 ORDER BY 2 DESC`}
116
+ />
117
+ );
118
+ ```
119
+
120
+ Bar, line, area, scatter and pie; `series` unfolds a column into one series per
121
+ value, `stacked` and `horizontal` change the shape, `types` offers the reader a
122
+ switch between chart types, and `x`/`y` override the columns when the defaults
123
+ guess wrong. Only a single `SELECT` runs, over the OCEL's stored tables
124
+ (`events`, `objects`, `e2o`, `o2o`, `object_changes`); bind values through
125
+ `parameters` rather than building SQL by hand. `SqlChart` is the same chart for
126
+ a result you already have.
127
+
128
+ Line charts can share a scale or overlay an independent scale per line. The
129
+ second form is useful for measures with different units or very different
130
+ ranges:
131
+
132
+ ```tsx
133
+ <OcelChart
134
+ type="line"
135
+ x="time"
136
+ y="value"
137
+ series="measure"
138
+ yAxes="independent"
139
+ sql={measurements}
140
+ />
141
+ ```
142
+
143
+ Charts are r4pm viewers. Colours come from the ambient `ViewerConfig` when a
144
+ `colorScope` is given, so a category keeps the colour the graph viewers give it;
145
+ clicks report a `ViewerTarget` to `onSelect`; and a chart inside a
146
+ `<ViewerExportFrame>` exports with everything else in the frame:
147
+
148
+ ```tsx
149
+ <ViewerExportFrame filename="activities">
150
+ <OcelChart title="Events per activity" sql={events} />
151
+ <OcelChart title="Objects per type" sql={objects} series="type" stacked />
152
+ </ViewerExportFrame>
153
+ ```
154
+
108
155
  ## About
109
156
 
110
157
  Part of [Ocelescope](https://github.com/promi4s/ocelescope), a framework for
@@ -0,0 +1,18 @@
1
+ import { t as useOcelId } from "./useOcelId-BSU0-Srk.js";
2
+ import { NamePicker } from "./NamePicker-Be-qmr1x.js";
3
+ import { useEventCounts } from "@ocelescope/api-base";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/pickers/ActivityPicker.tsx
6
+ /** The log's activities, by how often they occur. */
7
+ const ActivityPicker = ({ ocelId, ocelVersion = "filtered", ...picker }) => {
8
+ const id = useOcelId(ocelId);
9
+ const { data, isPending } = useEventCounts(id, { ocel_version: ocelVersion }, { query: { enabled: id != null } });
10
+ return /* @__PURE__ */ jsx(NamePicker, {
11
+ items: data ?? {},
12
+ scope: "activity",
13
+ loading: isPending,
14
+ ...picker
15
+ });
16
+ };
17
+ //#endregion
18
+ export { ActivityPicker };
@@ -0,0 +1,36 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { i as measureOf, n as axis, t as Plot } from "./Plot-CxHHyXot.js";
3
+ import { n as traceCount, t as cartesian } from "./cartesian-5TPNWjl_.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/charts/AreaChart.tsx
6
+ var AreaChart_exports = /* @__PURE__ */ __exportAll({ AreaChart: () => AreaChart });
7
+ /** A line with the ground beneath it filled in, for totals over time. */
8
+ const AreaChart = ({ xRange, ...props }) => {
9
+ const measure = measureOf(props.y);
10
+ return /* @__PURE__ */ jsx(Plot, {
11
+ traces: cartesian(props, {
12
+ type: "scatter",
13
+ mode: "lines",
14
+ fill: "tozeroy",
15
+ line: {
16
+ width: 2,
17
+ shape: "spline",
18
+ smoothing: .35
19
+ },
20
+ opacity: .72,
21
+ connectgaps: false,
22
+ hovertemplate: "<b>%{fullData.name}</b><br>%{x}<br>%{y}<extra></extra>"
23
+ }, false, false),
24
+ legend: traceCount(props) > 1,
25
+ onSelect: props.onSelect,
26
+ layout: {
27
+ hovermode: "x unified",
28
+ hoverdistance: 40,
29
+ spikedistance: -1,
30
+ xaxis: axis(props.x, xRange),
31
+ yaxis: axis(measure)
32
+ }
33
+ });
34
+ };
35
+ //#endregion
36
+ export { AreaChart_exports as n, AreaChart as t };
@@ -0,0 +1,69 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { i as measureOf, n as axis, t as Plot } from "./Plot-CxHHyXot.js";
3
+ import { n as traceCount, t as cartesian } from "./cartesian-5TPNWjl_.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/charts/BarChart.tsx
6
+ var BarChart_exports = /* @__PURE__ */ __exportAll({ BarChart: () => BarChart });
7
+ /** Counts as bars, one per category. */
8
+ const BarChart = ({ stacked = false, horizontal = false, xRange, ...props }) => {
9
+ const measure = measureOf(props.y);
10
+ const count = props.rows.length;
11
+ const traces = cartesian(props, {
12
+ type: "bar",
13
+ marker: {
14
+ line: { width: 0 },
15
+ cornerradius: 4
16
+ },
17
+ ...count <= 24 && {
18
+ texttemplate: horizontal ? "%{x:~s}" : "%{y:~s}",
19
+ textposition: "auto"
20
+ },
21
+ hovertemplate: horizontal ? `<b>%{y}</b><br>${measure}: %{x}<extra>%{fullData.name}</extra>` : `<b>%{x}</b><br>${measure}: %{y}<extra>%{fullData.name}</extra>`,
22
+ cliponaxis: false
23
+ }, horizontal);
24
+ return /* @__PURE__ */ jsx(Plot, {
25
+ traces: stacked ? withStackShare(traces, horizontal, measure) : traces,
26
+ legend: traceCount(props) > 1,
27
+ readName: (point) => String((horizontal ? point.y : point.x) ?? point.label ?? ""),
28
+ onSelect: props.onSelect,
29
+ layout: {
30
+ barmode: stacked ? "stack" : "group",
31
+ bargap: .22,
32
+ bargroupgap: .08,
33
+ hovermode: "closest",
34
+ uniformtext: {
35
+ mode: "hide",
36
+ minsize: 10
37
+ },
38
+ xaxis: axis(horizontal ? measure : props.x, xRange),
39
+ yaxis: axis(horizontal ? props.x : measure)
40
+ }
41
+ });
42
+ };
43
+ /** Add each segment's share of its complete bar to stacked-bar hover. */
44
+ const withStackShare = (traces, horizontal, measure) => {
45
+ const totals = /* @__PURE__ */ new Map();
46
+ for (const trace of traces) {
47
+ const labels = horizontal ? trace.y : trace.x;
48
+ const values = horizontal ? trace.x : trace.y;
49
+ labels.forEach((label, index) => {
50
+ const key = String(label);
51
+ totals.set(key, (totals.get(key) ?? 0) + Number(values[index] ?? 0));
52
+ });
53
+ }
54
+ return traces.map((trace) => {
55
+ const labels = horizontal ? trace.y : trace.x;
56
+ const values = horizontal ? trace.x : trace.y;
57
+ const shares = labels.map((label, index) => {
58
+ const total = totals.get(String(label)) ?? 0;
59
+ return total > 0 ? Number(values[index] ?? 0) / total : 0;
60
+ });
61
+ return {
62
+ ...trace,
63
+ customdata: shares,
64
+ hovertemplate: horizontal ? `<b>%{y}</b><br>${measure}: %{x}<br>Share: %{customdata:.1%}<extra>%{fullData.name}</extra>` : `<b>%{x}</b><br>${measure}: %{y}<br>Share: %{customdata:.1%}<extra>%{fullData.name}</extra>`
65
+ };
66
+ });
67
+ };
68
+ //#endregion
69
+ export { BarChart_exports as n, BarChart as t };
@@ -0,0 +1,27 @@
1
+ import { t as useOcelId } from "./useOcelId-BSU0-Srk.js";
2
+ import { NamePicker } from "./NamePicker-Be-qmr1x.js";
3
+ import { useEventAttributes } from "@ocelescope/api-base";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/pickers/EventAttributePicker.tsx
6
+ /** The attributes events carry, counted by how many values each takes. */
7
+ const EventAttributePicker = ({ activity, ocelId, ocelVersion = "filtered", ...picker }) => {
8
+ const id = useOcelId(ocelId);
9
+ const { data, isPending } = useEventAttributes(id, {
10
+ ocel_version: ocelVersion,
11
+ ...activity ? { names: [activity] } : {}
12
+ }, { query: { enabled: id != null } });
13
+ return /* @__PURE__ */ jsx(NamePicker, {
14
+ items: (data ?? []).map((attribute) => ({
15
+ key: attribute.name,
16
+ count: attribute.distinct_values
17
+ })),
18
+ scope: "attribute",
19
+ loading: isPending,
20
+ bars: false,
21
+ cutoff: false,
22
+ sort: "name",
23
+ ...picker
24
+ });
25
+ };
26
+ //#endregion
27
+ export { EventAttributePicker };
@@ -0,0 +1,64 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { i as measureOf, n as axis, t as Plot } from "./Plot-CxHHyXot.js";
3
+ import { n as toSeries, t as paint } from "./series-BBXVjHHY.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/charts/LineChart.tsx
6
+ var LineChart_exports = /* @__PURE__ */ __exportAll({ LineChart: () => LineChart });
7
+ /** Lines through ordered values, optionally with one scale per line. */
8
+ const LineChart = ({ step = false, yAxes = "shared", xRange, ...props }) => {
9
+ const groups = toSeries(props);
10
+ const independent = yAxes === "independent" && groups.length > 1;
11
+ const colors = groups.map((group) => {
12
+ const color = paint(group, props.colorOf, false);
13
+ return typeof color === "string" ? color : void 0;
14
+ });
15
+ const traces = groups.map((group, index) => ({
16
+ type: "scatter",
17
+ mode: step || group.values.length <= 40 ? "lines+markers" : "lines",
18
+ name: group.name,
19
+ x: group.labels,
20
+ y: group.values,
21
+ ...independent && { yaxis: index === 0 ? "y" : `y${index + 1}` },
22
+ line: {
23
+ width: 2,
24
+ ...step && { shape: "hv" },
25
+ ...colors[index] && { color: colors[index] }
26
+ },
27
+ marker: {
28
+ size: 5,
29
+ ...colors[index] && { color: colors[index] }
30
+ },
31
+ connectgaps: false,
32
+ hovertemplate: "<b>%{fullData.name}</b><br>%{x}<br>%{y}<extra></extra>"
33
+ }));
34
+ const yLayout = independent ? Object.fromEntries(groups.map((group, index) => [index === 0 ? "yaxis" : `yaxis${index + 1}`, {
35
+ ...axis(group.name),
36
+ ...index > 0 && {
37
+ overlaying: "y",
38
+ side: index % 2 === 0 ? "left" : "right",
39
+ anchor: "free",
40
+ autoshift: true,
41
+ showgrid: false,
42
+ zeroline: false
43
+ },
44
+ ...colors[index] && {
45
+ tickfont: { color: colors[index] },
46
+ title: {
47
+ text: group.name,
48
+ font: { color: colors[index] }
49
+ }
50
+ }
51
+ }])) : { yaxis: axis(measureOf(props.y)) };
52
+ return /* @__PURE__ */ jsx(Plot, {
53
+ traces,
54
+ legend: groups.length > 1,
55
+ onSelect: props.onSelect,
56
+ layout: {
57
+ hovermode: "x unified",
58
+ xaxis: axis(props.x, xRange),
59
+ ...yLayout
60
+ }
61
+ });
62
+ };
63
+ //#endregion
64
+ export { LineChart_exports as n, LineChart as t };
@@ -0,0 +1,43 @@
1
+ import { Stack, Text } from "@mantine/core";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { FrequencyPicker } from "@r4pm/components";
4
+ //#region src/components/pickers/types.ts
5
+ /** The names as a set, which is how the pickers themselves hold them. */
6
+ const chosen = (selection) => new Set(selection.multiple ? selection.value : selection.value == null ? [] : [selection.value]);
7
+ /** Report a set back as the one name, or the list, the caller asked for. */
8
+ const report = (selection, names) => {
9
+ const list = [...names];
10
+ if (selection.multiple) selection.onChange(list);
11
+ else selection.onChange(list[0]);
12
+ };
13
+ //#endregion
14
+ //#region src/components/pickers/NamePicker.tsx
15
+ /**
16
+ * Picking names out of a list, with what is known about how often each occurs.
17
+ *
18
+ * Presentational: it is handed the names and reports back what was picked.
19
+ * `ActivityPicker` and the others wrap it around an endpoint.
20
+ */
21
+ const NamePicker = ({ items, label, searchable = true, bars = true, counts = true, cutoff = false, sort = "count", scope = "name", loading = false, emptyText, disabled = false, autoFocus, ...selection }) => /* @__PURE__ */ jsxs(Stack, {
22
+ gap: 4,
23
+ children: [label && /* @__PURE__ */ jsx(Text, {
24
+ size: "xs",
25
+ c: "dimmed",
26
+ children: label
27
+ }), /* @__PURE__ */ jsx(FrequencyPicker, {
28
+ items,
29
+ mode: selection.multiple ? "multi" : "single",
30
+ value: chosen(selection),
31
+ onChange: (next) => !disabled && report(selection, next),
32
+ searchable,
33
+ showBars: bars,
34
+ showCounts: counts,
35
+ showCutoff: cutoff,
36
+ sort,
37
+ scope,
38
+ autoFocus,
39
+ emptyText: emptyText ?? (loading ? "Reading the OCEL…" : "Nothing here")
40
+ })]
41
+ });
42
+ //#endregion
43
+ export { NamePicker };
@@ -0,0 +1,27 @@
1
+ import { t as useOcelId } from "./useOcelId-BSU0-Srk.js";
2
+ import { NamePicker } from "./NamePicker-Be-qmr1x.js";
3
+ import { useObjectAttributes } from "@ocelescope/api-base";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/pickers/ObjectAttributePicker.tsx
6
+ /** The attributes objects carry, counted by how many values each takes. */
7
+ const ObjectAttributePicker = ({ objectType, ocelId, ocelVersion = "filtered", ...picker }) => {
8
+ const id = useOcelId(ocelId);
9
+ const { data, isPending } = useObjectAttributes(id, {
10
+ ocel_version: ocelVersion,
11
+ ...objectType ? { names: [objectType] } : {}
12
+ }, { query: { enabled: id != null } });
13
+ return /* @__PURE__ */ jsx(NamePicker, {
14
+ items: (data ?? []).map((attribute) => ({
15
+ key: attribute.name,
16
+ count: attribute.distinct_values
17
+ })),
18
+ scope: "attribute",
19
+ loading: isPending,
20
+ bars: false,
21
+ cutoff: false,
22
+ sort: "name",
23
+ ...picker
24
+ });
25
+ };
26
+ //#endregion
27
+ export { ObjectAttributePicker };
@@ -0,0 +1,18 @@
1
+ import { t as useOcelId } from "./useOcelId-BSU0-Srk.js";
2
+ import { NamePicker } from "./NamePicker-Be-qmr1x.js";
3
+ import { useObjectCounts } from "@ocelescope/api-base";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/pickers/ObjectTypePicker.tsx
6
+ /** The log's object types, by how often they occur. */
7
+ const ObjectTypePicker = ({ ocelId, ocelVersion = "filtered", ...picker }) => {
8
+ const id = useOcelId(ocelId);
9
+ const { data, isPending } = useObjectCounts(id, { ocel_version: ocelVersion }, { query: { enabled: id != null } });
10
+ return /* @__PURE__ */ jsx(NamePicker, {
11
+ items: data ?? {},
12
+ scope: "objectType",
13
+ loading: isPending,
14
+ ...picker
15
+ });
16
+ };
17
+ //#endregion
18
+ export { ObjectTypePicker };
@@ -0,0 +1,41 @@
1
+ import { t as useOcelQuery } from "./useOcelQuery-D1QM-Zgv.js";
2
+ import { SqlChart } from "./SqlChart-bwDArK2l.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { AsyncBoundary, EmptyState } from "@r4pm/components";
5
+ //#region src/components/charts/OcelChart.tsx
6
+ /**
7
+ * A chart of the current OCEL: give it SQL, get a chart.
8
+ *
9
+ * Loading, failure and emptiness are handled here, so the charts never have to
10
+ * think about them; the failure shows the query's own error text, because that
11
+ * is the part someone writing SQL can act on.
12
+ */
13
+ const OcelChart = ({ sql, parameters, ocelVersion = "filtered", enabled = true, height = 320, ...chart }) => {
14
+ const query = useOcelQuery({
15
+ sql,
16
+ parameters,
17
+ ocelVersion,
18
+ enabled
19
+ });
20
+ return /* @__PURE__ */ jsx("div", {
21
+ style: {
22
+ height,
23
+ width: "100%"
24
+ },
25
+ children: query.ocelId ? /* @__PURE__ */ jsx(AsyncBoundary, {
26
+ status: query,
27
+ loadingLabel: "Querying the OCEL…",
28
+ errorTitle: "The query failed",
29
+ onRetry: () => void query.refetch(),
30
+ children: (data) => /* @__PURE__ */ jsx(SqlChart, {
31
+ ...chart,
32
+ data
33
+ })
34
+ }) : /* @__PURE__ */ jsx(EmptyState, {
35
+ title: "No OCEL selected",
36
+ description: "Import or select an OCEL to run this chart's query."
37
+ })
38
+ });
39
+ };
40
+ //#endregion
41
+ export { OcelChart };
@@ -0,0 +1,62 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { t as Plot } from "./Plot-CxHHyXot.js";
3
+ import { n as toSeries, t as paint } from "./series-BBXVjHHY.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/charts/PieChart.tsx
6
+ var PieChart_exports = /* @__PURE__ */ __exportAll({ PieChart: () => PieChart });
7
+ /** Shares of a whole, one slice per category. */
8
+ const PieChart = ({ hole = .5, ...props }) => {
9
+ const [slices] = toSeries(props);
10
+ const colors = slices && paint(slices, props.colorOf, true);
11
+ const total = slices?.values.reduce((sum, value) => sum + (typeof value === "number" ? value : 0), 0);
12
+ const compact = new Intl.NumberFormat(void 0, {
13
+ notation: "compact",
14
+ maximumFractionDigits: 1
15
+ });
16
+ return /* @__PURE__ */ jsx(Plot, {
17
+ traces: slices ? [{
18
+ type: "pie",
19
+ labels: slices.labels,
20
+ values: slices.values,
21
+ hole,
22
+ sort: false,
23
+ direction: "clockwise",
24
+ rotation: -90,
25
+ textinfo: slices.labels.length <= 8 ? "label+percent" : "percent",
26
+ textposition: slices.labels.length <= 5 ? "inside" : "auto",
27
+ insidetextorientation: "horizontal",
28
+ hovertemplate: "<b>%{label}</b><br>%{value} · %{percent}<extra></extra>",
29
+ marker: {
30
+ ...Array.isArray(colors) && { colors },
31
+ line: {
32
+ color: "rgba(128,128,128,0.28)",
33
+ width: 1.5
34
+ }
35
+ }
36
+ }] : [],
37
+ legend: true,
38
+ onSelect: props.onSelect,
39
+ layout: {
40
+ margin: {
41
+ t: 44,
42
+ r: 12,
43
+ b: 20,
44
+ l: 12
45
+ },
46
+ uniformtext: {
47
+ mode: "hide",
48
+ minsize: 11
49
+ },
50
+ ...hole > 0 && total != null && { annotations: [{
51
+ text: `<b>${compact.format(total)}</b><br><span style="font-size:11px">total</span>`,
52
+ showarrow: false,
53
+ x: .5,
54
+ y: .5,
55
+ xanchor: "center",
56
+ yanchor: "middle"
57
+ }] }
58
+ }
59
+ });
60
+ };
61
+ //#endregion
62
+ export { PieChart_exports as n, PieChart as t };
@@ -0,0 +1,75 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { ThemedPlot } from "@r4pm/components/charts";
3
+ //#region src/components/charts/types.ts
4
+ /** How a cell reads once it is a label. */
5
+ const label = (value) => value == null ? "∅" : String(value);
6
+ /** A cell as the chart plots it: a number where it is one, else its text. */
7
+ const plotted = (value) => {
8
+ if (typeof value === "number") return value;
9
+ if (value == null || value === "") return 0;
10
+ const number = Number(value);
11
+ return Number.isFinite(number) ? number : String(value);
12
+ };
13
+ /** The first, or only, column holding numbers. */
14
+ const measureOf = (y) => (Array.isArray(y) ? y[0] : y) ?? "";
15
+ //#endregion
16
+ //#region src/components/charts/internal/Plot.tsx
17
+ /**
18
+ * The plot every chart draws on: r4pm's themed Plotly, with the parts that
19
+ * should look the same everywhere - margins, legend, no mode bar - already set.
20
+ */
21
+ const Plot = ({ traces, layout, legend = false, readName = (point) => String(point?.label ?? point?.x ?? ""), onSelect }) => /* @__PURE__ */ jsx(ThemedPlot, {
22
+ data: traces,
23
+ layout: {
24
+ autosize: true,
25
+ margin: {
26
+ t: legend ? 44 : 12,
27
+ r: 16,
28
+ b: 48,
29
+ l: 56,
30
+ pad: 2
31
+ },
32
+ showlegend: legend,
33
+ clickmode: onSelect ? "event+select" : "event",
34
+ legend: {
35
+ orientation: "h",
36
+ y: 1.02,
37
+ yref: "paper",
38
+ yanchor: "bottom",
39
+ x: 0,
40
+ xanchor: "left",
41
+ bgcolor: "rgba(0,0,0,0)"
42
+ },
43
+ hoverlabel: { namelength: -1 },
44
+ ...layout
45
+ },
46
+ config: {
47
+ displaylogo: false,
48
+ displayModeBar: false,
49
+ responsive: true,
50
+ scrollZoom: false,
51
+ doubleClick: "reset"
52
+ },
53
+ style: {
54
+ width: "100%",
55
+ height: "100%"
56
+ },
57
+ useResizeHandler: true,
58
+ onClick: ({ points }) => points[0] && onSelect?.(readName(points[0]), points[0])
59
+ });
60
+ /** An axis that keeps its labels on screen, named after its column. */
61
+ const axis = (title, range) => ({
62
+ automargin: true,
63
+ ticks: "outside",
64
+ ticklen: 4,
65
+ tickfont: { size: 11 },
66
+ zeroline: false,
67
+ ...title && { title: {
68
+ text: title,
69
+ font: { size: 12 },
70
+ standoff: 10
71
+ } },
72
+ ...range && { range: [...range] }
73
+ });
74
+ //#endregion
75
+ export { plotted as a, measureOf as i, axis as n, label as r, Plot as t };
@@ -0,0 +1,36 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { i as measureOf, n as axis, t as Plot } from "./Plot-CxHHyXot.js";
3
+ import { n as traceCount, t as cartesian } from "./cartesian-5TPNWjl_.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/charts/ScatterChart.tsx
6
+ var ScatterChart_exports = /* @__PURE__ */ __exportAll({ ScatterChart: () => ScatterChart });
7
+ /** A point per row, for values that have no order to follow. */
8
+ const ScatterChart = ({ xRange, ...props }) => {
9
+ const measure = measureOf(props.y);
10
+ return /* @__PURE__ */ jsx(Plot, {
11
+ traces: cartesian(props, {
12
+ type: "scatter",
13
+ mode: "markers",
14
+ marker: {
15
+ size: props.rows.length > 500 ? 6 : 9,
16
+ opacity: props.rows.length > 500 ? .55 : .78,
17
+ line: {
18
+ color: "rgba(128,128,128,0.35)",
19
+ width: .75
20
+ }
21
+ },
22
+ hovertemplate: "<b>%{fullData.name}</b><br>%{x}<br>%{y}<extra></extra>"
23
+ }),
24
+ legend: traceCount(props) > 1,
25
+ onSelect: props.onSelect,
26
+ layout: {
27
+ hovermode: "closest",
28
+ hoverdistance: 24,
29
+ dragmode: "zoom",
30
+ xaxis: axis(props.x, xRange),
31
+ yaxis: axis(measure)
32
+ }
33
+ });
34
+ };
35
+ //#endregion
36
+ export { ScatterChart_exports as n, ScatterChart as t };