blob: e46a676432f4872ff49c1d3d9080790f1571632a [file] [log] [blame]
/*
* 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.FontUtils;
import com.androidplot.util.ValPixConverter;
import com.androidplot.util.ZHash;
import com.androidplot.util.ZIndexable;
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;
}
/**
* 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 rangeLabelTickExtension = 5;
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 float rangeLabelOrientation;
private float domainLabelOrientation;
// 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, Number xVal,
Paint labelPaint, Paint linePaint, boolean drawLineOnly) {
if (!drawLineOnly) {
if (linePaint != null) {
if (domainAxisBottom){
canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom
+ domainLabelTickExtension, linePaint);
} else {
canvas.drawLine(xPix, gridRect.top - domainLabelTickExtension, xPix,
gridRect.bottom , 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) {
canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom,
linePaint);
}
}
public void drawRangeTick(Canvas canvas, float yPix, Number yVal,
Paint labelPaint, Paint linePaint, boolean drawLineOnly) {
if (!drawLineOnly) {
if (linePaint != null) {
if (rangeAxisLeft){
canvas.drawLine(gridRect.left - rangeLabelTickExtension, yPix,
gridRect.right, yPix, linePaint);
} else {
canvas.drawLine(gridRect.left, 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) {
canvas.drawLine(gridRect.left, yPix, gridRect.right, 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);
}
drawDomainTick(canvas, domainOriginF, plot.getDomainOrigin()
.doubleValue(), domainOriginLabelPaint,
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();
if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) {
if (i % getTicksPerDomainLabel() == 0) {
drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
domainGridLinePaint, false);
} else {
drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
domainSubGridLinePaint, true);
}
}
i++;
}
}
// draw ticks RIGHT of origin:
{
int i = 1;
double xVal;
float xPix = domainOriginF + domainStep.getStepPix();
for (; xPix <= paddedGridRect.right; xPix = domainOriginF
+ (i * domainStep.getStepPix())) {
xVal = plot.getDomainOrigin().doubleValue() + i
* domainStep.getStepVal();
if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) {
if (i % getTicksPerDomainLabel() == 0) {
drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
domainGridLinePaint, false);
} else {
drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
domainSubGridLinePaint, true);
}
}
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);
}
drawRangeTick(canvas, rangeOriginF, plot.getRangeOrigin()
.doubleValue(), rangeOriginLabelPaint,
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();
if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) {
if (i % getTicksPerRangeLabel() == 0) {
drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
rangeGridLinePaint, false);
} else {
drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
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();
if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) {
if (i % getTicksPerRangeLabel() == 0) {
drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
rangeGridLinePaint, false);
} else {
drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
rangeSubGridLinePaint, true);
}
}
i++;
}
}
}
/**
* 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.clipRect(gridRect);
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 getRangeLabelTickExtension() {
return rangeLabelTickExtension;
}
public void setRangeLabelTickExtension(int rangeLabelTickExtension) {
this.rangeLabelTickExtension = rangeLabelTickExtension;
}
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;
}
/*
* 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());
}
}
}
}