initial commit
This commit is contained in:
commit
7b26ee23c9
|
@ -0,0 +1,3 @@
|
||||||
|
/bower_components/
|
||||||
|
/node_modules/
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
dist/
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
module.exports = (grunt) ->
|
||||||
|
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks)
|
||||||
|
|
||||||
|
grunt.initConfig {
|
||||||
|
watch:
|
||||||
|
coffee:
|
||||||
|
files: ['src/**/*.coffee']
|
||||||
|
tasks: ['browserify', 'coffee', 'uglify']
|
||||||
|
browserify:
|
||||||
|
dist:
|
||||||
|
files:
|
||||||
|
'dist/bismarck.js': 'src/bismarck.coffee'
|
||||||
|
options:
|
||||||
|
transform: ['coffeeify']
|
||||||
|
browserifyOptions:
|
||||||
|
debug: true
|
||||||
|
extensions: ['.coffee', '.litcoffee']
|
||||||
|
coffee:
|
||||||
|
lib:
|
||||||
|
expand: true
|
||||||
|
cwd: 'src'
|
||||||
|
src: ['**/*.coffee']
|
||||||
|
dest: 'lib'
|
||||||
|
ext: '.js'
|
||||||
|
options:
|
||||||
|
bare: true
|
||||||
|
karma:
|
||||||
|
unit:
|
||||||
|
configFile: 'karma.conf.js'
|
||||||
|
autoWatch: true
|
||||||
|
uglify:
|
||||||
|
options:
|
||||||
|
mangle: false
|
||||||
|
dist:
|
||||||
|
files:
|
||||||
|
'dist/bismarck.min.js': 'dist/bismarck.js'
|
||||||
|
concurrent:
|
||||||
|
default:
|
||||||
|
tasks: ['karma', 'watch']
|
||||||
|
options:
|
||||||
|
logConcurrentOutput: true
|
||||||
|
limit: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
grunt.registerTask 'default', ['browserify', 'uglify', 'coffee', 'concurrent:default']
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "bismarck",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "dist/bismarck.js",
|
||||||
|
"ignore": [
|
||||||
|
"**/.*",
|
||||||
|
"node_modules",
|
||||||
|
"bower_components",
|
||||||
|
"test",
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"snap.svg": "~0.3.0",
|
||||||
|
"jquery": "~2.1.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Karma configuration
|
||||||
|
// Generated on Tue Sep 23 2014 07:24:39 GMT-0400 (EDT)
|
||||||
|
|
||||||
|
module.exports = function(config) {
|
||||||
|
config.set({
|
||||||
|
|
||||||
|
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||||
|
basePath: '',
|
||||||
|
|
||||||
|
preprocessors: {
|
||||||
|
'test/index.coffee': 'browserify',
|
||||||
|
'src/coffee/app.coffee': 'browserify',
|
||||||
|
},
|
||||||
|
|
||||||
|
// frameworks to use
|
||||||
|
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||||
|
frameworks: ['browserify', 'jasmine'],
|
||||||
|
|
||||||
|
// list of files / patterns to load in the browser
|
||||||
|
files: [
|
||||||
|
'bower_components/jquery/dist/jquery.js',
|
||||||
|
'bower_components/snap.svg/dist/snap.svg.js',
|
||||||
|
'test/index.coffee'
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
// list of files to exclude
|
||||||
|
exclude: [
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
// preprocess matching files before serving them to the browser
|
||||||
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
|
browserify: {
|
||||||
|
extensions: ['.coffee', '.litcoffee'],
|
||||||
|
transform: ['coffeeify'],
|
||||||
|
watch: true,
|
||||||
|
debug: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// test results reporter to use
|
||||||
|
// possible values: 'dots', 'progress'
|
||||||
|
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||||
|
reporters: ['progress'],
|
||||||
|
|
||||||
|
|
||||||
|
// web server port
|
||||||
|
port: 9877,
|
||||||
|
|
||||||
|
|
||||||
|
// enable / disable colors in the output (reporters and logs)
|
||||||
|
colors: true,
|
||||||
|
|
||||||
|
|
||||||
|
// level of logging
|
||||||
|
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
|
||||||
|
|
||||||
|
// enable / disable watching file and executing tests whenever any file changes
|
||||||
|
autoWatch: true,
|
||||||
|
|
||||||
|
|
||||||
|
// start these browsers
|
||||||
|
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||||
|
browsers: ['PhantomJS'],
|
||||||
|
|
||||||
|
|
||||||
|
// Continuous Integration mode
|
||||||
|
// if true, Karma captures browsers, runs the tests and exits
|
||||||
|
singleRun: false
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "bismarck",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "lib/bismarck.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"browserify": "^6.1.0",
|
||||||
|
"coffeeify": "^0.7.0",
|
||||||
|
"grunt": "^0.4.5",
|
||||||
|
"grunt-browserify": "^3.1.0",
|
||||||
|
"grunt-concurrent": "^1.0.0",
|
||||||
|
"grunt-contrib-coffee": "^0.11.1",
|
||||||
|
"grunt-contrib-concat": "^0.5.0",
|
||||||
|
"grunt-contrib-uglify": "^0.6.0",
|
||||||
|
"grunt-contrib-watch": "^0.6.1",
|
||||||
|
"grunt-karma": "^0.9.0",
|
||||||
|
"karma": "^0.12.23",
|
||||||
|
"karma-bro": "^0.8.0",
|
||||||
|
"karma-chrome-launcher": "^0.1.4",
|
||||||
|
"karma-coffee-preprocessor": "^0.2.1",
|
||||||
|
"karma-firefox-launcher": "^0.1.3",
|
||||||
|
"karma-jasmine": "~0.2.2",
|
||||||
|
"karma-phantomjs-launcher": "^0.1.4",
|
||||||
|
"matchdep": "^0.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"q": "^1.0.1",
|
||||||
|
"underscore": "^1.7.0",
|
||||||
|
"xml2js": "^0.4.4"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
Canvas = require('./bismarck/canvas')
|
||||||
|
|
||||||
|
Bismarck = (element, options) ->
|
||||||
|
if typeof element == 'string'
|
||||||
|
new Canvas(document.getElementById(element), options)
|
||||||
|
else
|
||||||
|
new Canvas(element, options)
|
||||||
|
|
||||||
|
Bismarck.debug = false
|
||||||
|
|
||||||
|
module.exports = Bismarck
|
||||||
|
|
||||||
|
if window?
|
||||||
|
window.Bismarck = Bismarck
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
Animate =
|
||||||
|
_animationsEnabled: true
|
||||||
|
currentTime: -> +new Date
|
||||||
|
moveFromTo: (object, from, to, distance) ->
|
||||||
|
dx = to.x - from.x
|
||||||
|
dy = to.y - from.y
|
||||||
|
x = from.x + (dx * distance)
|
||||||
|
y = from.y + (dy * distance)
|
||||||
|
|
||||||
|
object.moveTo(x, y)
|
||||||
|
|
||||||
|
class Animate.Synchronizer
|
||||||
|
constructor: ->
|
||||||
|
@animations = []
|
||||||
|
@recalcs = []
|
||||||
|
@removers = []
|
||||||
|
@isRunning = false
|
||||||
|
@fpsLimit = 30
|
||||||
|
|
||||||
|
addAnimation: (recalc, animation) ->
|
||||||
|
@animations.push(animation)
|
||||||
|
@recalcs.push(recalc)
|
||||||
|
@removers.push => @removeAnimation(animation)
|
||||||
|
@run()
|
||||||
|
|
||||||
|
=> @removeAnimation(animation)
|
||||||
|
|
||||||
|
removeAnimation: (animation) ->
|
||||||
|
index = @animations.indexOf(animation)
|
||||||
|
@animations.splice(index, 1)
|
||||||
|
@recalcs.splice(index, 1)
|
||||||
|
@removers.splice(index, 1)
|
||||||
|
if @animations.length == 0
|
||||||
|
@isRunning = false
|
||||||
|
|
||||||
|
run: =>
|
||||||
|
if not @isRunning
|
||||||
|
@isRunning = true
|
||||||
|
|
||||||
|
window.requestAnimationFrame =>
|
||||||
|
if @animations.length > 0
|
||||||
|
startTime = Animate.currentTime()
|
||||||
|
Animate._animationsDisabled = false
|
||||||
|
for i in [0..@animations.length - 1]
|
||||||
|
if @animations[i]
|
||||||
|
@animations[i](startTime, @removers[i])
|
||||||
|
Animate._animationsDisabled = true
|
||||||
|
r.recalc() for r in @recalcs
|
||||||
|
endTime = Animate.currentTime()
|
||||||
|
if @isRunning
|
||||||
|
calc = Math.max(0, (1 / @fpsLimit - ((endTime - startTime) / 1000)) * 1000)
|
||||||
|
setTimeout(@run, calc)
|
||||||
|
else
|
||||||
|
@isRunning = false
|
||||||
|
|
||||||
|
Animate.synchronizer = new Animate.Synchronizer()
|
||||||
|
|
||||||
|
Animate.once = (recalc, options, code) ->
|
||||||
|
startTime = Animate.currentTime()
|
||||||
|
endTime = startTime + options.duration
|
||||||
|
|
||||||
|
Animate.synchronizer.addAnimation recalc, (currentTime, stop) ->
|
||||||
|
index = Math.min(1, (currentTime - startTime) / options.duration)
|
||||||
|
result = code(index)
|
||||||
|
|
||||||
|
stop() if not result or currentTime >= endTime
|
||||||
|
|
||||||
|
Animate.loop = (recalc, options, code) ->
|
||||||
|
startTime = Animate.currentTime()
|
||||||
|
|
||||||
|
Animate.synchronizer.addAnimation recalc, (currentTime, stop) ->
|
||||||
|
index = Math.min(1, (currentTime - startTime) % options.duration / options.duration)
|
||||||
|
result = code(index)
|
||||||
|
|
||||||
|
stop() if not result
|
||||||
|
|
||||||
|
module.exports = Animate
|
|
@ -0,0 +1,32 @@
|
||||||
|
Drawable = require('./drawable')
|
||||||
|
Resources = require('./resources')
|
||||||
|
Layout = require('./layout')
|
||||||
|
|
||||||
|
class Canvas
|
||||||
|
constructor: (@element, options = {}) ->
|
||||||
|
@snapElement = Snap(@element)
|
||||||
|
if not @snapElement.attr('bismarck')
|
||||||
|
if options.id
|
||||||
|
@snapElement.attr(id: options.id, bismarck: 'canvas')
|
||||||
|
|
||||||
|
fromLayout: (xml, callback) ->
|
||||||
|
Layout(xml, this, callback)
|
||||||
|
|
||||||
|
create: (options) ->
|
||||||
|
drawable = new Drawable(this, options)
|
||||||
|
drawable.attachTo(@snapElement)
|
||||||
|
drawable
|
||||||
|
|
||||||
|
find: (id) ->
|
||||||
|
drawable = new Drawable(this)
|
||||||
|
drawable.useExisting(@snapElement.select("##{id}"))
|
||||||
|
drawable
|
||||||
|
|
||||||
|
resources: ->
|
||||||
|
return @_resources if @_resources
|
||||||
|
|
||||||
|
@_resources = new Resources(this)
|
||||||
|
@_resources.attachTo(@snapElement)
|
||||||
|
@_resources
|
||||||
|
|
||||||
|
module.exports = Canvas
|
|
@ -0,0 +1,327 @@
|
||||||
|
Animate = require('./animate')
|
||||||
|
|
||||||
|
class Drawable
|
||||||
|
constructor: (@canvas, options = {}) ->
|
||||||
|
defaults =
|
||||||
|
centerPoint: 'northwest'
|
||||||
|
position: {x: 0, y: 0}
|
||||||
|
angle: 0
|
||||||
|
scale: 1
|
||||||
|
|
||||||
|
for own key, value of defaults
|
||||||
|
switch key
|
||||||
|
when 'centerPoint'
|
||||||
|
@setCenterPoint(value || defaults[key])
|
||||||
|
else
|
||||||
|
this[key] = value || defaults[key]
|
||||||
|
|
||||||
|
for own key, value of options
|
||||||
|
switch key
|
||||||
|
when 'centerPoint'
|
||||||
|
@setCenterPoint(value || defaults[key])
|
||||||
|
else
|
||||||
|
this[key] = value || defaults[key]
|
||||||
|
|
||||||
|
@_resources = @canvas.resources()
|
||||||
|
@_children = []
|
||||||
|
|
||||||
|
resources: -> @_resources
|
||||||
|
snapBBox: -> @_scale.getBBox(true)
|
||||||
|
|
||||||
|
create: (options) ->
|
||||||
|
drawable = new Drawable(@canvas, options)
|
||||||
|
drawable.attachTo(@_scale)
|
||||||
|
drawable.parentDrawable = this
|
||||||
|
@_children.push(drawable)
|
||||||
|
drawable
|
||||||
|
|
||||||
|
remove: ->
|
||||||
|
@_translateRotate.remove()
|
||||||
|
|
||||||
|
attachTo: (snapElement) ->
|
||||||
|
@_translateRotate = snapElement.group().addClass('-translate')
|
||||||
|
@_scale = @_translateRotate.group().addClass('-scale')
|
||||||
|
|
||||||
|
@_translateRotate.attr(id: @id, bismarck: 'drawable')
|
||||||
|
@_currentBBox = @snapBBox()
|
||||||
|
@snapElement = @_translateRotate
|
||||||
|
|
||||||
|
forceSize: (w, h) ->
|
||||||
|
@_forcedSize.remove() if @_forcedSize
|
||||||
|
|
||||||
|
@_forcedDims = { w: w, h: h }
|
||||||
|
@_forcedSize = @_translateRotate.rect(0, 0, w, h).attr(fill: 'rgba(0,0,0,0)')
|
||||||
|
|
||||||
|
me = @_forcedSize.node
|
||||||
|
@_translateRotate.node.insertBefore(me, @_translateRotate.node.childNodes[0])
|
||||||
|
|
||||||
|
useExisting: (@_translateRotate) ->
|
||||||
|
throw "Not a Bismarck element!" if not @_translateRotate.attr('bismarck')
|
||||||
|
childNodes = @_translateRotate.node.childNodes
|
||||||
|
|
||||||
|
@_scale = Snap(childNodes[0])
|
||||||
|
if childNodes[1]
|
||||||
|
@centerPointCross = Snap(childNodes[1])
|
||||||
|
|
||||||
|
currentNode = @_translateRotate.node.parentNode
|
||||||
|
while currentNode
|
||||||
|
switch currentNode.getAttribute('bismarck')
|
||||||
|
when 'drawable'
|
||||||
|
@parentDrawable = new Drawable(@canvas, {})
|
||||||
|
@parentDrawable.useExisting(Snap(currentNode))
|
||||||
|
currentNode = null
|
||||||
|
when 'canvas'
|
||||||
|
currentNode = null
|
||||||
|
else
|
||||||
|
# stop!
|
||||||
|
if currentNode.nodeName == 'svg'
|
||||||
|
currentNode = null
|
||||||
|
else
|
||||||
|
currentNode = currentNode.parentNode
|
||||||
|
|
||||||
|
angular.extend(this, @_translateRotate.data('settings'))
|
||||||
|
|
||||||
|
showCenterPoint: ->
|
||||||
|
@centerPointCross = @_translateRotate.group()
|
||||||
|
@centerPointCross.line(0, -5, 0, 5).attr(stroke: 'black')
|
||||||
|
@centerPointCross.line(-5, 0, 5, 0).attr(stroke: 'black')
|
||||||
|
|
||||||
|
@recalc()
|
||||||
|
|
||||||
|
moveTo: (x, y) ->
|
||||||
|
@position = {x: x, y: y}
|
||||||
|
@recalc()
|
||||||
|
|
||||||
|
rotateTo: (angle) ->
|
||||||
|
@angle = angle
|
||||||
|
@recalc()
|
||||||
|
|
||||||
|
scaleTo: (scale) ->
|
||||||
|
@scale = scale
|
||||||
|
@recalc()
|
||||||
|
|
||||||
|
alignTo: (object, how = 'center', args...) ->
|
||||||
|
switch how
|
||||||
|
when 'center'
|
||||||
|
@moveTo(object.position.x, object.position.y, args...)
|
||||||
|
when 'horizontal'
|
||||||
|
@moveTo(@position.x, object.position.y, args...)
|
||||||
|
when 'vertical'
|
||||||
|
@moveTo(object.position.x, @position.y, args...)
|
||||||
|
|
||||||
|
centerInParent: ->
|
||||||
|
@_centerInParent = true
|
||||||
|
@setCenterPoint('center')
|
||||||
|
@recalc(true)
|
||||||
|
|
||||||
|
recalc: (down = false) ->
|
||||||
|
if Animate._animationsEnabled
|
||||||
|
if @_centerInParent
|
||||||
|
parent = @parentDrawable.getDimensions()
|
||||||
|
|
||||||
|
@position = { x: parent.w / 2, y: parent.h / 2}
|
||||||
|
|
||||||
|
if @oldScale != @scale
|
||||||
|
matrix = new Snap.Matrix()
|
||||||
|
matrix.scale(@scale)
|
||||||
|
@_scale.transform(matrix)
|
||||||
|
@oldScale = @scale
|
||||||
|
|
||||||
|
offset = @getOffset()
|
||||||
|
upperLeft = @upperLeft()
|
||||||
|
|
||||||
|
if @centerPointCross?
|
||||||
|
matrix = new Snap.Matrix()
|
||||||
|
matrix.translate(offset.x + upperLeft.x, offset.y + upperLeft.y)
|
||||||
|
@centerPointCross.transform(matrix)
|
||||||
|
|
||||||
|
matrix = new Snap.Matrix()
|
||||||
|
|
||||||
|
matrix.translate(@position.x, @position.y)
|
||||||
|
matrix.rotate(@angle, 0, 0)
|
||||||
|
|
||||||
|
if not @_forcedSize
|
||||||
|
matrix.translate(-offset.x - upperLeft.x, -offset.y - upperLeft.y)
|
||||||
|
|
||||||
|
@_translateRotate.transform(matrix)
|
||||||
|
|
||||||
|
if false
|
||||||
|
@_translateRotate.data('settings', {
|
||||||
|
centerPoint: @centerPoint
|
||||||
|
position: @position
|
||||||
|
angle: @angle
|
||||||
|
scale: @scale
|
||||||
|
_currentBBox: @_currentBBox
|
||||||
|
_centerInParent: @_centerInParent
|
||||||
|
_forcedDims: @_forcedDims
|
||||||
|
_children: @_children
|
||||||
|
})
|
||||||
|
|
||||||
|
if down
|
||||||
|
child.recalc(true) for child in @_children
|
||||||
|
else
|
||||||
|
@parentDrawable.recalc() if @parentDrawable?
|
||||||
|
|
||||||
|
moveForward: ->
|
||||||
|
me = @_translateRotate.node
|
||||||
|
parent = me.parentNode
|
||||||
|
|
||||||
|
l = parent.childNodes.length - 1
|
||||||
|
|
||||||
|
for i in [0..l]
|
||||||
|
if parent.childNodes[i] == me and i < l
|
||||||
|
parent.insertBefore(parent.childNodes[i + 1], me)
|
||||||
|
|
||||||
|
moveBackward: ->
|
||||||
|
me = @_translateRotate.node
|
||||||
|
parent = me.parentNode
|
||||||
|
|
||||||
|
for i in [0..parent.childNodes.length]
|
||||||
|
if parent.childNodes[i] == me and i > 0
|
||||||
|
parent.insertBefore(me, parent.childNodes[i - 1])
|
||||||
|
|
||||||
|
moveToFront: ->
|
||||||
|
me = @_translateRotate.node
|
||||||
|
me.parent.appendChild(me)
|
||||||
|
|
||||||
|
moveToBack: ->
|
||||||
|
me = @_translateRotate.node
|
||||||
|
me.parent.insertBefore(me, me.parent.childNodes[0])
|
||||||
|
|
||||||
|
on: (event, args...) ->
|
||||||
|
code = args.pop()
|
||||||
|
targetSelector = args.shift()
|
||||||
|
|
||||||
|
target = @_translateRotate
|
||||||
|
if targetSelector?
|
||||||
|
target = target.select(targetSelector)
|
||||||
|
|
||||||
|
target[event](code)
|
||||||
|
|
||||||
|
draw: (code) ->
|
||||||
|
code(@_scale)
|
||||||
|
@recalc()
|
||||||
|
|
||||||
|
@_currentBBox = @snapBBox()
|
||||||
|
|
||||||
|
scaleAndRotateCoords: (coords) ->
|
||||||
|
pairs = if coords.w? then ['w', 'h'] else ['x', 'y']
|
||||||
|
|
||||||
|
coords[pairs[0]] *= @scale
|
||||||
|
coords[pairs[1]] *= @scale
|
||||||
|
|
||||||
|
rad2ang = Math.PI / 180
|
||||||
|
|
||||||
|
radianAngle = rad2ang * @angle
|
||||||
|
cos = Math.cos(radianAngle)
|
||||||
|
sin = Math.sin(radianAngle)
|
||||||
|
|
||||||
|
newW = coords[pairs[0]] * cos + coords[pairs[1]] * sin
|
||||||
|
newH = coords[pairs[1]] * cos + coords[pairs[0]] * sin
|
||||||
|
|
||||||
|
coords[pairs[0]] = newW
|
||||||
|
coords[pairs[1]] = newh
|
||||||
|
|
||||||
|
coords
|
||||||
|
|
||||||
|
getOffset: ->
|
||||||
|
output = {}
|
||||||
|
|
||||||
|
source = if @_children.length > 0
|
||||||
|
@getDimensions()
|
||||||
|
else
|
||||||
|
@_currentBBox
|
||||||
|
|
||||||
|
for coordPart in @centerPoint
|
||||||
|
output[coordPart.which] = coordPart.convert(source[coordPart.dim])
|
||||||
|
|
||||||
|
output
|
||||||
|
|
||||||
|
upperLeft: ->
|
||||||
|
if @_children.length == 0
|
||||||
|
{ x: 0, y: 0 }
|
||||||
|
else
|
||||||
|
furthestUpperLeft = { x: null, y: null }
|
||||||
|
for child in @_children
|
||||||
|
offset = child.getOffset()
|
||||||
|
offset.x = -offset.x + child.position.x
|
||||||
|
offset.y = -offset.y + child.position.y
|
||||||
|
|
||||||
|
if !furthestUpperLeft.x
|
||||||
|
furthestUpperLeft.x = offset.x
|
||||||
|
furthestUpperLeft.y = offset.y
|
||||||
|
else
|
||||||
|
furthestUpperLeft.x = Math.min(furthestUpperLeft.x, offset.x)
|
||||||
|
furthestUpperLeft.y = Math.min(furthestUpperLeft.y, offset.y)
|
||||||
|
|
||||||
|
furthestUpperLeft
|
||||||
|
|
||||||
|
setCenterPoint: (args...) ->
|
||||||
|
if args.length == 1
|
||||||
|
args = switch args[0]
|
||||||
|
when 'center'
|
||||||
|
['50%', '50%']
|
||||||
|
when 'northwest'
|
||||||
|
[0,0]
|
||||||
|
|
||||||
|
@coordinates = {}
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{ index: 0, which: 'x', dim: 'w' },
|
||||||
|
{ index: 1, which: 'y', dim: 'h' }
|
||||||
|
]
|
||||||
|
|
||||||
|
coordMap = (datum) ->
|
||||||
|
datum.value = args[datum.index]
|
||||||
|
if datum.value.substr? and datum.value.substr(-1) == '%'
|
||||||
|
percent = Number(datum.value.slice(0, -1)) / 100.0
|
||||||
|
|
||||||
|
datum.convert = (value) -> value * percent
|
||||||
|
else
|
||||||
|
datum.convert = (value) -> datum.value
|
||||||
|
|
||||||
|
datum
|
||||||
|
|
||||||
|
@centerPoint = data.map(coordMap)
|
||||||
|
|
||||||
|
getDimensions: ->
|
||||||
|
if @_forcedDims
|
||||||
|
@_forcedDims
|
||||||
|
else
|
||||||
|
if @_children.length > 0
|
||||||
|
@getDimensionsWithChildren()
|
||||||
|
else
|
||||||
|
@_currentBBox
|
||||||
|
|
||||||
|
getDimensionsWithChildren: ->
|
||||||
|
bbox =
|
||||||
|
sx: null
|
||||||
|
sy: null
|
||||||
|
ex: null
|
||||||
|
ey: null
|
||||||
|
|
||||||
|
for child in @_children
|
||||||
|
childBBox = child.getDimensions()
|
||||||
|
childOffset = child.getOffset()
|
||||||
|
|
||||||
|
childBBox.x = child.position.x - childOffset.x
|
||||||
|
childBBox.y = child.position.y - childOffset.y
|
||||||
|
|
||||||
|
if bbox.sx == null
|
||||||
|
bbox.sx = childBBox.x
|
||||||
|
bbox.sy = childBBox.y
|
||||||
|
bbox.ex = childBBox.x + childBBox.w
|
||||||
|
bbox.ey = childBBox.y + childBBox.h
|
||||||
|
else
|
||||||
|
bbox.sx = Math.min(bbox.sx, childBBox.x)
|
||||||
|
bbox.sy = Math.min(bbox.sy, childBBox.y)
|
||||||
|
bbox.ex = Math.max(bbox.ex, childBBox.x + childBBox.w)
|
||||||
|
bbox.ey = Math.max(bbox.ey, childBBox.y + childBBox.h)
|
||||||
|
|
||||||
|
{ w: bbox.ex - bbox.sx, h: bbox.ey - bbox.sy }
|
||||||
|
|
||||||
|
append: (drawable) ->
|
||||||
|
@_translateRotate.append(drawable._scaleRotate)
|
||||||
|
|
||||||
|
module.exports = Drawable
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
xml2js = require('xml2js')
|
||||||
|
parseString = require('xml2js').parseString
|
||||||
|
_ = require('underscore')
|
||||||
|
|
||||||
|
svgObjs =
|
||||||
|
rect: (options, canvas) ->
|
||||||
|
rect = canvas.rect(options.x, options.y, options.width, options.height)
|
||||||
|
rect.attr(_.omit(options, 'x', 'y', 'width', 'height'))
|
||||||
|
|
||||||
|
rect
|
||||||
|
|
||||||
|
oneOrMore = (value, code) ->
|
||||||
|
if value.push?
|
||||||
|
for node in value
|
||||||
|
code(node)
|
||||||
|
else
|
||||||
|
code(value)
|
||||||
|
|
||||||
|
dive = (node, drawable, topLevel = null) ->
|
||||||
|
for own key, value of node
|
||||||
|
switch key
|
||||||
|
when 'drawable'
|
||||||
|
oneOrMore value, (v) ->
|
||||||
|
childDrawable = drawable.create(v.$)
|
||||||
|
topLevel ||= childDrawable
|
||||||
|
dive(v, childDrawable, topLevel)
|
||||||
|
when '$'
|
||||||
|
# nothing
|
||||||
|
else
|
||||||
|
oneOrMore value, (v) ->
|
||||||
|
drawable.draw (svg) ->
|
||||||
|
svgObjs[key](v.$, svg)
|
||||||
|
dive(v, drawable, topLevel)
|
||||||
|
|
||||||
|
topLevel
|
||||||
|
|
||||||
|
module.exports = (xml, canvas, callback = null) ->
|
||||||
|
parseString xml, (err, result) ->
|
||||||
|
if err?
|
||||||
|
throw err
|
||||||
|
else
|
||||||
|
diveResult = dive(result, canvas)
|
||||||
|
|
||||||
|
if callback
|
||||||
|
callback(diveResult)
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
class Resources
|
||||||
|
constructor: (@canvas) ->
|
||||||
|
@resources = {}
|
||||||
|
@bboxes = {}
|
||||||
|
|
||||||
|
attachTo: (snapElement) ->
|
||||||
|
@resourceGroup = snapElement.group().attr(display: 'none')
|
||||||
|
|
||||||
|
copyIDsFrom: (snapElement, ids...) ->
|
||||||
|
for id in ids
|
||||||
|
node = snapElement.select("##{id}")
|
||||||
|
node.transform('')
|
||||||
|
@resourceGroup.append(node)
|
||||||
|
@resources[id] = node
|
||||||
|
|
||||||
|
clone: (id) ->
|
||||||
|
@resources[id].use()
|
||||||
|
|
||||||
|
copy: (id) ->
|
||||||
|
@resources[id].clone()
|
||||||
|
|
||||||
|
bbox: (id) ->
|
||||||
|
@bboxes[id] ||= @resources[id].getBBox()
|
||||||
|
|
||||||
|
module.exports = Resources
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
Canvas = require('../../src/bismarck/canvas')
|
||||||
|
Animate = require('../../src/bismarck/animate')
|
||||||
|
|
||||||
|
describe 'Animate', ->
|
||||||
|
svg = null
|
||||||
|
canvas = null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
$('body').append('<svg id="svg" width="300" height="300"></svg>')
|
||||||
|
canvas = new Canvas(document.getElementById('svg'), id: 'canvas')
|
||||||
|
|
||||||
|
afterEach ->
|
||||||
|
$('svg').remove()
|
||||||
|
|
||||||
|
describe '.moveFromTo', ->
|
||||||
|
it 'should move an object from one point to another', ->
|
||||||
|
drawable = canvas.create(id: 'drawable', centerPoint: 'northwest')
|
||||||
|
|
||||||
|
Animate.moveFromTo(drawable, {x: 0, y: 0}, {x: 100, y: 50}, 0)
|
||||||
|
expect(drawable.position).toEqual({x: 0, y: 0})
|
||||||
|
expect(drawable.snapElement.getBBox().x).toEqual(0)
|
||||||
|
|
||||||
|
Animate.moveFromTo(drawable, {x: 0, y: 0}, {x: 100, y: 50}, 0.5)
|
||||||
|
expect(drawable.position).toEqual({x: 50, y: 25})
|
||||||
|
expect(drawable.snapElement.getBBox().x).toEqual(50)
|
||||||
|
|
||||||
|
Animate.moveFromTo(drawable, {x: 0, y: 0}, {x: 100, y: 50}, 1.0)
|
||||||
|
expect(drawable.position).toEqual({x: 100, y: 50})
|
||||||
|
expect(drawable.snapElement.getBBox().x).toEqual(100)
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
Canvas = require('../../src/bismarck/canvas')
|
||||||
|
|
||||||
|
describe 'Drawable', ->
|
||||||
|
svg = null
|
||||||
|
canvas = null
|
||||||
|
drawable = null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
$('body').append('<svg id="svg" width="300" height="300"></svg>')
|
||||||
|
canvas = new Canvas(document.getElementById('svg'), id: 'canvas')
|
||||||
|
drawable = canvas.create(id: 'drawable', centerPoint: 'northwest')
|
||||||
|
|
||||||
|
afterEach ->
|
||||||
|
$('svg').remove()
|
||||||
|
|
||||||
|
describe 'offset', ->
|
||||||
|
describe 'with children', ->
|
||||||
|
it 'should calculate the offset correctly', ->
|
||||||
|
childOne = drawable.create(id: 'one', centerPoint: 'northwest')
|
||||||
|
|
||||||
|
childOne.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 10, 1).attr(fill: '#000000')
|
||||||
|
|
||||||
|
childTwo = drawable.create(id: 'two', centerPoint: 'northwest')
|
||||||
|
|
||||||
|
childTwo.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 1, 10).attr(fill: '#000000')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(0)
|
||||||
|
expect(drawable.getOffset().y).toEqual(0)
|
||||||
|
|
||||||
|
drawable.setCenterPoint('50%', '50%')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(5)
|
||||||
|
expect(drawable.getOffset().y).toEqual(5)
|
||||||
|
|
||||||
|
childTwo.moveTo(20, 0)
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(10.5)
|
||||||
|
expect(drawable.getOffset().y).toEqual(5)
|
||||||
|
|
||||||
|
it 'should calculate the offset correctly', ->
|
||||||
|
drawable.setCenterPoint('center')
|
||||||
|
childOne = drawable.create(id: 'one', centerPoint: 'center')
|
||||||
|
|
||||||
|
childOne.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 20, 30).attr(fill: '#000000')
|
||||||
|
|
||||||
|
childOne.moveTo(0, 0)
|
||||||
|
|
||||||
|
childTwo = drawable.create(id: 'two', centerPoint: 'center')
|
||||||
|
|
||||||
|
childTwo.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 20, 30).attr(fill: '#000000')
|
||||||
|
|
||||||
|
childTwo.moveTo(0, 30)
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(10)
|
||||||
|
expect(drawable.getOffset().y).toEqual(30)
|
||||||
|
|
||||||
|
it 'should calculate the offset correctly', ->
|
||||||
|
spyOn(drawable, 'snapBBox').and.callThrough()
|
||||||
|
|
||||||
|
orect = null
|
||||||
|
|
||||||
|
drawable.setCenterPoint(0, 0)
|
||||||
|
|
||||||
|
drawable.draw (surface) ->
|
||||||
|
orect = surface.rect(0, 0, 20, 10).attr(fill: '#000000')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(0)
|
||||||
|
expect(drawable.getOffset().y).toEqual(0)
|
||||||
|
|
||||||
|
drawable.setCenterPoint('50%', '50%')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(10)
|
||||||
|
expect(drawable.getOffset().y).toEqual(5)
|
||||||
|
|
||||||
|
drawable.setCenterPoint('-100%', '0%')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(-20)
|
||||||
|
expect(drawable.getOffset().y).toEqual(0)
|
||||||
|
|
||||||
|
drawable.draw (surface) ->
|
||||||
|
orect = surface.rect(0, 0, 20, 10).attr(fill: 'red')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(-20)
|
||||||
|
expect(drawable.getOffset().y).toEqual(0)
|
||||||
|
|
||||||
|
drawable.rotateTo(90)
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(-20)
|
||||||
|
expect(drawable.getOffset().y).toEqual(0)
|
||||||
|
|
||||||
|
drawable.setCenterPoint('center')
|
||||||
|
|
||||||
|
expect(drawable.getOffset().x).toEqual(10)
|
||||||
|
expect(drawable.getOffset().y).toEqual(5)
|
||||||
|
|
||||||
|
describe 'dimensions', ->
|
||||||
|
describe 'after drawing', ->
|
||||||
|
it 'should position the bbox correctly', ->
|
||||||
|
spyOn(drawable, 'snapBBox').and.callThrough()
|
||||||
|
|
||||||
|
orect = null
|
||||||
|
|
||||||
|
drawable.draw (surface) ->
|
||||||
|
orect = surface.rect(0, 0, 20, 10).attr(fill: '#000000')
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toEqual(20)
|
||||||
|
expect(drawable.getDimensions().h).toEqual(10)
|
||||||
|
|
||||||
|
drawable.draw (surface) ->
|
||||||
|
orect.remove()
|
||||||
|
surface.rect(10, 10, 20, 10).attr(fill: '#000000')
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toEqual(20)
|
||||||
|
expect(drawable.getDimensions().h).toEqual(10)
|
||||||
|
|
||||||
|
expect(drawable.snapBBox.calls.count()).toEqual(2)
|
||||||
|
|
||||||
|
drawable.scaleTo(2)
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toEqual(20)
|
||||||
|
expect(drawable.getDimensions().h).toEqual(10)
|
||||||
|
|
||||||
|
drawable.rotateTo(90)
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toBeCloseTo(20, 2)
|
||||||
|
expect(drawable.getDimensions().h).toBeCloseTo(10, 2)
|
||||||
|
|
||||||
|
describe 'with children', ->
|
||||||
|
describe 'northwest', ->
|
||||||
|
it 'should composite together all the drawables', ->
|
||||||
|
childOne = drawable.create(id: 'one', centerPoint: 'northwest')
|
||||||
|
|
||||||
|
childOne.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 10, 1).attr(fill: '#000000')
|
||||||
|
|
||||||
|
childTwo = drawable.create(id: 'two', centerPoint: 'northwest')
|
||||||
|
|
||||||
|
childTwo.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 1, 10).attr(fill: '#000000')
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toEqual(10)
|
||||||
|
expect(drawable.getDimensions().h).toEqual(10)
|
||||||
|
|
||||||
|
expect(drawable.upperLeft().x).toBeCloseTo(0, 1)
|
||||||
|
expect(drawable.upperLeft().y).toBeCloseTo(0, 1)
|
||||||
|
|
||||||
|
expect(childTwo.upperLeft().x).toBeCloseTo(0, 1)
|
||||||
|
expect(childTwo.upperLeft().y).toBeCloseTo(0, 1)
|
||||||
|
|
||||||
|
childTwo.moveTo(-10, 0)
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toEqual(20)
|
||||||
|
expect(drawable.getDimensions().h).toEqual(10)
|
||||||
|
expect(drawable.upperLeft().x).toBeCloseTo(-10, 1)
|
||||||
|
expect(drawable.upperLeft().y).toBeCloseTo(0, 1)
|
||||||
|
expect(childTwo.upperLeft().x).toBeCloseTo(0, 1)
|
||||||
|
expect(childTwo.upperLeft().y).toBeCloseTo(0, 1)
|
||||||
|
|
||||||
|
describe 'center', ->
|
||||||
|
it 'should composite together all the drawables', ->
|
||||||
|
drawable.setCenterPoint('center')
|
||||||
|
childOne = drawable.create(id: 'one', centerPoint: 'center')
|
||||||
|
|
||||||
|
childOne.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 10, 20).attr(fill: '#000000')
|
||||||
|
|
||||||
|
childTwo = drawable.create(id: 'two', centerPoint: 'center')
|
||||||
|
|
||||||
|
childTwo.draw (surface) ->
|
||||||
|
surface.rect(0, 0, 10, 20).attr(fill: '#000000')
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toEqual(10)
|
||||||
|
expect(drawable.getDimensions().h).toEqual(20)
|
||||||
|
|
||||||
|
expect(childTwo.upperLeft().x).toBeCloseTo(0, 1)
|
||||||
|
expect(childTwo.upperLeft().y).toBeCloseTo(0, 1)
|
||||||
|
|
||||||
|
expect(drawable.upperLeft().x).toBeCloseTo(-5, 1)
|
||||||
|
expect(drawable.upperLeft().y).toBeCloseTo(-10, 1)
|
||||||
|
|
||||||
|
childTwo.rotateTo(270)
|
||||||
|
|
||||||
|
expect(drawable.getDimensions().w).toBeCloseTo(10, 2)
|
||||||
|
expect(drawable.getDimensions().h).toBeCloseTo(20, 2)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
Canvas = require('../../src/bismarck/canvas')
|
||||||
|
|
||||||
|
describe 'layoutSpec', ->
|
||||||
|
svg = null
|
||||||
|
canvas = null
|
||||||
|
drawable = null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
$('body').append('<svg id="svg" width="300" height="300"></svg>')
|
||||||
|
canvas = new Canvas(document.getElementById('svg'), id: 'canvas')
|
||||||
|
drawable = canvas.create(id: 'drawable', centerPoint: 'northwest')
|
||||||
|
svg = canvas.snapElement
|
||||||
|
|
||||||
|
afterEach ->
|
||||||
|
$('svg').remove()
|
||||||
|
|
||||||
|
describe 'from canvas', ->
|
||||||
|
it 'should create a group of objects with SVG bits', ->
|
||||||
|
layout = canvas.fromLayout("""
|
||||||
|
<drawable id="cat">
|
||||||
|
<rect id="dog" x="10" y="10" width="30" height="20" style="fill: red" />
|
||||||
|
</drawable>
|
||||||
|
""")
|
||||||
|
|
||||||
|
console.log svg.outerSVG()
|
||||||
|
|
||||||
|
expect(svg).toContainSVG('#cat')
|
||||||
|
expect(svg).toContainSVG('rect#dog[style="fill: red"]')
|
|
@ -0,0 +1,17 @@
|
||||||
|
beforeEach ->
|
||||||
|
jasmine.addMatchers {
|
||||||
|
toContainSVG: ->
|
||||||
|
compare: (svg, select) ->
|
||||||
|
result = { pass: svg.select(select)? }
|
||||||
|
|
||||||
|
result.message = if !result.pass
|
||||||
|
"Expect #{svg.outerSVG()} to contain #{select}"
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
require('./bismarckSpec')
|
||||||
|
require('./bismarck/animateSpec')
|
||||||
|
require('./bismarck/drawableSpec')
|
||||||
|
require('./bismarck/layoutSpec')
|
||||||
|
|
Loading…
Reference in New Issue