Skip to content

Instantly share code, notes, and snippets.

@edom18
Created November 24, 2012 16:30
Show Gist options
  • Select an option

  • Save edom18/4140388 to your computer and use it in GitHub Desktop.

Select an option

Save edom18/4140388 to your computer and use it in GitHub Desktop.
Animation Graph
# Animation Graph
案件で使うグラフにアニメーションを付加するために作ったモック。
意外といい感じに仕上がった。
@import "compass/reset";
@import "compass/css3";
.graph-container {
position: relative;
width: 500px;
height: 500px;
margin-bottom: 15px;
> canvas {
position: absolute;
left: 0;
top: 0;
}
border: solid 1px #999;
}
#ctrl {
> p {
display: inline-block;
}
}
<div id="graphContainer"></div>
var app = app || {};
(function (ns, win, doc) {
'use strict';
/*! -------------------------------------
IMPORT
----------------------------------------- */
var utils = f.utils,
EventDispatcher = f.events.EventDispatcher;
function noop () {
//noop.
}
function _each(arr, func) {
if (({}).toString.call(func) !== '[object Function]') {
return false;
}
for (var i = 0, l = arr.length; i < l; i++) {
if (func(arr[i], i) === false) {
break;
}
}
}
/**
* Stage
*/
function Stage (cv) {
this.init.apply(this, arguments);
}
utils.copyClone(Stage.prototype, EventDispatcher.prototype, {
init: function (cv, attr) {
attr || (attr = {});
this._cv = cv;
this._ctx = cv.getContext('2d');
this._width = cv.width;
this._height = cv.height;
this._actors = [];
this._time = attr.time || 1000; //Default time is 5 sec.
this._endActor = 0;
},
add: function (actor) {
if (!Actor.prototype.isPrototypeOf(actor)) {
throw new Error('Parameter MUST be taken Actor class and extended class.');
}
actor.on('end', this.done, this);
this._actors.push(actor);
},
getActors: function () {
return utils.makeArr(this._actors);
},
each: function (func) {
_each(this._actors, func);
},
animete: function () {
var self = this,
startTime = +new Date(),
currentTime = 0,
endTime = this._time,
INTERVAL = 1000 / 30;
(function loop() {
var now = 0;
if (self.isEnd()) {
console.log('End of Stage!');
self.trigger('animationend');
return;
}
self.clear();
self.each(function (actor) {
actor.update();
actor.draw();
});
now = +new Date();
currentTime = now - startTime;
self.timer = setTimeout(loop, INTERVAL);
}());
},
draw: function () {
this.each(function (actor) {
actor.draw();
});
},
start: function () {
this.animete();
},
isEnd: function () {
return this._ended;
},
done: function () {
this._endActor++;
if (this._endActor === this._actors.length) {
this._ended = true;
}
},
clear: function () {
this._ctx.clearRect(0, 0, this._width, this._height);
}
});
Stage.prototype.constructor = Stage;
////////////////////////////////////////////////////////////////////////////////////////
/**
* Actor
*/
function Actor () {
this.init.apply(this, arguments);
}
utils.copyClone(Actor.prototype, EventDispatcher.prototype, {
init: noop,
easing: function (t, b, c, d) {
return b + (c * t / d);
},
update: function () {
this.updateInternal();
if (this.isEnd()) {
this.update = noop;
this.trigger('end');
}
},
updateInternal: function () { throw new Error('MUST BE IMPLEMENTS THE `update` METHOD'); },
draw: function () { throw new Error('MUST BE IMPLEMENTS THE `draw` METHOD'); },
isEnd: function () { throw new Error('MUST BE IMPLEMENTS THE `isEnd` METHOD'); },
/**
* Set a new color.
* @param {string} color A new color.
*/
setColor: function (color) {
this._color = color;
}
});
Actor.prototype.constructor = Actor;
///////////////////////////////////////////////////////////////////////////
/**
* Actors class
* @constructor
* @extend Actor
* @param {Array.<Actor>} actors
*/
function Actors () {
this.init.apply(this, arguments);
}
//Extend by Actor.
Actors.prototype = new Actor();
/** @override */
Actors.prototype.init = function (actors) {
this._actors = actors || [];
this._index = 0;
this._currentActor = this._actors[0];
};
/** @override */
Actors.prototype.updateInternal = function () {
var activeIndex = this._index;
this.each(function (actor, i) {
actor.update();
if (i === activeIndex) {
return false;
}
});
if (this._currentActor.isEnd()) {
this._currentActor = this.next();
}
};
/** @override */
Actors.prototype.draw = function () {
this.each(function (line) {
line.draw();
});
};
/** @override */
Actors.prototype.isEnd = function () {
return !this._currentActor;
};
Actors.prototype.next = function () {
if (!this.hasNext()) {
return null;
}
return this._actors[++this._index];
};
Actors.prototype.hasNext = function () {
return (this._index + 1 < this._actors.length);
};
Actors.prototype.each = function (func) {
_each(this._actors, func);
};
/**
* Set a new color.
* @param {string} color A new color.
*/
Actors.prototype.setColor = function (color) {
this.each(function (line) {
line._color = color;
});
};
////////////////////////////////////////////////////////////////////////////////////////
/**
* Line class
* @constructor
* @extend Actor
* @param {CanvasElement} cv
* @param {number} sx start x.
* @param {number} sy start y.
* @param {number} ex end x.
* @param {number} ey end y.
* @param {Object} opt An option.
*/
function Line (cv, sx, sy, ex, ey, opt) {
this.init.apply(this, arguments);
}
//Extend by Actor.
Line.prototype = new Actor();
/** @override */
Line.prototype.init = function (cv, sx, sy, ex, ey, opt) {
opt || (opt = {});
this._cv = cv;
this._ctx = cv.getContext('2d');
this._sx = sx;
this._sy = sy;
this._ex = ex;
this._ey = ey;
this._t = 0;
this._d = 5;
this._color = opt.color || '#666';
};
/** @override */
Line.prototype.updateInternal = function () {
this._t++;
this._x = this.easing(this._t, this._sx, this._ex - this._sx, this._d);
this._y = this.easing(this._t, this._sy, this._ey - this._sy, this._d);
};
/** @override */
Line.prototype.draw = function () {
var ctx = this._ctx;
ctx.save();
ctx.beginPath();
ctx.strokeStyle = this._color;
ctx.moveTo(this._sx, this._sy);
ctx.lineTo(this._x, this._y);
ctx.stroke();
ctx.closePath();
ctx.restore();
};
/**
* Check to end.
*/
Line.prototype.isEnd = function () {
return this._t >= this._d;
};
///////////////////////////////////////////////////////////////////////
/**
* Dot class
* @constructor
* @extend Actor
* @param {CanvasElement} cv
* @param {number} x position x.
* @param {number} y position y.
* @param {number} sr start radius.
* @param {number} er end radius.
* @param {Object} opt An option.
*/
function Dot(cv, x, y, sr, er) {
this.init.apply(this, arguments);
}
//Extend by Actor.
Dot.prototype = new Actor();
/** @override */
Dot.prototype.init = function (cv, x, y, sr, er, opt) {
opt || (opt = {});
this._cv = cv;
this._ctx = cv.getContext('2d');
this._x = x;
this._y = y;
this._sr = sr;
this._er = er;
this._t = 0;
this._d = 5;
this._color = opt.color || '#666';
};
/** @override */
Dot.prototype.updateInternal = function () {
this._t++;
this._r = this.easing(this._t, this._sr, this._er - this._sr, this._d);
};
Dot.prototype.easing = function (t, b, c, d) {
var s = 2.5;
if (t / d >= 1) {
return b + c;
}
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
};
/** @override */
Dot.prototype.draw = function () {
var ctx = this._ctx;
ctx.save();
ctx.beginPath();
ctx.fillStyle = this._color;
ctx.moveTo(this._x, this._y);
ctx.arc(this._x, this._y, this._r, 0, Math.PI * 2, false);
ctx.fill();
ctx.closePath();
ctx.restore();
};
/**
* Check to end.
*/
Dot.prototype.isEnd = function () {
return this._t >= this._d;
};
/*! =============================================================
EXPORTS
================================================================= */
ns.Actor = Actor;
ns.Actors = Actors;
ns.Stage = Stage;
ns.Line = Line;
ns.Dot = Dot;
}(app, window, document));
var app = app || {};
(function (ns, win, doc, exports, undefined) {
'use strict';
/*! -------------------------------------
IMPORT
----------------------------------------- */
var utils = f.utils,
EventDispatcher = f.events.EventDispatcher,
//Actors
Stage = ns.Stage,
Actor = ns.Actor,
Actors = ns.Actors,
Line = ns.Line,
Dot = ns.Dot;
function noop () {
//noop.
}
function _each(arr, func) {
if (({}).toString.call(func) !== '[object Function]') {
return false;
}
for (var i = 0, l = arr.length; i < l; i++) {
if (func(arr[i], i) === false) {
break;
}
}
}
/**
* Graph class
* @constructor
* @param {Element} container
* @param {Object} args
* {Object.<Array.<GraphData>>} data
* {Object.<Object>} memory
* {Object<Array.<number>>} span
*/
function Graph (container, args) {
this.init.apply(this, arguments);
}
utils.copyClone(Graph.prototype, EventDispatcher.prototype, {
init: function (container, args) {
args || (args = {});
var cvs = [];
this._container = container;
this._container.className += ' graph-container';
cvs = this._createCanvas(container.clientWidth, container.clientHeight);
this._cv = cvs[0];
this._memory = new GraphMemory(cvs[1], args.memory);
this._span = new GraphSpan(cvs[2], args.span, {
baseLine: this._memory.getHeight()
});
this._stage = new Stage(this._cv);
this._graphData = [];
this._actors = [];
for (var i = 0, l = args.data.length; i < l; i++) {
this.add(args.data[i]);
}
this._createGraphData();
//Event handlers.
this._stage.on('animationend', this.done, this);
},
each: function (func) {
_each(this._graphData, func);
},
/**
* Create canvas for memory and span.
* @param {number} w The container's width
* @param {number} h The container's height
*/
_createCanvas: function (w, h) {
var cv1 = doc.createElement('canvas'),
cv2 = doc.createElement('canvas'),
cv3 = doc.createElement('canvas');
cv1.width = cv2.width = cv3.width = w;
cv1.height = cv2.height = cv3.height = h;
cv1.className = 'graph-canvas';
cv2.className = 'graph-memory';
cv3.className = 'graph-span';
this._container.appendChild(cv3);
this._container.appendChild(cv2);
this._container.appendChild(cv1);
return [cv1, cv2, cv3];
},
/**
* Create graph data.
*/
_createGraphData: function () {
var self = this;
this.each(function (graphData) {
var sx = 0,
sy = 0,
ex = 0,
ey = 0,
r = 3,
actors = [],
cv = self._cv,
color = graphData.getColor(),
data = graphData.getData(),
width = self._span.getWidth(),
height = self._memory.getHeight(),
max = self._memory.getMax(),
left = self._span.getLeft(),
horDiv = self._span.getDivision(),
verDiv = self._memory.getDivision(),
bottom = self._memory.getHeight(),
baseX = width / horDiv,
d = null;
for (var i = 0, l = horDiv; i <= l; i++) {
d = data[i];
sx = (baseX * i) + left;
sy = bottom - ((d / max * height));
actors.push(new Dot(cv, sx, sy, 0, r, {color: color}));
if ((d = data[i + 1])) {
ex = (baseX * (i + 1)) + left;
ey = bottom - ((d / max * height));
actors.push(new Line(cv, sx, sy, ex, ey, {color: color}));
}
}
self.addActor(new Actors(actors));
});
},
/**
* Draw the Graph.
*/
draw: function () {
this._stage.start();
},
/**
* Add a graph data.
* @param {GraphData} graphdata
*/
add: function (graphdata) {
if (GraphData.isPrototypeOf(graphdata)) {
throw new Error('`data` of arguments must be instance that created by GraphData.');
}
this._graphData.push(graphdata);
},
addActor: function (actor) {
if (!Actor.prototype.isPrototypeOf(actor)) {
throw new Error('actor of arguments must be Actor instance.');
}
this._stage.add(actor);
},
/**
* Select graph by index.
* @param {number} index
*/
select: function (index) {
var preActors,
actors,
actor;
if (!this._stage.isEnd()) {
return false;
}
if (index === undefined) {
this._index = null;
this._stage.each(function (actor) {
actor.revertColor();
});
//Redraw
this._stage.redraw();
return;
}
this._index = index;
//Get actors as previous one and normal one.
preActors = this._stage.getActors();
actors = this._stage.getActors();
//Get an actor at index.
actor = this._stage.getActorAt(index);
if (!actor) {
throw new Error('Not found the actor at ' + index + ' of index.');
}
for (var i = 0, l = actors.length; i < l; i++) {
actors[i].setColor('#aaa');
}
//Will be back to previous color.
actor.revertColor();
actors.splice(index, 1);
actors.push(actor);
//set in oerder to by z-index array.
this._stage.setActors(actors);
//Redraw
this._stage.redraw();
this._stage.setActors(preActors);
actors = null;
actor = null;
preActors = null;
},
/**
* This invoked when all of graph are done.
*/
done: function () {
this.trigger('drawend');
}
});
Graph.prototype.constructor = Graph;
////////////////////////////////////////////////////////////////////////
/**
* Graph data
* @constructor
* @param {Object} data
*/
function GraphData (args) {
this.init.apply(this, arguments);
}
GraphData.prototype = {
init: function (args) {
this._data = args.data;
this._color = args.color;
},
getColor: function () {
return this._color;
},
getData: function () {
return utils.makeArr(this._data);
}
};
GraphData.prototype.constructor = GraphData;
////////////////////////////////////////////////////////////////////////
/**
* Graph memory class
* @constructor
* @param {number} min Number of min to memory.
* @param {number} max Number of max to memory.
* @param {number} div Division number.
* @example
* new GraphMemory(0, 2200, 10);
*/
function GraphMemory (cv, min, max, div) {
this.init.apply(this, arguments);
}
GraphMemory.prototype = {
init: function (cv, args) {
this._cv = cv;
this._ctx = cv.getContext('2d');
this._min = args.min;
this._max = args.max;
this._div = args.div;
this._padding = 30;
this._txtLine = 50;
this._startLine = 30;
this.draw();
},
/**
* @return {number} The memory width.
*/
getWidth: function () {
return this._txtLine;
},
/**
* @return {number} The memory height.
*/
getHeight: function () {
return this._cv.height - (this._padding * 2) - this.getTxtHeight() + this._startLine;
},
/**
* @return {number} memory width.
*/
getTxtWidth: function () {
var txtRect = this._ctx.measureText(this._max);
return txtRect.width;
},
/**
* @return {number} memory height.
*/
getTxtHeight: function () {
return 20;
},
getTop: function () {
return this._startLine - 10;
},
_drawVerticalLine: function () {
var cv = this._cv,
ctx = this._ctx,
w = cv.width,
h = this.getHeight(),
txtWidth = this.getTxtWidth(),
baseLine = this._txtLine + 5;
ctx.save();
ctx.beginPath();
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
ctx.moveTo(baseLine, this.getTop());
ctx.lineTo(baseLine, h);
ctx.stroke();
ctx.closePath();
ctx.restore();
},
draw: function () {
var cv = this._cv,
ctx = this._ctx,
w = cv.width,
h = this.getHeight() - this._startLine,
txtWidth = this.getTxtWidth(),
base = h / this._div,
baseTxt = ~~((this._max - this._min) / this._div),
curHeight = 0;
this._drawVerticalLine();
ctx.save();
ctx.textAlign = 'right';
ctx.fillStyle = '#333';
ctx.strokeStyle = '#ccc';
for (var i = 0, l = this._div; i <= l; i++) {
ctx.beginPath();
curHeight = (h - base * i) + this._startLine;
ctx.fillText(baseTxt * i, this._txtLine, curHeight);
if (i !== 0) {
ctx.beginPath();
ctx.moveTo(this._txtLine + 5, curHeight - 2);
ctx.lineTo(w - this._padding, curHeight - 2);
ctx.stroke();
}
ctx.closePath
}
ctx.restore();
},
getMax: function () {
return this._max;
},
getMin: function () {
return this._min;
},
getDivision: function () {
return this._div;
}
};
GraphMemory.prototype.constructor = GraphMemory;
////////////////////////////////////////////////////////////////////////
/**
* Graph span class
* @constructor
* @param {CanvasElement} cv
* @param {Object} spanList
*/
function GraphSpan (cv, spanList, opt) {
this.init.apply(this, arguments);
}
GraphSpan.prototype = {
init: function (cv, spanList, opt) {
opt || (opt = {});
this._cv = cv;
this._ctx = cv.getContext('2d');
this._spanList = spanList;
this._div = spanList.length - 1;
this._padding = 30;
this._txtLine = 50;
this._baseLine = opt.baseLine || 30;
this._startLine = opt.startLine || 55;
this.draw();
},
getDivision: function () {
return this._div;
},
getLeft: function () {
return this._startLine;
},
getBaseline: function () {
return this._baseLine;
},
getWidth: function () {
return this._cv.width - this._padding - this._startLine;
},
getHeight: function () {
return 30;
},
/**
* Draw a horizontal line as saparetor.
*/
_drawLine: function () {
var ctx = this._ctx,
w = this.getWidth(),
startLine = this.getLeft();
ctx.save();
ctx.beginPath();
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
ctx.moveTo(startLine, this.getBaseline());
ctx.lineTo(startLine + w, this._baseLine);
ctx.stroke();
ctx.closePath();
ctx.restore();
},
draw: function () {
var cv = this._cv,
ctx = this._ctx,
w = this.getWidth(),
base = w / this._div,
curWidth = 0;
this._drawLine();
ctx.save();
ctx.textBaseline = 'top';
ctx.textAlign = 'center';
ctx.fillStyle = '#333';
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 20;
for (var i = 0, l = this._spanList.length; i < l; i++) {
ctx.beginPath();
curWidth = this._startLine + (base * i);
ctx.fillText(this._spanList[i], curWidth, this._baseLine + 5);
if (i !== 0) {
ctx.beginPath();
ctx.moveTo(curWidth - 10, this._baseLine - 1);
ctx.lineTo(curWidth - 10, this._padding - 2);
ctx.stroke();
}
ctx.closePath();
}
ctx.restore();
}
};
GraphSpan.prototype.constructor = GraphSpan;
/*! =============================================================
EXPORTS
================================================================= */
ns.Graph = Graph;
ns.GraphData = GraphData;
ns.GraphMemory = GraphMemory;
ns.GraphSpan = GraphSpan;
}(app, window, document));
(function (win, doc, ns) {
'use strict';
/*! -------------------------------------
IMPORT
----------------------------------------- */
var Stage = ns.Stage,
Actor = ns.Actor,
Actors = ns.Actors,
Line = ns.Line,
Dot = ns.Dot,
//for Graph
Graph = ns.Graph,
GraphData = ns.GraphData,
GraphMemory = ns.GraphMemory,
GraphSpan = ns.GraphSpan;
//An entry point.
function main() {
console.log('Start app');
var cont = doc.getElementById('graphContainer'),
btn1 = doc.getElementById('data1'),
btn2 = doc.getElementById('data2');
//First data.
var data = new GraphData({
data: [ 220, 880, 660, 1500, 800, 920, 1540, 1760 ],
color: '#c00'
});
//Secound data.
var data2 = new GraphData({
data: [ 1320, 1880, 1660, 1700, 1760, 1420, 1550, 1700 ],
color: '#00c'
});
//Graph set up.
var graph = new Graph(cont, {
data: [data, data2],
memory: {
min: 0,
max: 2200,
div: 10
},
span: ['11/11', '11/15', '12/01', '12/05', '12/10', '12/19', '12/23', '12/29']
});
graph.on('drawend', function (e) {
//do something if you want when `graph` ended.
}, false);
setTimeout(function () {
graph.draw();
}, 500);
btn1.addEventListener('click', function (e) {
e.stopPropagation();
graph.select(0);
}, false);
btn2.addEventListener('click', function (e) {
e.stopPropagation();
graph.select(1);
}, false);
doc.addEventListener('click', function (e) {
graph.select();
return false;
}, false);
window.graph = graph;
}
//Start.
window.addEventListener('DOMContentLoaded', main, false);
}(window, document, app));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment