@@ -19,8 +19,7 @@ import style from "../hiplot.scss";
1919import { HiPlotPluginData } from "../plugin" ;
2020import { ResizableH } from "../lib/resizable" ;
2121import { Filter , FilterType , apply_filters } from "../filters" ;
22- import { foDynamicSizeFitContent , foCreateAxisLabel } from "../lib/svghelpers" ;
23- import { IS_SAFARI , redrawForeignObject } from "../lib/browsercompat" ;
22+ import { IS_MOBILE } from "../lib/browsercompat" ;
2423
2524interface StringMapping < V > { [ key : string ] : V ; } ;
2625
@@ -107,6 +106,13 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
107106 axis : d3 . Axis < number > ;
108107 d3brush = d3 . brushY ( ) ;
109108
109+ private labelTextFromHtml ( html : string , fallback : string ) : string {
110+ const tmp = document . createElement ( "div" ) ;
111+ tmp . innerHTML = html || "" ;
112+ const text = ( tmp . textContent || tmp . innerText || "" ) . trim ( ) ;
113+ return text . length ? text : fallback ;
114+ }
115+
110116 constructor ( props : ParallelPlotData ) {
111117 super ( props ) ;
112118 this . state = {
@@ -159,28 +165,11 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
159165 this . xscale . domain ( this . state . dimensions ) ;
160166 this . dimensions_dom . filter ( function ( this : ParallelPlot , p ) { return this . state . dimensions . indexOf ( p ) == - 1 ; } . bind ( this ) ) . remove ( ) ;
161167 this . dimensions_dom = this . dimensions_dom . filter ( function ( this : ParallelPlot , p ) { return this . state . dimensions . indexOf ( p ) !== - 1 ; } . bind ( this ) ) ;
162- if ( ! this . state . dragging && ! IS_SAFARI ) {
168+ if ( ! this . state . dragging ) {
163169 g = g . transition ( ) ;
164170 }
165171 g . attr ( "transform" , function ( this : ParallelPlot , p ) { return "translate(" + this . position ( p ) + ")" ; } . bind ( this ) ) ;
166172
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-
184173 this . update_ticks ( ) ;
185174 this . updateAxisTitlesAnglesAndFontSize ( ) ;
186175 // Notify parent of hidden columns count change (dimensions affects can_restore_axis)
@@ -396,22 +385,8 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
396385 }
397386 me . dimensions_dom . attr ( "transform" , function ( d ) { return "translate(" + me . position ( d ) + ")" ; } ) ;
398387
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- }
408388 } )
409389 . 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-
415390 if ( ! me . state . dragging . dragging ) {
416391 // no movement, invert axis
417392 var extent = invert_axis ( d ) ;
@@ -425,15 +400,7 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
425400 me . setState ( { order : Array . from ( me . state . dimensions ) , dragging : null } , function ( ) {
426401 // reorder axes
427402 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 ) + ")" ) ;
436- }
403+ parentG . transition ( ) . attr ( "transform" , "translate(" + me . xscale ( d ) + ")" ) ;
437404 var extents = brush_extends ( ) ;
438405 extent = extents [ d ] ;
439406 me . update_ticks ( d , extent ) ;
@@ -465,17 +432,49 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
465432 // @ts -ignore
466433 d3 . select ( this ) . call ( me . get_axis ( d ) ) ;
467434 } )
468- . append ( function ( dim ) { return foCreateAxisLabel ( me . props . params_def [ dim ] , me . props . context_menu_ref , null ) ; } )
469- . attr ( "y" , - 20 )
435+ . append ( "text" )
436+ . text ( function ( dim ) {
437+ const pd = me . props . params_def [ dim ] ;
438+ return me . labelTextFromHtml ( pd . label_html , pd . name ) ;
439+ } )
470440 . attr ( "text-anchor" , "left" )
471441 . classed ( "pplot-label" , true )
472- . classed ( style . pplotLabel , true ) ;
473- me . dimensions_dom . selectAll ( ".label-name" ) . style ( "font-size" , "12px" ) ;
474- me . dimensions_dom . selectAll ( ".pplot-label" ) . each ( function ( this : SVGForeignObjectElement , d : string ) {
475- foDynamicSizeFitContent ( this , [ - me . xscale ( d ) + 5 , - me . xscale ( d ) + me . state . width - 5 ] ) ;
476- } ) . attr ( "x" , 0 ) . style ( "width" , "1px" ) ;
442+ . classed ( "label-name" , true )
443+ . classed ( style . axisLabelText , true )
444+ . classed ( style . pplotLabel , true )
445+ . each ( function ( dim ) {
446+ d3 . select ( this ) . append ( "title" )
447+ . text ( IS_MOBILE ? "Long-press for options" : "Right click for options" ) ;
448+ } )
449+ . on ( "contextmenu" , function ( event , dim : string ) {
450+ if ( me . props . context_menu_ref && me . props . context_menu_ref . current ) {
451+ me . props . context_menu_ref . current . show ( event . pageX , event . pageY , dim ) ;
452+ event . preventDefault ( ) ;
453+ event . stopPropagation ( ) ;
454+ }
455+ } )
456+ . on ( "touchstart" , function ( event , dim : string ) {
457+ const target = this as SVGTextElement ;
458+ if ( ! me . props . context_menu_ref || ! me . props . context_menu_ref . current ) {
459+ return ;
460+ }
461+ const touch = ( event as TouchEvent ) . touches [ 0 ] ;
462+ const timer = window . setTimeout ( ( ) => {
463+ me . props . context_menu_ref . current . show ( touch . pageX , touch . pageY , dim ) ;
464+ ( target as any ) . __longPressTimer = null ;
465+ } , 500 ) ;
466+ ( target as any ) . __longPressTimer = timer ;
467+ } )
468+ . on ( "touchend touchcancel touchmove" , function ( ) {
469+ const target = this as SVGTextElement ;
470+ const timer = ( target as any ) . __longPressTimer ;
471+ if ( timer ) {
472+ window . clearTimeout ( timer ) ;
473+ ( target as any ) . __longPressTimer = null ;
474+ }
475+ } ) ;
477476 me . updateAxisTitlesAnglesAndFontSize ( ) ;
478- me . dimensions_dom . selectAll ( "foreignObject " ) . call ( create_drag_beh ( ) ) ;
477+ me . dimensions_dom . selectAll ( ".pplot-label " ) . call ( create_drag_beh ( ) ) ;
479478
480479 // Add and store a brush for each axis.
481480 me . dimensions_dom . append ( "svg:g" )
@@ -736,18 +735,14 @@ export class ParallelPlot extends React.Component<ParallelPlotData, ParallelPlot
736735
737736 updateAxisTitlesAnglesAndFontSize ( ) {
738737 const FONT_SIZE = 12 ;
739- this . dimensions_dom . selectAll ( ".label-name " ) . each ( function ( this : HTMLSpanElement ) {
738+ this . dimensions_dom . selectAll ( ".pplot-label " ) . each ( function ( this : SVGTextElement ) {
740739 this . style . fontSize = FONT_SIZE + "px" ;
741-
742- // Use writing-mode for vertical text on all browsers
743- // This provides a unified look and avoids Safari's foreignObject bugs
744- this . style . writingMode = "vertical-lr" ;
745- this . style . textOrientation = "mixed" ;
746- this . style . transform = "" ;
747-
748- const fo = this . parentElement . parentElement as any as SVGForeignObjectElement ;
749- // Position labels at the very top of the margin area
750- fo . setAttribute ( "y" , ( - TOP_MARGIN_PIXELS + 2 ) + "" ) ;
740+ this . style . writingMode = "" ;
741+ this . style . textOrientation = "" ;
742+ this . setAttribute ( "x" , "10" ) ;
743+ this . setAttribute ( "y" , "0" ) ;
744+ this . setAttribute ( "text-anchor" , "start" ) ;
745+ this . setAttribute ( "transform" , "rotate(-90)" ) ;
751746 } ) ;
752747 }
753748
0 commit comments