1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.ui.charts;
  34 
  35 import java.awt.Color;
  36 import java.awt.Graphics2D;
  37 import java.awt.Paint;
  38 import java.awt.Point;
  39 import java.awt.geom.Point2D;
  40 import java.util.Arrays;
  41 
  42 import org.openjdk.jmc.common.unit.IQuantity;
  43 import org.openjdk.jmc.common.unit.UnitLookup;
  44 import org.openjdk.jmc.ui.charts.XYQuantities.AbstractSpan;
  45 
  46 public class QuantitySpanRenderer implements IXDataRenderer {
  47         /**
  48          * Sentinel value used to indicate a missing (unknown) start timestamp.
  49          */
  50         public static final IQuantity MISSING_START = UnitLookup.EPOCH_S.quantity(0);
  51 
  52         /**
  53          * Sentinel value used to indicate a missing (unknown) end timestamp.
  54          */
  55         public static final IQuantity MISSING_END = UnitLookup.EPOCH_S.quantity(Long.MAX_VALUE);
  56 
  57         private final IQuantitySeries<?> ranges;
  58         private final Paint paint;
  59         private final int minOutlineHeight;
  60         private final IXDataRenderer content;
  61         private final String text;
  62         private final String description;
  63 
  64         public QuantitySpanRenderer(IQuantitySeries<?> ranges, IXDataRenderer content, Paint paint, int minOutlineHeight,
  65                         String text, String description) {
  66                 this.ranges = ranges;
  67                 this.content = content;
  68                 this.paint = paint;
  69                 this.minOutlineHeight = minOutlineHeight;
  70                 this.text = text;
  71                 this.description = description;
  72         }
  73 
  74         private static int calcMargin(int height) {
  75                 return Math.min(5, (height + 10) / 20);
  76         }
  77 
  78         @Override
  79         public IRenderedRow render(Graphics2D context, SubdividedQuantityRange xRange, int height) {
  80                 int margin = calcMargin(height);
  81                 int innerHeight = height - 2 * margin;
  82                 context.translate(0, margin);
  83                 context.setPaint(paint);
  84                 XYQuantities<?> quantities = ranges.getQuantities(xRange);
  85                 // Need to set y range to same as x range to be able to draw ranges (and eliminate creation of quantities).
  86                 quantities.setYRange(xRange);
  87                 AWTChartToolkit.drawRanges(context, quantities, innerHeight, true);
  88                 IRenderedRow renderedContent = content.render(context, xRange, innerHeight);
  89                 if (innerHeight >= minOutlineHeight) {
  90                         context.setPaint(Color.BLACK);
  91                         AWTChartToolkit.drawRanges(context, quantities, innerHeight, false);
  92                 }
  93                 context.translate(0, -margin);
  94                 return new QuantitySpanRendering(margin, quantities, renderedContent, paint, text, description);
  95         }
  96 
  97         private static class QuantitySpanRendering extends RenderedRowBase {
  98 
  99                 private final XYQuantities<?> points;
 100                 private final IRenderedRow content;
 101                 private final Paint paint;
 102                 private final int margin;
 103                 private String description;
 104 
 105                 public QuantitySpanRendering(int margin, XYQuantities<?> points, IRenderedRow content, Paint paint, String text,
 106                                 String description) {
 107                         super(Arrays.asList(new RenderedRowBase(margin), content, new RenderedRowBase(margin)),
 108                                         content.getHeight() + 2 * margin, text, description, null);
 109                         this.margin = margin;
 110                         this.points = points;
 111                         this.content = content;
 112                         this.paint = paint;
 113                         this.description = description;
 114                 }
 115 
 116                 @Override
 117                 public void infoAt(IChartInfoVisitor visitor, int x, int y, Point offset) {
 118                         offset = new Point(offset.x, offset.y + margin);
 119                         content.infoAt(visitor, x, y, offset);
 120 
 121                         // FIXME: Only output this if near the boundaries? At least handle infinite lengths.
 122                         if (points != null) {
 123                                 int bucket = points.floorIndexAtX(x);
 124                                 if (bucket < points.getSize()) {
 125                                         Span span = new Span(bucket, offset);
 126                                         while (bucket >= 0) {
 127                                                 double x2 = points.getPixelY(bucket);
 128                                                 if (x < x2) {
 129                                                         span.setIndex(bucket);
 130                                                         visitor.visit(span);
 131                                                         // Break now, or can there be more to collect?
 132                                                         break;
 133                                                 }
 134                                                 bucket--;
 135                                         }
 136                                 }
 137                         }
 138                 }
 139 
 140                 private class Span extends AbstractSpan {
 141                         public Span(int index, Point2D offset) {
 142                                 super(index, offset);
 143                         }
 144 
 145                         @Override
 146                         protected XYQuantities<?> getXYSet() {
 147                                 return points;
 148                         }
 149 
 150                         @Override
 151                         protected int getHeight() {
 152                                 return content.getHeight();
 153                         }
 154 
 155                         @Override
 156                         public Color getColor() {
 157                                 return (paint instanceof Color) ? (Color) paint : null;
 158                         }
 159 
 160                         private void setIndex(int index) {
 161                                 this.index = index;
 162                         }
 163 
 164                         @Override
 165                         public IQuantity getStartX() {
 166                                 IQuantity org = super.getStartX();
 167                                 return (org == MISSING_START) ? null : org;
 168                         }
 169 
 170                         @Override
 171                         public IQuantity getEndX() {
 172                                 IQuantity org = super.getEndX();
 173                                 return (org == MISSING_END) ? null : org;
 174                         }
 175 
 176                         @Override
 177                         public String getDescription() {
 178                                 return description;
 179                         }
 180                 }
 181 
 182         }
 183 
 184 }