OpenAI o3でマインドマップを作成する
OpenAI o3-miniとo3-mini-hghがChatGPTで使えるようになりました。使ってみた感想は、マインドマップ作れるのかと思いました。もっと正確に指示をすれば商用レベルのものまで作れる気がしました。
今回は、ChatGPT o3-mini-highを使用してどれくらいのことができるのか確認します。
お題:ブラウザで出来るマインドマップツールを作成してください。
上記を実行すると、ChatGPTが思考を開始します。
すると、次のコードを作成してくれました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ブラウザ上のマインドマップツール</title>
<style>
/* ページ全体および SVG を画面全体に展開 */
html, body, svg {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
/* ノードにマウスカーソルを合わせたときに移動可能であることを示す */
.node {
cursor: move;
}
/* リンク(線)のスタイル */
.link {
stroke: #888;
stroke-width: 2;
}
</style>
</head>
<body>
<!-- マインドマップ描画用の SVG 要素 -->
<svg id="mindmap"></svg>
<script>
// SVG 要素と管理用の変数
const svg = document.getElementById("mindmap");
const nodes = []; // ノード情報を保持({ id, x, y, text })
const links = []; // リンク情報を保持({ id, from, to })
let nodeIdCounter = 0;
let linkIdCounter = 0;
// ノードのドラッグ時に利用する変数
let selectedNode = null;
let offsetX = 0, offsetY = 0;
// Shift+クリックでリンク作成する場合の始点ノード
let linkStartNode = null;
// ノードを作成する関数
function createNode(x, y) {
const label = prompt("ノードの名前を入力してください:", "ノード" + nodeIdCounter);
if (label === null) { return; } // キャンセルされた場合は中断
const node = { id: nodeIdCounter++, x: x, y: y, text: label };
nodes.push(node);
drawNode(node);
}
// ノードを描画する関数
function drawNode(node) {
// <g> 要素でノード全体をグループ化
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
group.setAttribute("class", "node");
group.setAttribute("data-id", node.id);
group.setAttribute("transform", `translate(${node.x}, ${node.y})`);
// ノードの背景となる円
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("r", 30);
circle.setAttribute("cx", 0);
circle.setAttribute("cy", 0);
circle.setAttribute("fill", "#FFD700");
circle.setAttribute("stroke", "#000");
circle.setAttribute("stroke-width", 2);
// ノードのテキストラベル
const textElem = document.createElementNS("http://www.w3.org/2000/svg", "text");
textElem.setAttribute("x", 0);
textElem.setAttribute("y", 5);
textElem.setAttribute("text-anchor", "middle");
textElem.textContent = node.text;
group.appendChild(circle);
group.appendChild(textElem);
svg.appendChild(group);
// イベントリスナーの設定
group.addEventListener("mousedown", onNodeMouseDown);
group.addEventListener("mouseup", onNodeMouseUp);
group.addEventListener("click", onNodeClick);
}
// ノードの位置を更新する関数
function updateNodePosition(node, groupElem) {
groupElem.setAttribute("transform", `translate(${node.x}, ${node.y})`);
updateLinks();
}
// リンクを描画する関数
function drawLink(link) {
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("class", "link");
line.setAttribute("data-id", link.id);
// ノードの下に描画するため、最前面ではなく最背面に挿入
svg.insertBefore(line, svg.firstChild);
updateLinkPosition(link, line);
}
// リンクの座標を更新する関数
function updateLinkPosition(link, lineElem) {
const fromNode = nodes.find(n => n.id === link.from);
const toNode = nodes.find(n => n.id === link.to);
if (fromNode && toNode) {
lineElem.setAttribute("x1", fromNode.x);
lineElem.setAttribute("y1", fromNode.y);
lineElem.setAttribute("x2", toNode.x);
lineElem.setAttribute("y2", toNode.y);
}
}
// 全リンクの位置を更新する関数
function updateLinks() {
links.forEach(link => {
const lineElem = svg.querySelector(`line[data-id='${link.id}']`);
if (lineElem) {
updateLinkPosition(link, lineElem);
}
});
}
// ノードのドラッグ開始処理
function onNodeMouseDown(event) {
// Shift キーが押されている場合はリンク作成モードなのでドラッグしない
if (event.shiftKey) return;
const group = event.currentTarget;
const nodeId = parseInt(group.getAttribute("data-id"), 10);
selectedNode = nodes.find(n => n.id === nodeId);
// マウス座標と SVG 内の座標の変換
const pt = svg.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
offsetX = svgP.x - selectedNode.x;
offsetY = svgP.y - selectedNode.y;
event.stopPropagation();
}
// ノードのドラッグ中の処理
function onNodeMouseMove(event) {
if (selectedNode) {
const pt = svg.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
selectedNode.x = svgP.x - offsetX;
selectedNode.y = svgP.y - offsetY;
const group = svg.querySelector(`g[data-id='${selectedNode.id}']`);
if (group) {
updateNodePosition(selectedNode, group);
}
}
}
// ノードのドラッグ終了処理
function onNodeMouseUp(event) {
if (selectedNode) {
selectedNode = null;
}
}
// ノードクリック時の処理(Shift キーでリンク作成)
function onNodeClick(event) {
if (event.shiftKey) {
const group = event.currentTarget;
const nodeId = parseInt(group.getAttribute("data-id"), 10);
const clickedNode = nodes.find(n => n.id === nodeId);
if (!linkStartNode) {
// 始点ノードを設定し、視覚的に強調
linkStartNode = clickedNode;
group.querySelector("circle").setAttribute("stroke", "red");
} else {
if (linkStartNode.id !== clickedNode.id) {
// 始点とクリックしたノードが異なる場合、リンクを作成
const link = { id: linkIdCounter++, from: linkStartNode.id, to: clickedNode.id };
links.push(link);
drawLink(link);
}
// 始点ノードの強調を解除し、リンク作成モードをリセット
const startGroup = svg.querySelector(`g[data-id='${linkStartNode.id}']`);
if (startGroup) {
startGroup.querySelector("circle").setAttribute("stroke", "#000");
}
linkStartNode = null;
}
event.stopPropagation();
}
}
// SVG の背景部分をダブルクリックでノードを作成
svg.addEventListener("dblclick", function(event) {
// 背景(SVG自体)がダブルクリックされた場合のみ
if (event.target === svg) {
const pt = svg.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
createNode(svgP.x, svgP.y);
}
});
// SVG 内でのマウス移動時にドラッグ処理を行う
svg.addEventListener("mousemove", onNodeMouseMove);
// SVG 内でのマウスアップ時にドラッグ終了処理を実行
svg.addEventListener("mouseup", onNodeMouseUp);
// 背景クリック時:Shift キーが押されていなければリンク作成モードをキャンセル
svg.addEventListener("click", function(event) {
if (linkStartNode && !event.shiftKey) {
const startGroup = svg.querySelector(`g[data-id='${linkStartNode.id}']`);
if (startGroup) {
startGroup.querySelector("circle").setAttribute("stroke", "#000");
}
linkStartNode = null;
}
});
</script>
</body>
</html>
上記をmindmap.htmlと保存してブラウザで開きます。
使用方法も記載されています。
今回作成したマインドマップを使用すると、次のようなものが出来ました。