Skip to content

Commit c5f4f2c

Browse files
authored
Merge pull request #1666 from virtualcell/dan-ss-display2
Dan ss display2
2 parents 5f63e06 + 6ef5bfd commit c5f4f2c

8 files changed

Lines changed: 498 additions & 211 deletions

File tree

vcell-client/src/main/java/cbit/plot/gui/AbstractPlotPanel.java

Lines changed: 65 additions & 190 deletions
Large diffs are not rendered by default.
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
package cbit.plot.gui;
2+
3+
import org.apache.logging.log4j.LogManager;
4+
import org.apache.logging.log4j.Logger;
5+
6+
import java.awt.*;
7+
import java.awt.geom.Path2D;
8+
9+
import static cbit.plot.gui.AbstractPlotPanel.*;
10+
11+
public final class PlotRenderers {
12+
13+
private static final Logger lg = LogManager.getLogger(PlotRenderers.class);
14+
15+
// Generic renderer interface
16+
protected interface SeriesRenderer {
17+
void draw(Graphics2D g2,
18+
int x0, int x1, int y0, int y1,
19+
int plotWidth, int plotHeight,
20+
double xMaxRounded, double yMaxRounded, double yMinRounded,
21+
double dt);
22+
/**
23+
* Returns the closest pixel-space point to (mouseX, mouseY),
24+
* or null if this renderer does not support snapping.
25+
*/
26+
default Point getClosestPoint(int mouseX, int mouseY) {
27+
return null; // default: no snapping
28+
}
29+
String getSeriesName();
30+
}
31+
32+
// -------------------------------------------------------------------------------------------------
33+
34+
// AVG renderer: polyline
35+
protected static class AvgRenderer implements SeriesRenderer {
36+
final String seriesName; // for legend and hover highlighting
37+
final double[] time;
38+
final double[] values;
39+
final Color color;
40+
final AbstractPlotPanel parent;
41+
42+
private int[] xs;
43+
private int[] ys;
44+
45+
AvgRenderer(String seriesName, double[] time, double[] values, Color color, AbstractPlotPanel parent) {
46+
this.seriesName = seriesName;
47+
this.time = time;
48+
this.values = values;
49+
this.color = color;
50+
this.parent = parent;
51+
}
52+
public String getSeriesName() { return seriesName; }
53+
54+
@Override
55+
public void draw(Graphics2D g2,
56+
int x0, int x1, int y0, int y1,
57+
int plotWidth, int plotHeight,
58+
double xMaxRounded, double yMaxRounded, double yMinRounded,
59+
double dt) {
60+
61+
int n = values.length;
62+
if (n < 2) return;
63+
64+
xs = new int[n];
65+
ys = new int[n];
66+
for (int i = 0; i < n; i++) {
67+
double t = (time != null ? time[i] : i * dt);
68+
xs[i] = x0 + (int)Math.round((t / xMaxRounded) * plotWidth);
69+
double v = values[i];
70+
double norm = (v - yMinRounded) / (yMaxRounded - yMinRounded);
71+
ys[i] = y0 - (int)Math.round(norm * plotHeight);
72+
}
73+
74+
Color c = color;
75+
// Dim this line if hovering another series
76+
if (parent.hoveredSeriesName != null) {
77+
if (!parent.hoveredSeriesName.equals(seriesName)) {
78+
c = new Color(c.getRed(), c.getGreen(), c.getBlue(), DIMMED_LINE_ALPHA);
79+
}
80+
}
81+
82+
// Unite the poins as a polyline or as steps, depending on the setting
83+
if(parent.getShowLines()) {
84+
if (!parent.stepAvg) { // draw the polyline (unite the data points directly)
85+
g2.setColor(c);
86+
g2.setStroke(new BasicStroke(CURVE_STROKE, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
87+
g2.drawPolyline(xs, ys, n);
88+
} else { // unite the data points as steps
89+
// step function: horizontal line from (time[i], values[i]) to (time[i+1], values[i]),
90+
// then vertical jump at time[i+1]
91+
g2.setColor(c);
92+
g2.setStroke(new BasicStroke(CURVE_STROKE, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
93+
for (int i = 0; i < n - 1; i++) {
94+
int xStart = xs[i];
95+
int yStart = ys[i];
96+
int xEnd = xs[i + 1];
97+
int yEnd = ys[i + 1];
98+
g2.drawLine(xStart, yStart, xEnd, yStart); // horizontal segment
99+
g2.drawLine(xEnd, yStart, xEnd, yEnd); // vertical jump
100+
}
101+
}
102+
}
103+
104+
// Draw nodes if enabled
105+
if (parent.getShowNodes()) { // parent is the AbstractPlotPanel
106+
g2.setColor(c);
107+
int diameter = parent.nodeDiameter; // small, unobtrusive
108+
if(!parent.getShowLines()) {
109+
diameter += 2; // if no lines, make nodes bigger to be more visible
110+
}
111+
int radius = diameter / 2;
112+
for (int i = 0; i < n; i++) {
113+
int cx = xs[i] - radius;
114+
int cy = ys[i] - radius;
115+
g2.fillOval(cx, cy, diameter, diameter);
116+
}
117+
}
118+
}
119+
120+
@Override
121+
public Point getClosestPoint(int mouseX, int mouseY) {
122+
if (xs == null || ys == null) return null;
123+
int bestIndex = -1;
124+
double bestDist2 = Double.POSITIVE_INFINITY;
125+
for (int i = 0; i < xs.length; i++) {
126+
double dx = xs[i] - mouseX;
127+
double dy = ys[i] - mouseY;
128+
double d2 = dx*dx + dy*dy;
129+
130+
if (d2 < bestDist2) {
131+
bestDist2 = d2;
132+
bestIndex = i;
133+
}
134+
}
135+
if (bestIndex < 0) return null;
136+
return new Point(xs[bestIndex], ys[bestIndex]);
137+
}
138+
}
139+
140+
// -------------------------------------------------------------------------------------------------
141+
142+
// Band renderer: min/max or sd (envelope)
143+
protected static class BandRenderer implements SeriesRenderer {
144+
final String seriesName; // for legend and hover highlighting
145+
final double[] time;
146+
final double[] upper;
147+
final double[] lower;
148+
final Color fillColor;
149+
final AbstractPlotPanel parent;
150+
151+
BandRenderer(String seriesName, double[] time, double[] lower, double[] upper, Color fillColor, AbstractPlotPanel parent) {
152+
this.seriesName = seriesName;
153+
this.time = time;
154+
this.upper = upper;
155+
this.lower = lower;
156+
this.fillColor = fillColor;
157+
this.parent = parent;
158+
}
159+
public String getSeriesName() { return seriesName; }
160+
161+
@Override
162+
public void draw(Graphics2D g2,
163+
int x0, int x1, int y0, int y1,
164+
int plotWidth, int plotHeight,
165+
double xMaxRounded, double yMaxRounded, double yMinRounded,
166+
double dt) {
167+
168+
int n = upper.length;
169+
if (n < 2) return;
170+
171+
Path2D path = new Path2D.Double();
172+
173+
double t0 = (time != null ? time[0] : 0.0);
174+
double v0 = upper[0];
175+
double norm0 = (v0 - yMinRounded) / (yMaxRounded - yMinRounded);
176+
int xStart = x0 + (int)Math.round((t0 / xMaxRounded) * plotWidth);
177+
int yStart = y0 - (int)Math.round(norm0 * plotHeight);
178+
path.moveTo(xStart, yStart);
179+
180+
for (int i = 1; i < n; i++) {
181+
double t = (time != null ? time[i] : i * dt);
182+
double v = upper[i];
183+
double norm = (v - yMinRounded) / (yMaxRounded - yMinRounded);
184+
int x = x0 + (int)Math.round((t / xMaxRounded) * plotWidth);
185+
int y = y0 - (int)Math.round(norm * plotHeight);
186+
path.lineTo(x, y);
187+
}
188+
189+
for (int i = n - 1; i >= 0; i--) {
190+
double t = (time != null ? time[i] : i * dt);
191+
double v = lower[i];
192+
double norm = (v - yMinRounded) / (yMaxRounded - yMinRounded);
193+
int x = x0 + (int)Math.round((t / xMaxRounded) * plotWidth);
194+
int y = y0 - (int)Math.round(norm * plotHeight);
195+
path.lineTo(x, y);
196+
}
197+
path.closePath();
198+
199+
Color c = fillColor;
200+
// If hovering, dim all bands except the hovered one
201+
if (parent.hoveredSeriesName != null) {
202+
if (!parent.hoveredSeriesName.equals(parent.getSeriesNameForRenderer(this))) {
203+
// Dim this band heavily
204+
c = new Color(c.getRed(), c.getGreen(), c.getBlue(), DIMMED_BAND_ALPHA);
205+
}
206+
}
207+
if (!parent.stepBand) {
208+
// your existing Path2D polygon
209+
g2.setColor(c);
210+
g2.fill(path);
211+
} else {
212+
// step function: horizontal line from (time[i], value[i]) to (time[i+1], value[i]),
213+
// then vertical jump at time[i+1]; do this for both upper and lower, then connect the ends
214+
Path2D step = new Path2D.Double();
215+
216+
// ---- UPPER BOUNDARY (left → right) ----
217+
// Start at first upper point
218+
double t0u = (time != null ? time[0] : 0.0);
219+
double v0u = upper[0];
220+
double norm0u = (v0u - yMinRounded) / (yMaxRounded - yMinRounded);
221+
int x0u = x0 + (int)Math.round((t0u / xMaxRounded) * plotWidth);
222+
int y0u = y0 - (int)Math.round(norm0u * plotHeight);
223+
step.moveTo(x0u, y0u);
224+
for (int i = 0; i < n - 1; i++) {
225+
// current point
226+
double tA = (time != null ? time[i] : i * dt);
227+
double vA = upper[i];
228+
double normA = (vA - yMinRounded) / (yMaxRounded - yMinRounded);
229+
int xA = x0 + (int)Math.round((tA / xMaxRounded) * plotWidth);
230+
int yA = y0 - (int)Math.round(normA * plotHeight);
231+
// next point
232+
double tB = (time != null ? time[i+1] : (i+1) * dt);
233+
double vB = upper[i+1];
234+
double normB = (vB - yMinRounded) / (yMaxRounded - yMinRounded);
235+
int xB = x0 + (int)Math.round((tB / xMaxRounded) * plotWidth);
236+
int yB = y0 - (int)Math.round(normB * plotHeight);
237+
// horizontal segment: (xA, yA) → (xB, yA)
238+
step.lineTo(xB, yA);
239+
// vertical segment: (xB, yA) → (xB, yB)
240+
step.lineTo(xB, yB);
241+
}
242+
// VERTICAL SEGMENT from last upper point to first lower point
243+
// compute first lower point at the rightmost x
244+
double tLast = (time != null ? time[n - 1] : (n - 1) * dt);
245+
double vLastLower = lower[n - 1];
246+
double normLastLower = (vLastLower - yMinRounded) / (yMaxRounded - yMinRounded);
247+
int xLast = x0 + (int)Math.round((tLast / xMaxRounded) * plotWidth);
248+
int yLastLower = y0 - (int)Math.round(normLastLower * plotHeight);
249+
// vertical segment from last upper point to first lower point
250+
step.lineTo(xLast, yLastLower);
251+
252+
// ---- LOWER BOUNDARY (right → left) ----
253+
for (int i = n - 1; i > 0; i--) {
254+
// current point
255+
double tA = (time != null ? time[i] : i * dt);
256+
double vA = lower[i];
257+
double normA = (vA - yMinRounded) / (yMaxRounded - yMinRounded);
258+
int xA = x0 + (int)Math.round((tA / xMaxRounded) * plotWidth);
259+
int yA = y0 - (int)Math.round(normA * plotHeight);
260+
// previous point
261+
double tB = (time != null ? time[i-1] : (i-1) * dt);
262+
double vB = lower[i-1];
263+
double normB = (vB - yMinRounded) / (yMaxRounded - yMinRounded);
264+
int xB = x0 + (int)Math.round((tB / xMaxRounded) * plotWidth);
265+
int yB = y0 - (int)Math.round(normB * plotHeight);
266+
// horizontal segment: (xA, yA) → (xB, yA)
267+
step.lineTo(xB, yA);
268+
// vertical segment: (xB, yA) → (xB, yB)
269+
step.lineTo(xB, yB);
270+
}
271+
step.closePath();
272+
g2.setColor(c);
273+
g2.fill(step);
274+
}
275+
}
276+
}
277+
278+
// ------------------------------------------------------------------
279+
280+
protected static class BarRenderer implements SeriesRenderer {
281+
282+
final String seriesName;
283+
final double[] time;
284+
final double[] upper;
285+
final double[] lower;
286+
final Color color;
287+
final AbstractPlotPanel parent;
288+
289+
public BarRenderer(String seriesName,
290+
double[] time,
291+
double[] lower,
292+
double[] upper,
293+
Color color,
294+
AbstractPlotPanel parent) {
295+
this.seriesName = seriesName;
296+
this.time = time;
297+
this.upper = upper;
298+
this.lower = lower;
299+
this.color = color;
300+
this.parent = parent;
301+
}
302+
303+
@Override
304+
public String getSeriesName() {
305+
return seriesName;
306+
}
307+
308+
@Override
309+
public void draw(Graphics2D g2,
310+
int x0, int x1, int y0, int y1,
311+
int plotWidth, int plotHeight,
312+
double xMaxRounded, double yMaxRounded, double yMinRounded,
313+
double dt) {
314+
int n = upper.length;
315+
if (n == 0) return;
316+
Color c = color;
317+
318+
if (parent.hoveredSeriesName != null && // dimming logic is the same as in BandRenderer
319+
!parent.hoveredSeriesName.equals(parent.getSeriesNameForRenderer(this))) {
320+
c = new Color(c.getRed(), c.getGreen(), c.getBlue(), AbstractPlotPanel.DIMMED_BAND_ALPHA);
321+
}
322+
323+
g2.setColor(c);
324+
g2.setStroke(new BasicStroke(2.0f));
325+
for (int i = 0; i < n; i++) {
326+
double t = (time != null ? time[i] : i * dt);
327+
double vLow = lower[i];
328+
double vHigh = upper[i];
329+
double vAvg = (vLow + vHigh) / 2.0;
330+
double normLow = (vLow - yMinRounded) / (yMaxRounded - yMinRounded);
331+
double normAvg = (vAvg - yMinRounded) / (yMaxRounded - yMinRounded);
332+
double normHigh = (vHigh - yMinRounded) / (yMaxRounded - yMinRounded);
333+
int x = x0 + (int)Math.round((t / xMaxRounded) * plotWidth);
334+
int yLowPix = y0 - (int)Math.round(normLow * plotHeight);
335+
int yAvgPix = y0 - (int)Math.round(normAvg * plotHeight);
336+
int yHighPix = y0 - (int)Math.round(normHigh * plotHeight);
337+
338+
if (parent.getShowLines()) {
339+
g2.drawLine(x, yLowPix, x, yHighPix); // vertical SD bar
340+
g2.drawLine(x - 3, yLowPix, x + 3, yLowPix); // optional caps
341+
g2.drawLine(x - 3, yHighPix, x + 3, yHighPix);
342+
} else {
343+
// only upper half
344+
g2.drawLine(x, yAvgPix, x, yHighPix); // vertical SD bar
345+
// diamond at top
346+
int d = 4; // diamond radius
347+
int[] dx = { x, x + d, x, x - d };
348+
int[] dy = { yHighPix - d, yHighPix, yHighPix + d, yHighPix };
349+
g2.fillPolygon(dx, dy, 4);
350+
351+
}
352+
}
353+
}
354+
}
355+
356+
357+
358+
}

vcell-client/src/main/java/cbit/vcell/solver/ode/gui/AbstractVisualizationPanel.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public LineIcon(Color color) {
2626
@Override
2727
public void paintIcon(Component c, Graphics g, int x, int y) {
2828
Graphics2D g2 = (Graphics2D)g;
29-
g2.setStroke(new BasicStroke(4.0f));
29+
g2.setStroke(new BasicStroke(6.0f));
3030
g2.setPaint(color);
3131
int midY = y + getIconHeight() / 2;
3232
g2.drawLine(x, midY, x + getIconWidth(), midY);
3333
}
3434
@Override
35-
public int getIconWidth() { return 50; }
35+
public int getIconWidth() { return 80; }
3636
@Override
3737
public int getIconHeight() {
3838
return 4; // more vertical room for a wider stroke

0 commit comments

Comments
 (0)