mirror of https://github.com/rancher/ui.git
358 lines
12 KiB
JavaScript
358 lines
12 KiB
JavaScript
import { select, event } from 'd3';
|
|
import {
|
|
getConfig, addArc, valueToPoint, addText, calcR, createArc, getRange
|
|
} from 'shared/utils/percent-gauge';
|
|
|
|
const OUT_RING_MARGIN = 3
|
|
|
|
export default function initGraph(options) {
|
|
const {
|
|
el, width, height, margin, thickness, fontSize
|
|
} = getConfig(options);
|
|
|
|
const svg = select(el).append('svg')
|
|
.attr('width', width).attr('height', height);
|
|
|
|
let {
|
|
value, ticks, live = 0, liveTicks, maxValue = 100
|
|
} = options
|
|
|
|
let currentLive = live
|
|
let currentLiveTicks = liveTicks
|
|
let currentMaxValue = maxValue
|
|
|
|
const {
|
|
valuePath, maxPath, liveMaxPath, liveValuePath
|
|
} = addArcValue(svg, width, margin, thickness, options.value, options, live);
|
|
let tooltip = addTooltip();
|
|
|
|
addTicks(svg, tooltip, width, height, margin, ticks, options.value, thickness, liveTicks, live, currentMaxValue);
|
|
const {
|
|
valueLabel, titleLabel, subtitleLabel, liveLabel, reservedLabel
|
|
} = addLabels(svg, options, width, height, fontSize);
|
|
|
|
return {
|
|
updateTitle(text) {
|
|
titleLabel.text(text);
|
|
},
|
|
updateSubTitle(text) {
|
|
subtitleLabel.text(text);
|
|
},
|
|
updateLiveLabel(text) {
|
|
liveLabel.text(text);
|
|
},
|
|
updateValue(reserved, live, maxValue) {
|
|
const {
|
|
width, height, margin, thickness
|
|
} = getConfig({ el });
|
|
|
|
svg.attr('width', width).attr('height', height);
|
|
|
|
live = (live || live === 0) ? `${ live }%` : '';
|
|
reserved = (reserved || reserved === 0) ? `${ reserved }%` : '';
|
|
valueLabel.text(live);
|
|
reservedLabel.text(reserved);
|
|
|
|
const r = calcR(width, margin);
|
|
const outR = calcR(width, OUT_RING_MARGIN)
|
|
|
|
if (maxValue) {
|
|
currentMaxValue = maxValue
|
|
maxPath.attr('d', createArc(-135, maxValue, outR, Math.round(thickness / 2)));
|
|
}
|
|
|
|
if (reserved) {
|
|
value = parseInt(reserved, 10);
|
|
|
|
valuePath.attr('d', createArc(-135, multiMaxPercent(value, currentMaxValue), outR, Math.round(thickness / 2)));
|
|
}
|
|
|
|
if (live) {
|
|
currentLive = parseInt(live, 10)
|
|
|
|
liveValuePath.attr('d', createArc(-135, currentLive, r, thickness));
|
|
}
|
|
},
|
|
updateTicks(t, liveTicks, live, maxValue) {
|
|
ticks = t;
|
|
currentLiveTicks = liveTicks
|
|
repaintTicks(svg, tooltip, width, height, margin, ticks, value, thickness, live, liveTicks, maxValue);
|
|
},
|
|
fit() {
|
|
fit(svg, el, value, ticks, tooltip, valuePath,
|
|
maxPath, valueLabel, titleLabel, subtitleLabel, liveMaxPath, liveValuePath, liveLabel, currentLive, currentLiveTicks, currentMaxValue, reservedLabel);
|
|
},
|
|
};
|
|
}
|
|
|
|
function fit(svg, el, value, ticks, tooltip, valuePath, maxPath, valueLabel, titleLabel, subtitleLabel, liveMaxPath, liveValuePath, liveLabel, live, liveTicks, currentMaxValue, reservedLabel) {
|
|
const {
|
|
width, height, margin, thickness, fontSize
|
|
} = getConfig({ el });
|
|
|
|
svg.attr('width', width).attr('height', height);
|
|
repaintArc(width, margin, value, thickness, valuePath, maxPath, liveMaxPath, liveValuePath, live, currentMaxValue);
|
|
repaintLabels(valueLabel, titleLabel, subtitleLabel, width, height, fontSize, liveLabel, reservedLabel);
|
|
repaintTicks(svg, tooltip, width, height, margin, ticks, value, thickness, live, liveTicks, currentMaxValue);
|
|
}
|
|
|
|
function repaintTicks(svg, tooltip, width, height, margin, ticks, value, thickness, live, liveTicks, currentMaxValue) {
|
|
svg.selectAll('path[tick = "custom"]').remove();
|
|
svg.selectAll('circle[tick = "custom"]').remove();
|
|
addTicks(svg, tooltip, width, height, margin, ticks, value, thickness, liveTicks, live, currentMaxValue);
|
|
}
|
|
|
|
function repaintArc(width, margin, value, thickness, valuePath, maxPath, liveMaxPath, liveValuePath, live, currentMaxValue) {
|
|
const r = calcR(width, margin);
|
|
|
|
const outR = calcR(width, OUT_RING_MARGIN)
|
|
|
|
liveValuePath.attr('d', createArc(-135, live, r, thickness))
|
|
.attr('transform', `translate(${ margin + r
|
|
},${ margin + r }), scale(1, 1)`);
|
|
liveMaxPath.attr('d', createArc(-135, 100, r, thickness))
|
|
.attr('transform', `translate(${ margin + r
|
|
},${ margin + r }), scale(1, 1)`);
|
|
maxPath.attr('d', createArc(-135, currentMaxValue, outR, Math.round(thickness / 2)))
|
|
.attr('transform', `translate(${ margin + r
|
|
},${ margin + r }), scale(1, 1)`);
|
|
valuePath.attr('d', createArc(-135, multiMaxPercent(value, currentMaxValue), outR, Math.round(thickness / 2)))
|
|
.attr('transform', `translate(${ margin + r
|
|
},${ margin + r }), scale(1, 1)`);
|
|
}
|
|
|
|
function addTooltip() {
|
|
let tooltip = select('#percent-gauge-tooltip');
|
|
|
|
if (tooltip.empty()) {
|
|
tooltip = 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, options, live) {
|
|
const liveMaxPath = addArc(svg, width, margin, thickness, 'gauge-max-path', 100);
|
|
const liveValuePath = addArc(svg, width, margin, thickness, 'gauge-live-value-path', live > 100 ? 100 : live);
|
|
const maxPath = addArc(svg, width, OUT_RING_MARGIN, Math.round(thickness / 2), 'gauge-max-path', options.maxValue);
|
|
const valuePath = addArc(svg, width, OUT_RING_MARGIN, Math.round(thickness / 2), 'gauge-value-path', value > 100 ? 100 : value);
|
|
|
|
return {
|
|
valuePath,
|
|
maxPath,
|
|
liveMaxPath,
|
|
liveValuePath,
|
|
};
|
|
}
|
|
|
|
function getValueLabelY(height, fontSize) {
|
|
return height / 5 + fontSize / 1.2;
|
|
}
|
|
|
|
function getLiveLabelY(height, fontSize) {
|
|
return height / 5 + 1.7 * fontSize;
|
|
}
|
|
|
|
function getReservedLabelY(height, fontSize) {
|
|
return height / 5 + 2.5 * fontSize;
|
|
}
|
|
|
|
function getSubtitleLabelY(height, fontSize) {
|
|
return height / 5 + 3.1 * fontSize;
|
|
}
|
|
|
|
function getTitleLabelY(height, fontSize) {
|
|
return height / 5 + 4.3 * fontSize;
|
|
}
|
|
|
|
function addLabels(svg, options, width, height, fontSize) {
|
|
return {
|
|
valueLabel: addText(options.live ? `${ options.live }%` : '0%', svg, width / 2, getValueLabelY(height, fontSize), fontSize, 'usedPercent', 3.5),
|
|
reservedLabel: addText(options.value ? `${ options.value }%` : '0%', svg, width / 2, getReservedLabelY(height, fontSize), fontSize / 2, 'reservedPercent', 2),
|
|
titleLabel: addText(options.title, svg, width / 2, getTitleLabelY(height, fontSize), fontSize / 3, 'title'),
|
|
subtitleLabel: addText(options.subtitle, svg, width / 2, getSubtitleLabelY(height, fontSize), fontSize / 3, 'subtitle'),
|
|
liveLabel: addText(options.liveTitle, svg, width / 2, getLiveLabelY(height, fontSize), fontSize / 3, 'liveLabel'),
|
|
}
|
|
}
|
|
|
|
function repaintLabels(valueLabel, titleLabel, subtitleLabel, width, height, fontSize, liveLabel, reservedLabel) {
|
|
valueLabel.attr('x', width / 2)
|
|
.attr('y', getValueLabelY(height, fontSize))
|
|
.attr('dy', fontSize / 2)
|
|
.style('font-size', `${ fontSize }px`);
|
|
reservedLabel.attr('x', width / 2)
|
|
.attr('y', getReservedLabelY(height, fontSize))
|
|
.attr('dy', fontSize / 4)
|
|
.style('font-size', `${ fontSize / 2 }px`);
|
|
titleLabel.attr('x', width / 2)
|
|
.attr('y', getTitleLabelY(height, fontSize))
|
|
.attr('dy', fontSize / 6)
|
|
.style('font-size', `${ fontSize / 3 }px`);
|
|
subtitleLabel.attr('x', width / 2)
|
|
.attr('y', getSubtitleLabelY(height, fontSize))
|
|
.attr('dy', fontSize / 6)
|
|
.style('font-size', `${ fontSize / 3 }px`);
|
|
liveLabel.attr('x', width / 2)
|
|
.attr('y', getLiveLabelY(height, fontSize))
|
|
.attr('dy', fontSize / 6)
|
|
.style('font-size', `${ fontSize / 3 }px`);
|
|
}
|
|
|
|
function addTicks(svg, tooltip, width, height, margin, ticks, currentValue, thickness, liveTicks = [], live, currentMaxValue) {
|
|
let { max, min } = getRange(ticks);
|
|
let liveMax = getRange(liveTicks).max;
|
|
let liveMin = getRange(liveTicks).min;
|
|
|
|
|
|
(ticks || []).forEach((tick) => {
|
|
if (tick.value !== 0 && !tick.value) {
|
|
return
|
|
}
|
|
|
|
let value = parseInt(tick.value, 10);
|
|
|
|
if ((value !== max && value !== min) || max === min) {
|
|
return
|
|
}
|
|
if (min === value && value === currentValue && value !== 0) {
|
|
value -= 1
|
|
}
|
|
if (max === value && value === currentValue && value !== 100) {
|
|
value += 1
|
|
}
|
|
|
|
const point = valueToPoint(width, height, OUT_RING_MARGIN, multiMaxPercent(value, currentMaxValue), thickness / 2);
|
|
|
|
if (ticks.length > 1) {
|
|
let tr = '';
|
|
|
|
tick.labels.forEach((label) => {
|
|
tr += `<tr>
|
|
<td>${ label }</td>
|
|
<td>${ tick.value }%</td>
|
|
</tr>`;
|
|
});
|
|
|
|
svg.append('circle').attr('tick', 'custom').attr('class', `gauge-circle-fill`).attr('cx', point.x)
|
|
.attr('cy', point.y)
|
|
.attr('r', 3);
|
|
|
|
const tp = svg.append('circle').attr('tick', 'custom').attr('class', `gauge-none-fill`).attr('cx', point.x)
|
|
.attr('cy', point.y)
|
|
.attr('r', thickness / 4);
|
|
|
|
tp.on('mouseover', () => {
|
|
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.reserved') }</th>
|
|
</tr>
|
|
${ tr }
|
|
</tbody>
|
|
</table>`)
|
|
.style('left', `${ (event.pageX) - 30 }px`)
|
|
.style('top', `${ (event.pageY) - 30 * (tick.labels.length + 1) }px`);
|
|
}).on('mouseout', () => {
|
|
tooltip.transition()
|
|
.duration(500)
|
|
.style('opacity', 0);
|
|
});
|
|
}
|
|
});
|
|
|
|
(liveTicks || []).map((tick) => {
|
|
if (tick.value !== 0 && !tick.value) {
|
|
return
|
|
}
|
|
let value = parseInt(tick.value, 10);
|
|
|
|
if ((value !== liveMax && value !== liveMin) || liveMax === liveMin) {
|
|
return
|
|
}
|
|
if (liveMin === value && value === live && value !== 0) {
|
|
value -= 1
|
|
}
|
|
if (liveMax === value && value === live && value !== 100) {
|
|
value += 1
|
|
}
|
|
const point = valueToPoint(width, height, margin, value, thickness);
|
|
|
|
if (liveTicks.length > 1) {
|
|
let tr = '';
|
|
|
|
tr += `<tr>
|
|
<td>${ tick.label }</td>
|
|
<td>${ tick.value }%</td>
|
|
</tr>`;
|
|
|
|
svg.append('circle').attr('tick', 'custom').attr('class', `gauge-circle-fill`).attr('cx', point.x)
|
|
.attr('cy', point.y)
|
|
.attr('r', 3);
|
|
|
|
const tp = svg.append('circle').attr('tick', 'custom').attr('class', `gauge-none-fill`).attr('cx', point.x)
|
|
.attr('cy', point.y)
|
|
.attr('r', thickness / 2);
|
|
|
|
tp.on('mouseover', () => {
|
|
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.used') }</th>
|
|
</tr>
|
|
${ tr }
|
|
</tbody>
|
|
</table>`)
|
|
.style('left', `${ (event.pageX) - 30 }px`)
|
|
.style('top', `${ (event.pageY) - 30 * 2 }px`);
|
|
}).on('mouseout', () => {
|
|
tooltip.transition()
|
|
.duration(500)
|
|
.style('opacity', 0);
|
|
});
|
|
}
|
|
});
|
|
|
|
if (liveTicks && liveTicks.length > 1 && (liveMax - liveMin >= 1)) {
|
|
let rangePath
|
|
|
|
if (liveMin === live && liveMin !== 0) {
|
|
liveMin -= 1
|
|
}
|
|
if (liveMax === live && liveMax !== 100) {
|
|
liveMax += 1
|
|
}
|
|
rangePath = addArc(svg, width, margin, thickness, 'gauge-tick-path', liveMax, liveMin, 2);
|
|
|
|
rangePath.attr('tick', 'custom');
|
|
}
|
|
|
|
if (ticks && ticks.length > 1 && (max - min >= 1)) {
|
|
let rangePath
|
|
|
|
if (min === currentValue && min !== 0) {
|
|
min -= 1
|
|
}
|
|
if (max === currentValue && max !== 100) {
|
|
max += 1
|
|
}
|
|
rangePath = addArc(svg, width, OUT_RING_MARGIN, thickness / 2, 'gauge-tick-path', multiMaxPercent(max, currentMaxValue), multiMaxPercent(min, currentMaxValue), 2);
|
|
|
|
rangePath.attr('tick', 'custom');
|
|
}
|
|
}
|
|
|
|
function multiMaxPercent(value, maxValue = 100) {
|
|
return Math.round(value * maxValue / 100)
|
|
} |