/
RcsbFvTrack.ts
285 lines (263 loc) · 11.7 KB
/
RcsbFvTrack.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import {RcsbBoard} from '../../RcsbBoard/RcsbBoard';
import {RcsbFvDefaultConfigValues, RcsbFvDisplayTypes} from '../RcsbFvConfig/RcsbFvDefaultConfigValues';
import {RcsbFvDisplay} from "./RcsbFvDisplay";
import {RcsbFvConfig} from "../RcsbFvConfig/RcsbFvConfig";
import {RcsbFvRowExtendedConfigInterface} from "../RcsbFvConfig/RcsbFvConfigInterface";
import {
RcsbFvTrackData,
RcsbDataManager,
RcsbFvTrackDataMap
} from "../../RcsbDataManager/RcsbDataManager";
import {RcsbDisplayInterface} from "../../RcsbBoard/RcsbDisplay/RcsbDisplayInterface";
import {
EventType,
RcsbFvContextManager,
RcsbFvContextManagerType, SelectionInterface
} from "../RcsbFvContextManager/RcsbFvContextManager";
import {Subscription} from "rxjs";
import {RcsbCompositeDisplay} from "../../RcsbBoard/RcsbDisplay/RcsbCompositeDisplay";
import {RcsbSelection} from "../../RcsbBoard/RcsbSelection";
import {RcsbScaleInterface} from "../../RcsbBoard/RcsbD3/RcsbD3ScaleFactory";
/**This className provides an abstraction layer to build and manage a particular board annotation cell*/
export class RcsbFvTrack {
/**SVG/HTML level object manager*/
private rcsbBoard: RcsbBoard;
/**Board annotation cells may contain different tracks to avoid visual overlapping*/
private rcsbTrackArray: Array<RcsbDisplayInterface> = new Array<RcsbDisplayInterface>();
/**Object that handles how data needs to be displayed*/
private rcsbFvDisplay: RcsbFvDisplay;
/**Row configuration object*/
private rcsbFvConfig: RcsbFvConfig;
/**DOM element id where the SVG component will be rendered*/
private elementId: string;
/**Event handler subscription*/
private subscription: Subscription;
/**Event Handler Manager. This is a common object for all board annotation cells*/
private readonly contextManager: RcsbFvContextManager;
/**X-Scale d3 object. This is a common for all board annotation cells*/
private readonly xScale: RcsbScaleInterface;
/**Current selection object. This is a common for all board annotation cells*/
private readonly selection: RcsbSelection;
public constructor(
args:RcsbFvRowExtendedConfigInterface,
xScale: RcsbScaleInterface,
selection: RcsbSelection,
contextManager: RcsbFvContextManager
) {
this.contextManager = contextManager;
this.xScale = xScale;
this.selection = selection;
if (typeof args.elementId === "string" && document.getElementById(args.elementId) != null) {
this.rcsbBoard = new RcsbBoard(args.elementId, xScale, this.selection, this.contextManager);
}
this.buildTrack(args);
this.subscription = this.subscribe();
this.rcsbBoard.highlightRegion(null, "set", "select", true);
}
/**Builds the board annotation cell
* @param args Board track configuration object
* */
private buildTrack(args:RcsbFvRowExtendedConfigInterface) : void{
this.setConfig(args);
if(typeof this.rcsbFvConfig.elementId === "string"){
this.init(this.rcsbFvConfig.elementId);
}
if(typeof this.rcsbFvConfig.trackData != "undefined" && this.rcsbFvConfig.displayType != RcsbFvDisplayTypes.COMPOSITE ){
this.load(this.rcsbFvConfig.trackData);
}else if(this.rcsbFvConfig.displayType === RcsbFvDisplayTypes.COMPOSITE){
const data: Array<RcsbFvTrackData> | null = this.collectCompositeData();
if(data != null) {
this.load(data);
}
}else{
this.buildRcsbTrack();
}
this.start();
}
/**Start rendering the board track annotation cell
* @param elementId DOM element Id
* */
public init(elementId: string) : void{
if(document.getElementById(elementId)!= null) {
this.elementId = elementId;
if(this.rcsbBoard === null){
this.rcsbBoard = new RcsbBoard(this.elementId, this.xScale, this.selection, this.contextManager);
}
if (this.rcsbFvConfig.configCheck()) {
this.initRcsbBoard();
}else{
throw "Board length is not defined";
}
}else{
throw "HTML element "+elementId+" not found";
}
}
/**Replaces the track configuration
* @param args Board row configuration object
* */
public setConfig(args: RcsbFvRowExtendedConfigInterface) : void{
if(this.rcsbFvConfig == null) {
this.rcsbFvConfig = new RcsbFvConfig(args);
}else{
this.rcsbFvConfig.updateConfig(args);
}
}
/**Sets parameters for the SVG/HTML level object manager*/
private initRcsbBoard(): void{
if(typeof this.rcsbFvConfig.elementClickCallback === "function")
this.rcsbBoard.setElementClickCallback(this.rcsbFvConfig.elementClickCallback);
if(this.rcsbFvConfig.highlightHoverPosition === true) {
this.rcsbBoard.setHighlightHoverPosition();
}
if(this.rcsbFvConfig.highlightHoverElement === true) {
this.rcsbBoard.setHighlightHoverElement(true);
}
if(typeof this.rcsbFvConfig.highlightHoverCallback === "function"){
this.rcsbBoard.addHoverCallback(this.rcsbFvConfig.highlightHoverCallback);
}
if(typeof this.rcsbFvConfig.trackWidth === "number")
this.rcsbBoard.setBoardWidth(this.rcsbFvConfig.trackWidth);
if(typeof this.rcsbFvConfig.range === "object")
this.rcsbBoard.setRange(this.rcsbFvConfig.range.min-RcsbFvDefaultConfigValues.increasedView, this.rcsbFvConfig.range.max+RcsbFvDefaultConfigValues.increasedView);
else if(typeof this.rcsbFvConfig.length === "number")
this.rcsbBoard.setRange(1-RcsbFvDefaultConfigValues.increasedView, this.rcsbFvConfig.length+RcsbFvDefaultConfigValues.increasedView);
}
/**Build an inner track within a board track annotation cell
* @return Inner track display object
* */
private buildRcsbTrack(): RcsbDisplayInterface{
this.rcsbFvDisplay = new RcsbFvDisplay(this.rcsbFvConfig);
const rcsbTrack: RcsbDisplayInterface = this.rcsbFvDisplay.initDisplay();
rcsbTrack.height( this.rcsbFvConfig.trackHeight );
rcsbTrack.trackColor( this.rcsbFvConfig.trackColor );
this.rcsbTrackArray.push(rcsbTrack);
return rcsbTrack;
}
/**Transforms data of composite displays
* @return Array of annotation objects
* */
private collectCompositeData(): Array<RcsbFvTrackData> | null {
const data: Array<RcsbFvTrackData> = new Array<RcsbFvTrackData>();
if(this.rcsbFvConfig?.displayConfig!=undefined) {
for (let displayItem of this.rcsbFvConfig.displayConfig) {
if (typeof displayItem.displayData != "undefined") {
data.push(displayItem.displayData);
}
}
if (data.length == this.rcsbFvConfig.displayConfig.length) {
return data;
}
}
return null;
}
/**Class inner function that transform annotation data for composite or single displays
* @param trackData array of annotation objects
* */
private load(trackData: RcsbFvTrackData | Array<RcsbFvTrackData>): void{
if( this.rcsbFvConfig.displayType === RcsbFvDisplayTypes.COMPOSITE && Array.isArray(trackData)){
const trackNonOverlappingMap: Array<Array<RcsbFvTrackData>> = new Array<Array<RcsbFvTrackData>>();
let maxTracks:number = 1;
(trackData as Array<RcsbFvTrackData>).forEach((f,i)=>{
if(!this.rcsbFvConfig.overlap) {
const nonOverlapping: Array<RcsbFvTrackData> = RcsbDataManager.getNonOverlappingData(f);
trackNonOverlappingMap.push(nonOverlapping);
if(nonOverlapping.length > maxTracks)
maxTracks = nonOverlapping.length;
}else {
trackNonOverlappingMap.push([f]);
}
});
for(let i=0;i<maxTracks;i++){
const rcsbCompositeTrack: RcsbDisplayInterface = this.buildRcsbTrack();
if(this.rcsbFvConfig?.trackHeight != undefined)
(rcsbCompositeTrack as RcsbCompositeDisplay).setCompositeHeight(i*this.rcsbFvConfig.trackHeight);
const displayIds: Array<string> = this.rcsbFvDisplay.getDisplayIds();
const trackDataMap: RcsbFvTrackDataMap = new RcsbFvTrackDataMap();
trackNonOverlappingMap.forEach((v,j)=>{
const id: string = displayIds[j];
if(i<v.length)
trackDataMap.set(id,v[i]);
else
trackDataMap.set(id,[]);
});
rcsbCompositeTrack.data(trackDataMap);
}
}else if (trackData instanceof RcsbFvTrackData){
let nonOverlapping: Array<RcsbFvTrackData>;
if(!this.rcsbFvConfig.overlap) {
nonOverlapping = RcsbDataManager.getNonOverlappingData(trackData);
}else{
nonOverlapping = [trackData];
}
nonOverlapping.forEach(trackData=>{
this.buildRcsbTrack().data(trackData);
});
}else{
throw new Error("Data loader error. Data type not supported.");
}
}
/**Add all inner track to the SVG/HTML level manager and start rendering*/
private start() : void{
this.rcsbTrackArray.forEach(track=>{
this.rcsbBoard.addTrack(track);
});
this.rcsbBoard.startBoard();
}
/**Subscribe function to handle events and communicate all board track annotations cell panels
* @return Subscription object
* */
private subscribe(): Subscription{
return this.contextManager.subscribe((obj:RcsbFvContextManagerType)=>{
if(obj.eventType===EventType.SCALE) {
this.setScale(obj.eventData as string);
}else if(obj.eventType===EventType.SELECTION){
this.setSelection(obj.eventData as SelectionInterface);
}else if(obj.eventType===EventType.RESET){
this.reset(obj.eventData as string);
}
});
}
/**Unsubscribe all functions
* */
public unsubscribe(): void{
this.subscription.unsubscribe();
this.rcsbBoard.removeScrollEvent();
}
/**Modify d3 x-scale
* @param boardId Id of the SVG/HTML manager that triggered the event
* */
private setScale(boardId: string) : void {
this.rcsbBoard.setScale(boardId);
}
/**Highlights the region(s) defined by the attribute selection
* @param selection object describing sequence regions
* */
private setSelection(selection:SelectionInterface) : void {
this.rcsbBoard.setSelection(selection.trackId, selection.mode);
}
/**Reset the cell content
* @param trackId Event reset object interface
* */
private reset(trackId: string){
if(this.rcsbFvConfig.trackId === trackId){
this._reset();
}
}
/**Reset all inner tracks*/
private _reset(): void{
this.rcsbTrackArray = new Array<RcsbDisplayInterface>();
this.rcsbBoard.reset();
}
/**Calculate height as function of the number of inner tracks
* @return Board track annotation cell height
* */
public getTrackHeight(): number | null{
if(this.rcsbTrackArray.length > 0 && this.rcsbFvConfig?.trackHeight != undefined) {
return this.rcsbTrackArray.length * this.rcsbFvConfig.trackHeight;
}else if( this.rcsbFvConfig?.trackHeight != undefined ){
return this.rcsbFvConfig.trackHeight;
}else{
return null;
}
}
}