test result dialog data is able to show the data as a graph.

This commit is contained in:
hneemann 2017-07-03 15:36:05 +02:00
parent 2d25909f9e
commit 4cb80b3918
10 changed files with 172 additions and 83 deletions

View File

@ -5,6 +5,8 @@ import de.neemann.digital.draw.graphics.Orientation;
import de.neemann.digital.draw.graphics.Style;
import de.neemann.digital.draw.graphics.Vector;
import de.neemann.digital.draw.shapes.Drawable;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
/**
* The dataSet stores the collected DataSamples.
@ -16,6 +18,7 @@ public class DataPlotter implements Drawable {
private final ValueTable data;
private final int maxTextLength;
private double size = SIZE;
private Sync modelSync = NoSync.INST;
/**
* Creates a new instance
@ -44,7 +47,7 @@ public class DataPlotter implements Drawable {
* @param width width of the frame
*/
public void fitInside(int width) {
size = ((double) (width - getTextBorder())) / data.getRows();
modelSync.access(() -> size = ((double) (width - getTextBorder())) / data.getRows());
}
/**
@ -63,43 +66,45 @@ public class DataPlotter implements Drawable {
@Override
public void drawTo(Graphic g, Style highLight) {
int x = getTextBorder();
modelSync.access(() -> {
int x = getTextBorder();
int yOffs = SIZE / 2;
int y = BORDER;
int signals = data.getColumns();
for (int i = 0; i < signals; i++) {
String text = data.getColumnName(i);
g.drawText(new Vector(x - 2, y + yOffs), new Vector(x + 1, y + yOffs), text, Orientation.RIGHTCENTER, Style.NORMAL);
g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH);
y += SIZE + SEP;
}
g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH);
int[] lastRy = new int[signals];
boolean first = true;
double pos = 0;
for (Value[] s : data) {
int xx = (int) (pos + x);
g.drawLine(new Vector(xx, BORDER - SEP2), new Vector(xx, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH);
y = BORDER;
int yOffs = SIZE / 2;
int y = BORDER;
int signals = data.getColumns();
for (int i = 0; i < signals; i++) {
long width = data.getMax(i);
if (width == 0) width = 1;
int ry = (int) (SIZE - (SIZE * s[i].getValue()) / width);
g.drawLine(new Vector(xx, y + ry), new Vector((int) (xx + size), y + ry), Style.NORMAL);
if (!first && ry != lastRy[i])
g.drawLine(new Vector(xx, y + lastRy[i]), new Vector(xx, y + ry), Style.NORMAL);
lastRy[i] = ry;
String text = data.getColumnName(i);
g.drawText(new Vector(x - 2, y + yOffs), new Vector(x + 1, y + yOffs), text, Orientation.RIGHTCENTER, Style.NORMAL);
g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH);
y += SIZE + SEP;
}
first = false;
pos += size;
}
g.drawLine(new Vector(x, BORDER - SEP2), new Vector(x, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH);
g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH);
int[] lastRy = new int[signals];
boolean first = true;
double pos = 0;
for (Value[] s : data) {
int xx = (int) (pos + x);
g.drawLine(new Vector(xx, BORDER - SEP2), new Vector(xx, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH);
y = BORDER;
for (int i = 0; i < signals; i++) {
long width = data.getMax(i);
if (width == 0) width = 1;
int ry = (int) (SIZE - (SIZE * s[i].getValue()) / width);
g.drawLine(new Vector(xx, y + ry), new Vector((int) (xx + size), y + ry), Style.NORMAL);
if (!first && ry != lastRy[i])
g.drawLine(new Vector(xx, y + lastRy[i]), new Vector(xx, y + ry), Style.NORMAL);
lastRy[i] = ry;
y += SIZE + SEP;
}
first = false;
pos += size;
}
g.drawLine(new Vector(x, BORDER - SEP2), new Vector(x, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH);
});
}
private int getTextBorder() {
@ -117,7 +122,24 @@ public class DataPlotter implements Drawable {
* @return the current width of the graphical representation
*/
public int getCurrentGraphicWidth() {
return getTextBorder() + (int) (data.getRows() * size);
return modelSync.access(new Runnable() {
private int r;
@Override
public void run() {
r = DataPlotter.this.getTextBorder() + (int) (data.getRows() * size);
}
}).r;
}
/**
* Sets lock to access the data
*
* @param modelSync the lock
* @return this for chained calls
*/
public DataPlotter setModelSync(Sync modelSync) {
this.modelSync = modelSync;
return this;
}
}

View File

@ -1003,9 +1003,9 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS
windowPosManager.register("probe", new ProbeDialog(this, model, updateEvent, ordering, modelSync)).setVisible(true);
if (settings.get(Keys.SHOW_DATA_GRAPH))
windowPosManager.register("dataSet", new DataSetDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering, modelSync)).setVisible(true);
windowPosManager.register("dataSet", DataSetDialog.createLiveDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering, modelSync)).setVisible(true);
if (settings.get(Keys.SHOW_DATA_GRAPH_MICRO))
windowPosManager.register("dataSetMicro", new DataSetDialog(this, model, true, ordering, modelSync)).setVisible(true);
windowPosManager.register("dataSetMicro", DataSetDialog.createLiveDialog(this, model, true, ordering, modelSync)).setVisible(true);
if (modelModifier != null)
modelModifier.preInit(model);

View File

@ -3,6 +3,7 @@ package de.neemann.digital.gui.components.data;
import de.neemann.digital.data.DataPlotter;
import de.neemann.digital.data.ValueTable;
import de.neemann.digital.draw.graphics.GraphicSwing;
import de.neemann.digital.gui.sync.Sync;
import javax.swing.*;
import java.awt.*;
@ -15,15 +16,20 @@ import java.awt.*;
*/
public class DataSetComponent extends JComponent {
private final DataPlotter plotter;
/**
* The data stored in the plotter needs to be seen as part of the model.
* So a lock is necessary to access the data.
*/
private JScrollPane scrollPane;
/**
* Creates a new dataSet
*
* @param dataSet the dataSet to paint
* @param dataSet the dataSet to paint
* @param modelSync lock to access the model
*/
public DataSetComponent(ValueTable dataSet) {
plotter = new DataPlotter(dataSet);
public DataSetComponent(ValueTable dataSet, Sync modelSync) {
plotter = new DataPlotter(dataSet).setModelSync(modelSync);
addMouseWheelListener(e -> {
double f = Math.pow(0.9, e.getWheelRotation());
scale(f, e.getX());
@ -55,8 +61,8 @@ public class DataSetComponent extends JComponent {
public void scale(double f, int xPos) {
revalidate();
repaint();
f=plotter.scale(f);
f = plotter.scale(f);
// keep relative mouse position
int x = (int) (xPos * f) - (xPos - (int) scrollPane.getViewport().getViewRect().getX());
if (x < 0) x = 0;
scrollPane.getViewport().setViewPosition(new Point(x, 0));

View File

@ -7,6 +7,7 @@ import de.neemann.digital.core.Signal;
import de.neemann.digital.data.ValueTable;
import de.neemann.digital.gui.SaveAsHelper;
import de.neemann.digital.gui.components.OrderMerger;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import de.neemann.gui.IconCreator;
@ -32,27 +33,29 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
private final DataSetComponent dsc;
private final JScrollPane scrollPane;
private final Sync modelSync;
private ValueTable logData;
private DataSetObserver dataSetObserver;
private static final Icon ICON_EXPAND = IconCreator.create("View-zoom-fit.png");
private static final Icon ICON_ZOOM_IN = IconCreator.create("View-zoom-in.png");
private static final Icon ICON_ZOOM_OUT = IconCreator.create("View-zoom-out.png");
/**
* Creates a new instance
* Creates a instance prepared for "live logging"
*
* @param owner the parent frame
* @param model the model used to collect the data
* @param microStep true the event type which triggers a new DataSample
* @param ordering the ordering of the measurement values
* @param modelSync used to access the running model
* @param model the model
* @param microStep stepping mode
* @param ordering the ordering to use
* @param modelSync the lock to access the model
* @return the created instance
*/
public DataSetDialog(Frame owner, Model model, boolean microStep, List<String> ordering, Sync modelSync) {
super(owner, createTitle(microStep), false);
this.modelSync = modelSync;
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setAlwaysOnTop(true);
public static DataSetDialog createLiveDialog(Frame owner, Model model, boolean microStep, List<String> ordering, Sync modelSync) {
String title;
if (microStep)
title = Lang.get("win_measures_microstep");
else
title = Lang.get("win_measures_fullstep");
ArrayList<Signal> signals = model.getSignalsCopy();
new OrderMerger<String, Signal>(ordering) {
@ -62,11 +65,40 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
}
}.order(signals);
DataSetObserver dataSetObserver = new DataSetObserver(microStep, signals, MAX_SAMPLE_SIZE);
ValueTable logData = dataSetObserver.getLogData();
dataSetObserver = new DataSetObserver(microStep, signals, MAX_SAMPLE_SIZE);
logData = dataSetObserver.getLogData();
return new DataSetDialog(owner, title, model, logData, dataSetObserver, modelSync);
}
dsc = new DataSetComponent(logData);
/**
* Creates a new instance
*
* @param owner the parent frame
* @param title the frame title
* @param logData the data to visualize
*/
public DataSetDialog(Frame owner, String title, ValueTable logData) {
this(owner, title, null, logData, null, NoSync.INST);
}
/**
* Creates a new instance
*
* @param owner the parent frame
* @param title the frame title
* @param model the model used to collect the data
* @param logData the data to visualize
* @param modelSync used to access the running model
*/
private DataSetDialog(Frame owner, String title, Model model, ValueTable logData, DataSetObserver dataSetObserver, Sync modelSync) {
super(owner, title, false);
this.dataSetObserver = dataSetObserver;
this.modelSync = modelSync;
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setAlwaysOnTop(true);
dsc = new DataSetComponent(logData, modelSync);
scrollPane = new JScrollPane(dsc);
getContentPane().add(scrollPane);
dsc.setScrollPane(scrollPane);
@ -100,17 +132,18 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
getContentPane().add(toolBar, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
modelSync.access(() -> model.addObserver(DataSetDialog.this));
}
if (model != null)
addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
modelSync.access(() -> model.addObserver(DataSetDialog.this));
}
@Override
public void windowClosed(WindowEvent e) {
modelSync.access(() -> model.removeObserver(DataSetDialog.this));
}
});
@Override
public void windowClosed(WindowEvent e) {
modelSync.access(() -> model.removeObserver(DataSetDialog.this));
}
});
scrollPane.getViewport().setPreferredSize(dsc.getPreferredSize());
@ -123,7 +156,7 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
JFileChooser fileChooser = new MyFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("Comma Separated Values", "csv"));
new SaveAsHelper(DataSetDialog.this, fileChooser, "csv")
.checkOverwrite(file -> logData.saveCSV(file));
.checkOverwrite(logData::saveCSV);
}
}.setToolTip(Lang.get("menu_saveData_tt")).createJMenuItem());
@ -138,14 +171,6 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
setLocationRelativeTo(owner);
}
private static String createTitle(boolean microStep) {
if (microStep)
return Lang.get("win_measures_microstep");
else
return Lang.get("win_measures_fullstep");
}
@Override
public void handleEvent(ModelEvent event) {
modelSync.access(() -> {

View File

@ -2,6 +2,7 @@ package de.neemann.digital.gui.components.testing;
import de.neemann.digital.core.Model;
import de.neemann.digital.core.NodeException;
import de.neemann.digital.data.ValueTable;
import de.neemann.digital.data.ValueTableModel;
import de.neemann.digital.data.Value;
import de.neemann.digital.draw.elements.Circuit;
@ -9,15 +10,18 @@ import de.neemann.digital.draw.elements.PinException;
import de.neemann.digital.draw.library.ElementLibrary;
import de.neemann.digital.draw.library.ElementNotFoundException;
import de.neemann.digital.draw.model.ModelCreator;
import de.neemann.digital.gui.components.data.DataSetDialog;
import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.*;
import de.neemann.gui.ErrorMessage;
import de.neemann.gui.IconCreator;
import de.neemann.gui.LineBreaker;
import de.neemann.gui.ToolTipAction;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
@ -32,6 +36,8 @@ public class TestResultDialog extends JDialog {
private static final Icon ICON_FAILED = IconCreator.create("testFailed.png");
private static final Icon ICON_PASSED = IconCreator.create("testPassed.png");
private final ArrayList<ValueTable> resultTableData;
/**
* Creates a new result dialog.
*
@ -50,6 +56,8 @@ public class TestResultDialog extends JDialog {
Collections.sort(tsl);
resultTableData = new ArrayList<>();
JTabbedPane tp = new JTabbedPane();
int i = 0;
int errorTabIndex = -1;
@ -65,8 +73,9 @@ public class TestResultDialog extends JDialog {
table.setDefaultRenderer(Value.class, new ValueRenderer());
table.setDefaultRenderer(Integer.class, new NumberRenderer());
final Font font = table.getFont();
table.getColumnModel().getColumn(0).setMaxWidth(font.getSize()*4);
table.getColumnModel().getColumn(0).setMaxWidth(font.getSize() * 4);
table.setRowHeight(font.getSize() * 6 / 5);
resultTableData.add(testExecuter.getResult());
String tabName;
Icon tabIcon;
@ -89,6 +98,21 @@ public class TestResultDialog extends JDialog {
if (errorTabIndex >= 0)
tp.setSelectedIndex(errorTabIndex);
JMenuBar bar = new JMenuBar();
JMenu view = new JMenu(Lang.get("menu_view"));
ToolTipAction asGraph = new ToolTipAction(Lang.get("menu_showDataAsGraph")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
int tab = tp.getSelectedIndex();
if (tab < 0) tab = 0;
new DataSetDialog(owner, Lang.get("win_testdata_N", tp.getTitleAt(tab)), resultTableData.get(tab)).setVisible(true);
}
}.setToolTip(Lang.get("menu_showDataAsGraph_tt"));
view.add(asGraph.createJMenuItem());
bar.add(view);
setJMenuBar(bar);
getContentPane().add(tp);
pack();
setLocationRelativeTo(owner);

View File

@ -20,20 +20,22 @@ public class LockSync implements Sync {
}
@Override
public void access(Runnable run) {
public <A extends Runnable> A access(A run) {
lock.lock();
try {
run.run();
return run;
} finally {
lock.unlock();
}
}
@Override
public void accessNEx(Sync.ModelRun run) throws NodeException {
public <A extends Sync.ModelRun> A accessNEx(A run) throws NodeException {
lock.lock();
try {
run.run();
return run;
} finally {
lock.unlock();
}

View File

@ -18,12 +18,14 @@ public final class NoSync implements Sync {
}
@Override
public void access(Runnable run) {
public <A extends Runnable> A access(A run) {
run.run();
return run;
}
@Override
public void accessNEx(Sync.ModelRun run) throws NodeException {
public <A extends Sync.ModelRun> A accessNEx(A run) throws NodeException {
run.run();
return run;
}
}

View File

@ -13,16 +13,20 @@ public interface Sync {
* Calls the given runnable
*
* @param run the runnable to execute
* @param <A> the type oth the runnable
* @return the given runnable. Used for chained calls
*/
void access(Runnable run);
<A extends Runnable> A access(A run);
/**
* Same as access, but catches an exception
*
* @param run the runnable to execute
* @param <A> the type oth the runnable
* @return the given runnable. Used for chained calls
* @throws NodeException NodeException
*/
void accessNEx(ModelRun run) throws NodeException;
<A extends Sync.ModelRun> A accessNEx(A run) throws NodeException;
/**
* Like runnable but throws an exception

View File

@ -863,7 +863,7 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="menu_undo_tt">Letzte Aktion rückgängig machen</string>
<string name="menu_redo">Wiederherstellen</string>
<string name="menu_redo_tt">Letzte rückgängig gemachte Aktion wieder herstellen.</string>
<string name="menu_showDataAsGraph">Als Graph anzeigen.</string>
<string name="menu_showDataAsGraph">Zeige Graph</string>
<string name="menu_showDataAsGraph_tt">Zeigt die Daten als Graph an.</string>
<string name="message">Digital
@ -947,6 +947,7 @@ Die Icons stammen aus dem Tango Desktop Project.</string>
<string name="tt_moveItemDown">Eintrag nach unten schieben</string>
<string name="tt_moveItemUp">Eintrag nach oben schieben</string>
<string name="win_allSolutions">Alle möglichen Lösungen</string>
<string name="win_testdata_N">Testdaten {0}</string>
<string name="win_confirmExit">Beenden bestätigen!</string>
<string name="win_measures">Messwerte</string>
<string name="win_measures_fullstep">Messwerte im Vollschrittmodus</string>

View File

@ -851,6 +851,8 @@ The names of the variables may not be unique.</string>
<string name="menu_undo_tt">Revert last modification</string>
<string name="menu_redo">Redo</string>
<string name="menu_redo_tt">Apply last reverted modification again.</string>
<string name="menu_showDataAsGraph">Show graph</string>
<string name="menu_showDataAsGraph_tt">Show the data as a Graph.</string>
<string name="message">Digital
@ -941,6 +943,7 @@ The icons are taken from the Tango Desktop Project.</string>
<string name="win_table">Table</string>
<string name="win_table_exportDialog">Export</string>
<string name="win_itempicker_title">Select</string>
<string name="win_testdata_N">Testdata {0}</string>
<string name="btn_help">Help</string>