Android - 熱力圖

前言:簡書菜鳥一枚,主要用于記錄彭则。如有侵權(quán)鳍刷,望告知,我會下架文章俯抖。

話不多输瓜,先上圖:


heatMap_both.png

最近公司在做一款能彩集壓力數(shù)據(jù)的坐墊。怎么樣能將這個功能高大上的體現(xiàn)出牛逼樣兒。這時同事們就想到了熱力圖了尤揣。如圖示搔啊,不明覺厲,確實高大上芹缔。

一坯癣、找資源

實際工作的開發(fā)過程中,因為時間限定研發(fā)成本等原因最欠,每遇到新玩意示罗,首先都是找“輪子”。我運氣不錯(天選打工人一枚)一個早上就找到2個芝硬,并且在接入時發(fā)現(xiàn)第一個就很不錯蚜点。大佬輪子鏈接https://github.com/ChristianFF/HeatMapForAndroid。效果也貼一個:

image.png

二拌阴、接入

由于直接效果略有出入绍绘,感覺要對應自己細節(jié)調(diào)整所以我是直接下載源碼接入,最終關鍵代碼沒變迟赃。配置略有調(diào)整陪拘,關鍵類Gradient、HeatMap纤壁、WeightedPoint左刽;關鍵代碼如下:

// 轉(zhuǎn)換數(shù)據(jù)
private List<WeightedPoint> generateHeatMapData(Object[] obs) {
        List<WeightedPoint> data = new ArrayList<>();
        for (int i = 0; i < obs.length; i++) {
            data.add(new WeightedPoint(obs[i].getOx() + mHeatMapPointRadius,
                    obs[i].getOy() + mHeatMapPointRadius, obs[i].getPressure()));
        }
        return data;
}

// 生成熱力圖
List<WeightedPoint> data = generateHeatMapData(mMotorCircles);
HeatMap heatMap = new HeatMap.Builder().weightedData(data).radius(mHeatMapPointRadius)
                .width(view.getWidth() + mHeatMapPointRadius).height(view.getHeight() + mHeatMapPointRadius).build();
Bitmap heatMap = heatMap.generateMap();

/**
* 顏色配置(色階漸變)
* 色彩區(qū)間,參數(shù)1色階酌媒。參數(shù)2色階占比(按0~1分段)欠痴,
*/
private static final Gradient DEFAULT_MY_GRADIENT = new Gradient(
            new int[]{0xFF7C89DF, Color.GREEN, Color.YELLOW, Color.RED},
            new float[]{0.2f, 0.4f, 0.7f, 1f});

三、源碼分析

我感覺這個東西很有意思秒咨,加上預防需求變化有調(diào)整喇辽。就具體看了看源碼,以下是我的個人收獲雨席。記錄一番菩咨。

1、設計思路:與二維碼相似陡厘,通過描繪像素點形成位圖Bitmap抽米;不同的是這是給圖片的不同像素點上色不同顏色值,以一個坐標點為圓心雏亚,圓形向外漸變顏色缨硝,如:
image.png

散點摩钙、相鄰點:
image.png

色彩與像素點的映射關系:(參考一個點的圖對比)

image.png

2罢低、代碼實現(xiàn)

設計:

image.png

關鍵計算代碼:

1.生成位圖方法(注釋是我個人的理解,僅供參考)

    public Bitmap generateMap() {
//        double[][] intensity = new double[mWidth][mHeight];
        // 強度二維數(shù)組(坐標強度)
        double[][] intensity = new double[mWidth + mRadius * 2][mHeight + mRadius * 2];
        for (WeightedPoint w : mData) {
            // 數(shù)據(jù)集點,熱力中心點网持,然后周邊擴散
            int bucketX = w.x;
            int bucketY = w.y;
            if (bucketX < mWidth && bucketX >= 0 && bucketY < mHeight && bucketY >= 0) {
                intensity[bucketX][bucketY] += w.intensity;
            }
        }
        // convolved 卷積宜岛;intensity 強度;Kernel 內(nèi)核
        double[][] convolved = convolve(intensity, mKernel);
        return colorize(convolved, mColorMap, mMaxIntensity);
    }

2.點與點之間交叉重合的處理(強度相容)-重點

/**
     * 使纏繞功舀,將坐標之間的強度相接
     *
     * @param grid   像素點集(x萍倡,y)
     * @param kernel 坐標(漸變系數(shù),圓心~圓邊:(1,0]辟汰,0是透明)
     * @return 具體圖片的像素點集(帶強度)
     */
    protected double[][] convolve(double[][] grid, double[] kernel) {
        Logc.i("radius = " + mRadius);
        int dimOldW = grid.length;
        int dimOldH = grid[0].length;
        int dimW = dimOldW - 2 * mRadius;
        int dimH = dimOldH - 2 * mRadius;
        int lowerLimit = mRadius;
        int upperLimitW = mRadius + dimW - 1; // 向外伸展列敲,目的:是支持強度(紅點)在邊界上。
        int upperLimitH = mRadius + dimH - 1;
        // 中間的; 中級的; (兩地帖汞、兩物戴而、兩種狀態(tài)等)之間的
        double[][] intermediate = new double[dimOldW][dimOldH];
        int x, y, x2, xUpperLimit, initial;
        double val;
        for (x = 0; x < dimOldW; x++) {
            for (y = 0; y < dimOldH; y++) {
                val = grid[x][y];
                if (val != 0) {
                    // 寬上限:x軸 + 半徑
                    xUpperLimit = ((upperLimitW < x + mRadius) ? upperLimitW : x + mRadius);
                    // 初始值
                    initial = (lowerLimit > x - mRadius) ? lowerLimit : x - mRadius;
                    // 遍歷x,賦予強度值.(一維數(shù)組翩蘸,同y軸上的強度)
                    for (x2 = initial; x2 < xUpperLimit; x2++) {
                        double v = kernel[x2 - (x - mRadius)];
                        intermediate[x2][y] += val * v;
//                        Logc.d("有坐標的所意,x2 = " + x2 + ", y = " + y + ", val = " + val + " , v = " + v + ", (x - mRadius) = " + (x - mRadius) + ", old = " + old + ", new =" + (val * v));
                    }
                }
            }
        }
        // 輸出的網(wǎng)格
        double[][] outputGrid = new double[dimW][dimH];
        int y2, yUpperLimit;
        // 坐標范圍的像素點強度  整圖范圍的x軸(radius ~ radiusX + mWidth - 1)
        for (x = lowerLimit; x < upperLimitW + 1; x++) {
            for (y = 0; y < dimOldH; y++) {
                val = intermediate[x][y];
                // val != 0 :有強度的,設置坐標了的
                if (val != 0) {
                    yUpperLimit = ((upperLimitH < y + mRadius) ? upperLimitH : y + mRadius);
                    // 初始y值
                    initial = (lowerLimit > y - mRadius) ? lowerLimit : y - mRadius;
                    // 遍歷y催首,賦予強度值.
                    for (y2 = initial; y2 < yUpperLimit; y2++) {
                        //
                        double v = kernel[y2 - (y - mRadius)];
                        // 不同點之間有交集所以用 +=扶踊;
                        outputGrid[x - mRadius][y2 - mRadius] += val * v;
                    }
                }
            }
        }
        return outputGrid;
    }

3.位圖上色,如zxing二維碼一樣郎任,給圖片的每個像素點按強度上色

 /**
     * 像素點上色秧耗,即畫位圖
     *
     * @param grid     像素點集(x,y)
     * @param colorMap 設置的顏色變化色彩集(比如4中顏色淡、弱涝滴、中绣版、強且按排序的色彩集)
     * @param max      最大強度
     * @return
     */
    private Bitmap colorize(double[][] grid, int[] colorMap, double max) {
        int maxColor = colorMap[colorMap.length - 1];
        double colorMapScaling = (colorMap.length - 1) / max;

        int dimW = mWidth;
        int dimH = mHeight;

        int i, j, index, col;
        double val;
        int[] colors = new int[dimW * dimH];
        for (i = 0; i < dimH; i++) {
            for (j = 0; j < dimW; j++) {
                val = grid[j][i];// 強度,最大值:max
                index = i * dimW + j;
                // val * colorMapScaling歼疮,結(jié)合之前的 colorMapScaling = (colorMap.length - 1) / max;
                // 算出某強度在顏色集的下標
                col = (int) (val * colorMapScaling);
                if (val != 0) {
                    if (col < colorMap.length) {
                        colors[index] = colorMap[col];
                    } else {
                        colors[index] = maxColor;
                    }
                } else {
                    colors[index] = Color.TRANSPARENT;
                }
            }
        }
        Bitmap tile = Bitmap.createBitmap(dimW, dimH, Bitmap.Config.ARGB_8888);
        tile.setPixels(colors, 0, dimW, 0, 0, dimW, dimH);
        return tile;
    }

4.色彩映射按強度形成數(shù)據(jù)便于與強度關聯(lián)然后位圖上色(注釋是我個人的理解杂抽,僅供參考)

/**
     * 生成顏色集(按淡、弱韩脏、中缩麸、強(colors)排序的)
     * @param opacity 不透明度
     * @return
     */
    public int[] generateColorMap(double opacity) {
        HashMap<Integer, ColorInterval> colorIntervals = generateColorIntervals();
        int[] colorMap = new int[colorMapSize]; // (各色階依據(jù)色階段比,在色彩集中也對應分段)
        ColorInterval interval = colorIntervals.get(0);
        // 初始顏色
        int start = 0;
        for (int i = 0; i < colorMapSize; i++) {
            // 從色階分段中獲取分段信息(紅-黃赡矢、黃-綠杭朱、綠-藍、藍-透明)
            if (colorIntervals.containsKey(i)) {
                // 區(qū)間范圍(色階分段范圍)
                interval = colorIntervals.get(i);
                // 色階初始值分段中的初始值吹散,類似(紅-黃[100~80]弧械、黃-綠[80~50]、綠-藍(50~20)空民、藍-透明(20~0))
                start = i; // 100\80\50\20
            }
            // ratio : 比率刃唐;colorEnd時 = 1羞迷。;如i = 90画饥,色階長度是20衔瓮,初始值是80,- 》 比率 = 0.5
            float ratio = (i - start) / interval.interval; // interval.interval // 色階長度(如紅黃[100~80] 是20)
            colorMap[i] = interpolateColor(interval.colorStart, interval.colorEnd, ratio);
        }
        // 轉(zhuǎn)成帶透明度的顏色值
        if (opacity != 1) {
            for (int i = 0; i < colorMapSize; i++) {
                int c = colorMap[i];
                colorMap[i] = Color.argb((int) (Color.alpha(c) * opacity),
                        Color.red(c), Color.green(c), Color.blue(c));
            }
        }

        return colorMap;
    }

    /**
     * 生成色階集合
     * @return
     */
    private HashMap<Integer, ColorInterval> generateColorIntervals() {
        // 顏色區(qū)間數(shù)數(shù)組
        HashMap<Integer, ColorInterval> colorIntervals = new HashMap<>();
        // Create first color if not already created
        // The initial color is transparent by default : 初始顏色默認為透明
        // 其實默認色階比colors多一個透明
        if (startPoints[0] != 0) {
            int initialColor = Color.argb(
                    0, Color.red(colors[0]), Color.green(colors[0]), Color.blue(colors[0]));
            colorIntervals.put(0, new ColorInterval(initialColor, colors[0], colorMapSize * startPoints[0]));
        }
        // Generate color intervals
        // 生成色階集
        for (int i = 1; i < colors.length; i++) {
            colorIntervals.put(((int) (colorMapSize * startPoints[i - 1])),
                    new ColorInterval(colors[i - 1], colors[i],
                            (colorMapSize * (startPoints[i] - startPoints[i - 1]))));
        }
        // If color for 100% intensity is not given, the color of highest intensity is used. 如果沒有給出100%強度的顏色抖甘,則使用最高強度的顏色热鞍。
        // 即最強強度去掉透明度
        if (startPoints[startPoints.length - 1] != 1) {
            int i = startPoints.length - 1;
            colorIntervals.put(((int) (colorMapSize * startPoints[i])),
                    new ColorInterval(colors[i], colors[i], colorMapSize * (1 - startPoints[i])));
        }
        return colorIntervals;
    }

    /**
     * 插入/篡改 顏色
     * @param colorStart 初始顏色
     * @param colorEnd 結(jié)束顏色
     * @param ratio 比率
     * @return
     */
    private int interpolateColor(int colorStart, int colorEnd, float ratio) {
        int alpha = (int) ((Color.alpha(colorEnd) - Color.alpha(colorStart)) * ratio + Color.alpha(colorStart));
        float[] hsvStart = new float[3];
        // https://blog.csdn.net/qq_42271561/article/details/115465061
        // HSV是一種比較直觀的顏色模型,所以在許多圖像編輯工具中應用比較廣泛衔彻,這個模型中顏色的參數(shù)分別是:色調(diào)(H,Hue:0°~360°)薇宠,飽和度(S,Saturation:0%~100%),明度(V, Value:0%(黑)到100%(白))艰额。
        Color.RGBToHSV(Color.red(colorStart), Color.green(colorStart), Color.blue(colorStart), hsvStart);
        float[] hsvEnd = new float[3];
        Color.RGBToHSV(Color.red(colorEnd), Color.green(colorEnd), Color.blue(colorEnd), hsvEnd);
        // 就近漸變昼接,避免漸變經(jīng)過大于180的色彩范圍
        if (hsvStart[0] - hsvEnd[0] > 180) {
            hsvEnd[0] += 360;
        } else if (hsvEnd[0] - hsvStart[0] > 180) {
            hsvStart[0] += 360;
        }

        float[] hsvResult = new float[3];
        // 轉(zhuǎn)換HSV顏色數(shù)據(jù)后按比率變換
        for (int i = 0; i < 3; i++) {
            // (結(jié)束色彩 - 初始色彩)* 比率
            hsvResult[i] = (hsvEnd[i] - hsvStart[i]) * (ratio) + hsvStart[i];
        }

        return Color.HSVToColor(alpha, hsvResult);
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市悴晰,隨后出現(xiàn)的幾起案子慢睡,更是在濱河造成了極大的恐慌,老刑警劉巖铡溪,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漂辐,死亡現(xiàn)場離奇詭異,居然都是意外死亡棕硫,警方通過查閱死者的電腦和手機髓涯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哈扮,“玉大人纬纪,你說我怎么就攤上這事』猓” “怎么了包各?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長靶庙。 經(jīng)常有香客問我问畅,道長,這世上最難降的妖魔是什么六荒? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任护姆,我火速辦了婚禮,結(jié)果婚禮上掏击,老公的妹妹穿的比我還像新娘卵皂。我一直安慰自己,他們只是感情好砚亭,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布灯变。 她就那樣靜靜地躺著豺旬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柒凉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天篓跛,我揣著相機與錄音膝捞,去河邊找鬼。 笑死愧沟,一個胖子當著我的面吹牛蔬咬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沐寺,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼林艘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了混坞?” 一聲冷哼從身側(cè)響起狐援,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎究孕,沒想到半個月后啥酱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡厨诸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年镶殷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片微酬。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡绘趋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颗管,到底是詐尸還是另有隱情陷遮,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布垦江,位于F島的核電站拷呆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疫粥。R本人自食惡果不足惜茬斧,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梗逮。 院中可真熱鬧项秉,春花似錦、人聲如沸慷彤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岁诉,卻和暖如春锚沸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涕癣。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工哗蜈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坠韩。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓距潘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親只搁。 傳聞我的和親對象是個殘疾皇子音比,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容