コピーして組み込む
標準テンプレートをFileMaker Webビューアへ組み込める。
Internal standard training
FileMaker開発者が、Chart.jsを標準的な方法として実案件に組み込み、基本的なエラーを切り分けられるようになるための教材です。
Overview
この教材は、HTML/CSS/JavaScriptを専門的に学ぶものではありません。FileMaker案件でChart.jsを再利用しやすく、安全に組み込むための標準実装を学びます。
標準テンプレートをFileMaker Webビューアへ組み込める。
FileMakerから標準JSONを渡してグラフを更新できる。
グラフ種別、横棒、積み上げ、単位、複合グラフを設定できる。
表示されない原因をHTML、JSON、Chart.js、FileMaker設定に分けて確認できる。
既存のテストWebページへ組み込みやすいように、教材を5カテゴリに整理しています。カテゴリ名は「HTML実装」「FileMakerデータ連携」「グラフ設定」「スクリプト連携」「トラブルシューティング」です。
Category 1
HTMLは、Webビューア内に表示する部品を置く場所です。Chart.jsでは、グラフの描画先としてcanvasを使います。
<div class="chart-shell">
<canvas id="lessonChart"></canvas>
</div>
canvasはグラフを描く領域です。FileMaker側から直接canvasへ値を入れるのではなく、JavaScript関数へJSONを渡して描画します。
.chart-shell {
position: relative;
width: 100%;
height: 360px;
}
グラフが表示されない場合、canvasそのものではなく、親要素の高さが不足していることがあります。
| 確認項目 | 標準 | 理由 |
|---|---|---|
| canvasのid | lessonChart | JavaScriptから描画先を特定するため。 |
| 親要素 | .chart-shell | 表示サイズを安定させるため。 |
| Chart.js読み込み | 学習時はCDN、本番は埋め込み | 社内環境やオフライン環境でも動かすため。 |
Category 2
FileMakerは、集計したデータを標準JSONに変換し、Webビューア内のJavaScript関数へ渡します。
{
"type": "bar",
"title": "月別売上",
"labels": ["1月", "2月", "3月"],
"datasets": [
{
"label": "売上",
"data": [1200000, 1450000, 1380000]
}
],
"options": {
"unit": "円",
"indexAxis": "x",
"stacked": false
}
}
JSONSetElement ( "{}" ;
[ "type" ; "bar" ; JSONString ] ;
[ "title" ; "月別売上" ; JSONString ] ;
[ "labels[0]" ; "1月" ; JSONString ] ;
[ "labels[1]" ; "2月" ; JSONString ] ;
[ "datasets[0].label" ; "売上" ; JSONString ] ;
[ "datasets[0].data[0]" ; 1200000 ; JSONNumber ] ;
[ "datasets[0].data[1]" ; 1450000 ; JSONNumber ] ;
[ "options.unit" ; "円" ; JSONString ] ;
[ "options.indexAxis" ; "x" ; JSONString ]
)
数値はJSONStringではなくJSONNumberで渡します。文字列で渡すと、グラフ上は表示できても、軸・ツールチップ・計算で意図しない結果になる場合があります。
Category 3
Chart.jsの設定は、標準的なJSONのtypeとoptionsで切り替えます。
| 目的 | type | options | 使いどころ |
|---|---|---|---|
| 縦棒グラフ | bar | indexAxis: "x" | 月別売上、カテゴリ別件数 |
| 横棒グラフ | bar | indexAxis: "y" | 担当者別ランキング、項目名が長い集計 |
| 折れ線グラフ | line | unit: "%"など | 売上推移、粗利率推移 |
| ドーナツグラフ | doughnut | unit: "%"など | 構成比、内訳 |
| 複合グラフ | bar | mixed: true | 売上と達成率など、棒と線を同時表示 |
下の「ライブエディタ」でプリセットを選んで実際にグラフを触ってみてください。
Live Editor
JSONを編集するとグラフがリアルタイムで更新されます。FileMakerから渡す感覚でコードを触ってみてください。
type を変えるとグラフ種別が切り替わります
JSONSetElement ( "{}" ;
[ "type" ; "bar" ; JSONString ] ;
[ "data.labels[0]" ; "1月" ; JSONString ] ;
[ "data.datasets[0].data[0]"; 120 ; JSONNumber ] ;
[ "data.datasets[0].label" ; "売上" ; JSONString ] ;
[ "options.responsive" ; True ; JSONBoolean ] ;
[ "options.maintainAspectRatio" ; False ; JSONBoolean ]
)
Category 4
FileMakerとJavaScriptは、関数名と引数を決めて連携します。標準的な実装では、グラフ更新用の関数名を固定します。
関数名: updateChartFromFileMaker
引数: JSON文字列
OKか、ERROR:で始まる文字列かを確認する。FileMaker.PerformScriptWithOption(
"ChartJS|WebViewerイベント受信",
JSON.stringify(payload),
"0"
);
クリック結果やエラー通知をFileMakerへ戻す場合に使います。Webビューア側でJavaScriptからFileMakerスクリプトを実行できる設定にします。
Category 5
表示されない時は、いきなりコード全体を疑わず、どの層で止まっているかを順番に確認します。
| 症状 | 確認する場所 | よくある原因 | 対処 |
|---|---|---|---|
| 何も表示されない | HTML / Chart.js | Chart.js本体が未読み込み、canvas id違い | HTML内のscriptとcanvas idを確認する。 |
| 枠だけ表示される | CSS | 親要素の高さ不足 | .chart-shellに高さを指定する。 |
| エラー表示が出る | JSON | labelsとdataの数不一致、JSON型違い | JSONFormatElementsで構造を確認する。 |
| FileMakerから更新できない | Webビューア / スクリプト | Webビューア未読み込み、関数名違い、オブジェクト名違い | Webビューアの表示完了後に実行し、関数名を確認する。 |
| 横棒が意図通りでない | グラフ設定 | indexAxis未指定 | options.indexAxis = "y"を指定する。 |
FileMaker Example
FileMakerのグローバル変数($$変数)でデータ・色・設定を制御し、Webビューアに渡す計算式の実例です。
//OK7
Let ( [
// --- FileMaker 変数設定 ---
budget = GetAsNumber ( $$S粗利予算 ) ;
data1 = GetAsNumber ( $$S粗利 ) ;
data2 = GetAsNumber ( $$S予定S ) ;
data3 = GetAsNumber ( $$S予定A ) ;
data4 = GetAsNumber ( $$S予定B ) ;
data5 = GetAsNumber ( $$S予定C ) ;
data6 = GetAsNumber ( $$S予定D ) ;
redThreshold = If ( IsEmpty ( $$red ) ; 50 / 100 ; GetAsNumber ( $$red ) / 100 ) ;
yellowThreshold = If ( IsEmpty ( $$Yellow ) ; 80 / 100 ; GetAsNumber ( $$Yellow ) / 100 ) ;
colorRed = If ( IsEmpty ( $$赤色 ) ; "#f44336" ; $$赤色 ) ;
colorYellow = If ( IsEmpty ( $$黄色 ) ; "#FDB912" ; $$黄色 ) ;
colorGreen = If ( IsEmpty ( $$緑色 ) ; "#4caf50" ; $$緑色 ) ;
colorYoteiS = If ( IsEmpty ( $$予定色S ) ; "#2EA7E0" ; $$予定色S ) ;
colorYoteiA = If ( IsEmpty ( $$予定色A ) ; "#3EB7E0" ; $$予定色A ) ;
colorYoteiB = If ( IsEmpty ( $$予定色B ) ; "#4EC7E0" ; $$予定色B ) ;
colorYoteiC = If ( IsEmpty ( $$予定色C ) ; "#5ED7E0" ; $$予定色C ) ;
colorYoteiD = If ( IsEmpty ( $$予定色D ) ; "#6EE7E0" ; $$予定色D ) ;
borderWidth = If ( $$区切線 = "OFF" ; 0 ; 1 ) ;
htmlData =
"data:text/html;charset=utf-8," &
"<!DOCTYPE html><html><head><meta charset='UTF-8'>" &
"<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>" &
"<style>" &
"body{margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:#FFF;}" &
".container{position:relative;width:200px;height:200px;}" &
".center-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;}" &
".center-text h2{margin:0;font-size:22px;font-weight:bold;}" &
"</style></head><body>" &
"<div class='container'>" &
"<canvas id='realValueDoughnutChart'></canvas>" &
"<div class='center-text'><h2>0%</h2></div>" &
"</div>" &
"<script>(function(){" &
"const FREEZE_CHART_ON_HOVER = true;" &
"const ANIMATE_COUNT_UP = true;" &
"const ENABLE_TOOLTIP_ON_HOVER = true;" &
"const HIDE_TEXT_ON_HOVER = false;" &
"let isInitialAnimation = true;" &
"const budget = " & budget & ";" &
"const borderWidth = " & borderWidth & ";" &
"const dataSources = [" &
"{label:'受注済', value:" & data1 & "}," &
"{label:'ランクS', value:" & data2 & "}," &
"{label:'ランクA', value:" & data3 & "}," &
"{label:'ランクB', value:" & data4 & "}," &
"{label:'ランクC', value:" & data5 & "}," &
"{label:'ランクD', value:" & data6 & "}" &
"];" &
"const redThreshold = " & redThreshold & ";" &
"const yellowThreshold = " & yellowThreshold & ";" &
"const colorRed = '" & colorRed & "';" &
"const colorYellow = '" & colorYellow & "';" &
"const colorGreen = '" & colorGreen & "';" &
"const yoteiColorMap = {" &
"'ランクS':'" & colorYoteiS & "'," &
"'ランクA':'" & colorYoteiA & "'," &
"'ランクB':'" & colorYoteiB & "'," &
"'ランクC':'" & colorYoteiC & "'," &
"'ランクD':'" & colorYoteiD & "'" &
"};" &
"const activeData = dataSources.filter(d => d.value > 0);" &
"const dataLabels = activeData.map(d => d.label);" &
"const dataValues = activeData.map(d => d.value);" &
"const sJuchuRatio = budget > 0 ? " & data1 & " / budget : 0;" &
"let sJuchuColor = colorGreen;" &
"if (sJuchuRatio < redThreshold) { sJuchuColor = colorRed; }" &
"else if (sJuchuRatio < yellowThreshold) { sJuchuColor = colorYellow; }" &
"const backgroundColors = activeData.map(d => {" &
"if (d.label === '受注済') return sJuchuColor;" &
"return yoteiColorMap[d.label] || '#ccc';" &
"});" &
"const totalAchieved = dataValues.reduce((sum, v) => sum + v, 0);" &
"const totalAchievementRatio = budget > 0 ? totalAchieved / budget : 0;" &
"const remainder = Math.max(0, budget - totalAchieved);" &
"if (remainder > 0 || totalAchieved === 0) {" &
"dataLabels.push('残り');" &
"dataValues.push(remainder > 0 ? remainder : budget);" &
"backgroundColors.push('#eee');" &
"}" &
"const config = { type:'doughnut', data:{ labels:dataLabels, datasets:[{" &
"data:dataValues, backgroundColor:backgroundColors," &
"borderWidth:borderWidth, borderColor:'#FFF'," &
"cutout:'60%', hoverOffset: FREEZE_CHART_ON_HOVER ? 0 : 4" &
"}]}, options:{ responsive:true, maintainAspectRatio:false," &
"animation: ANIMATE_COUNT_UP ? {" &
"duration:1200," &
"onProgress: function(a){ if(isInitialAnimation){" &
"const pct=(a.currentStep/a.numSteps*totalAchievementRatio*100).toFixed(2);" &
"const h2=a.chart.canvas.parentNode.querySelector('.center-text h2');" &
"if(h2) h2.textContent=pct+'%';" &
"}}," &
"onComplete: function(a){" &
"const h2=a.chart.canvas.parentNode.querySelector('.center-text h2');" &
"if(h2) h2.textContent=(totalAchievementRatio*100).toFixed(2)+'%';" &
"isInitialAnimation=false;" &
"}," &
"transitions:{active:{animation:{duration:0}}}" &
"} : false," &
"plugins:{legend:{display:false}," &
"tooltip:{enabled:ENABLE_TOOLTIP_ON_HOVER," &
"callbacks:{" &
"label:function(t){return ' '+(t.label==='残り'?'予算残':'金額')+': ¥'+t.raw.toLocaleString();}," &
"title:function(t){return t[0].label==='残り'?'予算残':t[0].label;}" &
"}}}" &
"}};" &
"new Chart(document.getElementById('realValueDoughnutChart'), config);" &
"if(HIDE_TEXT_ON_HOVER){" &
"const cv=document.getElementById('realValueDoughnutChart');" &
"const ct=document.querySelector('.center-text');" &
"if(cv&&ct){" &
"cv.addEventListener('mouseenter',function(){ct.style.display='none';});" &
"cv.addEventListener('mouseleave',function(){ct.style.display='flex';});" &
"}" &
"}" &
"if(!ANIMATE_COUNT_UP){" &
"const h2=document.querySelector('.center-text h2');" &
"if(h2) h2.textContent=(totalAchievementRatio*100).toFixed(2)+'%';" &
"}" &
"})();</script></body></html>"
];
htmlData
)