/**
* GraphView
* Copyright 2016 Jonas Gehring
*
* 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.jjoe64.graphview;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.TypedValue;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* The default renderer for the grid
* and the labels.
*
* @author jjoe64
*/
public class GridLabelRenderer {
/**
* Hoziontal label alignment
*/
public enum VerticalLabelsVAlign {
/**
* Above vertical line
*/
ABOVE,
/**
* Mid vertical line
*/
MID,
/**
* Below vertical line
*/
BELOW
}
/**
* wrapper for the styles regarding
* to the grid and the labels
*/
public final class Styles {
/**
* the general text size of the axis titles.
* can be overwritten with #verticalAxisTitleTextSize
* and #horizontalAxisTitleTextSize
*/
public float textSize;
/**
* the alignment of the vertical labels
*/
public Paint.Align verticalLabelsAlign;
/**
* the alignment of the labels on the right side
*/
public Paint.Align verticalLabelsSecondScaleAlign;
/**
* the color of the vertical labels
*/
public int verticalLabelsColor;
/**
* the color of the labels on the right side
*/
public int verticalLabelsSecondScaleColor;
/**
* the color of the horizontal labels
*/
public int horizontalLabelsColor;
/**
* the color of the grid lines
*/
public int gridColor;
/**
* flag whether the zero-lines (vertical+
* horizontal) shall be highlighted
*/
public boolean highlightZeroLines;
/**
* the padding around the graph and labels
*/
public int padding;
/**
* font size of the vertical axis title
*/
public float verticalAxisTitleTextSize;
/**
* font color of the vertical axis title
*/
public int verticalAxisTitleColor;
/**
* font size of the horizontal axis title
*/
public float horizontalAxisTitleTextSize;
/**
* font color of the horizontal axis title
*/
public int horizontalAxisTitleColor;
/**
* angle of the horizontal axis label in
* degrees between 0 and 180
*/
public float horizontalLabelsAngle;
/**
* flag whether the horizontal labels are
* visible
*/
boolean horizontalLabelsVisible;
/**
* flag whether the vertical labels are
* visible
*/
boolean verticalLabelsVisible;
/**
* defines which lines will be drawn in the background
*/
GridStyle gridStyle;
/**
* the space between the labels text and the graph content
*/
int labelsSpace;
/**
* vertical labels vertical align (above, below, mid of the grid line)
*/
VerticalLabelsVAlign verticalLabelsVAlign = VerticalLabelsVAlign.MID;
}
/**
* Definition which lines will be drawn in the background
*/
public enum GridStyle {
/**
* show vertical and horizonal lines
* this is the default
*/
BOTH,
/**
* show only vertical lines
*/
VERTICAL,
/**
* show only horizontal lines
*/
HORIZONTAL,
/**
* dont draw any lines
*/
NONE;
public boolean drawVertical() { return this == BOTH || this == VERTICAL && this != NONE; }
public boolean drawHorizontal() { return this == BOTH || this == HORIZONTAL && this != NONE; }
}
/**
* wraps the styles regarding the
* grid and labels
*/
protected Styles mStyles;
/**
* reference to graphview
*/
private final GraphView mGraphView;
/**
* cache of the vertical steps
* (horizontal lines and vertical labels)
* Key = Pixel (y)
* Value = y-value
*/
private Map<Integer, Double> mStepsVertical;
/**
* cache of the vertical steps for the
* second scale, which is on the right side
* (horizontal lines and vertical labels)
* Key = Pixel (y)
* Value = y-value
*/
private Map<Integer, Double> mStepsVerticalSecondScale;
/**
* cache of the horizontal steps
* (vertical lines and horizontal labels)
* Value = x-value
*/
private Map<Integer, Double> mStepsHorizontal;
/**
* the paint to draw the grid lines
*/
private Paint mPaintLine;
/**
* the paint to draw the labels
*/
private Paint mPaintLabel;
/**
* the paint to draw axis titles
*/
private Paint mPaintAxisTitle;
/**
* flag whether is bounds are automatically
* adjusted for nice human-readable numbers
*/
protected boolean mIsAdjusted;
/**
* the width of the vertical labels
*/
private Integer mLabelVerticalWidth;
/**
* indicates if the width was set manually
*/
private boolean mLabelVerticalWidthFixed;
/**
* the height of the vertical labels
*/
private Integer mLabelVerticalHeight;
/**
* indicates if the height was set manually
*/
private boolean mLabelHorizontalHeightFixed;
/**
* the width of the vertical labels
* of the second scale
*/
private Integer mLabelVerticalSecondScaleWidth;
/**
* the height of the vertical labels
* of the second scale
*/
private Integer mLabelVerticalSecondScaleHeight;
/**
* the width of the horizontal labels
*/
private Integer mLabelHorizontalWidth;
/**
* the height of the horizontal labels
*/
private Integer mLabelHorizontalHeight;
/**
* the label formatter, that converts
* the raw numbers to strings
*/
private LabelFormatter mLabelFormatter;
/**
* the title of the horizontal axis
*/
private String mHorizontalAxisTitle;
/**
* the title of the vertical axis
*/
private String mVerticalAxisTitle;
/**
* count of the vertical labels, that
* will be shown at one time.
*/
private int mNumVerticalLabels;
/**
* count of the horizontal labels, that
* will be shown at one time.
*/
private int mNumHorizontalLabels;
/**
* sets the space for the vertical labels on the right side
*
* @param newWidth set fixed width. set null to calculate it automatically
*/
public void setSecondScaleLabelVerticalWidth(Integer newWidth) {
mLabelVerticalSecondScaleWidth = newWidth;
}
/**
* activate or deactivate human rounding of the
* horizontal axis. GraphView tries to fit the labels
* to display numbers that can be divided by 1, 2, or 5.
*
* By default this is enabled. It makes sense to deactivate it
* when using Dates on the x axis.
*/
private boolean mHumanRounding;
/**
* create the default grid label renderer.
*
* @param graphView the corresponding graphview object
*/
public GridLabelRenderer(GraphView graphView) {
mGraphView = graphView;
setLabelFormatter(new DefaultLabelFormatter());
mStyles = new Styles();
resetStyles();
mNumVerticalLabels = 5;
mNumHorizontalLabels = 5;
mHumanRounding = true;
}
/**
* resets the styles. This loads the style
* from reading the values of the current
* theme.
*/
public void resetStyles() {
// get matching styles from theme
TypedValue typedValue = new TypedValue();
mGraphView.getContext().getTheme().resolveAttribute(android.R.attr.textAppearanceSmall, typedValue, true);
int color1;
int color2;
int size;
int size2;
TypedArray array = null;
try {
array = mGraphView.getContext().obtainStyledAttributes(typedValue.data, new int[]{
android.R.attr.textColorPrimary
, android.R.attr.textColorSecondary
, android.R.attr.textSize
, android.R.attr.horizontalGap});
color1 = array.getColor(0, Color.BLACK);
color2 = array.getColor(1, Color.GRAY);
size = array.getDimensionPixelSize(2, 20);
size2 = array.getDimensionPixelSize(3, 20);
array.recycle();
} catch (Exception e) {
color1 = Color.BLACK;
color2 = Color.GRAY;
size = 20;
size2 = 20;
}
mStyles.verticalLabelsColor = color1;
mStyles.verticalLabelsSecondScaleColor = color1;
mStyles.horizontalLabelsColor = color1;
mStyles.gridColor = color2;
mStyles.textSize = size;
mStyles.padding = size2;
mStyles.labelsSpace = (int) mStyles.textSize/5;
mStyles.verticalLabelsAlign = Paint.Align.RIGHT;
mStyles.verticalLabelsSecondScaleAlign = Paint.Align.LEFT;
mStyles.highlightZeroLines = true;
mStyles.verticalAxisTitleColor = mStyles.verticalLabelsColor;
mStyles.horizontalAxisTitleColor = mStyles.horizontalLabelsColor;
mStyles.verticalAxisTitleTextSize = mStyles.textSize;
mStyles.horizontalAxisTitleTextSize = mStyles.textSize;
mStyles.horizontalLabelsVisible = true;
mStyles.verticalLabelsVisible = true;
mStyles.horizontalLabelsAngle = 0f;
mStyles.gridStyle = GridStyle.BOTH;
reloadStyles();
}
/**
* will load the styles to the internal
* paint objects (color, text size, text align)
*/
public void reloadStyles() {
mPaintLine = new Paint();
mPaintLine.setColor(mStyles.gridColor);
mPaintLine.setStrokeWidth(0);
mPaintLabel = new Paint();
mPaintLabel.setTextSize(getTextSize());
mPaintLabel.setAntiAlias(true);
mPaintAxisTitle = new Paint();
mPaintAxisTitle.setTextSize(getTextSize());
mPaintAxisTitle.setTextAlign(Paint.Align.CENTER);
}
/**
* GraphView tries to fit the labels
* to display numbers that can be divided by 1, 2, or 5.
*
* By default this is enabled. It makes sense to deactivate it
* when using Dates on the x axis.
* @return if human rounding is enabled
*/
public boolean isHumanRounding() {
return mHumanRounding;
}
/**
* activate or deactivate human rounding of the
* horizontal axis. GraphView tries to fit the labels
* to display numbers that can be divided by 1, 2, or 5.
*
* By default this is enabled. It makes sense to deactivate it
* when using Dates on the x axis.
*
* @param humanRounding false to deactivate
*/
public void setHumanRounding(boolean humanRounding) {
this.mHumanRounding = humanRounding;
}
/**
* @return the general text size for the axis titles
*/
public float getTextSize() {
return mStyles.textSize;
}
/**
* @return the font color of the vertical labels
*/
public int getVerticalLabelsColor() {
return mStyles.verticalLabelsColor;
}
/**
* @return the alignment of the text of the
* vertical labels
*/
public Paint.Align getVerticalLabelsAlign() {
return mStyles.verticalLabelsAlign;
}
/**
* @return the font color of the horizontal labels
*/
public int getHorizontalLabelsColor() {
return mStyles.horizontalLabelsColor;
}
/**
* @return the angle of the horizontal labels
*/
public float getHorizontalLabelsAngle() {
return mStyles.horizontalLabelsAngle;
}
/**
* clears the internal cache and forces
* to redraw the grid and labels.
* Normally you should always call {@link GraphView#onDataChanged(boolean, boolean)}
* which will call this method.
*
* @param keepLabelsSize true if you don't want
* to recalculate the size of
* the labels. It is recommended
* to use "true" because this will
* improve performance and prevent
* a flickering.
* @param keepViewport true if you don't want that
* the viewport will be recalculated.
* It is recommended to use "true" for
* performance.
*/
public void invalidate(boolean keepLabelsSize, boolean keepViewport) {
if (!keepViewport) {
mIsAdjusted = false;
}
if (!keepLabelsSize) {
if (!mLabelVerticalWidthFixed) {
mLabelVerticalWidth = null;
}
mLabelVerticalHeight = null;
mLabelVerticalSecondScaleWidth = null;
mLabelVerticalSecondScaleHeight = null;
}
//reloadStyles();
}
/**
* calculates the vertical steps of
* the second scale.
* This will not do any automatically update
* of the bounds.
* Use always manual bounds for the second scale.
*
* @return true if it is ready
*/
protected boolean adjustVerticalSecondScale() {
if (mLabelHorizontalHeight == null) {
return false;
}
if (mGraphView.mSecondScale == null) {
return true;
}
double minY = mGraphView.mSecondScale.getMinY(false);
double maxY = mGraphView.mSecondScale.getMaxY(false);
// TODO find the number of labels
int numVerticalLabels = mNumVerticalLabels;
double newMinY;
double exactSteps;
if (mGraphView.mSecondScale.isYAxisBoundsManual()) {
// split range into equal steps
exactSteps = (maxY - minY) / (numVerticalLabels - 1);
// round because of floating error
exactSteps = Math.round(exactSteps * 1000000d) / 1000000d;
} else {
// TODO auto adjusting
throw new IllegalStateException("Not yet implemented");
}
if (mStepsVerticalSecondScale != null && mStepsVerticalSecondScale.size() > 1) {
// else choose other nice steps that previous
// steps are included (divide to have more, or multiplicate to have less)
double d1 = 0, d2 = 0;
int i = 0;
for (Double v : mStepsVerticalSecondScale.values()) {
if (i == 0) {
d1 = v;
} else {
d2 = v;
break;
}
i++;
}
double oldSteps = d2 - d1;
if (oldSteps > 0) {
double newSteps = Double.NaN;
if (oldSteps > exactSteps) {
newSteps = oldSteps / 2;
} else if (oldSteps < exactSteps) {
newSteps = oldSteps * 2;
}
// only if there wont be more than numLabels
// and newSteps will be better than oldSteps
int numStepsOld = (int) ((maxY - minY) / oldSteps);
int numStepsNew = (int) ((maxY - minY) / newSteps);
boolean shouldChange;
// avoid switching between 2 steps
if (numStepsOld <= numVerticalLabels && numStepsNew <= numVerticalLabels) {
// both are possible
// only the new if it hows more labels
shouldChange = numStepsNew > numStepsOld;
} else {
shouldChange = true;
}
if (newSteps != Double.NaN && shouldChange && numStepsNew <= numVerticalLabels) {
exactSteps = newSteps;
} else {
// try to stay to the old steps
exactSteps = oldSteps;
}
}
} else {
// first time
}
// find the first data point that is relevant to display
// starting from 1st datapoint so that the steps have nice numbers
// goal is to start with the minY or 1 step before
newMinY = mGraphView.getSecondScale().mReferenceY;
// must be down-rounded
double count = Math.floor((minY-newMinY)/exactSteps);
newMinY = count*exactSteps + newMinY;
// it can happen that we need to add some more labels to fill the complete screen
numVerticalLabels = (int) ((mGraphView.getSecondScale().mCurrentViewport.height()*-1 / exactSteps)) + 2;
if (mStepsVerticalSecondScale != null) {
mStepsVerticalSecondScale.clear();
} else {
mStepsVerticalSecondScale = new LinkedHashMap<>((int) numVerticalLabels);
}
int height = mGraphView.getGraphContentHeight();
// convert data-y to pixel-y in current viewport
double pixelPerData = height / mGraphView.getSecondScale().mCurrentViewport.height()*-1;
for (int i = 0; i < numVerticalLabels; i++) {
// dont draw if it is top of visible screen
if (newMinY + (i * exactSteps) > mGraphView.getSecondScale().mCurrentViewport.top) {
continue;
}
// dont draw if it is below of visible screen
if (newMinY + (i * exactSteps) < mGraphView.getSecondScale().mCurrentViewport.bottom) {
continue;
}
// where is the data point on the current screen
double dataPointPos = newMinY + (i * exactSteps);
double relativeToCurrentViewport = dataPointPos - mGraphView.getSecondScale().mCurrentViewport.bottom;
double pixelPos = relativeToCurrentViewport * pixelPerData;
mStepsVerticalSecondScale.put((int) pixelPos, dataPointPos);
}
return true;
}
/**
* calculates the vertical steps. This will
* automatically change the bounds to nice
* human-readable min/max.
*
* @return true if it is ready
*/
protected boolean adjustVertical(boolean changeBounds) {
if (mLabelHorizontalHeight == null) {
return false;
}
double minY = mGraphView.getViewport().getMinY(false);
double maxY = mGraphView.getViewport().getMaxY(false);
if (minY == maxY) {
return false;
}
// TODO find the number of labels
int numVerticalLabels = mNumVerticalLabels;
double newMinY;
double exactSteps;
// split range into equal steps
exactSteps = (maxY - minY) / (numVerticalLabels - 1);
// round because of floating error
exactSteps = Math.round(exactSteps * 1000000d) / 1000000d;
// smallest viewport
if (exactSteps == 0d) {
exactSteps = 0.0000001d;
maxY = minY + exactSteps * (numVerticalLabels - 1);
}
// human rounding to have nice numbers (1, 2, 5, ...)
if (isHumanRounding()) {
exactSteps = humanRound(exactSteps, changeBounds);
} else if (mStepsVertical != null && mStepsVertical.size() > 1) {
// else choose other nice steps that previous
// steps are included (divide to have more, or multiplicate to have less)
double d1 = 0, d2 = 0;
int i = 0;
for (Double v : mStepsVertical.values()) {
if (i == 0) {
d1 = v;
} else {
d2 = v;
break;
}
i++;
}
double oldSteps = d2 - d1;
if (oldSteps > 0) {
double newSteps = Double.NaN;
if (oldSteps > exactSteps) {
newSteps = oldSteps / 2;
} else if (oldSteps < exactSteps) {
newSteps = oldSteps * 2;
}
// only if there wont be more than numLabels
// and newSteps will be better than oldSteps
int numStepsOld = (int) ((maxY - minY) / oldSteps);
int numStepsNew = (int) ((maxY - minY) / newSteps);
boolean shouldChange;
// avoid switching between 2 steps
if (numStepsOld <= numVerticalLabels && numStepsNew <= numVerticalLabels) {
// both are possible
// only the new if it hows more labels
shouldChange = numStepsNew > numStepsOld;
} else {
shouldChange = true;
}
if (newSteps != Double.NaN && shouldChange && numStepsNew <= numVerticalLabels) {
exactSteps = newSteps;
} else {
// try to stay to the old steps
exactSteps = oldSteps;
}
}
} else {
// first time
}
// find the first data point that is relevant to display
// starting from 1st datapoint so that the steps have nice numbers
// goal is to start with the minX or 1 step before
newMinY = mGraphView.getViewport().getReferenceY();
// must be down-rounded
double count = Math.floor((minY-newMinY)/exactSteps);
newMinY = count*exactSteps + newMinY;
// now we have our labels bounds
if (changeBounds) {
mGraphView.getViewport().setMinY(newMinY);
mGraphView.getViewport().setMaxY(Math.max(maxY, newMinY + (numVerticalLabels - 1) * exactSteps));
mGraphView.getViewport().mYAxisBoundsStatus = Viewport.AxisBoundsStatus.AUTO_ADJUSTED;
}
// it can happen that we need to add some more labels to fill the complete screen
numVerticalLabels = (int) ((mGraphView.getViewport().mCurrentViewport.height()*-1 / exactSteps)) + 2;
if (mStepsVertical != null) {
mStepsVertical.clear();
} else {
mStepsVertical = new LinkedHashMap<>((int) numVerticalLabels);
}
int height = mGraphView.getGraphContentHeight();
// convert data-y to pixel-y in current viewport
double pixelPerData = height / mGraphView.getViewport().mCurrentViewport.height()*-1;
for (int i = 0; i < numVerticalLabels; i++) {
// dont draw if it is top of visible screen
if (newMinY + (i * exactSteps) > mGraphView.getViewport().mCurrentViewport.top) {
continue;
}
// dont draw if it is below of visible screen
if (newMinY + (i * exactSteps) < mGraphView.getViewport().mCurrentViewport.bottom) {
continue;
}
// where is the data point on the current screen
double dataPointPos = newMinY + (i * exactSteps);
double relativeToCurrentViewport = dataPointPos - mGraphView.getViewport().mCurrentViewport.bottom;
double pixelPos = relativeToCurrentViewport * pixelPerData;
mStepsVertical.put((int) pixelPos, dataPointPos);
}
return true;
}
/**
* calculates the horizontal steps.
*
* @param changeBounds This will automatically change the
* bounds to nice human-readable min/max.
* @return true if it is ready
*/
protected boolean adjustHorizontal(boolean changeBounds) {
if (mLabelVerticalWidth == null) {
return false;
}
double minX = mGraphView.getViewport().getMinX(false);
double maxX = mGraphView.getViewport().getMaxX(false);
if (minX == maxX) return false;
// TODO find the number of labels
int numHorizontalLabels = mNumHorizontalLabels;
double newMinX;
double exactSteps;
// split range into equal steps
exactSteps = (maxX - minX) / (numHorizontalLabels - 1);
// round because of floating error
exactSteps = Math.round(exactSteps * 1000000d) / 1000000d;
// smallest viewport
if (exactSteps == 0d) {
exactSteps = 0.0000001d;
maxX = minX + exactSteps * (numHorizontalLabels - 1);
}
// human rounding to have nice numbers (1, 2, 5, ...)
if (isHumanRounding()) {
exactSteps = humanRound(exactSteps, false);
} else if (mStepsHorizontal != null && mStepsHorizontal.size() > 1) {
// else choose other nice steps that previous
// steps are included (divide to have more, or multiplicate to have less)
double d1 = 0, d2 = 0;
int i = 0;
for (Double v : mStepsHorizontal.values()) {
if (i == 0) {
d1 = v;
} else {
d2 = v;
break;
}
i++;
}
double oldSteps = d2 - d1;
if (oldSteps > 0) {
double newSteps = Double.NaN;
if (oldSteps > exactSteps) {
newSteps = oldSteps / 2;
} else if (oldSteps < exactSteps) {
newSteps = oldSteps * 2;
}
// only if there wont be more than numLabels
// and newSteps will be better than oldSteps
int numStepsOld = (int) ((maxX - minX) / oldSteps);
int numStepsNew = (int) ((maxX - minX) / newSteps);
boolean shouldChange;
// avoid switching between 2 steps
if (numStepsOld <= numHorizontalLabels && numStepsNew <= numHorizontalLabels) {
// both are possible
// only the new if it hows more labels
shouldChange = numStepsNew > numStepsOld;
} else {
shouldChange = true;
}
if (newSteps != Double.NaN && shouldChange && numStepsNew <= numHorizontalLabels) {
exactSteps = newSteps;
} else {
// try to stay to the old steps
exactSteps = oldSteps;
}
}
} else {
// first time
}
// starting from 1st datapoint
// goal is to start with the minX or 1 step before
newMinX = mGraphView.getViewport().getReferenceX();
// must be down-rounded
double count = Math.floor((minX-newMinX)/exactSteps);
newMinX = count*exactSteps + newMinX;
// now we have our labels bounds
if (changeBounds) {
mGraphView.getViewport().setMinX(newMinX);
mGraphView.getViewport().setMaxX(newMinX + (numHorizontalLabels - 1) * exactSteps);
mGraphView.getViewport().mXAxisBoundsStatus = Viewport.AxisBoundsStatus.AUTO_ADJUSTED;
}
// it can happen that we need to add some more labels to fill the complete screen
numHorizontalLabels = (int) ((mGraphView.getViewport().mCurrentViewport.width() / exactSteps)) + 1;
if (mStepsHorizontal != null) {
mStepsHorizontal.clear();
} else {
mStepsHorizontal = new LinkedHashMap<>((int) numHorizontalLabels);
}
int width = mGraphView.getGraphContentWidth();
// convert data-x to pixel-x in current viewport
double pixelPerData = width / mGraphView.getViewport().mCurrentViewport.width();
for (int i = 0; i < numHorizontalLabels; i++) {
// dont draw if it is left of visible screen
if (newMinX + (i * exactSteps) < mGraphView.getViewport().mCurrentViewport.left) {
continue;
}
// where is the data point on the current screen
double dataPointPos = newMinX + (i * exactSteps);
double relativeToCurrentViewport = dataPointPos - mGraphView.getViewport().mCurrentViewport.left;
double pixelPos = relativeToCurrentViewport * pixelPerData;
mStepsHorizontal.put((int) pixelPos, dataPointPos);
}
return true;
}
/**
* adjusts the grid and labels to match to the data
* this will automatically change the bounds to
* nice human-readable values, except the bounds
* are manual.
*/
protected void adjustSteps() {
mIsAdjusted = adjustVertical(! Viewport.AxisBoundsStatus.FIX.equals(mGraphView.getViewport().mYAxisBoundsStatus));
mIsAdjusted &= adjustVerticalSecondScale();
mIsAdjusted &= adjustHorizontal(! Viewport.AxisBoundsStatus.FIX.equals(mGraphView.getViewport().mXAxisBoundsStatus));
}
/**
* calculates the vertical label size
* @param canvas canvas
*/
protected void calcLabelVerticalSize(Canvas canvas) {
// test label with first and last label
String testLabel = mLabelFormatter.formatLabel(mGraphView.getViewport().getMaxY(false), false);
if (testLabel == null) testLabel = "";
Rect textBounds = new Rect();
mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds);
mLabelVerticalWidth = textBounds.width();
mLabelVerticalHeight = textBounds.height();
testLabel = mLabelFormatter.formatLabel(mGraphView.getViewport().getMinY(false), false);
if (testLabel == null) testLabel = "";
mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds);
mLabelVerticalWidth = Math.max(mLabelVerticalWidth, textBounds.width());
// add some pixel to get a margin
mLabelVerticalWidth += 6;
// space between text and graph content
mLabelVerticalWidth += mStyles.labelsSpace;
// multiline
int lines = 1;
for (byte c : testLabel.getBytes()) {
if (c == '\n') lines++;
}
mLabelVerticalHeight *= lines;
}
/**
* calculates the vertical second scale
* label size
* @param canvas canvas
*/
protected void calcLabelVerticalSecondScaleSize(Canvas canvas) {
if (mGraphView.mSecondScale == null) {
mLabelVerticalSecondScaleWidth = 0;
mLabelVerticalSecondScaleHeight = 0;
return;
}
// test label
double testY = ((mGraphView.mSecondScale.getMaxY(false) - mGraphView.mSecondScale.getMinY(false)) * 0.783) + mGraphView.mSecondScale.getMinY(false);
String testLabel = mGraphView.mSecondScale.getLabelFormatter().formatLabel(testY, false);
Rect textBounds = new Rect();
mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds);
mLabelVerticalSecondScaleWidth = textBounds.width();
mLabelVerticalSecondScaleHeight = textBounds.height();
// multiline
int lines = 1;
for (byte c : testLabel.getBytes()) {
if (c == '\n') lines++;
}
mLabelVerticalSecondScaleHeight *= lines;
}
/**
* calculates the horizontal label size
* @param canvas canvas
*/
protected void calcLabelHorizontalSize(Canvas canvas) {
// test label
double testX = ((mGraphView.getViewport().getMaxX(false) - mGraphView.getViewport().getMinX(false)) * 0.783) + mGraphView.getViewport().getMinX(false);
String testLabel = mLabelFormatter.formatLabel(testX, true);
if (testLabel == null) {
testLabel = "";
}
Rect textBounds = new Rect();
mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds);
mLabelHorizontalWidth = textBounds.width();
if (!mLabelHorizontalHeightFixed) {
mLabelHorizontalHeight = textBounds.height();
// multiline
int lines = 1;
for (byte c : testLabel.getBytes()) {
if (c == '\n') lines++;
}
mLabelHorizontalHeight *= lines;
mLabelHorizontalHeight = (int) Math.max(mLabelHorizontalHeight, mStyles.textSize);
}
if (mStyles.horizontalLabelsAngle > 0f && mStyles.horizontalLabelsAngle <= 180f) {
int adjHorizontalHeightH = (int) Math.round(Math.abs(mLabelHorizontalHeight*Math.cos(Math.toRadians(mStyles.horizontalLabelsAngle))));
int adjHorizontalHeightW = (int) Math.round(Math.abs(mLabelHorizontalWidth*Math.sin(Math.toRadians(mStyles.horizontalLabelsAngle))));
int adjHorizontalWidthH = (int) Math.round(Math.abs(mLabelHorizontalHeight*Math.sin(Math.toRadians(mStyles.horizontalLabelsAngle))));
int adjHorizontalWidthW = (int) Math.round(Math.abs(mLabelHorizontalWidth*Math.cos(Math.toRadians(mStyles.horizontalLabelsAngle))));
mLabelHorizontalHeight = adjHorizontalHeightH + adjHorizontalHeightW;
mLabelHorizontalWidth = adjHorizontalWidthH + adjHorizontalWidthW;
}
// space between text and graph content
mLabelHorizontalHeight += mStyles.labelsSpace;
}
/**
* do the drawing of the grid
* and labels
* @param canvas canvas
*/
public void draw(Canvas canvas) {
boolean labelSizeChanged = false;
if (mLabelHorizontalWidth == null) {
calcLabelHorizontalSize(canvas);
labelSizeChanged = true;
}
if (mLabelVerticalWidth == null) {
calcLabelVerticalSize(canvas);
labelSizeChanged = true;
}
if (mLabelVerticalSecondScaleWidth == null) {
calcLabelVerticalSecondScaleSize(canvas);
labelSizeChanged = true;
}
if (labelSizeChanged) {
// redraw directly
mGraphView.drawGraphElements(canvas);
return;
}
if (!mIsAdjusted) {
adjustSteps();
}
if (mIsAdjusted) {
drawVerticalSteps(canvas);
drawVerticalStepsSecondScale(canvas);
drawHorizontalSteps(canvas);
} else {
// we can not draw anything
return;
}
drawHorizontalAxisTitle(canvas);
drawVerticalAxisTitle(canvas);
// draw second scale axis title if it exists
if (mGraphView.mSecondScale != null) {
mGraphView.mSecondScale.drawVerticalAxisTitle(canvas);
}
}
/**
* draws the horizontal axis title if
* it is set
* @param canvas canvas
*/
protected void drawHorizontalAxisTitle(Canvas canvas) {
if (mHorizontalAxisTitle != null && mHorizontalAxisTitle.length() > 0) {
mPaintAxisTitle.setColor(getHorizontalAxisTitleColor());
mPaintAxisTitle.setTextSize(getHorizontalAxisTitleTextSize());
float x = canvas.getWidth() / 2;
float y = canvas.getHeight() - mStyles.padding;
canvas.drawText(mHorizontalAxisTitle, x, y, mPaintAxisTitle);
}
}
/**
* draws the vertical axis title if
* it is set
* @param canvas canvas
*/
protected void drawVerticalAxisTitle(Canvas canvas) {
if (mVerticalAxisTitle != null && mVerticalAxisTitle.length() > 0) {
mPaintAxisTitle.setColor(getVerticalAxisTitleColor());
mPaintAxisTitle.setTextSize(getVerticalAxisTitleTextSize());
float x = getVerticalAxisTitleWidth();
float y = canvas.getHeight() / 2;
canvas.save();
canvas.rotate(-90, x, y);
canvas.drawText(mVerticalAxisTitle, x, y, mPaintAxisTitle);
canvas.restore();
}
}
/**
* @return the horizontal axis title height
* or 0 if there is no title
*/
public int getHorizontalAxisTitleHeight() {
if (mHorizontalAxisTitle != null && mHorizontalAxisTitle.length() > 0) {
return (int) getHorizontalAxisTitleTextSize();
} else {
return 0;
}
}
/**
* @return the vertical axis title width
* or 0 if there is no title
*/
public int getVerticalAxisTitleWidth() {
if (mVerticalAxisTitle != null && mVerticalAxisTitle.length() > 0) {
return (int) getVerticalAxisTitleTextSize();
} else {
return 0;
}
}
/**
* draws the horizontal steps
* vertical lines and horizontal labels
*
* @param canvas canvas
*/
protected void drawHorizontalSteps(Canvas canvas) {
// draw horizontal steps (vertical lines and horizontal labels)
mPaintLabel.setColor(getHorizontalLabelsColor());
int i = 0;
for (Map.Entry<Integer, Double> e : mStepsHorizontal.entrySet()) {
// draw line
if (mStyles.highlightZeroLines) {
if (e.getValue() == 0d) {
mPaintLine.setStrokeWidth(5);
} else {
mPaintLine.setStrokeWidth(0);
}
}
if (mStyles.gridStyle.drawVertical()) {
// dont draw if it is right of visible screen
if (e.getKey() <= mGraphView.getGraphContentWidth()) {
canvas.drawLine(mGraphView.getGraphContentLeft()+e.getKey(), mGraphView.getGraphContentTop(), mGraphView.getGraphContentLeft()+e.getKey(), mGraphView.getGraphContentTop() + mGraphView.getGraphContentHeight(), mPaintLine);
}
}
// draw label
if (isHorizontalLabelsVisible()) {
if (mStyles.horizontalLabelsAngle > 0f && mStyles.horizontalLabelsAngle <= 180f) {
if (mStyles.horizontalLabelsAngle < 90f) {
mPaintLabel.setTextAlign((Paint.Align.RIGHT));
} else if (mStyles.horizontalLabelsAngle <= 180f) {
mPaintLabel.setTextAlign((Paint.Align.LEFT));
}
} else {
mPaintLabel.setTextAlign(Paint.Align.CENTER);
if (i == mStepsHorizontal.size() - 1)
mPaintLabel.setTextAlign(Paint.Align.RIGHT);
if (i == 0)
mPaintLabel.setTextAlign(Paint.Align.LEFT);
}
// multiline labels
String label = mLabelFormatter.formatLabel(e.getValue(), true);
if (label == null) {
label = "";
}
String[] lines = label.split("\n");
// If labels are angled, calculate adjustment to line them up with the grid
int labelWidthAdj = 0;
if (mStyles.horizontalLabelsAngle > 0f && mStyles.horizontalLabelsAngle <= 180f) {
Rect textBounds = new Rect();
mPaintLabel.getTextBounds(lines[0], 0, lines[0].length(), textBounds);
labelWidthAdj = (int) Math.abs(textBounds.width()*Math.cos(Math.toRadians(mStyles.horizontalLabelsAngle)));
}
for (int li = 0; li < lines.length; li++) {
// for the last line y = height
float y = (canvas.getHeight() - mStyles.padding - getHorizontalAxisTitleHeight()) - (lines.length - li - 1) * getTextSize() * 1.1f + mStyles.labelsSpace;
float x = mGraphView.getGraphContentLeft()+e.getKey();
if (mStyles.horizontalLabelsAngle > 0 && mStyles.horizontalLabelsAngle < 90f) {
canvas.save();
canvas.rotate(mStyles.horizontalLabelsAngle, x + labelWidthAdj, y);
canvas.drawText(lines[li], x + labelWidthAdj, y, mPaintLabel);
canvas.restore();
} else if (mStyles.horizontalLabelsAngle > 0 && mStyles.horizontalLabelsAngle <= 180f) {
canvas.save();
canvas.rotate(mStyles.horizontalLabelsAngle - 180f, x - labelWidthAdj, y);
canvas.drawText(lines[li], x - labelWidthAdj, y, mPaintLabel);
canvas.restore();
} else {
canvas.drawText(lines[li], x, y, mPaintLabel);
}
}
}
i++;
}
}
/**
* draws the vertical steps for the
* second scale on the right side
*
* @param canvas canvas
*/
protected void drawVerticalStepsSecondScale(Canvas canvas) {
if (mGraphView.mSecondScale == null) {
return;
}
// draw only the vertical labels on the right
float startLeft = mGraphView.getGraphContentLeft() + mGraphView.getGraphContentWidth();
mPaintLabel.setColor(getVerticalLabelsSecondScaleColor());
mPaintLabel.setTextAlign(getVerticalLabelsSecondScaleAlign());
for (Map.Entry<Integer, Double> e : mStepsVerticalSecondScale.entrySet()) {
float posY = mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight()-e.getKey();
// draw label
int labelsWidth = mLabelVerticalSecondScaleWidth;
int labelsOffset = (int) startLeft;
if (getVerticalLabelsSecondScaleAlign() == Paint.Align.RIGHT) {
labelsOffset += labelsWidth;
} else if (getVerticalLabelsSecondScaleAlign() == Paint.Align.CENTER) {
labelsOffset += labelsWidth / 2;
}
float y = posY;
String[] lines = mGraphView.mSecondScale.mLabelFormatter.formatLabel(e.getValue(), false).split("\n");
y += (lines.length * getTextSize() * 1.1f) / 2; // center text vertically
for (int li = 0; li < lines.length; li++) {
// for the last line y = height
float y2 = y - (lines.length - li - 1) * getTextSize() * 1.1f;
canvas.drawText(lines[li], labelsOffset, y2, mPaintLabel);
}
}
}
/**
* draws the vertical steps
* horizontal lines and vertical labels
*
* @param canvas canvas
*/
protected void drawVerticalSteps(Canvas canvas) {
// draw vertical steps (horizontal lines and vertical labels)
float startLeft = mGraphView.getGraphContentLeft();
mPaintLabel.setColor(getVerticalLabelsColor());
mPaintLabel.setTextAlign(getVerticalLabelsAlign());
int numberOfLine = mStepsVertical.size();
int currentLine = 1;
for (Map.Entry<Integer, Double> e : mStepsVertical.entrySet()) {
float posY = mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight()-e.getKey();
// draw line
if (mStyles.highlightZeroLines) {
if (e.getValue() == 0d) {
mPaintLine.setStrokeWidth(5);
} else {
mPaintLine.setStrokeWidth(0);
}
}
if (mStyles.gridStyle.drawHorizontal()) {
canvas.drawLine(startLeft, posY, startLeft + mGraphView.getGraphContentWidth(), posY, mPaintLine);
}
//if draw the label above or below the line, we mustn't draw the first for last label, for beautiful design.
boolean isDrawLabel = true;
if ((mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.ABOVE && currentLine == 1)
|| (mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.BELOW && currentLine == numberOfLine)){
isDrawLabel = false;
}
// draw label
if (isVerticalLabelsVisible() && isDrawLabel) {
int labelsWidth = mLabelVerticalWidth;
int labelsOffset = 0;
if (getVerticalLabelsAlign() == Paint.Align.RIGHT) {
labelsOffset = labelsWidth;
labelsOffset -= mStyles.labelsSpace;
} else if (getVerticalLabelsAlign() == Paint.Align.CENTER) {
labelsOffset = labelsWidth / 2;
}
labelsOffset += mStyles.padding + getVerticalAxisTitleWidth();
float y = posY;
String label = mLabelFormatter.formatLabel(e.getValue(), false);
if (label == null) {
label = "";
}
String[] lines = label.split("\n");
switch (mStyles.verticalLabelsVAlign){
case MID:
y += (lines.length * getTextSize() * 1.1f) / 2; // center text vertically
break;
case ABOVE:
y -= 5;
break;
case BELOW:
y += (lines.length * getTextSize() * 1.1f) + 5;
break;
}
for (int li = 0; li < lines.length; li++) {
// for the last line y = height
float y2 = y - (lines.length - li - 1) * getTextSize() * 1.1f;
canvas.drawText(lines[li], labelsOffset, y2, mPaintLabel);
}
}
currentLine ++;
}
}
/**
* this will do rounding to generate
* nice human-readable bounds.
*
* @param in the raw value that is to be rounded
* @param roundAlwaysUp true if it shall always round up (ceil)
* @return the rounded number
*/
protected double humanRound(double in, boolean roundAlwaysUp) {
// round-up to 1-steps, 2-steps or 5-steps
int ten = 0;
while (Math.abs(in) >= 10d) {
in /= 10d;
ten++;
}
while (Math.abs(in) < 1d) {
in *= 10d;
ten--;
}
if (roundAlwaysUp) {
if (in == 1d) {
} else if (in <= 2d) {
in = 2d;
} else if (in <= 5d) {
in = 5d;
} else if (in < 10d) {
in = 10d;
}
} else { // always round down
if (in == 1d) {
} else if (in <= 4.9d) {
in = 2d;
} else if (in <= 9.9d) {
in = 5d;
} else if (in < 15d) {
in = 10d;
}
}
return in * Math.pow(10d, ten);
}
/**
* @return the wrapped styles
*/
public Styles getStyles() {
return mStyles;
}
/**
* @return the vertical label width
* 0 if there are no vertical labels
*/
public int getLabelVerticalWidth() {
if (mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.ABOVE
|| mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.BELOW) {
return 0;
}
return mLabelVerticalWidth == null || !isVerticalLabelsVisible() ? 0 : mLabelVerticalWidth;
}
/**
* sets a manual and fixed with of the space for
* the vertical labels. This will prevent GraphView to
* calculate the width automatically.
*
* @param width the width of the space for the vertical labels.
* Use null to let GraphView automatically calculate the width.
*/
public void setLabelVerticalWidth(Integer width) {
mLabelVerticalWidth = width;
mLabelVerticalWidthFixed = mLabelVerticalWidth != null;
}
/**
* @return the horizontal label height
* 0 if there are no horizontal labels
*/
public int getLabelHorizontalHeight() {
return mLabelHorizontalHeight == null || !isHorizontalLabelsVisible() ? 0 : mLabelHorizontalHeight;
}
/**
* sets a manual and fixed height of the space for
* the horizontal labels. This will prevent GraphView to
* calculate the height automatically.
*
* @param height the height of the space for the horizontal labels.
* Use null to let GraphView automatically calculate the height.
*/
public void setLabelHorizontalHeight(Integer height) {
mLabelHorizontalHeight = height;
mLabelHorizontalHeightFixed = mLabelHorizontalHeight != null;
}
/**
* @return the grid line color
*/
public int getGridColor() {
return mStyles.gridColor;
}
/**
* @return whether the line at 0 are highlighted
*/
public boolean isHighlightZeroLines() {
return mStyles.highlightZeroLines;
}
/**
* @return the padding around the grid and labels
*/
public int getPadding() {
return mStyles.padding;
}
/**
* @param textSize the general text size of the axis titles.
* can be overwritten with {@link #setVerticalAxisTitleTextSize(float)}
* and {@link #setHorizontalAxisTitleTextSize(float)}
*/
public void setTextSize(float textSize) {
mStyles.textSize = textSize;
reloadStyles();
}
/**
* @param verticalLabelsAlign the alignment of the vertical labels
*/
public void setVerticalLabelsAlign(Paint.Align verticalLabelsAlign) {
mStyles.verticalLabelsAlign = verticalLabelsAlign;
}
/**
* @param verticalLabelsColor the color of the vertical labels
*/
public void setVerticalLabelsColor(int verticalLabelsColor) {
mStyles.verticalLabelsColor = verticalLabelsColor;
}
/**
* @param horizontalLabelsColor the color of the horizontal labels
*/
public void setHorizontalLabelsColor(int horizontalLabelsColor) {
mStyles.horizontalLabelsColor = horizontalLabelsColor;
}
/**
* @param horizontalLabelsAngle the angle of the horizontal labels in degrees
*/
public void setHorizontalLabelsAngle(int horizontalLabelsAngle) {
mStyles.horizontalLabelsAngle = horizontalLabelsAngle;
}
/**
* @param gridColor the color of the grid lines
*/
public void setGridColor(int gridColor) {
mStyles.gridColor = gridColor;
reloadStyles();
}
/**
* @param highlightZeroLines flag whether the zero-lines (vertical+
* horizontal) shall be highlighted
*/
public void setHighlightZeroLines(boolean highlightZeroLines) {
mStyles.highlightZeroLines = highlightZeroLines;
}
/**
* @param padding the padding around the graph and labels
*/
public void setPadding(int padding) {
mStyles.padding = padding;
}
/**
* @return the label formatter, that converts
* the raw numbers to strings
*/
public LabelFormatter getLabelFormatter() {
return mLabelFormatter;
}
/**
* @param mLabelFormatter the label formatter, that converts
* the raw numbers to strings
*/
public void setLabelFormatter(LabelFormatter mLabelFormatter) {
this.mLabelFormatter = mLabelFormatter;
mLabelFormatter.setViewport(mGraphView.getViewport());
}
/**
* @return the title of the horizontal axis
*/
public String getHorizontalAxisTitle() {
return mHorizontalAxisTitle;
}
/**
* @param mHorizontalAxisTitle the title of the horizontal axis
*/
public void setHorizontalAxisTitle(String mHorizontalAxisTitle) {
this.mHorizontalAxisTitle = mHorizontalAxisTitle;
}
/**
* @return the title of the vertical axis
*/
public String getVerticalAxisTitle() {
return mVerticalAxisTitle;
}
/**
* @param mVerticalAxisTitle the title of the vertical axis
*/
public void setVerticalAxisTitle(String mVerticalAxisTitle) {
this.mVerticalAxisTitle = mVerticalAxisTitle;
}
/**
* @return font size of the vertical axis title
*/
public float getVerticalAxisTitleTextSize() {
return mStyles.verticalAxisTitleTextSize;
}
/**
* @param verticalAxisTitleTextSize font size of the vertical axis title
*/
public void setVerticalAxisTitleTextSize(float verticalAxisTitleTextSize) {
mStyles.verticalAxisTitleTextSize = verticalAxisTitleTextSize;
}
/**
* @return font color of the vertical axis title
*/
public int getVerticalAxisTitleColor() {
return mStyles.verticalAxisTitleColor;
}
/**
* @param verticalAxisTitleColor font color of the vertical axis title
*/
public void setVerticalAxisTitleColor(int verticalAxisTitleColor) {
mStyles.verticalAxisTitleColor = verticalAxisTitleColor;
}
/**
* @return font size of the horizontal axis title
*/
public float getHorizontalAxisTitleTextSize() {
return mStyles.horizontalAxisTitleTextSize;
}
/**
* @param horizontalAxisTitleTextSize font size of the horizontal axis title
*/
public void setHorizontalAxisTitleTextSize(float horizontalAxisTitleTextSize) {
mStyles.horizontalAxisTitleTextSize = horizontalAxisTitleTextSize;
}
/**
* @return font color of the horizontal axis title
*/
public int getHorizontalAxisTitleColor() {
return mStyles.horizontalAxisTitleColor;
}
/**
* @param horizontalAxisTitleColor font color of the horizontal axis title
*/
public void setHorizontalAxisTitleColor(int horizontalAxisTitleColor) {
mStyles.horizontalAxisTitleColor = horizontalAxisTitleColor;
}
/**
* @return the alignment of the labels on the right side
*/
public Paint.Align getVerticalLabelsSecondScaleAlign() {
return mStyles.verticalLabelsSecondScaleAlign;
}
/**
* @param verticalLabelsSecondScaleAlign the alignment of the labels on the right side
*/
public void setVerticalLabelsSecondScaleAlign(Paint.Align verticalLabelsSecondScaleAlign) {
mStyles.verticalLabelsSecondScaleAlign = verticalLabelsSecondScaleAlign;
}
/**
* @return the color of the labels on the right side
*/
public int getVerticalLabelsSecondScaleColor() {
return mStyles.verticalLabelsSecondScaleColor;
}
/**
* @param verticalLabelsSecondScaleColor the color of the labels on the right side
*/
public void setVerticalLabelsSecondScaleColor(int verticalLabelsSecondScaleColor) {
mStyles.verticalLabelsSecondScaleColor = verticalLabelsSecondScaleColor;
}
/**
* @return the width of the vertical labels
* of the second scale
*/
public int getLabelVerticalSecondScaleWidth() {
return mLabelVerticalSecondScaleWidth==null?0:mLabelVerticalSecondScaleWidth;
}
/**
* @return flag whether the horizontal labels are
* visible
*/
public boolean isHorizontalLabelsVisible() {
return mStyles.horizontalLabelsVisible;
}
/**
* @param horizontalTitleVisible flag whether the horizontal labels are
* visible
*/
public void setHorizontalLabelsVisible(boolean horizontalTitleVisible) {
mStyles.horizontalLabelsVisible = horizontalTitleVisible;
}
/**
* @return flag whether the vertical labels are
* visible
*/
public boolean isVerticalLabelsVisible() {
return mStyles.verticalLabelsVisible;
}
/**
* @param verticalTitleVisible flag whether the vertical labels are
* visible
*/
public void setVerticalLabelsVisible(boolean verticalTitleVisible) {
mStyles.verticalLabelsVisible = verticalTitleVisible;
}
/**
* @return count of the vertical labels, that
* will be shown at one time.
*/
public int getNumVerticalLabels() {
return mNumVerticalLabels;
}
/**
* @param mNumVerticalLabels count of the vertical labels, that
* will be shown at one time.
*/
public void setNumVerticalLabels(int mNumVerticalLabels) {
this.mNumVerticalLabels = mNumVerticalLabels;
}
/**
* @return count of the horizontal labels, that
* will be shown at one time.
*/
public int getNumHorizontalLabels() {
return mNumHorizontalLabels;
}
/**
* @param mNumHorizontalLabels count of the horizontal labels, that
* will be shown at one time.
*/
public void setNumHorizontalLabels(int mNumHorizontalLabels) {
this.mNumHorizontalLabels = mNumHorizontalLabels;
}
/**
* @return the grid style
*/
public GridStyle getGridStyle() {
return mStyles.gridStyle;
}
/**
* Define which grid lines shall be drawn
*
* @param gridStyle the grid style
*/
public void setGridStyle(GridStyle gridStyle) {
mStyles.gridStyle = gridStyle;
}
/**
* @return the space between the labels text and the graph content
*/
public int getLabelsSpace() {
return mStyles.labelsSpace;
}
/**
* the space between the labels text and the graph content
*
* @param labelsSpace the space between the labels text and the graph content
*/
public void setLabelsSpace(int labelsSpace) {
mStyles.labelsSpace = labelsSpace;
}
/**
* set horizontal label align
* @param align
*/
public void setVerticalLabelsVAlign(VerticalLabelsVAlign align){
mStyles.verticalLabelsVAlign = align;
}
/**
* Get horizontal label align
* @return align
*/
public VerticalLabelsVAlign getVerticalLabelsVAlign(){
return mStyles.verticalLabelsVAlign;
}
}