Skip to content

Commit

Permalink
updates per PR and add transition on resize
Browse files Browse the repository at this point in the history
  • Loading branch information
hallieswan committed Jul 7, 2023
1 parent 2096feb commit bb5a078
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<div id="median-barchart" class="chart-d3">
<div id="median-barchart" #medianBarChartContainer class="chart-d3">
<div
id="tooltip"
class="arrow-above tooltip-arrow"
#tooltip
(mouseout)="hideTooltip()"
></div>
<svg #chart (window:resize)="onResize()"></svg>
<svg #chart></svg>
<div class="chart-no-data" *ngIf="data.length === 0">
<div class="chart-no-data-text">No data is currently available.</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
private MEANINGFUL_EXPRESSION_THRESHOLD = Math.log2(5);
private maxValueY = -1;

private MIN_CHART_WIDTH = 500;
private CHART_HEIGHT = 350;
private chartMargin = { top: 20, right: 20, bottom: 65, left: 65 };
private chartXScale!: d3.ScaleBand<string>;
private chartXAxisDrawn!: d3.Selection<SVGGElement, unknown, null, undefined>;
private chartXAxisLabel!: d3.Selection<SVGTextElement, unknown, null, undefined>;
private chartBars!: d3.Selection<SVGRectElement, MedianExpression, any, unknown>;
private chartScoreLabels!: d3.Selection<SVGTextElement, MedianExpression, any, unknown>;
private chartThresholdLine!: d3.Selection<SVGLineElement, unknown, null, undefined>;

private resizeTimer: ReturnType<typeof setTimeout> | number = 0;

get data() {
return this._data;
}
Expand All @@ -53,17 +65,28 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
);
}

@Input() shouldResize = true;
@Input() xAxisLabel = '';
@Input() yAxisLabel = 'LOG2 CPM';

@ViewChild('medianBarChartContainer') medianBarChartContainer: ElementRef<HTMLElement> = {} as ElementRef;
@ViewChild('chart') chartRef: ElementRef<SVGElement> = {} as ElementRef;
@ViewChild('tooltip') tooltipRef: ElementRef<HTMLElement> = {} as ElementRef;

dimension: any;
group: any;

constructor(private helperService: HelperService) {}

@HostListener('window:resize', ['$event.target'])
onResize() {
if (this.shouldResize && this.chartInitialized) {
const self = this;
const divSize = this.medianBarChartContainer.nativeElement.getBoundingClientRect().width;
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
self.resizeChart(divSize);
}, 100);
}
};

ngOnChanges(changes: SimpleChanges): void {
if (
(changes._data && !changes._data.firstChange) ||
Expand Down Expand Up @@ -134,28 +157,27 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
getChartBoundingWidth(): number {
return (
d3.select(this.chartRef.nativeElement).node()?.getBoundingClientRect()
.width || 500
.width || this.MIN_CHART_WIDTH
);
}

createChart() {
if (this._data.length > 0) {
const barColor = this.helperService.getColor('secondary');
const width = this.getChartBoundingWidth();
const height = 350;
const margin = { top: 20, right: 20, bottom: 65, left: 65 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const height = this.CHART_HEIGHT;
const innerWidth = width - this.chartMargin.left - this.chartMargin.right;
const innerHeight = height - this.chartMargin.top - this.chartMargin.bottom;

this.chart = d3
.select(this.chartRef.nativeElement)
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
.attr('transform', `translate(${this.chartMargin.left}, ${this.chartMargin.top})`);

// SCALES
const xScale = d3
this.chartXScale = d3
.scaleBand()
.domain(this._data.map((d) => d.tissue))
.range([0, innerWidth])
Expand All @@ -168,45 +190,46 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
.range([innerHeight, 0]);

// BARS
this.chart
this.chartBars = this.chart
.selectAll('.medianbars')
.data(this._data)
.enter()
.append('rect')
.attr('class', 'medianbars')
.attr('x', (d) => xScale(d.tissue) as number)
.attr('x', (d) => this.chartXScale(d.tissue) as number)
.attr('y', (d) => yScale(d.medianlogcpm || 0))
.attr('width', xScale.bandwidth())
.attr('width', this.chartXScale.bandwidth())
.attr('height', (d) => innerHeight - yScale(d.medianlogcpm || 0))
.attr('fill', barColor);

// SCORE LABELS
this.chart
this.chartScoreLabels = this.chart
.selectAll('.bar-labels')
.data(this._data)
.enter()
.append('text')
.attr('class', 'bar-labels')
.attr('x', (d) => this.getBarCenterX(d.tissue, xScale))
.attr('x', (d) => this.getBarCenterX(d.tissue, this.chartXScale))
.attr('y', (d) => yScale(d.medianlogcpm || 0) - 5)
.text((d) => this.helperService.roundNumber(d.medianlogcpm || 0, 2));

// X-AXIS
const xAxis = d3.axisBottom(xScale);
this.chart
const xAxis = d3.axisBottom(this.chartXScale);
this.chartXAxisDrawn = this.chart
.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${innerHeight})`)
.call(xAxis.tickSizeOuter(0))
.call(xAxis);
this.chartXAxisDrawn
.selectAll('.tick')
.on('mouseenter', (_, tissue) => {
const tooltipText = this.helperService.getGCTColumnTooltipText(
tissue as string
);
this.showTooltip(
tooltipText,
this.getBarCenterX(tissue as string, xScale) + margin.left,
height - margin.top
this.getBarCenterX(tissue as string, this.chartXScale) + this.chartMargin.left,
height - this.chartMargin.top
);
})
.on('mouseleave', () => {
Expand All @@ -218,11 +241,11 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
this.chart.append('g').attr('class', 'y-axis').call(yAxis);

// X-AXIS LABEL
this.chart
this.chartXAxisLabel = this.chart
.append('text')
.attr('class', 'x-axis-label')
.attr('x', innerWidth / 2)
.attr('y', innerHeight + margin.bottom)
.attr('y', innerHeight + this.chartMargin.bottom)
.attr('text-anchor', 'middle')
.text(this.xAxisLabel);

Expand All @@ -231,14 +254,14 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
.append('text')
.attr('class', 'y-axis-label')
.attr('x', -innerHeight / 2)
.attr('y', -margin.left)
.attr('y', -this.chartMargin.left)
.attr('dy', '1em')
.attr('text-anchor', 'middle')
.attr('transform', 'rotate(-90)')
.text(this.yAxisLabel);

// THRESHOLD LINE
this.chart
this.chartThresholdLine = this.chart
.append('line')
.attr('class', 'meaningful-expression-threshold-line')
.attr('x1', 0)
Expand All @@ -251,9 +274,42 @@ export class MedianBarChartComponent implements OnChanges, AfterViewInit, OnDest
}
}

@HostListener('window:resize', ['$event'])
onResize() {
this.clearChart();
this.createChart();
}
resizeChart = (divSize: number): void => {
// calculate new width
const width = Math.max(divSize, this.MIN_CHART_WIDTH);
const innerWidth = width - this.chartMargin.left - this.chartMargin.right;

// update chart size
this.chart.attr('width', width);

// update chartXScale
this.chartXScale.range([0, innerWidth]);

// update bars
this.chartBars
.transition()
.attr('x', (d) => this.chartXScale(d.tissue) as number)
.attr('width', this.chartXScale.bandwidth());

// update score labels
this.chartScoreLabels
.transition()
.attr('x', (d) => this.getBarCenterX(d.tissue, this.chartXScale));

// update drawn x-axis
const xAxis = d3.axisBottom(this.chartXScale);
this.chartXAxisDrawn
.transition()
.call(xAxis.tickSizeOuter(0));

// update x-axis label
this.chartXAxisLabel
.transition()
.attr('x', innerWidth / 2);

// update threshold line
this.chartThresholdLine
.transition()
.attr('x2', innerWidth);
};
}
2 changes: 1 addition & 1 deletion src/styles/components/_chart_d3.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
}

&.arrow-above {
transform: translate(calc(-50%), calc(50%));
transform: translate(calc(-50%), calc(50% - 12px));

&::before {
top: -9px;
Expand Down

0 comments on commit bb5a078

Please sign in to comment.