Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/pages/patientView/timeline2/VAFChartUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IPoint,
numLeadingDecimalZeros,
round10,
minimalDistinctTickStrings,
yValueScaleFunction,
} from './VAFChartUtils';
import { GROUP_BY_NONE } from '../timeline2/VAFChartControls';
Expand Down Expand Up @@ -940,6 +941,36 @@ describe('VAFChartUtils', () => {
});
});

describe('minimalDistinctTickStrings', () => {
it('works with empty array', () => {
assert.deepEqual(minimalDistinctTickStrings([]), []);
});
it('converts to string', () => {
assert.deepEqual(minimalDistinctTickStrings([1]), ['1']);
});
it('deduplicate numbers', () => {
assert.deepEqual(minimalDistinctTickStrings([1, 1]), ['1']);
});
it('shows equals digits in fractional part', () => {
assert.deepEqual(minimalDistinctTickStrings([1, 1.1]), [
'1.0',
'1.1',
]);
});
it('shows just enough numbers in fractional part to distinguish number', () => {
assert.deepEqual(
minimalDistinctTickStrings([0.01, 0.002, 0.0003]),
['0.010', '0.002', '0.000']
);
});
it('falls back on the scientific notation of original numbers if 3 decimal digits are not enough to distinguish', () => {
assert.deepEqual(minimalDistinctTickStrings([0.0001, 0.000201]), [
'1e-4',
'2.01e-4',
]);
});
});

describe('yValueScaleFunction', () => {
it('handles linear scale, zero minY tickmark', () => {
const yPadding = 10;
Expand Down
28 changes: 28 additions & 0 deletions src/pages/patientView/timeline2/VAFChartUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,31 @@ export function numLeadingDecimalZeros(y: number) {
if (y == 0 || y >= 0.1) return 0;
return numberOfLeadingDecimalZeros(y);
}

/**
* Try to return tick labels with fractional part just long enogh to disttinguish them.
* It tries up to 3th positions in fractional part. Otherwise return scientific representation of original numbers.
* Function returns distinct labels.
* If input contains duplicates function deduplicates and return less labels that amount of input numbers.
* @param nums array of ticks as numbers
*/
export function minimalDistinctTickStrings(nums: number[]): string[] {
const distinctNums = nums.filter((v, i, a) => a.indexOf(v) === i);

const fractionalNumbersToShow = distinctNums.map(num =>
Number.isInteger(num) ? 0 : numLeadingDecimalZeros(num) + 1
);

const fromPos = Math.min(...fractionalNumbersToShow);
const toPos = 3;

for (let pos = fromPos; pos <= toPos; pos++) {
const labels = distinctNums
.map(num => num.toFixed(pos))
.filter((v, i, a) => a.indexOf(v) === i);
if (labels.length === distinctNums.length) {
return labels;
}
}
return distinctNums.map(num => num.toExponential());
}
35 changes: 23 additions & 12 deletions src/pages/patientView/timeline2/VAFChartWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
IPoint,
numLeadingDecimalZeros,
yValueScaleFunction,
minimalDistinctTickStrings,
} from './VAFChartUtils';
import { VAFChartHeader } from './VAFChartHeader';
import {
Expand Down Expand Up @@ -123,14 +124,18 @@ export default class VAFChartWrapper extends React.Component<
}

@computed get ticks(): { label: string; value: number; offset: number }[] {
const tickmarkValues = getYAxisTickmarks(
let tickmarkValues = getYAxisTickmarks(
this.minYTickmarkValue,
this.maxYTickmarkValue
);
const numDecimals = numLeadingDecimalZeros(this.minYTickmarkValue) + 1;
return _.map(tickmarkValues, (v: number) => {
const labels = minimalDistinctTickStrings(tickmarkValues);
const ticksHasDuplicates = tickmarkValues.length !== labels.length;
if (ticksHasDuplicates) {
tickmarkValues = labels.map(label => Number(label));
}
return _.map(tickmarkValues, (v: number, indx: number) => {
return {
label: v.toFixed(numDecimals),
label: labels[indx],
value: v,
offset: this.scaleYValue(v),
};
Expand Down Expand Up @@ -412,11 +417,13 @@ export default class VAFChartWrapper extends React.Component<
return _.sum([this.wrapperStore.dataHeight, footerHeight]);
}

render() {
if (!this.store || !this.wrapperStore) return null;

@computed get customTracks() {
const mouseOverMutation = this.props.dataStore.mouseOverMutation;
const selectedMutations = this.props.dataStore.selectedMutations;
const lineData = this.scaledAndColoredLineData;
const height = this.vafChartHeight;
const headerWidth =
this.numGroupByGroups > 0 ? 150 : this.props.headerWidth;

const vafPlotTrack = {
renderHeader: (store: TimelineStore) => (
Expand All @@ -438,17 +445,21 @@ export default class VAFChartWrapper extends React.Component<
onlyShowSelectedInVAFChart={
this.wrapperStore.onlyShowSelectedInVAFChart
}
lineData={this.scaledAndColoredLineData}
height={this.vafChartHeight}
lineData={lineData}
height={height}
width={this.store.pixelWidth}
/>
),
disableHover: true,
height: (store: TimelineStore) => this.vafChartHeight,
height: (store: TimelineStore) => height,
labelForExport: 'VAF',
} as CustomTrackSpecification;

let customTracks = [vafPlotTrack].concat(this.sampleIconsTracks);
return [vafPlotTrack].concat(this.sampleIconsTracks);
}

render() {
if (!this.store || !this.wrapperStore) return null;

return (
<>
Expand All @@ -471,7 +482,7 @@ export default class VAFChartWrapper extends React.Component<
hideLabels={false}
hideXAxis={this.wrapperStore.showSequentialMode}
visibleTracks={[]}
customTracks={customTracks}
customTracks={this.customTracks}
headerWidth={
this.numGroupByGroups > 0
? 150
Expand Down