/** * Created by mm28969 on 11/23/16. */ // declare let d3; // import {select as d3_select} from 'd3-select'; import * as d3 from "d3"; import {convertValueToAttribute} from "../mmviz-common/index"; import {Dispatcher} from "../mmviz-dispatch/index"; import {LayoutScale} from "../mmviz-layout/index"; import {Component} from "./component"; export class LineAnnotationComponentSvg extends Component { stroke; strokeWidth; dragBehavior; isDraggable; hasLabelBox; handleWidth; handleWidthLinePercent; dragMovementLimit; valueLabelOffsetX; valueLabelOffsetY; valueLabelContainerWidthFactor; valueLabelContainerHeightFactor; valueContainerLabelOffsetX; valueContainerLabelOffsetY; labelTextAnchor; constructor(parentSelector: string, public selector: string = ".annotation-line") { super(parentSelector, selector); this.stroke = "black"; this.strokeWidth = 2; this.isDraggable = false; this.hasLabelBox = false; this.handleWidth = 7; this.handleWidthLinePercent = 0.33; this.dragMovementLimit = 100; this.valueLabelOffsetX = 0; this.valueLabelOffsetY = 0; this.valueLabelContainerWidthFactor = 2; this.valueLabelContainerHeightFactor = 2; this.valueContainerLabelOffsetX = 0; this.valueContainerLabelOffsetY = 0; this.labelTextAnchor = "middle"; } updateView(parent, viewModel: any, layoutScale: LayoutScale): LineAnnotationComponentSvg { let dispatcher: Dispatcher = Dispatcher.getInstance(), lineAnnotationGroup = parent.select(this.selector); if (lineAnnotationGroup.empty()) { lineAnnotationGroup = parent.append("g").attr("class", super.getClassName()); } else { // remove and redraw everything lineAnnotationGroup.selectAll("*").remove(); } this.dragBehavior = d3.drag() .on("drag", (d, i) => { let key = d.key, attr = convertValueToAttribute(key), handle = d3.select(`#annotation-line-${attr}-handle`), line = d3.select(`#annotation-line-${attr}`), payload: any = {parentSelector: this.parentSelector, selector: this.selector, data: d}, hasMinLimit = d.minDragLimit !== undefined || d.minDragLimit !== null, hasMaxLimit = d.maxDragLimit !== undefined || d.maxDragLimit !== null; if (d.data.drag === "x") { let newX = d.x1 + d3.event.dx, xNewValue; //constrain dramatic changes if(d3.event.dx > this.dragMovementLimit){ return; } xNewValue = layoutScale.xScale.invert(newX); if(hasMinLimit && xNewValue < d.minDragLimit){ xNewValue = d.minDragLimit; newX = layoutScale.xScale(xNewValue); } if(hasMaxLimit && xNewValue > d.maxDragLimit){ xNewValue = d.maxDragLimit; newX = layoutScale.xScale(xNewValue); } if(d.data.setValue) { d.data.setValue(xNewValue); } else { d.data.xintercept = xNewValue; } d.x1 = newX; d.x2 = newX; d.lx = newX; handle.attr("x", (newX - (this.handleWidth * 0.5))); line.attr("x1", d.x1).attr("x2", d.x2); if(this.hasLabelBox || viewModel.hasLabel){ this.drawValueLabelBox(parent, lineAnnotationGroup, viewModel); } payload.newX = newX; dispatcher.notify("drag", payload); } else if (d.data.drag === "y") { let newY = d.y1 + d3.event.dy, yNewValue; //constrain dramatic changes if((d3.event.dy > this.dragMovementLimit) || newY === undefined || newY === null){ return; } yNewValue = layoutScale.yScale.invert(newY); if(hasMinLimit && yNewValue < d.minDragLimit){ yNewValue = d.minDragLimit; newY = layoutScale.yScale(yNewValue); } if(hasMaxLimit && yNewValue > d.maxDragLimit){ yNewValue = d.maxDragLimit; newY = layoutScale.yScale(yNewValue); } if(d.data.setValue) { d.data.setValue(yNewValue); } else { d.data.yintercept = yNewValue; } d.y1 = newY; d.y2 = newY; d.ly = newY; handle.attr("y", (newY - (this.handleWidth * 0.5))); line.attr("y1", newY).attr("y2", newY); if(this.hasLabelBox || viewModel.hasLabel){ this.drawValueLabelBox(parent, lineAnnotationGroup, viewModel); } payload.yNewValue = yNewValue; dispatcher.notify("drag", payload); } }) .on("end", (d, i) => { let payload: any = {parentSelector: this.parentSelector, selector: this.selector, data: d}; dispatcher.notify("dragend", payload); }); let lines = lineAnnotationGroup .selectAll("line") .data(viewModel.dataArray, viewModel.keyMap) .enter() .append("line"); lines.attr("id", (d, i) => { let attr = convertValueToAttribute(d.key); return "annotation-line-" + attr; }) .attr("class", "annotation-line") .attr("x1", (d) => { return d.x1; }) .attr("x2", (d) => { return d.x2; }) .attr("y1", (d) => { return d.y1; }) .attr("y2", (d) => { return d.y2; }) .attr("stroke", function(d){ if (d.data.stroke) { return d.data.stroke; } else { return this.stroke; } }) .attr("stroke-width", this.strokeWidth); this.drawValueLabelBox(parent, lineAnnotationGroup, viewModel); if (this.isDraggable) { this.addDragableBehavior(parent, lineAnnotationGroup, viewModel); } return this; } getLabelBBox(parent, key){ let attr = convertValueToAttribute(key); return parent.select(`#annotation-line-${attr}-value`).node().getBBox(); } drawValueLabelBox(parent, lineAnnotationGroup, viewModel){ let hasLabel = viewModel.hasLabel, hasLabelBox = this.hasLabelBox; if(!hasLabel && !hasLabelBox){ return; } let lineValueLabelGroup = lineAnnotationGroup .selectAll("g.annotation-line-value-group") .data(viewModel.dataArray, viewModel.keyMap) .enter() .append("g") .attr("id", (d) => { let attr = convertValueToAttribute(d.key); return `annotation-line-${attr}-value-group`; }) .attr("class", "annotation-line-value-group"); lineValueLabelGroup = lineAnnotationGroup .selectAll("g.annotation-line-value-group") .attr("transform", (d) => { let x = d.lx + this.valueLabelOffsetX, y = d.ly + this.valueLabelOffsetY; return `translate(${x}, ${y})`; }); if(hasLabelBox) { //value container lineValueLabelGroup .selectAll("polygon.annotation-line-value-container") .data(function(d) { return [d]; }, viewModel.keyMap) .enter() .append("polygon") .attr("id", (d) => { let attr = convertValueToAttribute(d.key); return `annotation-line-${attr}-value-container`; }) .attr("class", "annotation-line-value-container"); //value lineValueLabelGroup .selectAll("text.annotation-line-value") .data(function(d) { return [d]; }, viewModel.keyMap) .enter() .append("text") .attr("id", (d) => { let attr = convertValueToAttribute(d.key); return `annotation-line-${attr}-value`; }) .attr("class", "annotation-line-value") .attr("text-anchor", "middle"); lineValueLabelGroup .selectAll("text.annotation-line-value") .text(function (d, i) { return d.data.getValueLabel(); }); // calculate the bounding boxs of each label for (let d of viewModel.dataArray) { d.valueBox = this.getLabelBBox(parent, d.key); d.valueContainerWidth = d.valueBox.width * this.valueLabelContainerWidthFactor; d.valueContainerWidthHalf = d.valueContainerWidth * 0.5; d.valueContainerWidthFraction = d.valueContainerWidth * 0.3; d.valueContainerHeight = d.valueBox.height * this.valueLabelContainerHeightFactor; d.valueContainerHeightHalf = d.valueContainerHeight * 0.5; d.valueContainerHeightFraction = d.valueContainerHeight * 0.3; } lineValueLabelGroup .selectAll("text.annotation-line-value") .attr("dx", function(d, i){ let dx = (d.isInterceptY) ? (d.valueContainerWidthHalf + d.valueContainerWidthFraction) : 0; return dx; }) .attr("dy", function(d, i){ let dy = d.valueBox.height * 0.25; if(d.isInterceptX) { dy = dy + d.valueContainerHeightHalf + d.valueContainerHeightFraction; } return dy; }); lineValueLabelGroup .selectAll("polygon.annotation-line-value-container") .attr("points", (d, i) => { let points = []; if(d.isInterceptX) { points.push(0 + "," + 0); points.push(-d.valueContainerWidthHalf + "," + d.valueContainerHeightFraction); points.push(-d.valueContainerWidthHalf + "," + (d.valueContainerHeight + d.valueContainerHeightFraction)); points.push(d.valueContainerWidthHalf + "," + (d.valueContainerHeight + d.valueContainerHeightFraction)); points.push(d.valueContainerWidthHalf + "," + d.valueContainerHeightFraction); } if(d.isInterceptY) { points.push(d.valueContainerWidthFraction + "," + -d.valueContainerHeightHalf); points.push(0 + "," + 0); points.push(d.valueContainerWidthFraction + "," + d.valueContainerHeightHalf); points.push((d.valueContainerWidth + d.valueContainerWidthFraction) + "," + d.valueContainerHeightHalf); points.push((d.valueContainerWidth + d.valueContainerWidthFraction) + "," + -d.valueContainerHeightHalf); } return points.join(" "); }); } if(hasLabel){ lineValueLabelGroup .selectAll("text.annotation-line-label") .data(function(d) { return [d]; }, viewModel.keyMap) .enter() .append("text") .attr("id", (d) => { let attr = convertValueToAttribute(d.key); return `annotation-line-${attr}-label`; }) .attr("class", "annotation-line-label") .attr("text-anchor", this.labelTextAnchor) .text(function (d, i) { return d.label; }); lineValueLabelGroup .selectAll("text.annotation-line-label") .attr("y", function(d, i) { let y = 0; if (d.isInterceptX) { y = y + this.getBBox().height; } else if (d.isInterceptY) { if (hasLabelBox) { y = y + (this.getBBox().height); } else { y = y + (this.getBBox().height * 0.25); } } return y; }) .attr("dx", (d, i) => { let dx = 0; if(hasLabelBox) { if (this.labelTextAnchor === "middle" && d.isInterceptY) { dx = dx + d.valueContainerWidthHalf + d.valueContainerWidthFraction; } else if (this.labelTextAnchor === "start" && d.isInterceptX) { dx = dx - d.valueContainerWidthHalf; } dx = dx + this.valueContainerLabelOffsetX; } return dx; }) .attr("dy", (d, i) => { let dy = 0; if(hasLabelBox) { if (d.isInterceptX) { dy = dy + d.valueContainerHeight + d.valueContainerHeightFraction; } else if (d.isInterceptY) { dy = dy + d.valueContainerHeightHalf; } dy = dy + this.valueContainerLabelOffsetY; } return dy; }); } } addDragableBehavior(parent, lineAnnotationGroup, viewModel){ let handles = lineAnnotationGroup .selectAll("rect.annotation-line-handle") .data(viewModel.dataArray, viewModel.keyMap) .enter() .append("rect") .attr("id", (d) => { let attr = convertValueToAttribute(d.key); return `annotation-line-${attr}-handle`; }) .attr("class", (d, i) => { let classStr = "annotation-handle"; if (this.isDraggable) { if (d.data.drag === "x") { classStr = classStr + " annotation-handle-draggable-x"; } else if (d.data.drag === "y") { classStr = classStr + " annotation-handle-draggable-y"; } } return classStr; }) .attr("x", (d, i) => { let x = d.x1 - (this.handleWidth * 0.5); if (d.data.drag === "y") { x = d.x1 + (d.lineLength * 0.5) - (d.lineLength * this.handleWidthLinePercent * 0.5); } return x; }) .attr("y", (d, i) => { let y = d.y2 + (d.lineLength * 0.5) - (d.lineLength * this.handleWidthLinePercent * 0.5); if (d.data.drag === "y") { y = d.y2 - (this.handleWidth * 0.5); } return y; }) .attr("width", (d, i) => { let width = this.handleWidth; if (d.data.drag === "y") { width = d.lineLength * this.handleWidthLinePercent; } return width; }) .attr("height", (d, i) => { let height = d.lineLength * this.handleWidthLinePercent; if (d.data.drag === "y") { height = this.handleWidth; } return height; }); handles.call(this.dragBehavior); } }