3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

D3.jsのWord CloudにAjaxを実装する on Django

概要

クリックすると変化するWord Cloudを作った↓
ajax_word_cloud.gif

こちらのWebサービスで実際に触れます
【Where to Visit ?】

上記のWebサービスについては以下記事参照↓
GAE×DjangoでWebサービスを開発してみた

使った技術

  • D3.js
    • d3-cloudを利用して、WordCloudを作成した
  • Ajax
    • ページ遷移なしにWordCloudを変化させる
  • Python3, Django
    • バックエンド

説明

python, Django

DjangoをWebサービスのバックエンドとして利用している。
Ajaxも、基本的にはViewと同じで、word cloudのデータの受け渡しをする。
AjaxはJSONで送るので、datetime型はstrにして渡すなど、取扱に注意が必要。

# view用のメッソド
def photo(request):
    # 省略
    # ...
    wordcloud_list = [
        {
            "word": "日本",  # 表示される文字
            "property": "country",  # 種別
            "count": 10,  # prefecture=word
        },
        {
            "word": "日本",  
            "property": "country",
            "count": 10,  
        },
        #...
    ]
    output = {
        "wordcloud_list": wordcloud_list,
    }
    return TemplateResponse(request, "ra/photo.html", output)

# ajax用のメソッド
def ajax(request):
    if request.method == "GET":
        # 省略
        # datetimeはstrにして渡す
        # ...
        # response in JSON
        res_data = {
            "wordcloud_list": wordcloud_list,
        }
        response = json.dumps(res_data)  # JSON形式に直して・・
        return HttpResponse(response, content_type="text/javascript")  
    else:
        raise Http404

HTML・javascript

view関数やajax用関数から受け取ったデータをもとに、D3.jsでWordCloudを描写する。
各WordはonclickをトリガーとしてWordCloudを描写する関数を再帰関数的に呼び出すようにしている。

<!-- 該当部分以外省略しています -->
<head>
  <!-- Spinner -->
  <link rel="stylesheet" href="{% static 'css/loading.css' %}">
</head>

<!--Spinner-->
<div id="overlay" class="hide">
  <div class="cv-spinner">
    <span class="spinner"></span>
  </div>
</div>

<!--WordCloud-->
<div class="row">
  <div class="col-12">
    <div class="collapse show" id="collapseExample">
      <div class="card">
        <div id="wordcloud"></div>
      </div>
    </div>
  </div>
</div>

<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
<!-- 以下URL参照 -->
<!-- https://github.com/jasondavies/d3-cloud-->
<script src="{% static 'js/d3.layout.cloud.js' %}"></script>
<script>
var TARGET_ELEMENT_ID = '#wordcloud'; // 描画先

// Word Cloudを作成して、id="wordcloud"の要素に描写する関数
function draw_wc(data){
  var random = d3.randomIrwinHall(2); // アーウィンホール分布
  var countMax = d3.max(data, function(d){ return d.count} );
  var sizeScale = d3.scaleLinear().domain([0, countMax]).range([10, 100])
  var words = data.map(function(d) {
    return {
    text: d.word,
    property: d.property,
    size: sizeScale(d.count) //頻出カウントを文字サイズに反映
    };
  });
  d3.layout.cloud().size([w, h])
    .words(words)
    .rotate(function() { return (~~(Math.random() * 6) - 3) * 30; })
    .font("Impact")
    .fontSize(function(d) { return d.size; })
    .on("end", draw) //描画関数の読み込み
    .start();
  return words;

  // wordcloud 描画実行部分
  function draw(words) {
    d3.select(TARGET_ELEMENT_ID)
      .append("svg")
        .attr("class", "ui fluid image") // style using semantic ui
        .attr("viewBox", "0 0 " + w + " " + h )  // ViewBox : x, y, width, height
        .attr("width", "100%")    // 表示サイズの設定
        .attr("height", "100%")   // 表示サイズの設定
      .append("g")
        .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")")
      .selectAll("text")
        .data(words)
      .enter().append("text")
        .style("font-size", function(d) { return d.size + "px"; })
        .style("font-family", "Impact")
        .style("fill", function(d, i) { return d3.schemeCategory10[i % 10]; })
        .attr("text-anchor", "middle")
        .attr("transform", function(d) {
          return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
        })
        .text(function(d) { return d.text; })
        // onclickをトリガーにでAjaxでWord Cloudを更新する
        .on("click", function (d, i){
          // Ajax
          $.ajax({
            url: "{% url 'ra:ajax' %}", //ajaxのurlはDjangoのurls.pyから取得
            method: "GET",
            data: {
              text: d.text,
              prop: d.property,
            },
            timeout: 10000,
            dataType: "json",
            //リクエストが完了するまで実行される
            beforeSend: function(){
              $('#overlay').removeClass('hide');
            }
          })
          .done(function(data_ajax) {
            // 写真・モーダルの更新
            // 省略
            // ...
            // 既存Word Cloudを削除
            d3.select(TARGET_ELEMENT_ID).select('svg').remove();
            // 再帰関数的に呼び出して、再描写する
            draw_wc(data_ajax.wordcloud_list);
            // Spinner終了
            $('#overlay').addClass('hide');
          })
        });
  }
}

// 初回実行はDjangoから受け取ったデータを引数に、関数draw_wcを呼び出し
var data_list = [
  {% for td in wordcloud_list %}
  {
    "word": "{{td.word}}",
    "count": {{td.count}},
    "property": "{{td.property}}",
  },
{% endfor %}
]
words = draw_wc(data_list)
</script>

CSS

Ajax実行中はグルグル回るスピナーを表示させたかったので、CSSもを利用して実装した。
参考:https://pinkmonky.net/detail/?id=100

loading.css
.hide {
  display: none;
}

#overlay{
    position: fixed;
    top: 0;
    margin: 0;
    padding: 0;
    z-index: 100;
    width: 100%;
    height:100%;
    background: rgba(0,0,0,0.6);
}
.cv-spinner {
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}
.spinner {
    width: 40px;
    height: 40px;
    border: 4px #ddd solid;
    border-top: 4px #2e93e6 solid;
    border-radius: 50%;
    animation: sp-anime 0.8s infinite linear;
}
@keyframes sp-anime {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(359deg);
    }
}
.is-hide{
    display:none;
}

困ったこと(解決できていない)

データ数が少ないと正しく表示されない

どうもデータ数が少ないと、一部データが欠落して、全件正しく表示されないようだ。
→暫定対策として、同じデータを重複させることでデータ数を無理やり増やした。。。見栄えは良くない。。。

ちなみに

今回のWebサービスでは、WordCloudだけではなく、写真などの入れ替えもAjaxで実装しています。
そのへんも今後記事にしていきたい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?