0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPT 4o with canvasでhtmlツールを作成してみる

Posted at

ほぼGPTで遊んだだけ。

作成したツール

コード(html)

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>機能依存関係図</title>
        <style>
            body {
                margin: 0;
                overflow: auto;
            }
            .panel {
                border: 1px solid #ccc;
                margin: 10px;
                padding: 10px;
                width: 300px;
                position: absolute;
                cursor: move;
            }
            .panel-header {
                font-weight: bold;
                cursor: pointer;
                background-color: #f1f1f1;
                padding: 5px;
            }
            .panel-content {
                display: none;
                padding: 5px;
            }
            .control-button, .import-button, .export-button, .import-json-button, .comment-toggle-button {
                margin: 10px;
                padding: 10px;
                cursor: pointer;
                color: white;
                border: none;
                border-radius: 5px;
                z-index: 1000;
            }
            .control-button {
                background-color: #4CAF50;
            }
            .import-button {
                background-color: #2196F3;
            }
            .export-button, .import-json-button {
                background-color: #FFA500;
            }
            .comment-toggle-button {
                background-color: #808080;
            }
            .screen-panel {
                background-color: #add8e6;
            }
            .api-panel {
                background-color: #f08080;
            }
            .batch-panel {
                background-color: #ffcc99;
            }
            .table-panel {
                background-color: #90ee90;
            }
            .external-panel {
                background-color: #dda0dd;
            }
            .chart-container {
                position: relative;
                display: flex;
                justify-content: space-around;
                align-items: flex-start;
                transform-origin: 0 0;
                width: 10000px;
                height: 10000px;
                border-top: 2px solid #000;
            }
            .arrow {
                position: absolute;
                stroke: #000;
                fill: none;
                marker-end: url(#arrowhead);
            }
            .highlighted-item {
                color: red;
            }
            .highlighted-arrow {
                stroke: red;
            }
            .header-container {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                background-color: #fff;
                z-index: 1000;
                display: flex;
                align-items: center;
                padding: 10px;
                border-bottom: 2px solid #000;
            }
            .input-field {
                margin-left: 10px;
                padding: 5px;
            }
            .comment-bubble {
                position: absolute;
                background-color: #fff;
                border: 1px solid #ccc;
                padding: 5px;
                display: none;
                z-index: 1001;
            }
            .toggle-active {
                background-color: #008000;
            }
        </style>
    </head>
<body>
    <div class="header-container">
        <div>
            <button id="toggleAll" class="control-button">すべて展開/閉じる</button>
            <button id="importCsv" class="import-button">CSVインポート</button>
            <button id="exportJson" class="export-button">編集エクスポート</button>
            <button id="importJson" class="import-json-button">編集インポート</button>
            <button id="toggleComments" class="comment-toggle-button">コメント表示</button>
            <label for="widthInput">描画領域X(px)</label>
            <input type="number" id="widthInput" class="input-field" value="10000">
            <label for="heightInput">描画領域Y(px)</label>
            <input type="number" id="heightInput" class="input-field" value="10000">
            <button id="setDimensions" class="input-field">描画領域設定</button>
        </div>
    </div>
    <div id="contents">
      <div id="chart" class="chart-container"></div>
      <svg id="arrows" width="100%" height="100%" style="position:absolute; top:0; left:0; pointer-events:none;">
          <defs>
              <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" markerUnits="strokeWidth">
                  <polygon points="0 0, 10 3.5, 0 7" />
              </marker>
          </defs>
      </svg>
    </div>
    <script>
        var screenData = [];
        var comments = {};

        document.getElementById('importCsv').addEventListener('click', () => {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.csv';
            input.onchange = (event) => {
                const file = event.target.files[0];
                if (file) {
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        const text = e.target.result;
                        screenData = parseCsv(text);
                        renderChart();
                        drawArrows();
                    };
                    reader.readAsText(file);
                }
            };
            input.click();
        });

        document.getElementById('setDimensions').addEventListener('click', () => {
            const width = document.getElementById('widthInput').value;
            const height = document.getElementById('heightInput').value;
            const chart = document.getElementById('chart');
            chart.style.width = `${width}px`;
            chart.style.height = `${height}px`;
        });

        document.getElementById('exportJson').addEventListener('click', () => {
            const state = {
                panels: Object.values(panelPositions).map(panel => ({
                    id: panel.querySelector('.panel-header').textContent,
                    x: panel.style.left,
                    y: panel.style.top,
                    expanded: panel.querySelector('.panel-content').style.display === 'block',
                    items: Array.from(panel.querySelectorAll('.panel-content input')).map(input => ({
                        name: input.nextSibling.textContent,
                        checked: input.checked
                    })),
                    comment: comments[panel.querySelector('.panel-header').textContent] || ""
                }))
            };
            const blob = new Blob([JSON.stringify(state)], { type: 'application/json' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = 'exported_content.json';
            link.click();
        });

        document.getElementById('importJson').addEventListener('click', () => {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json';
            input.onchange = (event) => {
                const file = event.target.files[0];
                if (file) {
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        const state = JSON.parse(e.target.result);
                        renderChart(state);
                        drawArrows();
                    };
                    reader.readAsText(file);
                }
            };
            input.click();
        });

        document.getElementById('toggleComments').addEventListener('click', (e) => {
            const commentBubbles = document.querySelectorAll('.comment-bubble');
            const shouldShow = e.target.classList.toggle('toggle-active');
            e.target.textContent = shouldShow ? 'コメント非表示' : 'コメント表示';
            commentBubbles.forEach(bubble => {
                bubble.style.display = shouldShow ? 'block' : 'none';
            });
        });

        function parseCsv(csvText) {
            const rows = csvText.split('\n');
            const headers = rows[0].split(',');
            return rows.slice(1).map(row => {
                const values = row.split(',');
                return headers.reduce((acc, header, index) => {
                    acc[header.trim()] = values[index]?.trim() || '';
                    return acc;
                }, {});
            });
        }

        var groupedData;
        var panelPositions = {};
        var itemPositions = {};
        const chartDiv = document.getElementById('chart');

        function renderChart(state = null) {
            chartDiv.innerHTML = '';
            panelPositions = {};
            itemPositions = {};
            groupedData = screenData.reduce((acc, row) => {
                if (!acc[row.機能名]) {
                    acc[row.機能名] = {
                        type: row.機能種別,
                        screenName: row.機能名,
                        items: []
                    };
                }
                acc[row.機能名].items.push({
                    type: row.機能種別,
                    screenName: row.機能名,
                    itemName: row.項目名,
                    sourceScreen: row.リソース,
                    sourceItem: row.取得元項目,
                    resourceType: row.リソースの機能種別,
                    handoff: row.引渡し
                });
                return acc;
            }, {});

            const data = Object.values(groupedData);

            data.forEach((screen, index) => {
                const panel = document.createElement('div');
                panel.className = `panel ${screen.type === '画面' ? 'screen-panel' : screen.type === 'API' ? 'api-panel' : screen.type === 'バッチ' ? 'batch-panel' : screen.type === '外部機能' ? 'external-panel' : 'table-panel'}`;
                panel.style.top = state ? state.panels[index].y : `${index * 150 + 150}px`;
                panel.style.left = state ? state.panels[index].x : `${index % 2 === 0 ? 100 : 500}px`;

                const panelHeader = document.createElement('div');
                panelHeader.className = 'panel-header';
                panelHeader.textContent = screen.screenName;
                panelHeader.addEventListener('click', (e) => {
                    if (!panel.classList.contains('dragging') && !panel.dataset.moving) {
                        const content = panel.querySelector('.panel-content');
                        content.style.display = content.style.display === 'none' ? 'block' : 'none';
                        drawArrows();
                    }
                });

                const commentBubble = document.createElement('div');
                commentBubble.className = 'comment-bubble';
                commentBubble.contentEditable = true;
                commentBubble.style.display = 'none';
                if (state && state.panels[index].comment) {
                    commentBubble.textContent = state.panels[index].comment;
                }
                comments[screen.screenName] = commentBubble.textContent;

                panelHeader.addEventListener('contextmenu', (e) => {
                    e.preventDefault();
                    commentBubble.style.top = e.clientY - commentBubble.getBoundingClientRect().top;
                    commentBubble.style.left = e.clientX - commentBubble.getBoundingClientRect().left;
                    commentBubble.style.display = 'block';
                    commentBubble.focus();
                });

                commentBubble.addEventListener('blur', () => {
                    comments[screen.screenName] = commentBubble.textContent;
                });

                const panelContent = document.createElement('div');
                panelContent.className = 'panel-content';
                screen.items.forEach(item => {
                    if (!itemPositions[`${screen.screenName}-${item.itemName}`]) {
                        const itemDiv = document.createElement('div');
                        const checkbox = document.createElement('input');
                        checkbox.type = 'checkbox';
                        checkbox.style.marginRight = '5px';
                        itemDiv.appendChild(checkbox);
                        itemDiv.appendChild(document.createTextNode(item.itemName));
                        itemDiv.dataset.screenName = screen.screenName;
                        itemDiv.dataset.itemName = item.itemName;
                        panelContent.appendChild(itemDiv);

                        itemPositions[`${screen.screenName}-${item.itemName}`] = itemDiv;

                        checkbox.addEventListener('change', () => {
                            if (checkbox.checked) {
                                itemDiv.classList.add('highlighted-item');
                                highlightRelatedItems(item, {}, true);
                            } else {
                                itemDiv.classList.remove('highlighted-item');
                                highlightRelatedItems(item, {}, false);
                            }
                        });

                        itemDiv.addEventListener('contextmenu', (e) => {
                            e.preventDefault();
                            commentBubble.style.top = e.clientY - commentBubble.getBoundingClientRect().top;
                            commentBubble.style.left = e.clientX - commentBubble.getBoundingClientRect().left;
                            commentBubble.style.display = 'block';
                            commentBubble.focus();
                        });
                    }
                });

                if (state) {
                    const panelState = state.panels.find(p => p.id === screen.screenName);
                    if (panelState) {
                        panelContent.style.display = panelState.expanded ? 'block' : 'none';
                        panelState.items.forEach(itemState => {
                            const itemDiv = Array.from(panelContent.children).find(div => div.textContent.trim() === itemState.name);
                            if (itemDiv) {
                                const checkbox = itemDiv.querySelector('input');
                                checkbox.checked = itemState.checked;
                                if (checkbox.checked) {
                                    itemDiv.classList.add('highlighted-item');
                                    highlightRelatedItems(itemState, {}, true);
                                }
                            }
                        });
                    }
                }

                panel.appendChild(panelHeader);
                panel.appendChild(panelContent);
                panel.appendChild(commentBubble);
                chartDiv.appendChild(panel);

                panelPositions[screen.screenName] = panel;

                panel.addEventListener('mousedown', (e) => {
                    panel.dataset.moving = true;
                    let shiftX = e.clientX - panel.getBoundingClientRect().left;
                    let shiftY = e.clientY - panel.getBoundingClientRect().top;

                    function moveAt(pageX, pageY) {
                        panel.style.left = pageX - shiftX + 'px';
                        panel.style.top = pageY - shiftY + 'px';
                        drawArrows();
                    }

                    function onMouseMove(event) {
                        panel.classList.add('dragging');
                        moveAt(event.pageX, event.pageY);
                    }

                    document.addEventListener('mousemove', onMouseMove);

                    document.addEventListener('mouseup', function onMouseUp() {
                        document.removeEventListener('mousemove', onMouseMove);
                        document.removeEventListener('mouseup', onMouseUp);
                        panel.classList.remove('dragging');
                        delete panel.dataset.moving;
                    });
                });

                panel.ondragstart = function() {
                    return false;
                };
            });
        }
        renderChart();

        function drawArrows() {
            const svg = document.getElementById('arrows');
            svg.innerHTML = '<defs><marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" markerUnits="strokeWidth"><polygon points="0 0, 10 3.5, 0 7" /></marker></defs>';
            let maxX = 0;
            let maxY = 0;

            screenData.forEach(row => {
                if (
                    (row.リソース && row.取得元項目 && row.リソース !== row.機能名) ||
                    (row.引渡し && row.リソース)
                ) {
                    const sourceItem = row.取得元項目
                        ? itemPositions[`${row.リソース}-${row.取得元項目}`]
                        : itemPositions[`${row.機能名}-${row.項目名}`];
                    const targetItem = row.取得元項目
                        ? itemPositions[`${row.機能名}-${row.項目名}`]
                        : itemPositions[`${row.リソース}-${row.引渡し}`];
                    const sourcePanel = row.取得元項目
                        ? panelPositions[row.リソース]
                        : panelPositions[row.機能名];
                    const targetPanel = row.取得元項目
                        ? panelPositions[row.機能名]
                        : panelPositions[row.リソース];

                    if (sourcePanel && targetPanel) {
                        let sourceX, sourceY, targetX, targetY;

                        const sourceRect = sourceItem ? sourceItem.getBoundingClientRect() : sourcePanel.getBoundingClientRect();
                        const targetRect = targetItem ? targetItem.getBoundingClientRect() : targetPanel.getBoundingClientRect();

                        const sourceIsVisible = sourceItem ? sourceItem.closest('.panel-content').style.display === 'block' : false;
                        const targetIsVisible = targetItem ? targetItem.closest('.panel-content').style.display === 'block' : false;

                        if (sourceIsVisible) {
                            sourceX = sourceRect.right + window.pageXOffset;
                            sourceY = sourceRect.top + sourceRect.height / 2 + window.pageYOffset;
                        } else {
                            const sourcePanelRect = sourcePanel.getBoundingClientRect();
                            sourceX = sourcePanelRect.right + window.pageXOffset;
                            sourceY = sourcePanelRect.top + 20 + window.pageYOffset;
                        }

                        if (targetIsVisible) {
                            targetX = targetRect.left + window.pageXOffset;
                            targetY = targetRect.top + targetRect.height / 2 + window.pageYOffset;
                        } else {
                            const targetPanelRect = targetPanel.getBoundingClientRect();
                            targetX = targetPanelRect.left + window.pageXOffset;
                            targetY = targetPanelRect.top + 20 + window.pageYOffset;
                        }

                        if (sourceX < targetX) {
                            sourceX = sourceIsVisible ? sourceRect.right + window.pageXOffset : sourcePanel.getBoundingClientRect().right + window.pageXOffset;
                            targetX = targetIsVisible ? targetRect.left + window.pageXOffset : targetPanel.getBoundingClientRect().left + window.pageXOffset;
                        } else {
                            sourceX = sourceIsVisible ? sourceRect.left + window.pageXOffset : sourcePanel.getBoundingClientRect().left + window.pageXOffset;
                            targetX = targetIsVisible ? targetRect.right + window.pageXOffset : targetPanel.getBoundingClientRect().right + window.pageXOffset;
                        }

                        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                        line.setAttribute('x1', sourceX);
                        line.setAttribute('y1', sourceY);
                        line.setAttribute('x2', targetX);
                        line.setAttribute('y2', targetY);
                        line.setAttribute('class', 'arrow');

                        if (
                            (sourceItem && sourceItem.classList.contains('highlighted-item')) ||
                            (targetItem && targetItem.classList.contains('highlighted-item'))
                        ) {
                            line.classList.add('highlighted-arrow');
                        }

                        svg.appendChild(line);
                        maxX = Math.max(maxX, sourceX, targetX);
                        maxY = Math.max(maxY, sourceY, targetY);
                    }
                }
            });

            svg.setAttribute('width', maxX + 50);
            svg.setAttribute('height', maxY + 50);
        }

        function highlightRelatedItems(item, searchedMap, highlight) {
            if (searchedMap[item.screenName + "-" + item.itemName]) {
                return;
            }
            searchedMap[item.screenName + "-" + item.itemName] = true;
            const searchTargetList = screenData.filter(row => row.機能名 === item.screenName && row.項目名 === item.itemName);
            searchTargetList.forEach(srow => {
                screenData.forEach(row => {
                    if (
                        (srow.リソース === row.機能名 && srow.取得元項目 === row.項目名) ||
                        (srow.リソース === row.機能名 && srow.引渡し === row.項目名) ||
                        (srow.機能名 === row.リソース && srow.項目名 === row.取得元項目) ||
                        (srow.機能名 === row.リソース && srow.項目名 === row.引渡し)
                    ) {
                        const targetItemDiv = itemPositions[`${row.機能名}-${row.項目名}`];
                        if (targetItemDiv) {
                            if (highlight) {
                                targetItemDiv.classList.add('highlighted-item');
                                const checkbox = targetItemDiv.querySelector('input');
                                checkbox.checked = true;
                            } else {
                                targetItemDiv.classList.remove('highlighted-item');
                                const checkbox = targetItemDiv.querySelector('input');
                                checkbox.checked = false;
                            }
                        }
                        highlightRelatedItems({
                            type: row.機能種別,
                            screenName: row.機能名,
                            itemName: row.項目名,
                            sourceScreen: row.リソース,
                            sourceItem: row.取得元項目,
                            resourceType: row.リソースの機能種別,
                            handoff: row.引渡し
                        }, searchedMap, highlight);
                    }
                });
            });
            drawArrows();
        }

        requestAnimationFrame(drawArrows);

        const toggleAllButton = document.getElementById('toggleAll');
        toggleAllButton.addEventListener('click', () => {
            const panels = document.querySelectorAll('.panel-content');
            const allExpanded = Array.from(panels).every(panel => panel.style.display === 'block');
            panels.forEach(panel => {
                panel.style.display = allExpanded ? 'none' : 'block';
            });
            drawArrows();
        });

        window.addEventListener('resize', () => {
            requestAnimationFrame(drawArrows);
        });
    </script>
</body>
</html>

データ(csv)

機能種別,クラス名,機能名,項目種別,項目名,リソースの機能種別,リソース,取得元項目,引渡し
画面,顧客管理系画面,顧客検索画面,-,顧客ID,-,,,
画面,顧客管理系画面,顧客検索画面,-,姓,-,,,
画面,顧客管理系画面,顧客検索画面,-,名,-,,,
画面,顧客管理系画面,顧客検索画面,-,電話番号,-,,,
画面,顧客管理系画面,顧客詳細画面,-,顧客ID,画面,顧客検索画面,顧客ID,
画面,顧客管理系画面,顧客詳細画面,-,顧客ID,API,顧客取得,,顧客ID
画面,顧客管理系画面,顧客詳細画面,-,あああ,API,顧客取得,,ああ
画面,顧客管理系画面,顧客詳細画面,-,あああ,画面,顧客検索画面,ああああ,
画面,顧客管理系画面,顧客詳細画面,-,姓,API,顧客取得,姓,
画面,顧客管理系画面,顧客詳細画面,-,名,API,顧客取得,名,
画面,顧客管理系画面,顧客詳細画面,-,電話番号,API,顧客取得,電話番号,
画面,顧客管理系画面,顧客詳細画面,-,生年月日,API,顧客取得,生年月日,
画面,顧客管理系画面,顧客詳細画面,-,住所,API,顧客取得,住所,
API,顧客情報API,顧客取得,入力,顧客ID,,,,
API,顧客情報API,顧客取得,出力,姓,テーブル,顧客情報,姓,
API,顧客情報API,顧客取得,出力,名,テーブル,顧客情報,名,
API,顧客情報API,顧客取得,出力,電話番号,テーブル,顧客情報,電話番号,
API,顧客情報API,顧客取得,出力,生年月日,テーブル,顧客情報,生年月日,
API,顧客情報API,顧客取得,出力,住所,テーブル,顧客情報,住所,
テーブル,オブジェクト,顧客情報,-,姓,-,-,-,-
テーブル,オブジェクト,顧客情報,-,名,-,-,-,-
テーブル,オブジェクト,顧客情報,-,電話番号,-,-,-,-
テーブル,オブジェクト,顧客情報,-,生年月日,-,-,-,-
テーブル,オブジェクト,顧客情報,-,住所,-,-,-,-

テストデータ.png

ツールの紹介

データの流れを確認したいときや、項目の削減や名称変更などの影響範囲を見る時につかえるかも?

  1. htmlファイルで完結しているため、単純にブラウザ起動で使える
  2. 「csvインポート」でcsvデータを読み取る
     ※小規模なら手作業でもできるが、大規模なら設計書や仕様書からこうした情報を収集するツールが別途必要そう
  3. 主な編集操作は以下の通り
    3-1. 機能単位でパネルになっていて移動できる。
    3-2. 機能名部分をクリックすると項目部分の折り畳みと展開を切り替えられる
    3-3. 項目名のチェックボックスをクリックすると関連のある項目を探索し全てチェック状態にする
    3-4. 矢印の描画は基本的には項目対項目。関連先に該当項目が見つからない場合は機能名に線をつなげるようにした。
    3-5. コメント機能。パネルに右クリックでコメントを付けることができる。
  4. 「編集エクスポート」ではパネルの配置やチェック状態を保存した結果をjson形式で出力する
  5. 「編集インポート」では上記エクスポートを取り込める。ただし編集データだけでは復元できないため、同じデータでcsvインポートしてから編集インポートする必要がある
     ※ここは余力があったら改善したい

画面イメージ.png

作成過程

想定よりだいぶチャットが長期化したのですべては載せられないが一部参考として記載する。

  1. 初期状態
    GPTに投げたメッセージ
最終的に実施したいことは、仕様に変更を加えた時の影響調査を容易にすること。
画面はブラウザでの動作を想定。業務処理はWEBAPIで実装しているとする。
データはRDBで扱っている。
この時、項目名を変えたり、追加や削除を行ったときにどこに影響があるかを調査する手助けになるツールが欲しい。

応答
この段階ではアプローチの方法の提案まででコードはあくまでサンプルレベル。

仕様変更時の影響調査を支援するツールを作成するためには、データの依存関係を管理し、変更がどの部分に影響を与えるかを可視化する仕組みが必要です。ここでは、ブラウザベースで動作するツールのアイデアをいくつか提案します。...

コード

// Expressを使った簡単なサーバーサイドAPIの例
const express = require('express');
const app = express();
const port = 3000;

// 仮の依存関係データ(データベース接続を想定)
const dependencyData = {
    "table_a.column_x": ["screen_1", "api_2"],
    "table_b.column_y": ["screen_3", "api_1"]
};

// 項目名を指定して依存関係を返すAPI
app.get('/api/dependencies', (req, res) => {
    const itemName = req.query.item;
    if (dependencyData[itemName]) {
        res.json({ dependencies: dependencyData[itemName] });
    } else {
        res.status(404).send('No dependencies found');
    }
});

app.listen(port, () => {
    console.log(`Dependency analysis API running at http://localhost:${port}`);
});

2. 仕様の具体化
仕様を具体的に示すことで期待の結果が得られないかを試していく

2-1. 入力データのイメージとアウトプットのイメージを伝える
GPTに投げたメッセージ

htmlとjsを使うことと、元データのjsonはVBAまたはpythonで別途準備する前提はそのままとします。
出力イメージが少し違うのでいったん、これまでの内容を破棄。
1枚目のようなデータから、図として2枚目のようなものを作成するように変更してください。
四角いパネルは折り畳みが可能として、クリックで切り替える。展開した状態だと項目名まで見える。

GPTに送った画像
インプットイメージ
仕様具体化-インプット.png
アウトプットイメージ
仕様具体化-アウトプット.png

GPTの回答

コード

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>画面・項目依存関係図</title>
    <style>
        .panel {
            border: 1px solid #ccc;
            margin: 10px;
            padding: 10px;
            width: 300px;
        }
        .panel-header {
            font-weight: bold;
            cursor: pointer;
            background-color: #f1f1f1;
            padding: 5px;
        }
        .panel-content {
            display: none;
            padding: 5px;
        }
        .control-button {
            margin: 10px;
            padding: 10px;
            cursor: pointer;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <button id="toggleAll" class="control-button">すべて展開/閉じる</button>
    <div id="chart"></div>
    <script>
        // JSONデータをJavaScript内で直接定義
        const data = [
            {
                "class": "顧客管理",
                "screens": [
                    {
                        "screenName": "顧客検索画面",
                        "items": ["姓", "名", "電話番号"]
                    },
                    {
                        "screenName": "顧客詳細画面",
                        "items": ["姓", "名", "電話番号", "生年月日", "住所"]
                    }
                ]
            }
        ];

        // HTMLに画面と項目のパネルを生成
        const chartDiv = document.getElementById('chart');

        data.forEach(system => {
            system.screens.forEach(screen => {
                // パネル作成
                const panel = document.createElement('div');
                panel.className = 'panel';

                // パネルヘッダー(画面名)
                const panelHeader = document.createElement('div');
                panelHeader.className = 'panel-header';
                panelHeader.textContent = screen.screenName;
                panelHeader.addEventListener('click', () => {
                    const content = panel.querySelector('.panel-content');
                    content.style.display = content.style.display === 'none' ? 'block' : 'none';
                });

                // パネルコンテンツ(項目名)
                const panelContent = document.createElement('div');
                panelContent.className = 'panel-content';
                screen.items.forEach(item => {
                    const itemDiv = document.createElement('div');
                    itemDiv.textContent = item;
                    panelContent.appendChild(itemDiv);
                });

                // パネルにヘッダーとコンテンツを追加
                panel.appendChild(panelHeader);
                panel.appendChild(panelContent);
                chartDiv.appendChild(panel);
            });
        });

        // すべてのパネルを展開/閉じるボタンの動作
        const toggleAllButton = document.getElementById('toggleAll');
        toggleAllButton.addEventListener('click', () => {
            const panels = document.querySelectorAll('.panel-content');
            const allExpanded = Array.from(panels).every(panel => panel.style.display === 'block');
            panels.forEach(panel => {
                panel.style.display = allExpanded ? 'none' : 'block';
            });
        });
    </script>
</body>
</html>

動作
仕様具体化-出力コード動作.png
2-2.さらなる具体化
仕様を具体的に示すとある程度期待の結果が得られそうに見えた。
ここから、依存間の線での描画やパネルの移動などを指示して少しずつ具体化していった。
やり取りがあまりに多いので添付はここまでとするが、気づきを記載する。
・言葉だけで説明が難しい場合、図を作成して説明すると理解してくれる。とくにUIのイメージなどが画像で説明したほうがいい。
・想定した動作にならなかったとき、こちらで原因を特定してコード上の修正箇所を伝えなくても、期待動作になってない部分を説明するだけでも適切に直してくれる。
 ・原因箇所が複雑な箇所だとほとんど改善しない場合もある。以下のようにするのがよさそう。
  ①とりあえず動作を基準にNGを伝えてみる。
  ②自身でコードを確認して修正箇所を指定。
  ③それでも期待通りの結果にならなかったら自力で修正。
・再帰的関数は苦手かも?探索処理は自作してから処理の冗長さの排除をGPTに依頼した。

最後に

理想の形になるまで1.5日も費やしてしまった。
とはいえ、よほどスキルが高い人じゃない限り、自力で作るよりは速そうに思う。
もう少しシンプルな仕様のものなら量産できそう。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?