// Draw ticks int lastY = Integer.MAX_VALUE; for (int i = 0; i < n; i++) { long v = tickValues.get(i); y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin)); g.drawLine(x-2, y, x+2, y); String s = tickStrings[i]; if (unit == Unit.PERCENT) { s += "%"; } int sx = x-6-fm.stringWidth(s); if (y < lastY-13) { if (checkLeftMargin(sx)) { // Wait for next repaint return; } g.drawString(s, sx, y+4); } // Draw horizontal grid line g.setColor(Color.lightGray); g.drawLine(r.x + 4, y, r.x + r.width - 4, y); g.setColor(fg); lastY = y; } // Draw horizontal axis x = leftMargin; y = topMargin + h + 15; g.drawLine(x, y, x+w, y); long t1 = tMax; if (t1 <= 0L) { // No data yet, so draw current time t1 = System.currentTimeMillis(); } long tz = timeDF.getTimeZone().getOffset(t1); long tickInterval = calculateTickInterval(w, 40, viewRangeMS); if (tickInterval > 3 * HOUR) { tickInterval = calculateTickInterval(w, 80, viewRangeMS); } long t0 = tickInterval - (t1 - viewRangeMS + tz) % tickInterval; while (t0 < viewRangeMS) { x = leftMargin + (int)(w * t0 / viewRangeMS); g.drawLine(x, y-2, x, y+2); long t = t1 - viewRangeMS + t0; String str = formatClockTime(t); g.drawString(str, x, y+16); //if (tickInterval > (1 * HOUR) && t % (1 * DAY) == 0) { if ((t + tz) % (1 * DAY) == 0) { str = formatDate(t); g.drawString(str, x, y+27); } // Draw vertical grid line g.setColor(Color.lightGray); g.drawLine(x, topMargin, x, topMargin + h); g.setColor(fg); t0 += tickInterval; } // Plot values int start = 0; int nValues = 0; int nLists = seqs.size(); if (nLists > 0) { nValues = seqs.get(0).size; } if (nValues == 0) { g.setColor(oldColor); return; } else { Sequence seq = seqs.get(0); // Find starting point for (int p = 0; p < seq.size; p++) { if (times.time(p) >= tMax - viewRangeMS) { start = p; break; } } } //Optimization: collapse plot of more than four values per pixel int pointsPerPixel = (nValues - start) / w; if (pointsPerPixel < 4) { pointsPerPixel = 1; } // Draw graphs // Loop backwards over sequences because the first needs to be painted on top for (int i = nLists-1; i >= 0; i--) { int x0 = leftMargin; int y0 = topMargin + h + 1; Sequence seq = seqs.get(i); if (seq.isPlotted && seq.size > 0) { // Paint twice, with white and with color for (int pass = 0; pass < 2; pass++) { g.setColor((pass == 0) ? Color.white : seq.color); int x1 = -1; long v1 = -1; for (int p = start; p < nValues; p += pointsPerPixel) { // Make sure we get the last value if (pointsPerPixel > 1 && p >= nValues - pointsPerPixel) { p = nValues - 1; } int x2 = (int)(w * (times.time(p)-(t1-viewRangeMS)) / viewRangeMS); long v2 = seq.value(p); if (v2 >= vMin && v2 <= vMax) { int y2 = (int)(h * (v2 -vMin) / (vMax-vMin)); if (x1 >= 0 && v1 >= vMin && v1 <= vMax) { int y1 = (int)(h * (v1-vMin) / (vMax-vMin)); if (y1 == y2) { // fillrect is much faster g.fillRect(x0+x1, y0-y1-pass, x2-x1, 1); } else { Graphics2D g2d = (Graphics2D)g; Stroke oldStroke = null; if (seq.transitionStroke != null) { oldStroke = g2d.getStroke(); g2d.setStroke(seq.transitionStroke); } g.drawLine(x0+x1, y0-y1-pass, x0+x2, y0-y2-pass); if (oldStroke != null) { g2d.setStroke(oldStroke); } } } } x1 = x2; v1 = v2; } } // Current value long v = seq.value(seq.size - 1); if (v >= vMin && v <= vMax) { if (bgIsLight) { g.setColor(seq.color); } else { g.setColor(fg); } x = r.x + r.width + 2; y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin)); // a small triangle/arrow g.fillPolygon(new int[] { x+2, x+6, x+6 }, new int[] { y, y+3, y-3 }, 3); } g.setColor(fg); } } int[] valueStringSlots = new int[nLists]; for (int i = 0; i < nLists; i++) valueStringSlots[i] = -1; for (int i = 0; i < nLists; i++) { Sequence seq = seqs.get(i); if (seq.isPlotted && seq.size > 0) { // Draw current value // TODO: collapse values if pointsPerPixel >= 4 long v = seq.value(seq.size - 1); if (v >= vMin && v <= vMax) { x = r.x + r.width + 2; y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin)); int y2 = getValueStringSlot(valueStringSlots, y, 2*10, i); g.setFont(smallFont); if (bgIsLight) { g.setColor(seq.color); } else { g.setColor(fg); } String curValue = getFormattedValue(v, true); if (unit == Unit.PERCENT) { curValue += "%"; } int valWidth = fm.stringWidth(curValue); String legend = (displayLegend?seq.name:""); int legendWidth = fm.stringWidth(legend); if (checkRightMargin(valWidth) || checkRightMargin(legendWidth)) { // Wait for next repaint return; } g.drawString(legend , x + 17, Math.min(topMargin+h, y2 + 3 - 10)); g.drawString(curValue, x + 17, Math.min(topMargin+h + 10, y2 + 3)); // Maybe draw a short line to value if (y2 > y + 3) { g.drawLine(x + 9, y + 2, x + 14, y2); } else if (y2 < y - 3) { g.drawLine(x + 9, y - 2, x + 14, y2); } } g.setFont(oldFont); g.setColor(fg); } } g.setColor(oldColor); } private boolean checkLeftMargin(int x) { // Make sure leftMargin has at least 2 pixels over if (x < 2) { leftMargin += (2 - x); // Repaint from top (above any cell renderers) SwingUtilities.getWindowAncestor(this).repaint(); return true; } return false; } private boolean checkRightMargin(int w) { // Make sure rightMargin has at least 2 pixels over if (w + 2 > rightMargin) { rightMargin = (w + 2); // Repaint from top (above any cell renderers) SwingUtilities.getWindowAncestor(this).repaint(); return true; } return false; } private int getValueStringSlot(int[] slots, int y, int h, int i) { for (int s = 0; s < slots.length; s++) { if (slots[s] >= y && slots[s] < y + h) { // collide below us if (slots[s] > h) { return getValueStringSlot(slots, slots[s]-h, h, i); } else { return getValueStringSlot(slots, slots[s]+h, h, i); } } else if (y >= h && slots[s] > y - h && slots[s] < y) { // collide above us return getValueStringSlot(slots, slots[s]+h, h, i); } } slots[i] = y; return y; } private long calculateTickInterval(int w, int hGap, long viewRangeMS) { long tickInterval = viewRangeMS * hGap / w; if (tickInterval < 1 * MINUTE) { tickInterval = 1 * MINUTE; } else if (tickInterval < 5 * MINUTE) { tickInterval = 5 * MINUTE; } else if (tickInterval < 10 * MINUTE) { tickInterval = 10 * MINUTE; } else if (tickInterval < 30 * MINUTE) { tickInterval = 30 * MINUTE; } else if (tickInterval < 1 * HOUR) { tickInterval = 1 * HOUR; } else if (tickInterval < 3 * HOUR) { tickInterval = 3 * HOUR; } else if (tickInterval < 6 * HOUR) { tickInterval = 6 * HOUR; } else if (tickInterval < 12 * HOUR) { tickInterval = 12 * HOUR; } else if (tickInterval < 1 * DAY) { tickInterval = 1 * DAY; } else { tickInterval = normalizeMax(tickInterval / DAY) * DAY; } return tickInterval; } private long normalizeMin(long l) { int exp = (int)Math.log10((double)l); long multiple = (long)Math.pow(10.0, exp); int i = (int)(l / multiple); return i * multiple; } private long normalizeMax(long l) { int exp = (int)Math.log10((double)l); long multiple = (long)Math.pow(10.0, exp); int i = (int)(l / multiple); l = (i+1)*multiple; return l; } private String getFormattedValue(long v, boolean groupDigits) { String str; String fmt = "%"; if (groupDigits) { fmt += ","; } if (decimals > 0) { fmt += "." + decimals + "f"; str = String.format(fmt, v / decimalsMultiplier); } else { fmt += "d"; str = String.format(fmt, v); } return str; } private String getSizeString(long v, long vMax) { String s; if (unit == Unit.BYTES && decimals == 0) { s = formatBytes(v, vMax); } else { s = getFormattedValue(v, true); } return s; } private static synchronized Stroke getDashedStroke() { if (dashedStroke == null) { dashedStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[] { 2.0f, 3.0f }, 0.0f); } return dashedStroke; } private static Object extendArray(Object a1) { int n = Array.getLength(a1); Object a2 = Array.newInstance(a1.getClass().getComponentType(), n + ARRAY_SIZE_INCREMENT); System.arraycopy(a1, 0, a2, 0, n); return a2; } private static class TimeStamps { // Time stamps (long) are split into offsets (long) and a // series of times from the offsets (int). A new offset is // stored when the the time value doesn't fit in an int // (approx every 24 days). An array of indices is used to // define the starting point for each offset in the times // array. long[] offsets = new long[0]; int[] indices = new int[0]; int[] rtimes = new int[ARRAY_SIZE_INCREMENT]; // Number of stored timestamps int size = 0; /** * Returns the time stamp for index i */ public long time(int i) { long offset = 0; for (int j = indices.length - 1; j >= 0; j--) { if (i >= indices[j]) { offset = offsets[j]; break; } } return offset + rtimes[i]; } public void add(long time) { // May need to store a new time offset int n = offsets.length; if (n == 0 || time - offsets[n - 1] > Integer.MAX_VALUE) { // Grow offset and indices arrays and store new offset offsets = Arrays.copyOf(offsets, n + 1); offsets[n] = time; indices = Arrays.copyOf(indices, n + 1); indices[n] = size; } // May need to extend the array size if (rtimes.length == size) { rtimes = (int[])extendArray(rtimes); } // Store the time rtimes[size] = (int)(time - offsets[offsets.length - 1]); size++; } } private static class Sequence { String key; String name; Color color; boolean isPlotted; Stroke transitionStroke = null; // Values are stored in an int[] if all values will fit, // otherwise in a long[]. An int can represent up to 2 GB. // Use a random start size, so all arrays won't need to // be grown during the same update interval Object values = new byte[ARRAY_SIZE_INCREMENT + (int)(Math.random() * 100)]; // Number of stored values int size = 0; public Sequence(String key) { this.key = key; } /** * Returns the value at index i */ public long value(int i) { return Array.getLong(values, i); } public void add(long value) { // May need to switch to a larger array type if ((values instanceof byte[] || values instanceof short[] || values instanceof int[]) && value > Integer.MAX_VALUE) { long[] la = new long[Array.getLength(values)]; for (int i = 0; i < size; i++) { la[i] = Array.getLong(values, i); } values = la; } else if ((values instanceof byte[] || values instanceof short[]) && value > Short.MAX_VALUE) { int[] ia = new int[Array.getLength(values)]; for (int i = 0; i < size; i++) { ia[i] = Array.getInt(values, i); } values = ia; } else if (values instanceof byte[] && value > Byte.MAX_VALUE) { short[] sa = new short[Array.getLength(values)]; for (int i = 0; i < size; i++) { sa[i] = Array.getShort(values, i); } values = sa; } // May need to extend the array size if (Array.getLength(values) == size) { values = extendArray(values); } // Store the value if (values instanceof long[]) { ((long[])values)[size] = value; } else if (values instanceof int[]) { ((int[])values)[size] = (int)value; } else if (values instanceof short[]) { ((short[])values)[size] = (short)value; } else { ((byte[])values)[size] = (byte)value; } size++; } } // Can be overridden by subclasses long getValue() { return 0; } long getLastTimeStamp() { return times.time(times.size - 1); } long getLastValue(String key) { Sequence seq = getSequence(key); return (seq != null && seq.size > 0) ? seq.value(seq.size - 1) : 0L; } // Called on EDT public void propertyChange(PropertyChangeEvent ev) { String prop = ev.getPropertyName(); if (prop == JConsoleContext.CONNECTION_STATE_PROPERTY) { ConnectionState newState = (ConnectionState)ev.getNewValue(); switch (newState) { case DISCONNECTED: synchronized(this) { long time = System.currentTimeMillis(); times.add(time); for (Sequence seq : seqs) { seq.add(Long.MIN_VALUE); } } break; } } } private static class SaveDataFileChooser extends JFileChooser { private static final long serialVersionUID = -5182890922369369669L; SaveDataFileChooser() { setFileFilter(new FileNameExtensionFilter("CSV file", "csv")); } @OverrideLines 501–1000 of 1095. Previous lines Load more
// Draw ticks int lastY = Integer.MAX_VALUE; for (int i = 0; i < n; i++) { long v = tickValues.get(i); y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin)); g.drawLine(x-2, y, x+2, y); String s = tickStrings[i]; if (unit == Unit.PERCENT) { s += "%"; } int sx = x-6-fm.stringWidth(s); if (y < lastY-13) { if (checkLeftMargin(sx)) { // Wait for next repaint return; } g.drawString(s, sx, y+4); } // Draw horizontal grid line g.setColor(Color.lightGray); g.drawLine(r.x + 4, y, r.x + r.width - 4, y); g.setColor(fg); lastY = y; } // Draw horizontal axis x = leftMargin; y = topMargin + h + 15; g.drawLine(x, y, x+w, y); long t1 = tMax; if (t1 <= 0L) { // No data yet, so draw current time t1 = System.currentTimeMillis(); } long tz = timeDF.getTimeZone().getOffset(t1); long tickInterval = calculateTickInterval(w, 40, viewRangeMS); if (tickInterval > 3 * HOUR) { tickInterval = calculateTickInterval(w, 80, viewRangeMS); } long t0 = tickInterval - (t1 - viewRangeMS + tz) % tickInterval; while (t0 < viewRangeMS) { x = leftMargin + (int)(w * t0 / viewRangeMS); g.drawLine(x, y-2, x, y+2); long t = t1 - viewRangeMS + t0; String str = formatClockTime(t); g.drawString(str, x, y+16); //if (tickInterval > (1 * HOUR) && t % (1 * DAY) == 0) { if ((t + tz) % (1 * DAY) == 0) { str = formatDate(t); g.drawString(str, x, y+27); } // Draw vertical grid line g.setColor(Color.lightGray); g.drawLine(x, topMargin, x, topMargin + h); g.setColor(fg); t0 += tickInterval; } // Plot values int start = 0; int nValues = 0; int nLists = seqs.size(); if (nLists > 0) { nValues = seqs.get(0).size; } if (nValues == 0) { g.setColor(oldColor); return; } else { Sequence seq = seqs.get(0); // Find starting point for (int p = 0; p < seq.size; p++) { if (times.time(p) >= tMax - viewRangeMS) { start = p; break; } } } //Optimization: collapse plot of more than four values per pixel int pointsPerPixel = (nValues - start) / w; if (pointsPerPixel < 4) { pointsPerPixel = 1; } // Draw graphs // Loop backwards over sequences because the first needs to be painted on top for (int i = nLists-1; i >= 0; i--) { int x0 = leftMargin; int y0 = topMargin + h + 1; Sequence seq = seqs.get(i); if (seq.isPlotted && seq.size > 0) { // Paint twice, with white and with color for (int pass = 0; pass < 2; pass++) { g.setColor((pass == 0) ? Color.white : seq.color); int x1 = -1; long v1 = -1; for (int p = start; p < nValues; p += pointsPerPixel) { // Make sure we get the last value if (pointsPerPixel > 1 && p >= nValues - pointsPerPixel) { p = nValues - 1; } int x2 = (int)(w * (times.time(p)-(t1-viewRangeMS)) / viewRangeMS); long v2 = seq.value(p); if (v2 >= vMin && v2 <= vMax) { int y2 = (int)(h * (v2 -vMin) / (vMax-vMin)); if (x1 >= 0 && v1 >= vMin && v1 <= vMax) { int y1 = (int)(h * (v1-vMin) / (vMax-vMin)); if (y1 == y2) { // fillrect is much faster g.fillRect(x0+x1, y0-y1-pass, x2-x1, 1); } else { Graphics2D g2d = (Graphics2D)g; Stroke oldStroke = null; if (seq.transitionStroke != null) { oldStroke = g2d.getStroke(); g2d.setStroke(seq.transitionStroke); } g.drawLine(x0+x1, y0-y1-pass, x0+x2, y0-y2-pass); if (oldStroke != null) { g2d.setStroke(oldStroke); } } } } x1 = x2; v1 = v2; } } // Current value long v = seq.value(seq.size - 1); if (v >= vMin && v <= vMax) { if (bgIsLight) { g.setColor(seq.color); } else { g.setColor(fg); } x = r.x + r.width + 2; y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin)); // a small triangle/arrow g.fillPolygon(new int[] { x+2, x+6, x+6 }, new int[] { y, y+3, y-3 }, 3); } g.setColor(fg); } } int[] valueStringSlots = new int[nLists]; for (int i = 0; i < nLists; i++) valueStringSlots[i] = -1; for (int i = 0; i < nLists; i++) { Sequence seq = seqs.get(i); if (seq.isPlotted && seq.size > 0) { // Draw current value // TODO: collapse values if pointsPerPixel >= 4 long v = seq.value(seq.size - 1); if (v >= vMin && v <= vMax) { x = r.x + r.width + 2; y = topMargin+h-(int)(h * (v-vMin) / (vMax-vMin)); int y2 = getValueStringSlot(valueStringSlots, y, 2*10, i); g.setFont(smallFont); if (bgIsLight) { g.setColor(seq.color); } else { g.setColor(fg); } String curValue = getFormattedValue(v, true); if (unit == Unit.PERCENT) { curValue += "%"; } int valWidth = fm.stringWidth(curValue); String legend = (displayLegend?seq.name:""); int legendWidth = fm.stringWidth(legend); if (checkRightMargin(valWidth) || checkRightMargin(legendWidth)) { // Wait for next repaint return; } g.drawString(legend , x + 17, Math.min(topMargin+h, y2 + 3 - 10)); g.drawString(curValue, x + 17, Math.min(topMargin+h + 10, y2 + 3)); // Maybe draw a short line to value if (y2 > y + 3) { g.drawLine(x + 9, y + 2, x + 14, y2); } else if (y2 < y - 3) { g.drawLine(x + 9, y - 2, x + 14, y2); } } g.setFont(oldFont); g.setColor(fg); } } g.setColor(oldColor); } private boolean checkLeftMargin(int x) { // Make sure leftMargin has at least 2 pixels over if (x < 2) { leftMargin += (2 - x); // Repaint from top (above any cell renderers) SwingUtilities.getWindowAncestor(this).repaint(); return true; } return false; } private boolean checkRightMargin(int w) { // Make sure rightMargin has at least 2 pixels over if (w + 2 > rightMargin) { rightMargin = (w + 2); // Repaint from top (above any cell renderers) SwingUtilities.getWindowAncestor(this).repaint(); return true; } return false; } private int getValueStringSlot(int[] slots, int y, int h, int i) { for (int s = 0; s < slots.length; s++) { if (slots[s] >= y && slots[s] < y + h) { // collide below us if (slots[s] > h) { return getValueStringSlot(slots, slots[s]-h, h, i); } else { return getValueStringSlot(slots, slots[s]+h, h, i); } } else if (y >= h && slots[s] > y - h && slots[s] < y) { // collide above us return getValueStringSlot(slots, slots[s]+h, h, i); } } slots[i] = y; return y; } private long calculateTickInterval(int w, int hGap, long viewRangeMS) { long tickInterval = viewRangeMS * hGap / w; if (tickInterval < 1 * MINUTE) { tickInterval = 1 * MINUTE; } else if (tickInterval < 5 * MINUTE) { tickInterval = 5 * MINUTE; } else if (tickInterval < 10 * MINUTE) { tickInterval = 10 * MINUTE; } else if (tickInterval < 30 * MINUTE) { tickInterval = 30 * MINUTE; } else if (tickInterval < 1 * HOUR) { tickInterval = 1 * HOUR; } else if (tickInterval < 3 * HOUR) { tickInterval = 3 * HOUR; } else if (tickInterval < 6 * HOUR) { tickInterval = 6 * HOUR; } else if (tickInterval < 12 * HOUR) { tickInterval = 12 * HOUR; } else if (tickInterval < 1 * DAY) { tickInterval = 1 * DAY; } else { tickInterval = normalizeMax(tickInterval / DAY) * DAY; } return tickInterval; } private long normalizeMin(long l) { int exp = (int)Math.log10((double)l); long multiple = (long)Math.pow(10.0, exp); int i = (int)(l / multiple); return i * multiple; } private long normalizeMax(long l) { int exp = (int)Math.log10((double)l); long multiple = (long)Math.pow(10.0, exp); int i = (int)(l / multiple); l = (i+1)*multiple; return l; } private String getFormattedValue(long v, boolean groupDigits) { String str; String fmt = "%"; if (groupDigits) { fmt += ","; } if (decimals > 0) { fmt += "." + decimals + "f"; str = String.format(fmt, v / decimalsMultiplier); } else { fmt += "d"; str = String.format(fmt, v); } return str; } private String getSizeString(long v, long vMax) { String s; if (unit == Unit.BYTES && decimals == 0) { s = formatBytes(v, vMax); } else { s = getFormattedValue(v, true); } return s; } private static synchronized Stroke getDashedStroke() { if (dashedStroke == null) { dashedStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[] { 2.0f, 3.0f }, 0.0f); } return dashedStroke; } private static Object extendArray(Object a1) { int n = Array.getLength(a1); Object a2 = Array.newInstance(a1.getClass().getComponentType(), n + ARRAY_SIZE_INCREMENT); System.arraycopy(a1, 0, a2, 0, n); return a2; } private static class TimeStamps { // Time stamps (long) are split into offsets (long) and a // series of times from the offsets (int). A new offset is // stored when the the time value doesn't fit in an int // (approx every 24 days). An array of indices is used to // define the starting point for each offset in the times // array. long[] offsets = new long[0]; int[] indices = new int[0]; int[] rtimes = new int[ARRAY_SIZE_INCREMENT]; // Number of stored timestamps int size = 0; /** * Returns the time stamp for index i */ public long time(int i) { long offset = 0; for (int j = indices.length - 1; j >= 0; j--) { if (i >= indices[j]) { offset = offsets[j]; break; } } return offset + rtimes[i]; } public void add(long time) { // May need to store a new time offset int n = offsets.length; if (n == 0 || time - offsets[n - 1] > Integer.MAX_VALUE) { // Grow offset and indices arrays and store new offset offsets = Arrays.copyOf(offsets, n + 1); offsets[n] = time; indices = Arrays.copyOf(indices, n + 1); indices[n] = size; } // May need to extend the array size if (rtimes.length == size) { rtimes = (int[])extendArray(rtimes); } // Store the time rtimes[size] = (int)(time - offsets[offsets.length - 1]); size++; } } private static class Sequence { String key; String name; Color color; boolean isPlotted; Stroke transitionStroke = null; // Values are stored in an int[] if all values will fit, // otherwise in a long[]. An int can represent up to 2 GB. // Use a random start size, so all arrays won't need to // be grown during the same update interval Object values = new byte[ARRAY_SIZE_INCREMENT + (int)(Math.random() * 100)]; // Number of stored values int size = 0; public Sequence(String key) { this.key = key; } /** * Returns the value at index i */ public long value(int i) { return Array.getLong(values, i); } public void add(long value) { // May need to switch to a larger array type if ((values instanceof byte[] || values instanceof short[] || values instanceof int[]) && value > Integer.MAX_VALUE) { long[] la = new long[Array.getLength(values)]; for (int i = 0; i < size; i++) { la[i] = Array.getLong(values, i); } values = la; } else if ((values instanceof byte[] || values instanceof short[]) && value > Short.MAX_VALUE) { int[] ia = new int[Array.getLength(values)]; for (int i = 0; i < size; i++) { ia[i] = Array.getInt(values, i); } values = ia; } else if (values instanceof byte[] && value > Byte.MAX_VALUE) { short[] sa = new short[Array.getLength(values)]; for (int i = 0; i < size; i++) { sa[i] = Array.getShort(values, i); } values = sa; } // May need to extend the array size if (Array.getLength(values) == size) { values = extendArray(values); } // Store the value if (values instanceof long[]) { ((long[])values)[size] = value; } else if (values instanceof int[]) { ((int[])values)[size] = (int)value; } else if (values instanceof short[]) { ((short[])values)[size] = (short)value; } else { ((byte[])values)[size] = (byte)value; } size++; } } // Can be overridden by subclasses long getValue() { return 0; } long getLastTimeStamp() { return times.time(times.size - 1); } long getLastValue(String key) { Sequence seq = getSequence(key); return (seq != null && seq.size > 0) ? seq.value(seq.size - 1) : 0L; } // Called on EDT public void propertyChange(PropertyChangeEvent ev) { String prop = ev.getPropertyName(); if (prop == JConsoleContext.CONNECTION_STATE_PROPERTY) { ConnectionState newState = (ConnectionState)ev.getNewValue(); switch (newState) { case DISCONNECTED: synchronized(this) { long time = System.currentTimeMillis(); times.add(time); for (Sequence seq : seqs) { seq.add(Long.MIN_VALUE); } } break; } } } private static class SaveDataFileChooser extends JFileChooser { private static final long serialVersionUID = -5182890922369369669L; SaveDataFileChooser() { setFileFilter(new FileNameExtensionFilter("CSV file", "csv")); } @Override