diff --git a/assets/css/locuszoom.scss b/assets/css/locuszoom.scss
index ba5e8355..8acbc9ab 100644
--- a/assets/css/locuszoom.scss
+++ b/assets/css/locuszoom.scss
@@ -16,24 +16,6 @@ svg.#{$namespace}-locuszoom {
fill-opacity: 0;
}
- .#{$namespace}-curtain {
- rect {
- fill: rgb(210,210,210);
- fill-opacity: 0.85;
- }
- text, tspan {
- fill: rgb(0,0,0);
- font-weight: 600;
- font-size: 14px;
- text-anchor: start;
- alignment-baseline: hanging;
- }
- tspan.dismiss {
- fill: rgb(49,112,143);
- cursor: pointer;
- }
- }
-
.#{$namespace}-mouse_guide {
rect {
fill: rgb(210,210,210);
@@ -323,6 +305,114 @@ div.#{$namespace}-data_layer-tooltip-arrow_bottom_right {
overflow: hidden;
}
+.#{$namespace}-curtain {
+
+ position: absolute;
+ font-size: 2em;
+ font-weight: 600;
+ background: rgba(216,216,216,0.8);
+
+ .#{$namespace}-curtain-content {
+ position: absolute;
+ display: block;
+ width: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ margin: 0 auto;
+ padding: 0px 20px;
+ overflow-y: auto;
+ }
+
+ .#{$namespace}-curtain-dismiss {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ padding: 0.15em 0.5em;
+ font-size: 0.3em;
+ font-weight: 300;
+ background-color: #D8D8D8;
+ color: #333333;
+ border: 1px solid #333333;
+ border-radius: 0px 0px 0px 4px;
+ pointer-events: auto;
+ cursor: pointer;
+ }
+
+ .#{$namespace}-curtain-dismiss:hover {
+ background-color: #333333;
+ color: #D8D8D8;
+ }
+
+}
+
+.#{$namespace}-loader {
+
+ position: absolute;
+ font-family: "Helvetica Neue", Helvetica, Aria, sans-serif;
+ font-size: 12px;
+ padding: 6px;
+ background: rgba(240,235,228,1);
+ border: 1px solid #{$default_black};
+ border-radius: 4px;
+ box-shadow: 2px 2px 2px #{$default_black_shadow};
+
+ .#{$namespace}-loader-content {
+ position: relative;
+ display: block;
+ width: 100%;
+ }
+
+ .#{$namespace}-loader-cancel {
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ padding: 2px 4px;
+ font-size: 9px;
+ font-weight: 300;
+ background-color: #D8D8D8;
+ color: #333333;
+ border: 1px solid #333333;
+ border-radius: 0px 4px 0px 4px;
+ pointer-events: auto;
+ cursor: pointer;
+ }
+
+ .#{$namespace}-loader-cancel:hover {
+ background-color: #333333;
+ color: #D8D8D8;
+ }
+
+ .#{$namespace}-loader-progress-container {
+ position: relative;
+ display: block;
+ width: 100%;
+ height: 2px;
+ padding-top: 6px;
+ }
+
+ .#{$namespace}-loader-progress {
+ position: absolute;
+ left: 0%;
+ width: 0%;
+ height: 2px;
+ background-color: #{$default_black_shadow};
+ }
+
+ .#{$namespace}-loader-progress-animated {
+ animation-name: #{$namespace}-loader-animate;
+ animation-duration: 1.5s;
+ animation-iteration-count: infinite;
+ animation-timing-function: ease-in-out;
+ }
+
+}
+
+@keyframes #{$namespace}-loader-animate {
+ 0% { width: 0%; left: 0%; }
+ 50% { width: 100%; left: 0%; }
+ 100% { width: 0%; left: 100%; }
+}
+
.#{$namespace}-locuszoom-controls {
font-family: "Helvetica Neue", Helvetica, Aria, sans-serif;
font-size: 80%;
@@ -420,29 +510,27 @@ div.#{$namespace}-panel-description {
div.#{$namespace}-panel-boundary {
position: absolute;
- height: 4px;
- width: 200px;
- border-radius: 2px;
- background: rgba(235,240,228,0.5);
- border: 1px solid #{$default_black_shadow};
+ height: 3px;
+ cursor: row-resize;
+ display: block;
+ padding-top: 10px;
+ padding-bottom: 10px;
}
-div.#{$namespace}-panel-boundary {
- position: absolute;
- height: 3px;
- width: 200px;
+div.#{$namespace}-panel-boundary span {
+ display: block;
border-radius: 1px;
- background: rgba(216,216,216,0.4);
- border: 1px solid #{$default_black_shadow};
- cursor: row-resize;
+ background: rgba(216,216,216,0);
+ border: 1px solid rgba(216,216,216,0);
+ height: 3px;
}
-div.#{$namespace}-panel-boundary:hover {
+div.#{$namespace}-panel-boundary:hover span {
background: rgba(216,216,216,1);
border: 1px solid #{$default_black};
}
-div.#{$namespace}-panel-boundary:active {
+div.#{$namespace}-panel-boundary:active span {
background: rgba(51,51,51,1);
border: 1px solid rgba(216,216,216,1);
}
diff --git a/assets/js/app/Instance.js b/assets/js/app/Instance.js
index 06a10fec..b1df8505 100644
--- a/assets/js/app/Instance.js
+++ b/assets/js/app/Instance.js
@@ -52,6 +52,7 @@ LocusZoom.Instance = function(id, datasource, layout) {
// Event hooks
this.event_hooks = {
"layout_changed": [],
+ "data_requested": [],
"data_rendered": [],
"element_clicked": []
};
@@ -91,7 +92,9 @@ LocusZoom.Instance = function(id, datasource, layout) {
}
return {
x: x_offset + bounding_client_rect.left,
- y: y_offset + bounding_client_rect.top
+ y: y_offset + bounding_client_rect.top,
+ width: bounding_client_rect.width,
+ height: bounding_client_rect.height
};
};
@@ -182,7 +185,7 @@ LocusZoom.Instance.prototype.initializeLayout = function(){
* Calculate appropriate instance dimesions from panels contained within and update instance
*/
LocusZoom.Instance.prototype.setDimensions = function(width, height){
-
+
var id;
// Update minimum allowable width and height by aggregating minimums from panels.
@@ -225,10 +228,6 @@ LocusZoom.Instance.prototype.setDimensions = function(width, height){
this.panels[panel_id].controls.position();
}
}.bind(this));
- // Reposition panel boundaries if showing
- if (this.panel_boundaries && this.panel_boundaries.showing){
- this.panel_boundaries.position();
- }
}
// If width and height arguments were NOT passed (and panels exist) then determine the instance dimensions
@@ -260,6 +259,14 @@ LocusZoom.Instance.prototype.setDimensions = function(width, height){
// If the instance has been initialized then trigger some necessary render functions
if (this.initialized){
+ // Reposition panel boundaries if showing
+ if (this.panel_boundaries && this.panel_boundaries.showing){
+ this.panel_boundaries.position();
+ }
+ // Reposition plot curtain and loader
+ this.curtain.update();
+ this.loader.update();
+ // Reposition UI layer
this.ui.render();
}
@@ -487,40 +494,138 @@ LocusZoom.Instance.prototype.initialize = function(){
};
this.ui.initialize();
- // Create the curtain object with svg element and drop/raise methods
- var curtain_svg = this.svg.append("g")
- .attr("class", "lz-curtain").style("display", "none")
- .attr("id", this.id + ".curtain");
+ // Create the curtain object with show/update/hide methods
this.curtain = {
- svg: curtain_svg,
- drop: function(message){
- this.svg.style("display", null);
- if (typeof message != "undefined"){
- try {
- this.svg.select("text").selectAll("tspan").remove();
- message.split("\n").forEach(function(line){
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "1.5em").text(line);
+ showing: false,
+ selector: null,
+ content_selector: null,
+ show: function(content, css){
+ // Generate curtain
+ if (!this.curtain.showing){
+ this.curtain.selector = d3.select(this.svg.node().parentNode).insert("div")
+ .attr("class", "lz-curtain").attr("id", this.id + ".curtain");
+ this.curtain.content_selector = this.curtain.selector.append("div").attr("class", "lz-curtain-content");
+ this.curtain.selector.append("div").attr("class", "lz-curtain-dismiss").html("Dismiss")
+ .on("click", function(){
+ this.curtain.hide();
}.bind(this));
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "2.5em")
- .attr("class", "dismiss").text("Dismiss")
- .on("click", function(){
- this.raise();
- }.bind(this));
- } catch (e){
- console.error("LocusZoom tried to render an error message but it's not a string:", message);
- }
+ this.curtain.showing = true;
}
- },
- raise: function(){
- this.svg.style("display", "none");
- }
+ return this.curtain.update(content, css);
+ }.bind(this),
+ update: function(content, css){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Apply CSS if provided
+ if (typeof css == "object" && css != null){
+ this.curtain.selector.style(css);
+ }
+ // Update size and position
+ var plot_page_origin = this.getPageOrigin();
+ this.curtain.selector.style({
+ top: plot_page_origin.y + "px",
+ left: plot_page_origin.x + "px",
+ width: this.layout.width + "px",
+ height: this.layout.height + "px",
+ });
+ this.curtain.content_selector.style({
+ "max-width": (this.layout.width - 40) + "px",
+ "max-height": (this.layout.height - 40) + "px",
+ });
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.curtain.content_selector.html(content);
+ }
+ return this.curtain;
+ }.bind(this),
+ hide: function(){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Remove curtain
+ this.curtain.selector.remove();
+ this.curtain.selector = null;
+ this.curtain.content_selector = null;
+ this.curtain.showing = false;
+ return this.curtain;
+ }.bind(this)
+ };
+
+ // Create the loader object with show/update/animate/setPercentCompleted/hide methods
+ this.loader = {
+ showing: false,
+ selector: null,
+ content_selector: null,
+ progress_selector: null,
+ cancel_selector: null,
+ show: function(content){
+ // Generate loader
+ if (!this.loader.showing){
+ this.loader.selector = d3.select(this.svg.node().parentNode).insert("div")
+ .attr("class", "lz-loader").attr("id", this.id + ".loader");
+ this.loader.content_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-content");
+ this.loader.progress_selector = this.loader.selector
+ .append("div").attr("class", "lz-loader-progress-container")
+ .append("div").attr("class", "lz-loader-progress");
+ /* TODO: figure out how to make this cancel button work
+ this.loader.cancel_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-cancel").html("Cancel")
+ .on("click", function(){
+ this.loader.hide();
+ }.bind(this));
+ */
+ this.loader.showing = true;
+ if (typeof content == "undefined"){ content = "Loading..."; }
+ }
+ return this.loader.update(content);
+ }.bind(this),
+ update: function(content, percent){
+ if (!this.loader.showing){ return this.loader; }
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.loader.content_selector.html(content);
+ }
+ // Update size and position
+ var padding = 6; // is there a better place to store/define this?
+ var plot_page_origin = this.getPageOrigin();
+ var loader_boundrect = this.loader.selector.node().getBoundingClientRect();
+ this.loader.selector.style({
+ top: (plot_page_origin.y + this.layout.height - loader_boundrect.height - padding) + "px",
+ left: (plot_page_origin.x + padding) + "px",
+ });
+ /* Uncomment this code when a functional cancel button can be shown
+ var cancel_boundrect = this.loader.cancel_selector.node().getBoundingClientRect();
+ this.loader.content_selector.style({
+ "padding-right": (cancel_boundrect.width + padding) + "px"
+ });
+ */
+ // Apply percent if provided
+ if (typeof percent == "number"){
+ this.loader.progress_selector.style({
+ width: (Math.min(Math.max(percent, 1), 100)) + "%"
+ });
+ }
+ return this.loader;
+ }.bind(this),
+ animate: function(){
+ // For when it is impossible to update with percent checkpoints - animate the loader in perpetual motion
+ this.loader.progress_selector.classed("lz-loader-progress-animated", true);
+ return this.loader;
+ }.bind(this),
+ setPercentCompleted: function(percent){
+ this.loader.progress_selector.classed("lz-loader-progress-animated", false);
+ return this.loader.update(null, percent);
+ }.bind(this),
+ hide: function(){
+ if (!this.loader.showing){ return this.loader; }
+ // Remove loader
+ this.loader.selector.remove();
+ this.loader.selector = null;
+ this.loader.content_selector = null;
+ this.loader.progress_selector = null;
+ this.loader.cancel_selector = null;
+ this.loader.showing = false;
+ return this.loader;
+ }.bind(this)
};
- this.curtain.svg.append("rect").attr("width", "100%").attr("height", "100%");
- this.curtain.svg.append("text")
- .attr("id", this.id + ".curtain_text")
- .attr("x", "1em").attr("y", "0em");
// Create the panel_boundaries object with show/position/hide methods
this.panel_boundaries = {
@@ -531,11 +636,12 @@ LocusZoom.Instance.prototype.initialize = function(){
selectors: [],
show: function(){
// Generate panel boundaries
- if (!this.showing){
+ if (!this.showing && !this.parent.curtain.showing){
this.parent.panel_ids_by_y_index.forEach(function(panel_id, panel_idx){
var selector = d3.select(this.parent.svg.node().parentNode).insert("div", ".lz-data_layer-tooltip")
.attr("class", "lz-panel-boundary")
.attr("title", "Resize panels");
+ selector.append("span");
var panel_resize_drag = d3.behavior.drag();
panel_resize_drag.on("dragstart", function(){ this.dragging = true; }.bind(this));
panel_resize_drag.on("dragend", function(){ this.dragging = false; }.bind(this));
@@ -569,29 +675,37 @@ LocusZoom.Instance.prototype.initialize = function(){
this.showing = true;
}
this.position();
+ return this;
},
position: function(){
+ if (!this.showing){ return this; }
// Position panel boundaries
var plot_page_origin = this.parent.getPageOrigin();
this.selectors.forEach(function(selector, panel_idx){
var panel_page_origin = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].getPageOrigin();
var left = plot_page_origin.x;
- var top = panel_page_origin.y + this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].layout.height - 2;
+ var top = panel_page_origin.y + this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].layout.height - 12;
var width = this.parent.layout.width - 1;
selector.style({
top: top + "px",
left: left + "px",
width: width + "px"
});
+ selector.select("span").style({
+ width: width + "px"
+ });
}.bind(this));
+ return this;
},
hide: function(){
+ if (!this.showing){ return this; }
// Remove panel boundaries
this.selectors.forEach(function(selector){
selector.remove();
});
this.selectors = [];
this.showing = false;
+ return this;
}
};
@@ -652,16 +766,19 @@ LocusZoom.Instance.prototype.initialize = function(){
}
// Update all control element values
this.update();
+ return this;
},
update: function(){
this.div.attr("width", this.parent.layout.width);
var display_width = this.parent.layout.width.toString().indexOf(".") == -1 ? this.parent.layout.width : this.parent.layout.width.toFixed(2);
var display_height = this.parent.layout.height.toString().indexOf(".") == -1 ? this.parent.layout.height : this.parent.layout.height.toFixed(2);
this.dimensions.text(display_width + "px × " + display_height + "px");
+ return this;
},
hide: function(){
this.div.remove();
this.showing = false;
+ return this;
},
generateBase64SVG: function(){
return Q.fcall(function () {
@@ -804,6 +921,11 @@ LocusZoom.Instance.prototype.applyState = function(new_state){
this.state[property] = new_state[property];
}
+ this.emit("data_requested");
+ this.panel_ids_by_y_index.forEach(function(panel_id){
+ this.panels[panel_id].emit("data_requested");
+ }.bind(this));
+
this.remap_promises = [];
for (var id in this.panels){
this.remap_promises.push(this.panels[id].reMap());
diff --git a/assets/js/app/Panel.js b/assets/js/app/Panel.js
index b4e54e02..7c5d24f1 100644
--- a/assets/js/app/Panel.js
+++ b/assets/js/app/Panel.js
@@ -78,6 +78,7 @@ LocusZoom.Panel = function(layout, parent) {
// Event hooks
this.event_hooks = {
"layout_changed": [],
+ "data_requested": [],
"data_rendered": [],
"element_clicked": []
};
@@ -211,7 +212,11 @@ LocusZoom.Panel.prototype.setDimensions = function(width, height){
this.svg.clipRect.attr("width", this.layout.width).attr("height", this.layout.height);
}
- if (this.initialized){ this.render(); }
+ if (this.initialized){
+ this.render();
+ this.curtain.update();
+ this.loader.update();
+ }
return this;
};
@@ -270,41 +275,138 @@ LocusZoom.Panel.prototype.initialize = function(){
.attr("id", this.getBaseId() + ".panel")
.attr("clip-path", "url(#" + this.getBaseId() + ".clip)");
- // Append a curtain element with svg element and drop/raise methods
- var panel_curtain_svg = this.svg.container.append("g")
- .attr("id", this.getBaseId() + ".curtain")
- .attr("clip-path", "url(#" + this.getBaseId() + ".clip)")
- .attr("class", "lz-curtain").style("display", "none");
+ // Create the curtain object with show/update/hide methods
this.curtain = {
- svg: panel_curtain_svg,
- drop: function(message){
- this.svg.style("display", null);
- if (typeof message != "undefined"){
- try {
- this.svg.select("text").selectAll("tspan").remove();
- message.split("\n").forEach(function(line){
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "1.5em").text(line);
+ showing: false,
+ selector: null,
+ content_selector: null,
+ show: function(content, css){
+ // Generate curtain
+ if (!this.curtain.showing){
+ this.curtain.selector = d3.select(this.parent.svg.node().parentNode).insert("div")
+ .attr("class", "lz-curtain").attr("id", this.id + ".curtain");
+ this.curtain.content_selector = this.curtain.selector.append("div").attr("class", "lz-curtain-content");
+ this.curtain.selector.append("div").attr("class", "lz-curtain-dismiss").html("Dismiss")
+ .on("click", function(){
+ this.curtain.hide();
}.bind(this));
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "2.5em")
- .attr("class", "dismiss").text("Dismiss")
- .on("click", function(){
- this.raise();
- }.bind(this));
- } catch (e){
- console.error("LocusZoom tried to render an error message but it's not a string:", message);
- }
+ this.curtain.showing = true;
}
- },
- raise: function(){
- this.svg.style("display", "none");
- }
+ return this.curtain.update(content, css);
+ }.bind(this),
+ update: function(content, css){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Apply CSS if provided
+ if (typeof css == "object"){
+ this.curtain.selector.style(css);
+ }
+ // Update size and position
+ var panel_page_origin = this.getPageOrigin();
+ this.curtain.selector.style({
+ top: panel_page_origin.y + "px",
+ left: panel_page_origin.x + "px",
+ width: this.layout.width + "px",
+ height: this.layout.height + "px",
+ });
+ this.curtain.content_selector.style({
+ "max-width": (this.layout.width - 40) + "px",
+ "max-height": (this.layout.height - 40) + "px",
+ });
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.curtain.content_selector.html(content);
+ }
+ return this.curtain;
+ }.bind(this),
+ hide: function(){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Remove curtain
+ this.curtain.selector.remove();
+ this.curtain.selector = null;
+ this.curtain.content_selector = null;
+ this.curtain.showing = false;
+ return this.curtain;
+ }.bind(this)
+ };
+
+ // Create the loader object with show/update/animate/setPercentCompleted/hide methods
+ this.loader = {
+ showing: false,
+ selector: null,
+ content_selector: null,
+ progress_selector: null,
+ cancel_selector: null,
+ show: function(content){
+ // Generate loader
+ if (!this.loader.showing){
+ this.loader.selector = d3.select(this.parent.svg.node().parentNode).insert("div")
+ .attr("class", "lz-loader").attr("id", this.id + ".loader");
+ this.loader.content_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-content");
+ this.loader.progress_selector = this.loader.selector
+ .append("div").attr("class", "lz-loader-progress-container")
+ .append("div").attr("class", "lz-loader-progress");
+ /* TODO: figure out how to make this cancel button work
+ this.loader.cancel_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-cancel").html("Cancel")
+ .on("click", function(){
+ this.loader.hide();
+ }.bind(this));
+ */
+ this.loader.showing = true;
+ if (typeof content == "undefined"){ content = "Loading..."; }
+ }
+ return this.loader.update(content);
+ }.bind(this),
+ update: function(content, percent){
+ if (!this.loader.showing){ return this.loader; }
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.loader.content_selector.html(content);
+ }
+ // Update size and position
+ var padding = 6; // is there a better place to store/define this?
+ var panel_page_origin = this.getPageOrigin();
+ var loader_boundrect = this.loader.selector.node().getBoundingClientRect();
+ this.loader.selector.style({
+ top: (panel_page_origin.y + this.layout.height - loader_boundrect.height - padding) + "px",
+ left: (panel_page_origin.x + padding) + "px",
+ });
+ /* Uncomment this code when a functional cancel button can be shown
+ var cancel_boundrect = this.loader.cancel_selector.node().getBoundingClientRect();
+ this.loader.content_selector.style({
+ "padding-right": (cancel_boundrect.width + padding) + "px"
+ });
+ */
+ // Apply percent if provided
+ if (typeof percent == "number"){
+ this.loader.progress_selector.style({
+ width: (Math.min(Math.max(percent, 1), 100)) + "%"
+ });
+ }
+ return this.loader;
+ }.bind(this),
+ animate: function(){
+ // For when it is impossible to update with percent checkpoints - animate the loader in perpetual motion
+ this.loader.progress_selector.classed("lz-loader-progress-animated", true);
+ return this.loader;
+ }.bind(this),
+ setPercentCompleted: function(percent){
+ this.loader.progress_selector.classed("lz-loader-progress-animated", false);
+ return this.loader.update(null, percent);
+ }.bind(this),
+ hide: function(){
+ if (!this.loader.showing){ return this.loader; }
+ // Remove loader
+ this.loader.selector.remove();
+ this.loader.selector = null;
+ this.loader.content_selector = null;
+ this.loader.progress_selector = null;
+ this.loader.cancel_selector = null;
+ this.loader.showing = false;
+ return this.loader;
+ }.bind(this)
};
- this.curtain.svg.append("rect").attr("width", "100%").attr("height", "100%");
- this.curtain.svg.append("text")
- .attr("id", this.getBaseId() + ".curtain_text")
- .attr("x", "1em").attr("y", "0em");
// Initialize controls element
this.controls = {
@@ -312,7 +414,8 @@ LocusZoom.Panel.prototype.initialize = function(){
hide_timeout: null,
link_selectors: {},
show: function(){
- if (!this.layout.controls || this.controls.selector){ return; }
+ if (!this.layout.controls || this.controls.selector){ return this.controls; }
+ if (this.curtain.showing || this.parent.curtain.showing){ return this.controls; }
this.controls.selector = d3.select(this.parent.svg.node().parentNode).insert("div", ".lz-data_layer-tooltip")
.attr("class", "lz-locuszoom-controls lz-locuszoom-panel-controls")
.attr("id", this.getBaseId() + ".controls")
@@ -354,7 +457,7 @@ LocusZoom.Panel.prototype.initialize = function(){
.style({ "font-weight": "bold" })
.text("?")
.on("click", function(){
- if (this.controls.description.is_showing){
+ if (this.controls.description.showing){
this.controls.description.hide();
} else {
this.controls.description.show();
@@ -362,7 +465,7 @@ LocusZoom.Panel.prototype.initialize = function(){
}
}.bind(this));
this.controls.description = {
- is_showing: false,
+ showing: false,
selector: null,
show: function(){
this.controls.link_selectors.description.attr("class", "lz-panel-controls-button-selected");
@@ -370,9 +473,11 @@ LocusZoom.Panel.prototype.initialize = function(){
.attr("class", "lz-panel-description")
.attr("id", this.getBaseId() + ".description")
.html(this.layout.description);
- this.controls.description.is_showing = true;
+ this.controls.description.showing = true;
+ return this.controls.description;
}.bind(this),
position: function(){
+ if (!this.controls.description.showing){ return this.controls.description; }
var padding = 4; // is there a better place to store this?
var page_origin = this.getPageOrigin();
var controls_client_rect = this.controls.selector.node().getBoundingClientRect();
@@ -380,13 +485,17 @@ LocusZoom.Panel.prototype.initialize = function(){
var top = (page_origin.y + controls_client_rect.height + padding).toString() + "px";
var left = Math.max(page_origin.x + this.layout.width - desc_client_rect.width - padding, page_origin.x + padding).toString() + "px";
this.controls.description.selector.style({ top: top, left: left });
+ return this.controls.description;
}.bind(this),
hide: function(){
+ if (!this.controls.description.showing){ return this.controls.description; }
this.controls.link_selectors.description.attr("class", "lz-panel-controls-button");
this.controls.description.selector.remove();
- this.controls.description.is_showing = false;
+ this.controls.description.showing = false;
+ return this.controls.description;
}.bind(this)
};
+ return this.controls;
}
// Remove button
if (this.layout.controls.remove){
@@ -397,7 +506,7 @@ LocusZoom.Panel.prototype.initialize = function(){
.text("×")
.on("click", function(){
// Hide description and controls
- if (this.controls.description && this.controls.description.is_showing){ this.controls.description.hide(); }
+ if (this.controls.description && this.controls.description.showing){ this.controls.description.hide(); }
this.controls.hide();
// Remove mouse event listeners for these controls
d3.select(this.parent.svg.node().parentNode).on("mouseover." + this.getBaseId() + ".controls", null);
@@ -405,16 +514,18 @@ LocusZoom.Panel.prototype.initialize = function(){
// Remove the panel
this.parent.removePanel(this.id);
}.bind(this));
+ return this.controls;
}
}.bind(this),
position: function(){
+ if (!this.layout.controls || !this.controls.selector){ return this.controls; }
var page_origin = this.getPageOrigin();
var client_rect = this.controls.selector.node().getBoundingClientRect();
var top = page_origin.y.toString() + "px";
var left = (page_origin.x + this.layout.width - client_rect.width).toString() + "px";
this.controls.selector.style({ position: "absolute", top: top, left: left });
// Position description box if it's showing
- if (this.controls.description && this.controls.description.is_showing){
+ if (this.controls.description && this.controls.description.showing){
this.controls.description.position();
}
// Apply appropriate classes to reposition buttons as needed
@@ -424,15 +535,17 @@ LocusZoom.Panel.prototype.initialize = function(){
if (this.controls.link_selectors.reposition_down){
this.controls.link_selectors.reposition_down.attr("class", (this.layout.y_index == this.parent.panel_ids_by_y_index.length - 1) ? "lz-panel-controls-button-disabled" : "lz-panel-controls-button");
}
+ return this.controls;
}.bind(this),
hide: function(){
- if (!this.layout.controls || !this.controls.selector){ return; }
+ if (!this.layout.controls || !this.controls.selector){ return this.controls; }
// Do not hide if this panel is showing a description
- if (this.controls.description && this.controls.description.is_showing){ return; }
+ if (this.controls.description && this.controls.description.showing){ return this.controls; }
// Do not hide if actively in an instance-level drag event
- if (this.parent.ui.dragging || this.parent.panel_boundaries.dragging){ return; }
+ if (this.parent.ui.dragging || this.parent.panel_boundaries.dragging){ return this.controls; }
this.controls.selector.remove();
this.controls.selector = null;
+ return this.controls;
}.bind(this)
};
@@ -574,7 +687,7 @@ LocusZoom.Panel.prototype.reMap = function(){
this.data_promises.push(this.data_layers[id].reMap());
} catch (error) {
console.log(error);
- this.curtain.drop(error);
+ this.curtain.show(error);
}
}
// When all finished trigger a render
@@ -588,7 +701,7 @@ LocusZoom.Panel.prototype.reMap = function(){
}.bind(this))
.catch(function(error){
console.log(error);
- this.curtain.drop(error);
+ this.curtain.show(error);
}.bind(this));
};
diff --git a/demo.html b/demo.html
index f6935f90..a337a412 100644
--- a/demo.html
+++ b/demo.html
@@ -69,7 +69,18 @@
Top Hits
layout = LocusZoom.mergeLayouts(layout, LocusZoom.StandardLayout);
// Populate the div with a LocusZoom plot using the default layout
- var demo_instance = LocusZoom.populate("#lz-1", data_sources, layout);
+ var plot = LocusZoom.populate("#lz-1", data_sources, layout);
+
+ // Create event hooks to clear the loader whenever a panel renders new data
+ plot.layout.panels.forEach(function(panel){
+ plot.panels[panel.id].loader.show("Loading...").animate();
+ plot.panels[panel.id].on("data_requested", function(){
+ this.loader.show("Loading...").animate();
+ });
+ plot.panels[panel.id].on("data_rendered", function(){
+ this.loader.hide();
+ });
+ });
/**********************************************************************************
All of the following code sets up an example form with top hit buttons to jump to
@@ -119,7 +130,7 @@ Top Hits
start = +pos - 300000
end = +pos + 300000
}
- demo_instance.applyState({ chr: chr, start: start, end: end});
+ plot.applyState({ chr: chr, start: start, end: end });
}
function jumpTo(region) {
@@ -132,16 +143,16 @@ Top Hits
start = +pos - 300000
end = +pos + 300000
}
- demo_instance.applyState({ chr: chr, start: start, end: end, ldrefvar: "" });
+ plot.applyState({ chr: chr, start: start, end: end, ldrefvar: "" });
populateForms();
return(false);
}
// Fill demo forms with values already loaded into LocusZoom objects
function populateForms(){
- $("#lz-1_region")[0].value = demo_instance.state.chr + ":"
- + demo_instance.state.start + "-"
- + demo_instance.state.end;
+ $("#lz-1_region")[0].value = plot.state.chr + ":"
+ + plot.state.start + "-"
+ + plot.state.end;
}
function listHits() {
diff --git a/locuszoom.app.js b/locuszoom.app.js
index 4d1bec9d..2e73561b 100644
--- a/locuszoom.app.js
+++ b/locuszoom.app.js
@@ -3168,6 +3168,7 @@ LocusZoom.Instance = function(id, datasource, layout) {
// Event hooks
this.event_hooks = {
"layout_changed": [],
+ "data_requested": [],
"data_rendered": [],
"element_clicked": []
};
@@ -3207,7 +3208,9 @@ LocusZoom.Instance = function(id, datasource, layout) {
}
return {
x: x_offset + bounding_client_rect.left,
- y: y_offset + bounding_client_rect.top
+ y: y_offset + bounding_client_rect.top,
+ width: bounding_client_rect.width,
+ height: bounding_client_rect.height
};
};
@@ -3298,7 +3301,7 @@ LocusZoom.Instance.prototype.initializeLayout = function(){
* Calculate appropriate instance dimesions from panels contained within and update instance
*/
LocusZoom.Instance.prototype.setDimensions = function(width, height){
-
+
var id;
// Update minimum allowable width and height by aggregating minimums from panels.
@@ -3341,10 +3344,6 @@ LocusZoom.Instance.prototype.setDimensions = function(width, height){
this.panels[panel_id].controls.position();
}
}.bind(this));
- // Reposition panel boundaries if showing
- if (this.panel_boundaries && this.panel_boundaries.showing){
- this.panel_boundaries.position();
- }
}
// If width and height arguments were NOT passed (and panels exist) then determine the instance dimensions
@@ -3376,6 +3375,14 @@ LocusZoom.Instance.prototype.setDimensions = function(width, height){
// If the instance has been initialized then trigger some necessary render functions
if (this.initialized){
+ // Reposition panel boundaries if showing
+ if (this.panel_boundaries && this.panel_boundaries.showing){
+ this.panel_boundaries.position();
+ }
+ // Reposition plot curtain and loader
+ this.curtain.update();
+ this.loader.update();
+ // Reposition UI layer
this.ui.render();
}
@@ -3603,40 +3610,138 @@ LocusZoom.Instance.prototype.initialize = function(){
};
this.ui.initialize();
- // Create the curtain object with svg element and drop/raise methods
- var curtain_svg = this.svg.append("g")
- .attr("class", "lz-curtain").style("display", "none")
- .attr("id", this.id + ".curtain");
+ // Create the curtain object with show/update/hide methods
this.curtain = {
- svg: curtain_svg,
- drop: function(message){
- this.svg.style("display", null);
- if (typeof message != "undefined"){
- try {
- this.svg.select("text").selectAll("tspan").remove();
- message.split("\n").forEach(function(line){
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "1.5em").text(line);
+ showing: false,
+ selector: null,
+ content_selector: null,
+ show: function(content, css){
+ // Generate curtain
+ if (!this.curtain.showing){
+ this.curtain.selector = d3.select(this.svg.node().parentNode).insert("div")
+ .attr("class", "lz-curtain").attr("id", this.id + ".curtain");
+ this.curtain.content_selector = this.curtain.selector.append("div").attr("class", "lz-curtain-content");
+ this.curtain.selector.append("div").attr("class", "lz-curtain-dismiss").html("Dismiss")
+ .on("click", function(){
+ this.curtain.hide();
}.bind(this));
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "2.5em")
- .attr("class", "dismiss").text("Dismiss")
- .on("click", function(){
- this.raise();
- }.bind(this));
- } catch (e){
- console.error("LocusZoom tried to render an error message but it's not a string:", message);
- }
+ this.curtain.showing = true;
}
- },
- raise: function(){
- this.svg.style("display", "none");
- }
+ return this.curtain.update(content, css);
+ }.bind(this),
+ update: function(content, css){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Apply CSS if provided
+ if (typeof css == "object" && css != null){
+ this.curtain.selector.style(css);
+ }
+ // Update size and position
+ var plot_page_origin = this.getPageOrigin();
+ this.curtain.selector.style({
+ top: plot_page_origin.y + "px",
+ left: plot_page_origin.x + "px",
+ width: this.layout.width + "px",
+ height: this.layout.height + "px",
+ });
+ this.curtain.content_selector.style({
+ "max-width": (this.layout.width - 40) + "px",
+ "max-height": (this.layout.height - 40) + "px",
+ });
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.curtain.content_selector.html(content);
+ }
+ return this.curtain;
+ }.bind(this),
+ hide: function(){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Remove curtain
+ this.curtain.selector.remove();
+ this.curtain.selector = null;
+ this.curtain.content_selector = null;
+ this.curtain.showing = false;
+ return this.curtain;
+ }.bind(this)
+ };
+
+ // Create the loader object with show/update/animate/setPercentCompleted/hide methods
+ this.loader = {
+ showing: false,
+ selector: null,
+ content_selector: null,
+ progress_selector: null,
+ cancel_selector: null,
+ show: function(content){
+ // Generate loader
+ if (!this.loader.showing){
+ this.loader.selector = d3.select(this.svg.node().parentNode).insert("div")
+ .attr("class", "lz-loader").attr("id", this.id + ".loader");
+ this.loader.content_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-content");
+ this.loader.progress_selector = this.loader.selector
+ .append("div").attr("class", "lz-loader-progress-container")
+ .append("div").attr("class", "lz-loader-progress");
+ /* TODO: figure out how to make this cancel button work
+ this.loader.cancel_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-cancel").html("Cancel")
+ .on("click", function(){
+ this.loader.hide();
+ }.bind(this));
+ */
+ this.loader.showing = true;
+ if (typeof content == "undefined"){ content = "Loading..."; }
+ }
+ return this.loader.update(content);
+ }.bind(this),
+ update: function(content, percent){
+ if (!this.loader.showing){ return this.loader; }
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.loader.content_selector.html(content);
+ }
+ // Update size and position
+ var padding = 6; // is there a better place to store/define this?
+ var plot_page_origin = this.getPageOrigin();
+ var loader_boundrect = this.loader.selector.node().getBoundingClientRect();
+ this.loader.selector.style({
+ top: (plot_page_origin.y + this.layout.height - loader_boundrect.height - padding) + "px",
+ left: (plot_page_origin.x + padding) + "px",
+ });
+ /* Uncomment this code when a functional cancel button can be shown
+ var cancel_boundrect = this.loader.cancel_selector.node().getBoundingClientRect();
+ this.loader.content_selector.style({
+ "padding-right": (cancel_boundrect.width + padding) + "px"
+ });
+ */
+ // Apply percent if provided
+ if (typeof percent == "number"){
+ this.loader.progress_selector.style({
+ width: (Math.min(Math.max(percent, 1), 100)) + "%"
+ });
+ }
+ return this.loader;
+ }.bind(this),
+ animate: function(){
+ // For when it is impossible to update with percent checkpoints - animate the loader in perpetual motion
+ this.loader.progress_selector.classed("lz-loader-progress-animated", true);
+ return this.loader;
+ }.bind(this),
+ setPercentCompleted: function(percent){
+ this.loader.progress_selector.classed("lz-loader-progress-animated", false);
+ return this.loader.update(null, percent);
+ }.bind(this),
+ hide: function(){
+ if (!this.loader.showing){ return this.loader; }
+ // Remove loader
+ this.loader.selector.remove();
+ this.loader.selector = null;
+ this.loader.content_selector = null;
+ this.loader.progress_selector = null;
+ this.loader.cancel_selector = null;
+ this.loader.showing = false;
+ return this.loader;
+ }.bind(this)
};
- this.curtain.svg.append("rect").attr("width", "100%").attr("height", "100%");
- this.curtain.svg.append("text")
- .attr("id", this.id + ".curtain_text")
- .attr("x", "1em").attr("y", "0em");
// Create the panel_boundaries object with show/position/hide methods
this.panel_boundaries = {
@@ -3647,11 +3752,12 @@ LocusZoom.Instance.prototype.initialize = function(){
selectors: [],
show: function(){
// Generate panel boundaries
- if (!this.showing){
+ if (!this.showing && !this.parent.curtain.showing){
this.parent.panel_ids_by_y_index.forEach(function(panel_id, panel_idx){
var selector = d3.select(this.parent.svg.node().parentNode).insert("div", ".lz-data_layer-tooltip")
.attr("class", "lz-panel-boundary")
.attr("title", "Resize panels");
+ selector.append("span");
var panel_resize_drag = d3.behavior.drag();
panel_resize_drag.on("dragstart", function(){ this.dragging = true; }.bind(this));
panel_resize_drag.on("dragend", function(){ this.dragging = false; }.bind(this));
@@ -3685,29 +3791,37 @@ LocusZoom.Instance.prototype.initialize = function(){
this.showing = true;
}
this.position();
+ return this;
},
position: function(){
+ if (!this.showing){ return this; }
// Position panel boundaries
var plot_page_origin = this.parent.getPageOrigin();
this.selectors.forEach(function(selector, panel_idx){
var panel_page_origin = this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].getPageOrigin();
var left = plot_page_origin.x;
- var top = panel_page_origin.y + this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].layout.height - 2;
+ var top = panel_page_origin.y + this.parent.panels[this.parent.panel_ids_by_y_index[panel_idx]].layout.height - 12;
var width = this.parent.layout.width - 1;
selector.style({
top: top + "px",
left: left + "px",
width: width + "px"
});
+ selector.select("span").style({
+ width: width + "px"
+ });
}.bind(this));
+ return this;
},
hide: function(){
+ if (!this.showing){ return this; }
// Remove panel boundaries
this.selectors.forEach(function(selector){
selector.remove();
});
this.selectors = [];
this.showing = false;
+ return this;
}
};
@@ -3768,16 +3882,19 @@ LocusZoom.Instance.prototype.initialize = function(){
}
// Update all control element values
this.update();
+ return this;
},
update: function(){
this.div.attr("width", this.parent.layout.width);
var display_width = this.parent.layout.width.toString().indexOf(".") == -1 ? this.parent.layout.width : this.parent.layout.width.toFixed(2);
var display_height = this.parent.layout.height.toString().indexOf(".") == -1 ? this.parent.layout.height : this.parent.layout.height.toFixed(2);
this.dimensions.text(display_width + "px × " + display_height + "px");
+ return this;
},
hide: function(){
this.div.remove();
this.showing = false;
+ return this;
},
generateBase64SVG: function(){
return Q.fcall(function () {
@@ -3920,6 +4037,11 @@ LocusZoom.Instance.prototype.applyState = function(new_state){
this.state[property] = new_state[property];
}
+ this.emit("data_requested");
+ this.panel_ids_by_y_index.forEach(function(panel_id){
+ this.panels[panel_id].emit("data_requested");
+ }.bind(this));
+
this.remap_promises = [];
for (var id in this.panels){
this.remap_promises.push(this.panels[id].reMap());
@@ -4043,6 +4165,7 @@ LocusZoom.Panel = function(layout, parent) {
// Event hooks
this.event_hooks = {
"layout_changed": [],
+ "data_requested": [],
"data_rendered": [],
"element_clicked": []
};
@@ -4176,7 +4299,11 @@ LocusZoom.Panel.prototype.setDimensions = function(width, height){
this.svg.clipRect.attr("width", this.layout.width).attr("height", this.layout.height);
}
- if (this.initialized){ this.render(); }
+ if (this.initialized){
+ this.render();
+ this.curtain.update();
+ this.loader.update();
+ }
return this;
};
@@ -4235,41 +4362,138 @@ LocusZoom.Panel.prototype.initialize = function(){
.attr("id", this.getBaseId() + ".panel")
.attr("clip-path", "url(#" + this.getBaseId() + ".clip)");
- // Append a curtain element with svg element and drop/raise methods
- var panel_curtain_svg = this.svg.container.append("g")
- .attr("id", this.getBaseId() + ".curtain")
- .attr("clip-path", "url(#" + this.getBaseId() + ".clip)")
- .attr("class", "lz-curtain").style("display", "none");
+ // Create the curtain object with show/update/hide methods
this.curtain = {
- svg: panel_curtain_svg,
- drop: function(message){
- this.svg.style("display", null);
- if (typeof message != "undefined"){
- try {
- this.svg.select("text").selectAll("tspan").remove();
- message.split("\n").forEach(function(line){
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "1.5em").text(line);
+ showing: false,
+ selector: null,
+ content_selector: null,
+ show: function(content, css){
+ // Generate curtain
+ if (!this.curtain.showing){
+ this.curtain.selector = d3.select(this.parent.svg.node().parentNode).insert("div")
+ .attr("class", "lz-curtain").attr("id", this.id + ".curtain");
+ this.curtain.content_selector = this.curtain.selector.append("div").attr("class", "lz-curtain-content");
+ this.curtain.selector.append("div").attr("class", "lz-curtain-dismiss").html("Dismiss")
+ .on("click", function(){
+ this.curtain.hide();
}.bind(this));
- this.svg.select("text").append("tspan")
- .attr("x", "1em").attr("dy", "2.5em")
- .attr("class", "dismiss").text("Dismiss")
- .on("click", function(){
- this.raise();
- }.bind(this));
- } catch (e){
- console.error("LocusZoom tried to render an error message but it's not a string:", message);
- }
+ this.curtain.showing = true;
}
- },
- raise: function(){
- this.svg.style("display", "none");
- }
+ return this.curtain.update(content, css);
+ }.bind(this),
+ update: function(content, css){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Apply CSS if provided
+ if (typeof css == "object"){
+ this.curtain.selector.style(css);
+ }
+ // Update size and position
+ var panel_page_origin = this.getPageOrigin();
+ this.curtain.selector.style({
+ top: panel_page_origin.y + "px",
+ left: panel_page_origin.x + "px",
+ width: this.layout.width + "px",
+ height: this.layout.height + "px",
+ });
+ this.curtain.content_selector.style({
+ "max-width": (this.layout.width - 40) + "px",
+ "max-height": (this.layout.height - 40) + "px",
+ });
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.curtain.content_selector.html(content);
+ }
+ return this.curtain;
+ }.bind(this),
+ hide: function(){
+ if (!this.curtain.showing){ return this.curtain; }
+ // Remove curtain
+ this.curtain.selector.remove();
+ this.curtain.selector = null;
+ this.curtain.content_selector = null;
+ this.curtain.showing = false;
+ return this.curtain;
+ }.bind(this)
+ };
+
+ // Create the loader object with show/update/animate/setPercentCompleted/hide methods
+ this.loader = {
+ showing: false,
+ selector: null,
+ content_selector: null,
+ progress_selector: null,
+ cancel_selector: null,
+ show: function(content){
+ // Generate loader
+ if (!this.loader.showing){
+ this.loader.selector = d3.select(this.parent.svg.node().parentNode).insert("div")
+ .attr("class", "lz-loader").attr("id", this.id + ".loader");
+ this.loader.content_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-content");
+ this.loader.progress_selector = this.loader.selector
+ .append("div").attr("class", "lz-loader-progress-container")
+ .append("div").attr("class", "lz-loader-progress");
+ /* TODO: figure out how to make this cancel button work
+ this.loader.cancel_selector = this.loader.selector.append("div")
+ .attr("class", "lz-loader-cancel").html("Cancel")
+ .on("click", function(){
+ this.loader.hide();
+ }.bind(this));
+ */
+ this.loader.showing = true;
+ if (typeof content == "undefined"){ content = "Loading..."; }
+ }
+ return this.loader.update(content);
+ }.bind(this),
+ update: function(content, percent){
+ if (!this.loader.showing){ return this.loader; }
+ // Apply content if provided
+ if (typeof content == "string"){
+ this.loader.content_selector.html(content);
+ }
+ // Update size and position
+ var padding = 6; // is there a better place to store/define this?
+ var panel_page_origin = this.getPageOrigin();
+ var loader_boundrect = this.loader.selector.node().getBoundingClientRect();
+ this.loader.selector.style({
+ top: (panel_page_origin.y + this.layout.height - loader_boundrect.height - padding) + "px",
+ left: (panel_page_origin.x + padding) + "px",
+ });
+ /* Uncomment this code when a functional cancel button can be shown
+ var cancel_boundrect = this.loader.cancel_selector.node().getBoundingClientRect();
+ this.loader.content_selector.style({
+ "padding-right": (cancel_boundrect.width + padding) + "px"
+ });
+ */
+ // Apply percent if provided
+ if (typeof percent == "number"){
+ this.loader.progress_selector.style({
+ width: (Math.min(Math.max(percent, 1), 100)) + "%"
+ });
+ }
+ return this.loader;
+ }.bind(this),
+ animate: function(){
+ // For when it is impossible to update with percent checkpoints - animate the loader in perpetual motion
+ this.loader.progress_selector.classed("lz-loader-progress-animated", true);
+ return this.loader;
+ }.bind(this),
+ setPercentCompleted: function(percent){
+ this.loader.progress_selector.classed("lz-loader-progress-animated", false);
+ return this.loader.update(null, percent);
+ }.bind(this),
+ hide: function(){
+ if (!this.loader.showing){ return this.loader; }
+ // Remove loader
+ this.loader.selector.remove();
+ this.loader.selector = null;
+ this.loader.content_selector = null;
+ this.loader.progress_selector = null;
+ this.loader.cancel_selector = null;
+ this.loader.showing = false;
+ return this.loader;
+ }.bind(this)
};
- this.curtain.svg.append("rect").attr("width", "100%").attr("height", "100%");
- this.curtain.svg.append("text")
- .attr("id", this.getBaseId() + ".curtain_text")
- .attr("x", "1em").attr("y", "0em");
// Initialize controls element
this.controls = {
@@ -4277,7 +4501,8 @@ LocusZoom.Panel.prototype.initialize = function(){
hide_timeout: null,
link_selectors: {},
show: function(){
- if (!this.layout.controls || this.controls.selector){ return; }
+ if (!this.layout.controls || this.controls.selector){ return this.controls; }
+ if (this.curtain.showing || this.parent.curtain.showing){ return this.controls; }
this.controls.selector = d3.select(this.parent.svg.node().parentNode).insert("div", ".lz-data_layer-tooltip")
.attr("class", "lz-locuszoom-controls lz-locuszoom-panel-controls")
.attr("id", this.getBaseId() + ".controls")
@@ -4319,7 +4544,7 @@ LocusZoom.Panel.prototype.initialize = function(){
.style({ "font-weight": "bold" })
.text("?")
.on("click", function(){
- if (this.controls.description.is_showing){
+ if (this.controls.description.showing){
this.controls.description.hide();
} else {
this.controls.description.show();
@@ -4327,7 +4552,7 @@ LocusZoom.Panel.prototype.initialize = function(){
}
}.bind(this));
this.controls.description = {
- is_showing: false,
+ showing: false,
selector: null,
show: function(){
this.controls.link_selectors.description.attr("class", "lz-panel-controls-button-selected");
@@ -4335,9 +4560,11 @@ LocusZoom.Panel.prototype.initialize = function(){
.attr("class", "lz-panel-description")
.attr("id", this.getBaseId() + ".description")
.html(this.layout.description);
- this.controls.description.is_showing = true;
+ this.controls.description.showing = true;
+ return this.controls.description;
}.bind(this),
position: function(){
+ if (!this.controls.description.showing){ return this.controls.description; }
var padding = 4; // is there a better place to store this?
var page_origin = this.getPageOrigin();
var controls_client_rect = this.controls.selector.node().getBoundingClientRect();
@@ -4345,13 +4572,17 @@ LocusZoom.Panel.prototype.initialize = function(){
var top = (page_origin.y + controls_client_rect.height + padding).toString() + "px";
var left = Math.max(page_origin.x + this.layout.width - desc_client_rect.width - padding, page_origin.x + padding).toString() + "px";
this.controls.description.selector.style({ top: top, left: left });
+ return this.controls.description;
}.bind(this),
hide: function(){
+ if (!this.controls.description.showing){ return this.controls.description; }
this.controls.link_selectors.description.attr("class", "lz-panel-controls-button");
this.controls.description.selector.remove();
- this.controls.description.is_showing = false;
+ this.controls.description.showing = false;
+ return this.controls.description;
}.bind(this)
};
+ return this.controls;
}
// Remove button
if (this.layout.controls.remove){
@@ -4362,7 +4593,7 @@ LocusZoom.Panel.prototype.initialize = function(){
.text("×")
.on("click", function(){
// Hide description and controls
- if (this.controls.description && this.controls.description.is_showing){ this.controls.description.hide(); }
+ if (this.controls.description && this.controls.description.showing){ this.controls.description.hide(); }
this.controls.hide();
// Remove mouse event listeners for these controls
d3.select(this.parent.svg.node().parentNode).on("mouseover." + this.getBaseId() + ".controls", null);
@@ -4370,16 +4601,18 @@ LocusZoom.Panel.prototype.initialize = function(){
// Remove the panel
this.parent.removePanel(this.id);
}.bind(this));
+ return this.controls;
}
}.bind(this),
position: function(){
+ if (!this.layout.controls || !this.controls.selector){ return this.controls; }
var page_origin = this.getPageOrigin();
var client_rect = this.controls.selector.node().getBoundingClientRect();
var top = page_origin.y.toString() + "px";
var left = (page_origin.x + this.layout.width - client_rect.width).toString() + "px";
this.controls.selector.style({ position: "absolute", top: top, left: left });
// Position description box if it's showing
- if (this.controls.description && this.controls.description.is_showing){
+ if (this.controls.description && this.controls.description.showing){
this.controls.description.position();
}
// Apply appropriate classes to reposition buttons as needed
@@ -4389,15 +4622,17 @@ LocusZoom.Panel.prototype.initialize = function(){
if (this.controls.link_selectors.reposition_down){
this.controls.link_selectors.reposition_down.attr("class", (this.layout.y_index == this.parent.panel_ids_by_y_index.length - 1) ? "lz-panel-controls-button-disabled" : "lz-panel-controls-button");
}
+ return this.controls;
}.bind(this),
hide: function(){
- if (!this.layout.controls || !this.controls.selector){ return; }
+ if (!this.layout.controls || !this.controls.selector){ return this.controls; }
// Do not hide if this panel is showing a description
- if (this.controls.description && this.controls.description.is_showing){ return; }
+ if (this.controls.description && this.controls.description.showing){ return this.controls; }
// Do not hide if actively in an instance-level drag event
- if (this.parent.ui.dragging || this.parent.panel_boundaries.dragging){ return; }
+ if (this.parent.ui.dragging || this.parent.panel_boundaries.dragging){ return this.controls; }
this.controls.selector.remove();
this.controls.selector = null;
+ return this.controls;
}.bind(this)
};
@@ -4539,7 +4774,7 @@ LocusZoom.Panel.prototype.reMap = function(){
this.data_promises.push(this.data_layers[id].reMap());
} catch (error) {
console.log(error);
- this.curtain.drop(error);
+ this.curtain.show(error);
}
}
// When all finished trigger a render
@@ -4553,7 +4788,7 @@ LocusZoom.Panel.prototype.reMap = function(){
}.bind(this))
.catch(function(error){
console.log(error);
- this.curtain.drop(error);
+ this.curtain.show(error);
}.bind(this));
};
diff --git a/locuszoom.css b/locuszoom.css
index 065219bf..0763f4b6 100644
--- a/locuszoom.css
+++ b/locuszoom.css
@@ -7,18 +7,6 @@ svg.lz-locuszoom {
svg.lz-locuszoom rect.lz-clickarea {
fill: black;
fill-opacity: 0; }
- svg.lz-locuszoom .lz-curtain rect {
- fill: #d2d2d2;
- fill-opacity: 0.85; }
- svg.lz-locuszoom .lz-curtain text, svg.lz-locuszoom .lz-curtain tspan {
- fill: black;
- font-weight: 600;
- font-size: 14px;
- text-anchor: start;
- alignment-baseline: hanging; }
- svg.lz-locuszoom .lz-curtain tspan.dismiss {
- fill: #31708f;
- cursor: pointer; }
svg.lz-locuszoom .lz-mouse_guide rect {
fill: #d2d2d2;
fill-opacity: 0.85; }
@@ -263,6 +251,95 @@ div.lz-data_layer-tooltip-arrow_bottom_right {
display: inline-block;
overflow: hidden; }
+.lz-curtain {
+ position: absolute;
+ font-size: 2em;
+ font-weight: 600;
+ background: rgba(216, 216, 216, 0.8); }
+ .lz-curtain .lz-curtain-content {
+ position: absolute;
+ display: block;
+ width: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ margin: 0 auto;
+ padding: 0px 20px;
+ overflow-y: auto; }
+ .lz-curtain .lz-curtain-dismiss {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ padding: 0.15em 0.5em;
+ font-size: 0.3em;
+ font-weight: 300;
+ background-color: #D8D8D8;
+ color: #333333;
+ border: 1px solid #333333;
+ border-radius: 0px 0px 0px 4px;
+ pointer-events: auto;
+ cursor: pointer; }
+ .lz-curtain .lz-curtain-dismiss:hover {
+ background-color: #333333;
+ color: #D8D8D8; }
+
+.lz-loader {
+ position: absolute;
+ font-family: "Helvetica Neue", Helvetica, Aria, sans-serif;
+ font-size: 12px;
+ padding: 6px;
+ background: #f0ebe4;
+ border: 1px solid rgba(24, 24, 24, 1);
+ border-radius: 4px;
+ box-shadow: 2px 2px 2px rgba(24, 24, 24, 0.4); }
+ .lz-loader .lz-loader-content {
+ position: relative;
+ display: block;
+ width: 100%; }
+ .lz-loader .lz-loader-cancel {
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ padding: 2px 4px;
+ font-size: 9px;
+ font-weight: 300;
+ background-color: #D8D8D8;
+ color: #333333;
+ border: 1px solid #333333;
+ border-radius: 0px 4px 0px 4px;
+ pointer-events: auto;
+ cursor: pointer; }
+ .lz-loader .lz-loader-cancel:hover {
+ background-color: #333333;
+ color: #D8D8D8; }
+ .lz-loader .lz-loader-progress-container {
+ position: relative;
+ display: block;
+ width: 100%;
+ height: 2px;
+ padding-top: 6px; }
+ .lz-loader .lz-loader-progress {
+ position: absolute;
+ left: 0%;
+ width: 0%;
+ height: 2px;
+ background-color: rgba(24, 24, 24, 0.4); }
+ .lz-loader .lz-loader-progress-animated {
+ animation-name: lz-loader-animate;
+ animation-duration: 1.5s;
+ animation-iteration-count: infinite;
+ animation-timing-function: ease-in-out; }
+
+@keyframes lz-loader-animate {
+ 0% {
+ width: 0%;
+ left: 0%; }
+ 50% {
+ width: 100%;
+ left: 0%; }
+ 100% {
+ width: 0%;
+ left: 100%; } }
+
.lz-locuszoom-controls {
font-family: "Helvetica Neue", Helvetica, Aria, sans-serif;
font-size: 80%;
@@ -346,27 +423,25 @@ div.lz-panel-description {
border-radius: 4px;
box-shadow: 2px 2px 2px rgba(24, 24, 24, 0.4); }
-div.lz-panel-boundary {
- position: absolute;
- height: 4px;
- width: 200px;
- border-radius: 2px;
- background: rgba(235, 240, 228, 0.5);
- border: 1px solid rgba(24, 24, 24, 0.4); }
-
div.lz-panel-boundary {
position: absolute;
height: 3px;
- width: 200px;
+ cursor: row-resize;
+ display: block;
+ padding-top: 10px;
+ padding-bottom: 10px; }
+
+div.lz-panel-boundary span {
+ display: block;
border-radius: 1px;
- background: rgba(216, 216, 216, 0.4);
- border: 1px solid rgba(24, 24, 24, 0.4);
- cursor: row-resize; }
+ background: rgba(216, 216, 216, 0);
+ border: 1px solid rgba(216, 216, 216, 0);
+ height: 3px; }
-div.lz-panel-boundary:hover {
+div.lz-panel-boundary:hover span {
background: #d8d8d8;
border: 1px solid rgba(24, 24, 24, 1); }
-div.lz-panel-boundary:active {
+div.lz-panel-boundary:active span {
background: #333333;
border: 1px solid #d8d8d8; }
diff --git a/plot_builder.html b/plot_builder.html
index 0dd89760..df0bdd33 100644
--- a/plot_builder.html
+++ b/plot_builder.html
@@ -46,7 +46,7 @@
-
+
@@ -224,9 +224,9 @@
LocusZoom Plot Builder
]
};
var panel = plot.addPanel(layout);
- panel.curtain.drop("loading...");
+ panel.curtain.show("loading...", { "text-align": "center" });
panel.on("data_rendered", function(){
- this.curtain.raise();
+ this.curtain.hide();
}.bind(panel));
}
@@ -267,9 +267,9 @@ LocusZoom Plot Builder
return;
}
plot = LocusZoom.populate("#plot", data_sources, layout);
- plot.curtain.drop("applying layout...");
+ plot.curtain.show("applying layout...", { "text-align": "center" });
plot.on("data_rendered", function(){
- this.curtain.raise();
+ this.curtain.hide();
}.bind(plot));
applyOnLayoutChanged();
}
diff --git a/test/Instance.js b/test/Instance.js
index 5742d0a6..dfc6df17 100644
--- a/test/Instance.js
+++ b/test/Instance.js
@@ -209,10 +209,10 @@ describe('LocusZoom.Instance', function(){
d3.select("body").append("div").attr("id", "plot");
this.instance = LocusZoom.populate("#plot");
});
- it('second-to-last child should be a ui group element', function(){
+ it('last child should be a ui group element', function(){
var childNodes = this.instance.svg.node().childNodes.length;
- d3.select(this.instance.svg.node().childNodes[childNodes-2]).attr("id").should.be.exactly("plot.ui");
- d3.select(this.instance.svg.node().childNodes[childNodes-2]).attr("class").should.be.exactly("lz-ui");
+ d3.select(this.instance.svg.node().childNodes[childNodes-1]).attr("id").should.be.exactly("plot.ui");
+ d3.select(this.instance.svg.node().childNodes[childNodes-1]).attr("class").should.be.exactly("lz-ui");
});
it('should have a ui object with ui svg selectors', function(){
this.instance.ui.should.be.an.Object;
@@ -237,35 +237,6 @@ describe('LocusZoom.Instance', function(){
assert.equal(this.instance.ui.svg.style("display"), "none");
});
});
- describe("Curtain Layer", function() {
- beforeEach(function(){
- d3.select("body").append("div").attr("id", "plot");
- this.instance = LocusZoom.populate("#plot");
- });
- it('last child should be a curtain group element', function(){
- d3.select(this.instance.svg.node().lastChild).attr("id").should.be.exactly("plot.curtain");
- d3.select(this.instance.svg.node().lastChild).attr("class").should.be.exactly("lz-curtain");
- });
- it('should have a curtain object with stored svg selector', function(){
- this.instance.curtain.should.be.an.Object;
- this.instance.curtain.svg.should.be.an.Object;
- assert.equal(this.instance.curtain.svg.html(), this.instance.svg.select("#plot\\.curtain").html());
- });
- it('should be hidden by default', function(){
- assert.equal(this.instance.curtain.svg.style("display"), "none");
- });
- it('should have a method that drops the curtain', function(){
- this.instance.curtain.drop.should.be.a.Function;
- this.instance.curtain.drop();
- assert.equal(this.instance.curtain.svg.style("display"), "");
- });
- it('should have a method that raises the curtain', function(){
- this.instance.curtain.raise.should.be.a.Function;
- this.instance.curtain.drop();
- this.instance.curtain.raise();
- assert.equal(this.instance.curtain.svg.style("display"), "none");
- });
- });
});
describe("Dynamic Panel Positioning", function() {
@@ -371,4 +342,91 @@ describe('LocusZoom.Instance', function(){
});
});
+ describe("Instance Curtain and Loader", function() {
+ beforeEach(function(){
+ var datasources = new LocusZoom.DataSources();
+ this.layout = {
+ width: 100,
+ height: 100,
+ min_width: 100,
+ min_height: 100,
+ resizable: false,
+ aspect_ratio: 1,
+ panels: [],
+ controls: false
+ };
+ d3.select("body").append("div").attr("id", "plot");
+ this.plot = LocusZoom.populate("#plot", datasources, this.layout);
+ });
+ it("should have a curtain object with show/update/hide methods, a showing boolean, and selectors", function(){
+ this.plot.should.have.property("curtain").which.is.an.Object;
+ this.plot.curtain.should.have.property("showing").which.is.exactly(false);
+ this.plot.curtain.should.have.property("show").which.is.a.Function;
+ this.plot.curtain.should.have.property("update").which.is.a.Function;
+ this.plot.curtain.should.have.property("hide").which.is.a.Function;
+ this.plot.curtain.should.have.property("selector").which.is.exactly(null);
+ this.plot.curtain.should.have.property("content_selector").which.is.exactly(null);
+ });
+ it("should show/hide/update on command and track shown status", function(){
+ this.plot.curtain.showing.should.be.false();
+ this.plot.curtain.should.have.property("selector").which.is.exactly(null);
+ this.plot.curtain.should.have.property("content_selector").which.is.exactly(null);
+ this.plot.curtain.show("test content");
+ this.plot.curtain.showing.should.be.true();
+ this.plot.curtain.selector.empty().should.be.false();
+ this.plot.curtain.content_selector.empty().should.be.false();
+ this.plot.curtain.content_selector.html().should.be.exactly("test content");
+ this.plot.curtain.hide();
+ this.plot.curtain.showing.should.be.false();
+ this.plot.curtain.should.have.property("selector").which.is.exactly(null);
+ this.plot.curtain.should.have.property("content_selector").which.is.exactly(null);
+ });
+ it("should have a loader object with show/update/animate/setPercentCompleted/hide methods, a showing boolean, and selectors", function(){
+ this.plot.should.have.property("loader").which.is.an.Object;
+ this.plot.loader.should.have.property("showing").which.is.exactly(false);
+ this.plot.loader.should.have.property("show").which.is.a.Function;
+ this.plot.loader.should.have.property("update").which.is.a.Function;
+ this.plot.loader.should.have.property("animate").which.is.a.Function;
+ this.plot.loader.should.have.property("update").which.is.a.Function;
+ this.plot.loader.should.have.property("setPercentCompleted").which.is.a.Function;
+ this.plot.loader.should.have.property("selector").which.is.exactly(null);
+ this.plot.loader.should.have.property("content_selector").which.is.exactly(null);
+ this.plot.loader.should.have.property("progress_selector").which.is.exactly(null);
+ });
+ it("should show/hide/update on command and track shown status", function(){
+ this.plot.loader.showing.should.be.false();
+ this.plot.loader.should.have.property("selector").which.is.exactly(null);
+ this.plot.loader.should.have.property("content_selector").which.is.exactly(null);
+ this.plot.loader.should.have.property("progress_selector").which.is.exactly(null);
+ this.plot.loader.show("test content");
+ this.plot.loader.showing.should.be.true();
+ this.plot.loader.selector.empty().should.be.false();
+ this.plot.loader.content_selector.empty().should.be.false();
+ this.plot.loader.content_selector.html().should.be.exactly("test content");
+ this.plot.loader.progress_selector.empty().should.be.false();
+ this.plot.loader.hide();
+ this.plot.loader.showing.should.be.false();
+ this.plot.loader.should.have.property("selector").which.is.exactly(null);
+ this.plot.loader.should.have.property("content_selector").which.is.exactly(null);
+ this.plot.loader.should.have.property("progress_selector").which.is.exactly(null);
+ });
+ it("should allow for animating or showing discrete percentages of completion", function(){
+ this.plot.loader.show("test content").animate();
+ this.plot.loader.progress_selector.classed("lz-loader-progress-animated").should.be.true();
+ this.plot.loader.setPercentCompleted(15);
+ this.plot.loader.content_selector.html().should.be.exactly("test content");
+ this.plot.loader.progress_selector.classed("lz-loader-progress-animated").should.be.false();
+ this.plot.loader.progress_selector.style("width").should.be.exactly("15%");
+ this.plot.loader.update("still loading...", 62);
+ this.plot.loader.content_selector.html().should.be.exactly("still loading...");
+ this.plot.loader.progress_selector.style("width").should.be.exactly("62%");
+ this.plot.loader.setPercentCompleted(200);
+ this.plot.loader.progress_selector.style("width").should.be.exactly("100%");
+ this.plot.loader.setPercentCompleted(-43);
+ this.plot.loader.progress_selector.style("width").should.be.exactly("1%");
+ this.plot.loader.setPercentCompleted("foo");
+ this.plot.loader.progress_selector.style("width").should.be.exactly("1%");
+ });
+ });
+
});
diff --git a/test/Panel.js b/test/Panel.js
index 7b733d60..35b9877c 100644
--- a/test/Panel.js
+++ b/test/Panel.js
@@ -166,40 +166,95 @@ describe('LocusZoom.Panel', function(){
});
});
- describe("SVG Composition", function() {
- describe("Curtain", function() {
- beforeEach(function(){
- d3.select("body").append("div").attr("id", "instance_id");
- this.instance = LocusZoom.populate("#instance_id");
- });
- it('last child of each panel container should be a curtain element', function(){
- Object.keys(this.instance.panels).forEach(function(panel_id){
- d3.select(this.instance.panels[panel_id].svg.group.node().parentNode.lastChild).attr("id").should.be.exactly("instance_id." + panel_id + ".curtain");
- d3.select(this.instance.panels[panel_id].svg.group.node().parentNode.lastChild).attr("class").should.be.exactly("lz-curtain");
- }.bind(this));
- });
- it('each panel should have a curtain object with stored svg selector', function(){
- Object.keys(this.instance.panels).forEach(function(panel_id){
- this.instance.panels[panel_id].curtain.should.be.an.Object;
- this.instance.panels[panel_id].curtain.svg.should.be.an.Object;
- assert.equal(this.instance.panels[panel_id].curtain.svg.html(), this.instance.svg.select("#instance_id\\." + panel_id + "\\.curtain").html());
- }.bind(this));
- });
- it('each panel curtain should have a method that drops the curtain', function(){
- Object.keys(this.instance.panels).forEach(function(panel_id){
- this.instance.panels[panel_id].curtain.drop.should.be.a.Function;
- this.instance.panels[panel_id].curtain.drop();
- assert.equal(this.instance.panels[panel_id].curtain.svg.style("display"), "");
- }.bind(this));
- });
- it('each panel curtain should have a method that raises the curtain', function(){
- Object.keys(this.instance.panels).forEach(function(panel_id){
- this.instance.panels[panel_id].curtain.raise.should.be.a.Function;
- this.instance.panels[panel_id].curtain.drop();
- this.instance.panels[panel_id].curtain.raise();
- assert.equal(this.instance.panels[panel_id].curtain.svg.style("display"), "none");
- }.bind(this));
- });
+ describe("Panel Curtain and Loader", function() {
+ beforeEach(function(){
+ var datasources = new LocusZoom.DataSources();
+ this.layout = {
+ width: 100,
+ height: 100,
+ min_width: 100,
+ min_height: 100,
+ resizable: false,
+ aspect_ratio: 1,
+ panels: [
+ { id: "test",
+ width: 100,
+ height: 100 }
+ ],
+ controls: false
+ };
+ d3.select("body").append("div").attr("id", "plot");
+ this.plot = LocusZoom.populate("#plot", datasources, this.layout);
+ this.panel = this.plot.panels.test;
+ });
+ it("should have a curtain object with show/update/hide methods, a showing boolean, and selectors", function(){
+ this.panel.should.have.property("curtain").which.is.an.Object;
+ this.panel.curtain.should.have.property("showing").which.is.exactly(false);
+ this.panel.curtain.should.have.property("show").which.is.a.Function;
+ this.panel.curtain.should.have.property("update").which.is.a.Function;
+ this.panel.curtain.should.have.property("hide").which.is.a.Function;
+ this.panel.curtain.should.have.property("selector").which.is.exactly(null);
+ this.panel.curtain.should.have.property("content_selector").which.is.exactly(null);
+ });
+ it("should show/hide/update on command and track shown status", function(){
+ this.panel.curtain.showing.should.be.false();
+ this.panel.curtain.should.have.property("selector").which.is.exactly(null);
+ this.panel.curtain.should.have.property("content_selector").which.is.exactly(null);
+ this.panel.curtain.show("test content");
+ this.panel.curtain.showing.should.be.true();
+ this.panel.curtain.selector.empty().should.be.false();
+ this.panel.curtain.content_selector.empty().should.be.false();
+ this.panel.curtain.content_selector.html().should.be.exactly("test content");
+ this.panel.curtain.hide();
+ this.panel.curtain.showing.should.be.false();
+ this.panel.curtain.should.have.property("selector").which.is.exactly(null);
+ this.panel.curtain.should.have.property("content_selector").which.is.exactly(null);
+ });
+ it("should have a loader object with show/update/animate/setPercentCompleted/hide methods, a showing boolean, and selectors", function(){
+ this.panel.should.have.property("loader").which.is.an.Object;
+ this.panel.loader.should.have.property("showing").which.is.exactly(false);
+ this.panel.loader.should.have.property("show").which.is.a.Function;
+ this.panel.loader.should.have.property("update").which.is.a.Function;
+ this.panel.loader.should.have.property("animate").which.is.a.Function;
+ this.panel.loader.should.have.property("update").which.is.a.Function;
+ this.panel.loader.should.have.property("setPercentCompleted").which.is.a.Function;
+ this.panel.loader.should.have.property("selector").which.is.exactly(null);
+ this.panel.loader.should.have.property("content_selector").which.is.exactly(null);
+ this.panel.loader.should.have.property("progress_selector").which.is.exactly(null);
+ });
+ it("should show/hide/update on command and track shown status", function(){
+ this.panel.loader.showing.should.be.false();
+ this.panel.loader.should.have.property("selector").which.is.exactly(null);
+ this.panel.loader.should.have.property("content_selector").which.is.exactly(null);
+ this.panel.loader.should.have.property("progress_selector").which.is.exactly(null);
+ this.panel.loader.show("test content");
+ this.panel.loader.showing.should.be.true();
+ this.panel.loader.selector.empty().should.be.false();
+ this.panel.loader.content_selector.empty().should.be.false();
+ this.panel.loader.content_selector.html().should.be.exactly("test content");
+ this.panel.loader.progress_selector.empty().should.be.false();
+ this.panel.loader.hide();
+ this.panel.loader.showing.should.be.false();
+ this.panel.loader.should.have.property("selector").which.is.exactly(null);
+ this.panel.loader.should.have.property("content_selector").which.is.exactly(null);
+ this.panel.loader.should.have.property("progress_selector").which.is.exactly(null);
+ });
+ it("should allow for animating or showing discrete percentages of completion", function(){
+ this.panel.loader.show("test content").animate();
+ this.panel.loader.progress_selector.classed("lz-loader-progress-animated").should.be.true();
+ this.panel.loader.setPercentCompleted(15);
+ this.panel.loader.content_selector.html().should.be.exactly("test content");
+ this.panel.loader.progress_selector.classed("lz-loader-progress-animated").should.be.false();
+ this.panel.loader.progress_selector.style("width").should.be.exactly("15%");
+ this.panel.loader.update("still loading...", 62);
+ this.panel.loader.content_selector.html().should.be.exactly("still loading...");
+ this.panel.loader.progress_selector.style("width").should.be.exactly("62%");
+ this.panel.loader.setPercentCompleted(200);
+ this.panel.loader.progress_selector.style("width").should.be.exactly("100%");
+ this.panel.loader.setPercentCompleted(-43);
+ this.panel.loader.progress_selector.style("width").should.be.exactly("1%");
+ this.panel.loader.setPercentCompleted("foo");
+ this.panel.loader.progress_selector.style("width").should.be.exactly("1%");
});
});