UNPKG

3dmol

Version:

JavaScript/TypeScript molecular visualization library

337 lines (298 loc) 9.2 kB
import { SpriteAlignment, Texture, SpriteMaterial, Sprite, Vector2, Material, } from "./WebGL"; import { Gradient } from "./Gradient"; import { Color, CC, ColorSpec } from "./colors"; import {XYZ} from "./WebGL/math" // Adapted from the text sprite example from http://stemkoski.github.io/Three.js/index.html export let LabelCount = 0; // Function for drawing rounded rectangles - for Label drawing function roundRect(ctx: CanvasRenderingContext2D, x: any, y: any, w: number, h: number, r: number, drawBorder: boolean) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); ctx.fill(); if (drawBorder) ctx.stroke(); } //do all the checks to figure out what color is desired function getColor(style: any, stylealpha?: any, init?: any) { var ret = init; if (typeof style != "undefined") { //convet regular colors if (style instanceof Color) ret = style.scaled(); else { //hex or name ret = CC.color(style); if (typeof ret.scaled != "undefined") { ret = ret.scaled(); //not already scaled to 255 } } } if (typeof stylealpha != "undefined") { ret.a = parseFloat(stylealpha); } return ret; } /** Label style specification */ export interface LabelSpec { /** font name, default sans-serif */ font?: string; /** height of text, default 18 */ fontSize?: number; /** font color, default white */ fontColor?: ColorSpec; /** font opacity, default 1 */ fontOpacity?: number; /** line width of border around label, default 0 */ borderThickness?: number; /** color of border, default backgroundColor */ borderColor?: ColorSpec; /** opacity of border */ borderOpacity?: number; /** color of background, default black */ backgroundColor?: ColorSpec; /** opacity of background, default 1.0 */ backgroundOpacity?: number; /** coordinates for label */ position?: XYZ; /** x,y,z pixel offset of label from position; for screen labels z is a z-index */ screenOffset?: Vector2; /** always put labels in front of model */ inFront?: boolean; /** show background rounded rectangle, default true */ showBackground?: boolean; /** position is in screen (not model) coordinates which are pixel offsets from the upper left corner */ useScreen?: boolean; /** An elment to draw into the label. Any CanvasImageSource is allowed. Label is resized to size of image */ backgroundImage?: any; /** how to orient the label w/respect to position: "topLeft" (default), * "topCenter", "topRight", "centerLeft", "center", "centerRight", * "bottomLeft", "bottomCenter", "bottomRight", or an arbitrary offset */ alignment?: string | Vector2; /** if set, only display in this frame of an animation */ frame?: number; } /** * Renderable labels * @constructor $3Dmol.Label * @param {string} tag - Label text * @param {LabelSpec} parameters Label style and font specifications */ export class Label { id: number; stylespec: any; canvas: HTMLCanvasElement; context: any; sprite: Sprite; text: any; frame: any; constructor(text: string, parameters: LabelSpec) { this.id = LabelCount++; this.stylespec = parameters || {}; this.canvas = document.createElement("canvas"); //todo: implement resizing canvas.. this.canvas.width = 134; this.canvas.height = 35; this.context = this.canvas.getContext("2d"); this.sprite = new Sprite(); this.text = text; this.frame = this.stylespec.frame; } getStyle() { return this.stylespec; } /** Hide this label. */ public hide() { if(this.sprite) { this.sprite.visible = false; } } /** Show a hidden label. */ public show() { if(this.sprite) { this.sprite.visible = true; } } setContext() { var style = this.stylespec; var useScreen = typeof style.useScreen == "undefined" ? false : style.useScreen; var showBackground = style.showBackground; if (showBackground === "0" || showBackground === "false") showBackground = false; if (typeof showBackground == "undefined") showBackground = true; //default var font = style.font ? style.font : "sans-serif"; var fontSize = parseInt(style.fontSize) ? parseInt(style.fontSize) : 18; var fontColor = getColor(style.fontColor, style.fontOpacity, { r: 255, g: 255, b: 255, a: 1.0, }); var padding = style.padding ? style.padding : 4; var borderThickness = style.borderThickness ? style.borderThickness : 0; var backgroundColor = getColor( style.backgroundColor, style.backgroundOpacity, { r: 0, g: 0, b: 0, a: 1.0, } ); var borderColor = getColor( style.borderColor, style.borderOpacity, backgroundColor ); var position = style.position ? style.position : { x: -10, y: 1, z: 1, }; // Should labels always be in front of model? var inFront = style.inFront !== undefined ? style.inFront : true; if (inFront === "false" || inFront === "0") inFront = false; // clear canvas var spriteAlignment = style.alignment || SpriteAlignment.topLeft; if ( typeof spriteAlignment == "string" && spriteAlignment in SpriteAlignment ) { spriteAlignment = (SpriteAlignment as any)[spriteAlignment] ; } var bold = ""; if (style.bold) bold = "bold "; this.context.font = bold + fontSize + "px " + font; var metrics = this.context.measureText(this.text); var textWidth = metrics.width; if (!showBackground) borderThickness = 0; var width = textWidth + 2.5 * borderThickness + 2 * padding; var height = fontSize * 1.25 + 2 * borderThickness + 2 * padding; // 1.25 is extra height factor for text below baseline: g,j,p,q. if (style.backgroundImage) { //resize label to image var img = style.backgroundImage; var w = style.backgroundWidth ? style.backgroundWidth : img.width; var h = style.backgroundHeight ? style.backgroundHeight : img.height; if (w > width) width = w; if (h > height) height = h; } this.canvas.width = width; this.canvas.height = height; this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); bold = ""; if (style.bold) bold = "bold "; this.context.font = bold + fontSize + "px " + font; // background color this.context.fillStyle = "rgba(" + backgroundColor.r + "," + backgroundColor.g + "," + backgroundColor.b + "," + backgroundColor.a + ")"; // border color this.context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + "," + borderColor.b + "," + borderColor.a + ")"; if (style.backgroundGradient) { let gradient = this.context.createLinearGradient( 0, height / 2, width, height / 2 ); let g = Gradient.getGradient(style.backgroundGradient); let minmax = g.range(); let min = -1; let max = 1; if (minmax) { min = minmax[0]; max = minmax[1]; } let d = max - min; for (let i = 0; i < 1.01; i += 0.1) { let c = getColor(g.valueToHex(min + d * i)); let cname = "rgba(" + c.r + "," + c.g + "," + c.b + "," + c.a + ")"; gradient.addColorStop(i, cname); } this.context.fillStyle = gradient; } this.context.lineWidth = borderThickness; if (showBackground) { roundRect( this.context, borderThickness, borderThickness, width - 2 * borderThickness, height - 2 * borderThickness, 6, borderThickness > 0 ); } if (style.backgroundImage) { this.context.drawImage(img, 0, 0, width, height); } // text color this.context.fillStyle = "rgba(" + fontColor.r + "," + fontColor.g + "," + fontColor.b + "," + fontColor.a + ")"; this.context.fillText( this.text, borderThickness + padding, fontSize + borderThickness + padding, textWidth ); // canvas contents will be used for a texture var texture = new Texture(this.canvas); texture.needsUpdate = true; this.sprite.material = new SpriteMaterial({ map: texture, useScreenCoordinates: useScreen, alignment: spriteAlignment, depthTest: !inFront, screenOffset: style.screenOffset || null, }) as Material; this.sprite.scale.set(1, 1, 1); this.sprite.position.set(position.x, position.y, position.z); } // clean up material and texture dispose() { if (this.sprite.material.map !== undefined) this.sprite.material.map.dispose(); if (this.sprite.material !== undefined) this.sprite.material.dispose(); } }