UNPKG

oncoprintjs

Version:

A data visualization for cancer genomic data.

303 lines (269 loc) 9.28 kB
import { ComputedShapeParams, Rectangle } from './oncoprintshape'; import { RGBAColor } from './oncoprintruleset'; const halfsqrt2 = Math.sqrt(2) / 2; function normalizeRGBA(color: RGBAColor): [number, number, number, number] { return [color[0], color[1], color[2], color[3] * 255]; } type AddVertexCallback = ( vertex: [number, number, number], color: [number, number, number, number] ) => void; function rectangleToVertexes( params: ComputedShapeParams, z_index: number, addVertex: AddVertexCallback ) { const x = params.x, y = params.y, height = params.height, width = params.width; // Fill const fill_rgba = normalizeRGBA(params.fill); addVertex([x, y, z_index], fill_rgba); addVertex([x + width, y, z_index], fill_rgba); addVertex([x + width, y + height, z_index], fill_rgba); addVertex([x, y, z_index], fill_rgba); addVertex([x + width, y + height, z_index], fill_rgba); addVertex([x, y + height, z_index], fill_rgba); // Stroke const stroke_width = params['stroke-width']; if (stroke_width > 0) { // left side const stroke_rgba = normalizeRGBA(params.stroke); addVertex([x, y, z_index], stroke_rgba); addVertex([x + stroke_width, y, z_index], stroke_rgba); addVertex([x + stroke_width, y + height, z_index], stroke_rgba); addVertex([x, y, z_index], stroke_rgba); addVertex([x + stroke_width, y + height, z_index], stroke_rgba); addVertex([x, y + height, z_index], stroke_rgba); // right side addVertex([x + width, y, z_index], stroke_rgba); addVertex([x + width - stroke_width, y, z_index], stroke_rgba); addVertex([x + width - stroke_width, y + height, z_index], stroke_rgba); addVertex([x + width, y, z_index], stroke_rgba); addVertex([x + width - stroke_width, y + height, z_index], stroke_rgba); addVertex([x + width, y + height, z_index], stroke_rgba); // top side addVertex([x, y, z_index], stroke_rgba); addVertex([x + width, y, z_index], stroke_rgba); addVertex([x + width, y + stroke_width, z_index], stroke_rgba); addVertex([x, y, z_index], stroke_rgba); addVertex([x + width, y + stroke_width, z_index], stroke_rgba); addVertex([x, y + stroke_width, z_index], stroke_rgba); // bottom side addVertex([x, y + height, z_index], stroke_rgba); addVertex([x + width, y + height, z_index], stroke_rgba); addVertex([x + width, y + height - stroke_width, z_index], stroke_rgba); addVertex([x, y + height, z_index], stroke_rgba); addVertex([x + width, y + height - stroke_width, z_index], stroke_rgba); addVertex([x, y + height - stroke_width, z_index], stroke_rgba); } } function triangleToVertexes( params: ComputedShapeParams, z_index: number, addVertex: AddVertexCallback ) { const fill_rgba = normalizeRGBA(params.fill); addVertex([params.x1, params.y1, z_index], fill_rgba); addVertex([params.x2, params.y2, z_index], fill_rgba); addVertex([params.x3, params.y3, z_index], fill_rgba); } function ellipseToVertexes( params: ComputedShapeParams, z_index: number, addVertex: AddVertexCallback ) { const center = { x: params.x + params.width / 2, y: params.y + params.height / 2, }; const horzrad = params.width / 2; const vertrad = params.height / 2; const fill_rgba = normalizeRGBA(params.fill); addVertex([center.x, center.y, z_index], fill_rgba); addVertex([center.x + horzrad, center.y, z_index], fill_rgba); addVertex( [ center.x + halfsqrt2 * horzrad, center.y + halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x, center.y, z_index], fill_rgba); addVertex( [ center.x + halfsqrt2 * horzrad, center.y + halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x, center.y + vertrad, z_index], fill_rgba); addVertex([center.x, center.y, z_index], fill_rgba); addVertex([center.x, center.y + vertrad, z_index], fill_rgba); addVertex( [ center.x - halfsqrt2 * horzrad, center.y + halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x, center.y, z_index], fill_rgba); addVertex( [ center.x - halfsqrt2 * horzrad, center.y + halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x - horzrad, center.y, z_index], fill_rgba); addVertex([center.x, center.y, z_index], fill_rgba); addVertex([center.x - horzrad, center.y, z_index], fill_rgba); addVertex( [ center.x - halfsqrt2 * horzrad, center.y - halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x, center.y, z_index], fill_rgba); addVertex( [ center.x - halfsqrt2 * horzrad, center.y - halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x, center.y - vertrad, z_index], fill_rgba); addVertex([center.x, center.y, z_index], fill_rgba); addVertex([center.x, center.y - vertrad, z_index], fill_rgba); addVertex( [ center.x + halfsqrt2 * horzrad, center.y - halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x, center.y, z_index], fill_rgba); addVertex( [ center.x + halfsqrt2 * horzrad, center.y - halfsqrt2 * vertrad, z_index, ], fill_rgba ); addVertex([center.x + horzrad, center.y, z_index], fill_rgba); } function lineToVertexes( params: ComputedShapeParams, z_index: number, addVertex: AddVertexCallback ) { // For simplicity of dealing with webGL we'll implement lines as thin triangle pairs let x1 = params.x1; let x2 = params.x2; let y1 = params.y1; let y2 = params.y2; if (x1 !== x2) { // WLOG make x1,y1 the one on the left if (Math.min(x1, x2) === x2) { const tmpx1 = x1; const tmpy1 = y1; x1 = x2; y1 = y2; x2 = tmpx1; y2 = tmpy1; } } const perpendicular_vector = [y2 - y1, x1 - x2]; const perpendicular_vector_length = Math.sqrt( perpendicular_vector[0] * perpendicular_vector[0] + perpendicular_vector[1] * perpendicular_vector[1] ); const unit_perp_vector = [ perpendicular_vector[0] / perpendicular_vector_length, perpendicular_vector[1] / perpendicular_vector_length, ]; const half_stroke_width = params['stroke-width'] / 2; const direction1 = [ unit_perp_vector[0] * half_stroke_width, unit_perp_vector[1] * half_stroke_width, ]; const direction2 = [direction1[0] * -1, direction1[1] * -1]; const A = [x1 + direction1[0], y1 + direction1[1]]; const B = [x1 + direction2[0], y1 + direction2[1]]; const C = [x2 + direction1[0], y2 + direction1[1]]; const D = [x2 + direction2[0], y2 + direction2[1]]; const stroke_rgba = normalizeRGBA(params.stroke); addVertex([A[0], A[1], z_index], stroke_rgba); addVertex([B[0], B[1], z_index], stroke_rgba); addVertex([C[0], C[1], z_index], stroke_rgba); addVertex([C[0], C[1], z_index], stroke_rgba); addVertex([D[0], D[1], z_index], stroke_rgba); addVertex([B[0], B[1], z_index], stroke_rgba); } export default function( oncoprint_shape_computed_params: ComputedShapeParams, z_index: number, addVertex: AddVertexCallback ) { // target_position_array is an array with 3-d float vertexes // target_color_array is an array with rgba values in [0,1] // We pass them in to save on concatenation costs const type = oncoprint_shape_computed_params.type; if (type === 'rectangle') { return rectangleToVertexes( oncoprint_shape_computed_params, z_index, addVertex ); } else if (type === 'triangle') { return triangleToVertexes( oncoprint_shape_computed_params, z_index, addVertex ); } else if (type === 'ellipse') { return ellipseToVertexes( oncoprint_shape_computed_params, z_index, addVertex ); } else if (type === 'line') { return lineToVertexes( oncoprint_shape_computed_params, z_index, addVertex ); } } export function getNumWebGLVertexes(shape: ComputedShapeParams) { let ret: number; switch (shape.type) { case 'rectangle': if (shape['stroke-width'] > 0) { ret = 30; } else { ret = 6; } break; case 'triangle': ret = 3; break; case 'ellipse': ret = 24; break; case 'line': ret = 6; break; } return ret; }