speedscope/flamechart-view.tsx
Jamie Wong 0e654801b5
Upgrade the "Table view" to a "Sandwich" view (#73)
![image](https://user-images.githubusercontent.com/150329/41837387-25417bae-7812-11e8-83cb-d3e6782b734e.png)

This provides information about the caller & callees of individual functions selected in the table view.
2018-06-29 12:06:19 -07:00

177 lines
5.4 KiB
TypeScript

import {h} from 'preact'
import {css} from 'aphrodite'
import {ReloadableComponent} from './reloadable'
import {CallTreeNode, Frame} from './profile'
import {Flamechart} from './flamechart'
import {Rect, Vec2, AffineTransform} from './math'
import {formatPercent} from './utils'
import {FlamechartMinimapView} from './flamechart-minimap-view'
import {style} from './flamechart-style'
import {Sizes, commonStyle} from './style'
import {CanvasContext} from './canvas-context'
import {FlamechartRenderer} from './flamechart-renderer'
import {FlamechartDetailView} from './flamechart-detail-view'
import {FlamechartPanZoomView} from './flamechart-pan-zoom-view'
import {Hovertip} from './hovertip'
interface FlamechartViewProps {
flamechart: Flamechart
canvasContext: CanvasContext
flamechartRenderer: FlamechartRenderer
getCSSColorForFrame: (frame: Frame) => string
}
interface FlamechartViewState {
hover: {
node: CallTreeNode
event: MouseEvent
} | null
selectedNode: CallTreeNode | null
configSpaceViewportRect: Rect
}
export class FlamechartView extends ReloadableComponent<FlamechartViewProps, FlamechartViewState> {
constructor() {
super()
this.state = {
hover: null,
selectedNode: null,
configSpaceViewportRect: Rect.empty,
}
}
private configSpaceSize() {
return new Vec2(
this.props.flamechart.getTotalWeight(),
this.props.flamechart.getLayers().length,
)
}
private minConfigSpaceViewportRectWidth() {
return Math.min(
this.props.flamechart.getTotalWeight(),
3 * this.props.flamechart.getMinFrameWidth(),
)
}
private setConfigSpaceViewportRect = (viewportRect: Rect): void => {
const configSpaceDetailViewHeight = Sizes.DETAIL_VIEW_HEIGHT / Sizes.FRAME_HEIGHT
const configSpaceOriginBounds = new Rect(
new Vec2(0, -1),
Vec2.max(
new Vec2(0, 0),
this.configSpaceSize()
.minus(viewportRect.size)
.plus(new Vec2(0, configSpaceDetailViewHeight + 1)),
),
)
const configSpaceSizeBounds = new Rect(
new Vec2(this.minConfigSpaceViewportRectWidth(), viewportRect.height()),
new Vec2(this.configSpaceSize().x, viewportRect.height()),
)
this.setState({
configSpaceViewportRect: new Rect(
configSpaceOriginBounds.closestPointTo(viewportRect.origin),
configSpaceSizeBounds.closestPointTo(viewportRect.size),
),
})
}
private transformViewport = (transform: AffineTransform): void => {
const viewportRect = transform.transformRect(this.state.configSpaceViewportRect)
this.setConfigSpaceViewportRect(viewportRect)
}
onNodeHover = (hover: {node: CallTreeNode; event: MouseEvent} | null) => {
this.setState({hover})
}
onNodeClick = (node: CallTreeNode | null) => {
this.setState({
selectedNode: node,
})
}
formatValue(weight: number) {
const totalWeight = this.props.flamechart.getTotalWeight()
const percent = 100 * weight / totalWeight
const formattedPercent = formatPercent(percent)
return `${this.props.flamechart.formatValue(weight)} (${formattedPercent})`
}
renderTooltip() {
if (!this.container) return null
const {hover} = this.state
if (!hover) return null
const {width, height, left, top} = this.container.getBoundingClientRect()
const offset = new Vec2(hover.event.clientX - left, hover.event.clientY - top)
return (
<Hovertip containerSize={new Vec2(width, height)} offset={offset}>
<span className={css(style.hoverCount)}>
{this.formatValue(hover.node.getTotalWeight())}
</span>{' '}
{hover.node.frame.name}
</Hovertip>
)
}
container: HTMLDivElement | null = null
containerRef = (container?: Element) => {
this.container = (container as HTMLDivElement) || null
}
panZoomView: FlamechartPanZoomView | null = null
panZoomRef = (view: FlamechartPanZoomView | null) => {
this.panZoomView = view
}
subcomponents() {
return {
panZoom: this.panZoomView,
}
}
render() {
return (
<div className={css(style.fill, commonStyle.vbox)} ref={this.containerRef}>
<FlamechartMinimapView
configSpaceViewportRect={this.state.configSpaceViewportRect}
transformViewport={this.transformViewport}
flamechart={this.props.flamechart}
flamechartRenderer={this.props.flamechartRenderer}
canvasContext={this.props.canvasContext}
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect}
/>
<FlamechartPanZoomView
ref={this.panZoomRef}
canvasContext={this.props.canvasContext}
flamechart={this.props.flamechart}
flamechartRenderer={this.props.flamechartRenderer}
renderInverted={false}
onNodeHover={this.onNodeHover}
onNodeSelect={this.onNodeClick}
selectedNode={this.state.selectedNode}
transformViewport={this.transformViewport}
configSpaceViewportRect={this.state.configSpaceViewportRect}
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect}
/>
{this.renderTooltip()}
{this.state.selectedNode && (
<FlamechartDetailView
flamechart={this.props.flamechart}
getCSSColorForFrame={this.props.getCSSColorForFrame}
selectedNode={this.state.selectedNode}
/>
)}
</div>
)
}
}