All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cdc.perfs.ui.swing.ChartPanel Maven / Gradle / Ivy

There is a newer version: 0.52.0
Show newest version
package cdc.perfs.ui.swing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import cdc.perfs.Context;
import cdc.perfs.Environment;
import cdc.perfs.EnvironmentKind;
import cdc.perfs.Measure;
import cdc.perfs.MeasureStatus;
import cdc.perfs.Position;
import cdc.perfs.ui.PerfsChartHelper;
import cdc.perfs.ui.PerfsChartHelper.ChangeListener;
import cdc.perfs.ui.RefreshRate;
import cdc.perfs.ui.Rendering;
import cdc.util.time.RefTime;
import cdc.util.time.TimeMeasureMode;
import cdc.util.time.TimeUnit;

/**
 * Panel dedicated to graphical display of measures.
 *
 * @author Damien Carbonne
 *
 */
final class ChartPanel extends JPanel {
    private static final long serialVersionUID = 1L;
    private static final Logger LOGGER = LogManager.getLogger(ChartPanel.class);
    protected final transient DragParams dragParams = new DragParams();

    protected final Timer timer;
    private RefreshRate refreshRate = null;
    private Rendering rendering = null;
    private boolean displayStatsEnabled = false;
    protected boolean drawBordersEnabled = false;

    protected boolean mouseOver = false;
    protected long lastMouseMoveTime = -1;
    /**
     * Action that is triggered on timer events for refresh.
     */
    private final transient ActionListener timerHandler;
    protected final ContextsTableModel contextsModel;
    final transient Helper helper;

    public ChartPanel(ContextsTableModel model) {
        super();
        this.helper = new Helper(model.getEnvironment().getKind());
        this.timerHandler = e -> {
            helper.computeParameters();
            repaint();
            if (mouseOver) {
                showToolTip(ChartPanel.this, e.getWhen());
            }
        };
        this.contextsModel = model;
        if (model.getEnvironment().getKind() == EnvironmentKind.RUNTIME) {
            this.timer = new Timer(20, this.timerHandler);
        } else {
            this.timer = null;
            this.contextsModel.addTableModelListener(event -> repaintIfSnapshot());
        }

        addMouseWheelListener(e -> {
            final double ratio = e.getWheelRotation() < 0 ? 1.0 / 1.1 : 1.1;
            setScale(helper.getScale() * ratio);
        });
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                dragParams.enabled = helper.isFocusLocked() && (e.getButton() == MouseEvent.BUTTON1);
                dragParams.x0 = e.getX();
                dragParams.px = dragParams.x0;
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                // Ignore
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                mouseOver = true;
            }

            @Override
            public void mouseExited(MouseEvent e) {
                mouseOver = false;
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                if (dragParams.enabled) {
                    final int dx = e.getX() - dragParams.px;
                    dragParams.px = e.getX();
                    setFocusNanos(helper.getFocusNanos() + helper.deltaXToDeltaTime(-dx));
                }
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                helper.pick(e.getX(), e.getY());
                lastMouseMoveTime = e.getWhen();
            }
        });

        addHierarchyListener(e -> {
            if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
                if (isShowing()) {
                    repaintIfSnapshot();
                    if (timer != null) {
                        timer.restart();
                    }
                } else {
                    if (timer != null) {
                        timer.stop();
                    }
                }
            }
        });

        addAncestorListener(new AncestorListener() {
            @Override
            public void ancestorRemoved(AncestorEvent event) {
                // Ignore
            }

            @Override
            public void ancestorMoved(AncestorEvent event) {
                // Called when chart is scrolled
                repaintIfSnapshot();
            }

            @Override
            public void ancestorAdded(AncestorEvent event) {
                // Ignore
            }
        });

        setRefreshRate(RefreshRate.LOW);
        setRendering(Rendering.FASTEST);

        setPreferredSize(new Dimension(100, 1000));

        if (timer != null) {
            timer.start();
        } else {
            repaintIfSnapshot();
        }
    }

    /**
     * Blends a color with a fixed color (black).
     * 

* For a Runtime environment, result equals input color.
* For Snapshot environment, result is different. * * @param kind The environment kind. * @param c The color to blend. * @return The blended color. */ protected static Color blend(EnvironmentKind kind, Color c) { if (kind == EnvironmentKind.RUNTIME) { return c; } else { final double w0 = 0.9; final double w1 = 1.0 - w0; final double r = w0 * c.getRed() + w1 * 0.0; final double g = w0 * c.getGreen() + w1 * 0.0; final double b = w0 * c.getBlue() + w1 * 0.0; return new Color((int) r, (int) g, (int) b, 255); } } private class Helper extends PerfsChartHelper { protected final Rectangle2D.Double rect = new Rectangle2D.Double(); protected final Line2D line = new Line2D.Double(); private final Color bgStatsColor; private final Color bgScaleColor; private final Color bgColor; private final Color aliveEvenBackgroundColor; private final Color deadEvenBackgroundColor; private final Color aliveOddBackgroundColor; private final Color deadOddBackgroundColor; private final Color fgColor; private final Color runningColor; private final Color frozenColor; private final Color errorColor; protected final Color borderColor; protected final Color synthesisColor; protected final Color infoColor; private final Color centerTimeColor; private final Color maxTimeColor; final Rectangle visibleRectangle = new Rectangle(); final Filter rootFilter = new Filter(1); Filter currentFilter = null; Helper(EnvironmentKind kind) { bgStatsColor = new Color(0.0f, 0.0f, 0.0f, 0.5f); bgScaleColor = blend(kind, Color.WHITE); bgColor = blend(kind, new Color(0.95f, 0.95f, 0.95f)); aliveEvenBackgroundColor = blend(kind, new Color(0.95f, 0.95f, 0.95f)); deadEvenBackgroundColor = blend(kind, new Color(0.95f, 0.85f, 0.85f)); aliveOddBackgroundColor = blend(kind, new Color(0.85f, 0.85f, 0.85f)); deadOddBackgroundColor = blend(kind, new Color(0.85f, 0.75f, 0.75f)); fgColor = blend(kind, new Color(0.6f, 0.6f, 0.6f)); runningColor = blend(kind, new Color(0.6f, 1.0f, 0.2f)); frozenColor = blend(kind, new Color(1.0f, 1.0f, 0.4f)); errorColor = blend(kind, new Color(1.0f, 0.6f, 0.55f)); borderColor = blend(kind, new Color(0.6f, 0.6f, 0.6f)); synthesisColor = blend(kind, Color.GRAY); infoColor = Color.BLACK; centerTimeColor = Color.GREEN; maxTimeColor = Color.RED; } @Override protected void retrieveWindowSize() { final Dimension d = ChartPanel.this.getSize(); width = d.width; height = d.height; if (width < 0.01) { width = 600.0; } } @Override protected void setPreferredHeight(int height) { if (getPreferredSize().getHeight() != height) { setPreferredSize(new Dimension(100, height)); revalidate(); } } @Override public Environment getEnvironment() { return contextsModel.getEnvironment(); } @Override protected void setToolTipTextHtml(String text) { ChartPanel.this.setToolTipText(text); } @Override protected List getContexts() { return contextsModel.getContexts(); } @Override protected boolean isVisible(Context context) { return contextsModel.isVisible(context); } @Override protected void redraw() { ChartPanel.this.repaint(); } /** * Draw time scale on top of visible part of the window. * * @param g2 The Graphic context to use. */ protected void drawScale(Graphics2D g2) { final int y0 = (int) visibleRectangle.getMinY(); // Background g2.setPaint(bgScaleColor); rect.setRect(0, y0, width, DY0); g2.fill(rect); // Line under the area line.setLine(0.0, y0 + DY0, timeToX(timeSupNanos), y0 + DY0); g2.setPaint(fgColor); g2.draw(line); // Vertical time graduations g2.setPaint(fgColor); double x = Math.floor(timeInfNanos / timeStepNanos) * timeStepNanos; assert (x <= timeInfNanos); final Line2D line = new Line2D.Double(); for (; x <= timeSupNanos; x += timeStepNanos) { final double xp = timeToX(x); line.setLine(xp, y0 + DY0 - 5.0, xp, y0 + DY0); g2.draw(line); g2.drawString(RefTime.nanosToString((long) x, timeStepUnit), (int) xp - shift(timeStepUnit), y0 + 10); } } /** * Draw background elements. * * @param g2 The Graphic context to use. */ protected void drawBackground(Graphics2D g2) { // Contexts background double y0 = DY0; int count = 0; for (final Context context : contextsModel.getContexts()) { if (contextsModel.isVisible(context)) { final double dy = context.getHeight() * (DY1 + DY2) + DY2; if (count % 2 == 0) { if (context.isAlive()) { g2.setPaint(aliveEvenBackgroundColor); } else { g2.setPaint(deadEvenBackgroundColor); } } else { if (context.isAlive()) { g2.setPaint(aliveOddBackgroundColor); } else { g2.setPaint(deadOddBackgroundColor); } } rect.setRect(0.0, y0, width, dy); g2.fill(rect); y0 += dy; count++; } } // Remaining space g2.setPaint(bgColor); rect.setRect(0, y0, width, height - DY0); g2.fill(rect); // Vertical time lines g2.setPaint(fgColor); double x = Math.floor(timeInfNanos / timeStepNanos) * timeStepNanos; assert (x <= timeInfNanos); for (; x <= timeSupNanos; x += timeStepNanos) { final double xp = timeToX(x); line.setLine(xp, DY0, xp, y0); g2.draw(line); } // Elapsed time vertical line if (timeInfNanos <= elapsedNanos && elapsedNanos <= timeSupNanos) { g2.setPaint(maxTimeColor); final double xp = timeToX(elapsedNanos); line.setLine(xp, DY0 - 5.0, xp, y0); g2.draw(line); } // Focus time vertical line if (timeInfNanos <= focusNanos && focusNanos <= timeSupNanos) { g2.setPaint(centerTimeColor); final double xp = timeToX(focusNanos); line.setLine(xp, DY0 - 5.0, xp, y0); g2.draw(line); } } /** * Draw all visible contexts. * * @param g2 the graphical 2D context. */ protected void drawContexts(Graphics2D g2) { double y0 = DY0 + DY2; for (final Context context : contextsModel.getContexts()) { if (contextsModel.isVisible(context)) { drawContext(g2, context, y0); y0 += context.getHeight() * (DY1 + DY2); // Line under the context area line.setLine(0.0, y0, timeToX(timeSupNanos), y0); g2.setPaint(fgColor); g2.draw(line); y0 += DY2; } } } class Filter extends AbstractFilter { private Graphics2D g2; public Filter(int depth) { super(depth); } @Override protected AbstractFilter create(int depth) { return new Filter(depth); } @Override protected void drawMeasure(Measure measure, double y) { Helper.this.drawMeasure(g2, measure, y); } @Override public Filter next() { return (Filter) super.next(); } @Override protected void drawSynthesis() { addDrawing(); setRectSafe(rect, x0, y, x1 - x0, DY1); // Fill rectangle g2.setPaint(synthesisColor); g2.fill(rect); // Draw border if (drawBordersEnabled) { g2.setPaint(borderColor); g2.draw(rect); } // Draw infos if (x1 - x0 > 100.0 && x0 <= getWidth() && x0 > -1000.0) { g2.setPaint(infoColor); g2.drawString(count + " measures", (int) x0 + 2, (int) (y + DY1 - 2.0)); } } public void init(Graphics2D g2, double y) { super.init(y); this.g2 = g2; } } /** * Draw one context. * * @param g2 the graphical 2D context. * @param context the context to draw. * @param y0 vertical position of the first measure to draw. */ private void drawContext(Graphics2D g2, Context context, double y0) { // Index of the first root measure to draw int index = context.getRootMeasureIndex((long) timeInfNanos, TimeMeasureMode.RELATIVE, Position.GREATER_OR_EQUAL); final int count = context.getRootMeasuresCount(); rootFilter.init(g2, y0); currentFilter = rootFilter; while (index >= 0 && index < count) { final Measure measure = context.getRootMeasure(index); final double t0 = measure.getRelativeBeginNanos(); final double t1 = measure.getRelativeEndOrCurrentNanos(); if (t0 <= timeSupNanos && t1 >= timeInfNanos) { rootFilter.addMeasure(measure); index++; } else { // Measures are ordered: next measures are not visible index = -1; } } rootFilter.flush(); } protected void setRectSafe(Rectangle2D.Double r, double x, double y, double w, double h) { final double eps = 5.0; final double dx0 = (x <= -eps ? -eps - x : 0.0); final double dx1 = (x + w > width + eps ? x + w - width - eps : 0.0); r.setRect(x + dx0, y, w - dx0 - dx1, h); } /** * Recursively draw one measure and its children. * * @param g2 the graphical 2D context. * @param measure the measure to draw. * @param y vertical position of the measure to draw. */ protected void drawMeasure(Graphics2D g2, Measure measure, double y) { addDrawing(); final MeasureStatus status = measure.getStatus(); final double x0 = getX0(measure); final double x1 = getX1(measure); switch (status) { case FROZEN: g2.setPaint(frozenColor); break; case FROZEN_ON_ERROR: g2.setPaint(errorColor); break; case RUNNING: g2.setPaint(runningColor); break; default: break; } final double dx = x1 - x0; setRectSafe(rect, x0, y, dx, DY1); // Fill rectangle g2.fill(rect); // Draw border if (drawBordersEnabled) { g2.setPaint(borderColor); g2.draw(rect); } // Draw information if (dx > 100.0 && x0 <= width && x1 >= 50.0) { g2.setPaint(infoColor); g2.drawString(measure.getLabel(), Math.max(0, (int) x0 + 2), (int) (y + DY1 - 2.0)); } // Draw children if (measure.getFirstChild() != null) { final Filter mem = currentFilter; final Filter filter = currentFilter.next(); filter.init(g2, y + DY1 + DY2); currentFilter = filter; for (Measure child = measure.getFirstChild(); child != null; child = child.getNextSibling()) { filter.addMeasure(child); } filter.flush(); currentFilter = mem; } } protected void drawStats(Graphics2D g2) { if (getDisplayStatsEnabled()) { final boolean runtime = getEnvironment().getKind() == EnvironmentKind.RUNTIME; final int dx = 150; final int dy = 70 + (runtime ? 20 : 0); final int xmargin = 10; final int ymargin = 20 + (int) visibleRectangle.getMinY(); g2.setPaint(bgStatsColor); rect.setRect(width - dx - xmargin, ymargin, dx, dy); g2.fill(rect); g2.setPaint(Color.WHITE); g2.drawString(RefTime.nanosToString(getDrawingNanos()), (int) width - dx, ymargin + 20); g2.drawString(getDrawingCount() + " item(s)", (int) width - dx, ymargin + 40); g2.drawString(getEnvironment().getMeasuresCount() + " measure(s)", (int) width - dx, ymargin + 60); if (runtime) { g2.drawString(String.format("%03d ms", (int) RefTime.nanosToDouble(getTickNanos(), TimeUnit.MILLI)), (int) width - dx, ymargin + 80); } } } } @Override public void paint(Graphics g) { helper.startDrawing(); helper.visibleRectangle.setBounds(getVisibleRect()); super.paint(g); final Graphics2D g2 = (Graphics2D) g; switch (getRendering()) { case BEST: g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); break; case FASTEST: default: g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); break; } helper.computeParameters(); helper.drawBackground(g2); helper.drawContexts(g2); helper.drawScale(g2); helper.endDrawing(); helper.drawStats(g2); } protected static class DragParams { boolean enabled; /** Current x0. */ int x0; /** Previous x0. */ int px; } protected void showToolTip(JComponent component, long time) { final ToolTipManager manager = ToolTipManager.sharedInstance(); if (time - lastMouseMoveTime > manager.getInitialDelay()) { final PointerInfo info = MouseInfo.getPointerInfo(); if (info == null) { LOGGER.warn("Can not retrieve pointer info"); } else { final Point absLocation = info.getLocation(); final Point relLocation = new Point(absLocation); SwingUtilities.convertPointFromScreen(relLocation, component); helper.pick(relLocation.x, relLocation.y); if (component.contains(relLocation)) { final MouseEvent event = new MouseEvent(component, MouseEvent.MOUSE_MOVED, time, 0, relLocation.x, relLocation.y, absLocation.x, absLocation.y, 0, false, 0); manager.mouseMoved(event); } } } } public Environment getEnvironment() { return contextsModel.getEnvironment(); } protected List getContexts() { return contextsModel.getContexts(); } protected boolean isVisible(Context context) { return contextsModel.isVisible(context); } public final void addChangeListener(ChangeListener listener) { helper.addChangeListener(listener); } public final void removeChangeListener(ChangeListener listener) { helper.removeChangeListener(listener); } public void setFocusLocked(boolean locked) { helper.setFocusLocked(locked); repaint(); } public boolean isFocusLocked() { return helper.isFocusLocked(); } public void setScale(double scale) { helper.setScale(scale); } public double getScale() { return helper.getScale(); } public void setFocusNanos(double time) { helper.setFocusNanos(time); } public double getFocusNanos() { return helper.getFocusNanos(); } public void incrementFocus(long nanosSinceFirstChange) { helper.incrementFocus(nanosSinceFirstChange); } public void decrementFocus(long nanosSinceFirstChange) { helper.decrementFocus(nanosSinceFirstChange); } public void setRefreshRate(RefreshRate rate) { this.refreshRate = rate; if (this.timer != null) { this.timer.setDelay(rate.getDelay()); } helper.fireChange(); repaintIfSnapshot(); } public RefreshRate getRefreshRate() { return refreshRate; } public void setRendering(Rendering rendering) { this.rendering = rendering; helper.fireChange(); repaintIfSnapshot(); } public Rendering getRendering() { return rendering; } public void setDisplayStatsEnabled(boolean enabled) { this.displayStatsEnabled = enabled; helper.fireChange(); repaintIfSnapshot(); } public boolean getDisplayStatsEnabled() { return displayStatsEnabled; } public void setDrawBordersEnabled(boolean enabled) { this.drawBordersEnabled = enabled; helper.fireChange(); repaintIfSnapshot(); } public boolean getDrawBordersEnabled() { return drawBordersEnabled; } protected void repaintIfSnapshot() { if (getEnvironment().getKind() == EnvironmentKind.SNAPSHOT) { repaintIfShowing(); } } protected void repaintIfShowing() { if (isShowing()) { helper.computeParameters(); helper.redraw(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy