mirror of https://github.com/rancher/ui.git
252 lines
6.7 KiB
JavaScript
252 lines
6.7 KiB
JavaScript
import { GRADIENT_COLORS } from 'shared/components/svg-gradients/component';
|
|
import {
|
|
formatPercent,
|
|
formatMib,
|
|
formatKbps
|
|
} from 'shared/utils/util';
|
|
import initTooltip from 'shared/utils/graph-tooltip';
|
|
|
|
const FORMATTERS = { value: value => value, percent: formatPercent, mib: formatMib, kbps: formatKbps };
|
|
const DEFAULT_MARGIN = { top: 5, right: 20, bottom: 5, left: 75 };
|
|
const DEFAULT_MAX_POINTS = 60;
|
|
const DEFAULT_DURATION = 1000;
|
|
const DEFAULT_Y_TICKS = 5;
|
|
const DEFAULT_HEIGHT = 190;
|
|
const DEFAULT_INTERVAL = 1000;
|
|
|
|
export default function initGraph(options) {
|
|
const { el, margin, width, height, yTicks, duration,
|
|
maxPoints, formatter, fields, gradient, interpolate, min, query, interval } = getConfig(options);
|
|
|
|
const graph = d3.select(el).append('svg')
|
|
.attr('width', width + margin.left + margin.right)
|
|
.attr('height', height + margin.top + margin.bottom);
|
|
|
|
const svg = graph.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|
|
|
const x = d3.scale.linear().domain([0, maxPoints - 1]).range([0, width]);
|
|
const y = d3.scale.linear().domain([0, 1]).range([height, 0]);
|
|
|
|
const line = d3.svg.line().interpolate(interpolate)
|
|
.defined(d => typeof d === 'number')
|
|
.x((d, i) => x(i))
|
|
.y(d => y(d));
|
|
const area = d3.svg.area().interpolate(interpolate)
|
|
.defined(d => typeof d === 'number')
|
|
.x((d, i) => x(i))
|
|
.y0(height)
|
|
.y1(d => y(d));
|
|
|
|
const yAxis = d3.svg.axis().scale(y).orient('left')
|
|
.ticks(yTicks).tickFormat(FORMATTERS[formatter]);
|
|
const yAxisG = svg.append('g').attr('class', 'y axis').call(yAxis);
|
|
|
|
const clipPath = svg.append('defs').append('clipPath').attr('id', 'clip')
|
|
.append('rect').attr('width', width).attr('height', height);
|
|
|
|
const stripes = drawStripes(svg, width, y, yTicks);
|
|
|
|
const series = getSeries(fields, svg, gradient);
|
|
|
|
const tooltip = initTooltip({ el, svg, height, margin, maxPoints, duration, formatter: FORMATTERS[formatter], x, gradient: GRADIENT_COLORS[gradient] });
|
|
|
|
const params = {
|
|
svg,
|
|
el,
|
|
x,
|
|
y,
|
|
min,
|
|
margin,
|
|
height,
|
|
line,
|
|
area,
|
|
yAxisG,
|
|
yAxis,
|
|
yTicks,
|
|
stripes,
|
|
options,
|
|
maxPoints,
|
|
query,
|
|
series,
|
|
duration,
|
|
tooltip,
|
|
}
|
|
|
|
let intervalId
|
|
|
|
return {
|
|
start() {
|
|
render(params);
|
|
intervalId = setInterval(() => {
|
|
render(params);
|
|
}, interval);
|
|
},
|
|
fit() {
|
|
fit({ el, x, margin, graph, clipPath, stripes: params.stripes });
|
|
},
|
|
destory() {
|
|
clearInterval(intervalId);
|
|
}
|
|
};
|
|
}
|
|
|
|
function render(params) {
|
|
let all = [];
|
|
const series = [];
|
|
params.series.forEach(serie => {
|
|
const data = query(params.maxPoints, params.query, serie.field);
|
|
all = all.concat(data)
|
|
series.push({
|
|
field: serie.field,
|
|
lineChart: serie.lineChart,
|
|
areaChart: serie.areaChart,
|
|
data
|
|
})
|
|
})
|
|
|
|
updateTooltip(series, params)
|
|
updateAxis(all.filter(d => d !== null), params)
|
|
updateLines(series, params)
|
|
}
|
|
|
|
function fit(params) {
|
|
const margin = params.margin;
|
|
const width = getWidth(params.el, margin);
|
|
params.x.range([0, width]);
|
|
params.graph.attr('width', width + margin.left + margin.right);
|
|
params.clipPath.attr('width', width);
|
|
params.stripes.attr('width', width);
|
|
}
|
|
|
|
function query(maxPoints, query, field) {
|
|
let data = query(field.key) || []
|
|
|
|
if (data.length < maxPoints) {
|
|
const len = data.length
|
|
for (let i = 0; i < maxPoints - len; i++) {
|
|
data.unshift(null)
|
|
}
|
|
} else if (data.length > maxPoints) {
|
|
data = data.slice(-1 * maxPoints);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function adjustMax(dataMax, options) {
|
|
let optMinMax = options.minMax;
|
|
let optMax = options.max;
|
|
let optScaleDown = options.scaleDown;
|
|
let observedMax = options.observedMax;
|
|
let out = dataMax;
|
|
|
|
if (optMax) {
|
|
out = optMax;
|
|
} else if (optMinMax) {
|
|
out = Math.max(optMinMax, out);
|
|
}
|
|
|
|
if (observedMax && !optScaleDown) {
|
|
out = Math.max(observedMax, out);
|
|
}
|
|
|
|
if (!observedMax && out > 0 && options.maxDoubleInital) {
|
|
out *= 2;
|
|
}
|
|
options.observedMax = out;
|
|
return out;
|
|
}
|
|
|
|
function updateTooltip(series, params) {
|
|
params.tooltip.update(series)
|
|
}
|
|
|
|
function updateAxis(all, params) {
|
|
if (all.length === 0) {
|
|
return;
|
|
}
|
|
const min = params.min === null ? d3.min(all) : params.min;
|
|
const max = adjustMax(d3.max(all), params.options);
|
|
const update = params.y.domain()[0] !== min || params.y.domain()[1] !== max
|
|
params.y.domain([min, max]);
|
|
params.y.range([params.height - 2, 2]);
|
|
params.y.rangeRound([params.height - 2, 2]);
|
|
params.yAxisG.call(params.yAxis);
|
|
if (update) {
|
|
updateStripes(params);
|
|
}
|
|
}
|
|
|
|
function updateLines(series, params) {
|
|
series.forEach((serie) => {
|
|
serie.areaChart.attr('d', params.area(serie.data))
|
|
serie.lineChart.attr('d', params.line(serie.data))
|
|
})
|
|
}
|
|
|
|
function drawStripes(svg, width, y, yTicks) {
|
|
return svg.selectAll('rect.y')
|
|
.data(y.ticks(yTicks))
|
|
.enter().append('rect')
|
|
.attr('x', 0)
|
|
.attr('width', width)
|
|
.attr('y', y)
|
|
.attr('height', (d, i) => i === 0 ? 0 : y(y.ticks(yTicks)[i - 1]) - y(d))
|
|
.attr('class', (d, i) => i % 2 === 0 ? 'even' : 'odd')
|
|
.style('fill-opacity', 0.1);
|
|
}
|
|
|
|
function updateStripes(params) {
|
|
params.stripes.remove();
|
|
params.stripes = drawStripes(params.svg, getWidth(params.el, params.margin), params.y, params.yTicks);
|
|
}
|
|
|
|
function getWidth(el, margin) {
|
|
const width = el.parentNode.offsetWidth - margin.left - margin.right
|
|
return width > 0 ? width : 0;
|
|
}
|
|
|
|
function getSeries(fields, svg, gradient) {
|
|
return fields.map((field, i) => {
|
|
return {
|
|
field,
|
|
lineChart: svg.append('g').attr('clip-path', 'url(#clip)')
|
|
.append('path').attr('class', 'line')
|
|
.style('stroke', GRADIENT_COLORS[gradient][i]),
|
|
areaChart: svg.append('g').attr('clip-path', 'url(#clip)')
|
|
.append('path').attr('class', 'area')
|
|
.style('fill', `url(${window.location.pathname}#${gradient}-${i}-gradient)`)
|
|
}
|
|
});
|
|
}
|
|
|
|
function getConfig(options) {
|
|
const el = options.el;
|
|
const chartHeight = options.height || DEFAULT_HEIGHT
|
|
const margin = options.margin || DEFAULT_MARGIN
|
|
const intl = window.l('service:intl');
|
|
const fields = (options.fields || []).map(field => {
|
|
return {
|
|
key: field.key,
|
|
displayName: intl.t(field.displayName)
|
|
}
|
|
});
|
|
|
|
return {
|
|
el,
|
|
margin,
|
|
width: getWidth(el, margin),
|
|
height: chartHeight - margin.top - margin.bottom,
|
|
yTicks: options.yTicks || DEFAULT_Y_TICKS,
|
|
duration: options.duration || DEFAULT_DURATION,
|
|
maxPoints: options.maxPoints || DEFAULT_MAX_POINTS,
|
|
interval: options.interval || DEFAULT_INTERVAL,
|
|
formatter: options.formatter || 'value',
|
|
fields: fields,
|
|
gradient: options.gradient || 'memory',
|
|
interpolate: options.interpolate || 'basis',
|
|
min: options.min,
|
|
query: options.query,
|
|
}
|
|
}
|