Use ECharts instead of Chart.js

This commit is contained in:
cuigh 2018-04-02 16:19:12 +08:00
parent d5b5ff114a
commit 8d2575d229
21 changed files with 958 additions and 20343 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1
assets/echarts/echarts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1120,22 +1120,61 @@ var Swirl;
(function (Swirl) {
var Core;
(function (Core) {
class GraphOptions {
class ChartOptions {
constructor() {
this.type = "line";
this.width = 12;
this.height = 150;
this.height = 200;
}
}
Core.GraphOptions = GraphOptions;
class Graph {
constructor(elem, opts) {
this.$elem = $(elem);
this.opts = $.extend(new GraphOptions(), opts);
this.name = this.$elem.data("chart-name");
Core.ChartOptions = ChartOptions;
class Chart {
constructor(opts) {
this.opts = $.extend(new ChartOptions(), opts);
this.createElem();
}
getName() {
return this.name;
createElem() {
this.$elem = $(`<div class="column is-${this.opts.width}" data-name="${this.opts.name}">
<div class="card">
<header class="card-header">
<p class="card-header-title">${this.opts.title}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content" style="height: ${this.opts.height}px"></div>
</div>
</div>`);
}
init() {
let opt = {
legend: {
x: 'right',
},
tooltip: {
trigger: 'axis',
axisPointer: {
animation: false
}
},
xAxis: {
type: 'time',
splitLine: { show: false },
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
splitLine: { show: false },
axisLabel: {
formatter: this.formatValue.bind(this),
},
},
};
this.config(opt);
this.chart = echarts.init(this.$elem.find("div.card-content").get(0));
this.chart.setOption(opt, true);
}
getElem() {
return this.$elem;
@ -1143,54 +1182,13 @@ var Swirl;
getOptions() {
return this.opts;
}
}
Core.Graph = Graph;
class ValueGraph extends Graph {
constructor(elem, opts) {
super(elem, opts);
}
setData(d) {
}
resize(w, h) {
}
}
Core.ValueGraph = ValueGraph;
class ComplexGraph extends Graph {
constructor(elem, opts) {
super(elem, opts);
if (!this.opts.colors) {
this.opts.colors = ComplexGraph.defaultColors;
}
this.config = {
type: opts.type,
data: {},
options: {
responsive: false,
maintainAspectRatio: false,
animation: {
duration: 0,
},
}
};
this.fillConfig();
this.ctx = $(elem).find("canvas").get(0).getContext('2d');
if (opts.height) {
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
}
this.chart = new Chart(this.ctx, this.config);
}
setData(d) {
}
resize(w, h) {
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
resize() {
this.chart.resize();
}
fillConfig() {
config(opt) {
}
static formatValue(value, unit) {
switch (unit) {
formatValue(value) {
switch (this.opts.unit) {
case "percent:100":
return value.toFixed(1) + "%";
case "percent:1":
@ -1220,136 +1218,166 @@ var Swirl;
}
}
}
ComplexGraph.defaultColors = [
'rgb(255, 99, 132)',
'rgb(75, 192, 192)',
'rgb(255, 159, 64)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(255, 205, 86)',
'rgb(201, 203, 207)',
];
Core.ComplexGraph = ComplexGraph;
class VectorGraph extends ComplexGraph {
fillConfig() {
this.config.options.legend = {
position: "right"
};
this.config.options.tooltips = {
callbacks: {
label: (tooltipItem, data) => {
let label = data.labels[tooltipItem.index] + ": ";
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
if (typeof p == "number") {
label += ComplexGraph.formatValue(p, this.opts.unit);
}
return label;
}
Core.Chart = Chart;
class GaugeChart extends Chart {
constructor(opts) {
super(opts);
}
config(opt) {
$.extend(true, opt, {
grid: {
left: 0,
top: 20,
right: 0,
bottom: 0,
},
};
}
setData(d) {
this.config.data.datasets = [{
data: d.data,
backgroundColor: this.opts.colors,
}];
this.config.data.labels = d.labels;
this.chart.update();
}
}
Core.VectorGraph = VectorGraph;
class MatrixGraph extends ComplexGraph {
constructor(elem, opts) {
super(elem, opts);
}
fillConfig() {
this.config.options.scales = {
xAxes: [{
type: 'time',
time: {
unit: 'minute',
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
displayFormats: {
minute: 'HH:mm'
}
},
}],
};
if (this.opts.unit) {
this.config.options.scales.yAxes = [{
ticks: {
callback: (n) => ComplexGraph.formatValue(n, this.opts.unit),
}
}];
this.config.options.tooltips = {
callbacks: {
label: (tooltipItem, data) => {
let label = data.datasets[tooltipItem.datasetIndex].label + ": ";
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
label += ComplexGraph.formatValue(p.y, this.opts.unit);
return label;
}
xAxis: [
{
show: false,
},
};
}
],
yAxis: [
{
show: false,
},
],
});
}
setData(d) {
let datasets = (d);
datasets.forEach((ds, i) => {
let color = (i < this.opts.colors.length) ? this.opts.colors[i] : this.opts.colors[0];
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
ds.borderColor = color;
ds.borderWidth = 2;
ds.pointRadius = 2;
this.chart.setOption({
series: [
{
type: 'gauge',
radius: '100%',
center: ["50%", "58%"],
max: d.value,
axisLabel: { show: false },
pointer: { show: false },
detail: {
offsetCenter: [0, 0],
},
data: [{ value: d.value }]
}
]
});
this.config.data.datasets = d;
this.chart.update();
}
}
Core.MatrixGraph = MatrixGraph;
class GraphFactory {
static create(elem) {
let $elem = $(elem);
let opts = {
type: $elem.data("chart-type"),
unit: $elem.data("chart-unit"),
width: $elem.data("chart-width"),
height: $elem.data("chart-height"),
colors: $elem.data("chart-colors"),
};
Core.GaugeChart = GaugeChart;
class VectorChart extends Chart {
constructor(opts) {
super(opts);
}
config(opt) {
$.extend(true, opt, {
grid: {
left: 20,
top: 20,
right: 20,
bottom: 20,
},
legend: {
type: 'scroll',
orient: 'vertical',
},
tooltip: {
trigger: 'item',
formatter: (params, index) => {
return params.name + ": " + this.formatValue(params.value);
},
},
xAxis: [
{
show: false,
},
],
yAxis: [
{
show: false,
},
],
series: [{
type: this.opts.type,
radius: '80%',
center: ['45%', '50%'],
}],
});
}
setData(d) {
this.chart.setOption({
legend: {
data: d.legend,
},
series: [{
data: d.data,
}],
});
}
}
Core.VectorChart = VectorChart;
class MatrixChart extends Chart {
constructor(opts) {
super(opts);
}
config(opt) {
$.extend(true, opt, {
grid: {
left: 60,
top: 30,
right: 30,
bottom: 30,
},
tooltip: {
formatter: (params) => {
let html = params[0].axisValueLabel + '<br/>';
for (let i = 0; i < params.length; i++) {
html += params[i].marker + params[i].seriesName + ': ' + this.formatValue(params[i].value[1]) + '<br/>';
}
return html;
},
},
});
}
setData(d) {
d.series.forEach((s) => s.type = this.opts.type);
this.chart.setOption({
legend: {
data: d.legend,
},
series: d.series,
});
}
}
Core.MatrixChart = MatrixChart;
class ChartFactory {
static create(opts) {
switch (opts.type) {
case "value":
return new ValueGraph($elem, opts);
case "gauge":
return new GaugeChart(opts);
case "line":
case "bar":
return new MatrixGraph($elem, opts);
return new MatrixChart(opts);
case "pie":
return new VectorGraph($elem, opts);
return new VectorChart(opts);
}
return null;
}
}
Core.GraphFactory = GraphFactory;
class GraphPanelOptions {
Core.ChartFactory = ChartFactory;
class ChartDashboardOptions {
constructor() {
this.time = "30m";
this.refreshInterval = 15000;
this.period = 30;
this.refreshInterval = 15;
}
}
Core.GraphPanelOptions = GraphPanelOptions;
class GraphPanel {
constructor(elem, opts) {
Core.ChartDashboardOptions = ChartDashboardOptions;
class ChartDashboard {
constructor(elem, charts, opts) {
this.charts = [];
this.opts = $.extend(new GraphPanelOptions(), opts);
this.opts = $.extend(new ChartDashboardOptions(), opts);
this.$panel = $(elem);
this.$panel.children().each((i, e) => {
let g = GraphFactory.create(e);
if (g != null) {
this.charts.push(g);
}
});
charts.forEach(opts => this.createGraph(opts));
Core.Dispatcher.bind(this.$panel).on("remove-chart", e => {
let name = $(e.target).closest("div.column").data("chart-name");
let name = $(e.target).closest("div.column").data("name");
Core.Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", dlg => {
this.removeGraph(name);
dlg.close();
@ -1357,85 +1385,70 @@ var Swirl;
});
$(window).resize(e => {
$.each(this.charts, (i, g) => {
g.resize(0, 0);
g.resize();
});
});
this.refreshData();
}
refreshData() {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
}
this.refresh();
}
refresh() {
if (!this.timer) {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval * 1000);
}
}
}
refreshData() {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval * 1000);
}
}
stop() {
clearTimeout(this.timer);
this.timer = 0;
}
setTime(time) {
this.opts.time = time;
setPeriod(period) {
this.opts.period = period;
this.loadData();
}
addGraph(c) {
addGraph(opts) {
this.createGraph(opts);
this.loadData();
}
createGraph(opts) {
for (let i = 0; i < this.charts.length; i++) {
let chart = this.charts[i];
if (chart.getName() === c.name) {
if (chart.getOptions().name === opts.name) {
return;
}
}
let $chart = $(`<div class="column is-${c.width}" data-chart-name="${c.name}" data-chart-type="${c.type}" data-chart-unit="${c.unit}" data-chart-width="${c.width}" data-chart-height="${c.height}">
<div class="card">
<header class="card-header">
<p class="card-header-title">${c.title}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div style="height: ${c.height}px">
<canvas id="canvas_${c.name}"></canvas>
</div>
</div>
</div>
</div>`);
this.$panel.append($chart);
let g = GraphFactory.create($chart);
if (g != null) {
this.charts.push(g);
let chart = ChartFactory.create(opts);
if (chart != null) {
this.$panel.append(chart.getElem());
chart.init();
this.charts.push(chart);
}
this.loadData();
}
removeGraph(name) {
let index = -1;
for (let i = 0; i < this.charts.length; i++) {
let c = this.charts[i];
if (c.getName() === name) {
if (c.getOptions().name === name) {
index = i;
break;
}
}
if (index >= 0) {
console.info(this.charts.length);
let $elem = this.charts[index].getElem();
this.charts.splice(index, 1);
$elem.remove();
console.info(this.charts.length);
}
}
save() {
let charts = this.charts.map(c => {
return {
name: c.getName(),
name: c.getOptions().name,
width: c.getOptions().width,
height: c.getOptions().height,
};
@ -1456,37 +1469,37 @@ var Swirl;
}
loadData() {
let args = {
charts: this.charts.map(c => c.getName()).join(","),
time: this.opts.time,
charts: this.charts.map(c => c.getOptions().name).join(","),
period: this.opts.period,
};
if (this.opts.key) {
args.key = this.opts.key;
}
$ajax.get(`/system/chart/data`, args).json((d) => {
$.each(this.charts, (i, g) => {
if (d[g.getName()]) {
g.setData(d[g.getName()]);
if (d[g.getOptions().name]) {
g.setData(d[g.getOptions().name]);
}
});
});
}
}
Core.GraphPanel = GraphPanel;
Core.ChartDashboard = ChartDashboard;
})(Core = Swirl.Core || (Swirl.Core = {}));
})(Swirl || (Swirl = {}));
var Swirl;
(function (Swirl) {
var Modal = Swirl.Core.Modal;
var GraphPanel = Swirl.Core.GraphPanel;
var FilterBox = Swirl.Core.FilterBox;
var ChartDashboard = Swirl.Core.ChartDashboard;
class IndexPage {
constructor() {
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
this.panel = new GraphPanel("#div-charts", { name: "home" });
this.dashboard = new ChartDashboard("#div-charts", window.charts, { name: "home" });
$("#btn-add").click(this.showAddDlg.bind(this));
$("#btn-add-chart").click(this.addChart.bind(this));
$("#btn-save").click(() => {
this.panel.save();
this.dashboard.save();
});
}
showAddDlg() {
@ -1531,7 +1544,7 @@ var Swirl;
this.$charts.each((i, e) => {
if ($(e).find(":checked").length > 0) {
let c = this.charts[i];
this.panel.addGraph(c);
this.dashboard.addGraph(c);
}
});
Modal.close();
@ -2345,14 +2358,14 @@ var Swirl;
var Service;
(function (Service) {
var Modal = Swirl.Core.Modal;
var GraphPanel = Swirl.Core.GraphPanel;
var ChartDashboard = Swirl.Core.ChartDashboard;
class StatsPage {
constructor() {
let $cb_time = $("#cb-time");
if ($cb_time.length == 0) {
return;
}
this.panel = new GraphPanel("#div-charts", {
this.dashboard = new ChartDashboard("#div-charts", window.charts, {
name: "service",
key: $("#h2-service-name").text()
});
@ -2363,14 +2376,14 @@ var Swirl;
Modal.alert("Coming soon...");
});
$cb_time.change(e => {
this.panel.setTime($(e.target).val());
this.dashboard.setPeriod($(e.target).val());
});
$("#cb-refresh").change(e => {
if ($(e.target).prop("checked")) {
this.panel.refresh();
this.dashboard.refresh();
}
else {
this.panel.stop();
this.dashboard.stop();
}
});
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,416 @@
namespace Swirl.Core {
export class ChartOptions {
name: string;
title: string;
type?: string = "line";
unit?: string;
width?: number = 12;
height?: number = 200;
colors?: string[];
}
export abstract class Chart {
protected $elem: JQuery;
protected chart: echarts.ECharts;
protected opts?: ChartOptions;
protected constructor(opts: ChartOptions) {
this.opts = $.extend(new ChartOptions(), opts);
this.createElem();
}
private createElem() {
this.$elem = $(`<div class="column is-${this.opts.width}" data-name="${this.opts.name}">
<div class="card">
<header class="card-header">
<p class="card-header-title">${this.opts.title}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content" style="height: ${this.opts.height}px"></div>
</div>
</div>`);
}
init() {
let opt = {
legend: {
x: 'right',
},
tooltip: {
trigger: 'axis',
// formatter: function (params) {
// params = params[0];
// var date = new Date(params.name);
// return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear() + ' : ' + params.value[1];
// },
axisPointer: {
animation: false
}
},
xAxis: {
type: 'time',
splitLine: {show: false},
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
splitLine: {show: false},
axisLabel: {
formatter: this.formatValue.bind(this),
},
},
};
this.config(opt);
this.chart = echarts.init(<HTMLDivElement>this.$elem.find("div.card-content").get(0));
this.chart.setOption(opt, true);
}
getElem(): JQuery {
return this.$elem;
}
getOptions(): ChartOptions {
return this.opts;
}
resize() {
this.chart.resize();
}
abstract setData(d: any): void;
protected config(opt: echarts.EChartOption) {
}
protected formatValue(value: number): string {
switch (this.opts.unit) {
case "percent:100":
return value.toFixed(1) + "%";
case "percent:1":
return (value * 100).toFixed(1) + "%";
case "time:s":
if (value < 1) { // 1
return (value * 1000).toFixed(0) + 'ms';
} else {
return value.toFixed(2) + 's';
}
case "size:bytes":
if (value < 1024) { // 1K
return value.toString() + 'B';
} else if (value < 1048576) { // 1M
return (value / 1024).toFixed(2) + 'K';
} else if (value < 1073741824) { // 1G
return (value / 1048576).toFixed(2) + 'M';
} else {
return (value / 1073741824).toFixed(2) + 'G';
}
default:
return value.toFixed(2);
}
}
}
/**
* Gauge chart
*/
export class GaugeChart extends Chart {
constructor(opts: ChartOptions) {
super(opts);
}
protected config(opt: echarts.EChartOption) {
$.extend(true, opt, {
grid: {
left: 0,
top: 20,
right: 0,
bottom: 0,
},
xAxis: [
{
show: false,
},
],
yAxis: [
{
show: false,
},
],
});
}
setData(d: any): void {
this.chart.setOption({
series: [
{
// name: d.name,
type: 'gauge',
radius: '100%',
center: ["50%", "58%"], // 仪表位置
max: d.value,
axisLabel: {show: false},
pointer: {show: false},
detail: {
offsetCenter: [0, 0],
},
data: [{value: d.value}]
}
]
});
}
}
/**
* Pie chart.
*/
export class VectorChart extends Chart {
constructor(opts: ChartOptions) {
super(opts);
}
protected config(opt: echarts.EChartOption) {
$.extend(true, opt, {
grid: {
left: 20,
top: 20,
right: 20,
bottom: 20,
},
legend: {
type: 'scroll',
orient: 'vertical',
},
tooltip: {
trigger: 'item',
formatter: (params: any, index: number): string => {
return params.name + ": " + this.formatValue(params.value);
},
},
xAxis: [
{
show: false,
},
],
yAxis: [
{
show: false,
},
],
series: [{
type: this.opts.type,
radius: '80%',
center: ['45%', '50%'],
}],
});
}
setData(d: any): void {
this.chart.setOption({
legend: {
data: d.legend,
},
series: [{
data: d.data,
}],
});
}
}
/**
* Line/Bar etc.
*/
export class MatrixChart extends Chart {
constructor(opts: ChartOptions) {
super(opts);
}
protected config(opt: echarts.EChartOption) {
$.extend(true, opt, {
grid: {
left: 60,
top: 30,
right: 30,
bottom: 30,
},
tooltip: {
formatter: (params: any) => {
let html = params[0].axisValueLabel + '<br/>';
for (let i = 0; i < params.length; i++) {
html += params[i].marker + params[i].seriesName +': ' + this.formatValue(params[i].value[1]) + '<br/>';
}
return html;
},
},
});
}
setData(d: any): void {
d.series.forEach((s: any) => s.type = this.opts.type);
this.chart.setOption({
legend: {
data: d.legend,
},
series: d.series,
});
}
}
export class ChartFactory {
static create(opts: ChartOptions): Chart {
switch (opts.type) {
case "gauge":
return new GaugeChart(opts);
case "line":
case "bar":
return new MatrixChart(opts);
case "pie":
return new VectorChart(opts);
}
return null;
}
}
export class ChartDashboardOptions {
name: string;
key?: string;
period?: number = 30;
refreshInterval?: number = 15; // ms
}
export class ChartDashboard {
private $panel: JQuery;
private opts: ChartDashboardOptions;
private charts: Chart[] = [];
private timer: number;
constructor(elem: string | Element | JQuery, charts: ChartOptions[], opts?: ChartDashboardOptions) {
this.opts = $.extend(new ChartDashboardOptions(), opts);
this.$panel = $(elem);
charts.forEach(opts => this.createGraph(opts));
// events
Dispatcher.bind(this.$panel).on("remove-chart", e => {
let name = $(e.target).closest("div.column").data("name");
Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", dlg => {
this.removeGraph(name);
dlg.close();
});
});
$(window).resize(e => {
$.each(this.charts, (i: number, g: Chart) => {
g.resize();
});
});
this.refresh();
}
refresh() {
if (!this.timer) {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval * 1000);
}
}
}
private refreshData() {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval * 1000);
}
}
stop() {
clearTimeout(this.timer);
this.timer = 0;
}
setPeriod(period: number) {
this.opts.period = period;
this.loadData();
}
addGraph(opts: ChartOptions) {
this.createGraph(opts);
this.loadData();
}
private createGraph(opts: ChartOptions) {
for (let i = 0; i < this.charts.length; i++) {
let chart = this.charts[i];
if (chart.getOptions().name === opts.name) {
// chart already added.
return;
}
}
let chart = ChartFactory.create(opts);
if (chart != null) {
this.$panel.append(chart.getElem());
chart.init();
this.charts.push(chart);
}
}
removeGraph(name: string) {
let index = -1;
for (let i = 0; i < this.charts.length; i++) {
let c = this.charts[i];
if (c.getOptions().name === name) {
index = i;
break;
}
}
if (index >= 0) {
let $elem = this.charts[index].getElem();
this.charts.splice(index, 1);
$elem.remove();
}
}
save() {
let charts = this.charts.map(c => {
return {
name: c.getOptions().name,
width: c.getOptions().width,
height: c.getOptions().height,
}
});
let args = {
name: this.opts.name,
key: this.opts.key || '',
charts: charts,
};
$ajax.post(`/system/chart/save_dashboard`, args).json<AjaxResult>((r: AjaxResult) => {
if (r.success) {
Notification.show("success", "Successfully saved.");
} else {
Notification.show("danger", r.message);
}
});
}
private loadData() {
let args: any = {
charts: this.charts.map(c => c.getOptions().name).join(","),
period: this.opts.period,
};
if (this.opts.key) {
args.key = this.opts.key;
}
$ajax.get(`/system/chart/data`, args).json((d: { [index: string]: any[] }) => {
$.each(this.charts, (i: number, g: Chart) => {
if (d[g.getOptions().name]) {
g.setData(d[g.getOptions().name]);
}
});
});
}
}
}

View File

@ -1,461 +0,0 @@
namespace Swirl.Core {
export class GraphOptions {
type?: string = "line";
unit?: string;
width?: number = 12;
height?: number = 150;
colors?: string[];
}
export abstract class Graph {
protected $elem: JQuery;
protected opts?: GraphOptions;
private readonly name: string;
protected constructor(elem: string | Element | JQuery, opts?: GraphOptions) {
this.$elem = $(elem);
this.opts = $.extend(new GraphOptions(), opts);
this.name = this.$elem.data("chart-name");
}
getName(): string {
return this.name;
}
getElem(): JQuery {
return this.$elem;
}
getOptions(): GraphOptions {
return this.opts;
}
abstract resize(w: number, h: number): void;
abstract setData(d: any): void;
}
/**
* Simple value
*/
export class ValueGraph extends Graph {
private $canvas: JQuery;
constructor(elem: string | Element | JQuery, opts?: GraphOptions) {
super(elem, opts);
}
setData(d: any): void {
}
resize(w: number, h: number): void {
}
}
export class ComplexGraph extends Graph {
protected chart: Chart;
protected ctx: CanvasRenderingContext2D;
protected config: Chart.ChartConfiguration;
protected static defaultColors = [
'rgb(255, 99, 132)', // red
'rgb(75, 192, 192)', // green
'rgb(255, 159, 64)', // orange
'rgb(54, 162, 235)', // blue
'rgb(153, 102, 255)', // purple
'rgb(255, 205, 86)', // yellow
'rgb(201, 203, 207)', // grey
];
constructor(elem: string | Element | JQuery, opts?: GraphOptions) {
super(elem, opts);
if (!this.opts.colors) {
this.opts.colors = ComplexGraph.defaultColors;
}
this.config = {
type: opts.type,
data: {},
options: {
responsive: false,
maintainAspectRatio: false,
// title: {
// // display: true,
// text: opts.title || 'NONE'
// },
// legend: {
// position: "bottom"
// },
animation: {
duration: 0,
// easing: 'easeOutBounce',
},
// tooltips: {
// callbacks: {
// label: function (tooltipItem: Chart.ChartTooltipItem, data: Chart.ChartData) {
// let label = data.datasets[tooltipItem.datasetIndex].label || '';
// if (label) {
// label += ': ';
// }
//
// let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
// if (typeof p == "number") {
// label += ComplexGraph.formatValue(p, opts.unit);
// } else {
// label += ComplexGraph.formatValue(p.y, opts.unit);
// }
// return label;
// }
// },
// }
}
};
this.fillConfig();
this.ctx = (<HTMLCanvasElement>$(elem).find("canvas").get(0)).getContext('2d');
if (opts.height) {
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
}
this.chart = new Chart(this.ctx, this.config);
}
setData(d: any): void {
}
resize(w: number, h: number): void {
// this.ctx.canvas.style.width = this.ctx.canvas.parentElement.offsetWidth + "px";
// this.ctx.canvas.style.height = this.ctx.canvas.parentElement.offsetWidth + "px";
this.ctx.canvas.width = this.ctx.canvas.parentElement.offsetWidth;
this.ctx.canvas.height = this.ctx.canvas.parentElement.offsetHeight;
this.chart.resize();
}
protected fillConfig() {
}
protected static formatValue(value: number, unit: string): string {
switch (unit) {
case "percent:100":
return value.toFixed(1) + "%";
case "percent:1":
return (value * 100).toFixed(1) + "%";
case "time:s":
if (value < 1) { // 1
return (value * 1000).toFixed(0) + 'ms';
} else {
return value.toFixed(2) + 's';
}
case "size:bytes":
if (value < 1024) { // 1K
return value.toString() + 'B';
} else if (value < 1048576) { // 1M
return (value / 1024).toFixed(2) + 'K';
} else if (value < 1073741824) { // 1G
return (value / 1048576).toFixed(2) + 'M';
} else {
return (value / 1073741824).toFixed(2) + 'G';
}
default:
return value.toFixed(2);
}
}
}
/**
* Pie etc.
*/
export class VectorGraph extends ComplexGraph {
protected fillConfig() {
this.config.options.legend = {
position: "right"
};
this.config.options.tooltips = {
callbacks: {
label: (tooltipItem: Chart.ChartTooltipItem, data: Chart.ChartData): string => {
let label = data.labels[tooltipItem.index] + ": ";
let p = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
if (typeof p == "number") {
label += ComplexGraph.formatValue(p, this.opts.unit);
}
return label;
}
},
}
}
setData(d: any): void {
// data = {
// datasets: [{
// data: [10, 20, 30]
// }],
//
// // These labels appear in the legend and in the tooltips when hovering different arcs
// labels: [
// 'Red',
// 'Yellow',
// 'Blue'
// ]
// };
this.config.data.datasets = [{
data: d.data,
backgroundColor: this.opts.colors,
}];
this.config.data.labels = d.labels;
// this.config.data.datasets = [{
// data: [10, 20, 30],
// backgroundColor: this.opts.colors,
// }];
// this.config.data.labels = [
// 'Red',
// 'Yellow',
// 'Blue'
// ];
this.chart.update();
}
}
/**
* Line/Bar etc.
*/
export class MatrixGraph extends ComplexGraph {
constructor(elem: string | Element | JQuery, opts?: GraphOptions) {
super(elem, opts);
}
protected fillConfig() {
this.config.options.scales = {
xAxes: [{
type: 'time',
time: {
// min: new Date(),
// max: new Date(),
unit: 'minute',
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
displayFormats: {
minute: 'HH:mm'
}
},
}],
};
if (this.opts.unit) {
this.config.options.scales.yAxes = [{
ticks: {
callback: (n: number) => ComplexGraph.formatValue(n, this.opts.unit),
}
}];
this.config.options.tooltips = {
callbacks: {
label: (tooltipItem: Chart.ChartTooltipItem, data: Chart.ChartData): string => {
let label = data.datasets[tooltipItem.datasetIndex].label + ": ";
let p: any = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
label += ComplexGraph.formatValue(p.y, this.opts.unit);
return label;
}
},
}
}
}
setData(d: any): void {
let datasets = <Chart.ChartDataSets[]>(d);
datasets.forEach((ds, i) => {
let color = (i < this.opts.colors.length) ? this.opts.colors[i] : this.opts.colors[0];
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
ds.borderColor = color;
ds.borderWidth = 2;
ds.pointRadius = 2;
// ds.fill = false;
});
this.config.data.datasets = d;
this.chart.update();
}
}
export class GraphFactory {
static create(elem: string | Element | JQuery): Graph {
let $elem = $(elem);
let opts: GraphOptions = {
type: $elem.data("chart-type"),
unit: $elem.data("chart-unit"),
width: $elem.data("chart-width"),
height: $elem.data("chart-height"),
colors: $elem.data("chart-colors"),
};
switch (opts.type) {
case "value":
return new ValueGraph($elem, opts);
case "line":
case "bar":
return new MatrixGraph($elem, opts);
case "pie":
return new VectorGraph($elem, opts);
}
return null;
}
}
export class GraphPanelOptions {
name: string;
key?: string;
time?: string = "30m";
refreshInterval?: number = 15000; // ms
}
export class GraphPanel {
private $panel: JQuery;
private opts: GraphPanelOptions;
private charts: Graph[] = [];
private timer: number;
constructor(elem: string | Element | JQuery, opts?: GraphPanelOptions) {
this.opts = $.extend(new GraphPanelOptions(), opts);
this.$panel = $(elem);
this.$panel.children().each((i, e) => {
let g = GraphFactory.create(e);
if (g != null) {
this.charts.push(g);
}
});
Dispatcher.bind(this.$panel).on("remove-chart", e => {
let name = $(e.target).closest("div.column").data("chart-name");
Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", dlg => {
this.removeGraph(name);
dlg.close();
});
});
$(window).resize(e => {
$.each(this.charts, (i: number, g: Graph) => {
g.resize(0, 0);
});
});
this.refreshData();
}
private refreshData() {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
}
}
refresh() {
if (!this.timer) {
this.loadData();
if (this.opts.refreshInterval > 0) {
this.timer = setTimeout(this.refreshData.bind(this), this.opts.refreshInterval);
}
}
}
stop() {
clearTimeout(this.timer);
this.timer = 0;
}
setTime(time: string) {
this.opts.time = time;
this.loadData();
}
addGraph(c: any) {
for (let i = 0; i < this.charts.length; i++) {
let chart = this.charts[i];
if (chart.getName() === c.name) {
// chart already added.
return;
}
}
let $chart = $(`<div class="column is-${c.width}" data-chart-name="${c.name}" data-chart-type="${c.type}" data-chart-unit="${c.unit}" data-chart-width="${c.width}" data-chart-height="${c.height}">
<div class="card">
<header class="card-header">
<p class="card-header-title">${c.title}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div style="height: ${c.height}px">
<canvas id="canvas_${c.name}"></canvas>
</div>
</div>
</div>
</div>`);
this.$panel.append($chart);
let g = GraphFactory.create($chart);
if (g != null) {
this.charts.push(g);
}
this.loadData();
}
removeGraph(name: string) {
// todo:
let index = -1;
for (let i = 0; i < this.charts.length; i++) {
let c = this.charts[i];
if (c.getName() === name) {
index = i;
break;
}
}
if (index >= 0) {
console.info(this.charts.length);
let $elem = this.charts[index].getElem();
this.charts.splice(index, 1);
$elem.remove();
console.info(this.charts.length);
}
}
save() {
let charts = this.charts.map(c => {
return {
name: c.getName(),
width: c.getOptions().width,
height: c.getOptions().height,
// colors: ,
}
});
let args = {
name: this.opts.name,
key: this.opts.key || '',
charts: charts,
};
$ajax.post(`/system/chart/save_dashboard`, args).json<AjaxResult>((r: AjaxResult) => {
if (r.success) {
Notification.show("success", "Successfully saved.");
} else {
Notification.show("danger", r.message);
}
});
}
private loadData() {
let args: any = {
charts: this.charts.map(c => c.getName()).join(","),
time: this.opts.time,
};
if (this.opts.key) {
args.key = this.opts.key;
}
$ajax.get(`/system/chart/data`, args).json((d: { [index: string]: Chart.ChartDataSets[] }) => {
$.each(this.charts, (i: number, g: Graph) => {
if (d[g.getName()]) {
g.setData(d[g.getName()]);
}
});
});
}
}
}

View File

@ -1,23 +1,23 @@
///<reference path="core/core.ts" />
///<reference path="core/graph.ts" />
///<reference path="core/chart.ts" />
namespace Swirl {
import Modal = Swirl.Core.Modal;
import GraphPanel = Swirl.Core.GraphPanel;
import FilterBox = Swirl.Core.FilterBox;
import ChartDashboard = Swirl.Core.ChartDashboard;
export class IndexPage {
private panel: GraphPanel;
private dashboard: ChartDashboard;
private fb: FilterBox;
private charts: any;
private $charts: JQuery;
constructor() {
this.fb = new FilterBox("#txt-query", this.filterCharts.bind(this));
this.panel = new GraphPanel("#div-charts", {name: "home"});
this.dashboard = new ChartDashboard("#div-charts", window.charts, {name: "home"});
$("#btn-add").click(this.showAddDlg.bind(this));
$("#btn-add-chart").click(this.addChart.bind(this));
$("#btn-save").click(() => {
this.panel.save();
this.dashboard.save();
});
}
@ -69,7 +69,7 @@ namespace Swirl {
this.$charts.each((i, e) => {
if ($(e).find(":checked").length > 0) {
let c = this.charts[i];
this.panel.addGraph(c);
this.dashboard.addGraph(c);
}
});
Modal.close();

View File

@ -1,11 +1,11 @@
///<reference path="../core/core.ts" />
///<reference path="../core/graph.ts" />
///<reference path="../core/chart.ts" />
namespace Swirl.Service {
import Modal = Swirl.Core.Modal;
import GraphPanel = Swirl.Core.GraphPanel;
import ChartDashboard = Swirl.Core.ChartDashboard;
export class StatsPage {
private panel: GraphPanel;
private dashboard: ChartDashboard;
constructor() {
let $cb_time = $("#cb-time");
@ -13,7 +13,7 @@ namespace Swirl.Service {
return;
}
this.panel = new GraphPanel("#div-charts", {
this.dashboard = new ChartDashboard("#div-charts", window.charts, {
name: "service",
key: $("#h2-service-name").text()
});
@ -25,13 +25,13 @@ namespace Swirl.Service {
Modal.alert("Coming soon...");
});
$cb_time.change(e => {
this.panel.setTime($(e.target).val());
this.dashboard.setPeriod($(e.target).val());
});
$("#cb-refresh").change(e => {
if ($(e.target).prop("checked")) {
this.panel.refresh();
this.dashboard.refresh();
} else {
this.panel.stop();
this.dashboard.stop();
}
});
}

View File

@ -1,590 +0,0 @@
// Type definitions for Chart.js 2.7
// Project: https://github.com/nnnick/Chart.js
// Definitions by: Alberto Nuti <https://github.com/anuti>
// Fabien Lavocat <https://github.com/FabienLavocat>
// KentarouTakeda <https://github.com/KentarouTakeda>
// Larry Bahr <https://github.com/larrybahr>
// Daniel Luz <https://github.com/mernen>
// Joseph Page <https://github.com/josefpaij>
// Dan Manastireanu <https://github.com/danmana>
// Guillaume Rodriguez <https://github.com/guillaume-ro-fr>
// Sergey Rubanov <https://github.com/chicoxyzzy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
declare class Chart {
static readonly Chart: typeof Chart;
constructor(
context: string | CanvasRenderingContext2D | HTMLCanvasElement | ArrayLike<CanvasRenderingContext2D | HTMLCanvasElement>,
options: Chart.ChartConfiguration
);
config: Chart.ChartConfiguration;
data: Chart.ChartData;
destroy: () => {};
update: (duration?: any, lazy?: any) => {};
render: (duration?: any, lazy?: any) => {};
stop: () => {};
resize: () => {};
clear: () => {};
toBase64: () => string;
generateLegend: () => {};
getElementAtEvent: (e: any) => {};
getElementsAtEvent: (e: any) => Array<{}>;
getDatasetAtEvent: (e: any) => Array<{}>;
ctx: CanvasRenderingContext2D|null;
canvas: HTMLCanvasElement|null;
chartArea: Chart.ChartArea;
static pluginService: PluginServiceStatic;
static defaults: {
global: Chart.ChartOptions & Chart.ChartFontOptions;
[key: string]: any;
};
static controllers: {
[key: string]: any;
};
// Tooltip Static Options
static Tooltip: Chart.ChartTooltipsStaticConfiguration;
static helpers: HelperStatic;
}
declare class PluginServiceStatic {
register(plugin: PluginServiceRegistrationOptions): void;
unregister(plugin: PluginServiceRegistrationOptions): void;
}
declare class HelperStatic {
color(color: string): Color;
}
declare class Color {
alpha(alpha: number): Color;
rgbString(): string
}
interface PluginServiceRegistrationOptions {
beforeInit?(chartInstance: Chart): void;
afterInit?(chartInstance: Chart): void;
resize?(chartInstance: Chart, newChartSize: Size): void;
beforeUpdate?(chartInstance: Chart): void;
afterScaleUpdate?(chartInstance: Chart): void;
beforeDatasetsUpdate?(chartInstance: Chart): void;
afterDatasetsUpdate?(chartInstance: Chart): void;
afterUpdate?(chartInstance: Chart): void;
// This is called at the start of a render. It is only called once, even if the animation will run for a number of frames. Use beforeDraw or afterDraw
// to do something on each animation frame
beforeRender?(chartInstance: Chart): void;
// Easing is for animation
beforeDraw?(chartInstance: Chart, easing: string): void;
afterDraw?(chartInstance: Chart, easing: string): void;
// Before the datasets are drawn but after scales are drawn
beforeDatasetsDraw?(chartInstance: Chart, easing: string): void;
afterDatasetsDraw?(chartInstance: Chart, easing: string): void;
// Called before drawing the `tooltip`. If any plugin returns `false`,
// the tooltip drawing is cancelled until another `render` is triggered.
beforeTooltipDraw?(chartInstance: Chart): void;
// Called after drawing the `tooltip`. Note that this hook will not,
// be called if the tooltip drawing has been previously cancelled.
afterTooltipDraw?(chartInstance: Chart): void;
destroy?(chartInstance: Chart): void;
// Called when an event occurs on the chart
beforeEvent?(chartInstance: Chart, event: Event): void;
afterEvent?(chartInstance: Chart, event: Event): void;
}
interface Size {
height: number;
width: number;
}
declare namespace Chart {
type ChartType = 'line' | 'bar' | 'radar' | 'doughnut' | 'polarArea' | 'bubble' | 'pie';
type TimeUnit = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
type ScaleType = 'category' | 'linear' | 'logarithmic' | 'time' | 'radialLinear';
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle';
type PositionType = 'left' | 'right' | 'top' | 'bottom';
interface ChartArea {
top: number;
right: number;
bottom: number;
left: number;
}
interface ChartLegendItem {
text?: string;
fillStyle?: string;
hidden?: boolean;
lineCap?: string;
lineDash?: number[];
lineDashOffset?: number;
lineJoin?: string;
lineWidth?: number;
strokeStyle?: string;
pointStyle?: PointStyle;
}
interface ChartTooltipItem {
xLabel?: string;
yLabel?: string;
datasetIndex?: number;
index?: number;
}
interface ChartTooltipLabelColor {
borderColor: ChartColor;
backgroundColor: ChartColor;
}
interface ChartTooltipCallback {
beforeTitle?(item: ChartTooltipItem[], data: ChartData): string | string[];
title?(item: ChartTooltipItem[], data: ChartData): string | string[];
afterTitle?(item: ChartTooltipItem[], data: ChartData): string | string[];
beforeBody?(item: ChartTooltipItem[], data: ChartData): string | string[];
beforeLabel?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
label?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
labelColor?(tooltipItem: ChartTooltipItem, chart: Chart): ChartTooltipLabelColor;
labelTextColor?(tooltipItem: ChartTooltipItem, chart: Chart): string;
afterLabel?(tooltipItem: ChartTooltipItem, data: ChartData): string | string[];
afterBody?(item: ChartTooltipItem[], data: ChartData): string | string[];
beforeFooter?(item: ChartTooltipItem[], data: ChartData): string | string[];
footer?(item: ChartTooltipItem[], data: ChartData): string | string[];
afterFooter?(item: ChartTooltipItem[], data: ChartData): string | string[];
}
interface ChartAnimationParameter {
chartInstance?: any;
animationObject?: any;
}
interface ChartPoint {
x?: number | string | Date;
y?: number;
r?: number;
}
interface ChartConfiguration {
type?: ChartType | string;
data?: ChartData;
options?: ChartOptions;
// Plugins can require any options
plugins?: any;
}
interface ChartData {
labels?: Array<string | string[]>;
datasets?: ChartDataSets[];
}
interface ChartOptions {
responsive?: boolean;
responsiveAnimationDuration?: number;
aspectRatio?: number;
maintainAspectRatio?: boolean;
events?: string[];
onClick?(event?: MouseEvent, activeElements?: Array<{}>): any;
title?: ChartTitleOptions;
legend?: ChartLegendOptions;
tooltips?: ChartTooltipOptions;
hover?: ChartHoverOptions;
animation?: ChartAnimationOptions;
elements?: ChartElementsOptions;
layout?: ChartLayoutOptions;
scales?: ChartScales;
showLines?: boolean;
spanGaps?: boolean;
cutoutPercentage?: number;
circumference?: number;
rotation?: number;
}
interface ChartFontOptions {
defaultFontColor?: ChartColor;
defaultFontFamily?: string;
defaultFontSize?: number;
defaultFontStyle?: string;
}
interface ChartTitleOptions {
display?: boolean;
position?: PositionType;
fullWdith?: boolean;
fontSize?: number;
fontFamily?: string;
fontColor?: ChartColor;
fontStyle?: string;
padding?: number;
text?: string;
}
interface ChartLegendOptions {
display?: boolean;
position?: PositionType;
fullWidth?: boolean;
onClick?(event: MouseEvent, legendItem: ChartLegendItem): void;
onHover?(event: MouseEvent, legendItem: ChartLegendItem): void;
labels?: ChartLegendLabelOptions;
reverse?: boolean;
}
interface ChartLegendLabelOptions {
boxWidth?: number;
fontSize?: number;
fontStyle?: string;
fontColor?: ChartColor;
fontFamily?: string;
padding?: number;
generateLabels?(chart: any): any;
filter?(item: ChartLegendItem, data: ChartData): any;
usePointStyle?: boolean;
}
interface ChartTooltipOptions {
enabled?: boolean;
custom?(a: any): void;
mode?: string;
intersect?: boolean;
backgroundColor?: ChartColor;
titleFontFamily?: string;
titleFontSize?: number;
titleFontStyle?: string;
titleFontColor?: ChartColor;
titleSpacing?: number;
titleMarginBottom?: number;
bodyFontFamily?: string;
bodyFontSize?: number;
bodyFontStyle?: string;
bodyFontColor?: ChartColor;
bodySpacing?: number;
footerFontFamily?: string;
footerFontSize?: number;
footerFontStyle?: string;
footerFontColor?: ChartColor;
footerSpacing?: number;
footerMarginTop?: number;
xPadding?: number;
yPadding?: number;
caretSize?: number;
cornerRadius?: number;
multiKeyBackground?: string;
callbacks?: ChartTooltipCallback;
filter?(item: ChartTooltipItem): boolean;
itemSort?(itemA: ChartTooltipItem, itemB: ChartTooltipItem): number;
position?: string;
caretPadding?: number;
displayColors?: boolean;
borderColor?: ChartColor;
borderWidth?: number;
}
interface ChartTooltipsStaticConfiguration {
positioners: {[mode: string]: ChartTooltipPositioner};
}
type ChartTooltipPositioner = (elements: any[], eventPosition: Point) => Point;
interface ChartHoverOptions {
mode?: string;
animationDuration?: number;
intersect?: boolean;
onHover?(active: any): void;
}
interface ChartAnimationObject {
currentStep?: number;
numSteps?: number;
easing?: string;
render?(arg: any): void;
onAnimationProgress?(arg: any): void;
onAnimationComplete?(arg: any): void;
}
interface ChartAnimationOptions {
duration?: number;
easing?: string;
onProgress?(chart: any): void;
onComplete?(chart: any): void;
}
interface ChartElementsOptions {
point?: ChartPointOptions;
line?: ChartLineOptions;
arc?: ChartArcOptions;
rectangle?: ChartRectangleOptions;
}
interface ChartArcOptions {
backgroundColor?: ChartColor;
borderColor?: ChartColor;
borderWidth?: number;
}
interface ChartLineOptions {
tension?: number;
backgroundColor?: ChartColor;
borderWidth?: number;
borderColor?: ChartColor;
borderCapStyle?: string;
borderDash?: any[];
borderDashOffset?: number;
borderJoinStyle?: string;
capBezierPoints?: boolean;
fill?: 'zero' | 'top' | 'bottom' | boolean;
stepped?: boolean;
}
interface ChartPointOptions {
radius?: number;
pointStyle?: PointStyle;
backgroundColor?: ChartColor;
borderWidth?: number;
borderColor?: ChartColor;
hitRadius?: number;
hoverRadius?: number;
hoverBorderWidth?: number;
}
interface ChartRectangleOptions {
backgroundColor?: ChartColor;
borderWidth?: number;
borderColor?: ChartColor;
borderSkipped?: string;
}
interface ChartLayoutOptions {
padding?: ChartLayoutPaddingObject | number;
}
interface ChartLayoutPaddingObject {
top?: number;
right?: number;
bottom?: number;
left?: number;
}
interface GridLineOptions {
display?: boolean;
color?: ChartColor;
borderDash?: number[];
borderDashOffset?: number;
lineWidth?: number;
drawBorder?: boolean;
drawOnChartArea?: boolean;
drawTicks?: boolean;
tickMarkLength?: number;
zeroLineWidth?: number;
zeroLineColor?: ChartColor;
zeroLineBorderDash?: number[];
zeroLineBorderDashOffset?: number;
offsetGridLines?: boolean;
}
interface ScaleTitleOptions {
display?: boolean;
labelString?: string;
fontColor?: ChartColor;
fontFamily?: string;
fontSize?: number;
fontStyle?: string;
}
interface TickOptions {
autoSkip?: boolean;
autoSkipPadding?: number;
callback?(value: any, index: any, values: any): string|number;
display?: boolean;
fontColor?: ChartColor;
fontFamily?: string;
fontSize?: number;
fontStyle?: string;
labelOffset?: number;
maxRotation?: number;
minRotation?: number;
mirror?: boolean;
padding?: number;
reverse?: boolean;
min?: any;
max?: any;
}
interface AngleLineOptions {
display?: boolean;
color?: ChartColor;
lineWidth?: number;
}
interface PointLabelOptions {
callback?(arg: any): any;
fontColor?: ChartColor;
fontFamily?: string;
fontSize?: number;
fontStyle?: string;
}
interface TickOptions {
backdropColor?: ChartColor;
backdropPaddingX?: number;
backdropPaddingY?: number;
maxTicksLimit?: number;
showLabelBackdrop?: boolean;
}
interface LinearTickOptions extends TickOptions {
beginAtZero?: boolean;
min?: number;
max?: number;
maxTicksLimit?: number;
stepSize?: number;
suggestedMin?: number;
suggestedMax?: number;
}
interface LogarithmicTickOptions extends TickOptions {
min?: number;
max?: number;
}
type ChartColor = string | CanvasGradient | CanvasPattern | string[];
interface ChartDataSets {
cubicInterpolationMode?: 'default' | 'monotone';
backgroundColor?: ChartColor | ChartColor[];
borderWidth?: number | number[];
borderColor?: ChartColor | ChartColor[];
borderCapStyle?: string;
borderDash?: number[];
borderDashOffset?: number;
borderJoinStyle?: string;
borderSkipped?: PositionType;
data?: number[] | ChartPoint[];
fill?: boolean | number | string;
hoverBackgroundColor?: string | string[];
hoverBorderColor?: string | string[];
hoverBorderWidth?: number | number[];
label?: string;
lineTension?: number;
steppedLine?: 'before' | 'after' | boolean;
pointBorderColor?: ChartColor | ChartColor[];
pointBackgroundColor?: ChartColor | ChartColor[];
pointBorderWidth?: number | number[];
pointRadius?: number | number[];
pointHoverRadius?: number | number[];
pointHitRadius?: number | number[];
pointHoverBackgroundColor?: ChartColor | ChartColor[];
pointHoverBorderColor?: ChartColor | ChartColor[];
pointHoverBorderWidth?: number | number[];
pointStyle?: PointStyle | HTMLImageElement | Array<PointStyle | HTMLImageElement>;
xAxisID?: string;
yAxisID?: string;
type?: string;
hidden?: boolean;
hideInLegendAndTooltip?: boolean;
showLine?: boolean;
stack?: string;
spanGaps?: boolean;
}
interface ChartScales {
type?: ScaleType | string;
display?: boolean;
position?: PositionType | string;
gridLines?: GridLineOptions;
scaleLabel?: ScaleTitleOptions;
ticks?: TickOptions;
xAxes?: ChartXAxe[];
yAxes?: ChartYAxe[];
}
interface CommonAxe {
type?: ScaleType | string;
display?: boolean;
id?: string;
stacked?: boolean;
position?: string;
ticks?: TickOptions;
gridLines?: GridLineOptions;
barThickness?: number;
scaleLabel?: ScaleTitleOptions;
beforeUpdate?(scale?: any): void;
beforeSetDimension?(scale?: any): void;
beforeDataLimits?(scale?: any): void;
beforeBuildTicks?(scale?: any): void;
beforeTickToLabelConversion?(scale?: any): void;
beforeCalculateTickRotation?(scale?: any): void;
beforeFit?(scale?: any): void;
afterUpdate?(scale?: any): void;
afterSetDimension?(scale?: any): void;
afterDataLimits?(scale?: any): void;
afterBuildTicks?(scale?: any): void;
afterTickToLabelConversion?(scale?: any): void;
afterCalculateTickRotation?(scale?: any): void;
afterFit?(scale?: any): void;
}
interface ChartXAxe extends CommonAxe {
categoryPercentage?: number;
barPercentage?: number;
time?: TimeScale;
}
// tslint:disable-next-line no-empty-interface
interface ChartYAxe extends CommonAxe {
}
interface LinearScale extends ChartScales {
ticks?: LinearTickOptions;
}
interface LogarithmicScale extends ChartScales {
ticks?: LogarithmicTickOptions;
}
interface TimeDisplayFormat {
millisecond?: string;
second?: string;
minute?: string;
hour?: string;
day?: string;
week?: string;
month?: string;
quarter?: string;
year?: string;
}
interface TimeScale extends ChartScales {
displayFormats?: TimeDisplayFormat;
isoWeekday?: boolean;
max?: string;
min?: string;
parser?: string | ((arg: any) => any);
round?: TimeUnit;
tooltipFormat?: string;
unit?: TimeUnit;
unitStepSize?: number;
stepSize?: number;
minUnit?: TimeUnit;
}
interface RadialLinearScale {
lineArc?: boolean;
angleLines?: AngleLineOptions;
pointLabels?: PointLabelOptions;
ticks?: TickOptions;
}
interface Point {
x: number;
y: number;
}
}
export = Chart;
export as namespace Chart;

199
assets/swirl/typings/echarts.d.ts vendored Normal file
View File

@ -0,0 +1,199 @@
// Type definitions for echarts
// Project: http://echarts.baidu.com/
// Definitions by: Xie Jingyang <https://github.com/xieisabug>, AntiMoron <https://github.com/AntiMoron>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
declare namespace echarts {
function init(dom: HTMLDivElement | HTMLCanvasElement, theme?: Object | string, opts?: {
devicePixelRatio?: number
renderer?: string
}): ECharts;
const graphic: Graphic;
interface Graphic {
clipPointsByRect(points: number[][], rect: ERectangle): number[][];
clipRectByRect(targetRect: ERectangle, rect: ERectangle): ERectangle;
LinearGradient: { new(x: number, y: number, x2: number, y2: number, colorStops: Array<Object>, globalCoord?: boolean): LinearGradient }
}
function connect(group: string | Array<string>): void;
function disConnect(group: string): void;
function dispose(target: ECharts | HTMLDivElement | HTMLCanvasElement): void;
function getInstanceByDom(target: HTMLDivElement | HTMLCanvasElement): ECharts;
function registerMap(mapName: string, geoJson: Object, specialAreas?: Object): void;
function registerTheme(themeName: string, theme: Object): void;
interface MapObj {
/** geoJson data for map */
geoJson: object,
/** special areas fro map */
specialAreas: object
}
function getMap(mapName: string): MapObj;
interface LinearGradient {
colorStops: Array<Object>;
global: boolean;
type: string;
x: number
x2: number
y: number
y2: number
}
interface ECharts {
group: string
setOption(option: EChartOption, notMerge?: boolean, notRefreshImmediately?: boolean): void
getWidth(): number
getHeight(): number
getDom(): HTMLCanvasElement | HTMLDivElement
getOption(): Object
resize(): void
dispatchAction(payload: Object): void
on(eventName: string, handler: Function, context?: Object): void
off(eventName: string, handler?: Function): void
showLoading(type?: string, opts?: Object): void
hideLoading(): void
getDataURL(opts: {
/** 导出的格式,可选 png, jpeg */
type?: string,
/** 导出的图片分辨率比例,默认为 1。*/
pixelRatio?: number,
/** 导出的图片背景色,默认使用 option 里的 backgroundColor */
backgroundColor?: string
}): string
getConnectedDataURL(opts: {
/** 导出的格式,可选 png, jpeg */
type: string,
/** 导出的图片分辨率比例,默认为 1。 */
pixelRatio: number,
/** 导出的图片背景色,默认使用 option 里的 backgroundColor */
backgroundColor: string
}): string
clear(): void
isDisposed(): boolean
dispose(): void
/** 转换逻辑点到像素 */
convertToPixel(finder: ConvertFinder | string, value: string | Array<any>): string | Array<any>
convertFromPixel(finder: ConvertFinder | string, value: Array<any> | string): Array<any> | string
containPixel(finder: ConvertFinder | string,
/** 要被判断的点,为像素坐标值,以 echarts 实例的 dom 节点的左上角为坐标 [0, 0] 点。*/
value: any[]): boolean
}
interface ConvertFinder {
seriesIndex?: number,
seriesId?: string,
seriesName?: string,
geoIndex?: number,
geoId?: string,
geoName?: string,
xAxisIndex?: number,
xAxisId?: string,
xAxisName?: string,
yAxisIndex?: number,
yAxisId?: string,
yAxisName?: string,
gridIndex?: number,
gridId?: string
gridName?: string
}
interface ERectangle {
x: number,
y: number,
width: number,
height: number
}
interface EChartOption {
title?: EChartTitleOption
legend?: Object,
grid?: Object,
xAxis?: Object,
yAxis?: Object,
polar?: Object,
radiusAxis?: Object,
angleAxis?: Object,
radar?: Object,
dataZoom?: Array<Object>,
visualMap?: Array<Object>,
tooltip?: Object,
toolbox?: Object,
geo?: Object,
parallel?: Object,
parallelAxis?: Object,
timeline?: Object,
series?: Array<Object>,
color?: Array<Object>,
backgroundColor?: string,
textStyle?: Object,
animation?: boolean,
animationDuration?: number,
animationEasing?: string,
animationDurationUpdate?: number,
animationEasingUpdate?: string
}
interface EChartTitleOption {
show?: boolean;
text?: string;
link?: string,
target?: string,
textStyle?: Object,
subtext?: string,
sublink?: string,
subtarget?: string,
subtextStyle?: Object,
padding?: number,
itemGap?: number,
zlevel?: number,
z?: number,
left?: string,
top?: string,
right?: string,
bottom?: string,
backgroundColor?: string,
borderColor?: string,
borderWidth?: number,
shadowBlur?: number,
shadowColor?: number,
shadowOffsetX?: number,
shadowOffsetY?: number,
}
}
declare module 'echarts' {
export = echarts;
}
declare module 'echarts/lib/echarts' {
export = echarts;
}

View File

@ -144,9 +144,9 @@ func (b *chartBiz) GetDashboardCharts(dashboard *model.ChartDashboard) (charts [
if c.Height > 0 {
chart.Height = c.Height
}
if len(c.Colors) > 0 {
chart.Colors = c.Colors
}
//if len(c.Colors) > 0 {
// chart.Colors = c.Colors
//}
charts = append(charts, chart)
}
}
@ -172,20 +172,29 @@ func (b *chartBiz) FetchDatas(key string, names []string, period time.Duration)
switch chart.Type {
case "line", "bar":
m, err := Metric.GetMatrix(query, chart.Label, start, end)
d, err := Metric.GetMatrix(query, chart.Legend, start, end)
if err != nil {
log.Get("metric").Error(err, query)
} else {
datas[chart.Name] = m
datas[chart.Name] = d
}
case "pie", "table":
m, err := Metric.GetVector(query, chart.Label, end)
case "pie":
d, err := Metric.GetVector(query, chart.Legend, end)
if err != nil {
log.Get("metric").Error(err, query)
} else {
datas[chart.Name] = m
datas[chart.Name] = d
}
case "gauge":
d, err := Metric.GetScalar(query, end)
if err != nil {
log.Get("metric").Error(err, query)
} else {
datas[chart.Name] = &model.ChartValue{
//Name: "",
Value: d,
}
}
case "value":
}
}
return datas, nil

View File

@ -57,7 +57,7 @@ type metricBiz struct {
// return
// }
func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines []model.ChartLine, err error) {
func (b *metricBiz) GetMatrix(query, legend string, start, end time.Time) (data *model.ChartMatrixData, err error) {
api, err := b.getAPI()
if err != nil {
return nil, err
@ -73,9 +73,11 @@ func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines
return nil, err
}
data = &model.ChartMatrixData{}
matrix := value.(pmodel.Matrix)
for _, stream := range matrix {
line := model.ChartLine{Label: b.formatLabel(label, stream.Metric)}
data.Legend = append(data.Legend, b.formatLabel(legend, stream.Metric))
line := model.ChartLine{Name: b.formatLabel(legend, stream.Metric)}
for _, v := range stream.Values {
p := model.ChartPoint{
X: int64(v.Timestamp),
@ -83,7 +85,7 @@ func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines
}
line.Data = append(line.Data, p)
}
lines = append(lines, line)
data.Series = append(data.Series, line)
}
return
}
@ -99,11 +101,16 @@ func (b *metricBiz) GetScalar(query string, t time.Time) (v float64, err error)
return 0, err
}
scalar := value.(*pmodel.Scalar)
return float64(scalar.Value), nil
//scalar := value.(*pmodel.Scalar)
vector := value.(pmodel.Vector)
if len(vector) > 0 {
sample := vector[0]
return float64(sample.Value), nil
}
return 0, nil
}
func (b *metricBiz) GetVector(query, label string, t time.Time) (cv model.ChartVector, err error) {
func (b *metricBiz) GetVector(query, label string, t time.Time) (data *model.ChartVectorData, err error) {
var api papi.API
api, err = b.getAPI()
if err != nil {
@ -116,12 +123,15 @@ func (b *metricBiz) GetVector(query, label string, t time.Time) (cv model.ChartV
return
}
data = &model.ChartVectorData{}
vector := value.(pmodel.Vector)
for _, sample := range vector {
cv.Data = append(cv.Data, float64(sample.Value))
if label != "" {
cv.Labels = append(cv.Labels, b.formatLabel(label, sample.Metric))
cv := model.ChartValue{
Name: b.formatLabel(label, sample.Metric),
Value: float64(sample.Value),
}
data.Data = append(data.Data, cv)
data.Legend = append(data.Legend, cv.Name)
}
return
}

View File

@ -49,7 +49,8 @@ field.width: Width
field.height: Height
field.unit: Unit
field.title: Title
field.label: Label
field.legend: Legend
field.desc: Description
field.dashboard: Dashboard
# menu

View File

@ -49,7 +49,8 @@ field.width: 宽度
field.height: 高度
field.unit: 单位
field.title: 标题
field.label: 标签
field.legend: 图例
field.desc: 描述
field.dashboard: 仪表盘
# menu

View File

@ -69,7 +69,7 @@ func chartQuery(ctx web.Context) error {
func chartNew(ctx web.Context) error {
m := newModel(ctx).Set("Chart", &model.Chart{
Width: 12,
Height: 150,
Height: 200,
Type: "line",
Dashboard: "service",
})
@ -115,7 +115,7 @@ func chartDelete(ctx web.Context) error {
}
func chartData(ctx web.Context) error {
period := cast.ToDuration(ctx.Q("time"), time.Hour)
period := time.Duration(cast.ToInt64(ctx.Q("period"), 60)) * time.Minute
if v := ctx.Q("charts"); v != "" {
names := strings.Split(v, ",")
key := ctx.Q("key")

View File

@ -1,7 +1,10 @@
package model
import (
"fmt"
"github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/util/cast"
)
// Chart represents a dashboard chart.
@ -9,7 +12,7 @@ type Chart struct {
Name string `json:"name" bson:"_id" valid:"required"` // unique, the name of build-in charts has '$' prefix.
Title string `json:"title" valid:"required"`
Description string `json:"desc"`
Label string `json:"label"` // ${name} - ${instance}
Legend string `json:"legend"` // ${name} - ${instance}
Query string `json:"query" valid:"required"`
Kind string `json:"kind"` // builtin/custom
Dashboard string `json:"dashboard"` // home/service/task...
@ -17,38 +20,38 @@ type Chart struct {
Unit string `json:"unit"` // bytes/milliseconds/percent:100...
Width int32 `json:"width"` // 1-12(12 columns total)
Height int32 `json:"height"` // default 50
Colors []string `json:"colors"`
Options data.Map `json:"options"`
//Colors []string `json:"colors"`
}
func NewChart(dashboard, name, title, label, query, unit string) *Chart {
func NewChart(dashboard, name, title, legend, query, unit string) *Chart {
return &Chart{
Name: name,
Title: title,
Description: title,
Label: label,
Legend: legend,
Query: query,
Dashboard: dashboard,
Type: "line",
Unit: unit,
Width: 12,
Height: 150,
Height: 200,
}
}
type ChartItem struct {
Name string `json:"name"`
Width int32 `json:"width"`
Height int32 `json:"height"`
Colors []string `json:"colors"`
type ChartOption struct {
Name string `json:"name"`
Width int32 `json:"width"`
Height int32 `json:"height"`
//Colors []string `json:"colors"`
}
type ChartDashboard struct {
Name string `json:"name"`
Key string `json:"key"`
Period int32 `json:"period"` // minutes
RefreshInterval int32 `json:"refresh_interval"` // seconds, 0 means disabled.
Charts []ChartItem `json:"charts"`
Name string `json:"name"`
Key string `json:"key"`
Period int32 `json:"period"` // minutes
RefreshInterval int32 `json:"refresh_interval"` // seconds, 0 means disabled.
Charts []ChartOption `json:"charts"`
}
func (cd *ChartDashboard) ID() string {
@ -63,33 +66,26 @@ type ChartPoint struct {
Y float64 `json:"y"`
}
func (p *ChartPoint) MarshalJSON() ([]byte, error) {
return cast.StringToBytes(fmt.Sprintf("[%v,%v]", p.X, p.Y)), nil
}
type ChartLine struct {
Label string `json:"label"`
Data []ChartPoint `json:"data"`
Name string `json:"name"`
Data []ChartPoint `json:"data"`
}
type ChartMatrixData struct {
Legend []string `json:"legend"`
Series []ChartLine `json:"series"`
}
type ChartValue struct {
Label string `json:"label"`
Data float64 `json:"data"`
Name string `json:"name"`
Value float64 `json:"value"`
}
type ChartVector struct {
Data []float64 `json:"data"`
Labels []string `json:"labels"`
}
type ChartInfo struct {
Name string `json:"name"`
Title string `json:"title"`
Label string `json:"label"`
Query string `json:"query"`
}
func NewChartInfo(name, title, label, query string) ChartInfo {
return ChartInfo{
Name: name,
Title: title,
Label: label,
Query: query,
}
type ChartVectorData struct {
Legend []string `json:"legend"`
Data []ChartValue `json:"data"`
}

View File

@ -168,11 +168,11 @@ func cpuChecker(service string, low, high float64) (scaleType, float64) {
return scaleNone, 0
}
value := vector.Data[0]
if value <= low {
return scaleDown, value
} else if value >= high {
return scaleUp, value
cv := vector.Data[0]
if cv.Value <= low {
return scaleDown, cv.Value
} else if cv.Value >= high {
return scaleUp, cv.Value
}
return scaleNone, 0
}

View File

@ -1,7 +1,8 @@
{{ extends "_layouts/default" }}
{{ block script() }}
<script src="/assets/chart/chart.bundle.min.js?v=2.7.2"></script>
<script>var charts = {{ writeJson(.Charts) }};</script>
<script src="/assets/echarts/echarts.min.js?v=4.0.4"></script>
<script>$(() => new Swirl.IndexPage())</script>
{{ end }}
@ -85,35 +86,6 @@
</div>
</nav>
<div id="div-charts" class="columns is-multiline">
{{ range .Charts }}
<div class="column is-{{ .Width }}" data-chart-name="{{ .Name }}" data-chart-type="{{ .Type }}" data-chart-unit="{{ .Unit }}" data-chart-width="{{ .Width }}" data-chart-height="{{ .Height }}">
<div class="card">
<header class="card-header">
<p class="card-header-title">{{ .Title }}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
</span>
</a>
{*<a data-action="remove-chart" class="card-header-icon is-paddingless" aria-label="remove chart">*}
{*<span class="icon">*}
{*<i class="fas fa-times has-text-danger" aria-hidden="true"></i>*}
{*</span>*}
{*</a>*}
{*<a data-action="edit-options" class="card-header-icon" aria-label="edit options">*}
{*<span class="icon">*}
{*<i class="fas fa-ellipsis-h has-text-info" aria-hidden="true"></i>*}
{*</span>*}
{*</a>*}
</header>
<div class="card-content">
<div style="height: {{ .Height }}px">
<canvas id="canvas_{{ .Name }}"></canvas>
</div>
</div>
</div>
</div>
{{ end }}
</div>
</section>

View File

@ -3,7 +3,8 @@
{{ import "../_modules/form" }}
{{ block script() }}
<script src="/assets/chart/chart.bundle.min.js?v=2.7.2"></script>
<script>var charts = {{ writeJson(.Charts) }};</script>
<script src="/assets/echarts/echarts.min.js?v=4.0.4"></script>
<script>$(() => new Swirl.Service.StatsPage())</script>
{{ end }}
@ -53,12 +54,12 @@
<p class="control">
<div class="select">
<select id="cb-time" name="time">
{{ yield option(value="30m", label="Last 30 minutes", selected=.Time) }}
{{ yield option(value="1h", label="Last 1 hour", selected=.Time) }}
{{ yield option(value="3h", label="Last 3 hours", selected=.Time) }}
{{ yield option(value="6h", label="Last 6 hours", selected=.Time) }}
{{ yield option(value="12h", label="Last 12 hours", selected=.Time) }}
{{ yield option(value="24h", label="Last 24 hours", selected=.Time) }}
{{ yield option(value="30", label="Last 30 minutes", selected=.Time) }}
{{ yield option(value="60", label="Last 1 hour", selected=.Time) }}
{{ yield option(value="180", label="Last 3 hours", selected=.Time) }}
{{ yield option(value="360", label="Last 6 hours", selected=.Time) }}
{{ yield option(value="720", label="Last 12 hours", selected=.Time) }}
{{ yield option(value="1440", label="Last 24 hours", selected=.Time) }}
</select>
</div>
</p>
@ -82,30 +83,6 @@
</div>
</nav>
<div id="div-charts" class="columns is-multiline">
{{ range .Charts }}
<div class="column is-{{ .Width }}" data-chart-name="{{ .Name }}" data-chart-type="{{ .Type }}" data-chart-unit="{{ .Unit }}" data-chart-width="{{ .Width }}" data-chart-height="{{ .Height }}">
<div class="card">
<header class="card-header">
<p class="card-header-title">{{ .Title }}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
</span>
</a>
{*<a data-action="edit-options" class="card-header-icon" aria-label="edit options">*}
{*<span class="icon">*}
{*<i class="fas fa-ellipsis-h has-text-info" aria-hidden="true"></i>*}
{*</span>*}
{*</a>*}
</header>
<div class="card-content">
<div style="height: {{ .Height }}px">
<canvas id="canvas_{{ .Name }}"></canvas>
</div>
</div>
</div>
</div>
{{ end }}
</div>
{{ else }}
<div class="notification is-info">

View File

@ -22,9 +22,9 @@
</div>
</div>
<div class="field">
<label class="label">{{ i18n("field.label") }}</label>
<label class="label">{{ i18n("field.legend") }}</label>
<div class="control">
<input name="label" value="{{ .Chart.Label }}" class="input" placeholder="Label for dataset, e.g. ${name}">
<input name="legend" value="{{ .Chart.Legend }}" class="input" placeholder="Legend expression for dataset, e.g. ${name}">
</div>
</div>
</div>
@ -53,7 +53,7 @@
<div class="field">
<label class="label">{{ i18n("field.height") }}</label>
<div class="control">
<input name="height" value="{{ .Chart.Height }}" class="input" placeholder="Height" data-type="integer" data-v-rule="native" required>
<input name="height" value="{{ .Chart.Height }}" class="input" placeholder="Height in pixel" data-type="integer" data-v-rule="native" required>
</div>
</div>
<div class="field">
@ -89,7 +89,7 @@
</div>
</div>
<div class="field">
<label class="label">Description</label>
<label class="label">{{ i18n("field.desc") }}</label>
<div class="control">
<input name="desc" value="{{ .Chart.Description }}" class="input" placeholder="Chart description">
</div>
@ -106,8 +106,8 @@
{{ yield radio(name="type", value="line", label="Line", checked=.Chart.Type) }}
{{ yield radio(name="type", value="bar", label="Bar", checked=.Chart.Type) }}
{{ yield radio(name="type", value="pie", label="Pie", checked=.Chart.Type) }}
{{ yield radio(name="type", value="gauge", label="Gauge", checked=.Chart.Type) }}
{*{{ yield radio(name="type", value="table", label="Table", checked=.Chart.Type) }}*}
{{ yield radio(name="type", value="value", label="Value", checked=.Chart.Type) }}
</div>
</div>
<div class="field">
@ -116,7 +116,7 @@
{{ yield radio(name="dashboard", value="", label="Any", checked=.Chart.Dashboard) }}
{{ yield radio(name="dashboard", value="home", label="Home", checked=.Chart.Dashboard) }}
{{ yield radio(name="dashboard", value="service", label="Service", checked=.Chart.Dashboard) }}
{{ yield radio(name="dashboard", value="task", label="Task", checked=.Chart.Dashboard) }}
{*{{ yield radio(name="dashboard", value="task", label="Task", checked=.Chart.Dashboard) }}*}
</div>
</div>
{{ yield form_submit(url="/system/chart/") }}