@grafana/flamegraph
Version:
Grafana flamegraph visualization component
257 lines (250 loc) • 9.15 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var react = require('react');
var color = require('tinycolor2');
var ui = require('@grafana/ui');
var constants = require('../constants.cjs');
var types = require('../types.cjs');
var colors = require('./colors.cjs');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var color__default = /*#__PURE__*/_interopDefaultCompat(color);
;
function useFlameRender(options) {
const {
canvasRef,
data,
root,
depth,
direction,
wrapperWidth,
rangeMin,
rangeMax,
matchedLabels,
textAlign,
totalViewTicks,
totalColorTicks,
totalTicksRight,
colorScheme,
focusedItemData,
collapsedMap
} = options;
const ctx = useSetupCanvas(canvasRef, wrapperWidth, depth);
const theme = ui.useTheme2();
const mutedColor = react.useMemo(() => {
const barMutedColor = color__default.default(theme.colors.background.secondary);
return theme.isLight ? barMutedColor.darken(10).toHexString() : barMutedColor.lighten(10).toHexString();
}, [theme]);
const getBarColor = useColorFunction(
totalColorTicks,
totalTicksRight,
colorScheme,
theme,
mutedColor,
rangeMin,
rangeMax,
matchedLabels,
focusedItemData ? focusedItemData.item.level : 0
);
const renderFunc = useRenderFunc(ctx, data, getBarColor, textAlign, collapsedMap);
react.useEffect(() => {
if (!ctx) {
return;
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const mutedPath2D = new Path2D();
walkTree(
root,
direction,
data,
totalViewTicks,
rangeMin,
rangeMax,
wrapperWidth,
collapsedMap,
(item, x, y, width, height, label, muted) => {
if (muted) {
mutedPath2D.rect(x, y, width, height);
} else {
renderFunc(item, x, y, width, height, label);
}
}
);
ctx.fillStyle = mutedColor;
ctx.fill(mutedPath2D);
}, [
ctx,
data,
root,
wrapperWidth,
rangeMin,
rangeMax,
totalViewTicks,
direction,
renderFunc,
collapsedMap,
mutedColor
]);
}
function useRenderFunc(ctx, data, getBarColor, textAlign, collapsedMap) {
return react.useMemo(() => {
if (!ctx) {
return () => {
};
}
const renderFunc = (item, x, y, width, height, label) => {
ctx.beginPath();
ctx.rect(x + constants.BAR_BORDER_WIDTH, y, width, height);
ctx.fillStyle = getBarColor(item, label, false);
ctx.stroke();
ctx.fill();
const collapsedItemConfig = collapsedMap.get(item);
let finalLabel = label;
if (collapsedItemConfig && collapsedItemConfig.collapsed) {
const numberOfCollapsedItems = collapsedItemConfig.items.length;
finalLabel = `(${numberOfCollapsedItems}) ` + label;
}
if (width >= constants.LABEL_THRESHOLD) {
if (collapsedItemConfig) {
renderLabel(
ctx,
data,
finalLabel,
item,
width,
textAlign === "left" ? x + constants.GROUP_STRIP_MARGIN_LEFT + constants.GROUP_TEXT_OFFSET : x,
y,
textAlign
);
renderGroupingStrip(ctx, x, y, height, item, collapsedItemConfig);
} else {
renderLabel(ctx, data, finalLabel, item, width, x, y, textAlign);
}
}
};
return renderFunc;
}, [ctx, getBarColor, textAlign, data, collapsedMap]);
}
function renderGroupingStrip(ctx, x, y, height, item, collapsedItemConfig) {
const groupStripX = x + constants.GROUP_STRIP_MARGIN_LEFT;
ctx.beginPath();
ctx.rect(x, y, groupStripX - x + constants.GROUP_STRIP_WIDTH + constants.GROUP_STRIP_PADDING, height);
ctx.fill();
ctx.beginPath();
if (collapsedItemConfig.collapsed) {
ctx.rect(groupStripX, y + height / 4, constants.GROUP_STRIP_WIDTH, height / 2);
} else {
if (collapsedItemConfig.items[0] === item) {
ctx.rect(groupStripX, y + height / 2, constants.GROUP_STRIP_WIDTH, height / 2);
} else if (collapsedItemConfig.items[collapsedItemConfig.items.length - 1] === item) {
ctx.rect(groupStripX, y, constants.GROUP_STRIP_WIDTH, height / 2);
} else {
ctx.rect(groupStripX, y, constants.GROUP_STRIP_WIDTH, height);
}
}
ctx.fillStyle = "#666";
ctx.fill();
}
function walkTree(root, direction, data, totalViewTicks, rangeMin, rangeMax, wrapperWidth, collapsedMap, renderFunc) {
const stack = [];
stack.push({ item: root, levelOffset: 0 });
const pixelsPerTick = wrapperWidth * window.devicePixelRatio / totalViewTicks / (rangeMax - rangeMin);
let collapsedItemRendered = void 0;
while (stack.length > 0) {
const { item, levelOffset } = stack.shift();
let curBarTicks = item.value;
const muted = curBarTicks * pixelsPerTick <= constants.MUTE_THRESHOLD;
const width = curBarTicks * pixelsPerTick - (muted ? 0 : constants.BAR_BORDER_WIDTH * 2);
const height = constants.PIXELS_PER_LEVEL;
if (width < constants.HIDE_THRESHOLD) {
continue;
}
let offsetModifier = 0;
let skipRender = false;
const collapsedItemConfig = collapsedMap.get(item);
const isCollapsedItem = collapsedItemConfig && collapsedItemConfig.collapsed;
if (isCollapsedItem) {
if (collapsedItemRendered === collapsedItemConfig.items[0]) {
offsetModifier = direction === "children" ? -1 : 1;
skipRender = true;
} else {
collapsedItemRendered = void 0;
}
} else {
collapsedItemRendered = void 0;
}
if (!skipRender) {
const barX = getBarX(item.start, totalViewTicks, rangeMin, pixelsPerTick);
const barY = (item.level + levelOffset) * constants.PIXELS_PER_LEVEL;
let label = data.getLabel(item.itemIndexes[0]);
if (isCollapsedItem) {
collapsedItemRendered = item;
}
renderFunc(item, barX, barY, width, height, label, muted);
}
const nextList = direction === "children" ? item.children : item.parents;
if (nextList) {
stack.unshift(...nextList.map((c) => ({ item: c, levelOffset: levelOffset + offsetModifier })));
}
}
}
function useColorFunction(totalTicks, totalTicksRight, colorScheme, theme, mutedColor, rangeMin, rangeMax, matchedLabels, topLevel) {
return react.useCallback(
function getColor(item, label, muted) {
if (muted && !matchedLabels) {
return mutedColor;
}
const barColor = item.valueRight !== void 0 && (colorScheme === types.ColorSchemeDiff.Default || colorScheme === types.ColorSchemeDiff.DiffColorBlind) ? colors.getBarColorByDiff(item.value, item.valueRight, totalTicks, totalTicksRight, colorScheme) : colorScheme === types.ColorScheme.ValueBased ? colors.getBarColorByValue(item.value, totalTicks, rangeMin, rangeMax) : colors.getBarColorByPackage(label, theme);
if (matchedLabels) {
return matchedLabels.has(label) ? barColor.toHslString() : mutedColor;
}
return item.level > topLevel - 1 ? barColor.toHslString() : barColor.lighten(15).toHslString();
},
[totalTicks, totalTicksRight, colorScheme, theme, rangeMin, rangeMax, matchedLabels, topLevel, mutedColor]
);
}
function useSetupCanvas(canvasRef, wrapperWidth, numberOfLevels) {
const [ctx, setCtx] = react.useState();
react.useEffect(() => {
if (!(numberOfLevels && canvasRef.current)) {
return;
}
const ctx2 = canvasRef.current.getContext("2d");
const height = constants.PIXELS_PER_LEVEL * numberOfLevels;
canvasRef.current.width = Math.round(wrapperWidth * window.devicePixelRatio);
canvasRef.current.height = Math.round(height);
canvasRef.current.style.width = `${wrapperWidth}px`;
canvasRef.current.style.height = `${height / window.devicePixelRatio}px`;
ctx2.textBaseline = "middle";
ctx2.font = 12 * window.devicePixelRatio + "px monospace";
ctx2.strokeStyle = "white";
setCtx(ctx2);
}, [canvasRef, setCtx, wrapperWidth, numberOfLevels]);
return ctx;
}
function renderLabel(ctx, data, label, item, width, x, y, textAlign) {
ctx.save();
ctx.clip();
ctx.fillStyle = "#222";
const displayValue = data.valueDisplayProcessor(item.value);
const unit = displayValue.suffix ? displayValue.text + displayValue.suffix : displayValue.text;
const measure = ctx.measureText(label);
const spaceForTextInRect = width - constants.BAR_TEXT_PADDING_LEFT;
let fullLabel = `${label} (${unit})`;
let labelX = Math.max(x, 0) + constants.BAR_TEXT_PADDING_LEFT;
if (measure.width > spaceForTextInRect) {
ctx.textAlign = textAlign;
if (textAlign === "right") {
fullLabel = label;
labelX = x + width - constants.BAR_TEXT_PADDING_LEFT;
}
}
ctx.fillText(fullLabel, labelX, y + constants.PIXELS_PER_LEVEL / 2 + 2);
ctx.restore();
}
function getBarX(offset, totalTicks, rangeMin, pixelsPerTick) {
return (offset - totalTicks * rangeMin) * pixelsPerTick;
}
exports.getBarX = getBarX;
exports.useFlameRender = useFlameRender;
exports.walkTree = walkTree;
//# sourceMappingURL=rendering.cjs.map