Skip to content

Commit c96bb6d

Browse files
better desktop safari, worse mobile
1 parent 6ebfbb2 commit c96bb6d

3 files changed

Lines changed: 52 additions & 28 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "hiplot-mm"
7-
version = "0.0.3rc4"
7+
version = "0.0.3rc2"
88
description = "High dimensional Interactive Plotting tool"
99
readme = "README.md"
1010
license = "MIT"

src/lib/browsercompat.ts

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,17 @@ export const IS_MOBILE_SAFARI = IS_SAFARI && IS_IOS;
55
export const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
66
(navigator.maxTouchPoints > 0 && /Mobi|Android/i.test(navigator.userAgent));
77

8-
export function redrawObject(fo: SVGForeignObjectElement) {
8+
/**
9+
* Safari has a bug where foreignObjects don't visually update when their parent's
10+
* transform changes. This function forces a redraw by removing and re-adding them.
11+
* Works on mobile Safari but not reliably on desktop Safari.
12+
*/
13+
export function redrawForeignObject(fo: SVGForeignObjectElement) {
914
const parent = fo.parentNode;
1015
parent.removeChild(fo);
1116
parent.appendChild(fo);
12-
13-
}
14-
export function redrawAllForeignObjectsIfSafari() {
15-
if (!IS_SAFARI) {
16-
return;
17-
}
18-
const fo = document.getElementsByTagName("foreignObject");
19-
Array.from(fo).forEach(redrawObject);
2017
}
2118

22-
export function setupBrowserCompat(root: HTMLDivElement) {
23-
/**
24-
* Safari has a lot of trouble with foreignObjects inside canvas. Especially when we apply rotations, etc...
25-
* As it considers the parent of the objects inside the FO to be the canvas origin, and not the FO.
26-
* See https://stackoverflow.com/questions/51313873/svg-foreignobject-not-working-properly-on-safari
27-
* Applying the fix in the link above fixes their position upon scroll - we don't want that, so we
28-
* manually force-redraw them upon scroll.
29-
*/
30-
if (IS_SAFARI) {
31-
root.addEventListener("wheel", redrawAllForeignObjectsIfSafari);
32-
}
19+
export function setupBrowserCompat(_root: HTMLDivElement) {
20+
// No browser-specific setup needed currently
3321
}

src/parallel/parallel.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { HiPlotPluginData } from "../plugin";
2020
import { ResizableH } from "../lib/resizable";
2121
import { Filter, FilterType, apply_filters } from "../filters";
2222
import { foDynamicSizeFitContent, foCreateAxisLabel } from "../lib/svghelpers";
23-
import { IS_SAFARI, IS_MOBILE_SAFARI, redrawAllForeignObjectsIfSafari } from "../lib/browsercompat";
23+
import { IS_SAFARI, redrawForeignObject } from "../lib/browsercompat";
2424

2525
interface StringMapping<V> { [key: string]: V; };
2626

@@ -163,7 +163,24 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
163163
g = g.transition();
164164
}
165165
g.attr("transform", function(this: ParallelPlot, p) { return "translate(" + this.position(p) + ")"; }.bind(this));
166-
redrawAllForeignObjectsIfSafari();
166+
167+
// Safari: apply CSS transforms to foreignObjects during drag, redraw after
168+
if (IS_SAFARI) {
169+
if (this.state.dragging) {
170+
const me = this;
171+
this.dimensions_dom.each(function(dim: string) {
172+
const fo = d3.select(this).select("foreignObject");
173+
const delta = me.position(dim) - me.xscale(dim);
174+
fo.style("transform", delta !== 0 ? `translateX(${delta}px)` : null);
175+
});
176+
} else {
177+
// Not dragging - force redraw all foreignObjects to ensure correct position
178+
this.dimensions_dom.selectAll("foreignObject").each(function() {
179+
redrawForeignObject(this as SVGForeignObjectElement);
180+
});
181+
}
182+
}
183+
167184
this.update_ticks();
168185
this.updateAxisTitlesAnglesAndFontSize();
169186
// Notify parent of hidden columns count change (dimensions affects can_restore_axis)
@@ -378,9 +395,23 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
378395
me.setState({dimensions: new_dimensions});
379396
}
380397
me.dimensions_dom.attr("transform", function(d) { return "translate(" + me.position(d) + ")"; });
381-
redrawAllForeignObjectsIfSafari();
398+
399+
// Safari doesn't update foreignObject positions when parent transforms change.
400+
// Apply the position delta directly to each foreignObject as a CSS transform.
401+
if (IS_SAFARI) {
402+
me.dimensions_dom.each(function(dim: string) {
403+
const fo = d3.select(this).select("foreignObject");
404+
const delta = me.position(dim) - me.xscale(dim);
405+
fo.style("transform", delta !== 0 ? `translateX(${delta}px)` : null);
406+
});
407+
}
382408
})
383409
.on("end", function(event, d: string) {
410+
// Clear Safari CSS transforms from foreignObjects
411+
if (IS_SAFARI) {
412+
me.dimensions_dom.selectAll("foreignObject").style("transform", null);
413+
}
414+
384415
if (!me.state.dragging.dragging) {
385416
// no movement, invert axis
386417
var extent = invert_axis(d);
@@ -393,11 +424,16 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
393424
const element = this;
394425
me.setState({order: Array.from(me.state.dimensions), dragging: null}, function() {
395426
// reorder axes
396-
var drag: any = d3.select(this);
397-
if (!IS_SAFARI) {
398-
drag = drag.transition();
427+
const parentG = d3.select(element.parentElement.parentElement);
428+
if (IS_SAFARI) {
429+
// Skip transition on Safari - it causes foreignObject positioning issues
430+
parentG.attr("transform", "translate(" + me.xscale(d) + ")");
431+
// Force redraw of the foreignObject to ensure correct position
432+
const fo = parentG.select("foreignObject").node() as SVGForeignObjectElement;
433+
if (fo) redrawForeignObject(fo);
434+
} else {
435+
parentG.transition().attr("transform", "translate(" + me.xscale(d) + ")");
399436
}
400-
d3.select(element.parentElement.parentElement).attr("transform", "translate(" + me.xscale(d) + ")");
401437
var extents = brush_extends();
402438
extent = extents[d];
403439
me.update_ticks(d, extent);

0 commit comments

Comments
 (0)