【Django入門】Matplotlibで描いたグラフをAjaxでリアルタイムに変更する方法

この記事は約18分で読めます。

Matplotlibで描いたグラフをボタンを押したら
グラフだけが書き換わる方法です。
Ajaxを使うことによりページの一部だけを更新できます。
こちらの記事を参考にプログラムを作りました。
django】Ajaxによる非同期通信:動的にページ更新する方法
この記事では文字表示ですが、それをMatplotlibで描いた図に応用してみました。
効果が分かりやすいように2番目のyの値を±1ボタンで変えられるようにしました。
±1ボタンを押すたびにグラフのみ再描画します。

完成品  Matplotlibアプリ(Ajax版)

この記事の続きになります。

Ajaxとは

非同期という言葉がつきまといますが一旦忘れましょう。
非同期はレスポンスが返ってくる間に他のことをするのが目的なので
今回は関係ないと思います。

Ajaxを使ってみて以下のように私はとらえています。

  • レスポンスで返ってくるものは部分的なデータ
  • データ受信したことをきっかけにhtmlを書き換え(DOM操作)して再表示
  • 画面が部分的に書き換わっているように見えるが実際は
    DOM操作によって書き換わったindex.htmlを全部表示しなおしてる。
  • 書き換える際に白い画面が表示する時間が一瞬なので
    部分的に書き換わっているように見える。
  • 白い画面はプログラムでどうこうできるわけはなく
    プラウザまたはパソコンの標準機能
  • 画面をクリアしなければ(白い画面を出さなければ)残像が残ってしまう。

Matplotlibアプリ(Ajax版)でも、
Windowsの場合F5(再読み込み)すれば白い画面が一瞬出ます。
文字は消えてないように見えるだけで実際は一瞬消えてると思います。

Ajax解説図 2ページあります。>で次ページ。

グラフを更新する方法

【Django入門】データ入力してグラフを描画する方法【Matplotlib】では
以下のようなurlにアクセスすることで描画していました。

<img src="{% url 'mtplt_app:plot' %}" width=600 height=600>

今回も基本的に同じです。
Ajax通信でレスポンスを受け取ったところ(赤)で
青色をDOM操作で書き換えています。

          <div class="result">
            <p>Ajaxあり</p>
            <img src="{% url 'mtaja_app:plot' %}" width=300 height=300>
          </div>

略

            .done(function(response){
                 $('.result').children('img').attr('src', "{% url 'mtaja_app:plot' %}" );
            });

しかし見てわかる通り書き換えているといっても
まったく同じコードに書き換えています。
こうなるとプラウザはindex.htmlに変化がないものとして扱い
キャッシュにある画像を表示してしまい更新されません。

それを避ける方法です。
URLが同じ名前にならないように以下の赤部分のように
「?今の時間」というURLクエリを追加します。
そうすれば毎回違うURLになります。
この部分はurls.pyに行っても無視されるので問題ありません。
プラウザに違うURLの画像ですということを知らせるための仕掛けです。

           .done(function(response){
                 $('.result').children('img').attr('src', "{% url 'mtaja_app:plot' %}"+ '?' + new Date().getTime());
            });

目次へ

コード

コードのみです。
下の記事を参考にしてください。
替えたところ
プロジェクト名 matplotlib_app → matplotlib_ajax2
アプリ名 mtplt_app → mtaja_app
settings.pyのINSTALLED_APPS = [に追加するのは
‘mtaja_app.apps.MtajaAppConfig’,になります。

urls.py

プロジェクト(matplotlib_ajax2)のurls.py

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include("mtaja_app.urls")),
]

アプリ(mtaja_app)のurls.py

from django.urls import path
from . import views

# app_name = 'blog'
app_name = 'mtaja_app'
urlpatterns = [
    path('', views.MatplotlibView.as_view(), name="index"),
    path('plot/', views.get_svg, name='plot'),
    path('ajax-number/', views.ajax_number, name='ajax_number'),
    path('ajax-numberp/', views.ajax_numberp, name='ajax_numberp'),
    path('ajax-numberm/', views.ajax_numberm, name='ajax_numberm'),
]

views.py

from django.shortcuts import render
from django.conf import settings
from django.http import JsonResponse

def index(request):
    return render(request, 'mtaja_app/index.html', {})

def ajax_number(request):

    number1 = request.POST.get('number1')
    number2 = request.POST.get('number2')
    mode = request.POST.get('mode')
    #セッションクリア
    request.session.clear()
    #セッション&クッキー 削除
    request.session.flush()
    request.session['plusbtn']='OFF'
    request.session['minusbtn']='OFF'
    y=number2.split(",")
    request.session['delta']=y[1]
    request.session['maxy']=max(y)
    request.session['miny']=min(y)
    if number1 !="" and number2 !="":

        request.session['xvalue']=number1
        request.session['yvalue']=number2
        request.session['mode']=mode

    d = {
        'y2' : y[1],
    }
    return JsonResponse(d)
    
def ajax_numberp(request): 
    plusbtn = request.POST.get('plusbtn')
    delta=request.session['delta']+1
    if delta >= int(request.session['maxy']):
        delta=request.session['maxy']

    request.session['plusbtn']=plusbtn
    request.session['delta']=delta
    d = {
        'y2' : delta,
    }
    return JsonResponse(d)
def ajax_numberm(request): 
    minusbtn = request.POST.get('minusbtn')
    delta=request.session['delta']-1
    if delta <= int(request.session['miny']):
        delta=request.session['miny']
    request.session['minusbtn']=minusbtn
    request.session['delta']=delta
    d = {
        'y2' : delta,
    }
    return JsonResponse(d)   
    
    
import matplotlib
#バックエンドを指定
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import io
from django.http import HttpResponse

from django.shortcuts import render
#クラスベースのビューを作るため
from django.views import View

#Viewを継承してGET文、POST文の関数を作る
class MatplotlibView(View):

    def get(self, request, *args, **kwargs):
        return render(request,"mtaja_app/index.html")

    def post(self, request, *args, **kwargs):

        return render(request,"mtaja_app/index.html")
        
#グラフ作成
def setPlt(x,y,mode):

    fig, ax = plt.subplots(facecolor='white') 
    if mode =="plot":
        ax.plot(x,y)
        plt.title("X-Y plot")
    elif mode == "bar":
        ax.bar(x,y)
        plt.title("X-Y var")
    plt.xlabel("X")
    plt.ylabel("Y")
# SVG化
def plt2svg():
    buf = io.BytesIO()
    plt.savefig(buf, format='svg', bbox_inches='tight')
    s = buf.getvalue()
    buf.close()
    return s

# 実行するビュー関数
def get_svg(request):
    plusbtn=request.session['plusbtn']
    minusbtn=request.session['minusbtn']
    delta=int(request.session['delta'])
    x1=request.session['xvalue']
    y1=request.session['yvalue']

    if plusbtn == 'ON'or minusbtn == 'ON':
        x=x1.split(",")

        y2=y1.split(",")
        y = [int(n) for n in y2]
        y[1]=delta

    else:
        x=x1.split(",")
        y=y1.split(",")
    request.session['plusbtn']='OFF'
    request.session['minusbtn']='OFF'
    request.session['delta']=delta
    mode=request.session['mode']
    setPlt(x,y,mode)  
    svg = plt2svg()  #SVG化
    plt.cla()  # グラフをリセット
    response = HttpResponse(svg, content_type='image/svg+xml')
    return response

目次へ

Templates

result.html→index.htmlに名前を変更しています。

<!doctype html>
<html lang="ja">
  <head>
    <title>Ajax</title>
  </head>
  <body>
      <div class="container">
          <h2>Matplotlibアプリ(Ajax版)</h2>
          <p>XとYにそれぞれカンマ(,)区切りでデータを入力→グラフタイプを選択して描画を押せば表示します。</p>
          <form id="ajax-number" action="{% url 'mtaja_app:ajax_number' %}" method="POST">
              {% csrf_token %}

     		<label>X: 
            	<input type="text" value="" id="xvalue" name="xvalue"> 例1,2,3,4,5,6,7</label>
		    <br>
		    <label>Y: 
        	    <input type="text" value="" id="yvalue" name="yvalue"> 例10,20,30,40,50,60,70</label>

    		<br>
	    	<input type="radio" id="mode" name="mode" value="plot" checked="checked">折れ線
		    <input type="radio" id="mode" name="mode" value="bar">棒グラフ
    		<br>
              <button type="submit" >描画</button>
          </form>
          <p>2個目の要素を下のボタンで増減(他のyの値の範囲内)
          <p></p>y2:<span class="y2">0</span></p>
          <button id="buttonup">+1</button>
          <button id="buttondown">-1</button>

          <div class="result">
            <p>Ajaxあり</p>
            <img src="{% url 'mtaja_app:plot' %}" width=300 height=300>
          </div>
             <p>Ajaxなし(ページ更新しないと表示されません)</p>
            <img src="{% url 'mtaja_app:plot' %}" width=300 height=300>
      </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script>
        function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = jQuery.trim(cookies[i]);
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) === (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }

        var csrftoken = getCookie('csrftoken');

        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }

        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });

        $('#ajax-number').on('submit', function(e) {
            e.preventDefault();

            $.ajax({
                'url': '{% url "mtaja_app:ajax_number" %}',
                'type': 'POST',
                'data': {
                    'number1': $('#xvalue').val(),
                    'number2': $('#yvalue').val(),
                    'mode': $('input[name=mode]:checked').val(),
                },
                'dataType': 'json'
            })
            .done(function(response){
                $('.y2').text(response.y2);
                $('.result').children('img').attr('src', "{% url 'mtaja_app:plot' %}"+ '?' + new Date().getTime() );
            });
        });
        
        $('#buttonup').on('click', function() {
            $.ajax({
                'url': '{% url "mtaja_app:ajax_numberp" %}',
                'type': 'POST',
                'data': {
                   'plusbtn': 'ON',
                },
                'dataType': 'json'
            })
            .done(function(response){
                $('.y2').text(response.y2);
                $('.result').children('img').attr('src', "{% url 'mtaja_app:plot' %}"+ '?' + new Date().getTime() );
            }); 
        });
        $('#buttondown').on('click', function() {
            $.ajax({
                'url': '{% url "mtaja_app:ajax_numberm" %}',
                'type': 'POST',
                'data': {
                   'minusbtn': 'ON',
                },
                'dataType': 'json'
            })
            .done(function(response){
                $('.y2').text(response.y2);
                $('.result').children('img').attr('src', "{% url 'mtaja_app:plot' %}"+ '?' + new Date().getTime() );
            }); 
        });
    </script>
  </body>
</html>

目次へ
MENTAやってます(ichige)
イチゲをOFUSEで応援する


コメント

タイトルとURLをコピーしました