【321号】Grok~即席円・棒グラフ作成 令和070606

 

Grokのウェブアプリをひねり出してもらった

きょうはこれ。《表とグラフ生成ツール》。MS-Excelあるじゃんとツッコミ入れないでください。手軽に作りたい場合、先に行数指定して、構成比を自動計算、一発グラフ作成して画像生成。

MS-Excelなら式を入れて、グラフを選んで作成。3Dやお好きな角度や色も使えるいいところもいっぱい。積極的に使って豊かな資料も作れます。

この《表とグラフ生成ツール》。ぱっと作ってぱっと表示。細かなレイアウトや装飾はいらない、イメージをつかみたい場合に有効です。

【使用例】

例えば売上グラフ。営業所の数と指定すると必要な行が出てきます。そこに営業所名と売上いれると、合計と構成比を自動計算するようになっています。

円グラフか棒グラフを選び、画像出力かHTML文書出力できるようにしています。画像出力の例を見てみましょう。

【円グラフ】


【棒グラフ】


構成比は小数点以下第2位まで表示に調整しています。これで簡単にグラフが出来上がります。
データは1つのみ一時保存できるようにしています。保存を選んでいればブラウザを閉じてもあとから呼び出せませす。

【ソース(Grok生成)】


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>表とグラフ生成ツール(改良版6)</title>
    <style>
        body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background-color: #f4f4f4; }
        table { border-collapse: collapse; margin-bottom: 20px; width: 100%; max-width: 600px; }
        th, td { border: 1px solid #ccc; padding: 10px; text-align: right; }
        th { background-color: #e0e0e0; font-weight: 600; }
        #chartContainer { display: flex; justify-content: center; margin-top: 20px; }
        canvas { max-width: 500px; max-height: 400px; }
        .button { margin: 10px; padding: 12px 24px; cursor: pointer; background-color: #0078d4; color: white; border: none; border-radius: 5px; }
        .button:hover { background-color: #005ba1; }
        #titleInput, #rowsInput, #unitInput, #descriptionInput { width: 300px; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
        #descriptionInput { width: 500px; height: 80px; resize: vertical; }
        #graphType { margin: 10px; padding: 8px; border-radius: 4px; }
        label { font-weight: 500; margin-right: 10px; }
    </style>
</head>
<body>
    <h1>表とグラフ生成ツール</h1>
    <input type="text" id="titleInput" placeholder="グラフのタイトルを入力"><br>
    <label>行数:</label>
    <input type="number" id="rowsInput" min="1" value="1">
    <label>単位:</label>
    <input type="text" id="unitInput" placeholder="例: 万円"><br>
    <label>説明:</label>
    <textarea id="descriptionInput" placeholder="グラフの説明を入力"></textarea><br>
    <button class="button" onclick="generateTable()">表を生成</button><br>
    <table id="dataTable" style="display:none;">
        <thead>
            <tr>
                <th>項目</th>
                <th>数値</th>
                <th>構成比(%)</th>
            </tr>
        </thead>
        <tbody id="tableBody"></tbody>
        <tfoot>
            <tr>
                <td>合計</td>
                <td id="totalValue">0</td>
                <td id="totalRatio">100.00</td>
            </tr>
        </tfoot>
    </table>
    <select id="graphType">
        <option value="pie">円グラフ</option>
        <option value="bar">棒グラフ</option>
    </select>
    <button class="button" onclick="generateChart()">グラフ生成</button>
    <button class="button" onclick="clearTable()">クリア</button>
    <button class="button" onclick="saveData()">データ保存</button>
    <button class="button" onclick="loadData()">データ呼び出し</button>
    <button class="button" onclick="exportGraph('jpg')">JPGでエクスポート</button>
    <button class="button" onclick="exportGraph('html')">HTMLでエクスポート</button>
    <div id="chartContainer">
        <canvas id="chart"></canvas>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
    <script>
        let chart;

        function generateTable() {
            const rows = Number(document.getElementById('rowsInput').value);
            if (rows < 1) {
                alert('行数は1以上を指定してください。');
                return;
            }
            const tbody = document.getElementById('tableBody');
            tbody.innerHTML = '';
            for (let i = 0; i < rows; i++) {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td><input type="text" class="item" value="項目${i + 1}"></td>
                    <td><input type="number" class="value" placeholder="数値"></td>
                    <td><input type="number" class="ratio" placeholder="構成比" step="0.01" readonly></td>
                `;
                tbody.appendChild(row);
            }
            document.getElementById('dataTable').style.display = 'table';
            updateTotals();
        }

        function updateTotals() {
            const values = document.querySelectorAll('.value');
            const ratios = document.querySelectorAll('.ratio');
            const unit = document.getElementById('unitInput').value || '';
            let totalValue = 0;

            values.forEach(input => totalValue += Number(input.value) || 0);

            let totalRatio = 0;
            values.forEach((input, i) => {
                const value = Number(input.value) || 0;
                const ratio = totalValue > 0 ? (value / totalValue * 100) : 0;
                ratios[i].value = ratio.toFixed(2);
                totalRatio += ratio;
            });

            document.getElementById('totalValue').textContent = totalValue + (unit ? ` ${unit}` : '');
            document.getElementById('totalRatio').textContent = totalRatio.toFixed(2);
        }

        function generateChart() {
            const items = document.querySelectorAll('.item');
            const values = document.querySelectorAll('.value');
            const ratios = document.querySelectorAll('.ratio');
            const title = document.getElementById('titleInput').value || 'グラフ';
            const unit = document.getElementById('unitInput').value || '';
            const description = document.getElementById('descriptionInput').value || '';
            const graphType = document.getElementById('graphType').value;

            const labels = [], data = [];
            let isValid = true;

            items.forEach((item, i) => {
                if (item.value && values[i].value) {
                    labels.push(`${item.value} (${values[i].value}${unit ? ` ${unit}` : ''}, ${ratios[i].value}%)`);
                    data.push(Number(values[i].value));
                } else {
                    isValid = false;
                }
            });

            if (!isValid || labels.length === 0) {
                alert('すべての項目名と数値を入力してください。');
                return;
            }

            if (chart) chart.destroy();

            const ctx = document.getElementById('chart').getContext('2d');
            chart = new Chart(ctx, {
                type: graphType,
                data: {
                    labels: labels,
                    datasets: [{
                        label: graphType === 'bar' ? `数値 (${unit || '単位なし'})` : '',
                        data: data,
                        backgroundColor: graphType === 'pie' ? ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF'] : '#0078d4'
                    }]
                },
                options: {
                    plugins: {
                        title: { 
                            display: true, 
                            text: [title, description], 
                            font: { size: 18, weight: 'bold' },
                            padding: { top: 10, bottom: 10 }
                        },
                        legend: { 
                            position: graphType === 'pie' ? 'right' : 'top',
                            labels: { font: { size: 12 } }
                        },
                        datalabels: {
                            color: graphType === 'pie' ? '#fff' : '#000',
                            backgroundColor: graphType === 'pie' ? 'rgba(0,0,0,0.5)' : null,
                            borderRadius: graphType === 'pie' ? 3 : 0,
                            anchor: graphType === 'pie' ? 'center' : 'end',
                            align: graphType === 'pie' ? 'center' : 'top',
                            formatter: (value, context) => {
                                const label = context.chart.data.labels[context.dataIndex];
                                const ratio = (context.chart.data.datasets[0].data[context.dataIndex] / context.chart.data.datasets[0].data.reduce((a, b) => a + b, 0) * 100).toFixed(2);
                                return graphType === 'pie' ? label.split(' ')[0] + `\n${value}${unit ? ` ${unit}` : ''} (${ratio}%)` : `${value}${unit ? ` ${unit}` : ''}`;
                            },
                            font: { size: 12 }
                        }
                    },
                    scales: graphType === 'bar' ? { 
                        y: { 
                            beginAtZero: true, 
                            title: { display: true, text: unit || '数値' }
                        },
                        x: { ticks: { font: { size: 10 } } }
                    } : {}
                },
                plugins: [ChartDataLabels]
            });
        }

        function clearTable() {
            document.getElementById('tableBody').innerHTML = '';
            document.getElementById('titleInput').value = '';
            document.getElementById('rowsInput').value = '1';
            document.getElementById('unitInput').value = '';
            document.getElementById('descriptionInput').value = '';
            document.getElementById('totalValue').textContent = '0';
            document.getElementById('totalRatio').textContent = '100.00';
            document.getElementById('dataTable').style.display = 'none';
            if (chart) chart.destroy();
        }

        function saveData() {
            const items = document.querySelectorAll('.item');
            const values = document.querySelectorAll('.value');
            const ratios = document.querySelectorAll('.ratio');
            const title = document.getElementById('titleInput').value;
            const unit = document.getElementById('unitInput').value;
            const description = document.getElementById('descriptionInput').value;
            const data = {
                title,
                unit,
                description,
                rows: Array.from(items).map((item, i) => ({
                    item: item.value,
                    value: values[i].value,
                    ratio: ratios[i].value
                }))
            };
            localStorage.setItem('graphData', JSON.stringify(data));
            alert('データが保存されました。');
        }

        function loadData() {
            const data = JSON.parse(localStorage.getItem('graphData'));
            if (!data) {
                alert('保存されたデータがありません。');
                return;
            }
            document.getElementById('titleInput').value = data.title;
            document.getElementById('rowsInput').value = data.rows.length;
            document.getElementById('unitInput').value = data.unit;
            document.getElementById('descriptionInput').value = data.description;
            generateTable();
            const items = document.querySelectorAll('.item');
            const values = document.querySelectorAll('.value');
            const ratios = document.querySelectorAll('.ratio');
            data.rows.forEach((row, i) => {
                items[i].value = row.item;
                values[i].value = row.value;
                ratios[i].value = row.ratio;
            });
            updateTotals();
        }

        function exportGraph(format) {
            const title = document.getElementById('titleInput').value || 'グラフ';
            const description = document.getElementById('descriptionInput').value || '';
            const canvas = document.getElementById('chart');
            if (!chart) {
                alert('先にグラフを生成してください。');
                return;
            }

            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = 800;
            tempCanvas.height = 600;
            const ctx = tempCanvas.getContext('2d');

            // PowerPoint風デザイン
            ctx.fillStyle = '#ffffff';
            ctx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
            ctx.fillStyle = '#0078d4';
            ctx.fillRect(0, 0, tempCanvas.width, 80);
            ctx.font = 'bold 28px Segoe UI';
            ctx.fillStyle = '#ffffff';
            ctx.textAlign = 'left';
            ctx.fillText(title, 20, 50);
            ctx.font = '16px Segoe UI';
            ctx.fillStyle = '#333333';
            ctx.textAlign = 'left';

            // 説明文を複数行で描画
            const maxWidth = tempCanvas.width - 40;
            const lineHeight = 20;
            let words = description.split(' ');
            let line = '';
            let y = 120;
            for (let i = 0; i < words.length; i++) {
                const testLine = line + words[i] + ' ';
                const metrics = ctx.measureText(testLine);
                if (metrics.width > maxWidth && i > 0) {
                    ctx.fillText(line, 20, y);
                    line = words[i] + ' ';
                    y += lineHeight;
                } else {
                    line = testLine;
                }
            }
            ctx.fillText(line, 20, y);

            // グラフの描画位置とサイズを調整
            const graphWidth = 700;
            const graphHeight = 350;
            const scaleX = graphWidth / canvas.width;
            const scaleY = graphHeight / canvas.height;
            const scale = Math.min(scaleX, scaleY);
            const scaledWidth = canvas.width * scale;
            const scaledHeight = canvas.height * scale;
            const xPos = (tempCanvas.width - scaledWidth) / 2;
            const yPos = 150 + (y - 120);
            ctx.drawImage(canvas, xPos, yPos, scaledWidth, scaledHeight);

            if (format === 'jpg') {
                const link = document.createElement('a');
                link.href = tempCanvas.toDataURL('image/jpeg', 0.9);
                link.download = `${title}.jpg`;
                link.click();
            } else if (format === 'html') {
                const imgData = tempCanvas.toDataURL('image/jpeg', 0.9);
                const htmlContent = `
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <title>${title}</title>
                        <style>
                            body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background-color: #f4f4f4; }
                            .slide { width: 800px; height: 600px; background-color: #ffffff; margin: 20px auto; box-shadow: 0 4px 8px rgba(0,0,0,0.2); }
                            .header { background-color: #0078d4; height: 80px; padding: 20px; }
                            .header h1 { color: #ffffff; margin: 0; font-size: 28px; }
                            .description { padding: 10px 20px; color: #333333; font-size: 16px; }
                            .graph { display: block; margin: 20px auto; max-width: 700px; max-height: 350px; }
                        </style>
                    </head>
                    <body>
                        <div class="slide">
                            <div class="header"><h1>${title}</h1></div>
                            <div class="description">${description}</div>
                            <img src="${imgData}" alt="${title}" class="graph">
                        </div>
                    </body>
                    </html>`;
                const blob = new Blob([htmlContent], { type: 'text/html' });
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = `${title}.html`;
                link.click();
            }
        }

        document.addEventListener('input', (e) => {
            if (e.target.classList.contains('value') || e.target.classList.contains('unitInput')) {
                updateTotals();
            }
        });
    </script>
</body>
</html>


このソースをテキストエディタかメモ帳に貼り付けて、適当なファイル名.htmlにて保存してください。オフラインでも使えます。 


人気の投稿