1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2019, Red Hat Inc. All rights reserved.
   4  *
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * The contents of this file are subject to the terms of either the Universal Permissive License
   8  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   9  *
  10  * or the following license:
  11  *
  12  * Redistribution and use in source and binary forms, with or without modification, are permitted
  13  * provided that the following conditions are met:
  14  *
  15  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  16  * and the following disclaimer.
  17  *
  18  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  19  * conditions and the following disclaimer in the documentation and/or other materials provided with
  20  * the distribution.
  21  *
  22  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  23  * endorse or promote products derived from this software without specific prior written permission.
  24  *
  25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  27  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  30  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  31  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  32  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33  */
  34 package org.openjdk.jmc.ui.misc;
  35 
  36 import java.util.ArrayList;
  37 import java.util.HashMap;
  38 import java.util.List;
  39 import java.util.Map;
  40 import java.util.Timer;
  41 import java.util.TimerTask;
  42 
  43 import org.eclipse.swt.SWT;
  44 import org.eclipse.swt.custom.ScrolledComposite;
  45 import org.eclipse.swt.events.MouseAdapter;
  46 import org.eclipse.swt.events.MouseEvent;
  47 import org.eclipse.swt.events.MouseMoveListener;
  48 import org.eclipse.swt.events.MouseWheelListener;
  49 import org.eclipse.swt.events.PaintEvent;
  50 import org.eclipse.swt.events.PaintListener;
  51 import org.eclipse.swt.graphics.Cursor;
  52 import org.eclipse.swt.graphics.GC;
  53 import org.eclipse.swt.graphics.Point;
  54 import org.eclipse.swt.graphics.Rectangle;
  55 import org.eclipse.swt.layout.GridData;
  56 import org.eclipse.swt.layout.GridLayout;
  57 import org.eclipse.swt.widgets.Button;
  58 import org.eclipse.swt.widgets.Canvas;
  59 import org.eclipse.swt.widgets.Composite;
  60 import org.eclipse.swt.widgets.Event;
  61 import org.eclipse.swt.widgets.Listener;
  62 import org.eclipse.swt.widgets.Scale;
  63 import org.eclipse.swt.widgets.Text;
  64 import org.openjdk.jmc.common.unit.IQuantity;
  65 import org.openjdk.jmc.common.unit.IRange;
  66 import org.openjdk.jmc.ui.UIPlugin;
  67 import org.openjdk.jmc.ui.charts.SubdividedQuantityRange;
  68 import org.openjdk.jmc.ui.charts.XYChart;
  69 import org.openjdk.jmc.ui.misc.PatternFly.Palette;
  70 
  71 public class ChartDisplayControlBar extends Composite {
  72         private static final String ZOOM_IN_CURSOR = "zoomInCursor";
  73         private static final String ZOOM_OUT_CURSOR = "zoomOutCursor";
  74         private static final String DEFAULT_CURSOR = "defaultCursor";
  75         private static final String HAND_CURSOR = "handCursor";
  76         private static final int ZOOM_AMOUNT = 1;
  77         private Map<String, Cursor> cursors;
  78         private Scale scale;
  79         private Text zoomText;
  80         private XYChart chart;
  81         private ChartCanvas chartCanvas;
  82         private ChartTextCanvas textCanvas;
  83         private List<Button> buttonGroup;
  84         private Button zoomInBtn;
  85         private Button zoomOutBtn;
  86         private Button selectionBtn;
  87         private Button zoomPanBtn;
  88         private Button scaleToFitBtn;
  89         private ZoomPan zoomPan;
  90 
  91         public ChartDisplayControlBar(Composite parent) {
  92                 super(parent, SWT.NO_BACKGROUND);
  93 
  94                 this.setLayout(new GridLayout());
  95                 this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
  96                 this.setBackground(Palette.PF_BLACK_300.getSWTColor());
  97 
  98                 cursors = new HashMap<>();
  99                 cursors.put(DEFAULT_CURSOR, getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
 100                 cursors.put(HAND_CURSOR, getDisplay().getSystemCursor(SWT.CURSOR_HAND));
 101                 cursors.put(ZOOM_IN_CURSOR, new Cursor(getDisplay(),
 102                                 UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_ZOOM_IN).getImageData(), 0, 0));
 103                 cursors.put(ZOOM_OUT_CURSOR, new Cursor(getDisplay(),
 104                                 UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_ZOOM_OUT).getImageData(), 0, 0));
 105 
 106                 buttonGroup = new ArrayList<>();
 107                 selectionBtn = new Button(this, SWT.TOGGLE);
 108                 selectionBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
 109                 selectionBtn.setImage(UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_SELECTION));
 110                 selectionBtn.setSelection(true);
 111                 selectionBtn.addListener(SWT.Selection, new Listener() {
 112                         @Override
 113                         public void handleEvent(Event event) {
 114                                 setButtonSelectionStates(selectionBtn, null);
 115                                 changeCursor(DEFAULT_CURSOR);
 116                         };
 117                 });
 118                 buttonGroup.add(selectionBtn);
 119 
 120                 zoomInBtn = new Button(this, SWT.TOGGLE);
 121                 zoomInBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
 122                 zoomInBtn.setImage(UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_ZOOM_IN));
 123                 zoomInBtn.setSelection(false);
 124                 zoomInBtn.addListener(SWT.Selection,  new Listener() {
 125                         @Override
 126                         public void handleEvent(Event event) {
 127                                 if (scale.getSelection() > 0) {
 128                                         setButtonSelectionStates(zoomInBtn, zoomPanBtn);
 129                                         changeCursor(ZOOM_IN_CURSOR);
 130                                 } else {
 131                                         setButtonSelectionStates(selectionBtn, null);
 132                                         changeCursor(DEFAULT_CURSOR);
 133                                 }
 134                         }
 135                 });
 136                 zoomInBtn.addMouseListener(new LongPressListener(ZOOM_AMOUNT));
 137                 buttonGroup.add(zoomInBtn);
 138 
 139                 scale = new Scale(this, SWT.VERTICAL);
 140                 scale.setMinimum(0);
 141                 scale.setMaximum(30);
 142                 scale.setIncrement(1);
 143                 scale.setSelection(30);
 144                 scale.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true));
 145                 scale.setEnabled(false);
 146 
 147                 zoomText = new Text(this, SWT.BORDER | SWT.READ_ONLY | SWT.SINGLE);
 148                 zoomText.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
 149                 setZoomPercentageText(100);
 150 
 151                 zoomOutBtn = new Button(this, SWT.TOGGLE);
 152                 zoomOutBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
 153                 zoomOutBtn.setImage(UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_ZOOM_OUT));
 154                 zoomOutBtn.setSelection(false);
 155                 zoomOutBtn.addListener(SWT.Selection,  new Listener() {
 156                         @Override
 157                         public void handleEvent(Event e) {
 158                                 if (scale.getSelection() < scale.getMaximum()) {
 159                                         setButtonSelectionStates(zoomOutBtn, zoomPanBtn);
 160                                         changeCursor(ZOOM_OUT_CURSOR);
 161                                 } else {
 162                                         setButtonSelectionStates(selectionBtn, null);
 163                                         changeCursor(DEFAULT_CURSOR);
 164                                 }
 165                         }
 166                 });
 167                 zoomOutBtn.addMouseListener(new LongPressListener(-ZOOM_AMOUNT));
 168                 buttonGroup.add(zoomOutBtn);
 169 
 170                 zoomPanBtn = new Button(this, SWT.TOGGLE);
 171                 zoomPanBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
 172                 zoomPanBtn.setImage(UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_ZOOM_PAN));
 173                 zoomPanBtn.setSelection(false);
 174                 zoomPanBtn.addListener(SWT.Selection, new Listener() {
 175                         @Override
 176                         public void handleEvent(Event event) {
 177                                 showZoomPanDisplay(zoomPanBtn.getSelection());
 178                         }
 179                 });
 180                 buttonGroup.add(zoomPanBtn);
 181 
 182                 scaleToFitBtn = new Button(this, SWT.PUSH);
 183                 scaleToFitBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
 184                 scaleToFitBtn.setImage(UIPlugin.getDefault().getImage(UIPlugin.ICON_FA_SCALE_TO_FIT));
 185                 scaleToFitBtn.setSelection(false);
 186                 scaleToFitBtn.addListener(SWT.Selection, new Listener() {
 187                         @Override
 188                         public void handleEvent(Event event) {
 189                                 resetZoomScale();
 190                                 chart.resetTimeline();
 191                                 chartCanvas.redrawChart();
 192                         }
 193                 });
 194                 buttonGroup.add(scaleToFitBtn);
 195         }
 196 
 197         public void setChart(XYChart chart) {
 198                 this.chart = chart;
 199         }
 200 
 201         public void setChartCanvas(ChartCanvas chartCanvas) {
 202                 this.chartCanvas = chartCanvas;
 203         }
 204 
 205         public void setTextCanvas(ChartTextCanvas textCanvas) {
 206                 this.textCanvas = textCanvas;
 207         }
 208 
 209         public void zoomOnClick(Boolean mouseDown) {
 210                 boolean shouldZoom = zoomInBtn.getSelection() || zoomOutBtn.getSelection() ;
 211                 if (shouldZoom) {
 212                         if (mouseDown) {
 213                                 chart.clearSelection();
 214                         } else {
 215                                 int zoomAmount = zoomInBtn.getSelection() ? ZOOM_AMOUNT : -ZOOM_AMOUNT;
 216                                 zoom(zoomAmount);
 217                                 if (textCanvas != null) {
 218                                         textCanvas.redrawChartText();
 219                                 }
 220                         }
 221                 }
 222         }
 223 
 224         public void zoomToSelection() {
 225                 if (zoomInBtn.getSelection()) {
 226                         IQuantity selectionStart = chart.getSelectionStart();
 227                         IQuantity selectionEnd = chart.getSelectionEnd();
 228                         if (selectionStart == null || selectionEnd == null) {
 229                                 chart.clearVisibleRange();
 230                         } else {
 231                                 chart.setVisibleRange(selectionStart, selectionEnd);
 232                                 chartCanvas.redrawChart();
 233                         }
 234                 }
 235         }
 236 
 237         public void setZoomPercentageText(double zoom) {
 238                 zoomText.setText(String.format("%.2f %s", zoom, "%"));
 239         }
 240 
 241         public void setScaleValue(int value) {
 242                 scale.setSelection(scale.getMaximum() - value);
 243         }
 244 
 245         public void increaseScaleValue() {
 246                 scale.setSelection(scale.getSelection() - 1);
 247         }
 248 
 249         public void decreaseScaleValue() {
 250                 scale.setSelection(scale.getSelection() + 1);
 251         }
 252 
 253         public void resetZoomScale() {
 254                 scale.setSelection(scale.getMaximum());
 255                 setZoomPercentageText(100);
 256         }
 257 
 258         private void changeCursor(String cursorName) {
 259                 chartCanvas.changeCursor(cursors.get(cursorName));
 260         }
 261 
 262         private void setButtonSelectionStates(Button buttonSelected, Button dependentButton) {
 263                 for (Button button : buttonGroup) {
 264                         if ((button.getStyle() & SWT.TOGGLE) != 0) {
 265                                 if (button.equals(buttonSelected)) {
 266                                         button.setSelection(true);
 267                                 } else if (dependentButton != null ) {
 268                                         if (button.equals(dependentButton)) {
 269                                                 button.setSelection(true);
 270                                         } else {
 271                                                 button.setSelection(false);
 272                                         }
 273                                 } else {
 274                                         button.setSelection(false);
 275                                 }
 276                         }
 277                         showZoomPanDisplay(zoomPanBtn.getSelection());
 278                 }
 279         }
 280 
 281         private class LongPressListener extends MouseAdapter {
 282 
 283                 private static final long LONG_PRESS_TIME = 500;
 284                 private Timer timer;
 285                 private int zoomAmount;
 286 
 287                 LongPressListener(int zoomAmount) {
 288                         this.zoomAmount = zoomAmount;
 289                 }
 290 
 291                 @Override
 292                 public void mouseDown(MouseEvent e) {
 293                         if(e.button == 1) {
 294                                 timer = new Timer();
 295                                 timer.schedule(new LongPress(), LONG_PRESS_TIME, (long) (LONG_PRESS_TIME * 1.5));
 296                         }
 297                 }
 298 
 299                 @Override
 300                 public void mouseUp(MouseEvent e) {
 301                         timer.cancel();
 302                 }
 303 
 304                 public class LongPress extends TimerTask {
 305 
 306                         @Override
 307                         public void run() {
 308                                 doZoomInOut(zoomAmount);
 309                         }
 310                 }
 311 
 312                 private void doZoomInOut(int zoomAmount) {
 313                         DisplayToolkit.inDisplayThread().execute( () -> zoom(zoomAmount));
 314                 }
 315         }
 316 
 317         private void zoom(int zoomAmount) {
 318                 int newScaleValue = scale.getSelection() - zoomAmount;
 319                 if (newScaleValue >= scale.getMinimum() && newScaleValue <= scale.getMaximum()) {
 320                         scale.setSelection(scale.getSelection() - zoomAmount);
 321                         chart.zoom(zoomAmount);
 322                         chartCanvas.redrawChart();
 323                 }
 324         }
 325 
 326         public void createZoomPan(Composite parent) {
 327                 zoomPan = new ZoomPan(parent);
 328                 parent.setVisible(false);
 329         }
 330 
 331         private void showZoomPanDisplay(boolean show) {
 332                 if(show) {
 333                         zoomPan.getParent().setVisible(true);
 334                         zoomPan.redrawZoomPan();
 335                 } else {
 336                         zoomPan.getParent().setVisible(false);
 337                 }
 338         }
 339 
 340         private class ZoomPan extends Canvas  {
 341                 private static final int BORDER_PADDING = 2;
 342                 private IRange<IQuantity> chartRange;
 343                 private IRange<IQuantity> lastChartZoomedRange;
 344                 private Rectangle zoomRect;
 345 
 346                 public ZoomPan(Composite parent) {
 347                         super(parent, SWT.NO_BACKGROUND);
 348                         addPaintListener(new Painter());
 349                         PanDetector panDetector = new PanDetector();
 350                         addMouseListener(panDetector);
 351                         addMouseMoveListener(panDetector);
 352                         addMouseWheelListener(panDetector);
 353                         chartRange = chart.getVisibleRange();
 354                 }
 355 
 356                 public void redrawZoomPan() {
 357                         redraw();
 358                 }
 359 
 360                 private class PanDetector extends MouseAdapter implements MouseMoveListener, MouseWheelListener {
 361                         Point currentSelection;
 362                         Point lastSelection;
 363                         boolean isPan = false;
 364 
 365                         @Override
 366                         public void mouseDown(MouseEvent e) {
 367                                 if (e.button == 1 && zoomRect.contains(e.x, e.y)) {
 368                                         isPan = true;
 369                                         chart.setIsZoomPanDrag(isPan);
 370                                         currentSelection = chartCanvas.translateDisplayToImageCoordinates(e.x, e.y);
 371                                 }
 372                         }
 373 
 374                         @Override
 375                         public void mouseUp(MouseEvent e) {
 376                                 isPan = false;
 377                                 chart.setIsZoomPanDrag(isPan);
 378                         }
 379 
 380                         @Override
 381                         public void mouseMove(MouseEvent e) {
 382                                 zoomPan.setCursor(cursors.get(HAND_CURSOR));
 383                                 if (isPan && getParent().getSize().x >= e.x && getParent().getSize().y >= e.y ) {
 384                                         lastSelection = currentSelection;
 385                                         currentSelection = chartCanvas.translateDisplayToImageCoordinates(e.x, e.y);
 386                                         int xdiff = currentSelection.x - lastSelection.x;
 387                                         int ydiff = currentSelection.y - lastSelection.y;
 388                                         updateZoomRectFromPan(xdiff, ydiff);
 389                                 }
 390                         }
 391 
 392                         @Override
 393                         public void mouseScrolled(MouseEvent e) {
 394                                 updateZoomRectFromPan(0, -e.count);
 395                         }
 396                 }
 397 
 398                 private void updateZoomRectFromPan(int xdiff, int ydiff) {
 399                         Point bounds = getParent().getSize();
 400                         boolean xModified = false;
 401                         boolean yModified = false;
 402 
 403                         int xOld = zoomRect.x;
 404                         zoomRect.x += xdiff;
 405                         if (zoomRect.x > (bounds.x - zoomRect.width - BORDER_PADDING - 1))  {
 406                                 zoomRect.x = bounds.x - zoomRect.width - BORDER_PADDING - 1;
 407                         } else if (zoomRect.x < BORDER_PADDING ) {
 408                                 zoomRect.x = BORDER_PADDING;
 409                         }
 410                         xModified = xOld != zoomRect.x;
 411 
 412                         int yOld = zoomRect.y;
 413                         zoomRect.y += ydiff;
 414                         if (zoomRect.y < BORDER_PADDING ) {
 415                                 zoomRect.y = BORDER_PADDING;
 416                         } else if (zoomRect.y > (bounds.y - zoomRect.height- BORDER_PADDING - 1))  {
 417                                 zoomRect.y = bounds.y - zoomRect.height - BORDER_PADDING - 1;
 418                         }
 419                         yModified = yOld != zoomRect.y;
 420 
 421                         if (xModified || yModified) {
 422                                 updateChartFromZoomRect(xModified, yModified);
 423                                 chartCanvas.redrawChart();
 424                         }
 425                 }
 426 
 427                 private void updateChartFromZoomRect(boolean updateXRange, boolean updateYRange) {
 428                         Rectangle zoomCanvasBounds = new Rectangle(0, 0, getParent().getSize().x, getParent().getSize().y);
 429                         Rectangle totalBounds = chartCanvas.getBounds();
 430 
 431                         if (updateXRange) {
 432                                 double ratio = getVisibilityRatio(zoomRect.x - BORDER_PADDING,
 433                                                 zoomCanvasBounds.x, zoomCanvasBounds.width - BORDER_PADDING);
 434                                 int start = getPixelLocation(ratio, totalBounds.width, 0);
 435 
 436                                 ratio = getVisibilityRatio(zoomRect.x + zoomRect.width + BORDER_PADDING + 1,
 437                                                 zoomCanvasBounds.width, zoomCanvasBounds.width - BORDER_PADDING);
 438                                 int end = getPixelLocation(ratio, totalBounds.width, totalBounds.width);
 439 
 440                                 SubdividedQuantityRange xAxis = new SubdividedQuantityRange(chartRange.getStart(),
 441                                                 chartRange.getEnd(), totalBounds.width, 1);
 442                                 chart.setVisibleRange(xAxis.getQuantityAtPixel(start), xAxis.getQuantityAtPixel(end));
 443                                 lastChartZoomedRange = chart.getVisibleRange();
 444                         }
 445                         if (updateYRange) {
 446                                 double ratio = getVisibilityRatio(zoomRect.y - BORDER_PADDING, 0,
 447                                                 zoomCanvasBounds.height - BORDER_PADDING);
 448                                 int top = getPixelLocation(ratio, totalBounds.height, 0);
 449 
 450                                 Point p = ((ScrolledComposite) chartCanvas.getParent()).getOrigin();
 451                                 p.y = top;
 452 
 453                                 if (textCanvas != null) {
 454                                         textCanvas.syncScroll(p);
 455                                 }
 456                                 chartCanvas.syncScroll(p);
 457                         }
 458                 }
 459 
 460                 class Painter implements PaintListener {
 461                         @Override
 462                         public void paintControl(PaintEvent e) {
 463 
 464                                 Rectangle backgroundRect = new Rectangle(0, 0, getParent().getSize().x, getParent().getSize().y);
 465                                 GC gc = e.gc;
 466 
 467                                 gc.setBackground(Palette.PF_BLACK_400.getSWTColor());
 468                                 gc.fillRectangle(backgroundRect);
 469                                 gc.setForeground(Palette.PF_BLACK_900.getSWTColor());
 470                                 gc.drawRectangle(0, 0, backgroundRect.width - 1 , backgroundRect.height - 1);
 471 
 472                                 updateZoomRectFromChart();
 473 
 474                                 gc.setBackground(Palette.PF_BLACK_100.getSWTColor());
 475                                 gc.fillRectangle(zoomRect);
 476                                 gc.setForeground(Palette.PF_BLACK_900.getSWTColor());
 477                                 gc.drawRectangle(zoomRect);
 478                         }
 479                 }
 480 
 481                 private void updateZoomRectFromChart() {
 482                         Rectangle zoomCanvasBounds = new Rectangle(0, 0, getParent().getSize().x, getParent().getSize().y);
 483                         IRange<IQuantity> zoomedRange = chart.getVisibleRange();
 484                         IQuantity visibleWidth = chartRange.getExtent();
 485                         double visibleHeight =  chartCanvas.getParent().getBounds().height;
 486                         Rectangle totalBounds = chartCanvas.getBounds();
 487 
 488                         if (zoomRect == null ) {
 489                                 zoomRect = new Rectangle(0, 0, 0, 0);
 490                         }
 491                         if (!chart.getVisibleRange().equals(lastChartZoomedRange)) {
 492                                 double ratio = getVisibilityRatio(zoomedRange.getStart(), chartRange.getStart(), visibleWidth);
 493                                 int start = getPixelLocation(ratio, zoomCanvasBounds.width, 0);
 494 
 495                                 ratio = getVisibilityRatio(zoomedRange.getEnd(), chartRange.getEnd(), visibleWidth);
 496                                 int end = getPixelLocation(ratio, zoomCanvasBounds.width, zoomCanvasBounds.width);
 497 
 498                                 zoomRect.x = start + BORDER_PADDING;
 499                                 zoomRect.width = end - start - 2 * BORDER_PADDING - 1;
 500                                 lastChartZoomedRange = chart.getVisibleRange();
 501                         }
 502                         double ratio = getVisibilityRatio(0, totalBounds.y, totalBounds.height);
 503                         int top = getPixelLocation(ratio, zoomCanvasBounds.height, 0);
 504 
 505                         ratio = getVisibilityRatio(visibleHeight, totalBounds.height + totalBounds.y, totalBounds.height);
 506                         int bottom = getPixelLocation(ratio, zoomCanvasBounds.height, zoomCanvasBounds.height);
 507 
 508                         zoomRect.y  = top + BORDER_PADDING;
 509                         zoomRect.height = bottom - top - 2 * BORDER_PADDING - 1;
 510 
 511                 }
 512 
 513                 private double getVisibilityRatio(double visibleBound, double borderBound, double totalLength) {
 514                         double diff = visibleBound - borderBound;
 515                         return diff/totalLength;
 516                 }
 517 
 518                 private double getVisibilityRatio(IQuantity visibleBound, IQuantity borderBound, IQuantity totalLength) {
 519                         IQuantity diff = visibleBound.subtract(borderBound);
 520                         return diff.ratioTo(totalLength);
 521                 }
 522 
 523                 private int getPixelLocation(double visiblityRatio, int totalLength, int offset) {
 524                         return offset + (int) (visiblityRatio * totalLength);
 525                 }
 526         }
 527 }