UNPKG

@grafana/flamegraph

Version:

Grafana flamegraph visualization component

257 lines (250 loc) • 9.15 kB
'use strict'; 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); "use strict"; 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