Skip to content

Commit 99514df

Browse files
authored
Merge pull request #1661 from virtualcell/dan-ss-display
Springsalad batch run results visualization
2 parents 3116a1b + eb6529d commit 99514df

23 files changed

Lines changed: 4931 additions & 152 deletions

vcell-cli/src/main/java/org/vcell/cli/run/plotting/Results2DLinePlot.java

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
import com.lowagie.text.DocumentException;
66

77
import org.jfree.chart.ChartFactory;
8+
import org.jfree.chart.ChartPanel;
89
import org.jfree.chart.JFreeChart;
10+
import org.jfree.chart.block.BlockBorder;
911
import org.jfree.chart.labels.StandardXYItemLabelGenerator;
12+
import org.jfree.chart.plot.PlotOrientation;
1013
import org.jfree.chart.plot.XYPlot;
14+
import org.jfree.chart.renderer.xy.XYDifferenceRenderer;
15+
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
16+
import org.jfree.chart.ui.RectangleEdge;
1117
import org.jfree.data.xy.XYDataset;
1218
import org.jfree.data.xy.XYSeries;
1319
import org.jfree.data.xy.XYSeriesCollection;
@@ -16,7 +22,9 @@
1622
import org.apache.logging.log4j.Logger;
1723

1824
import javax.imageio.ImageIO;
25+
import javax.swing.*;
1926
import java.awt.*;
27+
import java.awt.geom.Path2D;
2028
import java.awt.image.BufferedImage;
2129
import java.awt.print.PageFormat;
2230
import java.awt.print.Paper;
@@ -319,4 +327,246 @@ private static PageFormat generateAlternatePageFormat(){
319327
pageFormat.setOrientation(PageFormat.LANDSCAPE);
320328
return pageFormat;
321329
}
330+
331+
332+
public static void main(String[] args) {
333+
SwingUtilities.invokeLater(() -> {
334+
335+
Random rand = new Random();
336+
337+
int n = 50;
338+
double xMin = 0.0;
339+
double xMax = 7.0;
340+
double dx = (xMax - xMin) / (n - 1);
341+
342+
double xLower = 0.0; // these are the limits for the x values of the axis
343+
double xUpper = 7.5;
344+
345+
Color sinColor = new Color(31, 119, 180); // blue
346+
Color tanColor = new Color(255, 127, 14); // orange
347+
348+
// --- SIN series ---
349+
XYSeries sinMin = new XYSeries("sin-min");
350+
XYSeries sinMax = new XYSeries("sin-max");
351+
XYSeries sinMain = new XYSeries("sin");
352+
XYSeries sinStd = new XYSeries("sin-std");
353+
354+
for (int i = 0; i < n; i++) {
355+
double x = xMin + i * dx;
356+
double y = Math.sin(x);
357+
358+
double delta = 0.2 + rand.nextDouble() * 0.2;
359+
double yMin = y - delta;
360+
double yMax = y + delta;
361+
362+
double std = 0.08 + rand.nextDouble() * 0.08;
363+
364+
sinMin.add(x, yMin);
365+
sinMax.add(x, yMax);
366+
sinMain.add(x, y);
367+
sinStd.add(x, y + std);
368+
}
369+
370+
// --- TAN series ---
371+
XYSeries tanMin = new XYSeries("tan-min");
372+
XYSeries tanMax = new XYSeries("tan-max");
373+
XYSeries tanMain = new XYSeries("tan");
374+
XYSeries tanStd = new XYSeries("tan-std");
375+
376+
for (int i = 0; i < n; i++) {
377+
double x = xMin + i * dx;
378+
double y = Math.tan(x);
379+
380+
if (y > 3) y = 3;
381+
if (y < -3) y = -3;
382+
383+
double delta = 0.3 + rand.nextDouble() * 0.3;
384+
double yMin = y - delta;
385+
double yMax = y + delta;
386+
387+
double std = 0.2 + rand.nextDouble() * 0.2;
388+
389+
tanMin.add(x, yMin);
390+
tanMax.add(x, yMax);
391+
tanMain.add(x, y);
392+
tanStd.add(x, y + std);
393+
}
394+
395+
double globalMin = Double.POSITIVE_INFINITY;
396+
double globalMax = Double.NEGATIVE_INFINITY;
397+
for (int i = 0; i < n; i++) {
398+
globalMin = Math.min(globalMin, sinMin.getY(i).doubleValue());
399+
globalMin = Math.min(globalMin, tanMin.getY(i).doubleValue());
400+
globalMax = Math.max(globalMax, sinMax.getY(i).doubleValue());
401+
globalMax = Math.max(globalMax, tanMax.getY(i).doubleValue());
402+
}
403+
// Add padding
404+
double pad = 0.1 * (globalMax - globalMin);
405+
globalMin -= pad;
406+
globalMax += pad;
407+
408+
// --- Datasets ---
409+
410+
// Dataset 0: sin min/max (for band)
411+
XYSeriesCollection sinMinMaxDataset = new XYSeriesCollection();
412+
sinMinMaxDataset.addSeries(sinMax); // upper
413+
sinMinMaxDataset.addSeries(sinMin); // lower
414+
415+
// Dataset 1: tan min/max (for band)
416+
XYSeriesCollection tanMinMaxDataset = new XYSeriesCollection();
417+
tanMinMaxDataset.addSeries(tanMax); // upper
418+
tanMinMaxDataset.addSeries(tanMin); // lower
419+
420+
// Dataset 2: main curves
421+
XYSeriesCollection mainDataset = new XYSeriesCollection();
422+
mainDataset.addSeries(sinMain);
423+
mainDataset.addSeries(tanMain);
424+
425+
// Dataset 3: std diamonds
426+
XYSeriesCollection stdDataset = new XYSeriesCollection();
427+
stdDataset.addSeries(sinStd);
428+
stdDataset.addSeries(tanStd);
429+
430+
// --- Chart skeleton ---
431+
JFreeChart chart = ChartFactory.createXYLineChart(
432+
"Min/Max Bands + STD Demo",
433+
"x",
434+
"y",
435+
null,
436+
PlotOrientation.VERTICAL,
437+
true,
438+
true,
439+
false
440+
);
441+
XYPlot plot = chart.getXYPlot();
442+
443+
// Transparent backgrounds
444+
chart.setBackgroundPaint(Color.WHITE);
445+
plot.setBackgroundPaint(Color.WHITE);
446+
plot.setOutlinePaint(null);
447+
plot.setDomainGridlinePaint(new Color(180, 180, 180)); // very light
448+
plot.setRangeGridlinePaint(new Color(180, 180, 180));
449+
450+
plot.getDomainAxis().setAutoRange(false); // lock the axis so that they never resize
451+
plot.getRangeAxis().setAutoRange(false);
452+
plot.getDomainAxis().setRange(xLower, xUpper);
453+
plot.getRangeAxis().setRange(globalMin, globalMax);
454+
455+
// --- Legend to the right
456+
chart.getLegend().setPosition(RectangleEdge.RIGHT);
457+
chart.getLegend().setBackgroundPaint(Color.WHITE);
458+
// chart.getLegend().setFrame(BlockBorder.NONE);
459+
460+
// --- Renderer 0: sin band ---
461+
XYDifferenceRenderer sinBandRenderer = new XYDifferenceRenderer();
462+
Color sinBandColor = new Color(sinColor.getRed(), sinColor.getGreen(), sinColor.getBlue(), 40);
463+
sinBandRenderer.setPositivePaint(sinBandColor);
464+
sinBandRenderer.setNegativePaint(sinBandColor);
465+
sinBandRenderer.setSeriesStroke(0, new BasicStroke(0f));
466+
sinBandRenderer.setSeriesStroke(1, new BasicStroke(0f));
467+
// sinBandRenderer.setOutlinePaint(null);
468+
sinBandRenderer.setSeriesVisibleInLegend(0, false); // hide sin-max legend entries
469+
sinBandRenderer.setSeriesVisibleInLegend(1, false);
470+
471+
plot.setDataset(0, sinMinMaxDataset);
472+
plot.setRenderer(0, sinBandRenderer);
473+
474+
// --- Renderer 1: tan band ---
475+
XYDifferenceRenderer tanBandRenderer = new XYDifferenceRenderer();
476+
Color tanBandColor = new Color(tanColor.getRed(), tanColor.getGreen(), tanColor.getBlue(), 40);
477+
tanBandRenderer.setPositivePaint(tanBandColor);
478+
tanBandRenderer.setNegativePaint(tanBandColor);
479+
tanBandRenderer.setSeriesStroke(0, new BasicStroke(0f));
480+
tanBandRenderer.setSeriesStroke(1, new BasicStroke(0f));
481+
// tanBandRenderer.setOutlinePaint(null);
482+
tanBandRenderer.setSeriesVisibleInLegend(0, false);
483+
tanBandRenderer.setSeriesVisibleInLegend(1, false);
484+
485+
plot.setDataset(1, tanMinMaxDataset);
486+
plot.setRenderer(1, tanBandRenderer);
487+
488+
// --- Renderer 2: main curves ---
489+
XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, false);
490+
lineRenderer.setSeriesPaint(0, sinColor);
491+
lineRenderer.setSeriesPaint(1, tanColor);
492+
lineRenderer.setSeriesStroke(0, new BasicStroke(2f));
493+
lineRenderer.setSeriesStroke(1, new BasicStroke(2f));
494+
lineRenderer.setSeriesVisibleInLegend(0, true); // keep the main curves visible in Legend
495+
lineRenderer.setSeriesVisibleInLegend(1, true);
496+
497+
plot.setDataset(2, mainDataset);
498+
plot.setRenderer(2, lineRenderer);
499+
500+
// --- Renderer 3: STD diamonds ---
501+
XYLineAndShapeRenderer stdRenderer = new XYLineAndShapeRenderer(false, true);
502+
Shape diamond = createDiamondShape(4);
503+
504+
stdRenderer.setSeriesShape(0, diamond);
505+
stdRenderer.setSeriesShape(1, diamond);
506+
stdRenderer.setSeriesPaint(0, sinColor.darker());
507+
stdRenderer.setSeriesPaint(1, tanColor.darker());
508+
stdRenderer.setSeriesVisibleInLegend(0, false);
509+
stdRenderer.setSeriesVisibleInLegend(1, false);
510+
511+
plot.setDataset(3, stdDataset);
512+
plot.setRenderer(3, stdRenderer);
513+
514+
// --- ChartPanel ---
515+
ChartPanel panel = new ChartPanel(chart);
516+
panel.setOpaque(true); // must be opaque for white to show
517+
panel.setBackground(Color.WHITE);
518+
519+
// --- checkboxes to control display
520+
JPanel controls = new JPanel();
521+
JCheckBox cbAvg = new JCheckBox("Averages", true);
522+
JCheckBox cbMinMax = new JCheckBox("Min-Max", false);
523+
JCheckBox cbStd = new JCheckBox("STD", false);
524+
controls.add(cbAvg);
525+
controls.add(cbMinMax);
526+
controls.add(cbStd);
527+
528+
// Averages (dataset 2)
529+
cbAvg.addActionListener(e -> {
530+
boolean on = cbAvg.isSelected();
531+
plot.getRenderer(2).setSeriesVisible(0, on);
532+
plot.getRenderer(2).setSeriesVisible(1, on);
533+
lineRenderer.setSeriesVisibleInLegend(0, true); // force legend visible
534+
lineRenderer.setSeriesVisibleInLegend(1, true);
535+
});
536+
537+
// Min-Max (datasets 0 and 1)
538+
cbMinMax.addActionListener(e -> {
539+
boolean on = cbMinMax.isSelected();
540+
plot.setRenderer(0, on ? sinBandRenderer : null);
541+
plot.setRenderer(1, on ? tanBandRenderer : null);
542+
});
543+
544+
// STD (dataset 3)
545+
cbStd.addActionListener(e -> {
546+
boolean on = cbStd.isSelected();
547+
stdRenderer.setSeriesVisible(0, on);
548+
stdRenderer.setSeriesVisible(1, on);
549+
});
550+
551+
// --- Frame ---
552+
JFrame frame = new JFrame("JFreeChart Min/Max/STD Demo");
553+
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
554+
frame.add(panel, BorderLayout.CENTER);
555+
frame.add(controls, BorderLayout.SOUTH);
556+
frame.setSize(900, 600);
557+
frame.setLocationRelativeTo(null);
558+
frame.setVisible(true);
559+
});
560+
}
561+
562+
private static Shape createDiamondShape(int size) {
563+
Path2D.Double p = new Path2D.Double();
564+
p.moveTo(0, -size);
565+
p.lineTo(size, 0);
566+
p.lineTo(0, size);
567+
p.lineTo(-size, 0);
568+
p.closePath();
569+
return p;
570+
}
571+
322572
}

0 commit comments

Comments
 (0)