import * as React from 'react'; import { Observer, observer } from 'mobx-react'; import { VictoryChart, VictorySelectionContainer, VictoryAxis, VictoryLabel, VictoryScatter, } from 'victory'; import { observable, action, computed, makeObservable } from 'mobx'; import { Popover } from 'react-bootstrap'; import { formatLogOddsRatio } from 'shared/lib/FormatUtils'; import { toConditionalPrecision } from 'shared/lib/NumberUtils'; import autobind from 'autobind-decorator'; import SelectionComponent from './SelectionComponent'; import HoverablePoint from './HoverablePoint'; import { axisLabelStyles, CBIOPORTAL_VICTORY_THEME, DownloadControls, getTextWidth, truncateWithEllipsis, } from 'cbioportal-frontend-commons'; export interface IMiniScatterChartProps { data: any[]; xAxisLeftLabel: string; xAxisRightLabel: string; xAxisDomain: number; xAxisTickValues: number[] | null; // can be hugoGeneSymbols (Gene) or stableIds (Generic Assay) onSelection: (selectedIds: string[]) => void; onSelectionCleared: () => void; selectedSet: { [selectedIds: string]: any }; // give either onGeneNameClick or onGenericAssayEntityClick onGeneNameClick?: (hugoGeneSymbol: string, entrezGeneId: number) => void; } @observer export default class MiniScatterChart< P extends IMiniScatterChartProps > extends React.Component { @observable tooltipModel: any = null; @observable private svgContainer: any = null; private dragging = false; constructor(props: any) { super(props); makeObservable(this); } @action.bound private svgRef(svgContainer: SVGElement | null) { this.svgContainer = svgContainer && svgContainer.children ? svgContainer.children[0] : null; } protected handleSelection(points: any, bounds: any, props: any) { this.props.onSelection( points[0].data.map((d: any) => d.hugoGeneSymbol) ); } @autobind protected handleSelectionCleared() { if (this.tooltipModel) { if (this.props.onGeneNameClick) { this.props.onGeneNameClick( this.tooltipModel.hugoGeneSymbol, this.tooltipModel.entrezGeneId ); } } this.props.onSelectionCleared(); } @action.bound private onPointMouseOver(datum: any, x: number, y: number) { this.tooltipModel = datum; this.tooltipModel.x = x; this.tooltipModel.y = y; } @action.bound private onPointMouseOut() { this.tooltipModel = null; } protected get tooltipTitle() { return `Gene: ${this.tooltipModel.hugoGeneSymbol}`; } @autobind private getTooltip() { if (this.tooltipModel) { return ( {this.tooltipTitle}
Log2 Ratio: {formatLogOddsRatio(this.tooltipModel.logRatio)}
p-Value:{' '} {toConditionalPrecision(this.tooltipModel.pValue, 3, 0.01)}
q-Value:{' '} {toConditionalPrecision(this.tooltipModel.qValue, 3, 0.01)}
); } else { return ; } } @autobind private onClick() { if (!this.dragging) { this.handleSelectionCleared(); } this.dragging = false; } @autobind private onSelectionComponentRender() { this.dragging = true; } private get totalLabelWidths() { return ( getTextWidth(this.props.xAxisLeftLabel, 'Arial', '13px') + getTextWidth(this.props.xAxisRightLabel, 'Arial', '13px') ); } @computed get xAxisLeftLabel() { if (this.totalLabelWidths > 200) { return truncateWithEllipsis( this.props.xAxisLeftLabel, 90, 'Arial', '13px' ); } else { return this.props.xAxisLeftLabel; } } @computed get xAxisRightLabel() { if (this.totalLabelWidths > 180) { return truncateWithEllipsis( this.props.xAxisRightLabel, 90, 'Arial', '13px' ); } else { return this.props.xAxisRightLabel; } } protected get scatterFillColorFunction() { return (datum: any) => { if (datum.hugoGeneSymbol in this.props.selectedSet) { return '#FE9929'; } else if (datum.qValue < 0.05) { return '#58ACFA'; } else { return '#D3D3D3'; } }; } public render() { return (
this.handleSelection(points, bounds, props) } responsive={false} onSelectionCleared={this.handleSelectionCleared} selectionComponent={ } /> } theme={CBIOPORTAL_VICTORY_THEME} domainPadding={{ y: 20 }} height={350} width={350} padding={{ top: 40, bottom: 60, left: 60, right: 40 }} singleQuadrantDomainPadding={false} > t >= 1000 || t <= -1000 ? `${t / 1000}k` : t } style={{ tickLabels: { padding: 20 }, axisLabel: { padding: 40 }, ticks: { size: 0 }, grid: { strokeOpacity: 1, }, }} crossAxis={false} orientation="bottom" offsetY={60} /> } /> this.svgContainer} filename="enrichments-volcano" dontFade={true} type="button" style={{ position: 'absolute', top: 10, right: 10, zIndex: 0, }} />
{this.getTooltip}
); } }