"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

Chart.jsの散布図で「バーンスレイのシダ」を描く/django3.0+Chart.js

Chart.jsの散布図の座標データ=配列をpythonで生成して、HTML経由でJavaScriptに渡す方法でチャートグラフィックを描いてみます。

f:id:arakan_no_boku:20200422010037p:plain

 

Chart.jsをグラフィックツールとして使う

 

Chart.jsの散布図は、ドットで描画するグラフィックツールとして使えます。

座標はPythonで計算し、結果をDjangoテンプレート経由で、JavaScriptに渡して、ブラウザ上で描画する手順のサンプルをやります。

描くのは、以下の「バーンスレイのシダ(フラクタル)」です。

f:id:arakan_no_boku:20200425133252p:plain

 

HTMLからデータとパラメータを受け渡す方法

 

pythonから、javascriptに直接は渡せません。

HTML経由でパラメータとデータを受け渡します。

HTML部分です。

<div class="container">
    <div class="mycanvas">
        <canvas id="myChart"></canvas>
    </div>
    <input type="hidden" id="x_data_str" name="x_data_str" value="{{ x_data_str }}">
    <input type="hidden" id="y_data_str" name="y_data_str" value="{{ y_data_str }}">
    <input type="hidden" id="label_text" name="label_text" value="{{ label_text }}">
    <input type="hidden" id="title_text" name="title_text" value="{{ title_text }}">
    <input type="hidden" id="chart_size" name="chart_size" value="{{ chart_size }}">
    <input type="hidden" id="show_line" name="show_line" value="{{ show_line }}">
</div>

ID=myChartの位置に散布図を表示します。

type:hiddenの部分が、javascriptに渡すパラメータとデータです。

Djangoテンプレートの機能を使っているので、実際の値の指定はpythonプログラム側で生成して、「value="{{ x_data_str }}」で受け取ります。

 

散布図を描画するJavaScriptです。

var xDataArray = String(document.getElementById('x_data_str').value).split(',');
var yDataArray = String(document.getElementById('y_data_str').value).split(',');
var labelText = String(document.getElementById('label_text').value);
var titleText = String(document.getElementById('title_text').value);
var chartSize = document.getElementById('chart_size').value;
var showLine = String(document.getElementById('show_line').value);

window.chartColors = {
    red: "#FF0000",
    blue: "#0000FF"
};

var color = Chart.helpers.color;
function generateData() {
    var data = [];
    for (let i = 0; i < xDataArray.length; i++) {
        data.push({
            x: xDataArray[i],
            y: yDataArray[i]
        });
    }
    return data;
};

var scatterChartData = {
    datasets: [{
        label: labelText,
        borderColor: window.chartColors.blue,
        backgroundColor: window.chartColors.blue,
        pointRadius: 3,
        showLine: Boolean(showLine),
        borderWidth: 1,
        data: generateData()
    }]
};

window.onload = function() {
    var ctx = document.getElementById('myChart').getContext('2d');
    ctx.canvas.width=chartSize;
    ctx.canvas.height=chartSize;
    window.myScatter = Chart.Scatter(ctx, {
        data: scatterChartData,
        options: {
            title: {
                display: false,
                text: titleText
            },
            scales: {
                xAxes: [{
                    gridLines: {                       // 補助線(縦線)
                        color: "rgba(255, 0, 0, 0.2)",   // 補助線の色
                        zeroLineColor: "black"           // x=0時の(縦線の色)
                    },
                }],
                yAxes: [{
                    gridLines: {                       // 補助線(縦線)
                        color: "rgba(255, 0, 0, 0.2)",   // 補助線の色
                        zeroLineColor: "black"           // x=0時の(縦線の色)
                    },
                }],
            },
        }
    });
};

最初に、HTMLのhiddenタグからIDセレクタでデータを取得します。 

最初の2行。

var xDataArray = String(document.getElementById('x_data_str').value).split(',');
var yDataArray = String(document.getElementById('y_data_str').value).split(',');

がポイントです。

ここが、座標データを受け取るところですが、HTML経由で配列は渡せません。

配列をカンマ区切りの文字列で受け取って、split(',')して配列に戻します。

このへんは、以下のレーダーチャートの場合と同じです。

arakan-pgm-ai.hatenablog.com

 

python側の処理

 

さて。

Pythonで「バースレイのシダ」を描く座標計算をするクラスです。

class GrFern:

    def __init__(self):
        self.tx = [0]
        self.ty = [0]

    def __tran_1(self, p):
        x = p[0]
        y = p[1]
        x1 = 0.85 * x + 0.04 * y
        y1 = -0.04 * x + 0.85 * y + 1.6
        return x1, y1

    def __tran_2(self, p):
        x = p[0]
        y = p[1]
        x1 = 0.2 * x - 0.26 * y
        y1 = 0.23 * x + 0.22 * y + 1.6
        return x1, y1

    def __tran_3(self, p):
        x = p[0]
        y = p[1]
        x1 = -0.15 * x + 0.28 * y
        y1 = 0.26 * x + 0.24 * y + 0.44
        return x1, y1

    def __tran_4(self, p):
        # x = p[0]
        y = p[1]
        x1 = 0
        y1 = 0.16 * y
        return x1, y1

    def get_index(self):
        prob = [0.85, 0.07, 0.07, 0.01]
        r = random.random()
        c = 0
        sump = []
        for p in prob:
            c += p
            sump.append(c)
        for item, sp in enumerate(sump):
            if(r <= sp):
                return item
        return len(prob) - 1

    def __tran(self, p):
        trans = [self.__tran_1, self.__tran_2, self.__tran_3, self.__tran_4]
        tindex = self.get_index()
        t = trans[tindex]
        x, y = t(p)
        return x, y

    def draw(self, n=10000):
        x1 = 0
        y1 = 0
        for i in range(n):
            x1, y1 = self.__tran((x1, y1))
            self.tx.append(x1)
            self.ty.append(y1)
        return ','.join(map(str, self.tx)), ','.join(map(str, self.ty))

HTML軽油で座標データを渡すため、座標を計算してから、カンマ区切り文字列に変換します。

return ','.join(map(str, self.tx)), ','.join(map(str, self.ty))

数値データなので、mapで、座標の配列を「str」型にしてからJOINします。

数値のままだとエラーになりますから。

ポイントはこんなところです。

このPython側の座標計算部分を変更していけば、いろいろな図形をブラウザ上で描画できます。

もちろん、普通のグラフのためにデータを渡すのも同じ要領です。

今回はこんなところで。

ではでは。