Android詞云放置算法

詞云(WordCloud)是分析數(shù)據(jù)時(shí)一項(xiàng)有趣的展示方式, 它將數(shù)據(jù)中的關(guān)鍵詞按權(quán)重設(shè)置不同的大小, 放置成一定的形狀(比如圓形). 它包括關(guān)鍵詞的統(tǒng)計(jì)提取和放置, 這里在安卓端實(shí)現(xiàn)一個(gè)放置詞云的View.

Google一下word cloud algorithm詞云算法, 這里有介紹
https://stackoverflow.com/questions/342687/algorithm-to-implement-a-word-cloud-like-wordle
主要思想是:

  1. 先放權(quán)重大的詞
  2. 放好后就不動(dòng)
  3. 放置時(shí)如果跟已放好的詞重疊了, 就按照螺旋線向外移動(dòng)到下一個(gè)位置

簡(jiǎn)單實(shí)現(xiàn)

使用自定義ViewGroup實(shí)現(xiàn), 每個(gè)關(guān)鍵詞用一個(gè)TextView表示, 放置到ViewGroup中, 這樣的好處是方便處理單個(gè)詞的樣式和事件. 主要就是重寫onLayout()方法

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int n = getChildCount();
        for(int i = 0; i < n; i++) {
            View v = getChildAt(i);
            if(placed.contains(v)) {
                continue;
            }
            int w = v.getMeasuredWidth();
            int h = v.getMeasuredHeight();

            int pivotX = getWidth() / 3 + random.nextInt(getWidth() / 3);
            int pivotY = getHeight() / 3 + random.nextInt(getHeight() / 3);

            List<Point> spiral = generateSpiral();
            for(Point p : spiral) {
                pivotX += p.x;
                pivotY += p.y;

                Log.d("chao", "place " + pivotX + "," + pivotY);
                Rect r1 = getVisualRect(pivotX, pivotY, w, h, v.getRotation());
                boolean isOverlap = false;
                for(View pv : placed) {
                    Rect r2 = getVisualRect(pv);
                    if(isOverlap(r1, r2)) {
                        isOverlap = true;
                        break;
                    }
                }
                if(isOverlap) {

                } else {
                    Log.d("chao", "placed");
                    Rect r = getRect(pivotX, pivotY, w, h);
                    v.layout(r.left, r.top, r.right, r.bottom);
                    break;
                }
            }
            placed.add(v);
        }
    }

為了同時(shí)有水平擺放和垂直擺放, 添加TextView時(shí)隨機(jī)給90度或270度的旋轉(zhuǎn). 注意雖然TextView旋轉(zhuǎn)了, 但是它的位置屬性還是旋轉(zhuǎn)前的, 因此在檢測(cè)重疊前要轉(zhuǎn)換成旋轉(zhuǎn)后的.

    float[] rotates = {
            0f,90f,270f
    };

    public void addTextView(String word, int weight) {
        TextView tv = new TextView(getContext());
        tv.setText(word);
        tv.setTextSize(weight);
        tv.setRotation(rotates[random.nextInt(rotates.length)]);
        tv.setOnClickListener(this);
        addView(tv, params);
    }

檢測(cè)兩個(gè)矩形是否重疊的算法

    public static boolean isOverlap(Rect r1, Rect r2) {
        return r1.right >= r2.left && r2.right >= r1.left
                && r1.bottom >= r2.top && r2.bottom >= r1.top;
    }
Screenshot_20190215-174230.png

這樣就實(shí)現(xiàn)了, 看一下效果不是很好, 這是因?yàn)橛玫氖?直角螺旋線算法", 即按照左上右下的順序移動(dòng)坐標(biāo), 距離逐漸增大. 要實(shí)現(xiàn)放置成圓形的效果, 需要加入真正的螺旋線算法.

一般數(shù)學(xué)中沒講過螺旋線, 網(wǎng)上找一下, 寫一個(gè)生成螺旋線點(diǎn)的算法, 用自定義View驗(yàn)證

public class SpiralView extends View {

    Paint paint;
    List<Point> points;

    public SpiralView(Context context) {
        this(context, null);
    }

    public SpiralView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SpiralView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);

        points = generateSpiral();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int cx = getWidth() / 2;
        int cy = getHeight() / 2;
        for(Point p : points) {
            canvas.drawCircle(cx + p.x, cy + p.y, 5, paint);
        }
    }

    private List<Point> generateSpiral() {
        List<Point> res = new ArrayList<>();
        int A = 10;
        int w = 1;
        double sita = Math.PI;
        for(double t = 0; t < 10 * Math.PI; t+=0.1) {
            int x = Double.valueOf(A * Math.cos(w * t + sita)).intValue();
            int y = Double.valueOf(A * Math.sin(w * t + sita)).intValue();
            A += 1;
            res.add(new Point(x, y));
            Log.e("chao", x + ", " + y);
        }
        return res;
    }
}

在把這個(gè)算法加入到onLayout()方法中就大功告成了

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int n = getChildCount();
        for(int i = 0; i < n; i++) {
            View v = getChildAt(i);
            if(placed.contains(v)) {
                continue;
            }
            int w = v.getMeasuredWidth();
            int h = v.getMeasuredHeight();

            int pivotX = getWidth() / 3 + random.nextInt(getWidth() / 3);
            int pivotY = getHeight() / 3 + random.nextInt(getHeight() / 3);

            List<Point> spiral = generateSpiral();
            for(Point p : spiral) {
                pivotX += p.x;
                pivotY += p.y;

                Log.d("chao", "place " + pivotX + "," + pivotY);
                Rect r1 = getVisualRect(pivotX, pivotY, w, h, v.getRotation());
                boolean isOverlap = false;
                for(View pv : placed) {
                    Rect r2 = getVisualRect(pv);
                    if(isOverlap(r1, r2)) {
                        isOverlap = true;
                        break;
                    }
                }
                if(isOverlap) {

                } else {
                    Log.d("chao", "placed");
                    Rect r = getRect(pivotX, pivotY, w, h);
                    v.layout(r.left, r.top, r.right, r.bottom);
                    break;
                }
            }
            placed.add(v);
        }
    }
Screenshot_20190218-104341.png

Github地址

https://github.com/rome753/WordCloudView

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裙品,一起剝皮案震驚了整個(gè)濱河市掏愁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谅年,老刑警劉巖闯传,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件经宏,死亡現(xiàn)場(chǎng)離奇詭異桥状,居然都是意外死亡茂缚,警方通過查閱死者的電腦和手機(jī)辟汰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阱佛,“玉大人,你說我怎么就攤上這事戴而〈帐酰” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵所意,是天一觀的道長(zhǎng)淮逊。 經(jīng)常有香客問我,道長(zhǎng)扶踊,這世上最難降的妖魔是什么泄鹏? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮秧耗,結(jié)果婚禮上备籽,老公的妹妹穿的比我還像新娘。我一直安慰自己分井,他們只是感情好车猬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尺锚,像睡著了一般珠闰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘫辩,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天伏嗜,我揣著相機(jī)與錄音坛悉,去河邊找鬼。 笑死承绸,一個(gè)胖子當(dāng)著我的面吹牛裸影,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播八酒,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼空民,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了羞迷?” 一聲冷哼從身側(cè)響起界轩,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衔瓮,沒想到半個(gè)月后浊猾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡热鞍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年葫慎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薇宠。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偷办,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澄港,到底是詐尸還是另有隱情椒涯,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布回梧,位于F島的核電站废岂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狱意。R本人自食惡果不足惜湖苞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望详囤。 院中可真熱鬧财骨,春花似錦、人聲如沸藏姐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽包各。三九已至摘仅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間问畅,已是汗流浹背娃属。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工六荒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矾端。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓掏击,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親秩铆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砚亭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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