mirror of https://github.com/rancher/ui.git
264 lines
8.3 KiB
JavaScript
264 lines
8.3 KiB
JavaScript
export default function initGraph(options) {
|
|
const { el, width, height, margin, thickness, fontSize, triangleSize } = getConfig(options);
|
|
|
|
const svg = d3.select(el).append('svg')
|
|
.attr('width', width).attr('height', height + margin);
|
|
|
|
let value = options.value;
|
|
let ticks = options.ticks;
|
|
const { valuePath, maxPath } = addArcValue(svg, width, margin, thickness, options.value);
|
|
const bottomLine = addBottomLine(svg, width, height);
|
|
let tooltip = addTooltip();
|
|
addTicks(svg, triangleSize, tooltip, width, height, margin, ticks);
|
|
const { valueLabel, titleLabel, subtitleLabel } = addLabels(svg, options, width, height, fontSize);
|
|
|
|
return {
|
|
updateTitle(text) {
|
|
titleLabel.text(text);
|
|
},
|
|
updateSubTitle(text) {
|
|
subtitleLabel.text(text);
|
|
},
|
|
updateValue(text) {
|
|
text = text ? `${text}%` : '';
|
|
valueLabel.text(text);
|
|
if (text) {
|
|
value = parseInt(text, 10);
|
|
const r = calcR(width, margin);
|
|
valuePath.attr('d', createArc(-90, value, r, thickness));
|
|
}
|
|
},
|
|
updateTicks(t) {
|
|
ticks = t;
|
|
repaintTicks(svg, triangleSize, tooltip, width, height, margin, ticks);
|
|
},
|
|
fit() {
|
|
fit(svg, el, value, ticks, tooltip, bottomLine, valuePath,
|
|
maxPath, valueLabel, titleLabel, subtitleLabel);
|
|
},
|
|
};
|
|
}
|
|
|
|
function fit(svg, el, value, ticks, tooltip, bottomLine, valuePath, maxPath, valueLabel, titleLabel, subtitleLabel) {
|
|
const { width, height, margin, thickness, fontSize, triangleSize } = getConfig({ el });
|
|
svg.attr('width', width).attr('height', height + margin);
|
|
repaintBottomLine(width, height, bottomLine);
|
|
repaintArc(width, margin, value, thickness, valuePath, maxPath);
|
|
repaintLabels(valueLabel, titleLabel, subtitleLabel, width, height, fontSize);
|
|
repaintTicks(svg, triangleSize, tooltip, width, height, margin, ticks);
|
|
}
|
|
|
|
function repaintTicks(svg, triangleSize, tooltip, width, height, margin, ticks) {
|
|
svg.selectAll('path[tick = "custom"]').remove();
|
|
addTicks(svg, triangleSize, tooltip, width, height, margin, ticks);
|
|
}
|
|
|
|
function repaintBottomLine(width, height, bottomLine) {
|
|
const lineFunction = d3.svg.line()
|
|
.x(function (d) { return d.x; })
|
|
.y(function (d) { return d.y; })
|
|
.interpolate("linear");
|
|
bottomLine.attr("d", lineFunction([{ "x": 0, "y": height }, { "x": width, "y": height }]))
|
|
.attr("stroke-width", width / 80);
|
|
}
|
|
|
|
function repaintArc(width, margin, value, thickness, valuePath, maxPath) {
|
|
const r = calcR(width, margin);
|
|
valuePath.attr('d', createArc(-90, value, r, thickness))
|
|
.attr('transform', 'translate(' + (margin + r)
|
|
+ ',' + (margin + r) + '), scale(1, 1)');
|
|
maxPath.attr('d', createArc(-90, 100, r, thickness))
|
|
.attr('transform', 'translate(' + (margin + r)
|
|
+ ',' + (margin + r) + '), scale(1, 1)');
|
|
}
|
|
|
|
function repaintLabels(valueLabel, titleLabel, subtitleLabel, width, height, fontSize) {
|
|
valueLabel.attr("x", width / 2)
|
|
.attr("y", height / 4 + fontSize / 1.2)
|
|
.attr("dy", fontSize / 2)
|
|
.style("font-size", fontSize + "px");
|
|
titleLabel.attr("x", width / 2)
|
|
.attr("y", height / 4 + 2 * fontSize)
|
|
.attr("dy", fontSize / 4)
|
|
.style("font-size", fontSize / 1.5 + "px");
|
|
subtitleLabel.attr("x", width / 2)
|
|
.attr("y", height / 4 + 3 * fontSize)
|
|
.attr("dy", fontSize / 4)
|
|
.style("font-size", fontSize / 2 + "px")
|
|
.style("letter-spacing", `${fontSize / 18}px`);
|
|
}
|
|
|
|
function addTooltip() {
|
|
let tooltip = d3.select('#percent-gauge-tooltip');
|
|
if (tooltip.empty()) {
|
|
tooltip = d3.select("body").append("div")
|
|
.attr('class', 'hover-label')
|
|
.attr("class", "percent-gauge-tooltip")
|
|
.attr("id", "percent-gauge-tooltip")
|
|
.style("opacity", 0);
|
|
}
|
|
return tooltip;
|
|
}
|
|
|
|
function addArcValue(svg, width, margin, thickness, value) {
|
|
const valuePath = addArc(svg, width, margin, thickness, 'gauge-value-path', value);
|
|
const maxPath = addArc(svg, width, margin, thickness, 'none', 100);
|
|
return {
|
|
valuePath,
|
|
maxPath,
|
|
};
|
|
}
|
|
|
|
function addLabels(svg, options, width, height, fontSize) {
|
|
return {
|
|
valueLabel: addText(options.value ? `${options.value}%` : '0%', svg, width / 2, height / 4 + fontSize / 1.2, fontSize),
|
|
titleLabel: addText(options.title, svg, width / 2, height / 4 + 2 * fontSize, fontSize / 1.5),
|
|
subtitleLabel: addText(options.subtitle, svg, width / 2, height / 4 + 3 * fontSize, fontSize / 2, fontSize / 18, 1.5),
|
|
}
|
|
}
|
|
|
|
function addBottomLine(svg, width, height) {
|
|
const lineFunction = d3.svg.line()
|
|
.x(function (d) { return d.x; })
|
|
.y(function (d) { return d.y; })
|
|
.interpolate("linear");
|
|
|
|
return svg.append("path")
|
|
.attr("d", lineFunction([{ "x": 0, "y": height }, { "x": width, "y": height }]))
|
|
.attr("class", "gauge-text-stroke")
|
|
.attr("stroke-width", width / 80)
|
|
.attr("fill", "none");
|
|
}
|
|
|
|
function addTicks(svg, triangleSize, tooltip, width, height, margin, ticks) {
|
|
(ticks || []).forEach(tick => {
|
|
if (tick.value !==0 && !tick.value) {
|
|
return
|
|
}
|
|
const value = parseInt(tick.value, 10);
|
|
const p1 = valueToPoint(width, height, margin, value, 1.1)
|
|
const p2 = valueToPoint(width, height, margin, value, 0.88)
|
|
const p3 = {
|
|
x: p1.x - triangleSize * sin(value),
|
|
y: p1.y + triangleSize * cos(value)
|
|
}
|
|
const p4 = {
|
|
x: p1.x + triangleSize * sin(value),
|
|
y: p1.y - triangleSize * cos(value)
|
|
}
|
|
|
|
let tr = '';
|
|
tick.labels.forEach(label => {
|
|
tr += `<tr>
|
|
<td>${label}</td>
|
|
<td>${tick.value}%</td>
|
|
</tr>`;
|
|
});
|
|
|
|
return svg.append('path')
|
|
.attr('d', function () {
|
|
return 'M ' + p3.x + ' ' + p3.y +
|
|
' L' + p2.x + ' ' + p2.y +
|
|
' L' + p4.x + ' ' + p4.y +
|
|
' z';
|
|
})
|
|
.attr('tick', 'custom')
|
|
.attr('class', 'gauge-tick').on("mouseover", function () {
|
|
tooltip.transition()
|
|
.duration(200)
|
|
.style("opacity", .9);
|
|
tooltip.html(`<table>
|
|
<tbody>
|
|
<tr>
|
|
<th>${window.l('service:intl').t('clusterDashboard.node')}</th>
|
|
<th>${window.l('service:intl').t('clusterDashboard.value')}</th>
|
|
</tr>
|
|
${tr}
|
|
</tbody>
|
|
</table>`)
|
|
.style("left", (d3.event.pageX) - 30 + "px")
|
|
.style("top", (d3.event.pageY) - 30*(tick.labels.length + 1) + "px");
|
|
})
|
|
.on("mouseout", function () {
|
|
tooltip.transition()
|
|
.duration(500)
|
|
.style("opacity", 0);
|
|
});
|
|
});
|
|
}
|
|
|
|
function addText(text, svg, x, y, fontSize, bold = 0, space = 0) {
|
|
return svg.append("svg:text")
|
|
.attr("x", x)
|
|
.attr("y", y)
|
|
.attr("dy", fontSize / 2)
|
|
.attr("text-anchor", "middle")
|
|
.text(text)
|
|
.style("font-size", fontSize + "px")
|
|
.attr("class", "gauge-text-fill gauge-text-stroke")
|
|
.style("letter-spacing", `${space}px`)
|
|
.style("stroke-width", `${bold}px`);
|
|
}
|
|
|
|
function addArc(svg, width, margin, thickness, gaugeColor, value) {
|
|
value = value || 0;
|
|
const r = calcR(width, margin);
|
|
return svg.append('path')
|
|
.attr('d', createArc(-90, value, r, thickness))
|
|
.style('stroke-width', '1')
|
|
.attr('class', `gauge-text-stroke ${gaugeColor}`)
|
|
.attr('transform', 'translate(' + (margin + r)
|
|
+ ',' + (margin + r) + '), scale(1, 1)');
|
|
}
|
|
|
|
function createArc(sa, ea, r, thickness) {
|
|
ea = 1.8 * parseInt(ea, 10) - 90;
|
|
return d3.svg.arc()
|
|
.outerRadius(r)
|
|
.innerRadius(r - thickness)
|
|
.startAngle(d2r(sa))
|
|
.endAngle(d2r(ea));
|
|
}
|
|
|
|
function d2r(d) {
|
|
return d * (Math.PI / 180);
|
|
}
|
|
|
|
function sin(value) {
|
|
return Math.sin(1.8 * value * Math.PI / 180);
|
|
}
|
|
|
|
function cos(value) {
|
|
return Math.cos(1.8 * value * Math.PI / 180);
|
|
}
|
|
|
|
function calcR(width, margin) {
|
|
return (width - 2 * margin) / 2;
|
|
}
|
|
|
|
function valueToPoint(width, height, margin, value, factor) {
|
|
const r = calcR(width, margin);
|
|
return {
|
|
x: width - r * factor * cos(value) - r - margin,
|
|
y: height - r * factor * sin(value),
|
|
};
|
|
}
|
|
|
|
function getWidth(el) {
|
|
const width = el.parentNode.offsetWidth;
|
|
return width > 0 ? width : 0;
|
|
}
|
|
|
|
function getConfig(options) {
|
|
const width = getWidth(options.el);
|
|
return {
|
|
el: options.el,
|
|
fontSize: width / 10,
|
|
margin: width / 22,
|
|
width,
|
|
height: width / 2,
|
|
thickness: width / 20,
|
|
triangleSize: width / 40,
|
|
};
|
|
}
|