/* * Copyright 2012 AndroidPlot.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.androidplot.xy; import android.graphics.*; import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.SizeMetrics; import com.androidplot.ui.widget.Widget; import com.androidplot.util.*; import java.text.DecimalFormat; import java.text.Format; /** * Displays graphical data annotated with domain and range tick markers. */ public class XYGraphWidget extends Widget { public float getRangeLabelOrientation() { return rangeLabelOrientation; } public void setRangeLabelOrientation(float rangeLabelOrientation) { this.rangeLabelOrientation = rangeLabelOrientation; } public float getDomainLabelOrientation() { return domainLabelOrientation; } public void setDomainLabelOrientation(float domainLabelOrientation) { this.domainLabelOrientation = domainLabelOrientation; } public Mapping<Paint, Number> getDomainLabelPaintMap() { return domainLabelPaintMap; } /** * Set a mapping to override the Paint used to draw domain labels. The mapping should * return null for values that should not be overridden. * @param domainLabelPaintMap */ public void setDomainLabelPaintMap(Mapping<Paint, Number> domainLabelPaintMap) { this.domainLabelPaintMap = domainLabelPaintMap; } public Mapping<Paint, Number> getRangeLabelPaintMap() { return rangeLabelPaintMap; } /** * Set a mapping to override the Paint used to draw range labels. The mapping should * return null for values that should not be overridden. * @param rangeLabelPaintMap */ public void setRangeLabelPaintMap(Mapping<Paint, Number> rangeLabelPaintMap) { this.rangeLabelPaintMap = rangeLabelPaintMap; } /** * Will be used in a future version. */ public enum XYPlotOrientation { HORIZONTAL, VERTICAL } private static final int MARKER_LABEL_SPACING = 2; private static final int CURSOR_LABEL_SPACING = 2; // space between cursor private static final String TAG = "AndroidPlot"; // lines and label in // pixels private float domainLabelWidth = 15; // how many pixels is the area // allocated for domain labels private float rangeLabelWidth = 41; // ... private float domainLabelVerticalOffset = -5; private float domainLabelHorizontalOffset = 0.0f; private float rangeLabelHorizontalOffset = 1.0f; // allows tweaking of text position private float rangeLabelVerticalOffset = 0.0f; // allows tweaking of text position private int ticksPerRangeLabel = 1; private int ticksPerDomainLabel = 1; private float gridPaddingTop = 0; private float gridPaddingBottom = 0; private float gridPaddingLeft = 0; private float gridPaddingRight = 0; private int domainLabelTickExtension = 5; private int domainLabelSubTickExtension = 0; private int rangeLabelTickExtension = 5; private int rangeLabelSubTickExtension = 0; private Paint gridBackgroundPaint; private Paint rangeGridLinePaint; private Paint rangeSubGridLinePaint; private Paint domainGridLinePaint; private Paint domainSubGridLinePaint; private Paint domainLabelPaint; private Paint rangeLabelPaint; private Paint domainCursorPaint; private Paint rangeCursorPaint; private Paint cursorLabelPaint; private Paint cursorLabelBackgroundPaint; private XYPlot plot; private Format rangeValueFormat; private Format domainValueFormat; private Paint domainOriginLinePaint; private Paint rangeOriginLinePaint; private Paint domainOriginLabelPaint; private Paint rangeOriginLabelPaint; private RectF gridRect; private RectF paddedGridRect; private float domainCursorPosition; private float rangeCursorPosition; @SuppressWarnings("FieldCanBeLocal") private boolean drawCursorLabelEnabled = true; private boolean drawMarkersEnabled = true; private boolean rangeAxisLeft = true; private boolean domainAxisBottom = true; private boolean rangeTick = true; private boolean rangeSubTick = true; private boolean domainTick = true; private boolean domainSubTick = true; private float rangeLabelOrientation; private float domainLabelOrientation; private Mapping<Paint, Number> domainLabelPaintMap; private Mapping<Paint, Number> rangeLabelPaintMap; // TODO: consider typing this manager with a special // axisLabelRegionFormatter // private ZHash<LineRegion, AxisValueLabelFormatter> domainLabelRegions; // private ZHash<LineRegion, AxisValueLabelFormatter> rangeLabelRegions; private ZHash<RectRegion, AxisValueLabelFormatter> axisValueLabelRegions; { gridBackgroundPaint = new Paint(); gridBackgroundPaint.setColor(Color.rgb(140, 140, 140)); gridBackgroundPaint.setStyle(Paint.Style.FILL); rangeGridLinePaint = new Paint(); rangeGridLinePaint.setColor(Color.rgb(180, 180, 180)); rangeGridLinePaint.setAntiAlias(true); rangeGridLinePaint.setStyle(Paint.Style.STROKE); domainGridLinePaint = new Paint(rangeGridLinePaint); domainSubGridLinePaint = new Paint(domainGridLinePaint); rangeSubGridLinePaint = new Paint(rangeGridLinePaint); domainOriginLinePaint = new Paint(); domainOriginLinePaint.setColor(Color.WHITE); domainOriginLinePaint.setAntiAlias(true); rangeOriginLinePaint = new Paint(); rangeOriginLinePaint.setColor(Color.WHITE); rangeOriginLinePaint.setAntiAlias(true); domainOriginLabelPaint = new Paint(); domainOriginLabelPaint.setColor(Color.WHITE); domainOriginLabelPaint.setAntiAlias(true); domainOriginLabelPaint.setTextAlign(Paint.Align.CENTER); rangeOriginLabelPaint = new Paint(); rangeOriginLabelPaint.setColor(Color.WHITE); rangeOriginLabelPaint.setAntiAlias(true); rangeOriginLabelPaint.setTextAlign(Paint.Align.RIGHT); domainLabelPaint = new Paint(); domainLabelPaint.setColor(Color.LTGRAY); domainLabelPaint.setAntiAlias(true); domainLabelPaint.setTextAlign(Paint.Align.CENTER); rangeLabelPaint = new Paint(); rangeLabelPaint.setColor(Color.LTGRAY); rangeLabelPaint.setAntiAlias(true); rangeLabelPaint.setTextAlign(Paint.Align.RIGHT); domainCursorPaint = new Paint(); domainCursorPaint.setColor(Color.YELLOW); rangeCursorPaint = new Paint(); rangeCursorPaint.setColor(Color.YELLOW); cursorLabelPaint = new Paint(); cursorLabelPaint.setColor(Color.YELLOW); cursorLabelBackgroundPaint = new Paint(); cursorLabelBackgroundPaint.setColor(Color.argb(100, 50, 50, 50)); setMarginTop(7); setMarginRight(4); setMarginBottom(4); rangeValueFormat = new DecimalFormat("0.0"); domainValueFormat = new DecimalFormat("0.0"); // domainLabelRegions = new ZHash<LineRegion, // AxisValueLabelFormatter>(); // rangeLabelRegions = new ZHash<LineRegion, AxisValueLabelFormatter>(); axisValueLabelRegions = new ZHash<RectRegion, AxisValueLabelFormatter>(); } public XYGraphWidget(LayoutManager layoutManager, XYPlot plot, SizeMetrics sizeMetrics) { super(layoutManager, sizeMetrics); this.plot = plot; } public ZIndexable<RectRegion> getAxisValueLabelRegions() { return axisValueLabelRegions; } /** * Add a new Region used for rendering axis valuelabels. Note that it is * possible to add multiple Region instances which overlap, in which cast * the last region to be added will be used. It is up to the developer to * guard against this often undesireable situation. * * @param region * @param formatter */ public void addAxisValueLabelRegion(RectRegion region, AxisValueLabelFormatter formatter) { axisValueLabelRegions.addToTop(region, formatter); } /** * Convenience method - wraps addAxisValueLabelRegion, using * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off range * axis value labels. * * @param min * @param max * @param formatter * */ public void addDomainAxisValueLabelRegion(double min, double max, AxisValueLabelFormatter formatter) { addAxisValueLabelRegion(new RectRegion(min, max, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, null), formatter); } /** * Convenience method - wraps addAxisValueLabelRegion, using * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off domain * axis value labels. * * @param min * @param max * @param formatter */ public void addRangeAxisValueLabelRegion(double min, double max, AxisValueLabelFormatter formatter) { addAxisValueLabelRegion(new RectRegion(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, min, max, null), formatter); } /* * public void addRangeLabelRegion(LineRegion region, * AxisValueLabelFormatter formatter) { rangeLabelRegions.addToTop(region, * formatter); } * * public boolean removeRangeLabelRegion(LineRegion region) { return * rangeLabelRegions.remove(region); } */ /** * Returns the formatter associated with the first (bottom) Region * containing x and y. * * @param x * @param y * @return the formatter associated with the first (bottom) region * containing x and y. null otherwise. */ public AxisValueLabelFormatter getAxisValueLabelFormatterForVal(double x, double y) { for (RectRegion r : axisValueLabelRegions.elements()) { if (r.containsValue(x, y)) { return axisValueLabelRegions.get(r); } } return null; } public AxisValueLabelFormatter getAxisValueLabelFormatterForDomainVal( double val) { for (RectRegion r : axisValueLabelRegions.elements()) { if (r.containsDomainValue(val)) { return axisValueLabelRegions.get(r); } } return null; } public AxisValueLabelFormatter getAxisValueLabelFormatterForRangeVal( double val) { for (RectRegion r : axisValueLabelRegions.elements()) { if (r.containsRangeValue(val)) { return axisValueLabelRegions.get(r); } } return null; } /** * Returns the formatter associated with the first (bottom-most) Region * containing value. * * @param value * @return */ /* * public AxisValueLabelFormatter getXYAxisFormatterForRangeVal(double * value) { return getRegionContainingVal(rangeLabelRegions, value); } *//** * Returns the formatter associated with the first (bottom-most) Region * containing value. * * @param value * @return */ /* * public AxisValueLabelFormatter getXYAxisFormatterForDomainVal(double * value) { return getRegionContainingVal(domainLabelRegions, value); } */ /* * private AxisValueLabelFormatter getRegionContainingVal(ZHash<LineRegion, * AxisValueLabelFormatter> zhash, double val) { for (LineRegion r : * zhash.elements()) { if (r.contains(val)) { return * rangeLabelRegions.get(r); } } // nothing found return null; } */ /** * Returns a RectF representing the grid area last drawn by this plot. * * @return */ public RectF getGridRect() { return paddedGridRect; } private String getFormattedRangeValue(Number value) { return rangeValueFormat.format(value); } private String getFormattedDomainValue(Number value) { return domainValueFormat.format(value); } /** * Convenience method. Wraps getYVal(float) * * @param point * @return */ public Double getYVal(PointF point) { return getYVal(point.y); } /** * Converts a y pixel to a y value. * * @param yPix * @return */ public Double getYVal(float yPix) { if (plot.getCalculatedMinY() == null || plot.getCalculatedMaxY() == null) { return null; } return ValPixConverter.pixToVal(yPix - paddedGridRect.top, plot .getCalculatedMinY().doubleValue(), plot.getCalculatedMaxY() .doubleValue(), paddedGridRect.height(), true); } /** * Convenience method. Wraps getXVal(float) * * @param point * @return */ public Double getXVal(PointF point) { return getXVal(point.x); } /** * Converts an x pixel into an x value. * * @param xPix * @return */ public Double getXVal(float xPix) { if (plot.getCalculatedMinX() == null || plot.getCalculatedMaxX() == null) { return null; } return ValPixConverter.pixToVal(xPix - paddedGridRect.left, plot .getCalculatedMinX().doubleValue(), plot.getCalculatedMaxX() .doubleValue(), paddedGridRect.width(), false); } @Override protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException { gridRect = getGridRect(widgetRect); // used for drawing the background // of the grid paddedGridRect = getPaddedGridRect(gridRect); // used for drawing lines // etc. //Log.v(TAG, "gridRect :" + gridRect); //Log.v(TAG, "paddedGridRect :" + paddedGridRect); // if (!plot.isEmpty()) { // don't draw if we have no space to draw into if ((paddedGridRect.height() > 0.0f) && (paddedGridRect.width() > 0.0f)) { if (plot.getCalculatedMinX() != null && plot.getCalculatedMaxX() != null && plot.getCalculatedMinY() != null && plot.getCalculatedMaxY() != null) { drawGrid(canvas); drawData(canvas); drawCursors(canvas); if (isDrawMarkersEnabled()) { drawMarkers(canvas); } } } // } } private RectF getGridRect(RectF widgetRect) { return new RectF(widgetRect.left + ((rangeAxisLeft)?rangeLabelWidth:1), widgetRect.top + ((domainAxisBottom)?1:domainLabelWidth), widgetRect.right - ((rangeAxisLeft)?1:rangeLabelWidth), widgetRect.bottom - ((domainAxisBottom)?domainLabelWidth:1)); } private RectF getPaddedGridRect(RectF gridRect) { return new RectF(gridRect.left + gridPaddingLeft, gridRect.top + gridPaddingTop, gridRect.right - gridPaddingRight, gridRect.bottom - gridPaddingBottom); } private void drawTickText(Canvas canvas, XYAxisType axis, Number value, float xPix, float yPix, Paint labelPaint) { AxisValueLabelFormatter rf = null; String txt = null; double v = value.doubleValue(); int canvasState = canvas.save(); try { switch (axis) { case DOMAIN: rf = getAxisValueLabelFormatterForDomainVal(v); txt = getFormattedDomainValue(value); canvas.rotate(getDomainLabelOrientation(), xPix, yPix); break; case RANGE: rf = getAxisValueLabelFormatterForRangeVal(v); txt = getFormattedRangeValue(value); canvas.rotate(getRangeLabelOrientation(), xPix, yPix); break; } // if a matching region formatter was found, create a clone // of labelPaint and use the formatter's color. Otherwise // just use labelPaint: Paint p; if (rf != null) { // p = rf.getPaint(); p = new Paint(labelPaint); p.setColor(rf.getColor()); // p.setColor(Color.RED); } else { p = labelPaint; } canvas.drawText(txt, xPix, yPix, p); } finally { canvas.restoreToCount(canvasState); } } private void drawDomainTick(Canvas canvas, float xPix,float width, Paint linePaint) { float startY = domainSubTick ? gridRect.top : gridRect.bottom; float stopY = gridRect.bottom + domainLabelSubTickExtension; Paint tmp = new Paint(linePaint); tmp.setStyle(Paint.Style.FILL); tmp.setColor(Color.GRAY); tmp.setAlpha(80); float right =xPix + width; right = right > gridRect.right ? gridRect.right : right; canvas.drawRect(xPix, startY, right, stopY, tmp); } private void drawDomainTick(Canvas canvas, float xPix, Number xVal, Paint labelPaint, Paint linePaint, boolean drawLineOnly) { if (!drawLineOnly) { if (linePaint != null && (domainTick || domainLabelTickExtension > 0)) { if (domainAxisBottom){ canvas.drawLine(xPix, domainTick ? gridRect.top : gridRect.bottom, xPix, gridRect.bottom + domainLabelTickExtension, linePaint); } else { canvas.drawLine(xPix, gridRect.top - domainLabelTickExtension, xPix, domainTick ? gridRect.bottom : gridRect.top, linePaint); } } if (labelPaint != null) { float fontHeight = FontUtils.getFontHeight(labelPaint); float yPix; if (domainAxisBottom){ yPix = gridRect.bottom + domainLabelTickExtension + domainLabelVerticalOffset + fontHeight; } else { yPix = gridRect.top - domainLabelTickExtension - domainLabelVerticalOffset; } drawTickText(canvas, XYAxisType.DOMAIN, xVal, xPix + domainLabelHorizontalOffset, yPix, labelPaint); } } else if (linePaint != null && (domainSubTick || domainLabelSubTickExtension > 0)) { if (domainAxisBottom){ canvas.drawLine(xPix, domainSubTick ? gridRect.top : gridRect.bottom, xPix, gridRect.bottom + domainLabelSubTickExtension, linePaint); } else { canvas.drawLine(xPix, gridRect.top - domainLabelSubTickExtension, xPix, domainSubTick ? gridRect.bottom : gridRect.top, linePaint); } } } public void drawRangeTick(Canvas canvas, float yPix, Number yVal, Paint labelPaint, Paint linePaint, boolean drawLineOnly) { if (!drawLineOnly) { if (linePaint != null && (rangeTick || rangeLabelTickExtension > 0)) { if (rangeAxisLeft){ canvas.drawLine(gridRect.left - rangeLabelTickExtension, yPix, rangeTick ? gridRect.right : gridRect.left, yPix, linePaint); } else { canvas.drawLine(rangeTick ? gridRect.left : gridRect.right, yPix, gridRect.right + rangeLabelTickExtension, yPix, linePaint); } } if (labelPaint != null) { float xPix; if (rangeAxisLeft){ xPix = gridRect.left - (rangeLabelTickExtension + rangeLabelHorizontalOffset); } else { xPix = gridRect.right + (rangeLabelTickExtension + rangeLabelHorizontalOffset); } drawTickText(canvas, XYAxisType.RANGE, yVal, xPix, yPix - rangeLabelVerticalOffset, labelPaint); } } else if (linePaint != null && (rangeSubTick || rangeLabelSubTickExtension > 0)) { if (rangeAxisLeft){ canvas.drawLine(gridRect.left - rangeLabelSubTickExtension, yPix, rangeSubTick ? gridRect.right : gridRect.left, yPix, linePaint); } else { canvas.drawLine(rangeTick ? gridRect.left : gridRect.right, yPix, gridRect.right + rangeLabelSubTickExtension, yPix, linePaint); } } } /** * Draws the drid and domain/range labels for the plot. * * @param canvas */ protected void drawGrid(Canvas canvas) { if (gridBackgroundPaint != null) { canvas.drawRect(gridRect, gridBackgroundPaint); } float domainOriginF; if (plot.getDomainOrigin() != null) { double domainOriginVal = plot.getDomainOrigin().doubleValue(); domainOriginF = ValPixConverter.valToPix(domainOriginVal, plot .getCalculatedMinX().doubleValue(), plot .getCalculatedMaxX().doubleValue(), paddedGridRect.width(), false); domainOriginF += paddedGridRect.left; // if no origin is set, use the leftmost value visible on the grid: } else { domainOriginF = paddedGridRect.left; } XYStep domainStep = XYStepCalculator.getStep(plot, XYAxisType.DOMAIN, paddedGridRect, plot.getCalculatedMinX().doubleValue(), plot .getCalculatedMaxX().doubleValue()); // draw domain origin: if (domainOriginF >= paddedGridRect.left && domainOriginF <= paddedGridRect.right) { if (domainOriginLinePaint != null){ domainOriginLinePaint.setTextAlign(Paint.Align.CENTER); } Paint olp = domainLabelPaintMap != null ? domainLabelPaintMap.get(plot.getDomainOrigin()) : domainLabelPaint; if(olp == null) { olp = domainLabelPaint; } drawDomainTick(canvas, domainOriginF, plot.getDomainOrigin() .doubleValue(), olp, domainOriginLinePaint, false); } // draw ticks LEFT of origin: { int i = 1; double xVal; float xPix = domainOriginF - domainStep.getStepPix(); for (; xPix >= paddedGridRect.left; xPix = domainOriginF - (i * domainStep.getStepPix())) { xVal = plot.getDomainOrigin().doubleValue() - i * domainStep.getStepVal(); Paint dlp = domainLabelPaintMap != null ? domainLabelPaintMap.get(xVal) : domainLabelPaint; if(dlp == null) { dlp = domainLabelPaint; } if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) { if (i % getTicksPerDomainLabel() == 0) { drawDomainTick(canvas, xPix, xVal, dlp, domainGridLinePaint, false); } else { drawDomainTick(canvas, xPix, xVal, dlp, domainSubGridLinePaint, true); } } i++; } } // draw ticks RIGHT of origin: { int i = 0; double xVal; float xPix = domainOriginF ; for (; xPix <= paddedGridRect.right; xPix = domainOriginF + (i * domainStep.getStepPix())) { xVal = plot.getDomainOrigin().doubleValue() + i * domainStep.getStepVal(); Paint dlp = domainLabelPaintMap != null ? domainLabelPaintMap.get(xVal) : domainLabelPaint; if(dlp == null) { dlp = domainLabelPaint; } if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) { if (i % getTicksPerDomainLabel() == 0) { drawDomainTick(canvas, xPix, xVal, dlp, domainGridLinePaint, false); } else { drawDomainTick(canvas, xPix, xVal, dlp, domainSubGridLinePaint, true); } if(i%2==0) { drawDomainTick(canvas, xPix, domainStep.getStepPix(), domainGridLinePaint); } } i++; } } // draw range origin: float rangeOriginF; if (plot.getRangeOrigin() != null) { // --------- NEW WAY ------ double rangeOriginD = plot.getRangeOrigin().doubleValue(); rangeOriginF = ValPixConverter.valToPix(rangeOriginD, plot .getCalculatedMinY().doubleValue(), plot .getCalculatedMaxY().doubleValue(), paddedGridRect.height(), true); rangeOriginF += paddedGridRect.top; // if no origin is set, use the leftmost value visible on the grid } else { rangeOriginF = paddedGridRect.bottom; } XYStep rangeStep = XYStepCalculator.getStep(plot, XYAxisType.RANGE, paddedGridRect, plot.getCalculatedMinY().doubleValue(), plot .getCalculatedMaxY().doubleValue()); // draw range origin: if (rangeOriginF >= paddedGridRect.top && rangeOriginF <= paddedGridRect.bottom) { if (rangeOriginLinePaint != null){ rangeOriginLinePaint.setTextAlign(Paint.Align.RIGHT); } Paint olp = rangeLabelPaintMap != null ? rangeLabelPaintMap.get(plot.getRangeOrigin()) : rangeLabelPaint; if(olp == null) { olp = rangeLabelPaint; } drawRangeTick(canvas, rangeOriginF, plot.getRangeOrigin() .doubleValue(), olp, rangeOriginLinePaint, false); } // draw ticks ABOVE origin: { int i = 1; double yVal; float yPix = rangeOriginF - rangeStep.getStepPix(); for (; yPix >= paddedGridRect.top; yPix = rangeOriginF - (i * rangeStep.getStepPix())) { yVal = plot.getRangeOrigin().doubleValue() + i * rangeStep.getStepVal(); Paint rlp = rangeLabelPaintMap != null ? rangeLabelPaintMap.get(yVal) : rangeLabelPaint; if (rlp == null) { rlp = rangeLabelPaint; } if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) { if (i % getTicksPerRangeLabel() == 0) { drawRangeTick(canvas, yPix, yVal, rlp, rangeGridLinePaint, false); } else { drawRangeTick(canvas, yPix, yVal, rlp, rangeSubGridLinePaint, true); } } i++; } } // draw ticks BENEATH origin: { int i = 1; double yVal; float yPix = rangeOriginF + rangeStep.getStepPix(); for (; yPix <= paddedGridRect.bottom; yPix = rangeOriginF + (i * rangeStep.getStepPix())) { yVal = plot.getRangeOrigin().doubleValue() - i * rangeStep.getStepVal(); Paint rlp = rangeLabelPaintMap != null ? rangeLabelPaintMap.get(yVal) : rangeLabelPaint; if (rlp == null) { rlp = rangeLabelPaint; } if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) { if (i % getTicksPerRangeLabel() == 0) { drawRangeTick(canvas, yPix, yVal, rlp, rangeGridLinePaint, false); } else { drawRangeTick(canvas, yPix, yVal, rlp, rangeSubGridLinePaint, true); } } i++; } } drawRangeGoodRange(canvas,rangeGridLinePaint, rangeStep.getStepPix()); } private void drawRangeGoodRange(Canvas canvas, Paint rangeGridLinePaint, double stepPix) { float bottom = (float) (8.5*stepPix) + gridRect.top; float top = (float) (7*stepPix) + gridRect.top; Paint paint = new Paint(rangeGridLinePaint); paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.FILL); paint.setAlpha(40); canvas.drawRect(gridRect.left,top,gridRect.right,bottom,paint); } /** * Renders the text associated with user defined markers * * @param canvas * @param text * @param marker * @param x * @param y */ private void drawMarkerText(Canvas canvas, String text, ValueMarker marker, float x, float y) { x += MARKER_LABEL_SPACING; y -= MARKER_LABEL_SPACING; RectF textRect = new RectF(FontUtils.getStringDimensions(text, marker.getTextPaint())); textRect.offsetTo(x, y - textRect.height()); if (textRect.right > paddedGridRect.right) { textRect.offset(-(textRect.right - paddedGridRect.right), 0); } if (textRect.top < paddedGridRect.top) { textRect.offset(0, paddedGridRect.top - textRect.top); } canvas.drawText(text, textRect.left, textRect.bottom, marker.getTextPaint()); } protected void drawMarkers(Canvas canvas) { for (YValueMarker marker : plot.getYValueMarkers()) { if (marker.getValue() != null) { double yVal = marker.getValue().doubleValue(); float yPix = ValPixConverter.valToPix(yVal, plot .getCalculatedMinY().doubleValue(), plot .getCalculatedMaxY().doubleValue(), paddedGridRect .height(), true); yPix += paddedGridRect.top; canvas.drawLine(paddedGridRect.left, yPix, paddedGridRect.right, yPix, marker.getLinePaint()); // String text = getFormattedRangeValue(yVal); float xPix = marker.getTextPosition().getPixelValue( paddedGridRect.width()); xPix += paddedGridRect.left; if (marker.getText() != null) { drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); } else { drawMarkerText(canvas, getFormattedRangeValue(marker.getValue()), marker, xPix, yPix); } } } for (XValueMarker marker : plot.getXValueMarkers()) { if (marker.getValue() != null) { double xVal = marker.getValue().doubleValue(); float xPix = ValPixConverter.valToPix(xVal, plot .getCalculatedMinX().doubleValue(), plot .getCalculatedMaxX().doubleValue(), paddedGridRect .width(), false); xPix += paddedGridRect.left; canvas.drawLine(xPix, paddedGridRect.top, xPix, paddedGridRect.bottom, marker.getLinePaint()); // String text = getFormattedDomainValue(xVal); float yPix = marker.getTextPosition().getPixelValue( paddedGridRect.height()); yPix += paddedGridRect.top; if (marker.getText() != null) { drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); } else { drawMarkerText(canvas, getFormattedDomainValue(marker.getValue()), marker, xPix, yPix); } } } } protected void drawCursors(Canvas canvas) { boolean hasDomainCursor = false; // draw the domain cursor: if (domainCursorPaint != null && domainCursorPosition <= paddedGridRect.right && domainCursorPosition >= paddedGridRect.left) { hasDomainCursor = true; canvas.drawLine(domainCursorPosition, paddedGridRect.top, domainCursorPosition, paddedGridRect.bottom, domainCursorPaint); } boolean hasRangeCursor = false; // draw the range cursor: if (rangeCursorPaint != null && rangeCursorPosition >= paddedGridRect.top && rangeCursorPosition <= paddedGridRect.bottom) { hasRangeCursor = true; canvas.drawLine(paddedGridRect.left, rangeCursorPosition, paddedGridRect.right, rangeCursorPosition, rangeCursorPaint); } if (drawCursorLabelEnabled && cursorLabelPaint != null && hasRangeCursor && hasDomainCursor) { String label = "X=" + getDomainValueFormat().format(getDomainCursorVal()); label += " Y=" + getRangeValueFormat().format(getRangeCursorVal()); // convert the label dimensions rect into floating-point: RectF cursorRect = new RectF(FontUtils.getPackedStringDimensions( label, cursorLabelPaint)); cursorRect.offsetTo(domainCursorPosition, rangeCursorPosition - cursorRect.height()); // if we are too close to the right edge of the plot, we will move // the // label to the left side of our cursor: if (cursorRect.right >= paddedGridRect.right) { cursorRect.offsetTo(domainCursorPosition - cursorRect.width(), cursorRect.top); } // same thing for the top edge of the plot: // dunno why but these rects can have negative values for top and // bottom. if (cursorRect.top <= paddedGridRect.top) { cursorRect.offsetTo(cursorRect.left, rangeCursorPosition); } if (cursorLabelBackgroundPaint != null) { canvas.drawRect(cursorRect, cursorLabelBackgroundPaint); } canvas.drawText(label, cursorRect.left, cursorRect.bottom, cursorLabelPaint); } } /** * Draws lines and points for each element in the series. * * @param canvas * @throws PlotRenderException */ protected void drawData(Canvas canvas) throws PlotRenderException { // TODO: iterate through a XYSeriesRenderer list // int canvasState = canvas.save(); try { canvas.save(Canvas.ALL_SAVE_FLAG); canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT); for (XYSeriesRenderer renderer : plot.getRendererList()) { renderer.render(canvas, paddedGridRect); } // canvas.restoreToCount(canvasState); } finally { canvas.restore(); } } protected void drawPoint(Canvas canvas, PointF point, Paint paint) { canvas.drawPoint(point.x, point.y, paint); } public float getDomainLabelWidth() { return domainLabelWidth; } public void setDomainLabelWidth(float domainLabelWidth) { this.domainLabelWidth = domainLabelWidth; } public float getRangeLabelWidth() { return rangeLabelWidth; } public void setRangeLabelWidth(float rangeLabelWidth) { this.rangeLabelWidth = rangeLabelWidth; } public float getDomainLabelVerticalOffset() { return domainLabelVerticalOffset; } public void setDomainLabelVerticalOffset(float domainLabelVerticalOffset) { this.domainLabelVerticalOffset = domainLabelVerticalOffset; } public float getDomainLabelHorizontalOffset() { return domainLabelHorizontalOffset; } public void setDomainLabelHorizontalOffset(float domainLabelHorizontalOffset) { this.domainLabelHorizontalOffset = domainLabelHorizontalOffset; } public float getRangeLabelHorizontalOffset() { return rangeLabelHorizontalOffset; } public void setRangeLabelHorizontalOffset(float rangeLabelHorizontalOffset) { this.rangeLabelHorizontalOffset = rangeLabelHorizontalOffset; } public float getRangeLabelVerticalOffset() { return rangeLabelVerticalOffset; } public void setRangeLabelVerticalOffset(float rangeLabelVerticalOffset) { this.rangeLabelVerticalOffset = rangeLabelVerticalOffset; } public Paint getGridBackgroundPaint() { return gridBackgroundPaint; } public void setGridBackgroundPaint(Paint gridBackgroundPaint) { this.gridBackgroundPaint = gridBackgroundPaint; } public Paint getDomainLabelPaint() { return domainLabelPaint; } public void setDomainLabelPaint(Paint domainLabelPaint) { this.domainLabelPaint = domainLabelPaint; } public Paint getRangeLabelPaint() { return rangeLabelPaint; } public void setRangeLabelPaint(Paint rangeLabelPaint) { this.rangeLabelPaint = rangeLabelPaint; } /** * Get the paint used to draw the domain grid line. */ public Paint getDomainGridLinePaint() { return domainGridLinePaint; } /** * Set the paint used to draw the domain grid line. * @param gridLinePaint */ public void setDomainGridLinePaint(Paint gridLinePaint) { this.domainGridLinePaint = gridLinePaint; } /** * Get the paint used to draw the range grid line. */ public Paint getRangeGridLinePaint() { return rangeGridLinePaint; } /** * Get the paint used to draw the domain grid line. */ public Paint getDomainSubGridLinePaint() { return domainSubGridLinePaint; } /** * Set the paint used to draw the domain grid line. * @param gridLinePaint */ public void setDomainSubGridLinePaint(Paint gridLinePaint) { this.domainSubGridLinePaint = gridLinePaint; } /** * Set the Paint used to draw the range grid line. * @param gridLinePaint */ public void setRangeGridLinePaint(Paint gridLinePaint) { this.rangeGridLinePaint = gridLinePaint; } /** * Get the paint used to draw the range grid line. */ public Paint getRangeSubGridLinePaint() { return rangeSubGridLinePaint; } /** * Set the Paint used to draw the range grid line. * @param gridLinePaint */ public void setRangeSubGridLinePaint(Paint gridLinePaint) { this.rangeSubGridLinePaint = gridLinePaint; } // TODO: make a generic renderer queue. public Format getRangeValueFormat() { return rangeValueFormat; } public void setRangeValueFormat(Format rangeValueFormat) { this.rangeValueFormat = rangeValueFormat; } public Format getDomainValueFormat() { return domainValueFormat; } public void setDomainValueFormat(Format domainValueFormat) { this.domainValueFormat = domainValueFormat; } public int getDomainLabelTickExtension() { return domainLabelTickExtension; } public void setDomainLabelTickExtension(int domainLabelTickExtension) { this.domainLabelTickExtension = domainLabelTickExtension; } public int getDomainLabelSubTickExtension() { return domainLabelSubTickExtension; } public void setDomainLabelSubTickExtension(int domainLabelSubTickExtension) { this.domainLabelSubTickExtension = domainLabelSubTickExtension; } public int getRangeLabelTickExtension() { return rangeLabelTickExtension; } public void setRangeLabelTickExtension(int rangeLabelTickExtension) { this.rangeLabelTickExtension = rangeLabelTickExtension; } public int getRangeLabelSubTickExtension() { return rangeLabelSubTickExtension; } public void setRangeLabelSubTickExtension(int rangeLabelSubTickExtension) { this.rangeLabelSubTickExtension = rangeLabelSubTickExtension; } public int getTicksPerRangeLabel() { return ticksPerRangeLabel; } public void setTicksPerRangeLabel(int ticksPerRangeLabel) { this.ticksPerRangeLabel = ticksPerRangeLabel; } public int getTicksPerDomainLabel() { return ticksPerDomainLabel; } public void setTicksPerDomainLabel(int ticksPerDomainLabel) { this.ticksPerDomainLabel = ticksPerDomainLabel; } public void setGridPaddingTop(float gridPaddingTop) { this.gridPaddingTop = gridPaddingTop; } public float getGridPaddingBottom() { return gridPaddingBottom; } public void setGridPaddingBottom(float gridPaddingBottom) { this.gridPaddingBottom = gridPaddingBottom; } public float getGridPaddingLeft() { return gridPaddingLeft; } public void setGridPaddingLeft(float gridPaddingLeft) { this.gridPaddingLeft = gridPaddingLeft; } public float getGridPaddingRight() { return gridPaddingRight; } public void setGridPaddingRight(float gridPaddingRight) { this.gridPaddingRight = gridPaddingRight; } public float getGridPaddingTop() { return gridPaddingTop; } public void setGridPadding(float left, float top, float right, float bottom) { setGridPaddingLeft(left); setGridPaddingTop(top); setGridPaddingRight(right); setGridPaddingBottom(bottom); } public Paint getDomainOriginLinePaint() { return domainOriginLinePaint; } public void setDomainOriginLinePaint(Paint domainOriginLinePaint) { this.domainOriginLinePaint = domainOriginLinePaint; } public Paint getRangeOriginLinePaint() { return rangeOriginLinePaint; } public void setRangeOriginLinePaint(Paint rangeOriginLinePaint) { this.rangeOriginLinePaint = rangeOriginLinePaint; } public Paint getDomainOriginLabelPaint() { return domainOriginLabelPaint; } public void setDomainOriginLabelPaint(Paint domainOriginLabelPaint) { this.domainOriginLabelPaint = domainOriginLabelPaint; } public Paint getRangeOriginLabelPaint() { return rangeOriginLabelPaint; } public void setRangeOriginLabelPaint(Paint rangeOriginLabelPaint) { this.rangeOriginLabelPaint = rangeOriginLabelPaint; } public void setCursorPosition(float x, float y) { setDomainCursorPosition(x); setRangeCursorPosition(y); } public void setCursorPosition(PointF point) { setCursorPosition(point.x, point.y); } public float getDomainCursorPosition() { return domainCursorPosition; } public Double getDomainCursorVal() { return getXVal(getDomainCursorPosition()); } public void setDomainCursorPosition(float domainCursorPosition) { this.domainCursorPosition = domainCursorPosition; } public float getRangeCursorPosition() { return rangeCursorPosition; } public Double getRangeCursorVal() { return getYVal(getRangeCursorPosition()); } public void setRangeCursorPosition(float rangeCursorPosition) { this.rangeCursorPosition = rangeCursorPosition; } public Paint getCursorLabelPaint() { return cursorLabelPaint; } public void setCursorLabelPaint(Paint cursorLabelPaint) { this.cursorLabelPaint = cursorLabelPaint; } public Paint getCursorLabelBackgroundPaint() { return cursorLabelBackgroundPaint; } public void setCursorLabelBackgroundPaint(Paint cursorLabelBackgroundPaint) { this.cursorLabelBackgroundPaint = cursorLabelBackgroundPaint; } public boolean isDrawMarkersEnabled() { return drawMarkersEnabled; } public void setDrawMarkersEnabled(boolean drawMarkersEnabled) { this.drawMarkersEnabled = drawMarkersEnabled; } public boolean isRangeAxisLeft() { return rangeAxisLeft; } public void setRangeAxisLeft(boolean rangeAxisLeft) { this.rangeAxisLeft = rangeAxisLeft; } public boolean isDomainAxisBottom() { return domainAxisBottom; } public void setDomainAxisBottom(boolean domainAxisBottom) { this.domainAxisBottom = domainAxisBottom; } public boolean isRangeTick() { return rangeTick; } public void setRangeTick(boolean rangeTick) { this.rangeTick = rangeTick; } public boolean isRangeSubTick() { return rangeSubTick; } public void setRangeSubTick(boolean rangeSubTick) { this.rangeSubTick = rangeSubTick; } public boolean isDomainTick() { return domainTick; } public void setDomainTick(boolean domainTick) { this.domainTick = domainTick; } public boolean isDomainSubTick() { return domainSubTick; } public void setDomainSubTick(boolean domainSubTick) { this.domainSubTick = domainSubTick; } /* * set the position of the range axis labels. Set the labelPaint textSizes before setting this. * This call sets the various vertical and horizontal offsets and widths to good defaults. * * @param rangeAxisLeft axis labels are on the left hand side not the right hand side. * @param rangeAxisOverlay axis labels are overlaid on the plot, not external to it. * @param tickSize the size of the tick extensions for none overlaid axis. * @param maxLableString Sample label representing the biggest size space needs to be allocated for. */ public void setRangeAxisPosition(boolean rangeAxisLeft, boolean rangeAxisOverlay, int tickSize, String maxLableString){ setRangeAxisLeft(rangeAxisLeft); if (rangeAxisOverlay) { setRangeLabelWidth(1); // needs to be at least 1 to display grid line. setRangeLabelHorizontalOffset(-2.0f); setRangeLabelVerticalOffset(2.0f); // get above the line Paint p = getRangeLabelPaint(); if (p != null) { p.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); } Paint po = getRangeOriginLabelPaint(); if (po != null) { po.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); } setRangeLabelTickExtension(0); } else { setRangeLabelWidth(1); // needs to be at least 1 to display grid line. // if we have a paint this gets bigger. setRangeLabelHorizontalOffset(1.0f); setRangeLabelTickExtension(tickSize); Paint p = getRangeLabelPaint(); if (p != null) { p.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); Rect r = FontUtils.getPackedStringDimensions(maxLableString,p); setRangeLabelVerticalOffset(r.top/2); setRangeLabelWidth(r.right + getRangeLabelTickExtension()); } Paint po = getRangeOriginLabelPaint(); if (po != null) { po.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); } } } /* * set the position of the domain axis labels. Set the labelPaint textSizes before setting this. * This call sets the various vertical and horizontal offsets and widths to good defaults. * * @param domainAxisBottom axis labels are on the bottom not the top of the plot. * @param domainAxisOverlay axis labels are overlaid on the plot, not external to it. * @param tickSize the size of the tick extensions for non overlaid axis. * @param maxLableString Sample label representing the biggest size space needs to be allocated for. */ public void setDomainAxisPosition(boolean domainAxisBottom, boolean domainAxisOverlay, int tickSize, String maxLabelString){ setDomainAxisBottom(domainAxisBottom); if (domainAxisOverlay) { setDomainLabelWidth(1); // needs to be at least 1 to display grid line. setDomainLabelVerticalOffset(2.0f); // get above the line setDomainLabelTickExtension(0); Paint p = getDomainLabelPaint(); if (p != null) { Rect r = FontUtils.getPackedStringDimensions(maxLabelString,p); if (domainAxisBottom){ setDomainLabelVerticalOffset(2 * r.top); } else { setDomainLabelVerticalOffset(r.top - 1.0f); } } } else { setDomainLabelWidth(1); // needs to be at least 1 to display grid line. // if we have a paint this gets bigger. setDomainLabelTickExtension(tickSize); Paint p = getDomainLabelPaint(); if (p != null) { float fontHeight = FontUtils.getFontHeight(p); if (domainAxisBottom){ setDomainLabelVerticalOffset(-4.0f); } else { setDomainLabelVerticalOffset(+1.0f); } setDomainLabelWidth(fontHeight + getDomainLabelTickExtension()); } } } }