brushable-chromosome.ts 3.88 KB
Newer Older
1
import { BrushableAxisData, GenericAxisData } from "../../types";
2
import { select, Selection, event } from "d3-selection";
3
import { scaleLinear } from "d3-scale";
Remi  PLANEL's avatar
Remi PLANEL committed
4
import GenomeAxis from "./chromosome";
5
import { brushX, BrushBehavior } from "d3-brush";
6
import { format } from "d3-format";
7
8
9

export default function () {
  const genomeAxisComponent = GenomeAxis();
Remi  PLANEL's avatar
Remi PLANEL committed
10
11
12
13
14
15
  const htmlClassName = {
    componentContainer: "whole-chromosome-rule",
    genericRule: "generic-rule",
    brushSelection: "brush-selection"
  };

16
17
  const tickFormat = format(".3s");
  const brushHeight = 50;
18
  function globalGenomeAxis(
Remi  PLANEL's avatar
Remi PLANEL committed
19
    _selection: Selection<SVGGElement, BrushableAxisData, any, any>,
20
    width: number,
21
    yPosition: number,
22
23
  ) {
    _selection.each(function (_data) {
24
25
26
      const { interval, maxWindowSize } = _data;
      const genomicToPx = scaleLinear()
        .domain(interval)
27
28
29
        .range([0, width]);

      const brush: BrushBehavior<any> = brushX()
30
        .extent([[0, 0], [width, brushHeight]])
31
        .on("brush", () => {
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
          const { selection: [start, end] } = event;
          globalAxisUpdate
            .select(".brush-selection-boundaries")
            .select("text.start")
            .style("text-anchor", "end")
            .attr("transform", "translate(" + start + "," + (brushHeight + 10) + ")")
            .text(tickFormat(genomicToPx.invert(start)));

          globalAxisUpdate
            .select(".brush-selection-boundaries")
            .select("text.end")
            .attr("transform", "translate(" + end + "," + (brushHeight + 10) + ")")
            .style("text-anchor", "start")
            .text(tickFormat(genomicToPx.invert(end)));


48
          if (_data.eventHandler) {
49
50
51
52
53
54
55
56
57
58
59
60
61
            _data.eventHandler.brushed(genomicToPx)
          }
        })
        .on("end", function () {
          const { selection: [pxStart, pxEnd] } = event;
          const currentGenomicWindowSize = Math.ceil(genomicToPx.invert(pxEnd) - genomicToPx.invert(pxStart));
          const maxPixelWindowSize = Math.floor(genomicToPx(maxWindowSize));
          if (currentGenomicWindowSize > maxWindowSize) {
            const roundedStart = Math.ceil(pxStart);
            brushSelection
              .transition()
              .duration(400)
              .call(brush.move, [roundedStart, roundedStart + maxPixelWindowSize])
62
63
64
          }
        });

65
66


67
68
69
      const container = select(this)
        .attr("transform", "translate(0," + yPosition + ")");
      const globalAxis = container
Remi  PLANEL's avatar
Remi PLANEL committed
70
        .selectAll<SVGElement, BrushableAxisData>("." + htmlClassName.componentContainer)
71
72
73
74
75
        .data([_data]);

      const globalAxisEnter = globalAxis
        .enter()
        .append<SVGElement>("g")
Remi  PLANEL's avatar
Remi PLANEL committed
76
        .classed(htmlClassName.componentContainer, true);
77
78

      globalAxisEnter.append("g")
Remi  PLANEL's avatar
Remi PLANEL committed
79
        .classed(htmlClassName.genericRule, true);
80
81


82
83
84
85
      const brushSelectionEnter = globalAxisEnter.append("g")
        .attr("transform", "translate(0,25)");
      brushSelectionEnter
        .append("g")
Remi  PLANEL's avatar
Remi PLANEL committed
86
        .classed(htmlClassName.brushSelection, true);
87

88
89
90
91
92
93
94
95
      const boundaries = brushSelectionEnter
        .append("g")
        .style("font-size", "9")
        .classed("brush-selection-boundaries", true)

      boundaries.append("text").classed("start", true);
      boundaries.append("text").classed("end", true);

96
97
98
99
100
      globalAxis.exit().remove();

      const globalAxisUpdate = globalAxis.merge(globalAxisEnter);


101
102
103
      const brushSelection = globalAxisUpdate
        .select<any>("." + htmlClassName.brushSelection);
      brushSelection
104
        .call(brush)
105
        .call(brush.move, [genomicToPx(_data.window[0]), genomicToPx(_data.window[1])]);
106
107

      globalAxisUpdate
108
        .select<SVGGElement>("." + htmlClassName.genericRule)
109
110
111
        .datum((d: BrushableAxisData): GenericAxisData => ({ title: d.title, interval: [d.interval[0], d.interval[1]] }))
        .call(genomeAxisComponent, width, yPosition);

112
113
114



115
116
117
118
    });
  }
  return globalGenomeAxis;
}