PageSourceSearch

http://chihuama.com/EnsembleProb/src/js/prototype/projectionMap.js

js chihuama.com collected 2026-10-05 07:24:54 UTC 13,533 bytes, 392 lines download raw bytes

1var App = App || {};
2App.views = App.views || {};
3
4App.views.projectionMap = (function() {
5    let targetElement = null;
6
7    let svg = null;
8    let grid = null;
9    let peaks = null;
10
11    let size = null;
12
13    let fullData = null;
14    let currentTimestep = 0;
15    let xProjection = 1;
16    let yProjection = 0;
17
18    let axisXLabel = null,
19        axisYLabel = null;
20
21    let updateCallback = null;
22
23
24    function setupView(targetID) {
25        targetElement = d3.select('#' + targetID);
26
27        calculateNewComponentSize();
28
29        // create SVG for the view
30        svg = targetElement.append('svg')
31            .attr('id', targetID + "_svg")
32            .attr('width', size.width)
33            .attr('height', size.height)
34            .attr('viewBox', '0 0 ' + size.width + " " + size.height)
35            .style('background', '#EEE');
36
37        // resize();
38        // draw();
39    }
40
41    function draw() {
42        // if this view does not yet have data
43        if (!fullData) return;
44
45        // let currentProb = fullData[0][currentTimestep];
46
47        const currentPeaks = aggregatePeaksInTimestep();
48        // draw grid
49        const maxPeakBound = App.data.projectionBounds.twoD[xProjection][yProjection];
50        const stateSpaceSize = App.data.stateSpaceSize.twoD[xProjection][yProjection];
51
52        // calculate what the shown dimensions will be (since the apsect ratios may be different)
53        const gridDimensions = calculateShownGridDimensions(maxPeakBound);
54        const gridSize = size.width / (gridDimensions.width[1] - gridDimensions.width[0]);
55        // draw grid
56
57        let margin = {
58            left: 30,
59            right: 0,
60            top: 0,
61            bottom: 35
62        }
63
64        const xScale = d3.scaleLinear()
65            .domain(gridDimensions.width)
66            .range([margin.left, size.width - margin.right]);
67
68        const yScale = d3.scaleLinear()
69            .domain(gridDimensions.height)
70            .range([margin.top, size.height - margin.bottom].reverse());
71
72        const radiusScale = d3.scaleLinear()
73            .domain([1, 6])
74            .range([gridSize / 3, 2 * gridSize / 3]);
75
76        const colorScale = d3.scaleOrdinal()
77            .domain(d3.range(0, 6))
78            .range(['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f']);
79
80        let xStart = d3.max([Math.ceil(gridDimensions.width[0]), 0]),
81            xEnd = d3.min([Math.floor(gridDimensions.width[1]), stateSpaceSize.x - 1]);
82
83        let yStart = d3.max([Math.ceil(gridDimensions.height[0]), 0]),
84            yEnd = d3.min([Math.floor(gridDimensions.height[1]), stateSpaceSize.y - 1]);
85
86        // console.log(xStart + " - " + xEnd);
87        // console.log(yStart + " - " + yEnd);
88
89
90        function highlightState(state) {
91            d3.selectAll('.highlight').remove();
92
93            const hlState = svg.append('rect')
94                .attr('class', 'highlight')
95                .attr('x', xScale(state[0]))
96                .attr('y', yScale(state[1]))
97                .attr('width', gridSize)
98                .attr('height', gridSize)
99                .style('fill', 'none')
100                .style('stroke', 'darkred')
101                .style('stroke-width', 1);
102        }
103
104        if (!grid) {
105            grid = svg.append('g')
106                .attr('class', targetElement + "_grid");
107
108            // horizontal lines
109            for (let y = yStart; y <= yEnd; y++) {
110                for (let x = xStart; x <= xEnd; x++) {
111                    grid.append('rect')
112                        .datum({
113                            x,
114                            y
115                        })
116                        .attr('class', 'gridSpace')
117                        .attr('x', xScale(x))
118                        .attr('y', yScale(y))
119                        .attr('width', gridSize)
120                        .attr('height', gridSize)
121                        .style('stroke', '#AAA')
122                        .style('fill', '#DDD')
123                        .on('click', (d, i) => {
124                            console.log('Click on Grid Space:', d);
125                            updateCallback([d.x, d.y]);
126                            highlightState([d.x, d.y]);
127                        });
128                }
129            }
130            grid.append('rect')
131                .attr('class', 'stateSpaceBoundary')
132                .attr('width', (stateSpaceSize.x) * gridSize * (size.width - margin.left - margin.right) / size.width)
133                .attr('height', (stateSpaceSize.y) * gridSize * (size.height - margin.top - margin.bottom) / size.height)
134                .attr('x', xScale(0))
135                .attr('y', yScale(stateSpaceSize.y - 1))
136                .style('fill-opacity', 0)
137                .style('stroke', '#888')
138                .style('stroke-width', 3)
139                .style('pointer-events', 'none');
140        }
141
142        if (!axisXLabel) {
143            axisXLabel = svg.append("text")
144                .attr("class", "label")
145                .attr("x", size.width / 2)
146                .attr("y", yScale(stateSpaceSize.y - 1) + (stateSpaceSize.y) * gridSize)
147                .style("text-anchor", "middle")
148                .style("font-size", "12px")
149                .style("stroke-width", 1)
150                .text("Number of " + protein[App.currentProjection.x]);
151        }
152
153        if (!axisYLabel) {
154            axisYLabel = svg.append("text")
155                .attr("class", "label")
156                .style("text-anchor", "middle")
157                .attr("transform", "translate(" + (margin.left / 2) + "," + (yScale(stateSpaceSize.y - 1) + (stateSpaceSize.y) * gridSize / 2) + ") rotate(-90)")
158                .style("font-size", "12px")
159                .style("stroke-width", 1)
160                .text("Number of " + protein[App.currentProjection.y]);
161        }
162
163        if (!peaks) {
164            peaks = svg.append('g')
165                .attr('class', targetElement + '_peaks');
166        }
167
168        const peakUpdate = peaks.selectAll('.peak')
169            .data(currentPeaks);
170
171        peakUpdate.exit().remove();
172
173        peaks.selectAll('.peak')
174            .attr('transform', d => 'translate(' + (xScale(d.coord.x) + gridSize / 2) + "," + (yScale(d.coord.y) + gridSize / 2) + ')')
175            .each(function(d, i) {
176                // remove old arcs
177                d3.select(this).selectAll('.arc').remove();
178
179                const numRuns = d.runs.length;
180
181                const outerRadius = radiusScale(numRuns); // 3px padding
182                const innerRadius = 0;
183
184                // create background
185                d3.select(this).select('circle')
186                    .attr('r', radiusScale(numRuns));
187
188                // create new arcs
189                const arc = d3.arc();
190                const arcAngle = (2 * Math.PI) / numRuns;
191
192                d3.select(this).selectAll('.arc')
193                    .data(d.runs).enter()
194                    .append('path')
195                    .attr('class', 'arc')
196                    .attr('d', (d, i) => {
197                        return arc({
198                            innerRadius,
199                            outerRadius,
200                            startAngle: i * arcAngle,
201                            endAngle: (i + 1) * arcAngle,
202                        });
203                    })
204                    .style('fill', colorScale)
205                    .style('stroke', '#444')
206                    .style('pointer-events', 'none');
207            });
208
209        peakUpdate.enter().append('g')
210            .attr('class', 'peak')
211            .attr('transform', d => 'translate(' + (xScale(d.coord.x) + gridSize / 2) + "," + (yScale(d.coord.y) + gridSize / 2) + ')')
212            .each(function(d, i) {
213                // remove old arcs
214                // d3.select(this).selectAll(".arc").remove();
215
216                const numRuns = d.runs.length;
217
218                const outerRadius = radiusScale(numRuns); // 3px padding
219                const innerRadius = 0;
220
221                // create background
222                d3.select(this).append('circle')
223                    .attr('r', radiusScale(numRuns))
224                    .style('fill', '#BBB')
225                    .style('stroke', '#888')
226                    .on('click', (d, i) => {
227                        console.log('Click on Pie:', d);
228                        updateCallback([d.coord.x, d.coord.y]);
229                        highlightState([d.coord.x, d.coord.y]);
230                    });
231
232                // create new arcs
233                const arc = d3.arc();
234                const arcAngle = (2 * Math.PI) / numRuns;
235
236                d3.select(this).selectAll('.arc')
237                    .data(d.runs).enter()
238                    .append('path')
239                    .attr('class', 'arc')
240                    .attr('d', (d, i) => {
241                        return arc({
242                            innerRadius,
243                            outerRadius,
244                            startAngle: i * arcAngle,
245                            endAngle: (i + 1) * arcAngle,
246                        });
247                    })
248                    .style('fill', colorScale)
249                    .style('stroke', '#444')
250                    .style('pointer-events', 'none');
251            });
252    }
253
254    function calculateShownGridDimensions(projectionBounds) {
255        const svgAspect = size.width / size.height;
256        const dataAspect = (projectionBounds.x.max - projectionBounds.x.min) / (projectionBounds.y.max - projectionBounds.y.min);
257
258        const spacesShown = {};
259
260        // data is wider than svg
261        if (dataAspect >= svgAspect) {
262            // expand height, width stays same
263            spacesShown.width = [projectionBounds.x.min, projectionBounds.x.max];
264
265            const numSpacesHigh = (projectionBounds.x.max - projectionBounds.x.min) / svgAspect;
266
267            spacesShown.height = [
268                (projectionBounds.y.max + projectionBounds.y.min - numSpacesHigh) / 2,
269                (projectionBounds.y.max + projectionBounds.y.min + numSpacesHigh) / 2,
270            ];
271        } else {
272            // expand width, height stays same
273            spacesShown.height = [projectionBounds.y.min, projectionBounds.y.max];
274
275            const numSpacesWide = svgAspect * (projectionBounds.y.max - projectionBounds.y.min);
276
277            spacesShown.width = [
278                (projectionBounds.x.max + projectionBounds.x.min - numSpacesWide) / 2,
279                (projectionBounds.x.max + projectionBounds.x.min + numSpacesWide) / 2,
280            ];
281        }
282
283        return spacesShown;
284    }
285
286    function aggregatePeaksInTimestep() {
287        // extract projection->run->peak data for only the current timestep
288        const currentPeaks = fullData[1].map(proj => proj.map(run => run[currentTimestep]));
289
290        const xPeaks = currentPeaks[xProjection];
291        const yPeaks = currentPeaks[yProjection];
292
293        const peakPairDictionary = {};
294        const peakPairs = [];
295
296        for (let run = 0; run < RUN_NUM; run++) {
297            for (const x of xPeaks[run]) {
298                for (const y of yPeaks[run]) {
299                    const coordString = "x" + x.index + "y" + y.index;
300
301                    if (!peakPairDictionary[coordString]) {
302                        peakPairDictionary[coordString] = {
303                            coord: {
304                                x: x.index,
305                                y: y.index,
306                            },
307                            runs: [],
308                        };
309                    }
310
311                    // add this run to the run list for the peak at this coordinate
312                    peakPairDictionary[coordString].runs.push(run);
313                }
314            }
315        }
316
317        for (const loc of Object.keys(peakPairDictionary)) {
318            peakPairs.push(peakPairDictionary[loc]);
319        }
320
321        return peakPairs;
322    }
323
324    function setData(data) {
325        // data[0]: probVal, data[1]: peakInfo
326        // Pa: data[1][0][0~5], Pb: data[1][1][0~5], Pc: data[1][2][0~5]
327
328        fullData = data;
329
330        xProjection = App.currentProjection && App.currentProjection.x != undefined ? App.currentProjection.x : 1;
331        yProjection = App.currentProjection && App.currentProjection.y != undefined ? App.currentProjection.y : 0;
332
333        currentTimestep = 0;
334    }
335
336    function setTimestep(timestep) {
337        currentTimestep = timestep;
338
339        draw();
340    }
341
342    function resize() {
343        calculateNewComponentSize();
344
345        // update svg size
346        svg
347            .attr('width', size.width)
348            .attr('height', size.height);
349    }
350
351    function calculateNewComponentSize() {
352        size = {
353            width: targetElement.node().clientWidth,
354            height: targetElement.node().clientWidth * 1.4,
355        };
356    }
357
358    function getSize() {
359        return size;
360    }
361
362    function reset() {
363      grid = null;
364      peaks = null;
365      axisXLabel = null;
366      axisYLabel = null;
367
368      d3.selectAll(".gridSpace").remove();
369      d3.selectAll(".stateSpaceBoundary").remove();
370      d3.selectAll(".peak").remove();
371      d3.selectAll('.highlight').remove();
372      d3.selectAll(".label").remove();
373    }
374
375    function stateUpdateCallback(callback) {
376        updateCallback = callback;
377    }
378
379    // return public methods
380    return {
381        // public methods
382        create: setupView,
383        draw,
384        resize,
385        stateUpdateCallback,
386        // getter / setter
387        setData,
388        setTimestep,
389        getSize,
390        reset: reset
391    };
392})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.