概述(项目地址:https://github.com/JiangYueA/android_aps)
使用AChartEngine实现股票分时、五日、k线图,分时图主要有均线,时价的走势和成交量的柱状图,可以使用AChartEngine的CombinedTemperatureChart这个图表类型作为demo,主要是混合Line,Bar这两种图;后期可以加入手势移动显示xy坐标值,实现思路是在GraphicView里面监听onTouchEvent,传递到各个Chart类型的图里面去;
下面先贴上最终要实现的效果图:
第一阶段先从简单的分时图开始,主要要考虑的是柱状图和线图数据的处理,AChartEngine支持多图型绘制在同一面板,这样我通过数据的处理把时价线,均价线,成交量柱状图画在同一画布上;
//数据处理
XYSeries series01 = new XYSeries("Line", 0);//均线
XYSeries series02 = new XYSeries("Line", 0);//时实走势
XYSeries series03 = new XYSeries("RangeBar", 0);//成交量柱状图
//极值
double minValue = 1000000000;
double maxValue = -1000000000;
double minTradeValue = 1000000000;
double maxTradeValue = -1000000000;
//数据填装
for (int i = 0, size = minuteSize; i < size; i++) {
series01.add(i, minuteLine.get(i).cjprice);
series02.add(i, minuteLine.get(i).avprice);
//求最小值
if (minValue > minuteLine.get(i).cjprice) {
minValue = minuteLine.get(i).cjprice;
}
if (minValue > minuteLine.get(i).avprice) {
minValue = minuteLine.get(i).avprice;
}
if (minTradeValue > minuteLine.get(i).cjnum) {
minTradeValue = minuteLine.get(i).cjnum;
}
//求最大值
if (maxValue < minuteLine.get(i).cjprice) {
maxValue = minuteLine.get(i).cjprice;
}
if (maxValue < minuteLine.get(i).avprice) {
maxValue = minuteLine.get(i).avprice;
}
if (maxTradeValue < minuteLine.get(i).cjnum) {
maxTradeValue = minuteLine.get(i).cjnum;
}
}
//重新设置极小值,留涨幅空隙
double temp = maxValue - minValue > minValue ? minValue : maxValue - minValue;
maxValue = maxValue + (temp) * 0.2;
minValue = minValue - (temp) * 0.2;
if (maxValue - entity.shareRtData.dRtPreClose > entity.shareRtData.dRtPreClose - minValue) {
minValue = entity.shareRtData.dRtPreClose - (maxValue - entity.shareRtData.dRtPreClose) * 5 / 7;
} else {
maxValue = entity.shareRtData.dRtPreClose + (entity.shareRtData.dRtPreClose - minValue) * 5 / 7;
}
//获取成交量基数
temp = maxValue - minValue > minValue ? minValue : maxValue - minValue;
double baseTradeValue = 1 / (maxTradeValue - minTradeValue) * (temp) * (0.4 - 0.1);
minValue = minValue - (temp) * 0.4;
//设置成交量柱状图数据
for (int i = 0, size = entity.shareFsDatas.size(); i < size; i++) {
series03.add(i, minValue + (entity.shareFsDatas.get(i).dFsVolume - minTradeValue) * baseTradeValue);
}
均线数据在分钟model的时候已经处理过,AChartEngine绘图有最高最小值,可通过滑分Y轴的数值范围去切割开时价线于成交量柱状图的绘制区间。
解析数据步骤:
- 获取均线和时价线的最大最小值,chart设定y轴的高度,在均线和时价线最大最小值的基础上,留0.4的高度来放置成交量,成交量的值通过与最大最小值的比换算;
- XYSeries值换算坐标点,在XYChart的draw里换算
- 生成的坐标值,传递到对应的图标类型LineChart,BarChart绘制,BarChart有两个坐标,左右,同时需要根据间隔系数获取间隔
坐标换算代码:
synchronized (series) {
SortedMap<Double, Double> range = series.getRange(minX[scale], maxX[scale],
seriesRenderer.isDisplayBoundingPoints());
int startIndex = -1;
for (Entry<Double, Double> value : range.entrySet()) {
double xValue = value.getKey();
double yValue = value.getValue();
if (startIndex < 0 && (!isNullValue(yValue) || isRenderNullValues())) {
startIndex = series.getIndexForKey(xValue);
}
// points.add((float) (left + xPixelsPerUnit[scale]
// * (value.getKey().floatValue() - minX[scale])));
// points.add((float) (bottom - yPixelsPerUnit[scale]
// * (value.getValue().floatValue() - minY[scale])));
values.add(value.getKey());
values.add(value.getValue());
if (!isNullValue(yValue)) {
points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale])));
points.add((float) (bottom - yPixelsPerUnit[scale] * (yValue - minY[scale])));
} else if (isRenderNullValues()) {
points.add((float) (left + xPixelsPerUnit[scale] * (xValue - minX[scale])));
points.add((float) (bottom - yPixelsPerUnit[scale] * (-minY[scale])));
} else {
if (points.size() > 0) {
drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or,
startIndex);
ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints(points, values,
yAxisValue, i, startIndex);
clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));
points.clear();
values.clear();
startIndex = -1;
}
clickableArea.add(null);
}
}
int count = series.getAnnotationCount();
if (count > 0) {
paint.setColor(seriesRenderer.getAnnotationsColor());
paint.setTextSize(seriesRenderer.getAnnotationsTextSize());
paint.setTextAlign(seriesRenderer.getAnnotationsTextAlign());
Rect bound = new Rect();
for (int j = 0; j < count; j++) {
float xS = (float) (left + xPixelsPerUnit[scale]
* (series.getAnnotationX(j) - minX[scale]));
float yS = (float) (bottom - yPixelsPerUnit[scale]
* (series.getAnnotationY(j) - minY[scale]));
paint.getTextBounds(series.getAnnotationAt(j), 0, series.getAnnotationAt(j).length(),
bound);
if (xS < (xS + bound.width()) && yS < canvas.getHeight()) {
drawString(canvas, series.getAnnotationAt(j), xS, yS, paint);
}
}
}
if (points.size() > 0) {
drawSeries(series, canvas, paint, points, seriesRenderer, yAxisValue, i, or, startIndex);
ClickableArea[] clickableAreasForSubSeries = clickableAreasForPoints(points, values,
yAxisValue, i, startIndex);
clickableArea.addAll(Arrays.asList(clickableAreasForSubSeries));
}
}
}
最后得到的List points既是坐标集合,(ps,在遇到NaN或无效值是会断开计算直接绘制,这种情况,绘制bar会有误差);