diff --git a/LICENSE b/LICENSE index d709441e912933d4d9d686c846ffee62e8d3e458..bef06029e48e5015010255a39a13cdd2a4aa6dcc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2021 Marcus Pöckelmann +Copyright (c) 2015 ongoing Marcus Pöckelmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README b/README index c62d4fae8bae9edfca9f6626f9d8e80b45272f65..c58f20cb1f11484e4155314c667be92c4412297c 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ Thank you for using CATview - the Colored & Aligned Texts view. -This is version 2.0. +This is version 2.2. For information about the features and usage of CATview, please visit: https://catview.uzi.uni-halle.de diff --git a/catview.js b/catview.js old mode 100644 new mode 100755 index 352c71769e446600391948d78e891ff509d638af..c1a143e1bdc359a3e280e7a0fa2831e2ffadae56 --- a/catview.js +++ b/catview.js @@ -1,7 +1,7 @@ /* The MIT License (MIT) - Copyright (c) 2015-2021 Marcus Pöckelmann + Copyright (c) 2015 ongoing Marcus Pöckelmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ */ // CATview - the Colored & Aligned Texts view -// version 2.1 +// version 2.2 const CATview = new function() { this.debug = false; @@ -32,7 +32,7 @@ const CATview = new function() { if(CATview.debug) console.log('CATview.initialize'); - CATview.version = '2.1'; + CATview.version = '2.2'; // id of the parent container that will include CATview CATview.parent_id = 'CATview'; @@ -81,9 +81,13 @@ const CATview = new function() { CATview.display_brush = false; CATview.brush_from_edge = -1; CATview.brush_to_edge = -1; + CATview.brush_range = 1; CATview.brush_from_name = -1; CATview.brush_to_name = -1; CATview.brush_callback = null; + CATview.brush_end_callback = null; + CATview.brush_offset_from = -0.5; + CATview.brush_offset_to = 0.5; // zooming parameters CATview.zoom = null; // the d3 object for zooming @@ -374,12 +378,13 @@ const CATview = new function() { // define the interval of edges and refresh the content afterwards this.refresh_content = function(from, to){ if(CATview.debug) - console.log('CATview.refresh_content'); + console.log('CATview.refresh_content(' + from + ', ' + to + ')'); if(CATview.content){ if (from != null) CATview.from = from; if (from == 0) CATview.from = 1; if (to != null) CATview.to = to; + if (to > CATview.edges.length) CATview.to = CATview.edges.length; // refresh the edges-scale (with one bin offset) CATview.set_scale_x(Math.max(CATview.from - 1, 0), Math.min(CATview.to + 1, CATview.edges.length + 1)) @@ -425,7 +430,7 @@ const CATview = new function() { }; this.draw_names_axis = function(){ if(CATview.debug) - console.log('CATview.draw_names_axis') + console.log('CATview.draw_names_axis'); let names_axis = CATview.svg.select(".names-axis"); names_axis.selectAll('*').remove(); // remove the current content @@ -472,7 +477,7 @@ const CATview = new function() { // draws the zebra stripes (gray lines in the background for every second text) this.draw_zebra_stripes = function(){ if(CATview.debug) - console.log('CATview.draw_zebra_lines') + console.log('CATview.draw_zebra_lines'); let zebra = CATview.svg.select(".zebra"); zebra.selectAll('*').remove(); // remove the current content @@ -503,7 +508,7 @@ const CATview = new function() { // draws the icons for tools included in CATview.tools this.draw_tool_icons = function(){ if(CATview.debug) - console.log('CATview.draw_tool_icons') + console.log('CATview.draw_tool_icons'); let toolicons = CATview.svg.select(".tool-icons"); toolicons.selectAll('*').remove(); // remove the current content @@ -1053,18 +1058,16 @@ const CATview = new function() { // refresh the brush if(CATview.brush) { - // remember the current extent - //let extent = CATview.brush.extent(); - // update the scale - // if(CATview.vertical == true) - // todo CATview.brush.y(CATview.scale_edges); - // else - // todo CATview.brush.x(CATview.scale_edges); - // restore the extent - //CATview.brush.extent(extent); - // redraw the brush - //CATview.content.select(".brush").call(CATview.brush); - } + let from = CATview.brush_from_edge + 1.0 + CATview.brush_offset_from; + let to = CATview.brush_to_edge + 1.0 + CATview.brush_offset_to; + if(CATview.x_inverted){ + let swap = from; + from = to; + to = swap; + } + CATview.brush.move(d3.select('.brush'), [from, to].map(CATview.scale_edges)) + // TODO prevent brushing listeners + } } else return false; @@ -1077,8 +1080,9 @@ const CATview = new function() { return true; }; - // enable the brush functionality and pass a callback function to the brush (will receive: from_edge, to_edge, from_name, to_name) - this.enable_brush = function(brush_callback){ + // enable the brush functionality and pass + // a callback function to the brush (will receive: from_edge, to_edge, from_name, to_name) + this.enable_brush = function(_brush_callback, _brush_end_callback){ if(CATview.debug) console.log('CATview.enable_brush'); @@ -1088,8 +1092,10 @@ const CATview = new function() { if(CATview.content) CATview.draw_brush(); } - if(brush_callback != null) - CATview.brush_callback = brush_callback; + if(_brush_callback != null) + CATview.brush_callback = _brush_callback; + if(_brush_end_callback != null) + CATview.brush_end_callback = _brush_end_callback; return true; }; @@ -1106,7 +1112,6 @@ const CATview = new function() { CATview.brush_to_edge = -1; CATview.brush_from_name = -1; CATview.brush_to_name = -1; - // CATview.brush_callback = null; // remove the brush from CATview if (CATview.content) CATview.content.select(".brush").remove(); @@ -1130,23 +1135,25 @@ const CATview = new function() { if(CATview.vertical == true) { CATview.brush = d3.brushY() - .on("brush", CATview.brushing); + .on("brush", CATview.brushing) + .on("end", CATview.brush_end); if(CATview.x_inverted) - CATview.brush.extent([[CATview.scale_names.range()[0], CATview.scale_edges.range()[1]], [CATview.scale_names.range()[1], CATview.scale_edges.range()[0]]]) + CATview.brush.extent([[CATview.scale_names.range()[0], CATview.scale_edges.range()[1]], [CATview.scale_names.range()[1], CATview.scale_edges.range()[0]]]); else - CATview.brush.extent([[CATview.scale_names.range()[0], CATview.scale_edges.range()[0]], [CATview.scale_names.range()[1], CATview.scale_edges.range()[1]]]) + CATview.brush.extent([[CATview.scale_names.range()[0], CATview.scale_edges.range()[0]], [CATview.scale_names.range()[1], CATview.scale_edges.range()[1]]]); } else { CATview.brush = d3.brushX() - .on("brush", CATview.brushing); + .on("brush", CATview.brushing) + .on("end", CATview.brush_end); if(CATview.x_inverted) - CATview.brush.extent([[CATview.scale_edges.range()[1], CATview.scale_names.range()[0]], [CATview.scale_edges.range()[0], CATview.scale_names.range()[1]]]) + CATview.brush.extent([[CATview.scale_edges.range()[1], CATview.scale_names.range()[0]], [CATview.scale_edges.range()[0], CATview.scale_names.range()[1]]]); else - CATview.brush.extent([[CATview.scale_edges.range()[0], CATview.scale_names.range()[0]], [CATview.scale_edges.range()[1], CATview.scale_names.range()[1]]]) + CATview.brush.extent([[CATview.scale_edges.range()[0], CATview.scale_names.range()[0]], [CATview.scale_edges.range()[1], CATview.scale_names.range()[1]]]); } - let brush = CATview.content.append("g") + CATview.content.append("g") .attr("class", "brush") .call(CATview.brush); } @@ -1162,51 +1169,108 @@ const CATview = new function() { return true; }; + this.set_brush_end_callback = function(_brush_end_callback) { + CATview.brush_end_callback = _brush_end_callback; + + return true; + }; // react on changes of the brush this.brushing = function() { if(CATview.debug) console.log('CATview.brushing'); - - // var edges_index = CATview.vertical == true ? 1 : 0; - // var names_index = CATview.vertical == true ? 0 : 1; - - // get the interval as integers - // var from_edge = Math.ceil(CATview.brush.extent()[0][edges_index]) - 1; - let from_edge = Math.ceil(d3.event.selection.map(CATview.scale_edges.invert)[0]) - 1; - if(from_edge < 0) - from_edge = 0; - // var to_edge = Math.floor(CATview.brush.extent()[1][edges_index]) - 1; - let to_edge = Math.floor(d3.event.selection.map(CATview.scale_edges.invert)[1]) - 1; - if(to_edge >= CATview.edges.length) - to_edge = CATview.edges.length - 1; - let from_name = null; // Math.ceil(CATview.brush.extent()[0][names_index]); - let to_name = null; // Math.floor(CATview.brush.extent()[1][names_index]); - - if(CATview.x_inverted){ - let swap = from_edge; - from_edge = to_edge; - to_edge = swap; - } + + // handle inverted edges axis + let from = CATview.x_inverted ? 1 : 0; + + // the starting position is construction by: + // d3.event.selection[0] → current begin of the drawn selection window + // CATview.brush_offset_from → the specified offset used to place the window at the begin of a rectangle + // 0.001 → a little additional offset for the case that the window was dragged to the most right position + let from_edge = Math.round(CATview.scale_edges.invert(d3.event.selection[from]) - CATview.brush_offset_from - 0.001) -1; + if(from_edge < CATview.from - 1) from_edge = CATview.from - 1; + else if(from_edge >= CATview.to) from_edge = CATview.to - 1; + + // add the current range to the from position to get the to position + let to_edge = from_edge + CATview.brush_range - 1; + + // TODO may allow selection of names axis + //let from_name = null; // Math.ceil(CATview.brush.extent()[0][names_index]); + //let to_name = null; // Math.floor(CATview.brush.extent()[1][names_index]); if(to_edge - from_edge >= 0) { // check whether the interval has changed - if (from_edge != CATview.brush_from_edge || to_edge != CATview.brush_to_edge || from_name != CATview.brush_from_name || to_name != CATview.brush_to_name) { + if (from_edge != CATview.brush_from_edge || to_edge != CATview.brush_to_edge){ // || from_name != CATview.brush_from_name || to_name != CATview.brush_to_name) { // save the current interval CATview.brush_from_edge = from_edge; CATview.brush_to_edge = to_edge; - CATview.brush_from_name = from_name; - CATview.brush_to_name = to_name; + //CATview.brush_from_name = from_name; + //CATview.brush_to_name = to_name; // call the brush_callback as the interval has changed if (CATview.brush_callback != null) { - CATview.brush_callback(from_edge, to_edge, from_name, to_name); + CATview.brush_callback(from_edge, to_edge, null, null); //, from_name, to_name); } else { - console.log('CATview brush edge from ' + from_edge + ' to ' + to_edge + ' and name from ' + from_name + ' to ' + to_name); + // no callback specified → log positions to console + console.log('CATview brush edge from ' + from_edge + ' to ' + to_edge);// + ' and name from ' + from_name + ' to ' + to_name); } } } }; + // react on the end of a brush gesture + this.brush_end = function() { + if(CATview.debug) + console.log('CATview.brush_end'); + + if (!d3.event.sourceEvent) return; // only transition after input + if (!d3.event.selection) return; // ignore empty selections + + // handle inverted edges axis + let from = CATview.x_inverted ? 1 : 0; + let to = CATview.x_inverted ? 0 : 1; + + // the starting position (end analogously) is construction by: + // d3.event.selection[0] → current begin of the drawn selection window + // CATview.brush_offset_from → the specified offset used to place the window at the begin of a rectangle + // 0.001 → a little additional offset for the case that the window was dragged to the most right position + let from_edge = Math.round(CATview.scale_edges.invert(d3.event.selection[from]) - CATview.brush_offset_from - 0.001) -1; + if(from_edge < CATview.from - 1) from_edge = CATview.from - 1; + else if(from_edge >= CATview.to) from_edge = CATview.to - 1; + let to_edge = Math.round(CATview.scale_edges.invert(d3.event.selection[to]) - CATview.brush_offset_to + 0.001) -1; + if(to_edge >= CATview.to) to_edge = CATview.to - 1; + else if(to_edge < CATview.from - 1) to_edge = CATview.from - 1; + + // calculated the number of rectangles within the window + let brush_range = to_edge - from_edge + 1; + + // intervene if range is zero + if(brush_range == 0){ + console.log('intervene') + to_edge = from_edge; + brush_range = 1; + } + + // smoothly redraw the brush to discrete positions (begin and end of the rectangles) + if(CATview.x_inverted){ + d3.select(this).transition().call(d3.event.target.move, + [to_edge + 1.0 + CATview.brush_offset_to, from_edge + 1.0 + CATview.brush_offset_from].map(CATview.scale_edges)); + }else{ + d3.select(this).transition().call(d3.event.target.move, + [from_edge + 1.0 + CATview.brush_offset_from, to_edge + 1.0 + CATview.brush_offset_to].map(CATview.scale_edges)); + } + + // save new values if there was a change + if(from_edge !== CATview.brush_from_edge || to_edge !== CATview.brush_to_edge || brush_range !== CATview.brush_range){ + // save the current interval + CATview.brush_from_edge = from_edge; + CATview.brush_to_edge = to_edge; + CATview.brush_range = brush_range; + // and call the custom callbacks + if (CATview.brush_end_callback != null) { + CATview.brush_end_callback(from_edge, to_edge); //, from_name, to_name); + } + } + }; // add a new tool icon and its behavior this.add_tool = function(_icon, _callback, _index, _rotation){ @@ -1398,4 +1462,4 @@ const CATview = new function() { // - may: method for changing the border_color → set_border(_color, _width) // - adopt font size in respect to the number of witnesses // - method to reset all data → reset -}; \ No newline at end of file +}; diff --git a/demo/demo_bottom.html b/demo/demo_bottom.html index 1c1e41801f13884d48f90abdcb62aa57470e414f..849851cd5391ae3a473113147ffb41f5d90c480f 100644 --- a/demo/demo_bottom.html +++ b/demo/demo_bottom.html @@ -157,5 +157,10 @@ // refresh CATviews scrollspy CATview.draw_scroll_spy(from); }); + + // reacts as soon as the size of the selection box (brush) is fixed + CATview.set_brush_end_callback(function(from, to){ + CATview.brush_callback(from, to); + }); </script> </html> diff --git a/demo/demo_left.html b/demo/demo_left.html index ba1e8756e6f9b05dcaede2b41c964cf67f486d5a..8d1939743bd1d496e7432f001aab819ef617cebd 100644 --- a/demo/demo_left.html +++ b/demo/demo_left.html @@ -155,5 +155,10 @@ // refresh CATviews scrollspy CATview.draw_scroll_spy(from); }); + + // reacts as soon as the size of the selection box (brush) is fixed + CATview.set_brush_end_callback(function(from, to){ + CATview.brush_callback(from, to); + }); </script> </html> diff --git a/demo/demo_right.html b/demo/demo_right.html index 5502515eb278bb2aaca6cb18dabd04da76879ab0..8bcb7abc4728b88c91d3e51e2be6a5317d2ee78b 100644 --- a/demo/demo_right.html +++ b/demo/demo_right.html @@ -155,5 +155,10 @@ // refresh CATviews scrollspy CATview.draw_scroll_spy(from); }); + + // reacts as soon as the size of the selection box (brush) is fixed + CATview.set_brush_end_callback(function(from, to){ + CATview.brush_callback(from, to); + }); </script> </html> diff --git a/demo/demo_top.html b/demo/demo_top.html index 2252784dc4d8af5026ca9357cf4b91ef1a5a397b..05480817907d35b7a884b463ce3fe8dbf7b85991 100644 --- a/demo/demo_top.html +++ b/demo/demo_top.html @@ -158,5 +158,10 @@ // refresh CATviews scrollspy CATview.draw_scroll_spy(from); }); + + // reacts as soon as the size of the selection box (brush) is fixed + CATview.set_brush_end_callback(function(from, to){ + CATview.brush_callback(from, to); + }); </script> </html>