/*
Copyright (C) Paul Falstad and Iain Sharp
This file is part of CircuitJS1.
CircuitJS1 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
CircuitJS1 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CircuitJS1. If not, see <http://www.gnu.org/licenses/>.
*/
package com.lushprojects.circuitjs1.client;
//import java.awt.*;
//import java.awt.image.*;
//import java.awt.event.*;
//import java.util.StringTokenizer;
//import java.lang.reflect.Constructor;
//import java.lang.reflect.Method;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.user.client.ui.MenuBar;
import java.util.Vector;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
// plot of single value on a scope
class ScopePlot {
double minValues[], maxValues[];
int scopePointCount;
int ptr, ctr, value, speed, units;
double lastValue;
String color;
CircuitElm elm;
ScopePlot(CircuitElm e, int u) {
elm = e;
units = u;
}
ScopePlot(CircuitElm e, int u, int v) {
elm = e;
units = u;
value = v;
}
int startIndex(int w) {
return ptr + scopePointCount - w;
}
void reset(int spc, int sp) {
scopePointCount = spc;
speed = sp;
minValues = new double[scopePointCount];
maxValues = new double[scopePointCount];
ptr = ctr = 0;
}
void timeStep() {
if (elm == null)
return;
double v = elm.getScopeValue(value);
if (v < minValues[ptr])
minValues[ptr] = v;
if (v > maxValues[ptr])
maxValues[ptr] = v;
lastValue = v;
ctr++;
if (ctr >= speed) {
ptr = (ptr+1) & (scopePointCount-1);
minValues[ptr] = maxValues[ptr] = v;
ctr = 0;
}
}
String getUnitText(double v) {
switch (units) {
case Scope.UNITS_V:
return CircuitElm.getVoltageText(v);
case Scope.UNITS_A:
return CircuitElm.getCurrentText(v);
case Scope.UNITS_OHMS:
return CircuitElm.getUnitText(v, CirSim.ohmString);
case Scope.UNITS_W:
return CircuitElm.getUnitText(v, "W");
}
return null;
}
static final String colors[] = {
"#FF0000", "#FF8000", "#FF00FF", "#7F00FF",
"#0000FF", "#0080FF", "#FFFF00", "#00FFFF",
};
void assignColor(int count) {
if (count > 0) {
color = colors[(count-1) % 8];
return;
}
switch (units) {
case Scope.UNITS_V:
color = "#00FF00";
break;
case Scope.UNITS_A:
color = "#FFFF00";
break;
default:
color = (CirSim.theSim.printableCheckItem.getState()) ? "#000000" : "#FFFFFF";
break;
}
}
}
class Scope {
final int FLAG_YELM = 32;
// bunch of other flags go here, see dump()
final int FLAG_IVALUE = 2048; // Flag to indicate if IVALUE is included in dump
final int FLAG_PLOTS = 4096; // new-style dump with multiple plots
// other flags go here too, see dump()
static final int VAL_POWER = 1;
static final int VAL_CURRENT = 3;
static final int VAL_IB = 1;
static final int VAL_IC = 2;
static final int VAL_IE = 3;
static final int VAL_VBE = 4;
static final int VAL_VBC = 5;
static final int VAL_VCE = 6;
static final int VAL_R = 2;
static final int UNITS_V = 0;
static final int UNITS_A = 1;
static final int UNITS_W = 2;
static final int UNITS_OHMS = 3;
static final int UNITS_COUNT = 4;
int scopePointCount = 128;
FFT fft;
int position;
int speed;
int stackCount; // number of scopes in this column
String text;
Rectangle rect;
boolean showI, showV, showScale, showMax, showMin, showFreq, lockScale, plot2d, plotXY, maxScale;
boolean showFFT, showNegative, showRMS;
Vector<ScopePlot> plots, visiblePlots;
// MemoryImageSource imageSource;
// Image image;
int pixels[];
int draw_ox, draw_oy;
float dpixels[];
CirSim sim;
Canvas imageCanvas;
Context2d imageContext;
int alphadiv =0;
double scopeTimeStep;
double scale[];
boolean expandRange[];
double scaleX, scaleY;
int wheelDeltaY;
int selectedPlot;
Scope(CirSim s) {
sim = s;
scale = new double[UNITS_COUNT];
expandRange = new boolean[UNITS_COUNT];
rect = new Rectangle(0, 0, 1, 1);
imageCanvas=Canvas.createIfSupported();
imageContext=imageCanvas.getContext2d();
allocImage();
reset();
}
void showCurrent(boolean b) {
showI = b;
if (b && !showingVoltageAndMaybeCurrent())
setValue(0);
calcVisiblePlots();
}
void showVoltage(boolean b) {
showV = b;
if (b && !showingVoltageAndMaybeCurrent())
setValue(0);
calcVisiblePlots();
}
void showMax (boolean b) { showMax = b; }
void showScale (boolean b) { showScale = b; }
void showMin (boolean b) { showMin = b; }
void showFreq (boolean b) { showFreq = b; }
void showFFT(boolean b) {
showFFT = b;
if (!showFFT)
fft = null;
}
void resetGraph() {
scopePointCount = 1;
while (scopePointCount <= rect.width)
scopePointCount *= 2;
if (plots == null)
plots = new Vector<ScopePlot>();
showNegative = false;
int i;
for (i = 0; i != plots.size(); i++)
plots.get(i).reset(scopePointCount, speed);
calcVisiblePlots();
scopeTimeStep = sim.timeStep;
allocImage();
}
boolean active() { return plots.size() > 0 && plots.get(0).elm != null; }
void reset() {
resetGraph();
scale[UNITS_W] = scale[UNITS_OHMS] = scale[UNITS_V] = 5;
scale[UNITS_A] = .1;
scaleX = 5;
scaleY = .1;
speed = 64;
showMax = true;
showV = showI = false;
showScale = showFreq = lockScale = showMin = false;
showFFT = false;
plot2d = false;
// set showV and showI appropriately depending on what plots are present
int i;
for (i = 0; i != plots.size(); i++) {
ScopePlot plot = plots.get(i);
if (plot.units == UNITS_V)
showV = true;
if (plot.units == UNITS_A)
showI = true;
}
}
void calcVisiblePlots() {
visiblePlots = new Vector<ScopePlot>();
int i;
int vc = 0, ac = 0, oc = 0;
for (i = 0; i != plots.size(); i++) {
ScopePlot plot = plots.get(i);
if (plot.units == UNITS_V) {
if (showV) {
visiblePlots.add(plot);
plot.assignColor(vc++);
}
} else if (plot.units == UNITS_A) {
if (showI) {
visiblePlots.add(plot);
plot.assignColor(ac++);
}
} else {
visiblePlots.add(plot);
plot.assignColor(oc++);
}
}
}
void setRect(Rectangle r) {
int w = this.rect.width;
this.rect = r;
if (this.rect.width != w)
resetGraph();
}
int getWidth() { return rect.width; }
int rightEdge() { return rect.x+rect.width; }
void setElm(CircuitElm ce) {
plots = new Vector<ScopePlot>();
if (ce instanceof TransistorElm)
setValue(VAL_VCE, ce);
else
setValue(0, ce);
reset();
}
void setValue(int val) {
if (plots.size() > 2 || plots.size() == 0)
return;
CircuitElm ce = plots.firstElement().elm;
if (plots.size() == 2 && plots.get(1).elm != ce)
return;
plot2d = plotXY = false;
setValue(val, ce);
}
void setValue(int val, CircuitElm ce) {
plots = new Vector<ScopePlot>();
if (val == 0) {
plots.add(new ScopePlot(ce, UNITS_V, 0));
// create plot for current if applicable
if (ce != null && !(ce instanceof OutputElm ||
ce instanceof LogicOutputElm ||
ce instanceof AudioOutputElm ||
ce instanceof ProbeElm))
plots.add(new ScopePlot(ce, UNITS_A, VAL_CURRENT));
} else {
int u = ce.getScopeUnits(val);
plots.add(new ScopePlot(ce, u, val));
if (u == UNITS_V)
showV = true;
if (u == UNITS_A)
showI = true;
}
calcVisiblePlots();
resetGraph();
// reset();
}
void setValues(int val, int ival, CircuitElm ce, CircuitElm yelm) {
if (ival > 0) {
plots = new Vector<ScopePlot>();
plots.add(new ScopePlot(ce, ce.getScopeUnits( val), val));
plots.add(new ScopePlot(ce, ce.getScopeUnits(ival), ival));
return;
}
if (yelm != null) {
plots = new Vector<ScopePlot>();
plots.add(new ScopePlot(ce, ce.getScopeUnits( val), 0));
plots.add(new ScopePlot(yelm, ce.getScopeUnits(ival), 0));
return;
}
setValue(val);
}
boolean showingValue(int v) {
int i;
for (i = 0; i != plots.size(); i++) {
ScopePlot sp = plots.get(i);
if (sp.value != v)
return false;
}
return true;
}
boolean showingVoltageAndMaybeCurrent() {
int i;
boolean gotv = false;
for (i = 0; i != plots.size(); i++) {
ScopePlot sp = plots.get(i);
if (sp.value == 0)
gotv = true;
else if (sp.value != VAL_CURRENT)
return false;
}
return gotv;
}
void combine(Scope s) {
/*
// if voltage and current are shown, remove current
if (plots.size() == 2 && plots.get(0).elm == plots.get(1).elm)
plots.remove(1);
if (s.plots.size() == 2 && s.plots.get(0).elm == s.plots.get(1).elm)
plots.add(s.plots.get(0));
else
*/
plots = visiblePlots;
plots.addAll(s.visiblePlots);
s.plots.removeAllElements();
}
void removePlot(int plot) {
if (plot < visiblePlots.size()) {
ScopePlot p = visiblePlots.get(plot);
plots.remove(p);
calcVisiblePlots();
}
}
void timeStep() {
int i;
for (i = 0; i != plots.size(); i++)
plots.get(i).timeStep();
if (plot2d && imageContext!=null) {
boolean newscale = false;
if (plots.size() < 2)
return;
double v = plots.get(0).lastValue;
while (v > scaleX || v < -scaleX) {
scaleX *= 2;
newscale = true;
}
double yval = plots.get(1).lastValue;
while (yval > scaleY || yval < -scaleY) {
scaleY *= 2;
newscale = true;
}
if (newscale)
clear2dView();
double xa = v /scaleX;
double ya = yval/scaleY;
int x = (int) (rect.width *(1+xa)*.499);
int y = (int) (rect.height*(1-ya)*.499);
drawTo(x, y);
}
}
void drawTo(int x2, int y2) {
if (draw_ox == -1) {
draw_ox = x2;
draw_oy = y2;
}
if (sim.printableCheckItem.getState()) {
imageContext.setStrokeStyle("#000000");
} else {
imageContext.setStrokeStyle("#ffffff");
}
imageContext.beginPath();
imageContext.moveTo(draw_ox, draw_oy);
imageContext.lineTo(x2,y2);
imageContext.stroke();
draw_ox = x2;
draw_oy = y2;
}
void clear2dView() {
if (imageContext!=null) {
if (sim.printableCheckItem.getState()) {
imageContext.setFillStyle("#ffffff");
} else {
imageContext.setFillStyle("#000000");
}
imageContext.fillRect(0, 0, rect.width-1, rect.height-1);
}
draw_ox = draw_oy = -1;
}
/*
void adjustScale(double x) {
scale[UNITS_V] *= x;
scale[UNITS_A] *= x;
scale[UNITS_OHMS] *= x;
scale[UNITS_W] *= x;
scaleX *= x;
scaleY *= x;
}
*/
void maxScale() {
if (plot2d) {
double x = 1e-8;
scale[UNITS_V] *= x;
scale[UNITS_A] *= x;
scale[UNITS_OHMS] *= x;
scale[UNITS_W] *= x;
scaleX *= x;
scaleY *= x;
return;
}
// toggle max scale. Why isn't this on by default? For the examples, we sometimes want two plots
// matched to the same scale so we can show one is larger. Also, for some fast-moving scopes
// (like for AM detector), the amplitude varies over time but you can't see that if the scale is
// constantly adjusting. It's also nice to set the default scale to hide noise and to avoid
// having the scale moving around a lot when a circuit starts up.
maxScale = !maxScale;
showNegative = false;
}
void drawFFTVerticalGridLines(Graphics g) {
// Draw x-grid lines and label the frequencies in the FFT that they point to.
int prevEnd = 0;
int divs = 20;
double maxFrequency = 1 / (sim.timeStep * speed * divs * 2);
for (int i = 0; i < divs; i++) {
int x = rect.width * i / divs;
if (x < prevEnd) continue;
String s = ((int) Math.round(i * maxFrequency)) + "Hz";
int sWidth = (int) Math.ceil(g.context.measureText(s).getWidth());
prevEnd = x + sWidth + 4;
if (i > 0) {
g.setColor("#880000");
g.drawLine(x, 0, x, rect.height);
}
g.setColor("#FF0000");
g.drawString(s, x + 2, rect.height);
}
}
void drawFFT(Graphics g) {
if (fft == null || fft.getSize() != scopePointCount)
fft = new FFT(scopePointCount);
int y = (rect.height - 1) / 2;
double[] real = new double[scopePointCount];
double[] imag = new double[scopePointCount];
ScopePlot plot = (visiblePlots.size() == 0) ? plots.firstElement() : visiblePlots.firstElement();
double maxV[] = plot.maxValues;
double minV[] = plot.minValues;
int ptr = plot.ptr;
for (int i = 0; i < scopePointCount; i++) {
int ii = (ptr - i + scopePointCount) & (scopePointCount - 1);
// need to average max and min or else it could cause average of function to be > 0, which
// produces spike at 0 Hz that hides rest of spectrum
real[i] = .5*(maxV[ii]+minV[ii]);
imag[i] = 0;
}
fft.fft(real, imag);
double maxM = 1e-8;
for (int i = 0; i < scopePointCount / 2; i++) {
double m = fft.magnitude(real[i], imag[i]);
if (m > maxM)
maxM = m;
}
double magnitude = fft.magnitude(real[0], imag[0]);
int prevHeight = (int) ((magnitude * y) / maxM);
int prevX = 0;
g.setColor("#FF0000");
for (int i = 1; i < scopePointCount / 2; i++) {
int x = 2 * i * rect.width / scopePointCount;
// rect.width may be greater than or less than scopePointCount/2,
// so x may be greater than or equal to prevX.
if (x == prevX) continue;
magnitude = fft.magnitude(real[i], imag[i]);
int height = (int) ((magnitude * y) / maxM);
g.drawLine(prevX, y - prevHeight, x, y - height);
prevHeight = height;
prevX = x;
}
}
void draw2d(Graphics g) {
if (imageContext==null)
return;
g.context.save();
g.context.translate(rect.x, rect.y);
alphadiv++;
if (alphadiv>2) {
alphadiv=0;
imageContext.setGlobalAlpha(0.01);
if (sim.printableCheckItem.getState()) {
imageContext.setFillStyle("#ffffff");
} else {
imageContext.setFillStyle("#000000");
}
imageContext.fillRect(0,0,rect.width,rect.height);
imageContext.setGlobalAlpha(1.0);
}
g.context.drawImage(imageContext.getCanvas(), 0.0, 0.0);
// g.drawImage(image, r.x, r.y, null);
g.setColor(CircuitElm.whiteColor);
g.fillOval(draw_ox-2, draw_oy-2, 5, 5);
int yt = 10;
int x = 0;
if (text != null && rect.height > yt+5) {
g.drawString(text, x, yt);
yt += 15;
}
g.setColor(Color.green);
g.drawLine(0, rect.height/2, rect.width-1, rect.height/2);
if (!plotXY)
g.setColor(Color.yellow);
g.drawLine(rect.width/2, 0, rect.width/2, rect.height-1);
g.context.restore();
}
boolean drawGridLines;
boolean somethingSelected;
void draw(Graphics g) {
if (plots.size() == 0)
return;
// reset if timestep changed
if (scopeTimeStep != sim.timeStep) {
scopeTimeStep = sim.timeStep;
resetGraph();
}
if (plot2d) {
draw2d(g);
return;
}
// if (pixels == null)
// return;
g.context.save();
g.setColor(Color.red);
g.context.translate(rect.x, rect.y);
if (showFFT) {
drawFFTVerticalGridLines(g);
drawFFT(g);
}
int i;
for (i = 0; i != UNITS_COUNT; i++) {
expandRange[i] = false;
if (maxScale)
scale[i] = 1e-4;
}
int si;
somethingSelected = false; // is one of our plots selected?
for (si = 0; si != visiblePlots.size(); si++) {
ScopePlot plot = visiblePlots.get(si);
calcPlotScale(plot);
if (sim.scopeSelected == -1 && plot.elm.isMouseElm())
somethingSelected = true;
expandRange[plot.units] = true;
}
checkForSelection();
if (selectedPlot >= 0)
somethingSelected = true;
drawGridLines = true;
boolean hGridLines = true;
for (i = 1; i < visiblePlots.size(); i++) {
if (visiblePlots.get(i).units != visiblePlots.get(0).units)
hGridLines = false;
}
if ((hGridLines || showMax || showMin) && visiblePlots.size() > 0)
calcMaxAndMin(visiblePlots.firstElement().units);
// draw volts on top (last), then current underneath, then everything else
for (i = 0; i != visiblePlots.size(); i++) {
if (visiblePlots.get(i).units > UNITS_A && i != selectedPlot)
drawPlot(g, visiblePlots.get(i), hGridLines, false);
}
for (i = 0; i != visiblePlots.size(); i++) {
if (visiblePlots.get(i).units == UNITS_A && i != selectedPlot)
drawPlot(g, visiblePlots.get(i), hGridLines, false);
}
for (i = 0; i != visiblePlots.size(); i++) {
if (visiblePlots.get(i).units == UNITS_V && i != selectedPlot)
drawPlot(g, visiblePlots.get(i), hGridLines, false);
}
// draw selection on top. only works if selection chosen from scope
if (selectedPlot >= 0)
drawPlot(g, visiblePlots.get(selectedPlot), hGridLines, true);
if (visiblePlots.size() > 0)
drawInfoTexts(g);
g.context.restore();
drawCrosshairs(g);
// there is no UI for setting lockScale but it's used in some of the example circuits (like crossover)
if (plots.get(0).ptr > 5 && !lockScale) {
for (i = 0; i != UNITS_COUNT; i++)
if (scale[i] > 1e-4 && expandRange[i])
scale[i] /= 2;
}
}
String curColor, voltColor;
double gridStepX, gridStepY;
double maxValue, minValue;
// calculate maximum and minimum values for all plots of given units
void calcMaxAndMin(int units) {
maxValue = -1e8;
minValue = 1e8;
int i;
int si;
for (si = 0; si != visiblePlots.size(); si++) {
ScopePlot plot = visiblePlots.get(si);
if (plot.units != units)
continue;
int ipa = plot.startIndex(rect.width);
double maxV[] = plot.maxValues;
double minV[] = plot.minValues;
for (i = 0; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
if (maxV[ip] > maxValue)
maxValue = maxV[ip];
if (minV[ip] < minValue)
minValue = minV[ip];
}
}
}
// adjust scale of a plot
void calcPlotScale(ScopePlot plot) {
int i;
int ipa = plot.startIndex(rect.width);
double maxV[] = plot.maxValues;
double minV[] = plot.minValues;
double max = 0;
double gridMax = scale[plot.units];
for (i = 0; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
if (maxV[ip] > max)
max = maxV[ip];
if (minV[ip] < -max)
max = -minV[ip];
}
// scale fixed at maximum?
if (maxScale)
gridMax = Math.max(max, gridMax);
else
// adjust in powers of two
while (max > gridMax)
gridMax *= 2;
scale[plot.units] = gridMax;
}
double mainGridMult, mainGridMid;
void drawPlot(Graphics g, ScopePlot plot, boolean drawHGridLines, boolean selected) {
int i;
String col;
// int col = (sim.printableCheckItem.getState()) ? 0xFFFFFFFF : 0;
// for (i = 0; i != pixels.length; i++)
// pixels[i] = col;
double multa[] = {2.0, 2.5, 2.0};
int multptr=0;
int x = 0;
int maxy = (rect.height-1)/2;
int y = maxy;
String color = (somethingSelected) ? "#A0A0A0" : plot.color;
if (sim.scopeSelected == -1 && plot.elm.isMouseElm())
color = "#00FFFF";
else if (selected)
color = plot.color;
int ipa = plot.startIndex(rect.width);
double maxV[] = plot.maxValues;
double minV[] = plot.minValues;
double gridMax = scale[plot.units];
double gridMid = 0;
if (drawHGridLines) {
// if we don't have overlapping scopes of different units, we can move zero around.
// Put it at the bottom if the scope is never negative.
double mx = gridMax;
double mn = 0;
if (maxScale) {
// scale is maxed out, so fix boundaries of scope at maximum and minimum.
mx = maxValue;
mn = minValue;
} else if (showNegative || minValue < (mx+mn)*.5 - (mx-mn)*.55) {
mn = -gridMax;
showNegative = true;
}
gridMid = (mx+mn)*.5;
gridMax = (mx-mn)*.55; // leave space at top and bottom
}
double gridMult = maxy/gridMax;
if (selected) {
mainGridMult = gridMult;
mainGridMid = gridMid;
}
int minRangeLo = -10-(int) (gridMid*gridMult);
int minRangeHi = 10-(int) (gridMid*gridMult);
gridStepY = 1e-8;
while (gridStepY < 20*gridMax/maxy) {
gridStepY *=multa[(multptr++)%3];
}
// Horizontal gridlines
int ll;
String minorDiv = "#303030";
String majorDiv = "#A0A0A0";
if (sim.printableCheckItem.getState()) {
minorDiv = "#D0D0D0";
majorDiv = "#808080";
curColor = "#A0A000";
}
// Vertical (T) gridlines
gridStepX = 1e-15;
double ts = sim.timeStep*speed;
// while (gridStep < ts*5)
// gridStep *= 10;
multptr=0;
while (gridStepX < ts*20) {
gridStepX *=multa[(multptr++)%3];
}
if (drawGridLines) {
// horizontal gridlines
// don't show gridlines if lines are too close together (except for center line)
boolean showGridLines = (gridStepY != 0) && drawHGridLines;
for (ll = -100; ll <= 100; ll++) {
if (ll != 0 && !showGridLines)
continue;
int yl = maxy-(int) ((ll*gridStepY-gridMid)*gridMult);
if (yl < 0 || yl >= rect.height-1)
continue;
col = ll == 0 ? majorDiv : minorDiv;
g.setColor(col);
g.drawLine(0,yl,rect.width-1,yl);
}
// vertical gridlines
double tstart = sim.t-sim.timeStep*speed*rect.width;
double tx = sim.t-(sim.t % gridStepX);
for (ll = 0; ; ll++) {
double tl = tx-gridStepX*ll;
int gx = (int) ((tl-tstart)/ts);
if (gx < 0)
break;
if (gx >= rect.width)
continue;
if (tl < 0)
continue;
col = minorDiv;
// first = 0;
if (((tl+gridStepX/4) % (gridStepX*10)) < gridStepX) {
col = majorDiv;
}
g.setColor(col);
g.drawLine(gx,0,gx,rect.height-1);
}
}
// only need gridlines drawn once
drawGridLines = false;
g.setColor(color);
int ox = -1, oy = -1;
for (i = 0; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
int minvy = (int) (gridMult*(minV[ip]-gridMid));
int maxvy = (int) (gridMult*(maxV[ip]-gridMid));
if (minvy <= maxy) {
if (minvy < minRangeLo || maxvy > minRangeHi) {
// we got a value outside min range, so we don't need to rescale later
expandRange[plot.units] = false;
minRangeLo = -1000;
minRangeHi = 1000; // avoid triggering this test again
}
if (ox != -1) {
if (minvy == oy && maxvy == oy)
continue;
g.drawLine(ox, y-oy, x+i-1, y-oy);
ox = oy = -1;
}
if (minvy == maxvy) {
ox = x+i;
oy = minvy;
continue;
}
g.drawLine(x+i, y-minvy, x+i, y-maxvy-1);
}
} // for (i=0...)
if (ox != -1)
g.drawLine(ox, y-oy, x+i-1, y-oy); // Horizontal
}
// find selected plot
void checkForSelection() {
if (sim.dialogIsShowing())
return;
if (!rect.contains(sim.mouseCursorX, sim.mouseCursorY)) {
selectedPlot = -1;
return;
}
int ipa = plots.get(0).startIndex(rect.width);
int ip = (sim.mouseCursorX-rect.x+ipa) & (scopePointCount-1);
int maxy = (rect.height-1)/2;
int y = maxy;
int i;
int bestdist = 10000;
int best = -1;
for (i = 0; i != visiblePlots.size(); i++) {
ScopePlot plot = visiblePlots.get(i);
int maxvy = (int) ((maxy/scale[plot.units])*plot.maxValues[ip]);
int dist = Math.abs(sim.mouseCursorY-(rect.y+y-maxvy));
if (dist < bestdist) {
bestdist = dist;
best = i;
}
}
selectedPlot = best;
}
void drawCrosshairs(Graphics g) {
if (sim.dialogIsShowing())
return;
if (!rect.contains(sim.mouseCursorX, sim.mouseCursorY))
return;
if (selectedPlot < 0 && !showFFT)
return;
String info[] = new String[4];
int ipa = plots.get(0).startIndex(rect.width);
int ip = (sim.mouseCursorX-rect.x+ipa) & (scopePointCount-1);
int ct = 0;
int maxy = (rect.height-1)/2;
int y = maxy;
if (selectedPlot >= 0) {
ScopePlot plot = visiblePlots.get(selectedPlot);
info[ct++] = plot.getUnitText(plot.maxValues[ip]);
int maxvy = (int) (mainGridMult*(plot.maxValues[ip]-mainGridMid));
g.setColor(plot.color);
g.fillOval(sim.mouseCursorX-2, rect.y+y-maxvy-2, 5, 5);
}
if (showFFT) {
double maxFrequency = 1 / (sim.timeStep * speed * 2);
info[ct++] = CircuitElm.getUnitText(maxFrequency*(sim.mouseCursorX-rect.x)/rect.width, "Hz");
}
if (visiblePlots.size() > 0) {
double t = sim.t-sim.timeStep*speed*(rect.x+rect.width-sim.mouseCursorX);
info[ct++] = CircuitElm.getUnitText(t, "s");
}
int szw = 0, szh = 15*ct;
int i;
for (i = 0; i != ct; i++) {
int w=(int)g.context.measureText(info[i]).getWidth();
if (w > szw)
szw = w;
}
g.setColor(CircuitElm.whiteColor);
g.drawLine(sim.mouseCursorX, rect.y, sim.mouseCursorX, rect.y+rect.height);
// g.drawLine(rect.x, sim.mouseCursorY, rect.x+rect.width, sim.mouseCursorY);
g.setColor(sim.printableCheckItem.getState() ? Color.white : Color.black);
int bx = sim.mouseCursorX;
if (bx < szw/2)
bx = szw/2;
g.fillRect(bx-szw/2, rect.y-szh, szw, szh);
g.setColor(CircuitElm.whiteColor);
for (i = 0; i != ct; i++) {
int w=(int)g.context.measureText(info[i]).getWidth();
g.drawString(info[i], bx-w/2, rect.y-2-(ct-1-i)*15);
}
}
void drawRMS(Graphics g) {
ScopePlot plot = visiblePlots.firstElement();
int i;
double avg = 0;
int ipa = plot.ptr+scopePointCount-rect.width;
double maxV[] = plot.maxValues;
double minV[] = plot.minValues;
double mid = (maxValue+minValue)/2;
int state = -1;
// skip zeroes
for (i = 0; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
if (maxV[ip] != 0) {
if (maxV[ip] > mid)
state = 1;
break;
}
}
int firstState = -state;
int start = i;
int end = 0;
int waveCount = 0;
double endAvg = 0;
for (; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
boolean sw = false;
// switching polarity?
if (state == 1) {
if (maxV[ip] < mid)
sw = true;
} else if (minV[ip] > mid)
sw = true;
if (sw) {
state = -state;
// completed a full cycle?
if (firstState == state) {
if (waveCount == 0) {
start = i;
firstState = state;
avg = 0;
}
waveCount++;
end = i;
endAvg = avg;
}
}
if (waveCount > 0)
avg += maxV[ip]*maxV[ip];
}
double rms;
if (waveCount > 1) {
rms = Math.sqrt(endAvg/(end-start));
drawInfoText(g, plot.getUnitText(rms) + "rms");
}
}
void drawFrequency(Graphics g) {
// try to get frequency
// get average
double avg = 0;
int i;
ScopePlot plot = visiblePlots.firstElement();
int ipa = plot.ptr+scopePointCount-rect.width;
double minV[] = plot.minValues;
double maxV[] = plot.maxValues;
for (i = 0; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
avg += minV[ip]+maxV[ip];
}
avg /= i*2;
int state = 0;
double thresh = avg*.05;
int oi = 0;
double avperiod = 0;
int periodct = -1;
double avperiod2 = 0;
// count period lengths
for (i = 0; i != rect.width; i++) {
int ip = (i+ipa) & (scopePointCount-1);
double q = maxV[ip]-avg;
int os = state;
if (q < thresh)
state = 1;
else if (q > -thresh)
state = 2;
if (state == 2 && os == 1) {
int pd = i-oi;
oi = i;
// short periods can't be counted properly
if (pd < 12)
continue;
// skip first period, it might be too short
if (periodct >= 0) {
avperiod += pd;
avperiod2 += pd*pd;
}
periodct++;
}
}
avperiod /= periodct;
avperiod2 /= periodct;
double periodstd = Math.sqrt(avperiod2-avperiod*avperiod);
double freq = 1/(avperiod*sim.timeStep*speed);
// don't show freq if standard deviation is too great
if (periodct < 1 || periodstd > 2)
freq = 0;
// System.out.println(freq + " " + periodstd + " " + periodct);
if (freq != 0)
drawInfoText(g, CircuitElm.getUnitText(freq, "Hz"));
}
int textY;
void drawInfoText(Graphics g, String text) {
if (rect.y + rect.height <= textY+5)
return;
g.drawString(text, 0, textY);
textY += 15;
}
void drawInfoTexts(Graphics g) {
g.setColor(CircuitElm.whiteColor);
textY = 10;
ScopePlot plot = visiblePlots.firstElement();
if (showScale) {
String vScaleText="";
if ( gridStepY!=0 && (!(showV && showI)))
vScaleText=" V=" + plot.getUnitText(gridStepY)+"/div";
drawInfoText(g, "H="+CircuitElm.getUnitText(gridStepX, "s")+"/div" +
vScaleText);
}
// if (showMax || showMin)
// calcMaxAndMin(plot.units);
if (showMax)
drawInfoText(g, plot.getUnitText(maxValue));
if (showMin) {
int ym=rect.height-5;
g.drawString(plot.getUnitText(minValue), 0, ym);
}
if (showRMS)
drawRMS(g);
String t = text;
if (t == null)
t = getScopeText();
t = CirSim.LS(t);
if (t != null)
drawInfoText(g, t);
if (showFreq)
drawFrequency(g);
}
String getScopeText() {
// stacked scopes? don't show text
if (stackCount != 1)
return null;
// multiple elms? don't show text (unless one is selected)
if (selectedPlot < 0 && getSingleElm() == null)
return null;
ScopePlot plot = visiblePlots.firstElement();
if (selectedPlot >= 0 && visiblePlots.size() > selectedPlot)
plot = visiblePlots.get(selectedPlot);
return plot.elm.getScopeText(plot.value);
}
void speedUp() {
if (speed > 1) {
speed /= 2;
resetGraph();
}
}
void slowDown() {
if (speed < 1024)
speed *= 2;
resetGraph();
}
// get scope element, returning null if there's more than one
CircuitElm getSingleElm() {
CircuitElm elm = plots.get(0).elm;
int i;
for (i = 1; i < plots.size(); i++) {
if (plots.get(i).elm != elm)
return null;
}
return elm;
}
MenuBar getMenu() {
CircuitElm elm = plots.get(0).elm;
if (elm == null)
return null;
elm = getSingleElm();
if (elm != null && elm instanceof TransistorElm) {
sim.scopeIbMenuItem.setState(showingValue(VAL_IB));
sim.scopeIcMenuItem.setState(showingValue(VAL_IC));
sim.scopeIeMenuItem.setState(showingValue(VAL_IE));
sim.scopeVbeMenuItem.setState(showingValue(VAL_VBE));
sim.scopeVbcMenuItem.setState(showingValue(VAL_VBC));
sim.scopeVceMenuItem.setState(showingValue(VAL_VCE));
sim.scopeVceIcMenuItem.setState(isShowingVceAndIc());
sim.scopeMaxScaleTransMenuItem.setState(maxScale && !plot2d);
return sim.transScopeMenuBar;
} else {
sim.scopeRemovePlotMenuItem.setEnabled(visiblePlots.size() > 1 && !plot2d);
sim.scopeVMenuItem .setState(showV && !showingValue(VAL_POWER));
sim.scopeIMenuItem .setState(showI && !showingValue(VAL_POWER));
sim.scopeScaleMenuItem.setState(showScale);
sim.scopeMaxMenuItem .setState(showMax);
sim.scopeMinMenuItem .setState(showMin);
sim.scopeFreqMenuItem .setState(showFreq);
sim.scopePowerMenuItem.setState(showingValue(VAL_POWER));
sim.scopeRMSMenuItem .setState(showRMS);
sim.scopeVIMenuItem .setState(plot2d && !plotXY);
sim.scopeXYMenuItem .setState(plotXY);
sim.scopeSelectYMenuItem.setEnabled(plotXY);
sim.scopeFFTMenuItem.setState(showFFT);
sim.scopeResistMenuItem.setState(showingValue(VAL_R));
sim.scopeResistMenuItem.setEnabled(elm != null && elm.canShowValueInScope(VAL_R));
sim.scopeMaxScaleMenuItem.setState(maxScale && !plot2d);
return sim.scopeMenuBar;
}
}
boolean isShowingVceAndIc() {
return plot2d && plots.size() == 2 && plots.get(0).value == VAL_VCE && plots.get(1).value == VAL_IC;
}
String dump() {
ScopePlot vPlot = plots.get(0);
CircuitElm elm = vPlot.elm;
if (elm == null)
return null;
int flags = (showI ? 1 : 0) | (showV ? 2 : 0) |
(showMax ? 0 : 4) | // showMax used to be always on
(showFreq ? 8 : 0) |
(lockScale ? 16 : 0) | (plot2d ? 64 : 0) |
(plotXY ? 128 : 0) | (showMin ? 256 : 0) | (showScale? 512:0) |
(showFFT ? 1024 : 0) | (maxScale ? 8192 : 0) | (showRMS ? 16384 : 0);
flags |= FLAG_PLOTS;
int eno = sim.locateElm(elm);
if (eno < 0)
return null;
String x = "o " + eno + " " +
vPlot.speed + " " + vPlot.value + " " + flags + " " +
scale[UNITS_V] + " " + scale[UNITS_A] + " " + position + " " +
plots.size();
int i;
for (i = 0; i < plots.size(); i++) {
ScopePlot p = plots.get(i);
if (i > 0)
x += " " + sim.locateElm(p.elm) + " " + p.value;
// dump scale if units are not V or A
if (p.units > UNITS_A)
x += " " + scale[p.units];
}
if (text != null)
x += " " + text;
return x;
}
void undump(StringTokenizer st) {
reset();
int e = new Integer(st.nextToken()).intValue();
if (e == -1)
return;
setElm(sim.getElm(e));
speed = new Integer(st.nextToken()).intValue();
int value = new Integer(st.nextToken()).intValue();
int flags = new Integer(st.nextToken()).intValue();
scale[UNITS_V] = new Double(st.nextToken()).doubleValue();
scale[UNITS_A] = new Double(st.nextToken()).doubleValue();
if (scale[UNITS_V] == 0)
scale[UNITS_V] = .5;
if (scale[UNITS_A] == 0)
scale[UNITS_A] = 1;
scaleX = scale[UNITS_V];
scaleY = scale[UNITS_A];
scale[UNITS_OHMS] = scale[UNITS_W] = scale[UNITS_V];
text = null;
boolean plot2dFlag = (flags & 64) != 0;
if ((flags & FLAG_PLOTS) != 0) {
// new-style dump
try {
position = Integer.parseInt(st.nextToken());
int sz = Integer.parseInt(st.nextToken());
int i;
setValue(value);
// setValue(0) creates an extra plot for current, so remove that
while (plots.size() > 1)
plots.removeElementAt(1);
int u = plots.get(0).units;
if (u > UNITS_A)
scale[u] = Double.parseDouble(st.nextToken());
for (i = 1; i != sz; i++) {
int ne = Integer.parseInt(st.nextToken());
int val = Integer.parseInt(st.nextToken());
CircuitElm elm = sim.getElm(ne);
u = elm.getScopeUnits(val);
if (u > UNITS_A)
scale[u] = Double.parseDouble(st.nextToken());
plots.add(new ScopePlot(elm, u, val));
}
while (st.hasMoreTokens()) {
if (text == null)
text = st.nextToken();
else
text += " " + st.nextToken();
}
} catch (Exception ee) {
}
} else {
// old-style dump
CircuitElm yElm = null;
int ivalue = 0;
try {
position = new Integer(st.nextToken()).intValue();
int ye = -1;
if ((flags & FLAG_YELM) != 0) {
ye = new Integer(st.nextToken()).intValue();
if (ye != -1)
yElm = sim.getElm(ye);
// sinediode.txt has yElm set to something even though there's no xy plot...?
if (!plot2dFlag)
yElm = null;
}
if ((flags & FLAG_IVALUE) !=0) {
ivalue = new Integer(st.nextToken()).intValue();
}
while (st.hasMoreTokens()) {
if (text == null)
text = st.nextToken();
else
text += " " + st.nextToken();
}
} catch (Exception ee) {
}
setValues(value, ivalue, sim.getElm(e), yElm);
}
showI = (flags & 1) != 0;
showV = (flags & 2) != 0;
showMax = (flags & 4) == 0;
showFreq = (flags & 8) != 0;
lockScale = (flags & 16) != 0;
plot2d = plot2dFlag;
plotXY = (flags & 128) != 0;
showMin = (flags & 256) != 0;
showScale = (flags & 512) !=0;
showFFT((flags & 1024) != 0);
maxScale = (flags & 8192) != 0;
showRMS = (flags & 16384) != 0;
}
void allocImage() {
if (imageCanvas != null) {
imageCanvas.setWidth(rect.width + "PX");
imageCanvas.setHeight(rect.height + "PX");
imageCanvas.setCoordinateSpaceWidth(rect.width);
imageCanvas.setCoordinateSpaceHeight(rect.height);
clear2dView();
}
}
void handleMenu(String mi) {
if (mi == "showvoltage")
showVoltage(sim.scopeVMenuItem.getState());
if (mi == "showcurrent")
showCurrent(sim.scopeIMenuItem.getState());
if (mi=="showscale")
showScale(sim.scopeScaleMenuItem.getState());
if (mi == "showpeak")
showMax(sim.scopeMaxMenuItem.getState());
if (mi == "shownegpeak")
showMin(sim.scopeMinMenuItem.getState());
if (mi == "showfreq")
showFreq(sim.scopeFreqMenuItem.getState());
if (mi == "showfft")
showFFT(sim.scopeFFTMenuItem.getState());
if (mi == "showrms")
showRMS = sim.scopeRMSMenuItem.getState();
if (mi == "showpower")
setValue(VAL_POWER);
if (mi == "showib")
setValue(VAL_IB);
if (mi == "showic")
setValue(VAL_IC);
if (mi == "showie")
setValue(VAL_IE);
if (mi == "showvbe")
setValue(VAL_VBE);
if (mi == "showvbc")
setValue(VAL_VBC);
if (mi == "showvce")
setValue(VAL_VCE);
if (mi == "showvcevsic") {
plot2d = true;
plotXY = false;
setValues(VAL_VCE, VAL_IC, getElm(), null);
resetGraph();
}
if (mi == "showvvsi") {
plot2d = sim.scopeVIMenuItem.getState();
plotXY = false;
resetGraph();
}
if (mi == "plotxy") {
plotXY = plot2d = sim.scopeXYMenuItem.getState();
if (plot2d)
plots = visiblePlots;
if (plot2d && plots.size() == 1)
selectY();
resetGraph();
}
if (mi == "showresistance")
setValue(VAL_R);
}
// void select() {
// sim.setMouseElm(elm);
// if (plotXY) {
// sim.plotXElm = elm;
// sim.plotYElm = yElm;
// }
// }
void selectY() {
CircuitElm yElm = (plots.size() == 2) ? plots.get(1).elm : null;
int e = (yElm == null) ? -1 : sim.locateElm(yElm);
int firstE = e;
while (true) {
for (e++; e < sim.elmList.size(); e++) {
CircuitElm ce = sim.getElm(e);
if ((ce instanceof OutputElm || ce instanceof ProbeElm) &&
ce != plots.get(0).elm) {
yElm = ce;
if (plots.size() == 1)
plots.add(new ScopePlot(yElm, UNITS_V));
else {
plots.get(1).elm = yElm;
plots.get(1).units = UNITS_V;
}
return;
}
}
if (firstE == -1)
return;
e = firstE = -1;
}
// not reached
}
void onMouseWheel(MouseWheelEvent e) {
wheelDeltaY += e.getDeltaY();
if (wheelDeltaY > 5) {
slowDown();
wheelDeltaY = 0;
}
if (wheelDeltaY < -5)
speedUp();
wheelDeltaY = 0;
}
CircuitElm getElm() {
if (selectedPlot >= 0 && visiblePlots.size() > selectedPlot)
return visiblePlots.get(selectedPlot).elm;
return visiblePlots.size() > 0 ? visiblePlots.get(0).elm : plots.get(0).elm;
}
CircuitElm getXElm() {
return getElm();
}
CircuitElm getYElm() {
if (plots.size() == 2)
return plots.get(1).elm;
return null;
}
boolean needToRemove() {
boolean ret = true;
boolean removed = false;
int i;
for (i = 0; i != plots.size(); i++) {
ScopePlot plot = plots.get(i);
if (sim.locateElm(plot.elm) < 0) {
plots.remove(i--);
removed = true;
} else
ret = false;
}
if (removed)
calcVisiblePlots();
return ret;
}
}