History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/tools/jconsole/Plotter.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/tools/jconsole/Plotter.java+1095−0
@@ -0,0 +1,1095 @@
1+/*
2+ * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+package sun.tools.jconsole;
27+
28+import java.awt.*;
29+import java.awt.event.*;
30+import java.beans.*;
31+import java.io.*;
32+import java.lang.reflect.Array;
33+import java.util.*;
34+
35+import javax.accessibility.*;
36+import javax.swing.*;
37+import javax.swing.border.*;
38+import javax.swing.filechooser.*;
39+import javax.swing.filechooser.FileFilter;
40+
41+import com.sun.tools.jconsole.JConsoleContext;
42+
43+import static com.sun.tools.jconsole.JConsoleContext.ConnectionState.*;
44+
45+import static sun.tools.jconsole.Formatter.*;
46+import static sun.tools.jconsole.ProxyClient.*;
47+import static sun.tools.jconsole.Resources.*;
48+import static sun.tools.jconsole.Utilities.*;
49+
50+@SuppressWarnings("serial")
51+public class Plotter extends JComponent
52+ implements Accessible, ActionListener, PropertyChangeListener {
53+
54+ public static enum Unit {
55+ NONE, BYTES, PERCENT
56+ }
57+
58+ static final String[] rangeNames = {
59+ Resources.getText(" 1 min"),
60+ Resources.getText(" 5 min"),
61+ Resources.getText("10 min"),
62+ Resources.getText("30 min"),
63+ Resources.getText(" 1 hour"),
64+ Resources.getText(" 2 hours"),
65+ Resources.getText(" 3 hours"),
66+ Resources.getText(" 6 hours"),
67+ Resources.getText("12 hours"),
68+ Resources.getText(" 1 day"),
69+ Resources.getText(" 7 days"),
70+ Resources.getText(" 1 month"),
71+ Resources.getText(" 3 months"),
72+ Resources.getText(" 6 months"),
73+ Resources.getText(" 1 year"),
74+ Resources.getText("All")
75+ };
76+
77+ static final int[] rangeValues = {
78+ 1,
79+ 5,
80+ 10,
81+ 30,
82+ 1 * 60,
83+ 2 * 60,
84+ 3 * 60,
85+ 6 * 60,
86+ 12 * 60,
87+ 1 * 24 * 60,
88+ 7 * 24 * 60,
89+ 1 * 31 * 24 * 60,
90+ 3 * 31 * 24 * 60,
91+ 6 * 31 * 24 * 60,
92+ 366 * 24 * 60,
93+ -1
94+ };
95+
96+
97+ final static long SECOND = 1000;
98+ final static long MINUTE = 60 * SECOND;
99+ final static long HOUR = 60 * MINUTE;
100+ final static long DAY = 24 * HOUR;
101+
102+ final static Color bgColor = new Color(250, 250, 250);
103+ final static Color defaultColor = Color.blue.darker();
104+
105+ final static int ARRAY_SIZE_INCREMENT = 4000;
106+
107+ private static Stroke dashedStroke;
108+
109+ private TimeStamps times = new TimeStamps();
110+ private ArrayList<Sequence> seqs = new ArrayList<Sequence>();
111+ private JPopupMenu popupMenu;
112+ private JMenu timeRangeMenu;
113+ private JRadioButtonMenuItem[] menuRBs;
114+ private JMenuItem saveAsMI;
115+ private JFileChooser saveFC;
116+
117+ private int viewRange = -1; // Minutes (value <= 0 means full range)
118+ private Unit unit;
119+ private int decimals;
120+ private double decimalsMultiplier;
121+ private Border border = null;
122+ private Rectangle r = new Rectangle(1, 1, 1, 1);
123+ private Font smallFont = null;
124+
125+ // Initial margins, may be recalculated as needed
126+ private int topMargin = 10;
127+ private int bottomMargin = 45;
128+ private int leftMargin = 65;
129+ private int rightMargin = 70;
130+ private final boolean displayLegend;
131+
132+ public Plotter() {
133+ this(Unit.NONE, 0);
134+ }
135+
136+ public Plotter(Unit unit) {
137+ this(unit, 0);
138+ }
139+
140+ public Plotter(Unit unit, int decimals) {
141+ this(unit,decimals,true);
142+ }
143+
144+ // Note: If decimals > 0 then values must be decimally shifted left
145+ // that many places, i.e. multiplied by Math.pow(10.0, decimals).
146+ public Plotter(Unit unit, int decimals, boolean displayLegend) {
147+ this.displayLegend = displayLegend;
148+ setUnit(unit);
149+ setDecimals(decimals);
150+
151+ enableEvents(AWTEvent.MOUSE_EVENT_MASK);
152+
153+ addMouseListener(new MouseAdapter() {
154+ @Override
155+ public void mousePressed(MouseEvent e) {
156+ if (getParent() instanceof PlotterPanel) {
157+ getParent().requestFocusInWindow();
158+ }
159+ }
160+ });
161+
162+ }
163+
164+ public void setUnit(Unit unit) {
165+ this.unit = unit;
166+ }
167+
168+ public void setDecimals(int decimals) {
169+ this.decimals = decimals;
170+ this.decimalsMultiplier = Math.pow(10.0, decimals);
171+ }
172+
173+ public void createSequence(String key, String name, Color color, boolean isPlotted) {
174+ Sequence seq = getSequence(key);
175+ if (seq == null) {
176+ seq = new Sequence(key);
177+ }
178+ seq.name = name;
179+ seq.color = (color != null) ? color : defaultColor;
180+ seq.isPlotted = isPlotted;
181+
182+ seqs.add(seq);
183+ }
184+
185+ public void setUseDashedTransitions(String key, boolean b) {
186+ Sequence seq = getSequence(key);
187+ if (seq != null) {
188+ seq.transitionStroke = b ? getDashedStroke() : null;
189+ }
190+ }
191+
192+ public void setIsPlotted(String key, boolean isPlotted) {
193+ Sequence seq = getSequence(key);
194+ if (seq != null) {
195+ seq.isPlotted = isPlotted;
196+ }
197+ }
198+
199+ // Note: If decimals > 0 then values must be decimally shifted left
200+ // that many places, i.e. multiplied by Math.pow(10.0, decimals).
201+ public synchronized void addValues(long time, long... values) {
202+ assert (values.length == seqs.size());
203+ times.add(time);
204+ for (int i = 0; i < values.length; i++) {
205+ seqs.get(i).add(values[i]);
206+ }
207+ repaint();
208+ }
209+
210+ private Sequence getSequence(String key) {
211+ for (Sequence seq : seqs) {
212+ if (seq.key.equals(key)) {
213+ return seq;
214+ }
215+ }
216+ return null;
217+ }
218+
219+ /**
220+ * @return the displayed time range in minutes, or -1 for all data
221+ */
222+ public int getViewRange() {
223+ return viewRange;
224+ }
225+
226+ /**
227+ * @param minutes the displayed time range in minutes, or -1 to diaplay all data
228+ */
229+ public void setViewRange(int minutes) {
230+ if (minutes != viewRange) {
231+ int oldValue = viewRange;
232+ viewRange = minutes;
233+ /* Do not i18n this string */
234+ firePropertyChange("viewRange", oldValue, viewRange);
235+ if (popupMenu != null) {
236+ for (int i = 0; i < menuRBs.length; i++) {
237+ if (rangeValues[i] == viewRange) {
238+ menuRBs[i].setSelected(true);
239+ break;
240+ }
241+ }
242+ }
243+ repaint();
244+ }
245+ }
246+
247+ @Override
248+ public JPopupMenu getComponentPopupMenu() {
249+ if (popupMenu == null) {
250+ popupMenu = new JPopupMenu(Resources.getText("Chart:"));
251+ timeRangeMenu = new JMenu(Resources.getText("Plotter.timeRangeMenu"));
252+ timeRangeMenu.setMnemonic(getMnemonicInt("Plotter.timeRangeMenu"));
253+ popupMenu.add(timeRangeMenu);
254+ menuRBs = new JRadioButtonMenuItem[rangeNames.length];
255+ ButtonGroup rbGroup = new ButtonGroup();
256+ for (int i = 0; i < rangeNames.length; i++) {
257+ menuRBs[i] = new JRadioButtonMenuItem(rangeNames[i]);
258+ rbGroup.add(menuRBs[i]);
259+ menuRBs[i].addActionListener(this);
260+ if (viewRange == rangeValues[i]) {
261+ menuRBs[i].setSelected(true);
262+ }
263+ timeRangeMenu.add(menuRBs[i]);
264+ }
265+
266+ popupMenu.addSeparator();
267+
268+ saveAsMI = new JMenuItem(getText("Plotter.saveAsMenuItem"));
269+ saveAsMI.setMnemonic(getMnemonicInt("Plotter.saveAsMenuItem"));
270+ saveAsMI.addActionListener(this);
271+ popupMenu.add(saveAsMI);
272+ }
273+ return popupMenu;
274+ }
275+
276+ public void actionPerformed(ActionEvent ev) {
277+ JComponent src = (JComponent)ev.getSource();
278+ if (src == saveAsMI) {
279+ saveAs();
280+ } else {
281+ int index = timeRangeMenu.getPopupMenu().getComponentIndex(src);
282+ setViewRange(rangeValues[index]);
283+ }
284+ }
285+
286+ private void saveAs() {
287+ if (saveFC == null) {
288+ saveFC = new SaveDataFileChooser();
289+ }
290+ int ret = saveFC.showSaveDialog(this);
291+ if (ret == JFileChooser.APPROVE_OPTION) {
292+ saveDataToFile(saveFC.getSelectedFile());
293+ }
294+ }
295+
296+ private void saveDataToFile(File file) {
297+ try {
298+ PrintStream out = new PrintStream(new FileOutputStream(file));
299+
300+ // Print header line
301+ out.print("Time");
302+ for (Sequence seq : seqs) {
303+ out.print(","+seq.name);
304+ }
305+ out.println();
306+
307+ // Print data lines
308+ if (seqs.size() > 0 && seqs.get(0).size > 0) {
309+ for (int i = 0; i < seqs.get(0).size; i++) {
310+ double excelTime = toExcelTime(times.time(i));
311+ out.print(String.format(Locale.ENGLISH, "%.6f", excelTime));
312+ for (Sequence seq : seqs) {
313+ out.print("," + getFormattedValue(seq.value(i), false));
314+ }
315+ out.println();
316+ }
317+ }
318+
319+ out.close();
320+ JOptionPane.showMessageDialog(this,
321+ getText("FileChooser.savedFile",
322+ file.getAbsolutePath(),
323+ file.length()));
324+ } catch (IOException ex) {
325+ String msg = ex.getLocalizedMessage();
326+ String path = file.getAbsolutePath();
327+ if (msg.startsWith(path)) {
328+ msg = msg.substring(path.length()).trim();
329+ }
330+ JOptionPane.showMessageDialog(this,
331+ getText("FileChooser.saveFailed.message",
332+ path, msg),
333+ getText("FileChooser.saveFailed.title"),
334+ JOptionPane.ERROR_MESSAGE);
335+ }
336+ }
337+
338+ @Override
339+ public void paintComponent(Graphics g) {
340+ super.paintComponent(g);
341+
342+ Color oldColor = g.getColor();
343+ Font oldFont = g.getFont();
344+ Color fg = getForeground();
345+ Color bg = getBackground();
346+ boolean bgIsLight = (bg.getRed() > 200 &&
347+ bg.getGreen() > 200 &&
348+ bg.getBlue() > 200);
349+
350+
351+ ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
352+ RenderingHints.VALUE_ANTIALIAS_ON);
353+
354+ if (smallFont == null) {
355+ smallFont = oldFont.deriveFont(9.0F);
356+ }
357+
358+ r.x = leftMargin - 5;
359+ r.y = topMargin - 8;
360+ r.width = getWidth()-leftMargin-rightMargin;
361+ r.height = getHeight()-topMargin-bottomMargin+16;
362+
363+ if (border == null) {
364+ // By setting colors here, we avoid recalculating them
365+ // over and over.
366+ border = new BevelBorder(BevelBorder.LOWERED,
367+ getBackground().brighter().brighter(),
368+ getBackground().brighter(),
369+ getBackground().darker().darker(),
370+ getBackground().darker());
371+ }
372+
373+ border.paintBorder(this, g, r.x, r.y, r.width, r.height);
374+
375+ // Fill background color
376+ g.setColor(bgColor);
377+ g.fillRect(r.x+2, r.y+2, r.width-4, r.height-4);
378+ g.setColor(oldColor);
379+
380+ long tMin = Long.MAX_VALUE;
381+ long tMax = Long.MIN_VALUE;
382+ long vMin = Long.MAX_VALUE;
383+ long vMax = 1;
384+
385+ int w = getWidth()-rightMargin-leftMargin-10;
386+ int h = getHeight()-topMargin-bottomMargin;
387+
388+ if (times.size > 1) {
389+ tMin = Math.min(tMin, times.time(0));
390+ tMax = Math.max(tMax, times.time(times.size-1));
391+ }
392+ long viewRangeMS;
393+ if (viewRange > 0) {
394+ viewRangeMS = viewRange * MINUTE;
395+ } else {
396+ // Display full time range, but no less than a minute
397+ viewRangeMS = Math.max(tMax - tMin, 1 * MINUTE);
398+ }
399+
400+ // Calculate min/max values
401+ for (Sequence seq : seqs) {
402+ if (seq.size > 0) {
403+ for (int i = 0; i < seq.size; i++) {
404+ if (seq.size == 1 || times.time(i) >= tMax - viewRangeMS) {
405+ long val = seq.value(i);
406+ if (val > Long.MIN_VALUE) {
407+ vMax = Math.max(vMax, val);
408+ vMin = Math.min(vMin, val);
409+ }
410+ }
411+ }
412+ } else {
413+ vMin = 0L;
414+ }
415+ if (unit == Unit.BYTES || !seq.isPlotted) {
416+ // We'll scale only to the first (main) value set.
417+ // TODO: Use a separate property for this.
418+ break;
419+ }
420+ }
421+
422+ // Normalize scale
423+ vMax = normalizeMax(vMax);
424+ if (vMin > 0) {
425+ if (vMax / vMin > 4) {
426+ vMin = 0;
427+ } else {
428+ vMin = normalizeMin(vMin);
429+ }
430+ }
431+
432+
433+ g.setColor(fg);
434+
435+ // Axes
436+ // Draw vertical axis
437+ int x = leftMargin - 18;
438+ int y = topMargin;
439+ FontMetrics fm = g.getFontMetrics();
440+
441+ g.drawLine(x, y, x, y+h);
442+
443+ int n = 5;
444+ if ((""+vMax).startsWith("2")) {
445+ n = 4;
446+ } else if ((""+vMax).startsWith("3")) {
447+ n = 6;
448+ } else if ((""+vMax).startsWith("4")) {
449+ n = 4;
450+ } else if ((""+vMax).startsWith("6")) {
451+ n = 6;
452+ } else if ((""+vMax).startsWith("7")) {
453+ n = 7;
454+ } else if ((""+vMax).startsWith("8")) {
455+ n = 8;
456+ } else if ((""+vMax).startsWith("9")) {
457+ n = 3;
458+ }
459+
460+ // Ticks
461+ ArrayList<Long> tickValues = new ArrayList<Long>();
462+ tickValues.add(vMin);
463+ for (int i = 0; i < n; i++) {
464+ long v = i * vMax / n;
465+ if (v > vMin) {
466+ tickValues.add(v);
467+ }
468+ }
469+ tickValues.add(vMax);
470+ n = tickValues.size();
471+
472+ String[] tickStrings = new String[n];
473+ for (int i = 0; i < n; i++) {
474+ long v = tickValues.get(i);
475+ tickStrings[i] = getSizeString(v, vMax);
476+ }
477+
478+ // Trim trailing decimal zeroes.
479+ if (decimals > 0) {
480+ boolean trimLast = true;
481+ boolean removedDecimalPoint = false;
482+ do {
483+ for (String str : tickStrings) {
484+ if (!(str.endsWith("0") || str.endsWith("."))) {
485+ trimLast = false;
486+ break;
487+ }
488+ }
489+ if (trimLast) {
490+ if (tickStrings[0].endsWith(".")) {
491+ removedDecimalPoint = true;
492+ }
493+ for (int i = 0; i < n; i++) {
494+ String str = tickStrings[i];
495+ tickStrings[i] = str.substring(0, str.length()-1);
496+ }
497+ }
498+ } while (trimLast && !removedDecimalPoint);
499+ }
500+
501+ // Draw ticks
502+ int lastY = Integer.MAX_VALUE;
503+ for (int i = 0; i < n; i++) {
504+ long v = tickValues.get(i);
505+ y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin));
506+ g.drawLine(x-2, y, x+2, y);
507+ String s = tickStrings[i];
508+ if (unit == Unit.PERCENT) {
509+ s += "%";
510+ }
511+ int sx = x-6-fm.stringWidth(s);
512+ if (y < lastY-13) {
513+ if (checkLeftMargin(sx)) {
514+ // Wait for next repaint
515+ return;
516+ }
517+ g.drawString(s, sx, y+4);
518+ }
519+ // Draw horizontal grid line
520+ g.setColor(Color.lightGray);
521+ g.drawLine(r.x + 4, y, r.x + r.width - 4, y);
522+ g.setColor(fg);
523+ lastY = y;
524+ }
525+
526+ // Draw horizontal axis
527+ x = leftMargin;
528+ y = topMargin + h + 15;
529+ g.drawLine(x, y, x+w, y);
530+
531+ long t1 = tMax;
532+ if (t1 <= 0L) {
533+ // No data yet, so draw current time
534+ t1 = System.currentTimeMillis();
535+ }
536+ long tz = timeDF.getTimeZone().getOffset(t1);
537+ long tickInterval = calculateTickInterval(w, 40, viewRangeMS);
538+ if (tickInterval > 3 * HOUR) {
539+ tickInterval = calculateTickInterval(w, 80, viewRangeMS);
540+ }
541+ long t0 = tickInterval - (t1 - viewRangeMS + tz) % tickInterval;
542+ while (t0 < viewRangeMS) {
543+ x = leftMargin + (int)(w * t0 / viewRangeMS);
544+ g.drawLine(x, y-2, x, y+2);
545+
546+ long t = t1 - viewRangeMS + t0;
547+ String str = formatClockTime(t);
548+ g.drawString(str, x, y+16);
549+ //if (tickInterval > (1 * HOUR) && t % (1 * DAY) == 0) {
550+ if ((t + tz) % (1 * DAY) == 0) {
551+ str = formatDate(t);
552+ g.drawString(str, x, y+27);
553+ }
554+ // Draw vertical grid line
555+ g.setColor(Color.lightGray);
556+ g.drawLine(x, topMargin, x, topMargin + h);
557+ g.setColor(fg);
558+ t0 += tickInterval;
559+ }
560+
561+ // Plot values
562+ int start = 0;
563+ int nValues = 0;
564+ int nLists = seqs.size();
565+ if (nLists > 0) {
566+ nValues = seqs.get(0).size;
567+ }
568+ if (nValues == 0) {
569+ g.setColor(oldColor);
570+ return;
571+ } else {
572+ Sequence seq = seqs.get(0);
573+ // Find starting point
574+ for (int p = 0; p < seq.size; p++) {
575+ if (times.time(p) >= tMax - viewRangeMS) {
576+ start = p;
577+ break;
578+ }
579+ }
580+ }
581+
582+ //Optimization: collapse plot of more than four values per pixel
583+ int pointsPerPixel = (nValues - start) / w;
584+ if (pointsPerPixel < 4) {
585+ pointsPerPixel = 1;
586+ }
587+
588+ // Draw graphs
589+ // Loop backwards over sequences because the first needs to be painted on top
590+ for (int i = nLists-1; i >= 0; i--) {
591+ int x0 = leftMargin;
592+ int y0 = topMargin + h + 1;
593+
594+ Sequence seq = seqs.get(i);
595+ if (seq.isPlotted && seq.size > 0) {
596+ // Paint twice, with white and with color
597+ for (int pass = 0; pass < 2; pass++) {
598+ g.setColor((pass == 0) ? Color.white : seq.color);
599+ int x1 = -1;
600+ long v1 = -1;
601+ for (int p = start; p < nValues; p += pointsPerPixel) {
602+ // Make sure we get the last value
603+ if (pointsPerPixel > 1 && p >= nValues - pointsPerPixel) {
604+ p = nValues - 1;
605+ }
606+ int x2 = (int)(w * (times.time(p)-(t1-viewRangeMS)) / viewRangeMS);
607+ long v2 = seq.value(p);
608+ if (v2 >= vMin && v2 <= vMax) {
609+ int y2 = (int)(h * (v2 -vMin) / (vMax-vMin));
610+ if (x1 >= 0 && v1 >= vMin && v1 <= vMax) {
611+ int y1 = (int)(h * (v1-vMin) / (vMax-vMin));
612+
613+ if (y1 == y2) {
614+ // fillrect is much faster
615+ g.fillRect(x0+x1, y0-y1-pass, x2-x1, 1);
616+ } else {
617+ Graphics2D g2d = (Graphics2D)g;
618+ Stroke oldStroke = null;
619+ if (seq.transitionStroke != null) {
620+ oldStroke = g2d.getStroke();
621+ g2d.setStroke(seq.transitionStroke);
622+ }
623+ g.drawLine(x0+x1, y0-y1-pass, x0+x2, y0-y2-pass);
624+ if (oldStroke != null) {
625+ g2d.setStroke(oldStroke);
626+ }
627+ }
628+ }
629+ }
630+ x1 = x2;
631+ v1 = v2;
632+ }
633+ }
634+
635+ // Current value
636+ long v = seq.value(seq.size - 1);
637+ if (v >= vMin && v <= vMax) {
638+ if (bgIsLight) {
639+ g.setColor(seq.color);
640+ } else {
641+ g.setColor(fg);
642+ }
643+ x = r.x + r.width + 2;
644+ y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin));
645+ // a small triangle/arrow
646+ g.fillPolygon(new int[] { x+2, x+6, x+6 },
647+ new int[] { y, y+3, y-3 },
648+ 3);
649+ }
650+ g.setColor(fg);
651+ }
652+ }
653+
654+ int[] valueStringSlots = new int[nLists];
655+ for (int i = 0; i < nLists; i++) valueStringSlots[i] = -1;
656+ for (int i = 0; i < nLists; i++) {
657+ Sequence seq = seqs.get(i);
658+ if (seq.isPlotted && seq.size > 0) {
659+ // Draw current value
660+
661+ // TODO: collapse values if pointsPerPixel >= 4
662+
663+ long v = seq.value(seq.size - 1);
664+ if (v >= vMin && v <= vMax) {
665+ x = r.x + r.width + 2;
666+ y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin));
667+ int y2 = getValueStringSlot(valueStringSlots, y, 2*10, i);
668+ g.setFont(smallFont);
669+ if (bgIsLight) {
670+ g.setColor(seq.color);
671+ } else {
672+ g.setColor(fg);
673+ }
674+ String curValue = getFormattedValue(v, true);
675+ if (unit == Unit.PERCENT) {
676+ curValue += "%";
677+ }
678+ int valWidth = fm.stringWidth(curValue);
679+ String legend = (displayLegend?seq.name:"");
680+ int legendWidth = fm.stringWidth(legend);
681+ if (checkRightMargin(valWidth) || checkRightMargin(legendWidth)) {
682+ // Wait for next repaint
683+ return;
684+ }
685+ g.drawString(legend , x + 17, Math.min(topMargin+h, y2 + 3 - 10));
686+ g.drawString(curValue, x + 17, Math.min(topMargin+h + 10, y2 + 3));
687+
688+ // Maybe draw a short line to value
689+ if (y2 > y + 3) {
690+ g.drawLine(x + 9, y + 2, x + 14, y2);
691+ } else if (y2 < y - 3) {
692+ g.drawLine(x + 9, y - 2, x + 14, y2);
693+ }
694+ }
695+ g.setFont(oldFont);
696+ g.setColor(fg);
697+
698+ }
699+ }
700+ g.setColor(oldColor);
701+ }
702+
703+ private boolean checkLeftMargin(int x) {
704+ // Make sure leftMargin has at least 2 pixels over
705+ if (x < 2) {
706+ leftMargin += (2 - x);
707+ // Repaint from top (above any cell renderers)
708+ SwingUtilities.getWindowAncestor(this).repaint();
709+ return true;
710+ }
711+ return false;
712+ }
713+
714+ private boolean checkRightMargin(int w) {
715+ // Make sure rightMargin has at least 2 pixels over
716+ if (w + 2 > rightMargin) {
717+ rightMargin = (w + 2);
718+ // Repaint from top (above any cell renderers)
719+ SwingUtilities.getWindowAncestor(this).repaint();
720+ return true;
721+ }
722+ return false;
723+ }
724+
725+ private int getValueStringSlot(int[] slots, int y, int h, int i) {
726+ for (int s = 0; s < slots.length; s++) {
727+ if (slots[s] >= y && slots[s] < y + h) {
728+ // collide below us
729+ if (slots[s] > h) {
730+ return getValueStringSlot(slots, slots[s]-h, h, i);
731+ } else {
732+ return getValueStringSlot(slots, slots[s]+h, h, i);
733+ }
734+ } else if (y >= h && slots[s] > y - h && slots[s] < y) {
735+ // collide above us
736+ return getValueStringSlot(slots, slots[s]+h, h, i);
737+ }
738+ }
739+ slots[i] = y;
740+ return y;
741+ }
742+
743+ private long calculateTickInterval(int w, int hGap, long viewRangeMS) {
744+ long tickInterval = viewRangeMS * hGap / w;
745+ if (tickInterval < 1 * MINUTE) {
746+ tickInterval = 1 * MINUTE;
747+ } else if (tickInterval < 5 * MINUTE) {
748+ tickInterval = 5 * MINUTE;
749+ } else if (tickInterval < 10 * MINUTE) {
750+ tickInterval = 10 * MINUTE;
751+ } else if (tickInterval < 30 * MINUTE) {
752+ tickInterval = 30 * MINUTE;
753+ } else if (tickInterval < 1 * HOUR) {
754+ tickInterval = 1 * HOUR;
755+ } else if (tickInterval < 3 * HOUR) {
756+ tickInterval = 3 * HOUR;
757+ } else if (tickInterval < 6 * HOUR) {
758+ tickInterval = 6 * HOUR;
759+ } else if (tickInterval < 12 * HOUR) {
760+ tickInterval = 12 * HOUR;
761+ } else if (tickInterval < 1 * DAY) {
762+ tickInterval = 1 * DAY;
763+ } else {
764+ tickInterval = normalizeMax(tickInterval / DAY) * DAY;
765+ }
766+ return tickInterval;
767+ }
768+
769+ private long normalizeMin(long l) {
770+ int exp = (int)Math.log10((double)l);
771+ long multiple = (long)Math.pow(10.0, exp);
772+ int i = (int)(l / multiple);
773+ return i * multiple;
774+ }
775+
776+ private long normalizeMax(long l) {
777+ int exp = (int)Math.log10((double)l);
778+ long multiple = (long)Math.pow(10.0, exp);
779+ int i = (int)(l / multiple);
780+ l = (i+1)*multiple;
781+ return l;
782+ }
783+
784+ private String getFormattedValue(long v, boolean groupDigits) {
785+ String str;
786+ String fmt = "%";
787+ if (groupDigits) {
788+ fmt += ",";
789+ }
790+ if (decimals > 0) {
791+ fmt += "." + decimals + "f";
792+ str = String.format(fmt, v / decimalsMultiplier);
793+ } else {
794+ fmt += "d";
795+ str = String.format(fmt, v);
796+ }
797+ return str;
798+ }
799+
800+ private String getSizeString(long v, long vMax) {
801+ String s;
802+
803+ if (unit == Unit.BYTES && decimals == 0) {
804+ s = formatBytes(v, vMax);
805+ } else {
806+ s = getFormattedValue(v, true);
807+ }
808+ return s;
809+ }
810+
811+ private static synchronized Stroke getDashedStroke() {
812+ if (dashedStroke == null) {
813+ dashedStroke = new BasicStroke(1.0f,
814+ BasicStroke.CAP_BUTT,
815+ BasicStroke.JOIN_MITER,
816+ 10.0f,
817+ new float[] { 2.0f, 3.0f },
818+ 0.0f);
819+ }
820+ return dashedStroke;
821+ }
822+
823+ private static Object extendArray(Object a1) {
824+ int n = Array.getLength(a1);
825+ Object a2 =
826+ Array.newInstance(a1.getClass().getComponentType(),
827+ n + ARRAY_SIZE_INCREMENT);
828+ System.arraycopy(a1, 0, a2, 0, n);
829+ return a2;
830+ }
831+
832+
833+ private static class TimeStamps {
834+ // Time stamps (long) are split into offsets (long) and a
835+ // series of times from the offsets (int). A new offset is
836+ // stored when the the time value doesn't fit in an int
837+ // (approx every 24 days). An array of indices is used to
838+ // define the starting point for each offset in the times
839+ // array.
840+ long[] offsets = new long[0];
841+ int[] indices = new int[0];
842+ int[] rtimes = new int[ARRAY_SIZE_INCREMENT];
843+
844+ // Number of stored timestamps
845+ int size = 0;
846+
847+ /**
848+ * Returns the time stamp for index i
849+ */
850+ public long time(int i) {
851+ long offset = 0;
852+ for (int j = indices.length - 1; j >= 0; j--) {
853+ if (i >= indices[j]) {
854+ offset = offsets[j];
855+ break;
856+ }
857+ }
858+ return offset + rtimes[i];
859+ }
860+
861+ public void add(long time) {
862+ // May need to store a new time offset
863+ int n = offsets.length;
864+ if (n == 0 || time - offsets[n - 1] > Integer.MAX_VALUE) {
865+ // Grow offset and indices arrays and store new offset
866+ offsets = Arrays.copyOf(offsets, n + 1);
867+ offsets[n] = time;
868+ indices = Arrays.copyOf(indices, n + 1);
869+ indices[n] = size;
870+ }
871+
872+ // May need to extend the array size
873+ if (rtimes.length == size) {
874+ rtimes = (int[])extendArray(rtimes);
875+ }
876+
877+ // Store the time
878+ rtimes[size] = (int)(time - offsets[offsets.length - 1]);
879+ size++;
880+ }
881+ }
882+
883+ private static class Sequence {
884+ String key;
885+ String name;
886+ Color color;
887+ boolean isPlotted;
888+ Stroke transitionStroke = null;
889+
890+ // Values are stored in an int[] if all values will fit,
891+ // otherwise in a long[]. An int can represent up to 2 GB.
892+ // Use a random start size, so all arrays won't need to
893+ // be grown during the same update interval
894+ Object values =
895+ new byte[ARRAY_SIZE_INCREMENT + (int)(Math.random() * 100)];
896+
897+ // Number of stored values
898+ int size = 0;
899+
900+ public Sequence(String key) {
901+ this.key = key;
902+ }
903+
904+ /**
905+ * Returns the value at index i
906+ */
907+ public long value(int i) {
908+ return Array.getLong(values, i);
909+ }
910+
911+ public void add(long value) {
912+ // May need to switch to a larger array type
913+ if ((values instanceof byte[] ||
914+ values instanceof short[] ||
915+ values instanceof int[]) &&
916+ value > Integer.MAX_VALUE) {
917+ long[] la = new long[Array.getLength(values)];
918+ for (int i = 0; i < size; i++) {
919+ la[i] = Array.getLong(values, i);
920+ }
921+ values = la;
922+ } else if ((values instanceof byte[] ||
923+ values instanceof short[]) &&
924+ value > Short.MAX_VALUE) {
925+ int[] ia = new int[Array.getLength(values)];
926+ for (int i = 0; i < size; i++) {
927+ ia[i] = Array.getInt(values, i);
928+ }
929+ values = ia;
930+ } else if (values instanceof byte[] &&
931+ value > Byte.MAX_VALUE) {
932+ short[] sa = new short[Array.getLength(values)];
933+ for (int i = 0; i < size; i++) {
934+ sa[i] = Array.getShort(values, i);
935+ }
936+ values = sa;
937+ }
938+
939+ // May need to extend the array size
940+ if (Array.getLength(values) == size) {
941+ values = extendArray(values);
942+ }
943+
944+ // Store the value
945+ if (values instanceof long[]) {
946+ ((long[])values)[size] = value;
947+ } else if (values instanceof int[]) {
948+ ((int[])values)[size] = (int)value;
949+ } else if (values instanceof short[]) {
950+ ((short[])values)[size] = (short)value;
951+ } else {
952+ ((byte[])values)[size] = (byte)value;
953+ }
954+ size++;
955+ }
956+ }
957+
958+ // Can be overridden by subclasses
959+ long getValue() {
960+ return 0;
961+ }
962+
963+ long getLastTimeStamp() {
964+ return times.time(times.size - 1);
965+ }
966+
967+ long getLastValue(String key) {
968+ Sequence seq = getSequence(key);
969+ return (seq != null && seq.size > 0) ? seq.value(seq.size - 1) : 0L;
970+ }
971+
972+
973+ // Called on EDT
974+ public void propertyChange(PropertyChangeEvent ev) {
975+ String prop = ev.getPropertyName();
976+
977+ if (prop == JConsoleContext.CONNECTION_STATE_PROPERTY) {
978+ ConnectionState newState = (ConnectionState)ev.getNewValue();
979+
980+ switch (newState) {
981+ case DISCONNECTED:
982+ synchronized(this) {
983+ long time = System.currentTimeMillis();
984+ times.add(time);
985+ for (Sequence seq : seqs) {
986+ seq.add(Long.MIN_VALUE);
987+ }
988+ }
989+ break;
990+ }
991+ }
992+ }
993+
994+ private static class SaveDataFileChooser extends JFileChooser {
995+ private static final long serialVersionUID = -5182890922369369669L;
996+ SaveDataFileChooser() {
997+ setFileFilter(new FileNameExtensionFilter("CSV file", "csv"));
998+ }
999+
1000+ @Override
1001+ public void approveSelection() {
1002+ File file = getSelectedFile();
1003+ if (file != null) {
1004+ FileFilter filter = getFileFilter();
1005+ if (filter != null && filter instanceof FileNameExtensionFilter) {
1006+ String[] extensions =
1007+ ((FileNameExtensionFilter)filter).getExtensions();
1008+
1009+ boolean goodExt = false;
1010+ for (String ext : extensions) {
1011+ if (file.getName().toLowerCase().endsWith("." + ext.toLowerCase())) {
1012+ goodExt = true;
1013+ break;
1014+ }
1015+ }
1016+ if (!goodExt) {
1017+ file = new File(file.getParent(),
1018+ file.getName() + "." + extensions[0]);
1019+ }
1020+ }
1021+
1022+ if (file.exists()) {
1023+ String okStr = getText("FileChooser.fileExists.okOption");
1024+ String cancelStr = getText("FileChooser.fileExists.cancelOption");
1025+ int ret =
1026+ JOptionPane.showOptionDialog(this,
1027+ getText("FileChooser.fileExists.message",
1028+ file.getName()),
1029+ getText("FileChooser.fileExists.title"),
1030+ JOptionPane.OK_CANCEL_OPTION,
1031+ JOptionPane.WARNING_MESSAGE,
1032+ null,
1033+ new Object[] { okStr, cancelStr },
1034+ okStr);
1035+ if (ret != JOptionPane.OK_OPTION) {
1036+ return;
1037+ }
1038+ }
1039+ setSelectedFile(file);
1040+ }
1041+ super.approveSelection();
1042+ }
1043+ }
1044+
1045+ @Override
1046+ public AccessibleContext getAccessibleContext() {
1047+ if (accessibleContext == null) {
1048+ accessibleContext = new AccessiblePlotter();
1049+ }
1050+ return accessibleContext;
1051+ }
1052+
1053+ protected class AccessiblePlotter extends AccessibleJComponent {
1054+ private static final long serialVersionUID = -3847205410473510922L;
1055+ protected AccessiblePlotter() {
1056+ setAccessibleName(getText("Plotter.accessibleName"));
1057+ }
1058+
1059+ @Override
1060+ public String getAccessibleName() {
1061+ String name = super.getAccessibleName();
1062+
1063+ if (seqs.size() > 0 && seqs.get(0).size > 0) {
1064+ String keyValueList = "";
1065+ for (Sequence seq : seqs) {
1066+ if (seq.isPlotted) {
1067+ String value = "null";
1068+ if (seq.size > 0) {
1069+ if (unit == Unit.BYTES) {
1070+ value = getText("Size Bytes", seq.value(seq.size - 1));
1071+ } else {
1072+ value =
1073+ getFormattedValue(seq.value(seq.size - 1), false) +
1074+ ((unit == Unit.PERCENT) ? "%" : "");
1075+ }
1076+ }
1077+ // Assume format string ends with newline
1078+ keyValueList +=
1079+ getText("Plotter.accessibleName.keyAndValue",
1080+ seq.key, value);
1081+ }
1082+ }
1083+ name += "\n" + keyValueList + ".";
1084+ } else {
1085+ name += "\n" + getText("Plotter.accessibleName.noData");
1086+ }
1087+ return name;
1088+ }
1089+
1090+ @Override
1091+ public AccessibleRole getAccessibleRole() {
1092+ return AccessibleRole.CANVAS;
1093+ }
1094+ }
1095+}