Chart.js https://www.chartjs.org/ X軸=時刻 の線グラフを描画するためのJSONデータ作成する
Java クラスを作成して、AJAX通信によるJSONデータ → Chart.js グラフ描画をデザインを除いた
可変の JSONデータ生成だけでも汎用的にならないかと考えました。
グラフ描画の点となるデータの定義の Javaオブジェクト
整数(int) と 小数点 (double) 各々のケースを考慮して用意します。
import java.io.Serializable; import java.time.LocalDateTime; /** * LocalDateTime - 整数値 */ public class IntPoint implements Serializable{ /** グラフX軸の時刻. */ public LocalDateTime time; /** グラフY軸の値. */ public Integer value; public IntPoint(){} public IntPoint(LocalDateTime time, int value){ this.time = time; this.value = value; } public void setTime(LocalDateTime time){ this.time = time; } public void setValue(int value){ this.value = value; } @Override public String toString() { return "Pointer(" + time + ", " + value + ")"; } }
import java.io.Serializable; import java.time.LocalDateTime; /** * LocalDateTime - 小数点 */ public class DoublePoint implements Serializable{ /** グラフX軸の時刻. */ public LocalDateTime time; /** グラフY軸の値. */ public Double value; public DoublePoint(){} public DoublePoint(LocalDateTime time, double value){ this.time = time; this.value = value; } public void setTime(LocalDateTime time) { this.time = time; } public void setValue(Double value) { this.value = value; } }
JSON生成するクラス、Google gson を使います。
import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.yipuran.gsonhelper.LocalDateTimeAdapter; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * Time Chart Json Builder. * <PRE> * * String json = new TimeChartJsonBuilder() * .addIntegers(list) * .addYAxesMin(10) * .addYAxesMax(500) * .addStepSize(20) * .addFontSize(12) * .build(); * * String json = new TimeChartJsonBuilder() * .addDoubles(list) * .addMaxTicksLimit(5) * .build(); * </PRE> */ public final class TimeChartJsonBuilder{ private Gson gson; private Map<String, Object> map; private int min = 0; private int max = 100; private Integer yAxesMax = null; private int yAxesStepSize = 10; private Double doubleStepsize = null; private int fontSize = 12; private Integer maxTicksLimit = null; private LocalDateTime xAxesMin = null; private LocalDateTime xAxesMax = null; private List<Map<String, Object>> mlist = new ArrayList<>(); public TimeChartJsonBuilder(){ map = new HashMap<>(); gson = new GsonBuilder().registerTypeHierarchyAdapter(LocalDateTime.class, LocalDateTimeAdapter.of("yyyy/MM/dd HH:mm:ss.SSS")).create(); } public TimeChartJsonBuilder addIntegers(List<IntPoint> list){ if (list.size() > 0){ max = list.stream().max((a, b)->a.value.compareTo(b.value)).map(e->e.value.intValue()).orElse(100); LocalDateTime tmin = list.stream().map(e->e.time).min((a, b)->a.compareTo(b)).orElse(null); if (tmin != null){ xAxesMin = xAxesMin==null ? tmin : xAxesMin.compareTo(tmin) < 0 ? xAxesMin : tmin; } LocalDateTime tmax = list.stream().map(e->e.time).max((a, b)->a.compareTo(b)).orElse(null); if (tmax != null){ xAxesMax = xAxesMax==null ? tmax : xAxesMax.compareTo(tmax) < 0 ? tmax : xAxesMax; } Map<String, Object> map = new HashMap<>(); map.put("plist", list); mlist.add(map); } return this; } public TimeChartJsonBuilder addDoubles(List<DoublePoint> list){ if (list.size() > 0){ max = list.stream().max((a, b)->a.value.compareTo(b.value)).map(e->e.value.intValue()).orElse(100); LocalDateTime tmin = list.stream().map(e->e.time).min((a, b)->a.compareTo(b)).orElse(null); if (tmin != null){ xAxesMin = xAxesMin==null ? tmin : xAxesMin.compareTo(tmin) < 0 ? xAxesMin : tmin; } LocalDateTime tmax = list.stream().map(e->e.time).max((a, b)->a.compareTo(b)).orElse(null); if (tmax != null){ xAxesMax = xAxesMax==null ? tmax : xAxesMax.compareTo(tmax) < 0 ? tmax : xAxesMax; } Map<String, Object> map = new HashMap<>(); map.put("plist", list); mlist.add(map); } return this; } public TimeChartJsonBuilder addYAxesMin(int min){ this.min = min; return this; } public TimeChartJsonBuilder addYAxesMax(int yAxesMax){ this.yAxesMax = yAxesMax; return this; } public TimeChartJsonBuilder addFontSize(int fontSize){ this.fontSize = fontSize; return this; } public TimeChartJsonBuilder addStepSize(int yAxesStepSize){ this.yAxesStepSize = yAxesStepSize; return this; } public TimeChartJsonBuilder addStepSize(double yAxesStepSize){ this.doubleStepsize = yAxesStepSize; return this; } public TimeChartJsonBuilder addMaxTicksLimit(int maxTicksLimit){ if (maxTicksLimit < 1) throw new IllegalArgumentException("maxTicksLimit must be over 1"); this.maxTicksLimit = maxTicksLimit; return this; } public TimeChartJsonBuilder addXAxesMin(LocalDateTime xAxesMin){ this.xAxesMin = xAxesMin; return this; } public TimeChartJsonBuilder addXAxesMax(LocalDateTime xAxesMax){ this.xAxesMax = xAxesMax; return this; } public String build(){ map.put("list", mlist); map.put("yAxesMin", min); map.put("yAxesStepSize", yAxesStepSize <= 0 ? max : yAxesStepSize); max = (max / yAxesStepSize + 1) * yAxesStepSize; map.put("yAxesMax", yAxesMax==null ? max : yAxesMax); if (doubleStepsize != null) map.put("yAxesStepSize", doubleStepsize); if (maxTicksLimit != null) map.put("maxTicksLimit", maxTicksLimit); map.put("yAxesFontSize", fontSize); map.put("xAxesMin", xAxesMin); map.put("xAxesMax", xAxesMax); return gson.toJson(map); } }
Wicket Pageでの使用
List<IntPoint> list ; // list に描画するデータを格納 TimeChartJsonBuilder builder = new TimeChartJsonBuilder().addIntegers(list) .addYAxesMax(200) .addStepSize(20); String json = builder.build(); try(StringResourceStream s = new StringResourceStream(json)){ getRequestCycle().scheduleRequestHandlerAfterCurrent( new ResourceStreamRequestHandler(s) ); }catch(IOException e){ logger.warn(e.getMessage(), e); }
注意しなければならないのは、TimeChartJsonBuilder の addIntegers で渡すリストは
グラフ描画の為の時系列に並んでなければならない。必要であればメソッドの実行前にソート
する必要がある。
HTML Chart.js と moment-with-locale.js を使用できるように ヘッダは記述しておく。
https://momentjs.com/
<div class="chart"> <canvas id="myChart"></canvas> </div>
JSソース
var graph = { xLabels: [], yLabels: [], datasets: [{ label: "Value", lineTension: 0, backgroundColor: "rgba(185, 64, 71, 0.6)", borderColor: "rgba(185, 64, 71, 0.6)", borderWidth: 1, borderCapStyle: 'round', borderDash: [], borderDashOffset: 0.0, borderJoinStyle: "round", pointBorderColor: "rgba(185, 64, 71, 0.6)", pointBackgroundColor: "rgba(185, 64, 71, 0.6)", pointBorderWidth: 0, pointHoverRadius: 4, pointHoverBackgroundColor: "rgba(185, 64, 71, 0.6)", pointHoverBorderColor: "rgba(185, 64, 71, 0.6)", pointHoverBorderWidth: 4, pointRadius: 0, pointHitRadius: 10, fill: false, spanGaps: false, data: [], yAxisID: "y-axis-1", }] }; var options = { responsive: true, title:{ display:true, text:'Sample' }, scales: { xAxes: [{ display: true, scaleLabel: { display: true, labelString: 'Time' }, type: "time", time: { displayFormat: true, displayFormats: { minute: "HH:mm" }, stepSize: 2 }, }], yAxes: [{ display: true, id: "y-axis-1", offset: true, scaleLabel: { display: true, labelString: 'Value', fontSize: 22 }, ticks: {} }] }, chartArea: { /* グラフ領域の背景色 */ backgroundColor: 'rgba(240, 248, 255, 1)' }, tooltips: { callbacks: { title: function(tooltipItem, data){ return moment(tooltipItem[0].xLabel._d).format('YYYY年M月D日(ddd)A hh:mm:ss'); }, label: function(tooltipItem, data){ return "value:" + tooltipItem.yLabel; } } }, }; $(function(){ /* moment.js 設定 */ moment.locale('ja'); Chart.pluginService.register({ beforeDraw: function(c){ if (c.config.options.chartArea && c.config.options.chartArea.backgroundColor) { var ctx = c.chart.ctx; var chartArea = c.chartArea; ctx.fillStyle = "rgba(255, 255, 255, 1)"; // 外側背景色の指定 ctx.fillRect(0, 0, c.chart.width, c.chart.height); // 外側背景色描画 ctx.save(); ctx.fillStyle = c.config.options.chartArea.backgroundColor; ctx.fillRect(chartArea.left , chartArea.top, chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); ctx.restore(); } } }); var ctx = document.getElementById("myChart").getContext("2d"); var myChart = new Chart(ctx, { type: 'line', data: graph, options: options }); var drawGraph = function(e){ graph.datasets[0].data = []; myChart.options.scales.yAxes[0].ticks['min'] = parseInt(e.yAxesMin, 10); myChart.options.scales.yAxes[0].ticks['max'] = parseInt(e.yAxesMax, 10); myChart.options.scales.yAxes[0].ticks['stepSize'] = parseInt(e.yAxesStepSize, 10); myChart.options.scales.yAxes[0].ticks['fontSize'] = parseInt(e.yAxesFontSize, 10); if (e.xAxesMin != null){ myChart.options.scales.xAxes[0].time['min'] = moment(e.xAxesMin, "YYYY/MM/DD HH:mm:ss.SSS"); } if (e.xAxesMax != null){ myChart.options.scales.xAxes[0].time['max'] = moment(e.xAxesMax, "YYYY/MM/DD HH:mm:ss.SSS"); } if (e.maxTicksLimit != undefined){ myChart.options.scales.yAxes[0].ticks['maxTicksLimit'] = e.maxTicksLimit; } $.each(e.list, function(i, data){ $.each(data.plist, function(n, v){ graph.datasets[i].data.push( { t:moment(v.time, "YYYY-MM-DD HH:mm:ss.SSS"), y:v.value } ); }); }); myChart.update(); }; var init_url = "/chartsample/line" $.ajax({ type: 'POST', timeout: 10000, url: init_url, data: { }, dataType: 'json', cache: false, }).done(function(e){ drawGraph(e); }).fail(function(e){ // 通信エラー }).always(function(e){ }); });
グラフの線、太さ、線の種類、色、背景色、スケールの文字フォントサイズ、
タイトルの文字フォントサイズ、ツールチップの表示コンテンツ、、、
沢山、JSで書かなくてはならないのだが、
1本の線だけではなく複数の線を描画するケースを考慮して、
TimeChartJsonBuilder は、複数の線のJSONデータを生成するために、
List → 複数の線、
Map → 各線のJSON 配列
private List<Map<String, Object>> mlist = new ArrayList<>();
で構成して、AJAX通信で受信したJSプログラム側が、Chart.js グラフオブジェクトに展開する時に、
1本ずつ展開する。
時刻フォーマットは、TimeChartJsonBuilder で決められており、
時刻のデータ moment-with-locale.js で生成する。
$.each(e.list, function(i, data){ $.each(data.plist, function(n, v){ graph.datasets[i].data.push( { t:moment(v.time, "YYYY-MM-DD HH:mm:ss.SSS"), y:v.value } ); }); });
サンプルグラフ描画