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.