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
26package sun.tools.jconsole;
27
28import java.awt.*;
29import java.awt.event.*;
30import java.beans.*;
31import java.io.*;
32import java.lang.reflect.Array;
33import java.util.*;
34
35import javax.accessibility.*;
36import javax.swing.*;
37import javax.swing.border.*;
38import javax.swing.filechooser.*;
39import javax.swing.filechooser.FileFilter;
40
41import com.sun.tools.jconsole.JConsoleContext;
42
43import static com.sun.tools.jconsole.JConsoleContext.ConnectionState.*;
44
45import static sun.tools.jconsole.Formatter.*;
46import static sun.tools.jconsole.ProxyClient.*;
47import static sun.tools.jconsole.Resources.*;
48import static sun.tools.jconsole.Utilities.*;
49
50@SuppressWarnings("serial")
51public 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}