< prev index next >

application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/charts/XYChart.java

Print this page

        

*** 39,90 **** import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; import org.openjdk.jmc.common.IDisplayable; import org.openjdk.jmc.common.unit.IQuantity; import org.openjdk.jmc.common.unit.IRange; import org.openjdk.jmc.common.unit.QuantitiesToolkit; import org.openjdk.jmc.common.unit.QuantityRange; import org.openjdk.jmc.ui.charts.IChartInfoVisitor.ITick; public class XYChart { private static final String ELLIPSIS = "..."; //$NON-NLS-1$ private static final Color SELECTION_COLOR = new Color(255, 255, 255, 220); private static final Color RANGE_INDICATION_COLOR = new Color(255, 60, 20); ! private static final int Y_OFFSET = 35; ! private static final int RANGE_INDICATOR_HEIGHT = 4; private final IQuantity start; private final IQuantity end; private IXDataRenderer rendererRoot; private IRenderedRow rendererResult; private final int xOffset; private final int bucketWidth; // FIXME: Use bucketWidth * ticksPerBucket instead of hardcoded value? // private final int ticksPerBucket = 4; private IQuantity currentStart; private IQuantity currentEnd; private final Set<Object> selectedRows = new HashSet<>(); private IQuantity selectionStart; private IQuantity selectionEnd; private SubdividedQuantityRange xBucketRange; private SubdividedQuantityRange xTickRange; ! private int axisWidth; public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot) { this(range.getStart(), range.getEnd(), rendererRoot); } public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot, int xOffset) { this(range.getStart(), range.getEnd(), rendererRoot, xOffset); } public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot, int xOffset, int bucketWidth) { this(range.getStart(), range.getEnd(), rendererRoot, xOffset, bucketWidth); } public XYChart(IQuantity start, IQuantity end, IXDataRenderer rendererRoot) { --- 39,120 ---- import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; + import java.util.Stack; import java.util.function.Consumer; import org.openjdk.jmc.common.IDisplayable; import org.openjdk.jmc.common.unit.IQuantity; import org.openjdk.jmc.common.unit.IRange; import org.openjdk.jmc.common.unit.QuantitiesToolkit; import org.openjdk.jmc.common.unit.QuantityRange; + import org.openjdk.jmc.common.unit.UnitLookup; import org.openjdk.jmc.ui.charts.IChartInfoVisitor.ITick; + import org.openjdk.jmc.ui.misc.ChartDisplayControlBar; + import org.openjdk.jmc.ui.misc.TimelineCanvas; + import org.openjdk.jmc.ui.misc.PatternFly.Palette; public class XYChart { private static final String ELLIPSIS = "..."; //$NON-NLS-1$ private static final Color SELECTION_COLOR = new Color(255, 255, 255, 220); private static final Color RANGE_INDICATION_COLOR = new Color(255, 60, 20); ! private static final int RANGE_INDICATOR_HEIGHT = 7; private final IQuantity start; private final IQuantity end; + private IQuantity rangeDuration; private IXDataRenderer rendererRoot; private IRenderedRow rendererResult; private final int xOffset; + private int yOffset = 35; private final int bucketWidth; // FIXME: Use bucketWidth * ticksPerBucket instead of hardcoded value? // private final int ticksPerBucket = 4; private IQuantity currentStart; private IQuantity currentEnd; private final Set<Object> selectedRows = new HashSet<>(); + private int axisWidth; + private int rowColorCounter; private IQuantity selectionStart; private IQuantity selectionEnd; private SubdividedQuantityRange xBucketRange; private SubdividedQuantityRange xTickRange; ! ! // JFR Threads Page ! private static final double ZOOM_PAN_FACTOR = 0.05; ! private static final int ZOOM_PAN_MODIFIER = 2; ! private double zoomPanPower = ZOOM_PAN_FACTOR / ZOOM_PAN_MODIFIER; ! private double currentZoom; ! private int zoomSteps; ! private ChartDisplayControlBar displayBar; ! private ChartFilterControlBar filterBar; ! private Stack<Integer> modifiedSteps; ! private TimelineCanvas timelineCanvas; public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot) { this(range.getStart(), range.getEnd(), rendererRoot); } public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot, int xOffset) { this(range.getStart(), range.getEnd(), rendererRoot, xOffset); } + // JFR Threads Page + public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot, int xOffset, Integer yOffset, TimelineCanvas timelineCanvas, ChartFilterControlBar filterBar, ChartDisplayControlBar displayBar) { + this(range.getStart(), range.getEnd(), rendererRoot, xOffset); + this.yOffset = yOffset; + this.timelineCanvas = timelineCanvas; + this.filterBar = filterBar; + this.displayBar = displayBar; + this.rangeDuration = range.getExtent(); + this.currentZoom = 100; + this.isZoomCalculated = false; + } + public XYChart(IRange<IQuantity> range, IXDataRenderer rendererRoot, int xOffset, int bucketWidth) { this(range.getStart(), range.getEnd(), rendererRoot, xOffset, bucketWidth); } public XYChart(IQuantity start, IQuantity end, IXDataRenderer rendererRoot) {
*** 97,109 **** public XYChart(IQuantity start, IQuantity end, IXDataRenderer rendererRoot, int xOffset, int bucketWidth) { this.rendererRoot = rendererRoot; // Start value must always be strictly less than end assert (start.compareTo(end) < 0); ! currentStart = start; this.start = start; ! currentEnd = end; this.end = end; this.xOffset = xOffset; this.bucketWidth = bucketWidth; } --- 127,139 ---- public XYChart(IQuantity start, IQuantity end, IXDataRenderer rendererRoot, int xOffset, int bucketWidth) { this.rendererRoot = rendererRoot; // Start value must always be strictly less than end assert (start.compareTo(end) < 0); ! this.currentStart = start; this.start = start; ! this.currentEnd = end; this.end = end; this.xOffset = xOffset; this.bucketWidth = bucketWidth; }
*** 131,226 **** public IRange<IQuantity> getSelectionRange() { return (selectionStart != null) && (selectionEnd != null) ? QuantityRange.createWithEnd(selectionStart, selectionEnd) : null; } ! public void render(Graphics2D context, int width, int height) { ! if (width > xOffset && height > Y_OFFSET) { axisWidth = width - xOffset; // FIXME: xBucketRange and xTickRange should be more related, so that each tick is typically an integer number of buckets (or possibly 2.5 buckets). xBucketRange = new SubdividedQuantityRange(currentStart, currentEnd, axisWidth, bucketWidth); // FIXME: Use bucketWidth * ticksPerBucket instead of hardcoded value? xTickRange = new SubdividedQuantityRange(currentStart, currentEnd, axisWidth, 100); AffineTransform oldTransform = context.getTransform(); context.translate(xOffset, 0); ! doRender(context, height - Y_OFFSET); context.setTransform(oldTransform); } } private void renderRangeIndication(Graphics2D context, int rangeIndicatorY) { // FIXME: Extract the needed functionality from SubdividedQuantityRange SubdividedQuantityRange fullRangeAxis = new SubdividedQuantityRange(start, end, axisWidth, 25); int x1 = (int) fullRangeAxis.getPixel(currentStart); int x2 = (int) Math.ceil(fullRangeAxis.getPixel(currentEnd)); ! if (x1 > 0 || x2 < axisWidth) { context.setPaint(RANGE_INDICATION_COLOR); context.fillRect(x1, rangeIndicatorY, x2 - x1, RANGE_INDICATOR_HEIGHT); context.setPaint(Color.DARK_GRAY); context.drawRect(0, rangeIndicatorY, axisWidth - 1, RANGE_INDICATOR_HEIGHT); } } ! private void doRender(Graphics2D context, int axisHeight) { context.setPaint(Color.LIGHT_GRAY); AWTChartToolkit.drawGrid(context, xTickRange, axisHeight, false); // Attempt to make graphs so low they cover the axis show by drawing the full axis first ... context.setPaint(Color.BLACK); AWTChartToolkit.drawAxis(context, xTickRange, axisHeight - 1, false, 1 - xOffset, false); // ... then the graph ... rendererResult = rendererRoot.render(context, xBucketRange, axisHeight); AffineTransform oldTransform = context.getTransform(); ! renderText(context, rendererResult); context.setTransform(oldTransform); if (!selectedRows.isEmpty()) { ! renderSelection(context, rendererResult); context.setTransform(oldTransform); } // .. and finally a semitransparent axis line again. context.setPaint(new Color(0, 0, 0, 64)); context.drawLine(0, axisHeight - 1, axisWidth - 1, axisHeight - 1); renderRangeIndication(context, axisHeight + 25); } ! private void renderSelection(Graphics2D context, IRenderedRow row) { if (selectedRows.contains(row.getPayload())) { renderSelection(context, xBucketRange, row.getHeight()); } else { List<IRenderedRow> subdivision = row.getNestedRows(); if (subdivision.isEmpty()) { dimRect(context, 0, axisWidth, row.getHeight()); } else { for (IRenderedRow nestedRow : row.getNestedRows()) { ! renderSelection(context, nestedRow); } return; } } context.translate(0, row.getHeight()); } private void renderText(Graphics2D context, IRenderedRow row) { String text = row.getName(); int height = row.getHeight(); if (height >= context.getFontMetrics().getHeight()) { if (text != null) { context.setColor(Color.BLACK); ! int y; ! if (height > 40) { ! context.drawLine(-xOffset, height - 1, -15, height - 1); ! y = height - context.getFontMetrics().getHeight() / 2; ! } else { ! // draw the string in the middle of the row ! y = ((height - context.getFontMetrics().getHeight()) / 2) + context.getFontMetrics().getAscent(); ! } int charsWidth = context.getFontMetrics().charsWidth(text.toCharArray(), 0, text.length()); ! if (charsWidth > xOffset) { float fitRatio = ((float) xOffset) / (charsWidth + context.getFontMetrics().charsWidth(ELLIPSIS.toCharArray(), 0, ELLIPSIS.length())); text = text.substring(0, ((int) (text.length() * fitRatio)) - 1) + ELLIPSIS; } ! context.drawString(text, -xOffset + 2, y); } else { List<IRenderedRow> subdivision = row.getNestedRows(); if (!subdivision.isEmpty()) { for (IRenderedRow nestedRow : row.getNestedRows()) { renderText(context, nestedRow); --- 161,331 ---- public IRange<IQuantity> getSelectionRange() { return (selectionStart != null) && (selectionEnd != null) ? QuantityRange.createWithEnd(selectionStart, selectionEnd) : null; } ! public void renderChart(Graphics2D context, int width, int height) { ! if (width > xOffset && height > yOffset) { axisWidth = width - xOffset; // FIXME: xBucketRange and xTickRange should be more related, so that each tick is typically an integer number of buckets (or possibly 2.5 buckets). xBucketRange = new SubdividedQuantityRange(currentStart, currentEnd, axisWidth, bucketWidth); // FIXME: Use bucketWidth * ticksPerBucket instead of hardcoded value? xTickRange = new SubdividedQuantityRange(currentStart, currentEnd, axisWidth, 100); AffineTransform oldTransform = context.getTransform(); context.translate(xOffset, 0); ! doRenderChart(context, height - yOffset); context.setTransform(oldTransform); } } + public void renderTextCanvasText(Graphics2D context, int width) { + axisWidth = width; + AffineTransform oldTransform = context.getTransform(); + doRenderTextCanvasText(context); + context.setTransform(oldTransform); + } + + public void renderText(Graphics2D context, int width, int height) { + if (width > xOffset && height > yOffset) { + axisWidth = xOffset; + AffineTransform oldTransform = context.getTransform(); + doRenderText(context); + context.setTransform(oldTransform); + axisWidth = width - xOffset; + } + } + private void renderRangeIndication(Graphics2D context, int rangeIndicatorY) { // FIXME: Extract the needed functionality from SubdividedQuantityRange SubdividedQuantityRange fullRangeAxis = new SubdividedQuantityRange(start, end, axisWidth, 25); int x1 = (int) fullRangeAxis.getPixel(currentStart); int x2 = (int) Math.ceil(fullRangeAxis.getPixel(currentEnd)); ! ! if (timelineCanvas != null) { ! timelineCanvas.renderRangeIndicator(x1, x2); ! } else { context.setPaint(RANGE_INDICATION_COLOR); context.fillRect(x1, rangeIndicatorY, x2 - x1, RANGE_INDICATOR_HEIGHT); context.setPaint(Color.DARK_GRAY); context.drawRect(0, rangeIndicatorY, axisWidth - 1, RANGE_INDICATOR_HEIGHT); } } ! private void doRenderChart(Graphics2D context, int axisHeight) { ! rowColorCounter = 0; context.setPaint(Color.LIGHT_GRAY); AWTChartToolkit.drawGrid(context, xTickRange, axisHeight, false); // Attempt to make graphs so low they cover the axis show by drawing the full axis first ... context.setPaint(Color.BLACK); + if (timelineCanvas != null) { + timelineCanvas.setXTickRange(xTickRange); + } else { AWTChartToolkit.drawAxis(context, xTickRange, axisHeight - 1, false, 1 - xOffset, false); + } // ... then the graph ... rendererResult = rendererRoot.render(context, xBucketRange, axisHeight); AffineTransform oldTransform = context.getTransform(); ! context.setTransform(oldTransform); if (!selectedRows.isEmpty()) { ! renderSelectionChart(context, rendererResult); context.setTransform(oldTransform); } // .. and finally a semitransparent axis line again. context.setPaint(new Color(0, 0, 0, 64)); context.drawLine(0, axisHeight - 1, axisWidth - 1, axisHeight - 1); renderRangeIndication(context, axisHeight + 25); } ! private void doRenderText(Graphics2D context) { ! AffineTransform oldTransform = context.getTransform(); ! rowColorCounter = -1; ! renderText(context, rendererResult); ! context.setTransform(oldTransform); ! } ! ! private void doRenderTextCanvasText(Graphics2D context) { ! AffineTransform oldTransform = context.getTransform(); ! rowColorCounter = 0; ! renderText(context, rendererResult); ! context.setTransform(oldTransform); ! if (!selectedRows.isEmpty()) { ! renderSelectionText(context, rendererResult); ! context.setTransform(oldTransform); ! } ! } ! ! private void renderSelectionText(Graphics2D context, IRenderedRow row) { ! if (selectedRows.contains(row.getPayload())) { ! if (row.getHeight() != rendererResult.getHeight()) { ! Color highlight = new Color(0, 206, 209, 20); ! context.setColor(highlight); ! context.fillRect(0, 0, axisWidth, row.getHeight()); ! } else { ! selectedRows.clear(); ! } ! } else { ! List<IRenderedRow> subdivision = row.getNestedRows(); ! if (subdivision.isEmpty()) { ! dimRect(context, 0, axisWidth, row.getHeight()); ! } else { ! for (IRenderedRow nestedRow : row.getNestedRows()) { ! renderSelectionText(context, nestedRow); ! } ! return; ! } ! } ! context.translate(0, row.getHeight()); ! } ! ! private void renderSelectionChart(Graphics2D context, IRenderedRow row) { if (selectedRows.contains(row.getPayload())) { renderSelection(context, xBucketRange, row.getHeight()); } else { List<IRenderedRow> subdivision = row.getNestedRows(); if (subdivision.isEmpty()) { dimRect(context, 0, axisWidth, row.getHeight()); } else { for (IRenderedRow nestedRow : row.getNestedRows()) { ! renderSelectionChart(context, nestedRow); } return; } } context.translate(0, row.getHeight()); } + // Paint the background of every-other row in a slightly different shade + // to better differentiate the thread lanes from one another + private void paintRowBackground(Graphics2D context, int height) { + if (rowColorCounter >= 0) { + if (rowColorCounter % 2 == 0) { + context.setColor(Palette.PF_BLACK_100.getAWTColor()); + } else { + context.setColor(Palette.PF_BLACK_200.getAWTColor()); + } + context.fillRect(0, 0, axisWidth, height); + rowColorCounter++; + } + } + private void renderText(Graphics2D context, IRenderedRow row) { String text = row.getName(); int height = row.getHeight(); if (height >= context.getFontMetrics().getHeight()) { if (text != null) { + paintRowBackground(context, row.getHeight()); context.setColor(Color.BLACK); ! context.drawLine(0, height - 1, axisWidth -15, height - 1); ! int y = ((height - context.getFontMetrics().getHeight()) / 2) + context.getFontMetrics().getAscent(); int charsWidth = context.getFontMetrics().charsWidth(text.toCharArray(), 0, text.length()); ! if (xOffset > 0 && charsWidth > xOffset) { float fitRatio = ((float) xOffset) / (charsWidth + context.getFontMetrics().charsWidth(ELLIPSIS.toCharArray(), 0, ELLIPSIS.length())); text = text.substring(0, ((int) (text.length() * fitRatio)) - 1) + ELLIPSIS; } ! context.drawString(text, 2, y); } else { List<IRenderedRow> subdivision = row.getNestedRows(); if (!subdivision.isEmpty()) { for (IRenderedRow nestedRow : row.getNestedRows()) { renderText(context, nestedRow);
*** 237,246 **** --- 342,354 ---- * * @param rightPercent * @return true if the bounds changed. That is, if a redraw is required. */ public boolean pan(int rightPercent) { + if (rangeDuration != null) { + return panRange(Integer.signum(rightPercent)); + } if (xBucketRange != null) { IQuantity oldStart = currentStart; IQuantity oldEnd = currentEnd; if (rightPercent > 0) { currentEnd = QuantitiesToolkit
*** 258,273 **** --- 366,419 ---- // Return true since a redraw forces creation of xBucketRange. return true; } /** + * Pan the view at a rate relative the current zoom level. + * @param panDirection -1 to pan left, 1 to pan right + * @return true if the chart needs to be redrawn + */ + public boolean panRange(int panDirection) { + if (zoomSteps == 0 || panDirection == 0 || + (currentStart.compareTo(start) == 0 && panDirection == -1) || + (currentEnd.compareTo(end) == 0 && panDirection == 1)) { + return false; + } + + IQuantity panDiff = rangeDuration.multiply(panDirection * zoomPanPower); + IQuantity newStart = currentStart.in(UnitLookup.EPOCH_NS).add(panDiff); + IQuantity newEnd = currentEnd.in(UnitLookup.EPOCH_NS).add(panDiff); + + // if panning would flow over the recording range start or end time, + // calculate the difference and add it so the other side. + if (newStart.compareTo(start) < 0) { + IQuantity diff = start.subtract(newStart); + newStart = start; + newEnd = newEnd.add(diff); + } else if (newEnd.compareTo(end) > 0) { + IQuantity diff = newEnd.subtract(end); + newStart = newStart.add(diff); + newEnd = end; + } + currentStart = newStart; + currentEnd = newEnd; + filterBar.setStartTime(currentStart); + filterBar.setEndTime(currentEnd); + isZoomCalculated = true; + return true; + } + + /** * Zoom the view. * * @param zoomInSteps * @return true if the bounds changed. That is, if a redraw is required. */ public boolean zoom(int zoomInSteps) { + if (rangeDuration != null) { + return zoomRange(zoomInSteps); + } return zoomXAxis(axisWidth / 2, zoomInSteps); } /** * Zoom the view.
*** 278,287 **** --- 424,434 ---- */ public boolean zoom(int x, int zoomInSteps) { return zoomXAxis(x - xOffset, zoomInSteps); } + // Default zoom mechanics private boolean zoomXAxis(int x, int zoomInSteps) { if (xBucketRange == null) { // Return true since a redraw forces creation of xBucketRange. return true; }
*** 297,307 **** --- 444,598 ---- return (currentStart.compareTo(oldStart) != 0) || (currentEnd.compareTo(oldEnd) != 0); } return false; } + /** + * Zoom based on a percentage of the recording range + * @param zoomInSteps + * @return true if a redraw is required as a result of a successful zoom + */ + public boolean zoomRange(int zoomInSteps) { + if (zoomInSteps > 0) { + zoomIn(); + } else { + if (zoomSteps == 0) { + return false; + } + zoomOut(); + } + // set displayBar text + displayBar.setZoomPercentageText(currentZoom); + return true; + } + + /** + * Zoom into the chart at a rate of 5% of the overall recording range at each step. + * If the chart is zoomed in far enough such that one more step at 5% is not possible, + * the zoom power is halved and the zoom will proceed. + * <br> + * Every time the zoom power is halved, the instigating step value is pushed onto the + * modifiedSteps stack. This stack is consulted on zoom out events in order to ensure + * the chart zooms out the same way it was zoomed in. + */ + private void zoomIn() { + IQuantity zoomDiff = rangeDuration.multiply(zoomPanPower); + IQuantity newStart = currentStart.in(UnitLookup.EPOCH_NS).add(zoomDiff); + IQuantity newEnd = currentEnd.in(UnitLookup.EPOCH_NS).subtract(zoomDiff); + if (newStart.compareTo(newEnd) >= 0) { // adjust the zoom factor + if (modifiedSteps == null) { + modifiedSteps = new Stack<Integer>(); + } + modifiedSteps.push(zoomSteps); + zoomPanPower = zoomPanPower / ZOOM_PAN_MODIFIER; + zoomDiff = rangeDuration.multiply(zoomPanPower); + newStart = currentStart.in(UnitLookup.EPOCH_NS).add(zoomDiff); + newEnd = currentEnd.in(UnitLookup.EPOCH_NS).subtract(zoomDiff); + } + currentZoom = currentZoom + (zoomPanPower * ZOOM_PAN_MODIFIER * 100); + isZoomCalculated = true; + setVisibleRange(newStart, newEnd); + zoomSteps++; + } + + /** + * Zoom out of the chart at a rate equal to the how the chart was zoomed in. + */ + private void zoomOut() { + if (modifiedSteps != null && modifiedSteps.size() > 0 && modifiedSteps.peek() == zoomSteps) { + modifiedSteps.pop(); + zoomPanPower = zoomPanPower * ZOOM_PAN_MODIFIER; + } + IQuantity zoomDiff = rangeDuration.multiply(zoomPanPower); + IQuantity newStart = currentStart.in(UnitLookup.EPOCH_NS).subtract(zoomDiff); + IQuantity newEnd = currentEnd.in(UnitLookup.EPOCH_NS).add(zoomDiff); + + // if zooming out would flow over the recording range start or end time, + // calculate the difference and add it to the other side. + if (newStart.compareTo(start) < 0) { + IQuantity diff = start.subtract(newStart); + newStart = start; + newEnd = newEnd.add(diff); + } else if (newEnd.compareTo(end) > 0) { + IQuantity diff = newEnd.subtract(end); + newStart = newStart.subtract(diff); + newEnd = end; + } + currentZoom = currentZoom - (zoomPanPower * ZOOM_PAN_MODIFIER * 100); + if (currentZoom < 100) { + currentZoom = 100; + } + isZoomCalculated = true; + setVisibleRange(newStart, newEnd); + zoomSteps--; + } + + // need to check from ChartAndPopupTableUI if not using the OG start/end position, + // will have to calculate the new zoom level + public void resetZoomFactor() { + zoomSteps = 0; + zoomPanPower = ZOOM_PAN_FACTOR / ZOOM_PAN_MODIFIER; + currentZoom = 100; + displayBar.setZoomPercentageText(currentZoom); + modifiedSteps = new Stack<Integer>(); + } + + /** + * Reset the visible range to be the recording range, and reset the zoom-related objects + */ + public void resetTimeline() { + resetZoomFactor(); + setVisibleRange(start, end); + } + + private void selectionZoom(IQuantity newStart, IQuantity newEnd) { + double percentage = calculateZoom(newStart, newEnd); + zoomSteps = calculateZoomSteps(percentage); + currentZoom = 100 + (percentage * 100); + displayBar.setScaleValue(zoomSteps); + displayBar.setZoomPercentageText(currentZoom); + } + + /** + * When a drag-select zoom occurs, use the new range value to determine how many steps have been taken, + * and adjust zoomSteps and zoomPower accordingly + */ + private double calculateZoom(IQuantity newStart, IQuantity newEnd) { + // calculate the new visible range, and it's percentage of the total range + IQuantity newRange = newEnd.in(UnitLookup.EPOCH_NS).subtract(newStart.in(UnitLookup.EPOCH_NS)); + return 1 - (newRange.longValue() / (double) rangeDuration.in(UnitLookup.NANOSECOND).longValue()); + } + + /** + * Calculate the number of steps required to achieve the passed zoom percentage + */ + private int calculateZoomSteps(double percentage) { + double tempPercent = 0; + int steps = 0; + do { + tempPercent = tempPercent + ZOOM_PAN_FACTOR; + steps++; + } while (tempPercent <= percentage); + return steps; + } + + private boolean isZoomCalculated; + private boolean isZoomPanDrag; + + public void setIsZoomPanDrag(boolean isZoomPanDrag) { + this.isZoomPanDrag = isZoomPanDrag; + } + + private boolean getIsZoomPanDrag() { + return isZoomPanDrag; + } + public void setVisibleRange(IQuantity rangeStart, IQuantity rangeEnd) { + if (rangeDuration != null && !isZoomCalculated && rangeStart != start && !getIsZoomPanDrag()) { + selectionZoom(rangeStart, rangeEnd); + } + isZoomCalculated = false; rangeStart = QuantitiesToolkit.max(rangeStart, start); rangeEnd = QuantitiesToolkit.min(rangeEnd, end); if (rangeStart.compareTo(rangeEnd) < 0) { SubdividedQuantityRange testRange = new SubdividedQuantityRange(rangeStart, rangeEnd, 10000, 1); if (testRange.getQuantityAtPixel(0).compareTo(testRange.getQuantityAtPixel(1)) < 0) {
*** 310,319 **** --- 601,614 ---- } else { // Ensures that zoom out is always allowed currentStart = QuantitiesToolkit.min(rangeStart, currentStart); currentEnd = QuantitiesToolkit.max(rangeEnd, currentEnd); } + if (filterBar != null) { + filterBar.setStartTime(currentStart); + filterBar.setEndTime(currentEnd); + } rangeListeners.stream().forEach(l -> l.accept(getVisibleRange())); } } private List<Consumer<IRange<IQuantity>>> rangeListeners = new ArrayList<>();
*** 330,363 **** public void clearVisibleRange() { currentStart = start; currentEnd = end; } ! public boolean select(int x1, int x2, int y1, int y2) { ! int xStart = Math.min(x1, x2) - xOffset; ! int xEnd = Math.max(x1, x2) - xOffset; ! ! if (xBucketRange != null && (xEnd >= 0)) { ! return select(xBucketRange.getQuantityAtPixel(Math.max(0, xStart)), xBucketRange.getQuantityAtPixel(xEnd), ! y1, y2); } else { ! return select(null, null, y1, y2); } } ! public boolean select(IQuantity xStart, IQuantity xEnd, int y1, int y2) { if (xStart != null && xStart.compareTo(start) < 0) { xStart = start; } if (xEnd != null && xEnd.compareTo(end) > 0) { xEnd = end; } Set<Object> oldRows = null; if (QuantitiesToolkit.same(selectionStart, xStart) && QuantitiesToolkit.same(selectionEnd, xEnd)) { oldRows = new HashSet<>(selectedRows); } selectedRows.clear(); addSelectedRows(rendererResult, 0, Math.min(y1, y2), Math.max(y1, y2)); selectionStart = xStart; selectionEnd = xEnd; return (oldRows == null) || !oldRows.equals(selectedRows); } --- 625,660 ---- public void clearVisibleRange() { currentStart = start; currentEnd = end; } ! public boolean select(int x1, int x2, int y1, int y2, boolean clear) { ! int xStart = Math.min(x1, x2); ! int xEnd = Math.max(x1, x2); ! ! if (xBucketRange != null && (xEnd != xStart) && xEnd - xOffset >= 0) { ! return select(xBucketRange.getQuantityAtPixel(Math.max(0, xStart - xOffset)), xBucketRange.getQuantityAtPixel(xEnd - xOffset), ! y1, y2, clear); } else { ! return select(null, null, y1, y2, clear); } } ! public boolean select(IQuantity xStart, IQuantity xEnd, int y1, int y2, boolean clear) { if (xStart != null && xStart.compareTo(start) < 0) { xStart = start; } if (xEnd != null && xEnd.compareTo(end) > 0) { xEnd = end; } Set<Object> oldRows = null; if (QuantitiesToolkit.same(selectionStart, xStart) && QuantitiesToolkit.same(selectionEnd, xEnd)) { oldRows = new HashSet<>(selectedRows); } + if (clear) { selectedRows.clear(); + } addSelectedRows(rendererResult, 0, Math.min(y1, y2), Math.max(y1, y2)); selectionStart = xStart; selectionEnd = xEnd; return (oldRows == null) || !oldRows.equals(selectedRows); }
*** 370,380 **** selectionStart = selectionEnd = null; return true; } private boolean addSelectedRows(IRenderedRow row, int yRowStart, int ySelectionStart, int ySelectionEnd) { ! List<IRenderedRow> subdivision = row.getNestedRows(); if (subdivision.isEmpty()) { return addPayload(row); } else { boolean nestedHasPayload = false; for (IRenderedRow nestedRow : row.getNestedRows()) { --- 667,677 ---- selectionStart = selectionEnd = null; return true; } private boolean addSelectedRows(IRenderedRow row, int yRowStart, int ySelectionStart, int ySelectionEnd) { ! List<IRenderedRow> subdivision = row.getNestedRows(); // height 1450, has all 32 rows if (subdivision.isEmpty()) { return addPayload(row); } else { boolean nestedHasPayload = false; for (IRenderedRow nestedRow : row.getNestedRows()) {
*** 391,401 **** --- 688,702 ---- } private boolean addPayload(IRenderedRow row) { Object payload = row.getPayload(); if (payload != null) { + if (selectedRows.contains(payload)) { // ctrl+click deselection + selectedRows.remove(payload); + } else { selectedRows.add(payload); + } return true; } return false; }
*** 429,439 **** } } private static void dimRect(Graphics2D context, int from, int width, int height) { context.setColor(SELECTION_COLOR); ! context.fillRect(from, 0, width, height); } /** * Let the {@code visitor} visit the chart elements in the vicinity of {@code x} and {@code y}. * The visitation should adhere to a basic front to back ordering, so that elements which --- 730,740 ---- } } private static void dimRect(Graphics2D context, int from, int width, int height) { context.setColor(SELECTION_COLOR); ! context.fillRect(from , 0, width, height); } /** * Let the {@code visitor} visit the chart elements in the vicinity of {@code x} and {@code y}. * The visitation should adhere to a basic front to back ordering, so that elements which
< prev index next >