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>
</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 ;
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.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 }
);
});
});
サンプルグラフ描画