From 830bcbfd15e4ab2ede73435ae2bce97a07885e41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcus=20P=C3=B6ckelmann?=
 <marcus.poeckelmann@informatik.uni-halle.de>
Date: Thu, 25 May 2023 18:59:01 +0200
Subject: [PATCH] v2.6: added methods to set the zoom as well as to zoom in and
 out

---
 README     |   2 +-
 catview.js | 118 ++++++++++++++++++++++++++++++++++++++---------------
 2 files changed, 87 insertions(+), 33 deletions(-)

diff --git a/README b/README
index 7aeb9ee..3ca7298 100644
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
 Thank you for using CATview - the Colored & Aligned Texts view.
 
-This is version 2.5.2
+This is version 2.6
 
 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
index 18a9fd0..e7cf2e9 100644
--- a/catview.js
+++ b/catview.js
@@ -22,7 +22,7 @@
  SOFTWARE.
 */
 
-// CATview - the Colored & Aligned Texts view - version 2.5.2
+// CATview - the Colored & Aligned Texts view - version 2.6
 const CATview = new function() {
   this.debug = false;
 
@@ -31,7 +31,7 @@ const CATview = new function() {
     if(CATview.debug)
       console.log('CATview.initialize');
 
-    CATview.version = '2.5.2';
+    CATview.version = '2.6';
 
     // id of the parent container that will include CATview
     CATview.parent_id = 'CATview';
@@ -98,6 +98,8 @@ const CATview = new function() {
     CATview.to_pixel = 0;
     CATview.scale = 1;     // current zooming factor
     CATview.translate = 0; // current translation for zoomed-in excerpt
+    CATview.zoom_step = 0.25; // steps by which to increase/decrease zoom in zoom_in/_out()
+    CATview.zoom_button_clicked = false;
 
     // data
     CATview.names = [];     // array with witnesses names (number of rows)
@@ -617,9 +619,9 @@ const CATview = new function() {
           if(!CATview.drag_do_end)
             return;
 
-          let old_pos = CATview.drag_old_pos
-          let new_pos = CATview.drag_new_pos
-          let objects = {}
+          let old_pos = CATview.drag_old_pos;
+          let new_pos = CATview.drag_new_pos;
+          let objects = {};
           // get all name objects
           for(let pos = 0; pos < CATview.names.length; pos++){
             objects[pos] = d3.select("g.axis.names-axis>text[current=\"" + pos + "\"]");
@@ -950,7 +952,7 @@ const CATview = new function() {
         .enter().append("g")
         .attr("class", "row_witness")
         .attr("transform", function(d) { return "translate(" +
-          (CATview.vertical == true ? (-CATview.rect_width/2 + ", " + (CATview.scale_edges(parseInt(d[1]))-(CATview.rect_height/2))) :
+          (CATview.vertical === true ? (-CATview.rect_width/2 + ", " + (CATview.scale_edges(parseInt(d[1]))-(CATview.rect_height/2))) :
             (CATview.scale_edges(parseInt(d[1]))-(CATview.rect_width/2)) + ", " + (-CATview.rect_height/2))
           + ")"; })
         .on('mouseenter', function(d, i){CATview.show_edge_name(i)})
@@ -965,10 +967,10 @@ const CATview = new function() {
         .attr("width", function(d){ return CATview.scaled_rect_width(d[1], d[0]); })
         .attr("height", function(d){ return CATview.scaled_rect_height(d[1], d[0]); })
         //.attr("ry", rect_corner)      // rounded Corners
-        .attr(CATview.vertical == true ? "x" : "y", function(d) { return CATview.scaled_position(d[1], d[0]); })
+        .attr(CATview.vertical === true ? "x" : "y", function(d) { return CATview.scaled_position(d[1], d[0]); })
         .attr("style", function(d) {
           return CATview.rect_stroke + CATview.rect_border + "fill: " +
-            (parseFloat(d[2]) == 0.0 && CATview.use_equality_color ? CATview.equality_color : CATview.scale_color(parseFloat(d[2])));
+            (parseFloat(d[2]) === 0.0 && CATview.use_equality_color ? CATview.equality_color : CATview.scale_color(parseFloat(d[2])));
         })
         .attr("data-segment-index", function(d) { return d[1] + "_" + d[0];})
         .on("mouseenter", function(d){ CATview.show_rect_linking(d[1]+"_"+d[0]); })
@@ -1028,12 +1030,12 @@ const CATview = new function() {
     let offset_x = "50%";
     let anchor_y = "top";
     let offset_y = document.getElementById("CATview").offsetTop + CATview.height_svg + 'px';
-    let transform = "translate(-50%, 0)"
+    let transform = "translate(-50%, 0)";
     switch(CATview.orientation) {
       case 'left':
         offset_x = CATview.width_svg + 'px';
-        offset_y = "50%"
-        transform = "translate(0, -50%)"
+        offset_y = "50%";
+        transform = "translate(0, -50%)";
         break;
       case 'bottom':
         anchor_y = "bottom";
@@ -1043,8 +1045,8 @@ const CATview = new function() {
       case 'right':
         anchor_x = "right";
         offset_x = CATview.width_svg + 'px';
-        offset_y = "50%"
-        transform = "translate(0, -50%)"
+        offset_y = "50%";
+        transform = "translate(0, -50%)";
         break;
       default: // top
     }
@@ -1148,7 +1150,7 @@ const CATview = new function() {
     return true;
   };
   this.toggle_display_extra_segments = function(_toggle){
-    if(_toggle != null && typeof(_toggle) == "boolean")
+    if(_toggle != null && typeof(_toggle) === "boolean")
       CATview.display_extra_segments = !_toggle;
     CATview.display_extra_segments = !CATview.display_extra_segments;
 
@@ -1173,7 +1175,7 @@ const CATview = new function() {
             .attr('cursor', 'pointer')
             .attr("width", function(d){ return CATview.scaled_rect_width(segment[0], segment[1]) - 1;})
             .attr("height", function(d){ return CATview.scaled_rect_height(segment[0], segment[1]) - 2;})
-            .attr(CATview.vertical == true ? "x" : "y", function(d) {
+            .attr(CATview.vertical === true ? "x" : "y", function(d) {
               return CATview.scaled_position(segment[0], segment[1]) + 1;
             })
             .attr("style", function(d) {
@@ -1271,7 +1273,7 @@ const CATview = new function() {
     // TODO validation
     this.rect_scaling = _rect_scaling;
     return true;
-  }
+  };
   this.enable_rect_scaling = function(_mode){
     if(CATview.debug)
       console.log('CATview.enable_rect_scaling');
@@ -1285,7 +1287,7 @@ const CATview = new function() {
       CATview.rect_scaling_mode = 'default';
     CATview.refresh_content(CATview.from, CATview.to);
     return true;
-  }
+  };
   this.disable_rect_scaling = function(){
     if(CATview.debug)
       console.log('CATview.disable_rect_scaling');
@@ -1293,7 +1295,7 @@ const CATview = new function() {
     CATview.rect_scaling_enabled = false;
     CATview.refresh_content(CATview.from, CATview.to);
     return true;
-  }
+  };
   this.toggle_rect_scaling = function(){
     if(CATview.debug)
       console.log('CATview.toggle_rect_scaling');
@@ -1302,7 +1304,7 @@ const CATview = new function() {
       return CATview.disable_rect_scaling();
     else
       return CATview.enable_rect_scaling(CATview.rect_scaling_mode);
-  }
+  };
   // internal methods to define the dimensions and positioning of rectabgles in respect to rectangle scaling
   this.scaled_rect_width = function(_egde_idx, _name_idx){
     if(!CATview.rect_scaling_enabled || !CATview.vertical || CATview.rect_scaling === null){
@@ -1315,7 +1317,7 @@ const CATview = new function() {
     let minimal_size = CATview.rect_width * CATview.rect_scaling_minimum; // fixed minimal size of the rectangle
     let addable_size = CATview.rect_width - minimal_size;                 // variable size available to add
     return Math.round(minimal_size + scaling * addable_size);
-  }
+  };
   this.scaled_rect_height = function(_egde_idx, _name_idx){
     if(!CATview.rect_scaling_enabled || CATview.vertical || CATview.rect_scaling === null){
       // return the default height on disabled scaling or vertical orientation
@@ -1327,7 +1329,7 @@ const CATview = new function() {
     let minimal_size = CATview.rect_height * CATview.rect_scaling_minimum; // fixed minimal size of the rectangle
     let addable_size = CATview.rect_height - minimal_size;                 // variable size available to add
     return Math.round(minimal_size + scaling * addable_size);
-  }
+  };
   this.scaled_position = function(_egde_idx, _name_idx){
     if(!CATview.rect_scaling_enabled || CATview.rect_scaling_mode == 'inverted'){
       // the default value if scaling was disabled or on inverted mode
@@ -1348,7 +1350,7 @@ const CATview = new function() {
       // the original posittion + maximal size - used size
       return CATview.scale_names(CATview.drag_order_name2pos[_name_idx]) + CATview.rect_height - scaled
     }
-  }
+  };
 
 
   // public methods for rectangle linking
@@ -1358,7 +1360,7 @@ const CATview = new function() {
     // TODO validation
     this.rect_linking_data = _data;
     return true;
-  }
+  };
   this.enable_rect_linking = function(){
     if(CATview.debug)
       console.log('CATview.enable_rect_linking');
@@ -1366,7 +1368,7 @@ const CATview = new function() {
     CATview.rect_linking_enabled = true;
 
     return true;
-  }
+  };
   this.disable_rect_linking = function(){
     if(CATview.debug)
       console.log('CATview.disable_rect_linking');
@@ -1374,7 +1376,7 @@ const CATview = new function() {
     CATview.rect_linking_enabled = false;
     CATview.hide_rect_linking();
     return true;
-  }
+  };
   this.toggle_rect_linking = function(){
     if(CATview.debug)
       console.log('CATview.toggle_rect_linking');
@@ -1383,7 +1385,7 @@ const CATview = new function() {
       return CATview.disable_rect_linking();
     else
       return CATview.enable_rect_linking();
-  }
+  };
   // internal methods for rectangle linking
   this.show_rect_linking = function(_idx) {
     if(CATview.debug)
@@ -1393,7 +1395,7 @@ const CATview = new function() {
       return false;
 
     CATview.rect_linking_data.forEach( function (group){
-      if(group.indexOf(_idx) != -1){
+      if(group.indexOf(_idx) !== -1){
         group.forEach( function (group_member) {
           // add the 'rect_linked' class to highlight all segments in the group, including the one hovered
           let seg = document.querySelector('[data-segment-index="' + group_member + '"]');
@@ -1401,7 +1403,7 @@ const CATview = new function() {
         });
       }
     });
-  }
+  };
   this.hide_rect_linking = function(){
     if(CATview.debug)
       console.log('CATview.hide_rect_linking');
@@ -1411,7 +1413,7 @@ const CATview = new function() {
     for (let i = 0; i < segments.length; i++) {
       segments[i].classList.remove('rect_linked');
     }
-  }
+  };
 
   // callback to switch the order of edges
   this.invert_edges_axis = function(){
@@ -1471,6 +1473,35 @@ const CATview = new function() {
     return true;
   };
 
+  this.set_zoom = function(_factor) {
+    if (CATview.zoom.scaleExtent()[0] <= _factor && _factor <= CATview.zoom.scaleExtent()[1]){
+      CATview.zoom.scaleTo(CATview.content, _factor);
+    }
+  };
+
+  this.zoom_in = function() {
+    CATview.zoom_button_clicked = true;
+    if (CATview.scale+CATview.zoom_step <= CATview.zoom.scaleExtent()[1]){
+      CATview.zoom.scaleTo(CATview.content, CATview.scale+CATview.zoom_step);
+    }
+    else {
+      // we hit a boundary, might as well set scale to the boundary
+      // sometimes mixed scaling with mouse wheel and buttons causes the zoom-step to be
+      // too large so it would exceed the boundary. In that case clip to the boundary
+      CATview.zoom.scaleTo(CATview.content, CATview.zoom.scaleExtent()[1]);
+    }
+  };
+
+  this.zoom_out = function() {
+    CATview.zoom_button_clicked = true;
+    if (CATview.scale-CATview.zoom_step >= CATview.zoom.scaleExtent()[0]){
+      CATview.zoom.scaleTo(CATview.content, CATview.scale-CATview.zoom_step);
+    }
+    else{
+      CATview.zoom.scaleTo(CATview.content, CATview.zoom.scaleExtent()[0]);
+    }
+  };
+
   // callback with functionality for scaling and translation of the content
   this.zooming = function () {
     if(CATview.debug)
@@ -1490,7 +1521,17 @@ const CATview = new function() {
         // calculate the change of the size
         let delta_size = (current_size / new_scale) - (current_size / CATview.scale);
         // distribute the change of the size among the left/top and right/bottom site due to the relative mouse center
-        scale_from = delta_size * ((CATview.vertical === true ? d3.mouse(this)[1] : d3.mouse(this)[0]) / current_size);
+
+        // simulate central mouse position in case of zoom button clicks
+        if (CATview.zoom_button_clicked){
+          var art_mouse_x = CATview.width_content/2;
+          var art_mouse_y = CATview.height_content/2;
+          scale_from = delta_size * ((CATview.vertical === true ? art_mouse_y : art_mouse_x) / current_size);
+          CATview.zoom_button_clicked = false;
+        }
+        else{
+          scale_from = delta_size * ((CATview.vertical === true ? d3.mouse(this)[1] : d3.mouse(this)[0]) / current_size);
+        }
         scale_to = delta_size - scale_from;
       }
       else if(CATview.display_brush === false){
@@ -1856,11 +1897,24 @@ const CATview = new function() {
   // add a tool that toggles the rectangle scaling
   this.add_tool_toggle_rect_scaling = function(index){
     return CATview.add_tool(
-      (CATview.vertical ? 'f036' : 'f038'),
+      'f037', // (CATview.vertical ? 'f036' : 'f038'),
       function(){CATview.toggle_rect_scaling()},
       index,
       (CATview.vertical ? 0 : 90 ));
   };
+  this.add_tool_zoom_in = function(index){
+    return CATview.add_tool('f00e', // f067
+      function(){CATview.zoom_in();},
+      index,
+      0);
+  };
+  this.add_tool_zoom_out = function(index){
+    return CATview.add_tool('f010', // f068
+      function(){CATview.zoom_out();},
+      index,
+      0);
+  };
+
 
   //todo remove and set the identifiers of the witnesses
   this.set_names = function(names){
@@ -1972,7 +2026,7 @@ const CATview = new function() {
 
   // enable/disable console output to debug
   this.toggle_debug = function(debug){
-    if(debug != null && typeof(debug) == "boolean")
+    if(debug != null && typeof(debug) === "boolean")
       CATview.debug = !debug;
 
     CATview.debug = !CATview.debug;
-- 
GitLab