/* * Copyright (c) 2013-2016 Chris Newland. * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki */ package org.adoptopenjdk.jitwatch.ui.graphing; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_STAMP; import static org.adoptopenjdk.jitwatch.util.UserInterfaceUtil.fix; import java.util.Map; import org.adoptopenjdk.jitwatch.model.Tag; import org.adoptopenjdk.jitwatch.ui.main.JITWatchUI; import org.adoptopenjdk.jitwatch.util.ParseUtil; import org.adoptopenjdk.jitwatch.util.StringUtil; import org.adoptopenjdk.jitwatch.util.UserInterfaceUtil; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.VPos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.stage.StageStyle; public abstract class AbstractGraphStage extends Stage { protected Canvas canvas; protected GraphicsContext gc; protected JITWatchUI mainUI; protected double graphGapLeft = 20.5; protected final double graphGapRight = 20.5; protected final double graphGapTop = 20.5; protected static final int[] Y_SCALE = new int[21]; protected double width; protected double height; protected double chartWidth; protected double chartHeight; protected long minX; protected long maxX; protected long minY; protected long maxY; protected long minXQ; protected long maxXQ; protected long minYQ; protected long maxYQ; protected double endOfXAxis; private boolean xAxisTime = false; protected static final Font STANDARD_FONT = new Font(UserInterfaceUtil.FONT_MONOSPACE_FAMILY, Double.valueOf(UserInterfaceUtil.FONT_MONOSPACE_SIZE)); protected static final Font MEMBER_FONT = new Font(UserInterfaceUtil.FONT_MONOSPACE_FAMILY, Double.valueOf(UserInterfaceUtil.FONT_MONOSPACE_SIZE) * 2.0); static { int multiplier = 1; for (int i = 0; i < Y_SCALE.length; i += 3) { Y_SCALE[i + 0] = 1 * multiplier; Y_SCALE[i + 1] = 2 * multiplier; Y_SCALE[i + 2] = 5 * multiplier; multiplier *= 10; } } public AbstractGraphStage(final JITWatchUI parent, int width, int height, boolean xAxisTime) { this.mainUI = parent; this.width = width; this.height = height; this.xAxisTime = xAxisTime; canvas = new Canvas(width, height); gc = canvas.getGraphicsContext2D(); initStyle(StageStyle.DECORATED); class SceneResizeListener implements ChangeListener<Number> { @Override public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) { redraw(); } } SceneResizeListener rl = new SceneResizeListener(); canvas.widthProperty().addListener(rl); canvas.heightProperty().addListener(rl); } public abstract void redraw(); protected void baseRedraw() { gc.setFont(STANDARD_FONT); width = canvas.getWidth(); height = canvas.getHeight(); chartWidth = width - graphGapLeft - graphGapRight; chartHeight = height - graphGapTop * 2; setStrokeForAxis(); gc.setFill(Color.WHITE); gc.fillRect(0, 0, width, height); gc.setFill(Color.rgb(210, 255, 255)); gc.fillRect(graphGapLeft, graphGapTop, chartWidth, chartHeight); gc.setStroke(Color.BLACK); gc.strokeRect(graphGapLeft, graphGapTop, chartWidth, chartHeight); } protected void drawAxes() { if (xAxisTime) { drawXAxisTime(); } else { drawXAxis(); } drawYAxis(); } protected long getStampFromTag(Tag tag) { Map<String, String> attrs = tag.getAttributes(); return ParseUtil.parseStamp(attrs.get(ATTR_STAMP)); } protected void continueLineToEndOfXAxis(double lastX, double lastY, Color color, double lineWidth) { gc.setStroke(color); gc.setLineWidth(lineWidth); gc.strokeLine(fix(lastX), fix(lastY), endOfXAxis, fix(lastY)); } protected void drawLabel(String text, double xPos, double yPos, Color backgroundColour) { double boxPad = 4; double boxWidth = getApproximateStringWidth(text) + boxPad * 2; double boxHeight = getStringHeight() + boxPad * 2; gc.setFill(backgroundColour); setStrokeForAxis(); gc.fillRect(fix(xPos), fix(yPos), fix(boxWidth), fix(boxHeight)); gc.strokeRect(fix(xPos), fix(yPos), fix(boxWidth), fix(boxHeight)); setStrokeForText(); gc.fillText(text, fix(xPos + boxPad), fix(yPos)); } private void drawXAxisTime() { long xInc = getXStepTime(); minXQ = (minX / xInc) * xInc; maxXQ = (1 + (maxX / xInc)) * xInc; long gridX = minXQ; while (gridX <= maxX) { double x = graphGapLeft + normaliseX(gridX); setStrokeForAxis(); gc.strokeLine(fix(x), fix(graphGapTop), fix(x), fix(graphGapTop + chartHeight)); boolean showMillis = maxX < 5000; setStrokeForText(); gc.fillText(StringUtil.formatTimestamp(gridX, showMillis), fix(x), fix(graphGapTop + chartHeight + 2)); gridX += xInc; } endOfXAxis = fix(graphGapLeft + normaliseX(gridX)); } private void drawXAxis() { long xInc = findScale(maxX - minX); minXQ = (minX / xInc) * xInc; maxXQ = (1 + (maxX / xInc)) * xInc; long gridX = minXQ; while (gridX <= maxX) { double x = graphGapLeft + normaliseX(gridX); setStrokeForAxis(); gc.strokeLine(fix(x), fix(graphGapTop), fix(x), fix(graphGapTop + chartHeight)); setStrokeForText(); gc.fillText(StringUtil.formatThousands(Long.toString(gridX)), fix(x), fix(graphGapTop + chartHeight + 2)); gridX += xInc; } endOfXAxis = fix(graphGapLeft + normaliseX(gridX)); } private void drawYAxis() { long yInc = findScale(maxY - minY); minYQ = (minY / yInc) * yInc; maxYQ = (1 + (maxY / yInc)) * yInc; long gridY = minYQ; int maxYLabelWidth = StringUtil.formatThousands(Long.toString(maxYQ)).length(); graphGapLeft = Math.max(40.5, maxYLabelWidth*9); double yLabelX = graphGapLeft - (1 + maxYLabelWidth) * 8; while (gridY <= maxYQ) { if (gridY >= minYQ) { double y = graphGapTop + normaliseY(gridY); setStrokeForAxis(); gc.strokeLine(fix(graphGapLeft), fix(y), fix(graphGapLeft + chartWidth), fix(y)); setStrokeForText(); gc.fillText(StringUtil.formatThousands(Long.toString(gridY)), fix(yLabelX), fix(y - getStringHeight() / 2)); } gridY += yInc; } } protected double getApproximateStringWidth(String text) { return text.length() * gc.getFont().getSize() * 0.6; } protected double getStringHeight() { return gc.getFont().getSize(); } private long getXStepTime() { long rangeMillis = maxX - minX; int requiredLines = 5; long[] gapMillis = new long[] { 30 * 24 * 60 * 60000, 14 * 24 * 60 * 60000, 7 * 24 * 60 * 60000, 4 * 24 * 60 * 60000, 2 * 24 * 60 * 60000, 24 * 60 * 60000, 16 * 60 * 60000, 12 * 60 * 60000, 8 * 60 * 60000, 6 * 60 * 60000, 4 * 60 * 60000, 2 * 60 * 60000, 60 * 60000, 30 * 60000, 15 * 60000, 10 * 60000, 5 * 60000, 2 * 60000, 1 * 60000, 30000, 15000, 10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1 }; long incrementMillis = 120 * 60000; for (int i = 0; i < gapMillis.length; i++) { if (rangeMillis / gapMillis[i] >= requiredLines) { incrementMillis = gapMillis[i]; break; } } return incrementMillis; } protected long findScale(long range) { long requiredLines = 8; for (int i = 0; i < Y_SCALE.length; i++) { if (range / Y_SCALE[i] < requiredLines) { return Y_SCALE[i]; } } return range / requiredLines; } protected double normaliseX(double value) { return normalise(value, minXQ, maxXQ, chartWidth, false); } protected double normaliseY(double value) { return normalise(value, minYQ, maxYQ, chartHeight, true); } protected double normalise(double value, double min, double max, double size, boolean invert) { double range = max - min; double result = 0; if (range == 0) { result = 0; } else { result = (value - min) / range; } result *= size; if (invert) { result = size - result; } return result; } protected void setStrokeForAxis() { gc.setStroke(Color.BLACK); gc.setLineWidth(0.5); } protected void setStrokeForText() { gc.setStroke(Color.BLACK); gc.setFill(Color.BLACK); gc.setLineWidth(1.0); gc.setTextBaseline(VPos.TOP); } }