MPAndroid是开源的图标绘制框架。能够绘制 ,支持绘制折线图,柱状图,组合图,radar图等。
本文主要解决: Y轴绘制出现了负数,且不能取整。
Y轴数据.png如:左侧出现-18
类图结构
MPAndroid.png- 通过类图可知,MPAndroid的Chart继承自ViewGroup. 重写了onMeasure, onLayout,onDraw等方法。
- 绘制分为坐标轴AxisRenderer 和 数据区域DataRender.
AxisRenderer
AxisRenderer 就是绘制X轴和Y轴。直接子类是YAxisRenderer 和 XAxisRenderer
class AxisRenderer{
//通过最大值和最小值得到Entry[]
protected void computeAxisValues(float min, float max)
//绘制Label(就是数值)
public abstract void renderAxisLabels(Canvas c);
//绘制网格线
public abstract void renderGridLines(Canvas c);
//绘制坐标轴
public abstract void renderAxisLine(Canvas c);
//绘制limit
public abstract void renderLimitLines(Canvas c);
}
DataRender
DataRender 绘制折线,柱状图,贝塞尔曲线,数值,其他的如MakerView等
class DataRenderer{
//绘制折线,柱状图
public abstract void drawData(Canvas c);
//绘制Values
public abstract void drawValues(Canvas c);
}
绘制Y轴label的计算规则
protected void computeAxisValues(float min, float max) {
float yMin = min;
float yMax = max;
int labelCount = mAxis.getLabelCount();
double range = Math.abs(yMax - yMin);
double rawInterval = range / labelCount;
double interval = Utils.roundToNextSignificant(rawInterval);//61-->60 111 =>100 165=>200
// Normalize interval //65 ==》 10 200 =》 100
//interval 超过50,就是100的间隔 低于50值不变
double intervalMagnitude = Utils.roundToNextSignificant(Math.pow(10, (int) Math.log10(interval)));
int intervalSigDigit = (int) (interval / intervalMagnitude);
if (intervalSigDigit > 5) {
// Use one order of magnitude higher, to avoid intervals like 0.9 or 90
// if it's 0.0 after floor(), we use the old value
interval = Math.floor(10.0 * intervalMagnitude) == 0.0
? interval
: Math.floor(10.0 * intervalMagnitude); //65 => 10 =>10*10 = 100
}
int n = mAxis.isCenterAxisLabelsEnabled() ? 1 : 0;
// force label count
if (mAxis.isForceLabelsEnabled()) {
//labelcount 默认为6 ,此处interval重新赋值
interval = (float) range / (float) (labelCount - 1);
mAxis.mEntryCount = labelCount;
if (mAxis.mEntries.length < labelCount) {
// Ensure stops contains at least numStops elements.
mAxis.mEntries = new float[labelCount];
}
float v = min;
for (int i = 0; i < labelCount; i++) {
mAxis.mEntries[i] = v;
v += interval;
}
n = labelCount;
// no forced count
} else {
//mEntry[]的第一个值是和ymin有关,不一定从0开始的。
double first = interval == 0.0 ? 0.0 : Math.ceil( yMin/ interval) * interval;
double last = interval == 0.0 ? 0.0 : Utils.nextUp(Math.floor(yMax / interval) * interval);
double f;
int i;
if (interval != 0.0 && last != first) {
//依据interval 动态计算label的数量
for (f = first; f <= last; f += interval) {
++n;
}
}
else if (last == first && n == 0) {
n = 1;
}
mAxis.mEntryCount = n;
if (mAxis.mEntries.length < n) {
// Ensure stops contains at least numStops elements.
mAxis.mEntries = new float[n];
}
for (f = first, i = 0; i < n; f += interval, ++i) {
if (f == 0.0) // Fix for negative zero case (Where value == -0.0, and 0.0 == -0.0)
f = 0.0;
mAxis.mEntries[i] = (float) f;
}
}
}
上述分为2种情况,通过mAxis.isForceLabelsEnabled() 判断采用哪种计算规则。这个值是通过 yAxis.setLabelCount(6, true) 传递的。
简单描述一下2种算法,都是为了确定mEntry[],存储的数据就是要绘制在Y轴上的label,也就是Y轴上的数字:
- 如果labelIsEnable() = true
interval = (max - min) / (labelcount -1)
mEntry[0] = min ; mEntry[1] = mEntry[0] + interval
缺点: interval 任意,mEntry[0] 的值也是任意的。不是我们想要10 或者 5 的倍数的数字。 - 如果labelIsEnable() = false
interval计算分为2步
第一步: interval = (max - min) / labelcount
第二步: interval = 65 转换成100 ; interval = 44 转换成40
mEntry[0] = math.ceil(ymin/interval)*interval ;
mEntry[1] = mEntry[0] + interval;
缺点:比如65的interval 转成了100 ,导致我们期望6个label,最终可能是5个,间隔过大问题。
这2种算法,如果不满足我们的需求,可以自定义Y轴。
自定义Y轴算法
写一种算法,保证lablecount = 6 , 同时间距也是5的倍数,保证mEntry[0] 也是10的整数。
public class HyYAxisRenderer extends YAxisRenderer {
@Override
protected void computeAxisValues(float min, float max) {
if (mAxis.isForceLabelsEnabled()) {
if(mAxis.mEntries != null) {
int length = mAxis.mEntries.length;
if (length > 0) {
if (max == mAxis.mEntries[length-1]){
return;
}
}
}
// interval = (float) range / (float) (labelCount - 1);
double rangeLength = Math.floor(Math.log10(range / (labelCount - 1)));
double pow = Math.pow(10, rangeLength);
float floorMin = (float) (Math.floor(min / pow) * pow);
if (floorMin > 0) {
floorMin = 0;
}
interval = getInterval(max, floorMin, (labelCount - 1));
mAxis.mEntryCount = labelCount;
if (mAxis.mEntries.length < labelCount) {
// Ensure stops contains at least numStops elements.
mAxis.mEntries = new float[labelCount];
}
float v = floorMin;
for (int i = 0; i < labelCount; i++) {
mAxis.mEntries[i] = v;
v += interval;
}
n = labelCount;
// no forced count
}else { }
mAxis.mAxisMinimum = mAxis.mEntries[0];
mAxis.mAxisMaximum = mAxis.mEntries[n - 1];
mAxis.mAxisRange = Math.abs(mAxis.mAxisMaximum - mAxis.mAxisMinimum);
}
public double getInterval(float max, float min, int count) {
float range2 = (max - min) / count;
double rangeLength2 = Math.floor(Math.log10(range2));
double pow = Math.pow(10, rangeLength2);
float floorMax = (float) (Math.ceil(max / pow) * pow);
int interval = (int) Math.ceil((floorMax - min) / count);
double finalInterval = interval;
if (interval > 5 && interval < 100) { //
double aa = Math.floor(interval / 5);
if (aa * 5 * count < floorMax) {
finalInterval = (aa + 1) * 5;
} else {
finalInterval = aa * 5;
}
}
return finalInterval;
}
}
主要是getInterval()方法,处理isForceLabelsEnabled() 为true的情况,计算最终的Interval,不会出现任意数字,label为5或者10的倍数。
使用自定义的Y轴
HyYAxisRenderer hyYAxisRenderer = new HyYAxisRenderer(chart.getViewPortHandler(),yAxis,chart.getTransformer(YAxis.AxisDependency.LEFT));
chart.setRendererLeftYAxis(hyYAxisRenderer);
yAxis.setLabelCount(6, true);
其他类图
MpAndroid_dataSet.png其他问题
1.Y轴坐标的mAxisMinimum 和 mAxisMaximum 在计算的时候,默认会增加SpaceBottom和SpaceTop距离,可能是为了方便绘制贝塞尔曲线,防止越界问题,不使用,可以设置为0。
public class YAxis extends AxisBase {
this.mAxisMinimum = mCustomAxisMin ? this.mAxisMinimum : min - (range / 100f) * getSpaceBottom();
this.mAxisMaximum = mCustomAxisMax ? this.mAxisMaximum : max + (range / 100f) * getSpaceTop();
this.mAxisRange = Math.abs(this.mAxisMinimum - this.mAxisMaximum); // 912
- xAxis.setCenterAxisLabels(true); 设置数字居中,默认只有X坐标会生效。Y轴通过设置offsetX,offsetY实现偏移。
public class XAxisRenderer extends AxisRenderer {
protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();
float[] positions = new float[mXAxis.mEntryCount * 2];
for (int i = 0; i < positions.length; i += 2) {
// only fill x values
if (centeringEnabled) {
positions[i] = mXAxis.mCenteredEntries[i / 2];
} else {
positions[i] = mXAxis.mEntries[i / 2];
}
}
效果
图一.png图二.png
图三
图三对比图二就是去掉默认padding, 坐标轴也不是负数了。
网友评论