import { scaleLinear, scaleLog } from 'd3-scale'; import _ from 'lodash'; import { getSampleViewUrl, getStudySummaryUrl } from 'shared/api/urls'; import * as React from 'react'; import { ClinicalViolinPlotBoxData, ClinicalViolinPlotIndividualPoint, } from 'cbioportal-ts-api-client'; import { toFixedWithoutTrailingZeros } from 'shared/lib/FormatUtils'; import joinJsx from 'shared/lib/joinJsx'; export function getDataX( violinX: number, dataBounds: { min: number; max: number }, plotWidth: number ) { // inverse of getViolinX // map from violin scale to data scale return ( dataBounds.min + ((violinX - violinPlotXPadding) * (dataBounds.max - dataBounds.min)) / plotWidth ); } export function getViolinX( dataX: number, dataBounds: { min: number; max: number }, plotWidth: number ) { // map from data scale to violin scale return ( violinPlotXPadding + ((dataX - dataBounds.min) * plotWidth) / (dataBounds.max - dataBounds.min) ); } function isPowerOfTen(x: number) { const str = x.toString(); return str[0] === '1' && parseFloat(str.substring(1)) === 0; } export function getTickValues( bounds: { min: number; max: number }, chartDimensionWidth: number, logScale: boolean ) { let range = [bounds.min, bounds.max]; let ret = [...range]; const numTotalTicks = 2 * chartDimensionWidth; const numMiddleTicks = numTotalTicks - 2; // we will always have the start and end as ticks if (logScale) { range = range.map(Math.exp); let middleTicks = scaleLog() .domain(range) .ticks(); if (middleTicks.length > numMiddleTicks) { // first just take out all non-multiples of ten middleTicks = middleTicks.filter(x => x % 10 === 0); } if (middleTicks.length > numMiddleTicks) { // if still too many, filter out non-powers of ten middleTicks = middleTicks.filter(isPowerOfTen); } while (middleTicks.length > numMiddleTicks) { // if still too many, iteratively filter out every other tick middleTicks = middleTicks.filter((x, index) => index % 2 === 0); } ret.push(...middleTicks.map(x => Math.log(x + 1))); // need to add 1 to counter the inversion } else { ret.push( ...scaleLinear() .domain(range) .ticks(2 * chartDimensionWidth - 2) ); } ret = _.sortBy(_.uniq(ret)); return ret; } export function renderTooltipForBoxPlot( boxData: ClinicalViolinPlotBoxData, logScale: boolean ) { const properties: Partial< { [prop in keyof ClinicalViolinPlotBoxData]: string } > = { median: 'Median', q1: 'Quartile 1', q3: 'Quartile 3', }; return ( <> {joinJsx( _.map( properties, (label: string, prop: keyof ClinicalViolinPlotBoxData) => { return ( <> {label}: {` `} {toFixedWithoutTrailingZeros( logScale ? Math.exp(boxData[prop]) - 1 : boxData[prop], 2 )} ); } ),
)} ); } export function renderTooltipForPoint( point: ClinicalViolinPlotIndividualPoint, violinColumnName: string, logScale: boolean ) { return ( <> Study ID: {` `} {point.studyId}
Sample ID: {` `} {point.sampleId}
{violinColumnName}: {` `} {toFixedWithoutTrailingZeros( logScale ? Math.exp(point.value) - 1 : point.value, 2 )} ); } export const violinPlotXPadding = 5; export const violinPlotSvgHeight = 30;