|
function Layer(parentElement, width, height) { |
|
this._layerGroup = parentElement.append("g"); |
|
this._isDrawBorderInSeparatedState = false; |
|
this._separateStateBorder = this._layerGroup.append("rect") |
|
.attr({x: 0, y: 0, width: width, height: height}) |
|
.classed("separate-state-border", true) |
|
.style({fill: "none", stroke: "#777", "stroke-opacity": 0, "stroke-dasharray": "5,5"}); |
|
} |
|
|
|
Layer.prototype.getElement = function() { |
|
return this._layerGroup; |
|
}; |
|
|
|
Layer.prototype.getSeparateStateBorder = function() { |
|
return this._separateStateBorder; |
|
}; |
|
|
|
Layer.prototype.isDrawBorderInSeparatedState = function() { |
|
return this._isDrawBorderInSeparatedState; |
|
}; |
|
|
|
Layer.prototype.setDrawBorderInSeparatedState = function(shouldDraw) { |
|
this._isDrawBorderInSeparatedState = shouldDraw; |
|
}; |
|
|
|
function StackedLayers(element, width, height) { |
|
this._element = element.append("g"); |
|
this._layers = []; |
|
this._width = width; |
|
this._height = height; |
|
// TODO(vsapsai): it might be good to let configure these values. |
|
this._separatedMinY = -50; |
|
this._separatedMaxY = 250; |
|
this._duration = 1000; |
|
this._scaleFactor = 0.7; |
|
this._skewAngleInDegrees = 20; |
|
} |
|
|
|
StackedLayers.prototype.addLayer = function() { |
|
var layer = new Layer(this._element, this._width, this._height); |
|
this._layers.push(layer); |
|
return layer; |
|
}; |
|
|
|
StackedLayers.prototype.separateLayers = function() { |
|
// Introduce variables because `this` inside `forEach` callback is bound to different object. |
|
var duration = this._duration, |
|
scaleFactor = this._scaleFactor, |
|
skewAngleInDegrees = this._skewAngleInDegrees, |
|
separatedMinY = this._separatedMinY; |
|
var xSkewingOffset = this._height * Math.sin(this._skewAngleInDegrees * (Math.PI / 180)); |
|
var layerHeightAfterSeparating = this._height * this._scaleFactor |
|
* Math.cos(this._skewAngleInDegrees * (Math.PI / 180)); |
|
var layersCount = this._layers.length; |
|
var newDesignatedHeight = this._separatedMaxY - this._separatedMinY; |
|
var offsetBetweenLayers = 0; |
|
if ((layersCount > 1) && (newDesignatedHeight > layerHeightAfterSeparating)) { |
|
offsetBetweenLayers = (newDesignatedHeight - layerHeightAfterSeparating) / (layersCount - 1); |
|
} |
|
this._layers.forEach(function(layer, index) { |
|
var layerOffset = separatedMinY + offsetBetweenLayers * (layersCount - 1 - index); |
|
layer.getElement().transition() |
|
.duration(duration) |
|
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform |
|
// is indispensable for all transform crazyness |
|
.attr("transform", [ |
|
"translate(0, " + layerOffset + ")", |
|
"scale(1.0, " + scaleFactor + ")", |
|
"skewX(-" + skewAngleInDegrees + ")", |
|
"translate(" + xSkewingOffset + ", 0)" |
|
].join(",")); |
|
if (layer.isDrawBorderInSeparatedState()) { |
|
layer.getSeparateStateBorder().transition() |
|
.duration(duration) |
|
.style("stroke-opacity", 1.0); |
|
} |
|
}); |
|
}; |
|
|
|
StackedLayers.prototype.collapseLayers = function() { |
|
var duration = this._duration; |
|
this._layers.forEach(function(layer, index) { |
|
layer.getElement().transition() |
|
.duration(duration) |
|
.attr("transform", [ |
|
"translate(0, " + 0 + ")", |
|
"scale(1.0, " + 1.0 + ")", |
|
"skewX(-" + 0 + ")", |
|
"translate(" + 0 + ", 0)" |
|
].join(",")); |
|
layer.getSeparateStateBorder().transition() |
|
.duration(duration) |
|
.style("stroke-opacity", 0.0); |
|
}); |
|
}; |