LoginSignup
2
1

More than 5 years have passed since last update.

はてなのホットエントリをTreeMapで表示する。

Posted at

Qiita初投稿です。

とりあえず、何か投稿したいと思い、昔作った、はてなブックマークをTreeMapアルゴリズムで表示するWebアプリをリファインしてみました。

データのTreeMap表示は「ビジュアライジングデータ」で紹介されていた手法で、アルゴリズムはTreemaps for space-constrained visualization of hierarchiesで詳しく解説されています。

Google Feed APIで取得したはてなブックマークのホットエントリを元にTreeMapデータを作成し、Processing.jsで描画しています。

一応、JSBinにもソースコードを登録しました。

hatena_treemap.html
<html lang="jp" class="ui-mobile landscape">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=8"/>
<base href=".">
<title>Hatemap</title>
<style type="text/css">
#toptitle{
    width:600px;
    margin: 0 auto;
    font-family: Georgia, Times, serif;
    font-size: 24px;
    font-weight: bold;
    color: #ffffff;
    background-color: #cccccc;
    vertical-align : bottom;
    line-height: 38px;
}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/processing.js/1.4.8/processing.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
var scwidth;
var scheight;
var proc;
var root;
var tmp=[];
var urls=[
    ['http://feeds.feedburner.com/hatena/b/hotentry'],
    ['http://b.hatena.ne.jp/hotentry/social.rss',
        'http://b.hatena.ne.jp/hotentry/economics.rss',
        'http://b.hatena.ne.jp/hotentry/life.rss',
        'http://b.hatena.ne.jp/hotentry/entertainment.rss',
        'http://b.hatena.ne.jp/hotentry/knowledge.rss',
        'http://b.hatena.ne.jp/hotentry/it.rss',
        'http://b.hatena.ne.jp/hotentry/game.rss',
        'http://b.hatena.ne.jp/hotentry/fun.rss'],
    ['http://b.hatena.ne.jp/entrylist?sort=hot&threshold=&mode=rss&']
];

google.load("feeds", "1");

$(document).ready(function(){
    scwidth=600;
    scheight=600;
    $("#canvas").attr("width",scwidth);
    $("#canvas").attr("height",scheight);
    initLayer();
    $("#layer").css("font-size","16px");
    $("#layer").css("padding","10px 0px 0px 10px");
    var canvas = document.getElementById('canvas');
    var codeElm = document.getElementById('processing-code');
    var code = codeElm.textContent || codeElm.innerText;
    proc=new Processing(canvas, code);
    var list=[];
    for(var i=0;i<urls[2].length;i++)list[i]=urls[2][i];
    hatebu_Init(list);
});

var initLayer=function(){
    var off=$("#canvas").offset();
    var ox=off.left;
    var oy=off.top+20;  
    $("#layer").css("z-index","0");
    $("#layer").css("position","absolute");
    $("#layer").css("top",oy+10+"px");
    $("#layer").css("line-height","20px");
    $("#layer").css("left",(ox+20)+"px");
    $("#layer").css("width",(scwidth-40)+"px");
    var mm=Math.max(300,(scheight*0.45));
    $("#layer").css("height",mm+"px");
    $("#layer").css("background-color","#ffffff");
    $("#layer").css("opacity","0.9");
    $("#layer").css("display","none");
};

var hatebu_Init=function(list){
    page=0;
    numOfBookmark=0;
    tmp=[];
    root={name:"TOP",size:0};
    root.array=[];
    var now=new Date();
    var q=now.getMonth();
    var q2=now.getDate();
    var q3=now.getHours();
    hatebu_Analyze(list,q+"0"+q2+"0"+q3);
};

var hatebu_Analyze=function(list,dummy){
    startLoading();
    var url=list.shift();
    if(dummy)url=url+"?"+dummy;
    var feed = new google.feeds.Feed(url);
    feed.setNumEntries(100);
    feed.setResultFormat(google.feeds.Feed.MIXED_FORMAT);   
    feed.load(function(result) {
        if (!result.error) {
            var entries = result.feed.entries;
            if(entries.length>0){
                for (var i = 0; i < entries.length; i++) {
                    var obj=new Object();
                    obj.name=entries[i].title;
                    obj.link=entries[i].link;
                    obj.content=entries[i].content;
                    var category=$(entries[i].xmlNode.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/","subject")[0]).text();
                    if(!tmp[category]){
                        tmp[category]={name:category};
                        tmp[category].size=0;
                        tmp[category].array=[];
                        root.array.push(tmp[category]);
                    }
                    obj.size=parseInt($(entries[i].xmlNode.getElementsByTagNameNS("http://www.hatena.ne.jp/info/xmlns#","bookmarkcount")[0]).text());
                    tmp[category].array.push(obj);
                }
            }
        }else{
            alert("エントリが見つかりませんでした。");
            return;
        }
        if(list.length>0){
            hatebu_Analyze(list,dummy);
        }else{
            proc.setRoot(root);
        }
    });
};

var startLoading=function(){
    closeLayer();
    proc.loading();
};

var showLayer=function(html,link){
    initLayer();
    var code=html.replace("blockquote","div");
    setTimeout(function(){
        $("#layer").css("display","inline");
        $("#layer").html(code);

        var msg=$("#layer div").text();
        var aa=$("#layer div p a").text();
        msg=msg.replace(aa,"");
        msg=msg.replace("続きを読む","");
        msg=replaceAll(msg,"\n","");
        msg=replaceAll(msg," ","");
        $("#layer div cite a").attr("target","_blank");
        var name=$("#layer div p a img:first").attr("src");
        if(name&&name.indexOf("http://b.hatena.ne.jp/")==-1)
            $("#layer div p a img:first").attr("height","90px");
        $("#layer div p a").attr("target","_blank");
        if($("#layer div p").length>=4){
            $("#layer div p:last").remove();
        }
    },500);
};

var closeLayer=function(){
    $("#layer").css("display","none");
    $("#layer").empty();
};

var arraycopy=function(src,srcPos,dest,destPos,length){
    while (length > 0){
     dest[destPos] = src[srcPos];
     srcPos++;
     destPos++;
     length--;
    }
};

var replaceAll=function(expression, org, dest){
    return expression.split(org).join(dest);
};
</script>

<script id="processing-code" type="application/processing">
BoundsIntegrator zoomBounds;
GroupItem rootItem;
LeafItem rolloverItem;
GroupItem taggedItem;
LeafItem zoomItem;
final int PIVOT_BY_MIDDLE = 1;
final int PIVOT_BY_SPLIT_SIZE = 2;
final int PIVOT_BY_BIGGEST = 3;
boolean isLoading=false;
float ATTRACTION = 0.8f;
float DAMPING = 0.4f;
int alphaL=25;

void setup() {
    size(scwidth,scheight);
    PFont font=createFont("FFScala", 24);
    textFont(font);
    textSize(16);
    zoomBounds = new BoundsIntegrator(0, 0, width, height);
    rectMode(CORNERS);
    smooth();
    noStroke();
    textMode(SCREEN);
}

void setRoot(array) {
    closeLayer();
    GroupItem tm = new GroupItem(null, array, 0, 0);
    tm.setBounds(0, 0, width, height);
    tm.contentsVisible = true;
    rootItem = tm;
    rootItem.zoomIn();
    rootItem.updateColors();
    isLoading=false;
}

void loading(){
    isLoading=true;
}

void drawLoading(){
    background(0);
    frameRate(10);
    noStroke();
    int r=5;
    int rr=10;
    int sx=scwidth/2-rr*2;
    int sy=scheight/2-rr*2;
    fill(126,126,126,alphaL);alphaL=(alphaL+10)%256;
    ellipse(sx+3*rr, sy+0*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+5*rr, sy+1*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+6*rr, sy+3*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+5*rr, sy+5*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+3*rr, sy+6*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+1*rr, sy+5*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+0*rr, sy+3*rr, 2*r, 2*r);
    fill(126,126,126,alphaL);alphaL=(alphaL+25)%256;
    ellipse(sx+1*rr, sy+1*rr, 2*r, 2*r);
}

void draw() {
    if(isLoading){
        drawLoading();
        return;
    }
    background(0);
    frameRate(30);
    zoomBounds.update();
    rolloverItem = null;
    taggedItem = null;
    if (rootItem != null) rootItem.draw();
    if (rolloverItem != null) rolloverItem.drawTitleBar();
    if (taggedItem != null) taggedItem.drawTag();
}

void mousePressed() {
    if (zoomItem != null) {
        zoomItem.mousePressed();
    }
}

void zoomOut(){
    if (zoomItem != null) {
        mouseButton=RIGHT;
        zoomItem.mousePressed();
    }
}

class BoundsIntegrator {
    float valueX, velocityX, accelerationX;
    float valueY, velocityY, accelerationY;
    float valueW, velocityW, accelerationW;
    float valueH, velocityH, accelerationH;
    float damping;
    float attraction;
    boolean targeting;
    float targetX, targetY, targetW, targetH;

    BoundsIntegrator(float x, float y, float w, float h) {
        this.valueX = x;
        this.valueY = y;
        this.valueW = w;
        this.valueH = h;
        this.damping = DAMPING;
        this.attraction = ATTRACTION;
    }

    void set(float x, float y, float w, float h) {
        this.valueX = x;
        this.valueY = y;
        this.valueW = w;
        this.valueH = h;
    }

    float getX() {
        return valueX;
    }

    float getY() {
        return valueY;
    }

    float getW() {
        return valueW;
    }

    float getH() {
        return valueH;
    }

    float spanX(float pointX, float start, float span) {
        if (valueW != 0) {
            float n = (pointX - valueX) / valueW;
            return start + n*span;
        } else {
            return Float.NaN;
        }
    }

    float spanY(float pointY, float start, float span) {
        if (valueH != 0) {
            float n = (pointY - valueY) / valueH;
            return start + n*span;
        } else {
            return Float.NaN;
        }
    }

    void setAttraction(float a) {
        attraction = a;
    }

    void setDamping(float d) {
        damping = d;
    }

    boolean update() {
        if (targeting) {
            accelerationX += attraction * (targetX - valueX);
            velocityX = (velocityX + accelerationX) * damping;
            valueX += velocityX;
            accelerationX = 0;
            boolean updated = (Math.abs(velocityX) > 0.0001f);
            accelerationY += attraction * (targetY - valueY);
            velocityY = (velocityY + accelerationY) * damping;
            valueY += velocityY;
            accelerationY = 0;
            updated |= (Math.abs(velocityY) > 0.0001f);
            accelerationW += attraction * (targetW - valueW);
            velocityW = (velocityW + accelerationW) * damping;
            valueW += velocityW;
            accelerationW = 0;
            updated |= (Math.abs(velocityW) > 0.0001f);
            accelerationH += attraction * (targetH - valueH);
            velocityH = (velocityH + accelerationH) * damping;
            valueH += velocityH;
            accelerationH = 0;
            updated |= (Math.abs(velocityH) > 0.0001f);
        }
        return false;
    }

    void target(float tx, float ty, float tw, float th) {
        targeting = true;
        targetX = tx;
        targetY = ty; 
        targetW = tw;
        targetH = th;
    }

    void targetLocation(float tx, float ty) {
        targeting = true;
        targetX = tx;
        targetY = ty;
    }

    void targetSize(float tw, float th) {
        targeting = true;
        targetW = tw;
        targetH = th;
    }

    void targetX(float tx) {
        targeting = true;
        targetX = tx;
    }

    void targetY(float ty) {
        targeting = true;
        targetY = ty;
    }

    void targetW(float tw) {
        targeting = true;
        targetW = tw;
    }

    void targetH(float th) {
        targeting = true;
        targetH = th;
    }
}

class LeafItem extends SimpleMapItem {
    GroupItem parent;    
    Object obj;
    String name;
    int level;
    color c;
    float hue;
    float brightness;
    float textPadding = 8;
    float boxLeft, boxTop;
    float boxRight, boxBottom;

    LeafItem(GroupItem parent, Object obj, int level, int order) {
        this.parent = parent;
        this.obj = obj;
        this.order = order;
        this.level = level;
        name=obj.name;
        if(name.length>30)name=name.substring(0,30)+"...";
        size = obj.size;
    }
    void updateColors() {
        if (parent != null) {
            hue = map(order, 0, parent.getItemCount(), 0, 360);
        }
        brightness = Math.min(100.0/((float)level*0.8),100.0);      
        colorMode(HSB, 360, 100, 100);
        if (parent == zoomItem) {
            c = color(hue, 60, 60);
        } else if (parent != null) {
            color pc = color(parent.hue, 60, 60);
            c = color(hue, brightness, brightness);
            colorMode(RGB, 255);
            c=blendColor(c,pc,SCREEN)
        }
        colorMode(RGB, 255);
    }

    void calcBox() {
        boxLeft = zoomBounds.spanX(x, 0, width);
        boxRight = zoomBounds.spanX(x+w, 0, width);
        boxTop = zoomBounds.spanY(y, 0, height);
        boxBottom = zoomBounds.spanY(y+h, 0, height);
    }

    void draw() {
        calcBox();
        if(mouseInside()){
            fill(c,255);
        }else{
            fill(c,200);
        }
//      fill(c);
        rect(boxLeft, boxTop, boxRight, boxBottom);
        stroke(128);
        rect(boxLeft, boxTop, boxRight, boxBottom);
        if (textFits()) {
            drawTitle();
            drawTitle3();
        }else if(textFits2()){
            drawTitle2(14);
            drawTitle3();
        } else if (mouseInside()) {
            rolloverItem = this;
        }else{
            textSize(12);
            if(textFits2()){
                drawTitle2(12);     
            }else{
                textSize(10);
                if(textFits2())drawTitle2(10)
            }
            drawTitle3();
        }
    }

    void drawTitleBar(){
        if (textFits()) {
            drawTitle();
            drawTitle3();
        }else if (textFits3()) {
            drawTitle();
            drawTitle3();   
        }else if(textFits2()){
            drawTitle2(14);
            drawTitle3();
        }else{
            textSize(12);
            if(textFits2()){
                drawTitle2(12);     
            }else{
                textSize(10);
                drawTitle2(10)
            }
            drawTitle3();
        }
        textSize(16);
    }

    void drawTitle() {
        fill(255, 200);
        textAlign(LEFT);
        text(name, boxLeft + textPadding, boxBottom - textPadding);
    }

    void drawTitle2(int n) {
        fill(255, 200);
        textSize(n);
        float ascent = textAscent();
        text(name,boxLeft + textPadding, boxBottom -ascent - textPadding*2,boxRight-boxLeft,ascent + textPadding*2);
        textSize(16);
    }
    void drawTitle3() {
        if(zoomItem!=this){
            fill(255, 200);
            float middleX = (boxLeft + boxRight) / 2;
            float middleY = (boxTop + boxBottom) / 2;
            String tp=this.size;
            int ww=(boxRight-boxLeft)/2;
            ww=(int)Math.min(ww,(boxBottom-boxTop)/2);
            if(ww>=192){
                textSize(128);
            }else if(ww>=96){
                textSize(64);
            }else if(ww>=48){
                textSize(32);
            }else if(ww>=24){
                textSize(16);
            }else if(ww>=18){
                textSize(12);   
            }else if(ww>=12){
                textSize(8);            
            }else{
                return ;
            }
            float ascent = textAscent();
            text(tp, boxLeft+5,boxTop+ascent+5);
            textSize(16);
        }
    }

    boolean textFits() {
        int s=64;
        while(s>16){
            textSize(s);
            if(checkTextFits(name)){
                return true;
            }else{
                s *=0.75;
            }
        }
        return false;
    }

    boolean checkTextFits(String n){
        float wide = textWidth(n) + textPadding*2;
        float high = textAscent() + textDescent() + textPadding*2;
        return (boxRight - boxLeft > wide) && (boxBottom - boxTop > high);
    }

    boolean textFits2() {
        int l=name.length()/2;
        if(l%2!=0)l++;
        String n2=name.substring(0,l);
        float wide = textWidth(n2) + textPadding*4;
        float high = textAscent()*3 + textDescent()*3 + textPadding*6;
        return (boxRight - boxLeft > wide) && (boxBottom - boxTop > high); 
    }

    boolean textFits3() {
        float wide = textWidth(name) + textPadding*2;
        return (boxRight - boxLeft > wide); 
    }

    boolean mouseInside() {
        return (mouseX > boxLeft && mouseX < boxRight && 
            mouseY > boxTop && mouseY < boxBottom);    
    }

    boolean mousePressed() {
        if (mouseInside()) {
            if (mouseButton == LEFT) {
                if(parent.contentsVisible){
                    if(parent.isZoom){
                        if(zoomItem!=this){
                            zoomItem = this;
                            zoomBounds.target(x, y, w, h);
                            showLayer(obj.content,obj.link);
                        }else{
                            showLayer(obj.content,obj.link);
                        }
                    }else{
                        parent.zoomIn();
                    }
                }else{
                    parent.zoomIn();
                }
                return true;
            } else if (mouseButton == RIGHT) {
                if(zoomItem == this){
                    zoomItem = parent;
                    parent.zoomIn();
                }else if (parent == zoomItem) {
                    parent.zoomOut();
                } else {
                    parent.hideContents();
                }
                closeLayer();
                return true;
            }
        }
        return false;
    }
}

class GroupItem extends LeafItem implements MapModel {
    MapLayout algorithm = new SquarifiedLayout();
    Mappable[] items;
    boolean contentsVisible;
    boolean layoutValid;
    float darkness;
    boolean isZoom=false;
    GroupItem(GroupItem parent, Object folder, int level, int order) {
        super(parent, folder, level, order);
        ArrayList contents = folder.array;
        if (contents) {
            contents = sort(contents);
            items = new Mappable[contents.length];
            int count = 0;
            for (int i = 0; i < contents.length; i++) {
                if (contents[i].array) {
                    GroupItem newItem = new GroupItem(this, contents[i], level+1, count);
                    items[count++] = newItem;
                    size += newItem.getSize();
                } else {
                    LeafItem newItem = new LeafItem(this, contents[i], level+1, count);
                    items[count++] = newItem;
                    size += newItem.getSize();
                }
            }
            if (count != items.length) {
                items = (Mappable[]) subset(items, 0, count);
            }
        } else {
            items = new Mappable[0];
        }
    }

    void updateColors() {
        super.updateColors();
        for (int i = 0; i < items.length; i++) {
            LeafItem fi = (LeafItem) items[i];
            fi.updateColors();
        }
    }

    void checkLayout() {
        if (!layoutValid) {
            if (getItemCount() != 0) {
                algorithm.layout(this, bounds);
            }
            layoutValid = true;
        }
    }

    boolean mousePressed() {
        if (mouseInside()) {
            if (contentsVisible) {
                for (int i = 0; i < items.length; i++) {
                    LeafItem fi = (LeafItem) items[i];
                    if (fi.mousePressed()) {
                        return true;
                    }
                }
            } else {
                if (mouseButton == LEFT) {
                    if (parent == zoomItem) {
                        showContents();
                    } else {
                        parent.zoomIn();
                    }            
                } else if (mouseButton == RIGHT) {
                    if (parent == zoomItem) {
                        parent.zoomOut();
                    } else {
                        parent.hideContents();
                    }
                }
                return true;
            }
        }
        return false;
    }

    void zoomOut() {
        if (parent != null) {
            for (int i = 0; i < items.length; i++) {
                if (items[i] instanceof GroupItem) {
                    ((GroupItem)items[i]).hideContents();
                }
            }
            parent.zoomIn();
        }
        isZoom=false;
    }

    void zoomIn() {
        zoomItem = this;
        zoomBounds.target(x, y, w, h);
        isZoom=true;
    }

    void showContents() {
        contentsVisible = true;
    }

    void hideContents() {
        if (parent != null)contentsVisible = false;
    }

    void draw() {
        checkLayout();
        calcBox();
        if (contentsVisible) {
            for (int i = 0; i < items.length; i++) {
                items[i].draw();
            }
        } else {
            super.draw();
        }
        if (contentsVisible) {
            if (mouseInside()) {
                if (parent == zoomItem) {
                    taggedItem = this;
                }
            }
        }
        if (mouseInside()) {
            darkness *= 0.05;
        } else {
            darkness += (150 - darkness) * 0.05;
        }
        darkness=Math.min(darkness,80);
        if (parent == zoomItem) {
            colorMode(RGB, 255);
            fill(0, darkness);
            rect(boxLeft, boxTop, boxRight, boxBottom);
        }
    }

    void drawTitle() {
        if (!contentsVisible) super.drawTitle();
    }

    void drawTag() {
        float boxHeight = textAscent() + textPadding*2;
        if (boxBottom - boxTop > boxHeight*2) {
            fill(0, 128);
            rect(boxLeft, boxTop, boxRight, boxTop+boxHeight);
            fill(255);
            textAlign(LEFT, TOP);
            text(name, boxLeft+textPadding, boxTop+textPadding);
        } else if (boxTop > boxHeight) {
            fill(0, 128);
            rect(boxLeft, boxTop-boxHeight, boxRight, boxTop);
            fill(255);
            text(name, boxLeft+textPadding, boxTop-textPadding);
        } else if (boxBottom + boxHeight < height) {
            fill(0, 128);
            rect(boxLeft, boxBottom, boxRight, boxBottom+boxHeight);
            fill(255);
            textAlign(LEFT, TOP);
            text(name, boxLeft+textPadding, boxBottom+textPadding);
        }
    }

    Mappable[] getItems() {
        return items;
    }

    int getItemCount() {
        return items.length;
    }
}

class Rect{
    double x,y,w,h;

    Rect(){
        this(0,0,1,1);
    }

    Rect(Rect r){
        setRect(r.x, r.y, r.w, r.h);
    }

    Rect(double x, double y, double w, double h){
        setRect(x, y, w, h);
    }

    void setRect(double x, double y, double w, double h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    double aspectRatio(){
        return Math.max(w/h, h/w);
    }

    double distance(Rect r){
        return Math.sqrt((r.x-x)*(r.x-x)+
                    (r.y-y)*(r.y-y)+
                    (r.w-w)*(r.w-w)+
                    (r.h-h)*(r.h-h));
    }

    Rect copy(){
        return new Rect(x,y,w,h);
    }

    String toString(){
        return "Rect: "+x+", "+y+", "+w+", "+h;
    }
}
interface MapModel{
    Mappable[] getItems();
}

interface MapLayout{
    void layout(MapModel model, Rect bounds);
    void layout(MapModel model, double x, double y, double w, double h);
}

interface Mappable {
    double getSize();
    void setSize(double size);
    Rect getBounds();
    void setBounds(Rect bounds);
    void setBounds(double x, double y, double w, double h);
    int getOrder();
    void setOrder(int order);
    int getDepth();
    void setDepth(int depth);
    void draw();
}

class SimpleMapItem implements Mappable {
    double size;
    Rect bounds;
    int order = 0;
    int depth;
    float x, y, w, h;

    SimpleMapItem() {
        this(1, 0);
    }

    SimpleMapItem(double size, int order) {
        this.size = size;
        this.order = order;
        bounds = new Rect();
    }

    double getSize() {
        return size;
    }

    void setSize(double size) {
        this.size = size;
    }

    void incrementSize() {
        size++;
    }

    Rect getBounds() {
        return bounds;
    }

    void setBounds(Rect bounds) {
        this.bounds = bounds;
        x = (float) bounds.x;
        y = (float) bounds.y;
        w = (float) bounds.w;
        h = (float) bounds.h;
    }

    void setBounds(double bx, double by, double bw, double bh) {
        setBounds(new Rect(bx, by, bw, bh));
    }

    int getOrder() {
        return order;
    }

    void setOrder(int order) {
        this.order = order;
    }

    void setDepth(int depth) {
        this.depth = depth;
    }

    int getDepth() {
        return depth;
    }

    void draw() {}
}

class SquarifiedLayout implements MapLayout{
    int VERTICAL=0, HORIZONTAL=1;
    int ASCENDING=0, DESCENDING=1;

    void layout(MapModel model, Rect bounds){
        layout(model.getItems(),bounds);
    }
    void layout(MapModel model, double x, double y, double w, double h){
        layout(model, new Rect(x, y, w, h));
    }

    void layout(Mappable[] items, Rect bounds){
        if(!items.length)items=items.getItems();
        layout(sortDescending(items),0,items.length-1,bounds);
    }

    void layout(Mappable[] items, int start, int end, Rect bounds){
        if (start>end) return;
        if (end-start<2){
            layoutBest(items,start,end,bounds);
            return;
        }
        double x=bounds.x, y=bounds.y, w=bounds.w, h=bounds.h;
        double total=sum(items, start, end);
        int mid=start;
        double a=items[start].getSize()/total;
        double b=a;
        if (w<h){
            while (mid<=end){
                double aspect=normAspect(h,w,a,b);
                double q=items[mid].getSize()/total;
                if (normAspect(h,w,a,b+q)>aspect) break;
                mid++;
                b+=q;
            }
            layoutBest(items,start,mid,new Rect(x,y,w,h*b));
            layout(items,mid+1,end,new Rect(x,y+h*b,w,h*(1-b)));
        }else{
            while (mid<=end){
                double aspect=normAspect(w,h,a,b);
                double q=items[mid].getSize()/total;
                if (normAspect(w,h,a,b+q)>aspect) break;
                mid++;
                b+=q;
            }
            layoutBest(items,start,mid,new Rect(x,y,w*b,h));
            layout(items,mid+1,end,new Rect(x+w*b,y,w*(1-b),h));
        }
    }

    void layoutBest(Mappable[] items, int start, int end, Rect bounds){
        sliceLayout(items,start,end,bounds,
            bounds.w>bounds.h ? HORIZONTAL : VERTICAL, ASCENDING);
    }

    void layoutBest(Mappable[] items, int start, int end, Rect bounds, int order){
        sliceLayout(items,start,end,bounds,
            bounds.w>bounds.h ? HORIZONTAL : VERTICAL, order);
    }

    double aspect(double big, double small, double a, double b){
        return (big*b)/(small*a/b);
    }

    double normAspect(double big, double small, double a, double b){
        double x=aspect(big,small,a,b);
        if (x<1) return 1/x;
        return x;
    }

    double sum(Mappable[] items, int start, int end){
        double s=0;
        for (int i=start; i<=end; i++)
            s+=items[i].getSize();
        return s;
    }

    String getName(){
        return "Squarified";
    }

    public String getDescription(){
        return "Algorithm used by J.J. van Wijk.";
    }

    double totalSize(Mappable[] items){
        return totalSize(items,0,items.length-1);
    }

    double totalSize(Mappable[] items, int start, int end){
        double sum=0;
        for (int i=start; i<=end; i++)
            sum+=items[i].getSize();
        return sum;
    }

    Mappable[] sortDescending(Mappable[] items){
        Mappable[] s=new Mappable[items.length];
        arraycopy(items,0,s,0,items.length);
        int n=s.length;
        boolean outOfOrder=true;
        while (outOfOrder){
            outOfOrder=false;
            for (int i=0; i<n-1; i++){
                boolean wrong=(s[i].getSize()<s[i+1].getSize());
                if (wrong){
                    Mappable temp=s[i];
                    s[i]=s[i+1];
                    s[i+1]=temp;
                    outOfOrder=true;
                }
            }
        }
        return s;
    }

    void sliceLayout(Mappable[] items, int start, int end, Rect bounds, int orientation){
        sliceLayout(items,start,end,bounds,orientation,ASCENDING);
    }

    void sliceLayout(Mappable[] items, int start, int end, Rect bounds, int orientation, int order){
        double total=totalSize(items, start, end);
        double a=0;
        boolean vertical=orientation==VERTICAL;
        for (int i=start; i<=end; i++){
            Rect r=new Rect();
            double b=items[i].getSize()/total;
            if (vertical){
                r.x=bounds.x;
                r.w=bounds.w;
                if (order==ASCENDING){
                    r.y=bounds.y+bounds.h*a;
                }else{
                    r.y=bounds.y+bounds.h*(1-a-b);
                }
                r.h=bounds.h*b;
            }else{
                if (order==ASCENDING){
                    r.x=bounds.x+bounds.w*a;
                }else{
                    r.x=bounds.x+bounds.w*(1-a-b);
                }
                r.w=bounds.w*b;
                r.y=bounds.y;
                r.h=bounds.h;
            }
            items[i].setBounds(r);
            a+=b;
        }
    }
}
</script>
</head>
<body>
    <div id="toptitle"> はてなブックマーク TreeMap</div><br />
    <div><center><canvas id="canvas"></canvas></center></div>
    <div id="layer"></div>
</body>
</html>
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1