@marimo-team/islands 0.22.6-dev4 → 0.22.6-dev6

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/dist/main.js CHANGED
@@ -57414,10 +57414,22 @@ ${c}
57414
57414
  ...e.layout
57415
57415
  };
57416
57416
  }
57417
- function computeLayoutOnFigureChange(e, r) {
57418
- return {
57419
- ...createInitialLayout(e),
57420
- ...Objects.pick(r, PERSISTED_LAYOUT_KEYS)
57417
+ function hasCompatibleTraces(e, r) {
57418
+ return e.data.length === r.data.length ? e.data.every((e2, c) => {
57419
+ var _a3;
57420
+ return (e2.type ?? "scatter") === (((_a3 = r.data[c]) == null ? void 0 : _a3.type) ?? "scatter");
57421
+ }) : false;
57422
+ }
57423
+ function computeLayoutOnFigureChange(e, r, c) {
57424
+ let d = createInitialLayout(e);
57425
+ return hasCompatibleTraces(r, e) ? {
57426
+ ...d,
57427
+ ...Objects.pick(c, PERSISTED_LAYOUT_KEYS)
57428
+ } : {
57429
+ ...d,
57430
+ ..."dragmode" in c ? {
57431
+ dragmode: c.dragmode
57432
+ } : {}
57421
57433
  };
57422
57434
  }
57423
57435
  function computeOmitKeys(e, r) {
@@ -57435,40 +57447,40 @@ ${c}
57435
57447
  function usePlotlyLayout(e) {
57436
57448
  let r = (0, import_compiler_runtime$32.c)(20), { originalFigure: c, initialValue: d, isScriptLoaded: f } = e, h = f === void 0 ? true : f, _;
57437
57449
  r[0] === c ? _ = r[1] : (_ = () => structuredClone(c), r[0] = c, r[1] = _);
57438
- let [v, y] = (0, import_react.useState)(_), S;
57439
- r[2] !== v || r[3] !== d ? (S = () => ({
57450
+ let [v, y] = (0, import_react.useState)(_), S = (0, import_react.useRef)(v), w;
57451
+ r[2] !== v || r[3] !== d ? (w = () => ({
57440
57452
  ...createInitialLayout(v),
57441
57453
  ...d
57442
- }), r[2] = v, r[3] = d, r[4] = S) : S = r[4];
57443
- let [w, E] = (0, import_react.useState)(S), O;
57444
- r[5] === c ? O = r[6] : (O = () => {
57445
- let e2 = structuredClone(c);
57446
- y(e2), E((r2) => computeLayoutOnFigureChange(e2, r2));
57447
- }, r[5] = c, r[6] = O);
57448
- let M;
57449
- r[7] !== h || r[8] !== c ? (M = [
57454
+ }), r[2] = v, r[3] = d, r[4] = w) : w = r[4];
57455
+ let [E, O] = (0, import_react.useState)(w), M;
57456
+ r[5] === c ? M = r[6] : (M = () => {
57457
+ let e2 = structuredClone(c), r2 = S.current;
57458
+ S.current = e2, y(e2), O((c2) => computeLayoutOnFigureChange(e2, r2, c2));
57459
+ }, r[5] = c, r[6] = M);
57460
+ let I;
57461
+ r[7] !== h || r[8] !== c ? (I = [
57450
57462
  c,
57451
57463
  h
57452
- ], r[7] = h, r[8] = c, r[9] = M) : M = r[9], (0, import_react.useEffect)(O, M);
57453
- let I = usePrevious(v) ?? v, z, G;
57454
- r[10] !== v.layout || r[11] !== I.layout ? (z = () => {
57455
- E((e2) => computeLayoutUpdate(v.layout, I.layout, e2));
57456
- }, G = [
57464
+ ], r[7] = h, r[8] = c, r[9] = I) : I = r[9], (0, import_react.useEffect)(M, I);
57465
+ let z = usePrevious(v) ?? v, G, q;
57466
+ r[10] !== v.layout || r[11] !== z.layout ? (G = () => {
57467
+ O((e2) => computeLayoutUpdate(v.layout, z.layout, e2));
57468
+ }, q = [
57457
57469
  v.layout,
57458
- I.layout
57459
- ], r[10] = v.layout, r[11] = I.layout, r[12] = z, r[13] = G) : (z = r[12], G = r[13]), (0, import_react.useEffect)(z, G);
57460
- let q;
57461
- r[14] === c ? q = r[15] : (q = () => {
57470
+ z.layout
57471
+ ], r[10] = v.layout, r[11] = z.layout, r[12] = G, r[13] = q) : (G = r[12], q = r[13]), (0, import_react.useEffect)(G, q);
57472
+ let Z7;
57473
+ r[14] === c ? Z7 = r[15] : (Z7 = () => {
57462
57474
  let e2 = structuredClone(c);
57463
- y(e2), E(createInitialLayout(e2));
57464
- }, r[14] = c, r[15] = q);
57465
- let Z7 = q, Q7;
57466
- return r[16] !== v || r[17] !== Z7 || r[18] !== w ? (Q7 = {
57475
+ y(e2), O(createInitialLayout(e2));
57476
+ }, r[14] = c, r[15] = Z7);
57477
+ let Q7 = Z7, $7;
57478
+ return r[16] !== v || r[17] !== Q7 || r[18] !== E ? ($7 = {
57467
57479
  figure: v,
57468
- layout: w,
57469
- setLayout: E,
57470
- handleReset: Z7
57471
- }, r[16] = v, r[17] = Z7, r[18] = w, r[19] = Q7) : Q7 = r[19], Q7;
57480
+ layout: E,
57481
+ setLayout: O,
57482
+ handleReset: Q7
57483
+ }, r[16] = v, r[17] = Q7, r[18] = E, r[19] = $7) : $7 = r[19], $7;
57472
57484
  }
57473
57485
  var import_compiler_runtime$31 = require_compiler_runtime(), PlotlyPlugin = class {
57474
57486
  constructor() {
@@ -65633,7 +65645,7 @@ ${c}
65633
65645
  return Logger.warn("Failed to get version from mount config"), null;
65634
65646
  }
65635
65647
  }
65636
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.22.6-dev4"), showCodeInRunModeAtom = atom(true);
65648
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.22.6-dev6"), showCodeInRunModeAtom = atom(true);
65637
65649
  atom(null);
65638
65650
  var VIRTUAL_FILE_REGEX = /\/@file\/([^\s"&'/]+)\.([\dA-Za-z]+)/g, VirtualFileTracker = class e {
65639
65651
  constructor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.22.6-dev4",
3
+ "version": "0.22.6-dev6",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -8,11 +8,15 @@ import {
8
8
  computeLayoutUpdate,
9
9
  computeOmitKeys,
10
10
  createInitialLayout,
11
+ hasCompatibleTraces,
11
12
  } from "../usePlotlyLayout";
12
13
 
13
- function createFigure(layoutOverrides: Partial<Plotly.Layout> = {}): Figure {
14
+ function createFigure(
15
+ layoutOverrides: Partial<Plotly.Layout> = {},
16
+ data: Plotly.Data[] = [],
17
+ ): Figure {
14
18
  return {
15
- data: [],
19
+ data,
16
20
  layout: { ...layoutOverrides } as Plotly.Layout,
17
21
  frames: null,
18
22
  };
@@ -35,9 +39,46 @@ describe("createInitialLayout", () => {
35
39
  });
36
40
  });
37
41
 
42
+ describe("hasCompatibleTraces", () => {
43
+ it("returns true for same trace types", () => {
44
+ const a = createFigure({}, [{ type: "scatter" } as Plotly.Data]);
45
+ const b = createFigure({}, [{ type: "scatter" } as Plotly.Data]);
46
+ expect(hasCompatibleTraces(a, b)).toBe(true);
47
+ });
48
+
49
+ it("returns true for default scatter types (undefined type)", () => {
50
+ const a = createFigure({}, [{} as Plotly.Data]);
51
+ const b = createFigure({}, [{ type: "scatter" } as Plotly.Data]);
52
+ expect(hasCompatibleTraces(a, b)).toBe(true);
53
+ });
54
+
55
+ it("returns false for different trace types", () => {
56
+ const a = createFigure({}, [{ type: "scatter" } as Plotly.Data]);
57
+ const b = createFigure({}, [{ type: "histogram" } as Plotly.Data]);
58
+ expect(hasCompatibleTraces(a, b)).toBe(false);
59
+ });
60
+
61
+ it("returns false for different number of traces", () => {
62
+ const a = createFigure({}, [{ type: "scatter" } as Plotly.Data]);
63
+ const b = createFigure({}, [
64
+ { type: "scatter" } as Plotly.Data,
65
+ { type: "scatter" } as Plotly.Data,
66
+ ]);
67
+ expect(hasCompatibleTraces(a, b)).toBe(false);
68
+ });
69
+
70
+ it("returns true for empty data arrays", () => {
71
+ const a = createFigure({}, []);
72
+ const b = createFigure({}, []);
73
+ expect(hasCompatibleTraces(a, b)).toBe(true);
74
+ });
75
+ });
76
+
38
77
  describe("computeLayoutOnFigureChange", () => {
39
- it("preserves only dragmode/xaxis/yaxis from previous layout (#7964)", () => {
40
- const nextFigure = createFigure({ title: { text: "New" } });
78
+ it("preserves only dragmode/xaxis/yaxis from previous layout for compatible traces (#7964)", () => {
79
+ const scatterData = [{ type: "scatter" } as Plotly.Data];
80
+ const prevFigure = createFigure({}, scatterData);
81
+ const nextFigure = createFigure({ title: { text: "New" } }, scatterData);
41
82
  const prevLayout: Partial<Plotly.Layout> = {
42
83
  dragmode: "zoom",
43
84
  xaxis: { range: [0, 10] },
@@ -46,7 +87,11 @@ describe("computeLayoutOnFigureChange", () => {
46
87
  annotations: [{ text: "Old", x: 0, y: 0 }],
47
88
  };
48
89
 
49
- const result = computeLayoutOnFigureChange(nextFigure, prevLayout);
90
+ const result = computeLayoutOnFigureChange(
91
+ nextFigure,
92
+ prevFigure,
93
+ prevLayout,
94
+ );
50
95
 
51
96
  // Preserved from prev
52
97
  expect(result.dragmode).toBe("zoom");
@@ -59,15 +104,68 @@ describe("computeLayoutOnFigureChange", () => {
59
104
  expect(result.annotations).toBeUndefined();
60
105
  });
61
106
 
107
+ it("resets axis settings when trace types change (#5898)", () => {
108
+ const prevFigure = createFigure({}, [{ type: "histogram" } as Plotly.Data]);
109
+ const nextFigure = createFigure({ title: { text: "Bar" } }, [
110
+ { type: "bar" } as Plotly.Data,
111
+ ]);
112
+ const prevLayout: Partial<Plotly.Layout> = {
113
+ dragmode: "zoom",
114
+ xaxis: { range: [-3, 3] },
115
+ yaxis: { range: [0, 200] },
116
+ };
117
+
118
+ const result = computeLayoutOnFigureChange(
119
+ nextFigure,
120
+ prevFigure,
121
+ prevLayout,
122
+ );
123
+
124
+ // Dragmode is still preserved
125
+ expect(result.dragmode).toBe("zoom");
126
+ // Axis settings are NOT preserved — they come from the new figure's layout
127
+ expect(result.xaxis).toBeUndefined();
128
+ expect(result.yaxis).toBeUndefined();
129
+ // New figure's layout is applied
130
+ expect(result.title).toEqual({ text: "Bar" });
131
+ });
132
+
133
+ it("preserves nextFigure dragmode when prevLayout has no dragmode", () => {
134
+ const prevFigure = createFigure({}, [{ type: "histogram" } as Plotly.Data]);
135
+ const nextFigure = createFigure({ dragmode: "lasso" }, [
136
+ { type: "bar" } as Plotly.Data,
137
+ ]);
138
+ const prevLayout: Partial<Plotly.Layout> = {
139
+ xaxis: { range: [0, 10] },
140
+ };
141
+
142
+ const result = computeLayoutOnFigureChange(
143
+ nextFigure,
144
+ prevFigure,
145
+ prevLayout,
146
+ );
147
+
148
+ // nextFigure.layout.dragmode should be preserved via base, not overwritten
149
+ expect(result.dragmode).toBe("lasso");
150
+ // Axis settings are NOT preserved for incompatible traces
151
+ expect(result.xaxis).toBeUndefined();
152
+ expect(result.yaxis).toBeUndefined();
153
+ });
154
+
62
155
  it("uses shapes from new figure, not previous layout", () => {
63
156
  const nextFigure = createFigure({
64
157
  shapes: [{ type: "circle", x0: 0, x1: 1, y0: 0, y1: 1 }],
65
158
  });
159
+ const prevFigure = createFigure({});
66
160
  const prevLayout: Partial<Plotly.Layout> = {
67
161
  shapes: [{ type: "rect", x0: 0, x1: 1, y0: 0, y1: 1 }],
68
162
  };
69
163
 
70
- const result = computeLayoutOnFigureChange(nextFigure, prevLayout);
164
+ const result = computeLayoutOnFigureChange(
165
+ nextFigure,
166
+ prevFigure,
167
+ prevLayout,
168
+ );
71
169
 
72
170
  expect(result.shapes).toHaveLength(1);
73
171
  expect(result.shapes?.[0].type).toBe("circle");
@@ -3,7 +3,7 @@
3
3
  import { usePrevious } from "@uidotdev/usehooks";
4
4
  import { dequal as isEqual } from "dequal";
5
5
  import type * as Plotly from "plotly.js";
6
- import { useEffect, useState } from "react";
6
+ import { useEffect, useRef, useState } from "react";
7
7
  import { Objects } from "@/utils/objects";
8
8
  import type { Figure } from "./Plot";
9
9
 
@@ -39,18 +39,46 @@ export function createInitialLayout(figure: Figure): Partial<Plotly.Layout> {
39
39
  };
40
40
  }
41
41
 
42
+ /**
43
+ * Returns true if two figures have compatible trace types.
44
+ * When traces are incompatible (different types, count, or order), axis settings
45
+ * from the old figure should not be preserved as they would distort the
46
+ * new chart. See https://github.com/marimo-team/marimo/issues/5898
47
+ */
48
+ export function hasCompatibleTraces(prev: Figure, next: Figure): boolean {
49
+ if (prev.data.length !== next.data.length) {
50
+ return false;
51
+ }
52
+ return prev.data.every(
53
+ (trace, i) =>
54
+ (trace.type ?? "scatter") === (next.data[i]?.type ?? "scatter"),
55
+ );
56
+ }
57
+
42
58
  /**
43
59
  * Computes the updated layout when the figure changes.
44
60
  * Preserves user-interaction values (dragmode, xaxis, yaxis) while
45
61
  * taking everything else from the new figure's layout.
62
+ *
63
+ * When trace types change, only dragmode is preserved — axis settings
64
+ * are reset to let Plotly auto-compute ranges for the new chart type.
46
65
  */
47
66
  export function computeLayoutOnFigureChange(
48
67
  nextFigure: Figure,
68
+ prevFigure: Figure,
49
69
  prevLayout: Partial<Plotly.Layout>,
50
70
  ): Partial<Plotly.Layout> {
71
+ const base = createInitialLayout(nextFigure);
72
+ if (hasCompatibleTraces(prevFigure, nextFigure)) {
73
+ return {
74
+ ...base,
75
+ ...Objects.pick(prevLayout, PERSISTED_LAYOUT_KEYS),
76
+ };
77
+ }
78
+ // Incompatible traces — only preserve dragmode, not axis settings
51
79
  return {
52
- ...createInitialLayout(nextFigure),
53
- ...Objects.pick(prevLayout, PERSISTED_LAYOUT_KEYS),
80
+ ...base,
81
+ ...("dragmode" in prevLayout ? { dragmode: prevLayout.dragmode } : {}),
54
82
  };
55
83
  }
56
84
 
@@ -121,6 +149,9 @@ export function usePlotlyLayout({
121
149
  return structuredClone(originalFigure);
122
150
  });
123
151
 
152
+ // Track the previous figure to detect trace type changes
153
+ const prevFigureRef = useRef(figure);
154
+
124
155
  const [layout, setLayout] = useState<Partial<Plotly.Layout>>(() => {
125
156
  return {
126
157
  ...createInitialLayout(figure),
@@ -132,12 +163,15 @@ export function usePlotlyLayout({
132
163
  // Update figure and layout when originalFigure changes
133
164
  useEffect(() => {
134
165
  const nextFigure = structuredClone(originalFigure);
166
+ const prevFig = prevFigureRef.current;
167
+ prevFigureRef.current = nextFigure;
135
168
  setFigure(nextFigure);
136
169
  // Start with the new figure's layout, then only preserve user-interaction
137
170
  // values (dragmode, xaxis, yaxis) from the previous layout.
138
171
  // We don't want to preserve other properties like `shapes` from the previous
139
172
  // layout, as they should be fully controlled by the figure prop.
140
- setLayout((prev) => computeLayoutOnFigureChange(nextFigure, prev));
173
+ // When trace types change, axis settings are reset to avoid distortion (#5898).
174
+ setLayout((prev) => computeLayoutOnFigureChange(nextFigure, prevFig, prev));
141
175
  }, [originalFigure, isScriptLoaded]);
142
176
 
143
177
  const prevFigure = usePrevious(figure) ?? figure;