Last active
January 31, 2018 21:03
-
-
Save takahashihideki-git/cd2b2f8ce5df75fe94ea to your computer and use it in GitHub Desktop.
Textwell Action "Diagram"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Diagram</title> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
font-family: sans-serif; | |
-webkit-user-select:none; | |
} | |
.container { | |
position: relative; | |
margin: 0 auto; | |
border: 1px solid #ccc; | |
} | |
.container .handlerLayer { | |
position: absolute; | |
top: 0; | |
height: 0; | |
width: 100%; | |
height: 100%; | |
} | |
.container .handler { | |
position: absolute; | |
background-color: rgba( 200, 200, 200, 0 ); | |
cursor: move; | |
} | |
.container .handler.dragging { | |
background-color: rgba( 200, 200, 200, 0 ); | |
} | |
.container .message { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
box-sizing: border-box; | |
padding: 0.5em; | |
background-color: rgba( 0, 0, 0, 0.5 ); | |
color: #eee; | |
text-align: center; | |
font-size: 200%; | |
} | |
.container img { | |
width: 100%; | |
} | |
.button { | |
width: 10em; | |
font-size: 100%; | |
margin: 1em auto; | |
padding: 0.5em 1em; | |
font-family: sans-serif; | |
text-align: center; | |
color: #2168FF; | |
border: 1px solid #2168FF; | |
border-radius: 20px; | |
cursor: pointer; | |
} | |
.container .resizer { | |
position: absolute; | |
bottom: 0; | |
right: 0; | |
width: 80px; | |
height: 80px; | |
cursor: pointer; | |
} | |
.transformer { | |
position: fixed; | |
top: 0; | |
width: 100%; | |
font-size: 100%; | |
overflow-y: auto; | |
opacity: 0; | |
transition: opacity 0.2s linear 0; | |
} | |
.transformer-pannel { | |
width: 90%; | |
margin: 0 auto; | |
padding: 1em 0.5em; | |
background-color: rgba( 0, 0, 0, 0.8 ); | |
border-radius: 0.5em; | |
} | |
.transformer th, | |
.transformer td { | |
padding: 0.5em; | |
} | |
.transformer th { | |
padding-right: 1em; | |
vertical-align: top; | |
text-align: right; | |
font-weight: bold; | |
color: #fff; | |
} | |
.transformer .setting-size th { | |
line-height: 2em; | |
} | |
.transformer ul { | |
margin: 0; | |
padding: 0; | |
list-style-type: none; | |
} | |
.transformer li { | |
margin: 0 0.5em 0.5em 0; | |
padding: 0 0.5em; | |
display: inline-block; | |
color: #666; | |
border: 2px solid #666; | |
border-radius: 1em; | |
line-height: 1.4; | |
cursor: pointer; | |
} | |
.transformer li.selected { | |
color: #fff; | |
border: 2px solid #fff; | |
} | |
.transformer .colors li { | |
opacity: 0.3; | |
padding: 0; | |
width: 1.4em; | |
height: 1.4em; | |
border: 2px solid #000; | |
} | |
.transformer .colors li.selected { | |
opacity: 1; | |
border: 2px solid #fff; | |
} | |
.transformer form { | |
position: relative; | |
} | |
.transformer label { | |
position: absolute; | |
top: 0; | |
display: block; | |
height: 1em; | |
color: #ccc; | |
} | |
.transformer label[for=transrator-width] { | |
left: 0.4em; | |
} | |
.transformer label[for=transrator-height] { | |
left: 5.7em; | |
} | |
.transformer input { | |
width: 3em; | |
height: 1em; | |
padding-left: 1.5em; | |
border: 1px solid #666; | |
border-radius: 1em; | |
font-size: 100%; | |
} | |
.transformer .button { | |
width: auto; | |
margin-left: 0; | |
} | |
@media (max-device-width: 480px) { | |
.button { | |
font-size: 400%; | |
} | |
.transformer { | |
position: absolute; | |
top: 20px; | |
font-size: 300%; | |
} | |
.transformer .button { | |
padding: 1em; | |
font-size: 100%; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<!-- for dev --> | |
<textarea style="display:none"> | |
1.Responsive | |
Canvas | |
2.Handler | |
3.Shape | |
4.Connection | |
5.Transformer | |
6.Button | |
7.Parser | |
3->1. | |
4->1.draw | |
4->3.connect | |
2->3.delegate | |
3->5.use | |
6->1.control | |
1.style.(15,282,326,141,roundedRect,blue,auto) | |
2.style.(822,135,264,96,roundedRect,blue,auto) | |
3.style.(454,307,236,96,roundedRect,blue,auto) | |
4.style.(326,688,324,96,roundedRect,blue,auto) | |
5.style.(783,509,348,96,roundedRect,blue,auto) | |
6.style.(57,38,244,96,roundedRect,blue,auto) | |
7.style.(48,865,242,96,roundedRect,gray,auto) | |
</textarea> | |
<!-- for dev --> | |
<div class="container"> | |
<canvas class="canvas"></canvas> | |
<div class="handlerLayer"></div> | |
<img sytle="display:none"> | |
<div class="message" style="display:none">Converted. You can save the diagram as a file.</div> | |
<div class="resizer"><canvas></canvas></div> | |
</div> | |
<div class="button button-imagify">Convert to PNG</div> | |
<div class="transformer" style="display:none"> | |
<div class="transformer-pannel"> | |
<table> | |
<tr class="setting setting-color"> | |
<th>Color</th> | |
<td> | |
<ul class="colors"> | |
<li class="blue"></li> | |
<li class="green"></li> | |
<li class="purple"></li> | |
<li class="red"></li> | |
<li class="gray"></li> | |
<li class="indigo"></li> | |
<li class="lightGreen"></li> | |
<li class="deepPurple"></li> | |
<li class="pink"></li> | |
<li class="blueGray"></li> | |
<li class="lightBlue"></li> | |
<li class="teal"></li> | |
<li class="brown"></li> | |
<li class="orange"></li> | |
<li class="yellow"></li> | |
<li class="cyan"></li> | |
<li class="lime"></li> | |
<li class="amber"></li> | |
<li class="deepOrange"></li> | |
<li class="white"></li> | |
</ul> | |
</td> | |
</tr> | |
<tr class="setting setting-shape"> | |
<th>Shape</th> | |
<td> | |
<ul class="shapes"> | |
<li class="rect">Rectangle</li> | |
<li class="roundedRect">Rounded Rectangle</li> | |
<li class="circle">Circle</li> | |
<li class="ellipse">Ellipse</li> | |
<li class="transparent">Transparent</li> | |
</ul> | |
</td> | |
</tr> | |
<tr class="setting setting-size"> | |
<th>Size</th> | |
<td> | |
<form> | |
<label for="transrator-width">w</label><input id="transrator-width" class="width" type="text" value=""> | |
<label for="transrator-height">h</label><input id="transrator-height" class="height" type="text" value=""> | |
</form> | |
</td> | |
</tr> | |
<tr class="setting setting-connector"> | |
<th>Connector</th> | |
<td> | |
<ul class="connectors"> | |
<li class="auto">auto</li> | |
<li class="top">top</li> | |
<li class="right">right</li> | |
<li class="bottom">bottom</li> | |
<li class="left">left</li> | |
</ul> | |
</td> | |
</tr> | |
</table> | |
<div class="button">OK</div> | |
</div> | |
</div> | |
<script> | |
const FONTSIZE = 32; | |
const LINEHEIGHT = 1.4; | |
const COLORS = { | |
blue: | |
{ color: "rgba(33,150,243,1)", | |
alpha: "rgba(33,150,243,0.2)" }, | |
green: | |
{ color: "rgba(76,175,80,1)", | |
alpha: "rgba(76,175,80,0.2)" }, | |
purple: | |
{ color: "rgba(156,39,176,1)", | |
alpha: "rgba(156,39,176,0.2)" }, | |
red: | |
{ color: "rgba(244,67,54,1)", | |
alpha: "rgba(244,67,54,0.2)" }, | |
gray: | |
{ color: "rgba(158,158,158,1)", | |
alpha: "rgba(158,158,158,0.2)" }, | |
indigo: | |
{ color: "rgba(63,81,181,1)", | |
alpha: "rgba(63,81,181,0.2)" }, | |
lightGreen: | |
{ color: "rgba(139,195,74,1)", | |
alpha: "rgba(139,195,74,0.2)" }, | |
deepPurple: | |
{ color: "rgba(103,58,183,1)", | |
alpha: "rgba(103,58,183,0.2)" }, | |
pink: | |
{ color: "rgba(233,30,99,1)", | |
alpha: "rgba(233,30,99,0.2)" }, | |
blueGray: | |
{ color: "rgba(96,125,139,1)", | |
alpha: "rgba(96,125,139,0.2)" }, | |
lightBlue: | |
{ color: "rgba(3,169,244,1)", | |
alpha: "rgba(3,169,244,0.2)" }, | |
teal: | |
{ color: "rgba(0,150,136,1)", | |
alpha: "rgba(0,150,136,0.2)" }, | |
brown: | |
{ color: "rgba(121,85,72,1)", | |
alpha: "rgba(121,85,72,0.2)" }, | |
orange: | |
{ color: "rgba(255,152,0,1)", | |
alpha: "rgba(255,152,0,0.2)" }, | |
yellow: | |
{ color: "rgba(255,235,59,1)", | |
alpha: "rgba(255,235,59,0.2)" }, | |
cyan: | |
{ color: "rgba(0,188,212,1)", | |
alpha: "rgba(0,188,212,0.2)" }, | |
lime: | |
{ color: "rgba(205,220,57,1)", | |
alpha: "rgba(205,220,57,0.2)" }, | |
amber: | |
{ color: "rgba(255,193,7,1)", | |
alpha: "rgba(255,193,7,0.2)" }, | |
deepOrange: | |
{ color: "rgba(255,87,34,1)", | |
alpha: "rgba(255,87,34,0.2)" }, | |
white: | |
{ color: "rgba(255,255,255,1)", | |
alpha: "rgba(255,255,255,0.2)" }, | |
darkGray: | |
{ | |
color: "rgba( 50, 50, 50, 1 )", | |
alpha: "rgba( 50, 50, 50, 0.5 )" }, | |
black: | |
{ | |
color: "rgba( 0, 0, 0, 1 )", | |
alpha: "rgba( 0, 0, 0, 0.5 )" } | |
}; | |
// pointing events | |
const TOUCHABLE = "ontouchstart" in document; | |
const POINTING = { | |
move: TOUCHABLE ? "touchmove" : "mousemove", | |
down: TOUCHABLE ? "touchstart" : "mousedown", | |
up: TOUCHABLE ? "touchend" : "mouseup" | |
} | |
/** | |
ResponsiveCanvas Class | |
*/ | |
var ResponsiveCanvas = function ( args ) { | |
// get base | |
this.container = args.container; | |
this.canvas = this.container.querySelector( ".canvas" ); | |
this.context = this.canvas.getContext( '2d' ); | |
// set assets | |
this.image = this.container.querySelector( "img" ); | |
this.message = this.container.querySelector( ".message" ); | |
this.resizer = this.container.querySelector( ".resizer" ); | |
this.setResizerIcon(); | |
this.shapes = new Array(); | |
this.connections = new Array(); | |
this.handlerLayer = this.container.querySelector( ".handlerLayer" ); | |
// set size | |
var width = window.innerWidth * 0.9; | |
var height = width; | |
if ( args.width ) { | |
width = args.width; | |
} | |
if ( args.height ) { | |
height = args.height; | |
} | |
this.resize( width, height ); | |
var canvas = this; | |
this.handlerLayer.addEventListener( POINTING.move, function( event ) { | |
event.preventDefault(); | |
}, true ); | |
this.resizer.addEventListener( POINTING.down, function ( event ) { | |
event.preventDefault(); | |
var e = TOUCHABLE ? event.changedTouches[ 0 ] : event; | |
canvas.resizing = true; | |
canvas.lastResizerPos = { | |
x: e.screenX, | |
y: e.screenY | |
} | |
}, true ); | |
document.querySelector( "body" ).addEventListener( POINTING.up, function ( event ) { | |
canvas.resizing = false; | |
canvas.lastResizerPos = false; | |
}, true ); | |
document.querySelector( "body" ).addEventListener( POINTING.move, function ( event ) { | |
var e = TOUCHABLE ? event.changedTouches[ 0 ] : event; | |
if ( canvas.resizing && canvas.lastResizerPos ) { | |
var distanceX = e.screenX - canvas.lastResizerPos.x; | |
var distanceY = e.screenY - canvas.lastResizerPos.y; | |
// threshold | |
if ( Math.abs( distanceX ) > canvas.resizingMax || Math.abs( distanceY ) > canvas.resizinMax ) { | |
return; | |
} | |
canvas.resize( canvas.width + distanceX, canvas.height + distanceY ); | |
canvas.lastResizerPos = { | |
x: e.screenX, | |
y: e.screenY | |
} | |
} | |
}, true ); | |
} | |
ResponsiveCanvas.prototype = { | |
container: null, | |
canvas: null, | |
context: null, | |
width: 0, | |
height: 0, | |
handlerLayer: null, | |
shapes: null, | |
connections: null, | |
image: null, | |
message: null, | |
resizing: false, | |
lastResizerPos: null, | |
resizingMax: 100, | |
autoAlignMark: { | |
x: 0, | |
line: 0, | |
lineY: [0], | |
marginX: 20, | |
marginY: 100, | |
}, | |
setResizerIcon: function () { | |
var width = this.resizer.clientWidth; | |
var height = this.resizer.clientHeight; | |
var canvas = this.resizer.querySelector( "canvas" ); | |
canvas.setAttribute( "width", width ); | |
canvas.setAttribute( "height", height ); | |
var context = canvas.getContext( '2d' ); | |
var start = width / 2; | |
var span = start / 3; | |
context.strokeStyle = COLORS[ "gray" ].color; | |
context.beginPath(); | |
context.moveTo( width, start ); | |
context.lineTo( start, height ); | |
context.moveTo( width, start + span ); | |
context.lineTo( start + span, height ); | |
context.moveTo( width, start + span * 2 ); | |
context.lineTo( start + span * 2, height ); | |
context.stroke(); | |
}, | |
resize: function ( width, height ) { | |
this.width = width; | |
this.height = height; | |
this.container.style.width = this.width + "px"; | |
this.container.style.height = this.height + "px"; | |
this.container.style.marginTop = this.width / 18 + "px"; | |
this.canvas.setAttribute( "width", this.width ); | |
this.canvas.setAttribute( "height", this.height ); | |
this.refresh(); | |
}, | |
appendShape: function ( shape ) { | |
this.shapes.push( shape ); | |
// auto align x | |
var x = this.autoAlignMark.x + this.autoAlignMark.marginX; | |
if ( x + shape.width > this.width ) { | |
//line feed | |
this.autoAlignMark.x = 0; | |
this.autoAlignMark.line++; | |
x = this.autoAlignMark.x + this.autoAlignMark.marginX | |
} | |
// set x to shape | |
shape.x = x; | |
//for next x | |
this.autoAlignMark.x = x + shape.width; | |
// auto align y | |
var y = this.autoAlignMark.lineY[ this.autoAlignMark.line ] + this.autoAlignMark.marginY; | |
// set y to shape | |
shape.y = y; | |
// resize canvas | |
if ( y + shape.height > this.height ) { | |
this.resize( this.width, y + shape.height + this.autoAlignMark.marginY ) | |
} | |
// for next y | |
if ( ! this.autoAlignMark.lineY[ this.autoAlignMark.line + 1 ] ) { | |
this.autoAlignMark.lineY[ this.autoAlignMark.line + 1 ] = y + shape.height; | |
} | |
else if ( y + shape.height > this.autoAlignMark.lineY[ this.autoAlignMark.line + 1 ] ) { | |
this.autoAlignMark.lineY[ this.autoAlignMark.line + 1 ] = y + shape.height; | |
} | |
}, | |
appendConnection: function ( connection ) { | |
this.connections.push( connection ); | |
}, | |
refresh: function () { | |
if ( this.connections.length || this.shapes.length ) { | |
this.context.clearRect( 0, 0, this.width, this.height ); | |
} | |
if ( this.connections.length ) { | |
for ( var i = 0; i < this.connections.length; i++ ) { | |
this.connections[ i ].refresh(); | |
} | |
} | |
if ( this.shapes.length ) { | |
for ( var i = 0; i < this.shapes.length; i++ ) { | |
this.shapes[ i ].refresh(); | |
} | |
} | |
}, | |
imagify: function () { | |
this.image.setAttribute( "src", this.canvas.toDataURL() ) | |
this.canvas.style.display = "none"; | |
this.handlerLayer.style.display = "none"; | |
this.resizer.style.display = "none"; | |
this.message.style.display = "block"; | |
this.image.style.display = "inline-block"; | |
}, | |
unimagify: function () { | |
this.message.style.display = "none"; | |
this.image.style.display = "none"; | |
this.canvas.style.display = "block"; | |
this.handlerLayer.style.display = "block"; | |
this.resizer.style.display = "block"; | |
}, | |
getIndexOfShape: function ( shape ) { | |
for ( var i = 0; i < this.shapes.length; i++ ) { | |
if ( shape == this.shapes[ i ] ) { | |
break; | |
} | |
} | |
return i; | |
} | |
}; | |
/** | |
Handler Class | |
*/ | |
var Handler = function ( args ) { | |
this.canvas = args.canvas; | |
this.shape = args.shape; | |
this.x = args.x; | |
this.y = args.y; | |
this.width = args.width; | |
this.height = args.height; | |
this.initialize(); | |
} | |
Handler.prototype = { | |
canvas: null, | |
shape: null, | |
element: null, | |
x: 0, | |
y: 0, | |
width: 0, | |
height: 0, | |
lastPos: null, | |
dragging: false, | |
draggingMax: 100, | |
holding: false, | |
holdingTime: 1000, | |
holdingTimer: null, | |
initialize: function () { | |
this.element = document.createElement( "div" ); | |
this.element.style.top = this.y + "px"; | |
this.element.style.left = this.x + "px"; | |
this.element.style.width = this.width + "px"; | |
this.element.style.height = this.height + "px"; | |
this.element.setAttribute( "class", "handler" ); | |
this.canvas.handlerLayer.appendChild( this.element ); | |
var handler = this; | |
this.element.addEventListener( POINTING.down, function ( event ) { | |
var e = TOUCHABLE ? event.touches[ 0 ] : event; | |
handler.lastPos = { | |
x: e.screenX, | |
y: e.screenY | |
} | |
var classes = handler.element.getAttribute( "class" ); | |
handler.element.setAttribute( "class", classes + " dragging" ); | |
handler.dragging = true; | |
handler.holding = true; | |
handler.holdingTimer = setTimeout( function () { | |
if ( handler.holding ) { | |
handler.holding = false; | |
handler.dragging = false; | |
handler.shape.transform(); | |
} | |
}, handler.holdingTime ) | |
} ); | |
this.element.addEventListener( POINTING.move, function ( event ) { | |
handler.holding = false; | |
if ( handler.holdingTimer ) { | |
clearTimeout( handler.holdingTimer ); | |
} | |
if ( ! handler.dragging ) { | |
return; | |
} | |
event.preventDefault(); | |
var e = TOUCHABLE ? event.changedTouches[ 0 ] : event; | |
var x = e.screenX; | |
var y = e.screenY; | |
var distanceX = x - handler.lastPos.x; | |
var distanceY = y - handler.lastPos.y; | |
// threshold | |
if ( Math.abs( distanceX ) > handler.draggingMax || Math.abs( distanceY ) > handler.draggingMax ) { | |
return; | |
} | |
var newX = handler.element.offsetLeft + distanceX; | |
var newY = handler.element.offsetTop + distanceY; | |
if ( newX < 0 ) { | |
newX = 0; | |
} | |
if ( newX + handler.width > handler.canvas.width ) { | |
newX = handler.canvas.width - handler.width; | |
} | |
if ( newY < 0 ) { | |
newY = 0; | |
} | |
if ( newY + handler.height > handler.canvas.height ) { | |
newY = handler.canvas.height - handler.height; | |
} | |
handler.lastPos = { | |
x: x, | |
y: y | |
} | |
handler.move( newX, newY ); | |
}, true ); | |
this.element.addEventListener( POINTING.up, function ( event ) { | |
var classes = handler.element.getAttribute( "class" ); | |
handler.element.setAttribute( "class", classes.replace( / dragging/, "" ) ); | |
handler.dragging = false; | |
handler.holding = false; | |
if ( handler.holdingTimer ) { | |
clearTimeout( handler.holdingTimer ); | |
} | |
} ); | |
}, | |
move: function ( x, y ) { | |
this.element.style.left = x + "px"; | |
this.element.style.top = y + "px"; | |
this.shape.move( x, y ); | |
}, | |
setSize: function ( width, height ) { | |
this.width = width; | |
this.height = height; | |
this.element.style.width = this.width + "px"; | |
this.element.style.height = this.height + "px"; | |
} | |
}; | |
/** | |
Shape Class | |
*/ | |
var Shape = function ( args ) { | |
this.canvas = args.canvas; | |
this.transformer = args.transformer; | |
this.drawer = args.drawer; | |
if ( args.text ) { | |
this.text = args.text; | |
var sizeByText = this.getSizeByText(); | |
this.width = sizeByText.width; | |
this.height = sizeByText.height; | |
} | |
if ( this.drawer == "circle" ) { | |
this.squarize(); | |
} | |
// set size by arguments | |
if ( args.width ) { | |
this.width = args.width; | |
} | |
if ( args.height ) { | |
this.height = args.height; | |
} | |
this.color = args.color; | |
this.canvas.appendShape( this ); | |
} | |
Shape.prototype = { | |
canvas: null, | |
transformer: null, | |
width: 100, | |
height: 100, | |
x: 0, | |
y: 0, | |
handler: null, | |
text: "", | |
fontSize: FONTSIZE, | |
lineHeight: LINEHEIGHT, | |
textBoxWidth: 0, | |
textBoxHeight: 0, | |
color: "", | |
connector: "auto", | |
drawer: "", | |
drawers: { | |
transparent: function () { | |
return; | |
}, | |
rect: function ( shape ) { | |
var ctx = shape.canvas.context; | |
var x = shape.x; | |
var y = shape.y; | |
var width = shape.width; | |
var height = shape.height; | |
var color = shape.color; | |
var radius = 50; | |
ctx.strokeStyle = COLORS[ "darkGray" ].color; | |
ctx.beginPath(); | |
ctx.moveTo( x, y ); | |
ctx.lineTo( x + width, y ); | |
ctx.lineTo( x + width, y + height ); | |
ctx.lineTo( x, y + height ); | |
ctx.closePath(); | |
if ( color && COLORS[ color ] ) { | |
ctx.fillStyle = COLORS[ color ].alpha; | |
ctx.fill(); | |
} | |
ctx.stroke(); | |
}, | |
roundedRect: function ( shape ) { | |
var ctx = shape.canvas.context; | |
var x = shape.x; | |
var y = shape.y; | |
var width = shape.width; | |
var height = shape.height; | |
var color = shape.color; | |
var radius = 30; | |
ctx.strokeStyle = COLORS[ "darkGray" ].color; | |
ctx.beginPath(); | |
ctx.moveTo( x + radius, y ); | |
ctx.lineTo( x + width - radius, y ); | |
ctx.arc( x + width - radius, y + radius, radius, Math.PI*1.5, 0, false ); | |
ctx.lineTo( x + width, y + height - radius ); | |
ctx.arc( x + width - radius, y + height - radius, radius, 0, Math.PI*0.5, false ); | |
ctx.lineTo( x + radius, y + height ); | |
ctx.arc( x + radius, y + height - radius, radius, Math.PI*0.5, Math.PI, false ); | |
ctx.lineTo( x, y + radius ); | |
ctx.arc( x + radius, y + radius, radius, Math.PI, Math.PI*1.5, false ); | |
ctx.closePath(); | |
if ( color && COLORS[ color ] ) { | |
ctx.fillStyle = COLORS[ color ].alpha; | |
ctx.fill(); | |
} | |
ctx.stroke(); | |
}, | |
circle: function ( shape ) { | |
shape.drawers[ "ellipse" ]( shape ); | |
}, | |
ellipse: function ( shape ) { | |
var x = shape.x + shape.width / 2; | |
var y = shape.y + shape.height / 2; | |
var ctx = shape.canvas.context; | |
ctx.strokeStyle = COLORS[ "darkGray" ].color; | |
var axisRatio = shape.width / shape.height; | |
ctx.moveTo( x, y ); | |
ctx.beginPath(); | |
ctx.save(); | |
ctx.scale( axisRatio, 1 ); | |
ctx.arc( | |
x * ( 1 / axisRatio ), | |
y, | |
shape.width * ( 1 / axisRatio ) / 2, | |
0, | |
Math.PI*2, | |
false | |
); | |
ctx.closePath(); | |
if ( shape.color && COLORS[ shape.color ] ) { | |
ctx.fillStyle = COLORS[ shape.color ].alpha; | |
ctx.fill(); | |
} | |
ctx.restore(); | |
ctx.stroke(); | |
} | |
}, | |
setHandler: function () { | |
this.handler = new Handler( { | |
canvas: this.canvas, | |
shape: this, | |
x: this.x, | |
y: this.y, | |
width: this.width, | |
height: this.height | |
} ); | |
}, | |
getFontStyle: function () { | |
return "bold " + this.fontSize + "px sans-serif"; | |
}, | |
getSizeByText: function () { | |
// set size by text | |
var ctx = this.canvas.context; | |
ctx.font = this.getFontStyle(); | |
var lines = this.text.split( /\n/ ); | |
var lineWidth = 0; | |
for ( var i = 0; i < lines.length; i++ ) { | |
lineWidth = ( ctx.measureText( lines[ i ] ) ).width; | |
if ( lineWidth > this.textBoxWidth ) { | |
this.textBoxWidth = lineWidth; | |
} | |
} | |
this.textBoxHeight = this.fontSize; | |
for ( var i = 1; i < lines.length; i++ ) { | |
this.textBoxHeight += this.fontSize * this.lineHeight | |
} | |
// margin-top,bottom: fontSize; margin-left,right: fontSize * 2 | |
return { | |
width: Math.round( this.textBoxWidth + FONTSIZE * 4 ), | |
height: Math.round( this.textBoxHeight + FONTSIZE * 2 ) | |
}; | |
}, | |
draw: function ( x, y ) { | |
if ( x ) { | |
this.x = x; | |
} | |
if ( y ) { | |
this.y = y; | |
} | |
this.drawers[ this.drawer ]( this ); | |
this.writeText(); | |
if ( ! this.handler ) { | |
this.setHandler(); | |
} | |
}, | |
writeText: function () { | |
if ( this.text ) { | |
var ctx = this.canvas.context; | |
ctx.font = this.getFontStyle(); | |
ctx.fillStyle = "rgb( 0, 0, 0 )"; | |
var lines = this.text.split( /\n/ ); | |
var fontBaseLineRatio = 0.8; | |
// margin-top,bottom: fontSize; margin-left,right: fontSize * 2 | |
var x = this.x; | |
var y = this.y + FONTSIZE * fontBaseLineRatio; | |
var maxWidth = this.width; | |
if ( this.textBoxWidth < this.width ) { | |
x = this.x + ( this.width - this.textBoxWidth ) / 2; | |
} | |
if ( this.textBoxHeight < this.height ) { | |
y = this.y + this.fontSize * fontBaseLineRatio + ( this.height - this.textBoxHeight ) / 2; | |
} | |
for ( var i = 0; i < lines.length; i++ ) { | |
ctx.fillText( lines[ i ], x, y + this.fontSize * this.lineHeight * i, maxWidth ); | |
} | |
} | |
}, | |
move: function ( x, y ) { | |
this.x = x; | |
this.y = y; | |
this.canvas.refresh(); | |
}, | |
refresh: function () { | |
this.draw( this.x, this.y ); | |
}, | |
resize: function ( width, height ) { | |
this.setSize( width, height ); | |
this.canvas.refresh(); | |
}, | |
setSize: function ( width, height ) { | |
this.width = width; | |
this.height = height; | |
if ( this.handler ) { | |
this.handler.setSize( this.width, this.height ); | |
} | |
}, | |
setConnector: function ( type ) { | |
this.connector = type; // top, right, bottom, left | |
this.canvas.refresh(); | |
}, | |
getConnector: function () { | |
var connectors = new Object(); | |
connectors[ "top" ] = { x: this.x + this.width / 2, y: this.y }, | |
connectors[ "right" ] = { x: this.x + this.width, y: this.y + this.height / 2 }, | |
connectors[ "bottom" ] = { x: this.x + this.width / 2, y: this.y + this.height }, | |
connectors[ "left" ] = { x: this.x, y: this.y + this.height / 2 } | |
if ( this.connector == "auto" ) { | |
return [ | |
connectors[ "top" ], connectors[ "right" ], connectors[ "bottom" ], connectors[ "left" ] | |
]; | |
} | |
else { | |
return [ connectors[ this.connector ] ]; | |
} | |
}, | |
changeColor: function ( color ) { | |
this.color = color; | |
this.canvas.refresh(); | |
}, | |
changeDrawer: function ( drawer ) { | |
if ( drawer == "circle" ) { | |
this.squarize(); | |
} | |
if ( this.drawer == "circle" ) { | |
var sizeByText = this.getSizeByText(); | |
this.setSize( sizeByText.width, sizeByText.height ); | |
} | |
this.drawer = drawer; | |
this.canvas.refresh(); | |
}, | |
transform: function () { | |
this.transformer.open( this ); | |
}, | |
squarize: function () { | |
if ( this.width != this.height ) { | |
if ( this.width < this.height ) { | |
this.setSize( this.height, this.height ); | |
} | |
else { | |
this.setSize( this.width, this.width ); | |
} | |
} | |
} | |
}; | |
/** | |
Connection Class | |
*/ | |
var Connection = function ( args ) { | |
this.canvas = args.canvas; | |
this.canvas.appendConnection( this ); | |
this.a = args.a; | |
this.b = args.b; | |
this.drawer = this.drawers[ args.drawer ]; | |
this.text = args.text; | |
if ( this.text ) { | |
var lines = this.text.split( /\n/ ); | |
var ctx = this.canvas.context; | |
this.textBoxHeight = this.fontSize; | |
for ( var i = 1; i < lines.length; i++ ) { | |
this.textBoxHeight += this.fontSize * this.lineHeight | |
} | |
this.textBoxWidth = 0; | |
var lineWidth = 0; | |
for ( var i = 0; i < lines.length; i++ ) { | |
lineWidth = ( ctx.measureText( lines[ i ] ) ).width; | |
if ( lineWidth > this.textBoxWidth ) { | |
this.textBoxWidth = lineWidth; | |
} | |
} | |
} | |
this.arrow = args.arrow; | |
} | |
Connection.prototype = { | |
canvas: null, | |
a: null, | |
b: null, | |
start: null, | |
end: null, | |
text: "", | |
fontSize: FONTSIZE, | |
lineHeight: LINEHEIGHT, | |
textBoxWidth: 0, | |
textBoxHeight: 0, | |
arrow: false, | |
drawer: null, | |
drawers: { | |
line: function ( connection ) { | |
var ctx = connection.canvas.context; | |
var start = connection.start; | |
var end = connection.end; | |
ctx.strokeStyle = COLORS[ "gray" ].color; | |
ctx.beginPath(); | |
ctx.moveTo( start.x, start.y ); | |
ctx.lineTo( end.x, end.y ); | |
ctx.stroke(); | |
if ( connection.arrow ) { | |
// make arrow http://www.dbp-consulting.com/tutorials/canvas/CanvasArrow.html | |
// arrow param | |
var d = 15; | |
var angle = 100; | |
// calculate the angle of the line | |
var lineangle = Math.atan2( end.y - start.y, end.x - start.x ); | |
// h is the line length of a side of the arrow head | |
var h = Math.abs( d / Math.cos( angle ) ); | |
// get arrow top serif | |
var angle1 = lineangle + Math.PI + angle; | |
var topx = end.x + Math.cos( angle1 ) * h; | |
var topy = end.y + Math.sin( angle1 ) * h; | |
// get arrow bottom serif | |
var angle2 = lineangle + Math.PI - angle; | |
var botx = end.x + Math.cos( angle2 ) * h; | |
var boty = end.y + Math.sin( angle2 ) * h; | |
// draw arrow | |
ctx.beginPath(); | |
ctx.moveTo( end.x, end.y ); | |
ctx.lineTo( topx, topy ); | |
ctx.lineTo( botx, boty ); | |
ctx.closePath(); | |
ctx.fillStyle = COLORS[ "black" ].alpha; | |
ctx.fill(); | |
} | |
} | |
}, | |
getFontStyle: function () { | |
return this.fontSize + "px sans-serif"; | |
}, | |
getDistance: function ( pos1, pos2 ) { | |
return Math.sqrt( Math.pow( pos1.x - pos2.x, 2 ) + Math.pow( pos1.y - pos2.y, 2 ) ) | |
}, | |
sortConnections: function ( con1, con2 ) { | |
return con1.distance - con2.distance; | |
}, | |
draw: function () { | |
var connections = new Array(); | |
var con1 = this.a.getConnector(); | |
var con2 = this.b.getConnector(); | |
for ( var i = 0; i < con1.length; i++ ) { | |
for ( var j = 0; j < con2.length; j++ ) { | |
connections.push( { | |
con1: con1[ i ], | |
con2: con2[ j ], | |
distance: this.getDistance( con1[ i ], con2[ j ] ) | |
} ); | |
} | |
} | |
connections.sort( this.sortConnections ); | |
this.start = connections[ 0 ].con1; | |
this.end = connections[ 0 ].con2; | |
this.drawer( this ); | |
this.writeText(); | |
}, | |
writeText: function () { | |
if ( this.text ) { | |
var ctx = this.canvas.context; | |
ctx.font = this.getFontStyle(); | |
ctx.fillStyle = COLORS[ "black" ].color; | |
var lines = this.text.split( /\n/ ); | |
var x = this.start.x < this.end.x ? this.start.x : this.end.x; | |
var y = this.start.y < this.end.y ? this.start.y : this.end.y; | |
var width = Math.abs( this.start.x - this.end.x ); | |
var height = Math.abs( this.start.y - this.end.y ); | |
var center = { | |
x: x + width / 2, | |
y: y + height / 2 | |
} | |
var x = center.x - this.textBoxWidth / 2; | |
var y = center.y; | |
if ( this.textBoxHeight < height ) { | |
y = center.y + this.fontSize - this.textBoxHeight / 2; | |
} | |
for ( var i = 0; i < lines.length; i++ ) { | |
ctx.fillText( lines[ i ], x, y + this.fontSize * this.lineHeight * i ); | |
} | |
} | |
}, | |
refresh: function () { | |
this.draw(); | |
} | |
}; | |
/** | |
Button Class | |
*/ | |
var ImagifyButton = function ( args ) { | |
this.element = args.element; | |
this.canvas = args.canvas; | |
var button = this; | |
this.element.addEventListener( POINTING.down, function () { | |
button.element.style.backgroundColor = "#eee"; | |
}, false ); | |
this.element.addEventListener( POINTING.up, function () { | |
button.element.style.backgroundColor = "#fff"; | |
button.execute(); | |
}, false ); | |
} | |
ImagifyButton.prototype = { | |
element: null, | |
canvas: null, | |
canImagify: true, | |
execute: function () { | |
if ( this.canImagify ) { | |
this.canvas.imagify(); | |
this.element.innerHTML = "Edit" | |
this.canImagify = false; | |
} | |
else { | |
this.canvas.unimagify(); | |
this.element.innerHTML = "Convert to PNG" | |
this.canImagify = true; | |
} | |
} | |
} | |
/** | |
Transformer Class | |
*/ | |
var Transformer = function ( args ) { | |
this.container = args.container; | |
var transformer = this; | |
// set form | |
var colors = this.container.querySelectorAll( ".colors li" ); | |
for ( var i = 0; i < colors.length; i++ ) { | |
colors[ i ].style.backgroundColor = COLORS[ colors[ i ].getAttribute( "class" ) ].color; | |
} | |
var settings = this.container.querySelectorAll( ".setting" ); | |
( function () { | |
for ( var i = 0; i < settings.length; i++ ) { ( function () { | |
var section = settings[ i ].querySelector( "ul" ); | |
if ( section ) { | |
var options = settings[ i ].querySelectorAll( "li" ); | |
for ( var j = 0; j < options.length; j++ ) { ( function () { | |
var option = options[ j ]; | |
option.addEventListener( POINTING.down, function () { | |
transformer.pushingButton = true; | |
} ); | |
option.addEventListener( POINTING.move, function () { | |
transformer.pushingButton = false; | |
} ); | |
option.addEventListener( POINTING.up, function () { | |
if ( transformer.pushingButton ) { | |
transformer.select( section, options, option ); | |
} | |
} ); | |
} )() } | |
} | |
} )() } | |
} )(); | |
var inputs = this.container.querySelectorAll( "input" ); | |
( function () { | |
for ( var i = 0; i < inputs.length; i++ ) { ( function () { | |
var input = inputs[ i ]; | |
input.addEventListener( "keyup", function () { | |
transformer.input( inputs, input ); | |
} ); | |
} )() } | |
} )(); | |
var button = this.container.querySelector( ".button" ); | |
button.addEventListener( POINTING.down, function () { | |
button.style.backgroundColor = "#eee"; | |
transformer.pushingButton = true; | |
}, false ); | |
button.addEventListener( POINTING.up, function () { | |
if ( transformer.pushingButton ) { | |
button.style.backgroundColor = "transparent"; | |
transformer.pushingButton = false; | |
transformer.close(); | |
} | |
}, false ); | |
} | |
Transformer.prototype = { | |
container: null, | |
shape: null, | |
pushingButton: false, | |
open: function ( shape ) { | |
this.shape = shape; | |
this.initialize(); | |
this.setColor(); | |
this.setShape(); | |
this.setSize(); | |
this.setConnector(); | |
this.container.style.display = "block"; | |
var transformer = this; | |
setTimeout( function () { | |
transformer.container.style.opacity = "1"; | |
}, 100 ); | |
}, | |
initialize: function () { | |
var selecteds = this.container.querySelectorAll( "li.selected" ); | |
for ( var i = 0; i < selecteds.length; i++ ) { | |
selecteds[ i ].setAttribute( "class", selecteds[ i ].getAttribute( "class" ).replace( / selected/, "" ) ); | |
} | |
}, | |
setColor: function () { | |
// set color | |
var option = this.container.querySelector( "li." + this.shape.color ); | |
if ( option ) { | |
option.setAttribute( "class", option.getAttribute( "class" ) + " selected" ); | |
} | |
}, | |
setShape: function () { | |
// set shape | |
var option = this.container.querySelector( "li." + this.shape.drawer ); | |
if ( option ) { | |
option.setAttribute( "class", option.getAttribute( "class" ) + " selected" ); | |
} | |
}, | |
setSize: function () { | |
// set size | |
this.container.querySelector( "input.width" ).value = this.shape.width; | |
this.container.querySelector( "input.height" ).value = this.shape.height; | |
}, | |
setConnector: function () { | |
// set connector | |
var option = this.container.querySelector( "li." + this.shape.connector ); | |
if ( option ) { | |
option.setAttribute( "class", option.getAttribute( "class" ) + " selected" ); | |
} | |
}, | |
select: function ( section, options, selected ) { | |
for ( var i = 0; i < options.length; i++ ) { | |
options[ i ].setAttribute( "class", options[ i ].getAttribute( "class" ).replace( / selected/, "" ) ); | |
} | |
selected.setAttribute( "class", selected.getAttribute( "class" ) + " selected" ); | |
var value = selected.getAttribute( "class" ).replace( / selected/, "" ); | |
if ( section.getAttribute( "class" ) == "colors" ) { | |
this.shape.changeColor( value ); | |
} | |
if ( section.getAttribute( "class" ) == "shapes" ) { | |
this.shape.changeDrawer( value ); | |
this.setSize(); | |
} | |
if ( section.getAttribute( "class" ) == "connectors" ) { | |
this.shape.setConnector( value ); | |
} | |
}, | |
input: function ( inputs, input ) { | |
var values = new Object(); | |
for ( var i = 0; i < inputs.length; i++ ) { | |
values[ inputs[ i ].getAttribute( "class" ) ] = Number( inputs[ i ].value ); | |
} | |
if ( values.width && values.height ) { | |
if ( this.shape.drawer == "circle" ) { | |
if ( inputs[ 0 ] != input ) { | |
inputs[ 0 ].value = input.value; | |
} | |
if ( inputs[ 1 ] != input ) { | |
inputs[ 1 ].value = input.value; | |
} | |
values.width = Number( input.value ); | |
values.height = Number( input.value ); | |
} | |
this.shape.resize( values.width, values.height ); | |
} | |
}, | |
close: function () { | |
this.container.style.display = "none"; | |
this.container.style.opacity = "0"; | |
} | |
}; | |
/** | |
Parser Class | |
*/ | |
var Parser = function () { | |
} | |
Parser.prototype = { | |
text: "", | |
parse: function ( text ) { | |
this.text = text; | |
var tokens = new Array(); | |
var splits = this.text.split( /\n\n+/ ); | |
for ( var i = 0; i < splits.length; i++ ) { | |
if ( splits[ i ].match( /^\s.*$/ ) ) { | |
continue; | |
} | |
tokens.push( splits[ i ].replace( /\n$/, "" ) ); | |
} | |
var connections = new Array(); | |
for ( var i = 0; i < tokens.length; i++ ) { ( function () { | |
if ( tokens[ i ].match( /^([0-9]+)(->*)([0-9]+)\.([\s\S]*)/ ) ) { | |
var from = RegExp.$1; | |
var to = RegExp.$3; | |
var arrow = false; | |
var text = RegExp.$4; | |
if ( RegExp.$2.match( />/ ) ) { | |
arrow =true | |
} | |
connections.push( { | |
from: from, | |
to: to, | |
arrow: arrow, | |
text: text | |
} ); | |
delete tokens[ i ]; | |
} | |
} )() } | |
var styles = new Array(); | |
for ( var i = 0; i < tokens.length; i++ ) { ( function () { | |
if ( tokens[ i ] && tokens[ i ].match( /^[0-9]+\.style\.\((.+)\)$/ ) ) { | |
var style = RegExp.$1.split( /,/ ); | |
styles.push( style ); | |
delete tokens[ i ]; | |
} | |
} )() } | |
var shapes = new Array(); | |
for ( var i = 0; i < tokens.length; i++ ) { ( function () { | |
if ( tokens[ i ] && tokens[ i ].match( /^[0-9]+\.([\s\S]+)$/ ) ) { | |
var shape = RegExp.$1; | |
shapes.push( shape.replace( /^\s+/, "" ).replace( /\s+$/, "" ) ); | |
} | |
} )() } | |
var parsed = { | |
shapes: shapes, | |
connections: connections, | |
styles: styles | |
}; | |
return parsed; | |
}, | |
serialize: function ( canvas ) { | |
var serialized = ""; | |
// shapes | |
var shapes = canvas.shapes; | |
var shapeTexts = new Array(); | |
for ( var i = 0; i < shapes.length; i++ ) { | |
shapeTexts.push( Number( i ) + 1 + "." + shapes[ i ].text ); | |
} | |
serialized = shapeTexts.join( "\n\n" ); | |
// connections | |
var connections = canvas.connections; | |
var connectionTexts = new Array(); | |
if ( connections.length ) { | |
serialized += "\n\n"; | |
} | |
for ( var i = 0; i < connections.length; i++ ) { | |
var startIndex = canvas.getIndexOfShape( connections[ i ].a ) + 1; | |
var endIndex = canvas.getIndexOfShape( connections[ i ].b ) + 1; | |
var line = "-"; | |
if ( connections[ i ].arrow ) { | |
line = "->"; | |
} | |
connectionTexts.push( startIndex + line + endIndex + "." + connections[ i ].text ); | |
} | |
serialized += connectionTexts.join( "\n\n" ); | |
// styles | |
serialized += "\n\n"; | |
var styleTexts = new Array(); | |
for ( var i = 0; i < shapes.length; i++ ) { | |
var shape = shapes[ i ]; | |
styleTexts.push( | |
Number( i + 1 ) + ".style.(" | |
+ shape.x + "," | |
+ shape.y + "," | |
+ shape.width + "," | |
+ shape.height + "," | |
+ shape.drawer + "," | |
+ shape.color + "," | |
+ shape.connector | |
+ ")" | |
); | |
} | |
serialized += styleTexts.join( "\n\n" ); | |
return serialized; | |
} | |
}; | |
/** | |
Main | |
*/ | |
var parser = new Parser(); | |
var canvas; | |
var initialize = function () { | |
try { | |
// textwell | |
T.closelets( [ | |
{ | |
title: 'Replace Diagram Data', | |
fn: function( arg ) { T( 'replaceCurrent', { text: parser.serialize( canvas ) } ) }, | |
arg: { | |
parser: parser, | |
canvas: canvas | |
} | |
} | |
] ); | |
input = parser.parse( T.current ); | |
} catch ( e ) { | |
// for dev | |
var input = parser.parse( document.querySelector( "textarea" ).value ); | |
} | |
// Make Canvas | |
canvas = new ResponsiveCanvas( { | |
container: document.querySelector( ".container" ), | |
} ); | |
// Make Imagify Button | |
var button = new ImagifyButton( { | |
element: document.querySelector( ".button-imagify" ), | |
canvas: canvas | |
} ); | |
// Make Transformer | |
var transformer = new Transformer( { | |
container: document.querySelector( ".transformer" ) | |
} ); | |
// Make Shapes and Connections | |
if ( input.shapes.length ) { | |
for ( var i = 0; i < input.shapes.length; i++ ) { | |
( new Shape( { | |
canvas: canvas, | |
transformer: transformer, | |
drawer: "roundedRect", | |
color: "blue", | |
text: input.shapes[ i ] | |
} ) ).draw(); | |
} | |
} | |
if ( input.connections.length ) { | |
for ( var i = 0; i < input.connections.length; i++ ) { | |
( new Connection( { | |
canvas: canvas, | |
transformer: transformer, | |
drawer: "line", | |
a: canvas.shapes[ Number( input.connections[ i ].from ) - 1 ], | |
b: canvas.shapes[ Number( input.connections[ i ].to ) - 1 ], | |
arrow: input.connections[ i ].arrow, | |
text: input.connections[ i ].text | |
} ) ).draw(); | |
} | |
} | |
// Set Style to Shapes | |
if ( input.styles.length ) { | |
var maxWidth = canvas.width; | |
var maxHeight = canvas.height; | |
for ( var i = 0; i < input.styles.length; i++ ) { ( function () { | |
var shape = canvas.shapes[ i ]; | |
var style = input.styles[ i ]; | |
// x, y | |
shape.handler.move( Number( style[ 0 ] ), Number( style[ 1 ] ) ); | |
// width, height | |
shape.resize( Number( style[ 2 ] ), Number( style[ 3 ] ) ); | |
// shape | |
shape.changeDrawer( style[ 4 ] ); | |
// color | |
shape.changeColor( style[ 5 ] ); | |
// connector | |
shape.setConnector( style[ 6 ] ); | |
var xPlusWidth = Number( style[ 0 ] ) + Number( style[ 2 ] ); | |
if ( xPlusWidth > maxWidth ) { | |
maxWidth = xPlusWidth; | |
} | |
var yPlusHeight = Number( style[ 1 ] ) + Number( style[ 3 ] ); | |
if ( yPlusHeight > maxHeight ) { | |
maxHeight = yPlusHeight; | |
} | |
} )() } | |
if ( maxWidth != canvas.width ) { | |
maxWidth += 20; | |
} | |
if ( maxHeight != canvas.height ) { | |
maxHeight += 20; | |
} | |
canvas.resize( maxWidth, maxHeight ); | |
} | |
} | |
// User Command | |
var serialize = function () { | |
console.log( parser.serialize( canvas ) ); | |
} | |
// Onload | |
window.onload = function () { | |
if ( window.webkit ) { | |
window.webkit.messageHandlers.initT.postMessage( "initialize" ); | |
} | |
else { | |
// dev | |
initialize(); | |
} | |
} | |
</script> | |
<!-- /* file:dialog.html */ --> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment