MPAndroidChart运动数据图表
yxc 人气:0引言
声明:文中的MPChart代指MPAndroidChart.
本系列之前的文章介绍的MPChart中BarChart相关的一些绘制,接下来我们看看LineChart相关的绘制。
这里以实际的运动相关的图表数据做业务支撑来讲解。MPChart图表支持多指触控方法,这里所有的图表自定义都关掉了这个属性,这样就减少Transformer,以及绘制过程中的更多的变动,相当于一个静态的图。
通常图表在放大的过程中,坐标轴也会随之展现更小的刻度,复杂度就变高了,具体的显示的刻度就可能出现小数之类的等情况。
这里我们关掉触摸放大后,相当于一个静态的图。这时候,产品以及设计可能需要我们的X轴坐标、Y轴坐标等刻度尽可能地为整数,这样看起来比较美观。
静态的情况下,因为没法移动,即便可以移动,首次展现也希望图表的数据能够比较居中,这就涉及到YAxis的自定义如何影响控制 Chart的相关展示逻辑的内容,本章节首先从这入手,讲解运动数据图表的绘制。
TimeAxis
这里自定义X轴TimeAxis,实际意义是一次运动耗费的时间,继承自XAxis。假如一次运动1个小时06分钟,设计希望展示4个刻度(0, 20 , 40, 60 分钟), 这时需要我们自己去控制,假如不加控制的话,Default情况下没法准确的实现设计的需求。
XAxis、YAxis有两个属性,Maximum, Minmum. 这样设定每个interval 就可以计算出要显示的刻度列表, labelList, 加入到XAxis中的 mEntries, 最后在XAxisRender会拿到mEntries 最终绘制 X坐标。
这里不在设计一个算法类计算每个时间段的刻度显示了,运动的时间范围有限,直接枚举, 在 TimeXAxis里的getlabelCount() 实现。
if (max > 6000 * TimeDateUtil.TIME_MIN_INT){ interval = 2000 * TimeDateUtil.TIME_MIN_INT; } else if (max > 4800 * TimeDateUtil.TIME_MIN_INT) {// 80个小时 interval = 1920 * TimeDateUtil.TIME_MIN_INT; } else if (max > 2400 * TimeDateUtil.TIME_MIN_INT) {// 40个小时 interval = 960 * TimeDateUtil.TIME_MIN_INT; } else if (max > 1200 * TimeDateUtil.TIME_MIN_INT) { interval = 480 * TimeDateUtil.TIME_MIN_INT; } 。。。。。 else if (max > 20 * TimeDateUtil.TIME_MIN_INT) { interval = 5 * TimeDateUtil.TIME_MIN_INT; } else if (max > 15 * TimeDateUtil.TIME_MIN_INT) { interval = 4 * TimeDateUtil.TIME_MIN_INT;//4分钟刻度 } else if (max > 5 * TimeDateUtil.TIME_MIN_INT) {// interval = 2 * TimeDateUtil.TIME_MIN_INT;//2分钟刻度。 } else { interval = TimeDateUtil.TIME_MIN_INT; } float currentEntry = min; List<Float> entryList = new ArrayList<>(); do { entryList.add(currentEntry); currentEntry += interval; } while (currentEntry <= max); labelCount = entryList.size(); mEntryCount = labelCount; if (mEntries.length < labelCount) { mEntries = new float[labelCount]; } for (int i = 0; i < labelCount; i++) { mEntries[i] = entryList.get(i); }
准备好XAxis 中的Entry数据后,依旧是交给Buffer,经过Transformer转化,最终绘制出来, 自定义TimeAxisRender, 然后 renderAxisLabels() 方法里drawLabel(), 绘制XAxis的坐标轴线:
图1.0 XAxis 坐标线的绘制
SportYAxis
Y轴的绘制相比XAxis要复杂一些,自定义的SportYAxis继承自YAxis, TimeXAxis 只有时间数据对应。SportYAxis根据具体的数据业务可以表示 心率, 高度海拔, 速度, 配速,频率等数据。
这些数据中, 心率、步频等取值范围可以比如(0, 250)类似这样的可以直接定下来Y轴的Max, Min 值以及对应的刻度,高度海拔有负数的,速度的Max根据 所给数据来定, 配速比较特殊,需要将Y轴 revert。
为了将图表能够居中,通常YAxis 上的Maximum 会比 数据中的极大值要偏大,保证图表不会呈现的太慢,影响美观。根据不同的Sport数据,将Y轴分为以下几种:
// TYPE_FIX_MIN_ZERO = 0; Y轴从固定的0开始 到 max;步频、起跳高度 // TYPE_FIX_MIN_POSITIVE = 1; 从 entryList的 真实的 min(min不能小于0)开始,到max; 心率、速度、划频、Swolf // TYPE_FIX_COMMON = 2; 从entryList的最小值min开始到max的最大值,无论最大、最小是否为Positive, 例如海拔; //TYPE_FIX_RESTRICT_MAX = 3; 限制最大值,比如配速。Y轴 Invert,所以最小值min为大于等于 0 的Positive value; 配速 public static final int TYPE_FIX_MIN_ZERO = 0; public static final int TYPE_FIX_MIN_POSITIVE = 1; public static final int TYPE_FIX_COMMON = 2; public static final int TYPE_FIX_RESTRICT_MAX = 3;
每种类型下计算Y轴的Maximum、Minmum; 然后计算刻度的间距 itemValue, 得到 坐标Label 的List。
以上的几种坐标的实现具体在 SportYAxis 中实现。
将Y轴数据限定下来之后,图表的展现因为Y轴的Maximum、Minmum 限定在比较居中的位置。
对于配速,当运动停下来时,单位距离的耗时可能无限大,假如我们考虑把这个极值画下来的话,Y轴可能跨度很大导致图表没法看,所以需要限定极大值,截断图形:
//限制最大值 private float getYAxisMax2(List<SportRecordEntry> values, float yAxisMin) { int size = values.size(); float yAxisMax = Integer.MIN_VALUE; float yAxisMinTemp = Integer.MAX_VALUE; float sum = 0; for (int i = 0; i < size; i++) { SportRecordEntry entry = values.get(i); yAxisMax = Math.max(entry.getY(), yAxisMax); yAxisMinTemp = Math.min(entry.getY(), yAxisMinTemp); sum += entry.getY(); } float averageY = sum / (size * 1.0f); float distanceMin = averageY - yAxisMinTemp; float distanceMax = yAxisMax - averageY; int num = (int) (distanceMax / distanceMin); if (num > 5) {// 配速中 有 配速值很慢的点,坐标时不考虑它们了。 yAxisMax = averageY + 2 * distanceMin; // 限制Y 轴坐标。 } float distance = yAxisMax - yAxisMin; if (yAxisMax > 0 && distance <= 2) { return yAxisMax + 2; } return yAxisMax + distance * mLineChartAttr.maxYAxisRatio; }
CustomLineChart
处理完XAxis、YAxis的数据及绘制后,处理LineChart的主体,这里包含了折线图、曲线图等体现数据展现的,还有drawFill, 底部的填充;drawMaxMinPop() 极值点的绘制等。
着重讲解折线图的绘制,对于LineChart,Entry比较简单,存有对应的X, Y值,
图1.1CustomLineChart 绘制逻辑
考虑先后两个点,PreEntry, CurrentEntry 然后 绘制每段折线,最终连成图表。
针对 配速图表Y轴需要倒过来的,
private float getYAsInverted(Entry entry) { final float phaseY = mAnimator.getPhaseY(); float yValueRange = mYAxis.getAxisMaximum() - mYAxis.getAxisMinimum(); if (mYAxis.isInverted()) { if (entry.getY() <= mYAxis.getAxisMinimum()) { return entry.getY() * phas eY; } else { return (yValueRange - entry.getY()) * phaseY; } } else { return entry.getY() * phaseY; } }
以下就是配速图表,极大值的限定线取的太大,导致整个图形太偏上了,可以做响应的修改。
图1.2配速图表线形图
绘制底部的Fill, 将所有的点连线,再连接到底部的X点坐标,最后形成闭环的Path,
图1.3 drawFill
以上大致就是线性图表的绘制逻辑,考虑X轴、Y轴的Label的设定,绘制,Y轴的极值设定来控制图表图形呈现的位置;配速表的这种Y轴图形的invert, 底部的drawFill().
自此大体的自定义绘制讲解完了。后续会做些补充,步频散点图,极值的绘制,RTL相关等。
加载全部内容