Skip to content

Commit ca84714

Browse files
committed
color management, legend panel
1 parent 1b3548d commit ca84714

File tree

3 files changed

+170
-72
lines changed

3 files changed

+170
-72
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
*/
5353
public class PlotPane extends JPanel {
5454

55-
class LineIcon implements Icon {
55+
public class LineIcon implements Icon {
5656
private Paint lineColor;
5757
private LineIcon(Paint paint) {
5858
lineColor = paint;

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

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
public class ClusterSpecificationPanel extends DocumentEditorSubPanel {
2626

27-
private enum DisplayMode { COUNTS, MEAN, OVERALL };
27+
public enum DisplayMode { COUNTS, MEAN, OVERALL };
2828
public static class ClusterSelection { // used to communicate y-list selection to the ClusterVisualizationPanel
2929
public final DisplayMode mode;
3030
public final java.util.List<ColumnDescription> columns;
@@ -65,36 +65,10 @@ public void propertyChange(PropertyChangeEvent evt) {
6565
public void valueChanged(ListSelectionEvent e) {
6666
if (e.getSource() == ClusterSpecificationPanel.this.getYAxisChoice() && !e.getValueIsAdjusting()) {
6767
// extract selected ColumnDescriptions
68-
java.util.List<ColumnDescription> selected = (java.util.List<ColumnDescription>) yAxisChoiceList.getSelectedValuesList();
69-
DisplayMode mode = null;
70-
JPanel content = displayOptionsCollapsiblePanel.getContentPanel();
71-
for (Component c : content.getComponents()) {
72-
if (c instanceof JRadioButton rb && rb.isSelected()) {
73-
switch (rb.getActionCommand()) {
74-
case "COUNTS":
75-
mode = DisplayMode.COUNTS;
76-
break;
77-
case "MEAN":
78-
mode = DisplayMode.MEAN;
79-
break;
80-
case "OVERALL":
81-
mode = DisplayMode.OVERALL;
82-
break;
83-
}
84-
}
85-
}
86-
ODESolverResultSet srs = null;
87-
switch (mode) {
88-
case COUNTS:
89-
srs = langevinSolverResultSet.getClusterCounts();
90-
break;
91-
case MEAN:
92-
srs = langevinSolverResultSet.getClusterMean();
93-
break;
94-
case OVERALL:
95-
srs = langevinSolverResultSet.getClusterOverall();
96-
break;
97-
}
68+
java.util.List<ColumnDescription> selected = yAxisChoiceList.getSelectedValuesList();
69+
DisplayMode mode = getCurrentDisplayMode();
70+
ODESolverResultSet srs = getResultSetForMode(mode);
71+
9872
// fire the event upward
9973
firePropertyChange("ClusterSelection", null, new ClusterSelection(mode, selected, srs));
10074
}
@@ -114,47 +88,30 @@ public void valueChanged(ListSelectionEvent e) {
11488
private JList yAxisChoiceList = null;
11589
private DefaultListModel<ColumnDescription> defaultListModelY = null;
11690

117-
11891
private void populateYAxisChoices(DisplayMode mode) {
119-
DefaultListModel model = getDefaultListModelY();
92+
DefaultListModel<ColumnDescription> model = getDefaultListModelY();
12093
model.clear();
12194
getYAxisChoice().setEnabled(false);
12295

123-
ODESolverResultSet srs = null;
124-
ColumnDescription[] cd = null;
125-
12696
updateYAxisLabel(mode);
12797

128-
if (langevinSolverResultSet == null) {
98+
ColumnDescription[] cds = getColumnDescriptionsForMode(mode);
99+
if (cds == null || cds.length <= 1) {
129100
return;
130101
}
131-
switch (mode) {
132-
case COUNTS:
133-
// we may never get non-trivial clusters if there's no binding reaction
134-
srs = langevinSolverResultSet.getClusterCounts();
135-
cd = srs.getColumnDescriptions();
136-
break;
137-
case MEAN:
138-
srs = langevinSolverResultSet.getClusterMean();
139-
cd = srs.getColumnDescriptions();
140-
break;
141-
case OVERALL:
142-
srs = langevinSolverResultSet.getClusterOverall();
143-
cd = srs.getColumnDescriptions();
144-
break;
145-
}
146102

147-
if(cd == null || cd.length == 1) {
148-
return;
149-
}
150-
for (ColumnDescription columnDescription : cd) {
151-
if(columnDescription.getName().equals("t")) {
152-
continue; // skip time column
103+
for (ColumnDescription cd : cds) {
104+
if (!"t".equals(cd.getName())) {
105+
model.addElement(cd);
153106
}
154-
model.addElement(columnDescription);
107+
}
108+
109+
if (!model.isEmpty()) {
155110
getYAxisChoice().setEnabled(true);
111+
getYAxisChoice().setSelectedIndex(0); // triggers valueChanged()
156112
}
157113
}
114+
158115
private void updateYAxisLabel(DisplayMode mode) {
159116
int count = yAxisCounts.getOrDefault(mode, 0);
160117
String text = "<html><b>" + YAxisLabelText + "</b><span style='color:#8B0000;'>(" + count + " entries)</span></html>";
@@ -350,5 +307,27 @@ private int countColumns(ODESolverResultSet srs) {
350307
if (cds == null) return 0;
351308
return cds.length > 1 ? cds.length-1 : 0; // subtract one for time column, but don't return negative if no column at all
352309
}
353-
310+
private ODESolverResultSet getResultSetForMode(DisplayMode mode) {
311+
if (langevinSolverResultSet == null) {
312+
return null;
313+
}
314+
return switch (mode) {
315+
case COUNTS -> langevinSolverResultSet.getClusterCounts();
316+
case MEAN -> langevinSolverResultSet.getClusterMean();
317+
case OVERALL-> langevinSolverResultSet.getClusterOverall();
318+
};
319+
}
320+
private ColumnDescription[] getColumnDescriptionsForMode(DisplayMode mode) {
321+
ODESolverResultSet srs = getResultSetForMode(mode);
322+
return (srs == null ? null : srs.getColumnDescriptions());
323+
}
324+
private DisplayMode getCurrentDisplayMode() {
325+
JPanel content = displayOptionsCollapsiblePanel.getContentPanel();
326+
for (Component c : content.getComponents()) {
327+
if (c instanceof JRadioButton rb && rb.isSelected()) {
328+
return DisplayMode.valueOf(rb.getActionCommand());
329+
}
330+
}
331+
return DisplayMode.COUNTS; // default fallback
332+
}
354333
}

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

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import cbit.vcell.client.data.ODEDataViewer;
44
import cbit.vcell.client.desktop.biomodel.DocumentEditorSubPanel;
5+
import cbit.vcell.parser.ExpressionException;
56
import cbit.vcell.util.ColumnDescription;
7+
import org.vcell.util.ColorUtil;
68
import org.vcell.util.gui.JToolBarToggleButton;
79
import org.vcell.util.gui.VCellIcons;
810

@@ -17,13 +19,21 @@
1719
import java.awt.event.ActionListener;
1820
import java.beans.PropertyChangeEvent;
1921
import java.beans.PropertyChangeListener;
22+
import java.util.*;
23+
import java.util.List;
2024

2125

2226
public class ClusterVisualizationPanel extends DocumentEditorSubPanel {
2327

2428
ODEDataViewer owner;
2529
IvjEventHandler ivjEventHandler = new IvjEventHandler();
2630

31+
private final Map<String, Color> persistentColorMap = new LinkedHashMap<>();
32+
private final java.util.List<Color> globalPalette = new ArrayList<>();
33+
private int nextColorIndex = 0;
34+
35+
36+
2737
private JPanel ivjJPanel1 = null;
2838
private JPanel ivjJPanelPlot = null;
2939
private JPanel ivjPlot2DPanel1 = null; // here
@@ -57,8 +67,13 @@ public void actionPerformed(ActionEvent e) {
5767
public void propertyChange(PropertyChangeEvent evt) {
5868
if (evt.getSource() == owner.getClusterSpecificationPanel() && "ClusterSelection".equals(evt.getPropertyName())) {
5969
ClusterSpecificationPanel.ClusterSelection sel = (ClusterSpecificationPanel.ClusterSelection) evt.getNewValue();
60-
updateLegend(sel); // update legend (one plot, multiple curves)
61-
redrawPlot(sel); // redraw plot (one plot, multiple curves)
70+
ensureColorsAssigned(sel.columns);
71+
try {
72+
redrawPlot(sel); // redraw plot (one plot, multiple curves)
73+
} catch (ExpressionException e) {
74+
throw new RuntimeException(e);
75+
}
76+
redrawLegend(sel); // update legend (one plot, multiple curves)
6277
updateDataTable(sel); // update data table
6378
return;
6479
}
@@ -90,7 +105,8 @@ private void initialize() {
90105
add(getJPanel1(), "Center");
91106
add(getBottomRightPanel(), "South");
92107
add(getJPanelLegend(), "East");
93-
initConnectionsRight();
108+
setBackground(Color.white);
109+
initConnections();
94110
}
95111

96112
private JPanel getJPanel1() {
@@ -231,11 +247,14 @@ private JPanel getJPanelLegend() {
231247
ivjJPanelLegend.setName("JPanelLegend");
232248
ivjJPanelLegend.setLayout(new BorderLayout());
233249
getJPanelLegend().add(new JLabel(" "), "South");
234-
getJPanelLegend().add(new JLabel("Plot Legend:"), "North");
250+
JLabel labelLegendTitle = new JLabel("Plot Legend:");
251+
labelLegendTitle.setBorder(new EmptyBorder(10, 4, 10, 4));
252+
getJPanelLegend().add(labelLegendTitle, "North");
235253
getJPanelLegend().add(getPlotLegendsScrollPane(), "Center");
236254
}
237255
return ivjJPanelLegend;
238256
}
257+
239258
private JScrollPane getPlotLegendsScrollPane() {
240259
if (ivjPlotLegendsScrollPane == null) {
241260
ivjPlotLegendsScrollPane = new JScrollPane();
@@ -256,8 +275,22 @@ private JPanel getJPanelPlotLegends() {
256275
return ivjJPanelPlotLegends;
257276
}
258277

278+
public void setBackground(Color color) {
279+
super.setBackground(color);
280+
getBottomRightPanel().setBackground(color);
281+
getJBottomLabel().setBackground(color);
282+
getJPanelLegend().setBackground(color);
283+
getJPanelPlotLegends().setBackground(color);
284+
getJPanel1().setBackground(color);
285+
getPlot2DPanel1().setBackground(color);
286+
getPlot2DDataPanel1().setBackground(color);
287+
getJPanelData().setBackground(color);
288+
getJPanelPlot().setBackground(color);
289+
290+
}
259291

260-
private void initConnectionsRight() {
292+
private void initConnections() {
293+
initializeGlobalPalette(); // get a stable, high contrast palette
261294
// group the two buttons so only one stays selected
262295
ButtonGroup bg = new ButtonGroup();
263296
bg.add(getPlotButton());
@@ -290,15 +323,101 @@ public void refreshData() {
290323

291324
// ---------------------------------------------------------------------
292325

293-
private void updateLegend(ClusterSpecificationPanel.ClusterSelection sel) {
294-
System.out.println("ClusterVisualizationPanel.updateLegend() called");
326+
private void initializeGlobalPalette() {
327+
// Use a curated palette from ColorUtil
328+
globalPalette.clear();
329+
globalPalette.addAll(Arrays.asList(ColorUtil.TABLEAU20));
330+
}
331+
private void ensureColorsAssigned(List<ColumnDescription> columns) {
332+
// assign colors only when needed, and keep them consistent across updates
333+
for (ColumnDescription cd : columns) {
334+
String name = cd.getName();
335+
if (!persistentColorMap.containsKey(name)) {
336+
Color c = globalPalette.get(nextColorIndex % globalPalette.size());
337+
persistentColorMap.put(name, c);
338+
nextColorIndex++;
339+
}
340+
}
341+
}
342+
private JComponent createLegendEntry(String name, Color color, ClusterSpecificationPanel.DisplayMode mode) {
343+
JPanel p = new JPanel();
344+
p.setName("JPanelClusterColorLegends");
345+
BoxLayout bl = new BoxLayout(p, BoxLayout.Y_AXIS);
346+
p.setLayout(bl);
347+
p.setBounds(0, 0, 72, 360);
348+
p.setOpaque(false);
349+
350+
String unitSymbol = "";
351+
if(ClusterSpecificationPanel.DisplayMode.COUNTS == mode) {
352+
unitSymbol = "molecules";
353+
}
354+
String shortLabel = "<html>" + name + "<font color=\"#8B0000\">" + " [" + unitSymbol + "] " + "</font></html>";
355+
356+
JLabel line = new JLabel(new LineIcon(color));
357+
JLabel text = new JLabel(shortLabel);
358+
line.setBorder(new EmptyBorder(6,0,1,0));
359+
text.setBorder(new EmptyBorder(1,8,6,0));
360+
p.add(line);
361+
p.add(text);
362+
363+
return p;
364+
}
365+
public class LineIcon implements Icon {
366+
private final Color color;
367+
public LineIcon(Color color) {
368+
this.color = color;
369+
}
370+
@Override
371+
public void paintIcon(Component c, Graphics g, int x, int y) {
372+
Graphics2D g2 = (Graphics2D)g;
373+
g2.setStroke(new BasicStroke(3.0f));
374+
g2.setPaint(color);
375+
int midY = y + getIconHeight() / 2;
376+
g2.drawLine(x, midY, x + getIconWidth(), midY);
377+
}
378+
@Override
379+
public int getIconWidth() { return 50; }
380+
@Override
381+
public int getIconHeight() {
382+
return 4; // more vertical room for a wider stroke
383+
}
295384
}
296-
private void redrawPlot(ClusterSpecificationPanel.ClusterSelection sel) {
385+
public static String getUnitSymbol(ClusterSpecificationPanel.ClusterSelection sel) {
386+
if(ClusterSpecificationPanel.DisplayMode.COUNTS == sel.mode) {
387+
388+
389+
}
390+
return "";
391+
}
392+
393+
private void redrawPlot(ClusterSpecificationPanel.ClusterSelection sel) throws ExpressionException {
297394
System.out.println("ClusterVisualizationPanel.redrawPlot() called");
298-
java.util.List<ColumnDescription> columnDescriptions = sel.columns;
299-
for(ColumnDescription cd : columnDescriptions) {
300-
System.out.println(" column name: '" + cd.getName() + "'");
395+
// java.util.List<ColumnDescription> columnDescriptions = sel.columns;
396+
// for(ColumnDescription cd : columnDescriptions) {
397+
// System.out.println(" column name: '" + cd.getName() + "'");
398+
// }
399+
// getPlot2DPanel1().removeAllPlots();
400+
//
401+
// int index = sel.resultSet.findColumn("t");
402+
// double[] t = sel.resultSet.extractColumn(index);
403+
//
404+
// for (ColumnDescription cd : sel.columns) {
405+
// index = sel.resultSet.findColumn(cd.getName());
406+
// double[] y = sel.resultSet.extractColumn(index);
407+
// Color c = persistentColorMap.get(cd.getName());
408+
// getPlot2DPanel1().addLinePlot(cd.getName(), c, t, y);
409+
// }
410+
// getPlot2DPanel1().repaint();
411+
}
412+
private void redrawLegend(ClusterSpecificationPanel.ClusterSelection sel) {
413+
System.out.println("ClusterVisualizationPanel.updateLegend() called");
414+
getJPanelPlotLegends().removeAll();
415+
for (ColumnDescription cd : sel.columns) {
416+
Color c = persistentColorMap.get(cd.getName());
417+
getJPanelPlotLegends().add(createLegendEntry(cd.getName(), c, sel.mode));
301418
}
419+
getJPanelPlotLegends().revalidate();
420+
getJPanelPlotLegends().repaint();
302421
}
303422
private void updateDataTable(ClusterSpecificationPanel.ClusterSelection sel) {
304423
System.out.println("ClusterVisualizationPanel.updateDataTable() called");

0 commit comments

Comments
 (0)