-
Notifications
You must be signed in to change notification settings - Fork 0
Heatmap
王集鹄 edited this page Sep 13, 2013
·
1 revision
package com.renrousousuo.ace;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.RadialGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.view.View;
import java.util.HashMap;
import java.util.Map;
/**
* Created by zswang on 13-9-10.
*/
public class AceHeatmap extends View {
private static final String LOG_TAG = "AceHeatmap";
/**
* 调色板
*/
private int[] mColorPalette;
/**
* 内半径
*/
private int mRadiusIn = 15;
/**
* 外半径
*/
private int mRadiusOut = 40;
/**
* 亮度图
*/
private Bitmap mBitmapAlpha;
/**
* 作色图
*/
private Bitmap mBitmapMap;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mBitmapAlpha != null) {
mBitmapAlpha.recycle();
}
mBitmapAlpha = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
if (mBitmapMap != null) {
mBitmapMap.recycle();
}
mBitmapMap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawAll();
}
private void drawAll() {
mBitmapAlpha.eraseColor(Color.TRANSPARENT);
mBitmapMap.eraseColor(Color.TRANSPARENT);
int left = getWidth();
int top = getHeight();
int right = 0;
int bottom = 0;
for (Point point : points.values()) {
drawAlpha(point.x, point.y);
left = Math.min(point.x - mRadiusOut, left);
top = Math.min(point.y - mRadiusOut, top);
right = Math.max(point.x + mRadiusOut, right);
bottom = Math.max(point.y + mRadiusOut, bottom);
}
if (left < right && top < bottom) {
colorize(left, top, right - left, bottom - top);
}
}
/**
* 获取调色板
* @return 返回调色板的颜色集合
*/
public static int[] colorPalette() {
int[] colors = new int[] {
Color.parseColor("#0000ff"),
Color.parseColor("#00ffff"),
Color.parseColor("#00ff00"),
Color.parseColor("#ffff00"),
Color.parseColor("#ff0000")
};
float[] positions = new float[] {
0.45f,
0.55f,
0.65f,
0.95f,
1.00f
};
return colorPalette(colors, positions);
}
private void drawAlpha(int x, int y) {
if (mBitmapAlpha == null) {
return;
}
String key = String.format("%s,%s", x, y);
Point point = points.get(key);
if (point == null) {
return;
}
double rate = point.count / (mFixedMax < 0.0001f ? mMax : mFixedMax);
Canvas canvas = new Canvas(mBitmapAlpha);
int[] colors = new int[] {
Color.argb((int)(rate * 256), 0, 0, 0),
Color.argb(0, 0, 0, 0)
};
float[] positions = new float[] {
0,
1,
};
RadialGradient radialGradient;
radialGradient = new RadialGradient(x, y, mRadiusIn, colors, positions, Shader.TileMode.CLAMP);
float left = x - mRadiusOut;
float top = y - mRadiusOut;
float mul = 2 * mRadiusOut;
float bottom = top + mul;
float right = left + mul;
Paint paint = new Paint();
paint.setShader(radialGradient);
canvas.drawRect(left, top, right, bottom, paint);
radialGradient = new RadialGradient(x, y, mRadiusOut, colors, positions, Shader.TileMode.CLAMP);
paint.setShader(radialGradient);
canvas.drawRect(left, top, right, bottom, paint);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBitmapMap != null) {
canvas.drawBitmap(mBitmapMap, 0, 0, null);
}
}
/**
* 获取调色板
* @param colors 色阶列表
* @param positions 色阶偏移
* @return 返回调色板的颜色集合
*/
public static int[] colorPalette(int[] colors, float[] positions) {
Bitmap bitmap = Bitmap.createBitmap(1, 256, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
LinearGradient linearGradient;
linearGradient = new LinearGradient(0f, 0f, 1f, 256f, colors, positions, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setShader(linearGradient);
canvas.drawRect(0f, 0f, 1f, 256f, paint);
int[] result = new int[256];
bitmap.getPixels(result, 0, 1, 0, 0, 1, 256);
bitmap.recycle();
return result;
}
public class Point {
public double count;
public int x;
public int y;
public Point(int x, int y, double count) {
this.count = count;
this.x = x;
this.y = y;
}
}
private Map<String, Point> points = new HashMap<String, Point>();
private double mMax;
private double mFixedMax = 0f;
private void put(int x, int y, double count) {
String key = String.format("%s,%s", x, y);
Point point = points.get(key);
if (point == null) {
point = new Point(x, y, count);
points.put(key, point);
} else {
point.count += count;
}
mMax = Math.max(mMax, point.count);
}
private boolean mPapercut;
private void colorize(int x, int y, int width, int height) {
if (mBitmapAlpha == null) {
return;
}
x = Math.max(0, x);
y = Math.max(0, y);
int currentWidth = getWidth();
int currentHeight = getHeight();
width = Math.min(width, currentWidth - x);
height = Math.min(height, currentHeight - y);
int[] pixels = new int[width * height];
mBitmapAlpha.getPixels(pixels, 0, width, x, y, width, height);
int A;
int R;
int G;
int B;
for (int i = 0; i < pixels.length; i++) {
A = (pixels[i] >> 24) & 0xff;
if (mPapercut){
R = G = B = 0;
A = 255 - A;
} else if (A > 0) {
int offset = A;
int color = mColorPalette[A];
R = (color >> 16) & 0xff;
G = (color >> 8) & 0xff;
B = color & 0xff;
} else {
R = G = B = 0;
}
pixels[i] = Color.argb(A, R, G, B);
}
mBitmapMap.setPixels(pixels, 0, width, x, y, width, height);
}
/**
* 添加一个热点
* @param x x坐标
* @param y y坐标
* @param count 热力值,同一位置会叠加
*/
public void add(int x, int y, double count) {
double oldMax = mMax;
put(x, y, count);
if (mBitmapAlpha == null) {
return;
}
if (oldMax == mMax) {
drawAlpha(x, y);
colorize(x - mRadiusOut, y - mRadiusOut, mRadiusOut * 2, mRadiusOut * 2);
//postInvalidate(x - mRadiusOut, y - mRadiusOut, mRadiusOut * 2, mRadiusOut * 2);
postInvalidate();
} else {
drawAll();
postInvalidate();
}
}
private void clear(boolean invalidate) {
points.clear();
mBitmapAlpha.eraseColor(Color.TRANSPARENT);
mBitmapMap.eraseColor(Color.TRANSPARENT);
if (invalidate) {
postInvalidate();
}
}
public void clear() {
clear(true);
}
public void setData(Point[] data) {
points.clear();
for (Point point : data) {
put(point.x, point.y, point.count);
}
drawAll();
postInvalidate();
}
public AceHeatmap(Context context) {
super(context);
mColorPalette = colorPalette();
}
} private AceHeatmap mAceHeadmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAceHeadmap = new AceHeatmap(this);
setContentView(mAceHeadmap);
mAceHeadmap.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
mAceHeadmap.add((int) motionEvent.getX(), (int) motionEvent.getY(), 1);
break;
}
return true;
}
});