///
//namespace GraphTableSVG {
import { ZPathTextBox } from "./z_path_textbox"
import { ZTextBox } from "./z_textbox"
import { ShapeObjectType, ConnectorType, msoDashStyle, Direction, AutoSizeShapeToFitText, VBAShapeType } from "../common/enums";
//import {ZTextBoxAttributes, ZObjectAttributes, ZCalloutAttributes, ZShapeArrowCalloutAttributes} from "../options/attributes_option"
import * as AttributeNames from "../common/attribute_names"
import { Rectangle, VLine, escapeWithRound100 } from "../common/vline"
import * as SVGTextBox from "../interfaces/svg_textbox";
import * as GOptions from "./z_options"
import * as ElementExtension from "../interfaces/element_extension"
import * as SVGTextExtensions from "../interfaces/svg_text_extension"
import * as SVGTextExtension from "../interfaces/svg_text_extension"
export type _ZShapeArrowCalloutAttributes = {
arrowHeadWidth?: number,
arrowHeadHeight?: number,
arrowNeckWidth?: number,
arrowNeckHeight?: number,
direction?: Direction
}
export type ZShapeArrowCalloutAttributes = GOptions.ZTextBoxAttributes & _ZShapeArrowCalloutAttributes
export class ZArrowCallout extends ZPathTextBox {
public constructor(svgbox: SVGElement | string) {
super(svgbox);
//this.height = 100;
//this.width = 100;
this.arrowNeckWidth = 10;
this.arrowNeckHeight = 10;
this.arrowHeadWidth = 20;
this.arrowHeadHeight = 20;
this.svgGroup.setAttribute("data-direction", "down");
this.updateAttributes.push("data-direction");
if (this.type == ShapeObjectType.ArrowCallout) this.firstFunctionAfterInitialized();
}
/*
static constructAttributes(e: Element, removeAttributes: boolean = false, output: ZShapeArrowCalloutAttributes = {}): ZShapeArrowCalloutAttributes {
ZTextBox.constructAttributes(e, removeAttributes, output);
output.arrowNeckWidth = ElementExtension.gtGetAttributeNumberWithUndefined(e, AttributeNames.arrowNeckWidth);
output.arrowNeckHeight = ElementExtension.gtGetAttributeNumberWithUndefined(e, AttributeNames.arrowNeckHeight);
output.arrowHeadWidth = ElementExtension.gtGetAttributeNumberWithUndefined(e, AttributeNames.arrowHeadWidth);
output.arrowHeadHeight = ElementExtension.gtGetAttributeNumberWithUndefined(e, AttributeNames.arrowNeckHeight);
const p = ElementExtension.gtGetAttributeStringWithUndefined(e, AttributeNames.direction);
if (p !== undefined) {
output.direction = Direction.toDirection(p);
}
if (removeAttributes) {
e.removeAttribute(AttributeNames.arrowNeckWidth);
e.removeAttribute(AttributeNames.arrowNeckHeight);
e.removeAttribute(AttributeNames.arrowHeadWidth);
e.removeAttribute(AttributeNames.arrowHeadHeight);
e.removeAttribute(AttributeNames.direction);
}
return output;
}
*/
public initializeSetBasicOption(source : SVGElement) {
super.initializeSetBasicOption(source);
const _arrowNeckWidth = ElementExtension._getAttributeNumber(source, AttributeNames.arrowNeckWidth, true);
const _arrowNeckHeight = ElementExtension._getAttributeNumber(source, AttributeNames.arrowNeckHeight, true);
const _arrowHeadWidth = ElementExtension._getAttributeNumber(source, AttributeNames.arrowHeadWidth, true);
const _arrowHeadHeight = ElementExtension._getAttributeNumber(source, AttributeNames.arrowNeckHeight, true);
const _direction = ElementExtension._getAttribute(source, AttributeNames.direction, true);
this.arrowNeckWidth = _arrowNeckWidth !== null ? _arrowNeckWidth : this.arrowNeckWidth;
this.arrowNeckHeight = _arrowNeckHeight !== null ? _arrowNeckHeight : this.arrowNeckHeight;
this.arrowHeadWidth = _arrowHeadWidth !== null ? _arrowHeadWidth : this.arrowHeadWidth;
this.arrowHeadHeight = _arrowHeadHeight !== null ? _arrowHeadHeight : this.arrowHeadHeight;
this.direction = _direction !== null ? Direction.toDirection(_direction) : this.direction;
}
/*
protected setBasicOption(option: ZShapeArrowCalloutAttributes) {
super.setBasicOption(option);
}
public setOption(option: ZShapeArrowCalloutAttributes) {
super.setOption(option)
}
*/
/*
static openCustomElement(e: SVGElement): ShapeArrowCallout {
const parent = e.parentElement;
if (parent instanceof SVGSVGElement) {
const option = ShapeArrowCallout.constructAttributes(e, true);
const attrs = e.gtGetAttributes();
const r = new ShapeArrowCallout(parent, option);
e.remove();
attrs.forEach((v) => r.svgGroup.setAttribute(v.name, v.value));
return r;
} else {
throw Error("error!");
}
}
*/
public get type(): ShapeObjectType {
return ShapeObjectType.ArrowCallout;
}
get arrowNeckWidth(): number {
return ElementExtension.gtGetAttributeNumberWithoutNull(this.svgGroup, "data-arrow-neck-width", 0);
}
set arrowNeckWidth(value: number) {
if (this.arrowNeckWidth != value) this.svgGroup.setAttribute("data-arrow-neck-width", value.toString());
}
get arrowNeckHeight(): number {
return ElementExtension.gtGetAttributeNumberWithoutNull(this.svgGroup, "data-arrow-neck-height", 0);
}
set arrowNeckHeight(value: number) {
if (this.arrowNeckHeight != value) this.svgGroup.setAttribute("data-arrow-neck-height", value.toString());
}
get arrowHeadWidth(): number {
return ElementExtension.gtGetAttributeNumberWithoutNull(this.svgGroup, "data-arrow-head-width", 0);
}
set arrowHeadWidth(value: number) {
if (this.arrowHeadWidth != value) this.svgGroup.setAttribute("data-arrow-head-width", value.toString());
}
get arrowHeadHeight(): number {
return ElementExtension.gtGetAttributeNumberWithoutNull(this.svgGroup, "data-arrow-head-height", 0);
}
set arrowHeadHeight(value: number) {
if (this.arrowHeadHeight != value) this.svgGroup.setAttribute("data-arrow-head-height", value.toString());
}
get direction(): Direction {
const r = this.svgGroup.getAttribute("data-direction");
return Direction.toDirection(r);
}
set direction(value: Direction) {
if (this.direction != value) {
this.svgGroup.setAttribute("data-direction", value.toString());
}
}
get topExtraLength(): number {
if (this.direction == "up") {
return this.arrowHeadHeight + this.arrowNeckHeight + this.marginPaddingTop;
} else {
return this.marginPaddingTop;
}
}
get leftExtraLength(): number {
if (this.direction == "left") {
return this.arrowHeadHeight + this.arrowNeckHeight + this.marginPaddingLeft;
} else {
return this.marginPaddingLeft;
}
}
get rightExtraLength(): number {
if (this.direction == "right") {
return this.arrowHeadHeight + this.arrowNeckHeight + this.marginPaddingRight;
} else {
return this.marginPaddingRight;
}
}
get bottomExtraLength(): number {
if (this.direction == "down") {
return this.arrowHeadHeight + this.arrowNeckHeight + this.marginPaddingBottom;
} else {
return this.marginPaddingBottom;
}
}
/*
get innerRectangle(): Rectangle {
const rect = new Rectangle();
if (this.isAutoSizeShapeToFitText == AutoSizeShapeToFitText.Auto) {
const textRect = SVGTextExtensions.getSize(this.svgText);
//const b = this.svgText.getBBox();
rect.width = textRect.width;
rect.height = textRect.height;
rect.x = (-this.width / 2) + this.marginPaddingLeft;
rect.y = (-this.height / 2) + this.marginPaddingTop;
} else {
rect.width = this.boxWidth - this.marginPaddingLeft;
rect.height = this.boxHeight - this.marginPaddingTop;
rect.x = (-this.width / 2) + this.marginPaddingLeft;
rect.y = (-this.height / 2) + this.marginPaddingTop;
}
if (this.direction == "up") rect.y += this.arrowNeckHeight + this.arrowHeadHeight;
if (this.direction == "left") rect.x += this.arrowNeckHeight + this.arrowHeadHeight;
return rect;
}
*/
/**
* 矢印部分を除いた図形の高さを表します。
*/
protected get boxHeight() {
if (this.direction == "up" || this.direction == "down") {
return this.height - this.arrowNeckHeight - this.arrowHeadWidth;
} else {
return this.height;
}
}
protected get boxWidth() {
if (this.direction == "up" || this.direction == "down") {
return this.width;
} else {
return this.width - this.arrowNeckHeight - this.arrowHeadWidth;
}
}
/*
protected updateToFitText() {
const textRect = SVGTextExtensions.getSize(this.svgText);
//const box = this.svgText.getBBox();
if (this.direction == "up" || this.direction == "down") {
this.width = textRect.width + this.marginPaddingLeft + this.marginPaddingRight;
this.height = textRect.height + this.marginPaddingTop + this.marginPaddingBottom + this.arrowNeckHeight + this.arrowHeadHeight;
} else {
this.width = textRect.width + this.marginPaddingLeft + this.marginPaddingRight + this.arrowNeckHeight + this.arrowHeadHeight;
this.height = textRect.height + this.marginPaddingTop + this.marginPaddingBottom;
}
}
*/
/*
public getVirtualRegion(): Rectangle {
const textRect = SVGTextExtension.getVirtualRegion(this.svgText);
let _w = 0;
let _h = 0;
if (this.direction == "up" || this.direction == "down") {
_w = textRect.width + this.marginPaddingLeft + this.marginPaddingRight;
_h = textRect.height + this.marginPaddingTop + this.marginPaddingBottom + this.arrowNeckHeight + this.arrowHeadHeight;
} else {
_w = textRect.width + this.marginPaddingLeft + this.marginPaddingRight + this.arrowNeckHeight + this.arrowHeadHeight;
_h = textRect.height + this.marginPaddingTop + this.marginPaddingBottom;
}
if (this.isAutoSizeShapeToFitText == AutoSizeShapeToFitText.Auto) {
const textWidth = _w < this._minimumWidth ? this._minimumWidth : _w;
const textHeight = _h < this._minimumHeight ? this._minimumHeight : _h;
const width = _w;
const height = _h;
return new Rectangle(this.cx - (width / 2), this.cy - (height / 2), width, height);
} else if (this.isAutoSizeShapeToFitText == AutoSizeShapeToFitText.SemiAuto) {
const newWidth = this.width < _w ? _w : this.width;
const newHeigth = this.height < _h ? _h : this.height;
return new Rectangle(this.cx - (newWidth / 2), this.cy - (newHeigth / 2), newWidth, newHeigth);
} else {
return new Rectangle(this.cx - (this.width / 2), this.cy - (this.height / 2), this.width, this.height);
//return new Rectangle(this.x, this.y, this.width, this.height);
}
}
*/
public update() {
super.update();
const region = this.getVirtualRegion();
const x1 = region.x;
const y1 = region.y;
const x2 = region.right;
const y2 = region.bottom;
if (this.direction == "up") {
const bx1 = x1;
const by1 = y1 + this.arrowHeadHeight + this.arrowNeckHeight;
const bx2 = x2;
const by2 = y2;
let nx1 = - (this.arrowNeckWidth / 2)
let nx2 = (this.arrowNeckWidth / 2)
let ny = by1 - this.arrowNeckHeight;
let cx = 0;
let hx1 = - (this.arrowHeadWidth / 2)
let hx2 = (this.arrowHeadWidth / 2)
let hy = y1;
const mes = escapeWithRound100 `H ${nx1} V ${ny} H ${hx1} L ${cx} ${hy} L ${hx2} ${ny} H ${nx2} V ${by1}`;
const top = escapeWithRound100 `M ${bx1} ${by1} ${mes} H ${bx2}`;
const right = escapeWithRound100 `V ${by2}`;
const bottom = escapeWithRound100 `H ${bx1}`;
const left = escapeWithRound100 `V ${by1}`
this.svgPath.setAttribute("d", escapeWithRound100 `${top} ${right} ${bottom} ${left} z`);
} else if (this.direction == "left") {
const bx1 = x1 + this.arrowHeadHeight + this.arrowNeckHeight;
const by1 = y1;
const bx2 = x2;
const by2 = y2;
let ny1 = 0 + (this.arrowNeckWidth / 2)
let ny2 = 0 - (this.arrowNeckWidth / 2)
let nx = bx1 - this.arrowNeckHeight;
let cy = 0;
let hy1 = 0 + (this.arrowHeadWidth / 2)
let hy2 = 0 - (this.arrowHeadWidth / 2)
let hx = x1;
const top = escapeWithRound100 `M ${bx1} ${by1} H ${bx2}`;
const right = escapeWithRound100 `V ${by2}`;
const bottom = escapeWithRound100 `H ${bx1}`;
const left = escapeWithRound100 `V ${ny1} H ${nx} V ${hy1} L ${hx} ${cy} L ${nx} ${hy2} V ${ny2} H ${bx1} V ${by1}`
this.svgPath.setAttribute("d", escapeWithRound100 `${top} ${right} ${bottom} ${left} z`);
} else if (this.direction == "right") {
const bx1 = x1;
const by1 = y1;
const bx2 = x2 - this.arrowNeckHeight - this.arrowHeadHeight;
const by2 = y2;
let ny1 = 0 - (this.arrowNeckWidth / 2)
let ny2 = 0 + (this.arrowNeckWidth / 2)
let nx = bx2 + this.arrowNeckHeight;
let cy = 0;
let hy1 = 0 - (this.arrowHeadWidth / 2)
let hy2 = 0 + (this.arrowHeadWidth / 2)
let hx = x2;
const top = escapeWithRound100 `M ${bx1} ${by1} H ${bx2}`;
const right = escapeWithRound100 `V ${ny1} H ${nx} V ${hy1} L ${hx} ${cy} L ${nx} ${hy2} V ${ny2} H ${bx2} V ${by2}`;
const bottom = escapeWithRound100 `H ${bx1}`;
const left = escapeWithRound100 `V ${by1}`;
this.svgPath.setAttribute("d", escapeWithRound100 `${top} ${right} ${bottom} ${left} z`);
} else {
const bx1 = x1;
const by1 = y1;
const bx2 = x2;
const by2 = y2 - this.arrowHeadHeight - this.arrowNeckHeight;
//const by = boxHeight + dy;
let nx1 = - (this.arrowNeckWidth / 2)
let nx2 = (this.arrowNeckWidth / 2)
let ny = by2 + this.arrowNeckHeight;
let cx = 0;
let hx1 = - (this.arrowHeadWidth / 2)
let hx2 = (this.arrowHeadWidth / 2)
let hy = y2;
const top = escapeWithRound100 `M ${bx1} ${by1} H ${bx2}`;
const right = escapeWithRound100 `V ${by2}`;
const bottom = escapeWithRound100 `H ${nx2} V ${ny} H ${hx2} L ${cx} ${hy} L ${hx1} ${ny} H ${nx1} V ${by2} H ${bx1}`;
const left = escapeWithRound100 `V ${by1}`
this.svgPath.setAttribute("d", escapeWithRound100 `${top} ${right} ${bottom} ${left} z`);
}
}
public get shape(): VBAShapeType {
switch (this.direction) {
case "up": return VBAShapeType.UpArrowCallout;
case "left": return VBAShapeType.LeftArrowCallout;
case "right": return VBAShapeType.RightArrowCallout;
case "down": return VBAShapeType.DownArrowCallout;
}
return VBAShapeType.DownArrowCallout;
}
/**
* VBAコードでのこの図形を表すShape図形のVBAAdjustmentsプロパティを表します。
* 第一要素は矢印の首の幅()
* 第二要素は矢印の頭の幅
* @returns VBAAdjustments値の配列。
*/
protected get VBAAdjustments(): number[] {
if (this.direction == "up") {
const neckWidthRatio = this.arrowNeckWidth / this.height;
const headWidthRatio = this.arrowHeadWidth / (this.height * 2);
const headHeightRatio = this.arrowHeadHeight / this.height;
const boxHeightRatio = this.boxHeight / this.height;
return [neckWidthRatio, headWidthRatio, headHeightRatio, boxHeightRatio];
} else if (this.direction == "right") {
const neckWidthRatio = this.arrowNeckWidth / this.height;
const headWidthRatio = this.arrowHeadWidth / (this.height * 2);
const headHeightRatio = this.arrowHeadHeight / this.height;
const boxWidthRatio = this.boxWidth / this.width;
return [neckWidthRatio, headWidthRatio, headHeightRatio, boxWidthRatio];
} else if (this.direction == "left") {
const neckWidthRatio = this.arrowNeckWidth / this.height;
const headWidthRatio = this.arrowHeadWidth / (this.height * 2);
const headHeightRatio = this.arrowHeadHeight / this.height;
const boxWidthRatio = this.boxWidth / this.width;
return [neckWidthRatio, headWidthRatio, headHeightRatio, boxWidthRatio];
} else {
const neckWidthRatio = this.arrowNeckWidth / this.height;
const headWidthRatio = this.arrowHeadWidth / (this.height * 2);
const headHeightRatio = this.arrowHeadHeight / this.height;
const boxHeightRatio = this.boxHeight / this.height;
return [neckWidthRatio, headWidthRatio, headHeightRatio, boxHeightRatio];
}
}
/**
* 接続部分の座標を返します。
* @param type
* @param x
* @param y
*/
public getContactPosition(type: ConnectorType, x: number, y: number): [number, number] {
const wr = this.width / 2;
const hr = this.height / 2;
switch (type) {
case ConnectorType.Top:
return [this.x, this.y - hr];
case ConnectorType.TopRight:
case ConnectorType.Right:
case ConnectorType.BottomRight:
return [this.x + wr, this.y];
case ConnectorType.Bottom:
return [this.x, this.y + hr];
case ConnectorType.BottomLeft:
case ConnectorType.Left:
case ConnectorType.TopLeft:
return [this.x - wr, this.y];
default:
const autoType = this.getContactAutoPosition(x, y);
return this.getContactPosition(autoType, x, y);
}
}
public getContactAutoPosition(x: number, y: number): ConnectorType {
const wr = this.width / 2;
const hr = this.height / 2;
const line1 = new VLine(this.x, this.y, this.x + wr, this.y + hr);
const line2 = new VLine(this.x, this.y, this.x + wr, this.y - hr);
const b1 = line1.contains(x, y);
const b2 = line2.contains(x, y);
if (b1) {
if (b2) {
return ConnectorType.Top;
} else {
return ConnectorType.Right;
}
} else {
if (b2) {
return ConnectorType.Left;
} else {
return ConnectorType.Bottom;
}
}
}
}
//}