99import java .awt .*;
1010import java .awt .event .*;
1111import java .util .ArrayList ;
12+ import java .util .Collections ;
1213import java .util .List ;
1314import java .util .function .Consumer ;
1415
@@ -45,9 +46,11 @@ public class RendererOptions {
4546 private boolean showBubbleSolid = false ; // whether to draw bubbles as solid circles (instead of empty circles)
4647 private boolean showBubbleFading = true ; // whether to fade bubbles (with alpha) based on their size
4748 private boolean showBubbleAsEmptyCircles = false ; // whether to draw bubbles as empty circles
49+ boolean showOnlySelectedSeries = true ; // whether to hide the gaps in the multi-selection entities
4850 }
51+
4952 // Renderer options
50- private final RendererOptions options = new RendererOptions ();
53+ final RendererOptions options = new RendererOptions ();
5154 protected JDialog optionsDialog = null ;
5255
5356 // Insets and strokes
@@ -60,21 +63,23 @@ public class RendererOptions {
6063 protected static final float CURVE_STROKE = 1.5f ; // stroke for the main curve (avg line); bands will be filled, not stroked
6164 protected static final int DIMMED_LINE_ALPHA = 20 ; // for dimming non-hovered series (0–255)
6265 protected static final int DIMMED_BAND_ALPHA = 0 ; // for dimming non-hovered bands (0–255)
66+ protected static final int DIMMED_SOLID_BUBBLE_ALPHA = 30 ; // for dimming bubble histograms
67+ protected static final int DIMMED_FADING_BUBBLE_ALPHA = 2 ;
6368
6469 // Renderers list
6570 protected final List <SeriesRenderer > renderers = new ArrayList <>();
6671
6772 // Scaling state
6873 protected double globalMin = 0 ; // on the-y axis; x-axis is always 0 to dt*(N-1)
6974 protected double globalMax = 1 ;
70- protected double dt = 1 ;
75+ protected double dt = 1 ; // time step between consecutive points in seconds
7176
7277 // Crosshair state
7378 protected Integer mouseX = null ; // mouse coordinates in pixels, relative to the panel; null if mouse is outside the plot area
7479 protected Integer mouseY = null ;
7580 protected Integer snappedX = null ; // we are snapped here (or null)
7681 protected Integer snappedY = null ;
77- private BubbleHit lastBubbleHit ; // used for snapping to bubbles and showing tooltip info about the bubble we snapped to
82+ // private BubbleHit lastBubbleHit; // used for snapping to bubbles and showing tooltip info about the bubble we snapped to
7883 protected Consumer <double []> coordCallback ;
7984
8085 // Cached plot area
@@ -100,7 +105,7 @@ public void mouseMoved(MouseEvent e) {
100105 mouseY = null ;
101106 snappedX = null ;
102107 snappedY = null ;
103- lastBubbleHit = null ;
108+ // lastBubbleHit = null; // don't seem to need it
104109 if (coordCallback != null ) {
105110 coordCallback .accept (null );
106111 }
@@ -151,7 +156,7 @@ public void mouseMoved(MouseEvent e) {
151156 if (isSnapToNodes ()) {
152157 hit = findClosestBubble (mx , my );
153158 }
154- lastBubbleHit = hit ;
159+ // lastBubbleHit = hit;
155160 if (hit != null ) {
156161 // highlight bubble
157162 snappedX = hit .px ;
@@ -180,7 +185,7 @@ public void mouseExited(MouseEvent e) {
180185 mouseY = null ;
181186 snappedX = null ;
182187 snappedY = null ;
183- lastBubbleHit = null ;
188+ // lastBubbleHit = null;
184189 if (coordCallback != null ) {
185190 coordCallback .accept (null );
186191 }
@@ -362,6 +367,10 @@ public void setShowBubbleSolid(boolean v) {
362367 }
363368 repaint ();
364369 }
370+ public void setShowOnlySelectedSeries (boolean v ) {
371+ this .options .showOnlySelectedSeries = v ;
372+ repaint ();
373+ }
365374
366375 public boolean isShowAvgAsStep () { return options .showAvgAsStep ; }
367376 public boolean isShowBandAsStep () { return options .showBandAsStep ; }
@@ -387,6 +396,9 @@ public boolean isShowBubbleFading() {
387396 public boolean isShowBubbleAsEmptyCircles () {
388397 return options .showBubbleAsEmptyCircles ;
389398 }
399+ public boolean isShowOnlySelectedSeries () {
400+ return options .showOnlySelectedSeries ;
401+ }
390402
391403 private Point findClosestNode (int mouseX , int mouseY ) { // use for SnapToNodes feature
392404 Point best = null ;
@@ -432,6 +444,16 @@ private BubbleHit findClosestBubble(int mouseX, int mouseY) {
432444 return best ;
433445 }
434446
447+ int getOrdinalIndexForClusterSize (int cs ) {
448+ int idx = 0 ;
449+ for (SeriesRenderer r : renderers ) {
450+ if (r instanceof BubbleRenderer bub ) {
451+ if (bub .getClusterSize () == cs ) return idx ;
452+ idx ++;
453+ }
454+ }
455+ return 0 ; // fallback
456+ }
435457
436458 public void setHoveredSeriesName (String name ) {
437459 this .hoveredSeriesName = name ;
@@ -504,22 +526,42 @@ protected void paintComponent(Graphics g) {
504526 return ;
505527 }
506528
529+ // --- collect selected cluster sizes (needed for bubble mode only)
530+ List <Integer > selectedSizes = null ;
531+ if (hasBubble && options .showOnlySelectedSeries ) {
532+ selectedSizes = new ArrayList <>();
533+ for (SeriesRenderer r : renderers ) {
534+ if (r instanceof BubbleRenderer bub ) {
535+ selectedSizes .add (bub .getClusterSize ());
536+ }
537+ }
538+ Collections .sort (selectedSizes );
539+ }
540+
507541 // --- compute axis scaling -----------------------------------------
508542 double yMaxRounded ;
509543 double yMinRounded ;
510544 if (!hasBubble ) {
511545 yMaxRounded = roundUpNice (globalMax ); // in molecules
512546 yMinRounded = (globalMin < 0 ) ? -roundUpNice (-globalMin ) : 0 ; // may be negative if avg-sd<0
513547 } else {
514- // bubble mode: Y-axis = cluster size (0,1,2,...)
515- int maxCluster = Integer .MIN_VALUE ;
516- for (SeriesRenderer r : renderers ) {
517- if (r instanceof BubbleRenderer bub ) {
518- maxCluster = Math .max (maxCluster , bub .getClusterSize ());
548+ if (!options .showOnlySelectedSeries ) {
549+ // bubble mode showing all between 0 and max selected cluster size
550+ // Y-axis = cluster size (0,1,2,...)
551+ int maxCluster = Integer .MIN_VALUE ;
552+ for (SeriesRenderer r : renderers ) {
553+ if (r instanceof BubbleRenderer bub ) {
554+ maxCluster = Math .max (maxCluster , bub .getClusterSize ());
555+ }
519556 }
557+ yMinRounded = 0 ; // Y-axis always starts at 0
558+ yMaxRounded = maxCluster + 0.5 ; // top padding: +0.5 so the top cluster size is centered
559+ } else {
560+ // compressed mode: only selected sizes
561+ int count = selectedSizes .size ();
562+ yMinRounded = 0 ;
563+ yMaxRounded = count + 0.5 ; // 0 baseline + count rows above
520564 }
521- yMinRounded = 0 ; // Y-axis always starts at 0
522- yMaxRounded = maxCluster + 0.5 ; // top padding: +0.5 so the top cluster size is centered
523565 }
524566 double xMax = dt * (maxLength - 1 ); // in seconds
525567 double xMaxRounded = roundUpNice (xMax );
@@ -563,12 +605,22 @@ protected void paintComponent(Graphics g) {
563605 }
564606 }
565607 } else {
566- // bubble mode: grid at every integer cluster size
567- int maxCluster = (int )Math .floor (yMaxRounded - 0.5 );
568- for (int cs = 0 ; cs <= maxCluster ; cs ++) {
569- double norm = (cs - yMinRounded ) / (yMaxRounded - yMinRounded );
570- int yPix = y0 - (int )Math .round (norm * plotHeight );
571- g2 .drawLine (x0 , yPix , x1 , yPix );
608+ if (!options .showOnlySelectedSeries ) {
609+ // bubble all mode: grid at every integer cluster size
610+ int maxCluster = (int )Math .floor (yMaxRounded - 0.5 );
611+ for (int cs = 0 ; cs <= maxCluster ; cs ++) {
612+ double norm = (cs - yMinRounded ) / (yMaxRounded - yMinRounded );
613+ int yPix = y0 - (int )Math .round (norm * plotHeight );
614+ g2 .drawLine (x0 , yPix , x1 , yPix );
615+ }
616+ } else {
617+ // bubble compressed mode: only selected cluster sizes
618+ for (int i = 0 ; i < selectedSizes .size (); i ++) {
619+ int row = i +1 ; // row 0 is for baseline (horizontal axis), then row 1 for first selected size, etc.
620+ double norm = (row - yMinRounded ) / (yMaxRounded - yMinRounded );
621+ int yPix = y0 - (int )Math .round (norm * plotHeight );
622+ g2 .drawLine (x0 , yPix , x1 , yPix );
623+ }
572624 }
573625 }
574626
@@ -626,14 +678,29 @@ protected void paintComponent(Graphics g) {
626678 }
627679 }
628680 } else {
629- int maxCluster = (int )Math .floor (yMaxRounded - 0.5 );
630- for (int cs = 0 ; cs <= maxCluster ; cs ++) {
631- double norm = (cs - yMinRounded ) / (yMaxRounded - yMinRounded );
632- int yPix = y0 - (int )Math .round (norm * plotHeight );
633- g2 .drawLine (x0 - 5 , yPix , x0 , yPix ); // major tick
634- String label = Integer .toString (cs ); // label
635- int sw = fm .stringWidth (label );
636- g2 .drawString (label , x0 - 10 - sw , yPix + fm .getAscent () / 2 );
681+ if (!options .showOnlySelectedSeries ) {
682+ // show all cluster sizes on the y-axis, from 0 to the max one selected
683+ int maxCluster = (int )Math .floor (yMaxRounded - 0.5 );
684+ for (int cs = 0 ; cs <= maxCluster ; cs ++) {
685+ double norm = (cs - yMinRounded ) / (yMaxRounded - yMinRounded );
686+ int yPix = y0 - (int )Math .round (norm * plotHeight );
687+ g2 .drawLine (x0 - 5 , yPix , x0 , yPix );
688+ String label = Integer .toString (cs );
689+ int sw = fm .stringWidth (label );
690+ g2 .drawString (label , x0 - 10 - sw , yPix + fm .getAscent () / 2 );
691+ }
692+ } else {
693+ // compressed mode
694+ for (int i = 0 ; i < selectedSizes .size (); i ++) {
695+ int cs = selectedSizes .get (i );
696+ int row = i +1 ; // row 0 is for baseline (horizontal axis), then row 1 for first selected size, etc.
697+ double norm = (row - yMinRounded ) / (yMaxRounded - yMinRounded );
698+ int yPix = y0 - (int )Math .round (norm * plotHeight );
699+ g2 .drawLine (x0 - 5 , yPix , x0 , yPix );
700+ String label = Integer .toString (cs );
701+ int sw = fm .stringWidth (label );
702+ g2 .drawString (label , x0 - 10 - sw , yPix + fm .getAscent () / 2 );
703+ }
637704 }
638705 }
639706 double xStep = xMajor [1 ] - xMajor [0 ]; // x-axis ticks (on the horizontal axis)
@@ -661,15 +728,6 @@ protected void paintComponent(Graphics g) {
661728 g2 .drawLine (x0 , mouseY , x1 , mouseY );
662729 }
663730
664- // Highlight snapped point (if any)
665- if (snappedX != null && snappedY != null ) {
666- Graphics2D g3 = (Graphics2D ) g2 .create ();
667- g3 .setColor (new Color (255 , 80 , 0 )); // bright red-ish
668- int r = 4 ; // radius of highlight
669- g3 .fillOval (snappedX - r , snappedY - r , 2 *r , 2 *r );
670- g3 .dispose ();
671- }
672-
673731 // Renderers (bands first, then bubbles, then lines)
674732 for (SeriesRenderer r : renderers ) {
675733 if (r instanceof BandRenderer || r instanceof BarRenderer ) {
@@ -686,5 +744,15 @@ protected void paintComponent(Graphics g) {
686744 r .draw (g2 , x0 , x1 , y0 , y1 , plotWidth , plotHeight , xMaxRounded , yMaxRounded , yMinRounded , dt );
687745 }
688746 }
747+
748+ // Highlight snapped point (if any)
749+ if (snappedX != null && snappedY != null ) {
750+ Graphics2D g3 = (Graphics2D ) g2 .create ();
751+ g3 .setColor (new Color (255 , 80 , 0 )); // bright red-ish
752+ int r = 3 ; // radius of highlight
753+ g3 .fillOval (snappedX - r , snappedY - r , 2 *r , 2 *r );
754+ g3 .dispose ();
755+ }
756+
689757 }
690758}
0 commit comments