メインコンテンツへスキップ

心の可視化アプリ 改善と進捗、追加案



    ​最新版:心の地図 


    from flask import Flask, request, render_template_string

    import pandas as pd

    import plotly.graph_objects as go

    import random

    import os

    from datetime import datetime


    app = Flask(__name__)

    DATA_FILE = "emotion_path_data.json"


    # 感情ベクトル方向(出力順を固定)

    EMOTION_DEFS = [

        {"key": "Q_LY", "name": "安心", "vec": (0, 1)},

        {"key": "Q_Hope", "name": "希望", "vec": (0.7, 0.7)},

        {"key": "Q_Fr", "name": "不安", "vec": (1, 0)},

        {"key": "Rp", "name": "無気力", "vec": (0, -1)},

        {"key": "Q_Lonely", "name": "孤独", "vec": (-1, 0)},

        {"key": "Q_Anger", "name": "怒り", "vec": (-0.7, -0.7)},

        {"key": "Q_Normal", "name": "いつも通り", "vec": (-0.1, 0.1)}

    ]


    CHARACTERS = ["🐱", "🤖", "🐻", "🐥", "👦", "👧", "🐕️", "🐑"]


    # 初期データ読み込み

    emotion_keys = [d["key"] for d in EMOTION_DEFS]

    cols = ["date", "Character"] + emotion_keys


    if os.path.exists(DATA_FILE):

        try:

            df = pd.read_json(DATA_FILE)

        except:

            df = pd.DataFrame(columns=cols)

    else:

        df = pd.DataFrame(columns=cols)


    HTML_TEMPLATE = """

    <!DOCTYPE html>

    <html>

    <head>

        <title>エモ・ナビ 🧭 心の幾何学</title>

        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <style>

            body { font-family: sans-serif; background: #121212 ; color: #e0e0e0 ; text-align:center; margin:0; padding:10px; }

            .container { max-width: 500px; margin: auto; background: #1e1e1e ; padding: 15px; border-radius: 20px; }

            .slider-box { text-align: left; background: #2c2c2c ; padding: 10px; border-radius: 10px; margin: 10px 0; }

            input[type="range"] { width: 60%; vertical-align: middle; }

            .val-disp { display: inline-block; width: 30px; text-align: right; font-size: 0.8rem; color: #03dac6 ; }

            label { display: inline-block; width: 80px; font-size: 0.9rem; }

            button { width: 100%; padding: 12px; background: #bb86fc ; border: none; border-radius: 10px; font-weight: bold; cursor: pointer; color: #000 ; }

            .feedback { font-size: 0.9rem; color: #03dac6 ; margin-top: 10px; text-align: left; border-left: 3px solid #bb86fc ; padding-left: 10px; }

            .char-select { margin-bottom: 15px; display: flex; flex-wrap: wrap; justify-content: center; }

        </style>

    </head>

    <body>

    <div class="container">

        <h2>心の幾何学すごろく 🧭</h2>

        <form method="POST">

            <div class="char-select">

                {% for c in characters %}

                <label style="width:auto; cursor:pointer; font-size:1.5rem; margin:5px;">

                    <input type="radio" name="character" value="{{ c }}" required> {{ c }}

                </label>

                {% endfor %}

            </div>

            <div class="slider-box">

                {% for d in defs %}

                <div>

                    <label>{{ d.name }}</label>

                    <input type="range" name="{{ d.key }}" min="0" max="5" step="0.5" value="1" 

                           oninput="this.nextElementSibling.innerText = this.value">

                    <span class="val-disp">1</span>

                </div>

                {% endfor %}

                <div style="margin-top:10px;">

                    <label>日付</label>

                    <input type="date" name="date" value="{{ today }}" style="background:#444; color:#fff; border:none; padding:5px; width:150px;">

                </div>

            </div>

            <button type="submit">心の歩みを記録する</button>

        </form>


        {% if plot %}

        <div style="margin-top:20px; background:#fff; border-radius:15px; overflow:hidden; display: flex; justify-content: center;">{{ plot|safe }}</div>

        {% endif %}


        {% if feedback %}

        <div class="feedback">{{ feedback|safe }}</div>

        {% endif %}

    </div>

    </body>

    </html>

    """


    @app.route("/", methods=["GET", "POST"])

    def index():

        global df

        plot_div = ""

        feedback = ""

        today = datetime.today().strftime("%Y-%m-%d")


        if request.method == "POST":

            try:

                # 1. 安全なデータ取得

                entry = {

                    "date": request.form.get("date", today),

                    "Character": request.form.get("character", "🐱")

                }

                for d in EMOTION_DEFS:

                    entry[d["key"]] = float(request.form.get(d["key"], 0))


                # データ更新

                new_row = pd.DataFrame([entry])

                df = pd.concat([df, new_row], ignore_index=True)

                df = df.drop_duplicates(subset="date", keep="last").sort_values("date")

                df.to_json(DATA_FILE, force_ascii=False)


                # 2. 軌跡計算(すごろくロジック)

                trail_points = [(0, 0)]

                

                # 全データを通じて一本の線にする

                for _, row in df.iterrows():

                    current_pos = trail_points[-1]

                    # 固定された順番で1日の感情を繋ぐ

                    for d in EMOTION_DEFS:

                        score = row[d["key"]]

                        if score > 0:

                            dx = d["vec"][0] * score

                            dy = d["vec"][1] * score

                            current_pos = (current_pos[0] + dx, current_pos[1] + dy)

                            trail_points.append(current_pos)


                # 3. 演出

                events = []

                if random.random() < 0.3: events.append("🍀 足元に小さな幸運を見つけた。")

                feedback = f"<b>{entry['date']}</b> の記録を完了。<br>" + "<br>".join(events)


                # 4. Plotly描画

                fig = go.Figure()

                

                # 軌跡

                fig.add_trace(go.Scatter(

                    x=[p[0] for p in trail_points],

                    y=[p[1] for p in trail_points],

                    mode="lines+markers",

                    line=dict(color="#6200ee", width=2),

                    marker=dict(size=3, color="#03dac6"),

                    name="Route"

                ))


                # 最新のキャラ位置

                last_p = trail_points[-1]

                fig.add_trace(go.Scatter(

                    x=[last_p[0]], y=[last_p[1]],

                    mode="text",

                    text=[entry["Character"]],

                    textfont=dict(size=25),

                    name="Now"

                ))


                # レイアウト調整

                fig.update_layout(

                    showlegend=False,

                    margin=dict(l=10, r=10, t=10, b=10),

                    xaxis=dict(showgrid=True, zeroline=True, gridcolor="#eee", scaleanchor="y", scaleratio=1),

                    yaxis=dict(showgrid=True, zeroline=True, gridcolor="#eee"),

                    width=350, height=350,

                    plot_bgcolor='white'

                )

                plot_div = fig.to_html(full_html=False, config={'displayModeBar': False})


            except Exception as e:

                feedback = f"エラー: {e}"


        return render_template_string(

            HTML_TEMPLATE, 

            plot=plot_div, 

            feedback=feedback, 

            characters=CHARACTERS, 

            defs=EMOTION_DEFS, 

            today=today

        )


    if __name__ == "__main__":

        app.run(host="0.0.0.0", port=9500, debug=True)


    イメージ


    画像



    ここからさらに改良案として
    背景画像の差し込み

    日付や乱数で切り替えると自然。

    例えば:

    草原 → 緑ベースで小さな花や木

    宇宙 → 星や惑星、暗め背景

    絵本風 → パステルカラー、雲や小道

    砂漠 → 黄土色の砂丘、サボテン少し

    雨の日 → 雨粒や水たまり、少し暗め

    やり方のイメージ:

    Python

    bg_options = [

        {"src": "https://example.com/grass.png", "sizex": 20, "sizey": 20},

        {"src": "https://example.com/space.png", "sizex": 20, "sizey": 20},

        {"src": "https://example.com/storybook.png", "sizex": 20, "sizey": 20}

    ]


    bg_choice = random.choice(bg_options)

    fig.update_layout(images=[dict(

        source=bg_choice["src"],

        xref="x", yref="y",

        x=-10, y=10,

        sizex=20, sizey=20,

        xanchor="left", yanchor="top",

        layer="below"

    )])

    layer="below" にすることで軌跡とキャラが上に描画されます。

    sizex/sizey で Plotly 座標系に合わせると自然に見えます。

    日付ごとに乱数か、曜日で切り替えでも面白い。
    入力画面で選択できるようにしても良し。

    こうすると、1日ごとに「今日は宇宙を歩く日だ!」みたいな演出が可能になる。


    また、選択or使用中の背景に応じたイベント

    例)水中マップなら魚に声をかけられたりする
    宇宙マップなら流れ星が横切るなど

    なんか"ラッキー"な小さなイベント等




     
     
    世界がよくわからないから感性と論理で編む猫 自分で作った理論や数式を使ってDAWと合成音声ソフトを開発、ボカロP 自分で作れば無料

    あなたへのおすすめ