You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
5.8 KiB
183 lines
5.8 KiB
import { DataRow, DataItem } from './data_processor'; |
|
import { BarConfig } from './bar_options'; |
|
|
|
import { PodState } from '@chartwerk/core'; |
|
|
|
import { BarOptions, BarPodType, BarSerie, SizeType } from '../types'; |
|
|
|
import * as d3 from 'd3'; |
|
import * as _ from 'lodash'; |
|
|
|
|
|
export class BarGroup { |
|
constructor( |
|
protected overlay: d3.Selection<SVGRectElement, unknown, null, undefined>, // overlay from core. It should be global |
|
protected container: d3.Selection<SVGGElement, unknown, null, undefined>, |
|
protected dataRow: DataRow, |
|
protected options: BarConfig, |
|
protected state: PodState<BarSerie, BarOptions>, |
|
protected boxParams: { width: number, height: number, zeroHorizon: number }, |
|
protected allDataLength: number, |
|
) { |
|
this.setGroupWidth(); |
|
this.setBarWidth(); |
|
|
|
this.renderBarGroup(); |
|
} |
|
|
|
protected _groupContainer: d3.Selection<SVGGElement, unknown, null, undefined>; |
|
protected _groupWidth: number; |
|
protected _barWidth: number; |
|
|
|
public getGroupContainer(): d3.Selection<SVGGElement, unknown, null, undefined> { |
|
return this._groupContainer; |
|
} |
|
|
|
public getGroupWidth(): number { |
|
return this._groupWidth; |
|
} |
|
|
|
public getBarWidth(): number { |
|
return this._barWidth; |
|
} |
|
|
|
protected renderBarGroup(): void { |
|
this._groupContainer = this.container.append('g').attr('class', `bar-group group-${this.dataRow.key}`); |
|
_.forEach(this.dataRow.items, (data: DataItem, serieIdx: number) => { |
|
this.renderStackedBars(data, serieIdx); |
|
}); |
|
} |
|
|
|
protected renderStackedBars(data: DataItem, serieIdx: number): void { |
|
const barsHeightPositiveList = []; |
|
const barsHeightNegativeList = []; |
|
data.values.forEach((value: number, valueIdx: number) => { |
|
const barHeight = this.getBarHeight(value); |
|
const config = { |
|
position: { |
|
x: this.getBarPositionX(serieIdx), |
|
y: this.getBarPositionY(value, barsHeightPositiveList, barsHeightNegativeList, barHeight), |
|
}, |
|
width: this._barWidth, |
|
height: barHeight, |
|
color: data.colors[valueIdx], |
|
opacity: data.opacity[valueIdx], |
|
value, |
|
}; |
|
if(value >= 0) { |
|
barsHeightPositiveList.push(barHeight); |
|
} else { |
|
barsHeightNegativeList.push(barHeight); |
|
} |
|
|
|
this.renderBar(config); |
|
}); |
|
} |
|
|
|
protected getBarPositionX(idx: number): number { |
|
return this.state.xScale(this.dataRow.key) + this._barWidth * idx; |
|
} |
|
|
|
protected getBarPositionY( |
|
value: number, barsHeightPositiveList: number[], barsHeightNegativeList: number[], barHeight: number |
|
): number { |
|
if(value >= 0) { |
|
const previousBarsHeight = _.sum(barsHeightPositiveList); |
|
return this.boxParams.zeroHorizon - previousBarsHeight - barHeight; |
|
} |
|
const previousBarsHeight = _.sum(barsHeightNegativeList); |
|
return this.boxParams.zeroHorizon + previousBarsHeight; |
|
} |
|
|
|
protected getBarHeight(value): number { |
|
return Math.abs(this.boxParams.zeroHorizon - this.state.yScale(value)); |
|
} |
|
|
|
protected setGroupWidth(): void { |
|
switch(this.options.barType) { |
|
case BarPodType.DISCRETE: |
|
this.setDiscreteGroupWidth(); |
|
return; |
|
case BarPodType.NON_DISCRETE: |
|
this.setNonDiscreteGroupWidth(); |
|
return; |
|
} |
|
} |
|
|
|
protected setDiscreteGroupWidth(): void { |
|
const groupSizeConfig = this.options.dicreteConfig.groupSize.width; |
|
let width; |
|
if(groupSizeConfig.type === SizeType.PX) { |
|
width = groupSizeConfig.value; |
|
} |
|
if(groupSizeConfig.type === SizeType.PERCENT) { |
|
const factor = groupSizeConfig.value / 100; |
|
width = (this.boxParams.width / this.allDataLength) * factor; |
|
} |
|
this._groupWidth = _.clamp(width, this.options.dicreteConfig.groupSize.min || width, this.options.dicreteConfig.groupSize.max || width); |
|
} |
|
|
|
protected setNonDiscreteGroupWidth(): void { |
|
const widthConfig = this.options.nonDicreteConfig.barWidth; |
|
if(widthConfig.estimated.value === undefined) { |
|
const groupWidth = this.boxParams.width / (this.allDataLength * this.dataRow.items.length); |
|
this._groupWidth = groupWidth * 0.5; |
|
return; |
|
} |
|
let width; |
|
if(widthConfig.estimated.type === SizeType.PX) { |
|
width = widthConfig.estimated.value; |
|
} |
|
if(widthConfig.estimated.type === SizeType.UNIT) { |
|
width = this.state.absXScale(widthConfig.estimated.value); |
|
} |
|
this._groupWidth = _.clamp(width, widthConfig.min || width, widthConfig.max || width); |
|
} |
|
|
|
protected setBarWidth(): void { |
|
switch(this.options.barType) { |
|
case BarPodType.DISCRETE: |
|
this._barWidth = this._groupWidth / this.dataRow.items.length; |
|
return; |
|
case BarPodType.NON_DISCRETE: |
|
this._barWidth = this._groupWidth; |
|
return; |
|
} |
|
} |
|
|
|
protected renderBar(config: any): void { |
|
this._groupContainer.append('rect') |
|
.attr('x', config.position.x) |
|
.attr('y', config.position.y) |
|
.attr('width', config.width) |
|
.attr('height', config.height) |
|
.style('fill', config.color) |
|
.attr('opacity', config.opacity) |
|
.on('contextmenu', this.contextMenu.bind(this)) |
|
.on('mouseover', this.redirectEventToOverlay.bind(this)) |
|
.on('mousemove', this.redirectEventToOverlay.bind(this)) |
|
.on('mouseout', this.redirectEventToOverlay.bind(this)) |
|
.on('mousedown', () => { d3.event.stopPropagation(); }); |
|
} |
|
|
|
contextMenu(): void { |
|
d3.event.preventDefault(); // do not open browser's context menu. |
|
const event = d3.mouse(this.container.node()); |
|
|
|
this.options.callbackContextMenu({ |
|
position: { |
|
eventX: event[0], |
|
eventY: event[1], |
|
pageX: d3.event.pageX, |
|
pageY: d3.event.pageY, |
|
valueX: this.state.xScale.invert(event[0]), |
|
valueY: this.state.yScale.invert(event[1]), |
|
}, |
|
data: this.dataRow, |
|
}); |
|
} |
|
|
|
redirectEventToOverlay(): void { |
|
this.overlay?.node().dispatchEvent(new MouseEvent(d3.event.type, d3.event)); |
|
} |
|
}
|
|
|