/** * Update the hoverEntry with the currently hovered entry, or none if none is hovered. Repaints * and informs listeners if something has changed. * * @param p Where the mouse is currently. */ private void updateHoverEntry(Point p) { long hoverEntryBefore = hoverEntry; hoverEntry = findHoverEntry(p); // If something has changed, then redraw. if (hoverEntry != hoverEntryBefore) { repaint(); if (listener != null) { if (hoverEntry == -1) { listener.noItemSelected(); } else { StreamInfoHistoryItem item = history.get(hoverEntry); if (item == null) { /** * This shouldn't happen, because the hover entry is set just before and selected from * the current data (and it should all be in the EDT), but apparently it still happens * on rare occasions. */ LOGGER.warning("Hovered Entry " + hoverEntry + " was null"); hoverEntry = -1; } else { listener.itemSelected(item.getViewers(), item.getTitle(), item.getGame()); } } } } }
/** * Updates the map of colors used for rendering. This creates the alternating colors based on the * full stream status, which should only be changed when new data is set. */ private void makeColors() { colors.clear(); Iterator<Entry<Long, StreamInfoHistoryItem>> it = history.entrySet().iterator(); String prevStatus = null; Color currentColor = FIRST_COLOR; while (it.hasNext()) { Entry<Long, StreamInfoHistoryItem> entry = it.next(); long time = entry.getKey(); StreamInfoHistoryItem item = entry.getValue(); String newStatus = item.getStatusAndGame(); // Only change color if neither the previous nor the new status // are null (offline) and the previous and new status are not equal. if (prevStatus != null && newStatus != null && !prevStatus.equals(newStatus)) { // Change color if (currentColor == FIRST_COLOR) { currentColor = SECOND_COLOR; } else { currentColor = FIRST_COLOR; } } colors.put(time, currentColor); // Save this status as previous status, but only if it's not // offline. if (newStatus != null) { prevStatus = newStatus; } } }
/** * Update the start/end/duration/min/max variables which can be changed when the data changes as * well when the displayed range changes. */ private void updateVars() { long startAt = getStartAt(currentRange); long endAt = getEndAt(); int max = 0; int min = -1; long start = -1; long end = -1; for (Long time : history.keySet()) { if (time < startAt) { continue; } if (endAt > startAt && time > endAt) { continue; } // Start/End time if (start == -1) { start = time; } end = time; // Max/min value StreamInfoHistoryItem historyObj = history.get(time); int viewerCount = historyObj.getViewers(); if (viewerCount < min || min == -1) { min = viewerCount; } if (viewerCount == -1) { min = 0; } if (viewerCount > max) { max = viewerCount; } } maxValue = max; minValue = min; startTime = start; endTime = end; duration = end - start; }
/** * Draw the text and graph. * * @param g */ @Override public void paintComponent(Graphics g) { locations.clear(); // Background g.setColor(background_color); g.fillRect(0, 0, getWidth(), getHeight()); // This color is used for everything until drawing the points g.setColor(foreground_color); // Anti-Aliasing Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Font FontMetrics fontMetrics = g.getFontMetrics(FONT); int fontHeight = fontMetrics.getHeight(); g.setFont(FONT); int topTextY = fontMetrics.getAscent(); // Margins int vMargin = fontHeight + MARGIN; int hMargin = MARGIN; // Calculate actual usable size double width = getWidth() - hMargin * 2; double height = getHeight() - vMargin * 2; boolean drawLowerLine = height > -vMargin; // If there is any data and no hovered entry is shown, draw current // viewercount int nowTextX = 0; if (history != null && hoverEntry == -1) { Integer viewers = history.get(endTime).getViewers(); long ago = System.currentTimeMillis() - endTime; String text; if (ago > CONSIDERED_AS_NOW) { text = "latest: " + Helper.formatViewerCount(viewers); } else { text = "now: " + Helper.formatViewerCount(viewers); } if (viewers == -1) { text = "Stream offline"; } nowTextX = getWidth() - fontMetrics.stringWidth(text); g.drawString(text, nowTextX, topTextY); } // Default text when no data is present if (history == null || history.size() < 2) { String text = "No viewer history yet"; int textWidth = fontMetrics.stringWidth(text); int y = getHeight() / 2 + fontMetrics.getDescent(); int x = (getWidth() - textWidth) / 2; boolean drawInfoText = false; if (history != null && y < topTextY + fontHeight + 4 && x + textWidth + 7 > nowTextX) { if (drawLowerLine || nowTextX > textWidth + 5) { if (drawLowerLine) { y = getHeight() - 2; } else { y = topTextY; } x = 0; drawInfoText = true; } } else { drawInfoText = true; } if (drawInfoText) { g.drawString(text, x, y); } return; } // ---------- // From here only when actual data is to be rendered // Show info on hovered entry String maxValueText = "max: " + Helper.formatViewerCount(maxValue); int maxValueEnd = fontMetrics.stringWidth(maxValueText); boolean displayMaxValue = true; if (hoverEntry != -1) { Integer viewers = history.get(hoverEntry).getViewers(); Date d = new Date(hoverEntry); String text = "Viewers: " + Helper.formatViewerCount(viewers) + " (" + sdf.format(d) + ")"; if (viewers == -1) { text = "Stream offline (" + sdf.format(d) + ")"; } int x = getWidth() - fontMetrics.stringWidth(text); if (maxValueEnd > x) { displayMaxValue = false; } g.drawString(text, x, topTextY); } String minText = "min: " + Helper.formatViewerCount(minValue); int minTextWidth = fontMetrics.stringWidth(minText); // Draw Times if (drawLowerLine) { String timeText = makeTimesText(startTime, endTime); int timeTextWidth = fontMetrics.stringWidth(timeText); int textX = getWidth() - timeTextWidth; g.drawString(timeText, textX, getHeight() - 1); if (minValue >= 1000 && timeTextWidth + minTextWidth > width) { minText = "min: " + minValue / 1000 + "k"; } } // Draw min/max if necessary if (isShowingInfo()) { if (displayMaxValue) { g.drawString(maxValueText, 0, topTextY); } if (drawLowerLine) { g.drawString(minText, 0, getHeight() - 1); } else if (maxValueEnd + minTextWidth + 29 < nowTextX) { g.drawString(minText, maxValueEnd + 10, topTextY); } } // If height available for the graph is too small, don't draw graph if (height < 5) { return; } // Calculation factors for calculating the points location int range = maxValue - minValue; if (showFullRange) { range = maxValue; } if (range == 0) { // Prevent division by zero range = 1; } double pixelPerViewer = height / range; double pixelPerTime = width / duration; // Go through all entries and calculate positions int prevX = -1; int prevY = -1; Iterator<Entry<Long, StreamInfoHistoryItem>> it = history.entrySet().iterator(); while (it.hasNext()) { Entry<Long, StreamInfoHistoryItem> entry = it.next(); // Get time and value to draw next long time = entry.getKey(); if (time < startTime || time > endTime) { continue; } long offsetTime = time - startTime; int viewers = entry.getValue().getViewers(); if (viewers == -1) { viewers = 0; } // Calculate point location int x = (int) (hMargin + offsetTime * pixelPerTime); int y; if (showFullRange) { y = (int) (-vMargin + getHeight() - (viewers) * pixelPerViewer); } else { y = (int) (-vMargin + getHeight() - (viewers - minValue) * pixelPerViewer); } // Draw connecting line if (prevX != -1) { g.drawLine(x, y, prevX, prevY); } // Save point coordinates to be able to draw the line next loop prevX = x; prevY = y; // Save point locations to draw points and to find entries on hover locations.put(new Point(x, y), time); } // Draw points (after lines, so they are in front) for (Point point : locations.keySet()) { int x = point.x; int y = point.y; long seconds = locations.get(point); StreamInfoHistoryItem historyObject = history.get(seconds); // Highlight hovered entry if (seconds == hoverEntry) { g.setColor(HOVER_COLOR); } else { // Draw offline points differently if (!historyObject.isOnline()) { g.setColor(OFFLINE_COLOR); } else { g.setColor(colors.get(seconds)); } } g.fillOval(x - POINT_SIZE / 2, y - POINT_SIZE / 2, POINT_SIZE, POINT_SIZE); } }