import * as React from 'react'; import { observer } from 'mobx-react'; import { ClinicalViolinPlotIndividualPoint, ClinicalViolinPlotBoxData, } from 'cbioportal-ts-api-client'; import { computed } from 'mobx'; import { getViolinX, violinPlotSvgHeight, violinPlotXPadding, } from 'pages/studyView/charts/violinPlotTable/StudyViewViolinPlotUtils'; export interface IStudyViewViolinPlotProps { curveMagnitudes: number[]; violinBounds: { min: number; max: number }; individualPoints: ClinicalViolinPlotIndividualPoint[]; boxData: ClinicalViolinPlotBoxData; showViolin: boolean; showBox: boolean; width: number; gridValues: number[]; onMouseOverPoint: ( p: ClinicalViolinPlotIndividualPoint, mouseX: number, mouseY: number ) => void; onMouseOverBoxPlot: (mouseX: number, mouseY: number) => void; onMouseOverBackground: () => void; } export const yPadding = 3; export const height = violinPlotSvgHeight - 2 * yPadding; @observer export default class StudyViewViolinPlot extends React.Component< IStudyViewViolinPlotProps, {} > { @computed get svgWidth() { return this.props.width; } @computed get violinWidth() { return this.svgWidth - 2 * violinPlotXPadding; } renderCurve() { if (this.props.curveMagnitudes.length > 0) { // This code draws a violin plot from the given curve magnitudes. // The violin plot is a mirrored version of the given curve. // Central y-coordinate const center = violinPlotSvgHeight / 2; // Maximum drawn magnitude (i.e. distance from the center) of each // half of the violin. const curveLimit = height / 2; // Multiplicative factor converting the given magnitudes to the // svg coordinate space. const scale = Math.min( 1, curveLimit / Math.max(...this.props.curveMagnitudes) ); // Compute the curve magnitudes in svg coordinates by multiplying the scale factor. const curve = this.props.curveMagnitudes.map(y => scale * y); // Compute the step width in the x-direction (i.e. the x distance between // each given curve point). const stepSize = this.violinWidth / (curve.length - 1); // Draw the same curve twice, mirrored halves - one will be // on the upper side of the y-center, one will be on the lower side. let dUpperHalf = `M 0 ${center} L 0 ${center - curve[0]}`; let dLowerHalf = `M 0 ${center} L 0 ${center + curve[0]}`; for (let i = 1; i < curve.length; i++) { dUpperHalf = `${dUpperHalf} L ${i * stepSize} ${center - curve[i]}`; dLowerHalf = `${dLowerHalf} L ${i * stepSize} ${center + curve[i]}`; } dUpperHalf = `${dUpperHalf} L ${this.violinWidth} ${center} L 0 ${center}`; dLowerHalf = `${dLowerHalf} L ${this.violinWidth} ${center} L 0 ${center}`; return ( ); } else { return null; } } private x(v: number) { return getViolinX(v, this.props.violinBounds, this.violinWidth); } renderPoints() { // Render individual given points (e.g. outliers, or points if // there are too few to create a density/violin plot). const y = violinPlotSvgHeight / 2; return this.props.individualPoints.map(d => { return ( { this.props.onMouseOverPoint(d, e.pageX, e.pageY); e.stopPropagation(); }} cx={this.x(d.value)} cy={y} r={3} /> ); }); } renderBox() { const whiskerStrokeWidth = 0.5; const center = violinPlotSvgHeight / 2; const whiskerLowerX = this.x(this.props.boxData.whiskerLower); const whiskerUpperX = this.x(this.props.boxData.whiskerUpper); const whiskerTop = center - 3; const whiskerBottom = center + 3; const boxWidth = this.x(this.props.boxData.q3) - this.x(this.props.boxData.q1); const boxHeight = 10; //whiskerBottom - whiskerTop; const boxX = this.x(this.props.boxData.q1); const boxY = center - boxHeight / 2; //whiskerTop; const median = this.x(this.props.boxData.median); return ( <> {/*left whisker*/} {/*connecting line*/} {/*right whisker*/} {/*box*/} this.props.onMouseOverBoxPlot(e.pageX, e.pageY) } /> {/*median*/} ); } renderGridLines() { return ( <> {this.props.gridValues.map((val, index) => { const x = this.x(val); return ( <> ); })} ); } render() { return ( {this.renderGridLines()} {this.props.curveMagnitudes.length > 0 && this.props.showViolin && this.renderCurve()} {this.props.curveMagnitudes.length > 0 && this.props.showBox && this.renderBox()} {(this.props.showViolin || this.props.showBox) && this.renderPoints()} ); } }