From b3fd9ff4d24931d04407d585c676f637c3cf7304 Mon Sep 17 00:00:00 2001
From: Marcus Poeckelmann <marcus.poeckelmann@informatik.uni-halle.de>
Date: Wed, 1 Mar 2023 16:48:12 +0100
Subject: [PATCH] v2.3: added drag'n'drop functionality to change the order of
 names

---
 LICENSE               |   2 +-
 README                |   2 +-
 catview.css           |   4 +-
 catview.js            | 698 +++++++++++++++++++++++++++++++-----------
 demo/demo_bottom.html |   1 +
 demo/demo_left.html   |   1 +
 demo/demo_right.html  |   1 +
 demo/demo_top.html    |   1 +
 8 files changed, 521 insertions(+), 189 deletions(-)
 mode change 100755 => 100644 catview.js

diff --git a/LICENSE b/LICENSE
index bef0602..7e0cff1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2015 ongoing Marcus Pöckelmann
+Copyright (c) 2015 and 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 c58f20c..2ece30a 100644
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
 Thank you for using CATview - the Colored & Aligned Texts view.
 
-This is version 2.2.
+This is version 2.3.
 
 For information about the features and usage of CATview, please visit:
 https://catview.uzi.uni-halle.de
diff --git a/catview.css b/catview.css
index 7caa6af..4d5e55f 100644
--- a/catview.css
+++ b/catview.css
@@ -1,7 +1,7 @@
 /*
   The MIT License (MIT)
 
-  Copyright (c) 2015-2021 Marcus Pöckelmann
+  Copyright (c) 2015 and 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
@@ -78,3 +78,5 @@
 #CATview .edge-name-right { text-align: right; border-radius: 8px 0 0 8px; } /* tl tr br bl*/
 #CATview .edge-name-bottom { text-align: center; border-radius: 8px 8px 0 0; } /* tl tr br bl*/
 #CATview .edge-name-left { text-align: left; border-radius: 0 8px 8px 0; } /* tl tr br bl*/
+
+#CATview .draggable text{ cursor: grab; }
diff --git a/catview.js b/catview.js
old mode 100755
new mode 100644
index c1a143e..9217e33
--- a/catview.js
+++ b/catview.js
@@ -1,7 +1,7 @@
 /*
  The MIT License (MIT)
 
- Copyright (c) 2015 ongoing Marcus Pöckelmann
+ Copyright (c) 2015 and 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.2
+// version 2.3
 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.2';
+    CATview.version = '2.3';
 
     // id of the parent container that will include CATview
     CATview.parent_id = 'CATview';
@@ -46,8 +46,8 @@ const CATview = new function() {
     CATview.height_svg = document.getElementById('CATview').offsetHeight;
 
     // CATviews orientation
-    CATview.orientation = ['left', 'right', 'bottom'].indexOf(_orientation) == -1 ? 'top' : _orientation;
-    CATview.vertical = (CATview.orientation == 'left' || CATview.orientation == 'right');
+    CATview.orientation = ['left', 'right', 'bottom'].indexOf(_orientation) === -1 ? 'top' : _orientation;
+    CATview.vertical = (CATview.orientation === 'left' || CATview.orientation === 'right');
     CATview.x_inverted = (typeof _x_inverted === 'undefined') ? false : _x_inverted;  // setting: default order of edges (false → left/top, true → right/bottom)
     CATview.y_inverted = (typeof _y_inverted === 'undefined') ? false : _y_inverted;  // setting: default order of witnesses (false → top/left, true → bottom/right)
 
@@ -70,6 +70,7 @@ const CATview = new function() {
     CATview.rect_v_margin = null;
     CATview.rect_stroke = null;
     CATview.rect_border = "stroke: #2f2f86;";
+    CATview.enable_scroll_spy = true;
     CATview.scroll_spy_pos = 1;   // default position of the scroll spy
     CATview.display_remaining_edges = false;    // setting: should remaining alignment edges be displayed
     CATview.threshold = 50;                     // setting: for which degree of difference remaining alignment edges should be displayed
@@ -107,7 +108,7 @@ const CATview = new function() {
     CATview.edge_names = [];      // array of the edges' names, displayed on mouse over
 
     // scales to map the elements to the available pixels
-    CATview.scale_edges = null;   // map [from, to]
+    CATview.scale_edges = [0.5,1]; //null;   // map [from, to]
     CATview.scale_names = null;   // map the names
     CATview.scale_edges_original = null;  // map [0, n-1]
     // a scale for the color
@@ -116,6 +117,20 @@ const CATview = new function() {
     CATview.use_equality_color = true;
     //CATview.color = d3.scaleLinear().domain([0, 1]).range(["#b8b8b8", "#000"]);
     //CATview.color = d3.scaleLinear().domain([0, 1]).range(["#7CCF7C", "c0c"]);
+
+    CATview.drag_enabled = false;    // is drag'n'drop allowed
+    CATview.drag_callback = null;   // called after a successful name swap
+    CATview.drag_names_order = null;
+    // the current order of names (might differ from the original order due to drag'n'drop)
+    CATview.drag_order_name2pos = null; // array that maps: name index → position index
+    // some helper variables across the events
+    CATview.drag_axis_attr = CATview.vertical? "x" : "y";
+    CATview.drag_positions = [];
+    CATview.drag_old_pos = null;
+    CATview.drag_new_pos = null;
+    CATview.drag_do_end = false;
+    CATview.drag_threshold = 0.1;
+    CATview.drag_mode = 'insert';  // 'insert' || 'swap'
   };
 
   // method to set margins and content width/height according to the current orientation of CATview and its axis
@@ -128,12 +143,12 @@ const CATview = new function() {
       CATview.margin.top = CATview.x_inverted ? CATview.space_for_tool_icons : CATview.space_for_names_axis;
       CATview.margin.bottom = CATview.x_inverted ? CATview.space_for_names_axis : CATview.space_for_tool_icons;
       // handle place of the edges axis according to the (left or right) orientation of CATview
-      CATview.margin.left = CATview.orientation == 'left' ? CATview.space_for_other : CATview.space_for_egdes_axis;
-      CATview.margin.right = CATview.orientation == 'left' ? CATview.space_for_egdes_axis : CATview.space_for_other;
+      CATview.margin.left = CATview.orientation === 'left' ? CATview.space_for_other : CATview.space_for_egdes_axis;
+      CATview.margin.right = CATview.orientation === 'left' ? CATview.space_for_egdes_axis : CATview.space_for_other;
     } else {
       // handle place of the edges axis according to the (top or bottom) orientation of CATview
-      CATview.margin.top = CATview.orientation == 'top' ? CATview.space_for_other : CATview.space_for_egdes_axis;
-      CATview.margin.bottom = CATview.orientation == 'top' ? CATview.space_for_egdes_axis : CATview.space_for_other;
+      CATview.margin.top = CATview.orientation === 'top' ? CATview.space_for_other : CATview.space_for_egdes_axis;
+      CATview.margin.bottom = CATview.orientation === 'top' ? CATview.space_for_egdes_axis : CATview.space_for_other;
       // handle place of names axis and tool icons in respect to the edges axis direction for horizontal orientation of CATview
       CATview.margin.left = CATview.x_inverted ? CATview.space_for_tool_icons : CATview.space_for_names_axis;
       CATview.margin.right = CATview.x_inverted ? CATview.space_for_names_axis : CATview.space_for_tool_icons;
@@ -144,14 +159,14 @@ const CATview = new function() {
     CATview.height_content  = CATview.height_svg - CATview.margin.top - CATview.margin.bottom;
   };
   // method to set scales according to the current orientation of CATview and its axis
-  this.set_scale_x = function(_from, _to){
+  this.set_scale_edges = function(_from, _to){
     if(CATview.debug)
-      console.log('CATview.set_scale_y');
+      console.log('CATview.set_scale_edges');
 
     if(CATview.vertical){
       // vertical orientation (left, right)
       if(CATview.x_inverted){
-        // inverted egdes axis (starting bottom)
+        // inverted edges axis (starting bottom)
         CATview.scale_edges = d3.scaleLinear().domain([_from, _to]).range([CATview.height_content, 0]);
       } else {
         // normal edges axis (starting top)
@@ -160,7 +175,7 @@ const CATview = new function() {
     } else {
       // horizontal orientation (top, bottom)
       if(CATview.x_inverted){
-        // inverted egdes axis (starting right)
+        // inverted edges axis (starting right)
         CATview.scale_edges = d3.scaleLinear().domain([_from, _to]).range([CATview.width_content, 0]);
       } else {
         // normal edges axis (starting left)
@@ -168,9 +183,9 @@ const CATview = new function() {
       }
     }
   };
-  this.set_scale_y = function(){
+  this.set_scale_names = function(){
     if(CATview.debug)
-      console.log('CATview.set_scale_y');
+      console.log('CATview.set_scale_names');
 
     if(CATview.vertical){
       // vertical orientation (left, right)
@@ -203,6 +218,12 @@ const CATview = new function() {
       if(CATview.debug)
         console.log(data);
       CATview.names = data.names;
+      // default order of names
+      CATview.drag_order_name2pos = [];
+      for(let i = 0; i < CATview.names.length; i++){
+        CATview.drag_order_name2pos.push(i);
+      }
+
       CATview.edges = data.edges;
       CATview.remaining_edges = data.remaining_edges;
       CATview.extra_segments = data.extra_segments;
@@ -259,7 +280,7 @@ const CATview = new function() {
               for (let j = CATview.search_results.length - 1; j >= 0; j--) {
                 if (CATview.search_results[j][0] > col)
                   CATview.search_results[j][0] -= 1;
-                else if (CATview.search_results[j][0] == col)
+                else if (CATview.search_results[j][0] === col)
                   CATview.search_results.splice(j, 1);
 
               }
@@ -272,7 +293,7 @@ const CATview = new function() {
           }
         }
 
-        CATview.set_scale_x(0, CATview.edges.length + 1)
+        CATview.set_scale_edges(0, CATview.edges.length + 1)
         CATview.scale_edges_original = CATview.scale_edges.copy();
         CATview.refresh_content(CATview.from, CATview.to);
         return true;
@@ -297,12 +318,12 @@ const CATview = new function() {
     let names_offset = 0;
     if(CATview.names.length > 0){
       CATview.svg.append("g")
-      .attr('id', 'CATview_prerendered_names')
-      .selectAll("text")
-      .data(CATview.names)
-      .enter().append("text")
-      .text(function (d) {return d})
-      .attr('font-size', CATview.font_size_y);
+        .attr('id', 'CATview_prerendered_names')
+        .selectAll("text")
+        .data(CATview.names)
+        .enter().append("text")
+        .text(function (d) {return d})
+        .attr('font-size', CATview.font_size_y);
       // get the dimension of the longest text and add 2 pixels padding
       let names = document.querySelectorAll('#CATview_prerendered_names text');
       for(let i = 0; i < names.length; i++){
@@ -319,16 +340,19 @@ const CATview = new function() {
     CATview.set_margins();
 
     // initialize the scales
-    CATview.set_scale_x(0, CATview.edges.length + 1)
+    CATview.set_scale_edges(0, CATview.edges.length + 1)
     CATview.scale_edges_original = CATview.scale_edges.copy();
-    CATview.set_scale_y()
+    CATview.set_scale_names();
 
     // add some zebra stripes
     CATview.svg.append("g").attr("class", "zebra");
     CATview.draw_zebra_stripes();
 
     // add the names-axis
-    CATview.svg.append("g").attr("class", "axis names-axis");
+    if(CATview.drag_enabled)
+      CATview.svg.append("g").attr("class", "axis names-axis draggable");
+    else
+      CATview.svg.append("g").attr("class", "axis names-axis");
     CATview.draw_names_axis();
 
     // add the group of tool icons
@@ -337,8 +361,8 @@ const CATview = new function() {
 
     // add a group for the content and its zoom behavior
     CATview.content = CATview.svg.append("g")
-    .attr("class", "content")
-    .attr("transform", "translate(" + CATview.margin.left + "," + CATview.margin.top + ")");
+      .attr("class", "content")
+      .attr("transform", "translate(" + CATview.margin.left + "," + CATview.margin.top + ")");
 
     // add the zooming behavior
     CATview.zoom = d3.zoom().on("zoom", CATview.zooming);
@@ -347,9 +371,9 @@ const CATview = new function() {
 
     // the zoom needs an element over the contents' complete size
     CATview.content.append('rect')
-    .attr('width', CATview.width_content)
-    .attr('height', CATview.height_content)
-    .attr('style', 'fill-opacity: 0.0;');
+      .attr('width', CATview.width_content)
+      .attr('height', CATview.height_content)
+      .attr('style', 'fill-opacity: 0.0;');
 
     // add the edges-axis
     CATview.content.append("g").attr("class", "axis edges-axis");
@@ -364,7 +388,7 @@ const CATview = new function() {
 
     // set default interval of data to be shown and draw the content
     CATview.from_pixel = 0;
-    CATview.to_pixel = CATview.vertical == true ? CATview.height_content : CATview.width_content;
+    CATview.to_pixel = CATview.vertical === true ? CATview.height_content : CATview.width_content;
     CATview.refresh_content(1, CATview.edges.length);
 
     // append the brush, if it was enabled prior the call of draw_svg
@@ -376,23 +400,24 @@ const CATview = new function() {
   };
 
   // define the interval of edges and refresh the content afterwards
-  this.refresh_content = function(from, to){
+  this.refresh_content = function(_from, _to){
     if(CATview.debug)
-      console.log('CATview.refresh_content(' + from + ', ' + to + ')');
+      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;
+      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))
+      CATview.set_scale_edges(Math.max(CATview.from - 1, 0), Math.min(CATview.to + 1, CATview.edges.length + 1));
 
       CATview.draw_alignment();
       CATview.draw_extra_segments();
       CATview.draw_search_results();
-      CATview.draw_scroll_spy();
-      CATview.draw_remaining_edges();
+      if(CATview.enable_scroll_spy)
+				CATview.draw_scroll_spy();
+      //CATview.draw_remaining_edges();
     }
     else
       return false;
@@ -425,8 +450,8 @@ const CATview = new function() {
         pos_y = CATview.height_content;
     }
     edges_axis
-    .attr("transform", "translate(" + pos_x + "," + pos_y + ")")
-    .call(CATview.edgesAxis);
+      .attr("transform", "translate(" + pos_x + "," + pos_y + ")")
+      .call(CATview.edgesAxis);
   };
   this.draw_names_axis = function(){
     if(CATview.debug)
@@ -444,22 +469,31 @@ const CATview = new function() {
     else
       names_axis.attr("transform", "translate(" + CATview.margin.left + ", " + CATview.margin.top + ")");
 
+    // default names order (may not initialised yet or number of names are not up-to-date)
+    if(CATview.drag_order_name2pos === null || CATview.drag_order_name2pos.length != CATview.names.length){
+      CATview.drag_order_name2pos = [];
+      for(let i = 0; i < CATview.names.length; i++){
+        CATview.drag_order_name2pos.push(i);
+      }
+    }
+
     // add the names
     let anchor = 'end';
     if(CATview.vertical){
-      if((CATview.orientation == 'left' && CATview.x_inverted) || (CATview.orientation == 'right' && CATview.x_inverted == false))
+      if((CATview.orientation === 'left' && CATview.x_inverted) || (CATview.orientation === 'right' && !CATview.x_inverted))
         anchor = 'start';
-
       names_axis.selectAll("text")
         .data(CATview.names)
         .enter().append("text")
         .text(function (d) {return d})
-        .attr("x", function(d, i) {return CATview.scale_names(i)})
+        .attr("x", function(d, i) {return CATview.scale_names(CATview.drag_order_name2pos[i])})
+        .attr("original", function(d, i) {return i})
+        .attr("current", function(d, i) {return CATview.drag_order_name2pos[i]})
         .attr("style", "text-anchor: " + anchor + ";")
         .attr('font-size', CATview.font_size_y)
         .attr("transform", function(d, i) {
           // rotate for 90 degrees than move the names a bit to the right to make them centered
-          return 'rotate(' + (CATview.orientation == 'left' ? 90 : -90) + ', ' + CATview.scale_names(i) + ', 0)translate(0,' + (CATview.font_size_y/2) + ')';
+          return 'rotate(' + (CATview.orientation === 'left' ? 90 : -90) + ', ' + CATview.scale_names(CATview.drag_order_name2pos[i]) + ', 0)translate(0,' + (CATview.font_size_y/2) + ')';
         });
     } else {
       if(CATview.x_inverted)
@@ -468,10 +502,290 @@ const CATview = new function() {
         .data(CATview.names)
         .enter().append("text")
         .text(function (d) {return d})
-        .attr("y", function(d, i) {return CATview.scale_names(i) + 5 }) // a little offset due to font size
+        .attr("y", function(d, i) {return CATview.scale_names(CATview.drag_order_name2pos[i]) + 5 }) // a little offset due to font size
+        .attr("original", function(d, i) {return i})
+        .attr("current", function(d, i) {return CATview.drag_order_name2pos[i]})
         .attr("style", "text-anchor: " + anchor + ";")
         .attr('font-size', CATview.font_size_y);
     }
+
+    // enable drag'n'drop of names (and their associated edges)
+    if(CATview.drag_enabled){
+      // which axis the draggable names are placed
+      CATview.drag_axis_attr = CATview.vertical? "x" : "y";
+      // get the valid positions from dragging names
+      CATview.drag_positions = [];
+      d3.selectAll("g.axis.names-axis>text").each(function(d,i) { CATview.drag_positions.push( parseFloat(d3.select(this).attr(CATview.drag_axis_attr)));});
+      // and sort them in ascending order
+      CATview.drag_positions.sort(function(a, b){ return a-b; });
+
+      // define the drag events
+      var drag_handler = d3.drag()
+        .on("start", function(){
+          let drag_object = d3.select(this);
+          CATview.drag_old_pos = parseInt(drag_object.attr('current'));
+
+          d3.select("g.axis.names-axis")
+            .append("text")
+            .text(drag_object.text())
+            .attr("id", "ghost")
+            .style("visibility", "hidden")
+            .style("opacity", 0.5)
+            .style("text-anchor", "middle")
+            .attr("transform", "rotate("+(CATview.orientation === "left"? "90" : CATview.orientation === "right"? "-90" : "0") + ")" );
+
+        })
+        .on("drag", function(d){
+          // track the current grabbed name label
+          d3.select("#ghost")
+            .style("visibility", "visible")
+            .style("cursor", "grabbing")
+            .attr("x", d3.event.x)
+            .attr("y", d3.event.y);
+
+          if (!CATview.vertical){
+            d3.select("#ghost")
+              .style("visibility", "visible")
+              .style("cursor", "grabbing")
+              .attr("x", d3.event.x)
+              .attr("y", d3.event.y);
+          }
+          else {
+            if (CATview.orientation === "left"){
+              d3.select("#ghost")
+                .style("visibility", "visible")
+                .style("cursor", "grabbing")
+                .attr("x", d3.event.y)
+                .attr("y", (-1)*d3.event.x);
+            }
+            else {
+              d3.select("#ghost")
+                .style("visibility", "visible")
+                .style("cursor", "grabbing")
+                .attr("x", (-1)*d3.event.y)
+                .attr("y", d3.event.x);
+            }
+          }
+
+          // if current mouse pos differs enough from valid pos, then choose
+          // the nearest neighbour and switch positions with it if there is a neighbour
+          if (Math.abs((CATview.vertical? d3.event.x : d3.event.y) - CATview.drag_positions[CATview.drag_old_pos]) >
+            CATview.drag_threshold * Math.abs(CATview.drag_positions[0]-CATview.drag_positions[1])){
+            // only when the attributes here have been updated, we should do a drag-end event.
+            // if not, this results in an error when double clicking on an element, after a first
+            // drag has been applied
+            CATview.drag_do_end = true;
+
+            // we don't need the direction, we just need to identify the position of the dragged element
+            // with the one with the closest coordinates to the mouse cursor
+
+            // select the element with the closest y to current mouse position
+            // for this calculate all differences of the valid positions to the mouse cursor
+            // and take the index with lowest distance.
+            let distances = CATview.drag_positions.map(function(el){return Math.abs(el-(CATview.vertical? d3.event.x : d3.event.y));});
+            // get the index with minimal distance
+            let minimum = Math.min(...distances); // ... → translates the array to a list of values
+            let idx_min = distances.indexOf(minimum); // just returns first index that matches argument
+
+            // now we can estimate the targets position
+            CATview.drag_new_pos = idx_min;
+          }
+        })
+        .on("end", function(d){
+          // only swap on end of dragging!
+          // swap the dragged elements and the target elements position:
+          d3.select("#ghost").remove();
+
+          if(!CATview.drag_do_end)
+            return;
+
+          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 + "\"]");
+          }
+
+          // collects all information necessary to move the names (and their associated segments)
+          let names_to_move = [];
+          // add the dragged name
+          names_to_move.push({
+            'object': objects[old_pos],
+            'from': old_pos,
+            'to': new_pos,
+            'new_position': CATview.drag_positions[new_pos], // move_pos[move_pos.length-1]
+            'transform': objects[new_pos].attr("transform") // of name on target position
+          });
+          // add other names depending on the drag mode:
+          if(CATview.drag_mode === 'swap'){
+            // for swap mode → add the name at the target position
+            names_to_move.push({
+              'object': objects[new_pos],
+              'from': new_pos,
+              'to': old_pos,
+              'new_position': CATview.drag_positions[old_pos],
+              'transform': objects[old_pos].attr("transform") // of name on target position
+            });
+          }
+          else{
+            // for insert mode = a ring swap → add the names between the old and new position of the dragged name
+            if(old_pos < new_pos){
+              for (let pos = new_pos; pos > old_pos; pos--){
+                names_to_move.push({
+                  'object': objects[pos],
+                  'from': pos,
+                  'to': pos - 1,
+                  'new_position': CATview.drag_positions[pos - 1],
+                  'transform': objects[pos - 1].attr("transform")
+                });
+              }
+            }
+            else{ // new_pos < old_pos
+              for (let pos = new_pos; pos < old_pos; pos++){
+                names_to_move.push({
+                  'object': objects[pos],
+                  'from': pos,
+                  'to': pos + 1,
+                  'new_position': CATview.drag_positions[pos + 1],
+                  'transform': objects[pos + 1].attr("transform")
+                });
+              }
+            }
+
+          }
+
+          // perform the movment of the names and their associated segments
+          names_to_move.forEach(function(name, i){
+            // move the name
+            name.object.transition()
+              .attr(CATview.drag_axis_attr, name.new_position)
+              .attr("transform", name.transform); // add transformation of the text (rotation in vertical orientation)
+
+            // remember the new current position
+            name.object.attr('current', name.to);
+            // and update the names order
+            let original = parseInt(name.object.attr('original'));
+            CATview.drag_order_name2pos[original] = name.to;
+
+            // console.log('  ' + name.object.text() + ': ' + name.from + ' → ' + name.to);
+
+            // move the associated segments
+            let segments = d3.selectAll("rect.rect-segment[data-segment-index$=\"_"+original+"\"],rect.search_result[data-segment-index$=\"_"+original+"\"]");
+            // set the x or y attribute to the one at the position before (in move_pos)
+            let add_to_source = CATview.drag_positions[name.to] - CATview.drag_positions[name.from];
+            // segments can differ in height and therefore y-value (in vertical orientation)
+            // so we need to add the same amount instead of setting the y value the same for all
+            segments.each( function(){
+              d3.select(this)
+                .transition()
+                .attr(CATview.drag_axis_attr, parseFloat(d3.select(this).attr(CATview.drag_axis_attr)) + add_to_source);
+            });
+          });
+
+          // drag finished
+          CATview.drag_do_end = false;
+
+          if (CATview.drag_callback != null) {
+            return CATview.drag_callback(old_pos_idx, new_pos_idx);
+          }
+
+        });
+
+      // select the name labels and listen for drag-events
+      let inner_texts = d3.selectAll("g.axis.names-axis>text");
+      drag_handler(inner_texts);
+    }
+  };
+
+  this.enable_drag = function(_mode){
+    if(CATview.debug)
+      console.log('CATview.enable_drag');
+
+    if(_mode === 'swap')
+      CATview.drag_mode = 'swap'
+    else
+      CATview.drag_mode = 'insert'    // default
+
+    CATview.drag_enabled = true;
+
+    if(CATview.svg !== null && CATview.svg.select(".names-axis").size() === 1 ){
+      CATview.svg.append("g").attr("class", "axis names-axis ");
+      let axis = document.querySelector('.names-axis');
+      axis.classList.add('draggable');
+      CATview.draw_names_axis();
+    }
+
+    return true;
+  };
+  this.disable_drag = function(){
+    if(CATview.debug)
+      console.log('CATview.disable_drag');
+
+    CATview.drag_enabled = false;
+
+    if(CATview.svg !== null && CATview.svg.select(".names-axis").size() === 1 ){
+      CATview.svg.append("g").attr("class", "axis names-axis ");
+      let axis = document.querySelector('.names-axis');
+      axis.classList.remove('draggable');
+      CATview.draw_names_axis();
+    }
+
+    return true;
+  };
+  // _name2pos = array that maps: name index → position index, e.g. [3,1,2,0] (swap first and last text)
+  this.set_names_order = function(_name2pos){
+    if(CATview.debug)
+      console.log('CATview.set_names_order');
+
+    // TODO validate data
+    CATview.drag_order_name2pos = _name2pos;
+
+    if(CATview.drag_enabled){
+      CATview.draw_names_axis();
+      CATview.refresh_content(CATview.from, CATview.to);
+    }
+
+    return true;
+  };
+  this.reset_names_order = function(){
+    if(CATview.debug)
+      console.log('CATview.reset_names_order');
+
+    let names_order = {};
+    for(let i = 0; i < CATview.names.length; i++)
+      names_order[i] = i;
+
+    return CATview.set_names_order(names_order);
+  };
+  // returns an array that maps: name index → position index, e.g. [3,1,2,0] (swap first and last text)
+  this.get_names_order = function(){
+    if(CATview.debug)
+      console.log('CATview.get_names_order');
+
+    return CATview.drag_order_name2pos;
+  };
+  this.set_drag_callback = function(drag_callback) {
+    if(CATview.debug)
+      console.log('CATview.set_drag_callback');
+
+    CATview.drag_callback = drag_callback;
+    return true;
+  };
+
+  this.toggle_drag_mode = function(){
+    if(CATview.debug)
+      console.log('CATview.toggle_drag_mode');
+
+    if(CATview.drag_mode === 'insert')
+      CATview.drag_mode = 'swap'
+    else
+      CATview.drag_mode = 'insert'
+
+    // redraw the names axis
+    CATview.draw_names_axis();
+
+    return CATview.drag_mode;
   };
 
   // draws the zebra stripes (gray lines in the background for every second text)
@@ -500,9 +814,9 @@ const CATview = new function() {
       .attr("class", "rect-zebra")
       .attr("width", width )
       .attr("height", height)
-      .attr('x', CATview.vertical == true ? function(d, i) {return CATview.scale_names(i) - width / 2.0 + CATview.margin.left} : offset)
-      .attr('y', CATview.vertical == true ? offset : function(d, i) {return CATview.scale_names(i) - height / 2.0 + CATview.margin.top})
-      .attr("fill", function(d,i) { return i%2==1 ? "#e8e8e8":"transparent";})
+      .attr('x', CATview.vertical === true ? function(d, i) {return CATview.scale_names(i) - width / 2.0 + CATview.margin.left} : offset)
+      .attr('y', CATview.vertical === true ? offset : function(d, i) {return CATview.scale_names(i) - height / 2.0 + CATview.margin.top})
+      .attr("fill", function(d,i) { return i%2===1 ? "#e8e8e8":"transparent";})
   };
 
   // draws the icons for tools included in CATview.tools
@@ -513,11 +827,11 @@ const CATview = new function() {
     let toolicons = CATview.svg.select(".tool-icons");
     toolicons.selectAll('*').remove();  // remove the current content
 
-    let margin = 3;     // extra margin around the tool icon conatiner
+    let margin = 3;     // extra margin around the tool icon container
     let width, height;  // available space for the tool icon container
 
     // place the container for the tool icons
-    if(CATview.vertical == true){
+    if(CATview.vertical === true){
       width = CATview.width_svg - CATview.margin.left - CATview.margin.right - 2 * margin;
       height = CATview.space_for_tool_icons - 2 * margin;
       if(CATview.x_inverted)
@@ -565,7 +879,7 @@ const CATview = new function() {
 
       for(let i = 0; i < CATview.tools.length; i++){
         let x, y;
-        if(CATview.vertical == true){
+        if(CATview.vertical === true){
           x =  i * (fontsize + padding) + fontsize/2;
           y = fontsize/2;
         } else {
@@ -574,17 +888,17 @@ const CATview = new function() {
         }
 
         toolicons.append('text')
-        .attr("class", "tool-icon")
-        .attr("x", x)
-        .attr("y", y)
-        .attr("style", "text-anchor: middle; dominant-baseline: middle;")
-        .attr("font-family","FontAwesome")
-        //.attr("textLength", fontsize)
-        //.attr("lengthAdjust","spacingAndGlyphs")
-        .text(String.fromCharCode(parseInt(CATview.tools[i][0], 16)))
-        .attr('font-size', fontsize - 4)
-        .attr("transform", (function(){ return 'rotate(' + CATview.tools[i][2] + ' ' + x + ' ' + y + ')' }))
-        .on("click", CATview.tools[i][1]);
+          .attr("class", "tool-icon")
+          .attr("x", x)
+          .attr("y", y)
+          .attr("style", "text-anchor: middle; dominant-baseline: middle;")
+          .attr("font-family","FontAwesome")
+          //.attr("textLength", fontsize)
+          //.attr("lengthAdjust","spacingAndGlyphs")
+          .text(String.fromCharCode(parseInt(CATview.tools[i][0], 16)))
+          .attr('font-size', fontsize - 4)
+          .attr("transform", (function(){ return 'rotate(' + CATview.tools[i][2] + ' ' + x + ' ' + y + ')' }))
+          .on("click", CATview.tools[i][1]);
       }
     }
   };
@@ -601,9 +915,9 @@ const CATview = new function() {
       let edges_temp = CATview.edges.map(function(d, i) { return [d, i + 1];}).filter(function(d) { return CATview.from <= d[1] && d[1] <= CATview.to; });
 
       // get the max. width and height that is available for a segment
-      let segment_width = Math.floor(CATview.width_content/(CATview.vertical == true ? CATview.names.length : edges_temp.length));
+      let segment_width = Math.floor(CATview.width_content/(CATview.vertical === true ? CATview.names.length : edges_temp.length));
       if(segment_width < 1) segment_width = 1;
-      let segment_height = Math.floor(CATview.height_content/(CATview.vertical == true ? edges_temp.length : CATview.names.length));
+      let segment_height = Math.floor(CATview.height_content/(CATview.vertical === true ? edges_temp.length : CATview.names.length));
       if(segment_height < 1) segment_height = 1;
 
       // set width, height, margin and stroke for a rectangle
@@ -615,50 +929,52 @@ const CATview = new function() {
 
       // create a grouping row for each edge
       let rows = CATview.content.select(".alignment").selectAll("g.row_witness")
-      .data(edges_temp)
-      .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.scale_edges(parseInt(d[1]))-(CATview.rect_width/2)) + ", " + (-CATview.rect_height/2))
-        + ")"; })
-      .on('mouseenter', function(d, i){CATview.show_edge_name(i)})
-      .on('mouseleave', function(){ CATview.hide_edge_name()});
+        .data(edges_temp)
+        .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.scale_edges(parseInt(d[1]))-(CATview.rect_width/2)) + ", " + (-CATview.rect_height/2))
+          + ")"; })
+        .on('mouseenter', function(d, i){CATview.show_edge_name(i)})
+        .on('mouseleave', function(){ CATview.hide_edge_name()});
 
       // draw the rects for the witnesses in each row
       rows.selectAll("rect")
-      .data(function(d) { return d[0].map(function(d2, j) {return [j, (d[1] - 1), d2];}).filter( function(d2) { return d2[2] != "-1"; } ); })
-      .enter().append("rect")
-      .attr("class", "rect-segment")
-      .on("click", function(d){
-        CATview.click_on_edge_callback(d[0], d[1]);
-       })
-      .attr('cursor', 'pointer')
-      .attr("width", CATview.rect_width)
-      .attr("height", CATview.rect_height)
-      //.attr("ry", rect_corner)      // rounded Corners
-      .attr(CATview.vertical == true ? "x" : "y", function(d) { return CATview.scale_names(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])));
-      });
+        .data(function(d) { return d[0].map(function(d2, j) {return [j, (d[1] - 1), d2];}).filter( function(d2) { return d2[2] != "-1"; } ); })
+				.enter().append("rect")
+        .attr("class", "rect-segment")
+        .attr("data-segment-index", function(d) { return d[1] + "_" + d[0];})
+        .on("click", function(d){
+          CATview.click_on_edge_callback(d[0], d[1]);
+        })
+        .attr('cursor', 'pointer')
+        .attr("width", CATview.rect_width)
+        .attr("height", CATview.rect_height)
+        //.attr("ry", rect_corner)      // rounded Corners
+        .attr(CATview.vertical == true ? "x" : "y", function(d) { return CATview.scale_names(CATview.drag_order_name2pos[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])));
+        });
+
       // update the edges-axis
       CATview.edgesAxis.scale(CATview.scale_edges);
       CATview.content.select(".edges-axis").call(CATview.edgesAxis);
       CATview.content.select(".edges-axis").selectAll("text")
-      .attr("style", "text-anchor: middle;")
-      .attr("transform", function() {
-        // rotate for 90 degrees than move the positions a bit to make them centered
-        let transform = '';
-        switch (CATview.orientation)
-        {
-          case "left": transform = 'translate(3, 0)'; break;
-          case "right": transform = 'translate(-3, 0)'; break;
-          default: break;
-        }
-        return transform;
-      })
-      .attr('font-size', CATview.font_size_x);
+        .attr("style", "text-anchor: middle;")
+        .attr("transform", function() {
+          // rotate for 90 degrees than move the positions a bit to make them centered
+          let transform = '';
+          switch (CATview.orientation)
+          {
+            case "left": transform = 'translate(3, 0)'; break;
+            case "right": transform = 'translate(-3, 0)'; break;
+            default: break;
+          }
+          return transform;
+        })
+        .attr('font-size', CATview.font_size_x);
     }
     else
       return false;
@@ -722,8 +1038,8 @@ const CATview = new function() {
     if(CATview.debug)
       console.log('CATview.hide_edge_name');
     CATview.edge_name.transition()
-    .duration(200)
-    .style("opacity", 0);
+      .duration(200)
+      .style("opacity", 0);
   };
 
   // draws the scroll spy as orange bar
@@ -731,29 +1047,29 @@ const CATview = new function() {
     if(CATview.debug)
       console.log('CATview.draw_scroll_spy: ' + index);
 
-    if (arguments.length == 1) CATview.scroll_spy_pos = parseInt(index) + 1;
+    if (arguments.length === 1) CATview.scroll_spy_pos = parseInt(index) + 1;
 
     if(CATview.content){
       CATview.content.selectAll("rect.scroll_spy").remove();
       if(CATview.from <= CATview.scroll_spy_pos && CATview.scroll_spy_pos <= CATview.to)
       {
-        if(CATview.vertical == true){
+        if(CATview.vertical === true){
           let height = CATview.rect_height + 2 * (CATview.rect_v_margin/2);
           CATview.content.insert("rect", ".edges-axis")
-          .attr("class", "scroll_spy")
-          .attr("width", CATview.width_content)
-          .attr("height", height)
-          .attr("x", 0)
-          .attr("y", CATview.scale_edges(CATview.scroll_spy_pos) - (height/2));
+            .attr("class", "scroll_spy")
+            .attr("width", CATview.width_content)
+            .attr("height", height)
+            .attr("x", 0)
+            .attr("y", CATview.scale_edges(CATview.scroll_spy_pos) - (height/2));
         }
         else{
           let width = CATview.rect_width + 2 * (CATview.rect_h_margin/2);
           CATview.content.insert("rect", ".edges-axis")
-          .attr("class", "scroll_spy")
-          .attr("width", width)
-          .attr("height", CATview.height_content)
-          .attr("x", CATview.scale_edges(CATview.scroll_spy_pos) - (width/2))
-          .attr("y", "0");
+            .attr("class", "scroll_spy")
+            .attr("width", width)
+            .attr("height", CATview.height_content)
+            .attr("x", CATview.scale_edges(CATview.scroll_spy_pos) - (width/2))
+            .attr("y", "0");
         }
       }
     }
@@ -787,11 +1103,11 @@ const CATview = new function() {
           edges[1].forEach(function (edge) {
             if (CATview.from <= edge[0][1] + 1 && edge[0][1] + 1 <= CATview.to && CATview.from <= edge[1][1] + 1 && edge[1][1] + 1 <= CATview.to) {
               CATview.content.select(".remaining_edges").append('line')
-              .attr("class", "remaining")
-              .attr('x1', CATview.scale_edges(edge[0][1] + 1))
-              .attr('y1', CATview.scale_names(edge[0][0]))
-              .attr('x2', CATview.scale_edges(edge[1][1] + 1))
-              .attr('y2', CATview.scale_names(edge[1][0]));
+                .attr("class", "remaining")
+                .attr('x1', CATview.scale_edges(edge[0][1] + 1))
+                .attr('y1', CATview.scale_names(edge[0][0]))
+                .attr('x2', CATview.scale_edges(edge[1][1] + 1))
+                .attr('y2', CATview.scale_names(edge[1][0]));
             }
           });
 
@@ -839,11 +1155,14 @@ const CATview = new function() {
             .attr('cursor', 'pointer')
             .attr("width", CATview.rect_width - 1)
             .attr("height", CATview.rect_height - 2)
-            .attr(CATview.vertical == true ? "x" : "y", function(d) { return CATview.scale_names(segment[1]) + 1})
+            .attr(CATview.vertical == true ? "x" : "y", function(d) {
+              return CATview.scale_names(CATview.drag_order_name2pos[segment[1]]) + 1
+            })
             .attr("style", function(d) {
               return 'fill:transparent; stroke-width:2; stroke-dasharray:3,1; stroke:' +
-                (parseFloat(segment[2]) == 0.0 && CATview.use_equality_color ? CATview.equality_color : CATview.scale_color(parseFloat(segment[2])));
-            });
+                (parseFloat(segment[2]) === 0.0 && CATview.use_equality_color ? CATview.equality_color : CATview.scale_color(parseFloat(segment[2])));
+            })
+            .attr("data-segment-index",segment[0] + "_" + segment[1]);
         });
       }
     }
@@ -871,7 +1190,7 @@ const CATview = new function() {
 
       if(CATview.search_results){
 
-        if(CATview.search_mode == 'seg'){
+        if(CATview.search_mode === 'seg'){
           // draw highlight-rectangles over the segment-rectangles
           // first filter the hits according to the zoom
           let hits = CATview.search_results.filter(function(d){return (CATview.from <= d[0] + 1 && d[0] +1 <= CATview.to)});
@@ -882,19 +1201,20 @@ const CATview = new function() {
               function (a, b) { return a.concat(b);});
 
             CATview.content.select(".search_results_foreground").selectAll("rect")
-            .data(hits)
-            .enter().append("rect")
-            .attr('class', 'search_result')
-            .on("click", function (d) {CATview.click_on_edge_callback(d[1], d[0] - 1);})
-            .attr('cursor', 'pointer')
-            .attr("width", CATview.rect_width)
-            .attr("height", CATview.rect_height)
-            .attr("x", function (d) {return CATview.vertical == true ?
-              (CATview.scale_names(d[1]) - CATview.rect_width / 2) : (CATview.scale_edges(d[0]) - CATview.rect_width / 2);
-            })
-            .attr("y", function (d) {return CATview.vertical == true ?
-              (CATview.scale_edges(d[0]) - CATview.rect_height / 2) : (CATview.scale_names(d[1]) - CATview.rect_height / 2);
-            });
+              .data(hits)
+              .enter().append("rect")
+              .attr('class', 'search_result')
+              .on("click", function (d) {CATview.click_on_edge_callback(d[1], d[0] - 1);})
+              .attr('cursor', 'pointer')
+              .attr("width", CATview.rect_width)
+              .attr("height", CATview.rect_height)
+              .attr("x", function (d) {return CATview.vertical == true ?
+                (CATview.scale_names(CATview.drag_order_name2pos[d[1]]) - CATview.rect_width / 2) : (CATview.scale_edges(d[0]) - CATview.rect_width / 2);
+              })
+              .attr("y", function (d) {return CATview.vertical == true ?
+                (CATview.scale_edges(d[0]) - CATview.rect_height / 2) : (CATview.scale_names(CATview.drag_order_name2pos[d[1]]) - CATview.rect_height / 2);
+              })
+              .attr("data-segment-index", function(d){return (d[0]-1) + "_" + d[1] });
           }
         }
         else{
@@ -903,24 +1223,24 @@ const CATview = new function() {
             let edge = CATview.search_results[i][0] + 1;
             // test whether the search hit is within the currently shown excerpt of the alignment
             if(CATview.from <= edge && edge <= CATview.to){
-              if(CATview.vertical == true){
+              if(CATview.vertical === true){
                 let height = CATview.rect_height + 2;
                 CATview.content.select(".search_results_background").append('rect')
-                .attr('class', 'search_result')
-                .attr("width", CATview.width_content - 4)
-                .attr("height", height)
-                .attr("x", 2)
-                .attr("y", CATview.scale_edges(edge) - (height/2));
+                  .attr('class', 'search_result')
+                  .attr("width", CATview.width_content - 4)
+                  .attr("height", height)
+                  .attr("x", 2)
+                  .attr("y", CATview.scale_edges(edge) - (height/2));
 
               }
               else{
                 let width = CATview.rect_width + 2;
                 CATview.content.select(".search_results_background").append('rect')
-                .attr('class', 'search_result')
-                .attr("width", width)
-                .attr("height", CATview.height_content - 4)
-                .attr("x", CATview.scale_edges(edge) - (width/2))
-                .attr("y", 2);
+                  .attr('class', 'search_result')
+                  .attr("width", width)
+                  .attr("height", CATview.height_content - 4)
+                  .attr("x", CATview.scale_edges(edge) - (width/2))
+                  .attr("y", 2);
               }
             }
           }
@@ -941,7 +1261,7 @@ const CATview = new function() {
 
     if(CATview.content){
       CATview.set_margins();
-      CATview.set_scale_x(0, CATview.edges.length + 1);
+      CATview.set_scale_edges(0, CATview.edges.length + 1);
       CATview.scale_edges_original = CATview.scale_edges.copy();
       // relocate the content container
       CATview.svg.select(".content").attr("transform", "translate(" + CATview.margin.left + "," + CATview.margin.top + ")");
@@ -949,7 +1269,7 @@ const CATview = new function() {
       CATview.draw_edges_axis();
       CATview.draw_names_axis();
       CATview.draw_tool_icons();
-      CATview.refresh_content();
+      CATview.refresh_content(CATview.from, CATview.to);
     }
     else
       return false;
@@ -960,15 +1280,15 @@ const CATview = new function() {
     if(CATview.debug)
       console.log('CATview.invert_names_axis');
 
-    CATview.y_inverted = !CATview.y_inverted;
+    let name2pos = CATview.get_names_order(); //  map: name index → position index
+    let pos2name = Array(name2pos.length);    //  create map: position index → name index
+    for(let i = 0; i < name2pos.length; i++)
+      pos2name[name2pos[i]] = i;
+    pos2name.reverse();                       // invert the positions
+    for(let i = 0; i < pos2name.length; i++)  // remap to: name index → position index
+      name2pos[pos2name[i]] = i;
 
-    if(CATview.content){
-      CATview.set_scale_y();
-      CATview.draw_names_axis();
-      CATview.refresh_content();
-    }
-    else
-      return false;
+    return CATview.set_names_order(name2pos);
   };
 
   // set the maximum zooming level (enable or disable zooming)
@@ -996,23 +1316,23 @@ const CATview = new function() {
       console.log('CATview.zooming');
 
     let new_scale = d3.event.transform.k;
-    let new_translate = CATview.vertical == true ? (d3.event.transform.y/new_scale) : (d3.event.transform.x/new_scale);
+    let new_translate = CATview.vertical === true ? (d3.event.transform.y/new_scale) : (d3.event.transform.x/new_scale);
 
     if(CATview.content){
 
       let delta_translate = 0;
       let scale_from = 0;
       let scale_to = 0;
-      let current_size = CATview.vertical == true ? CATview.height_content : CATview.width_content;
+      let current_size = CATview.vertical === true ? CATview.height_content : CATview.width_content;
       // if the scale factor has changed
-      if (new_scale != CATview.scale) {
+      if (new_scale !== CATview.scale) {
         // 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);
+        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){
+      else if(CATview.display_brush === false){
         // calculate the relative translation (only if the brush function is disabled)
         delta_translate = CATview.translate - new_translate;
       }
@@ -1051,7 +1371,7 @@ const CATview = new function() {
         new_from = new_to;
         new_to = swap;
       }
-      if(new_from != CATview.from || new_to != CATview.to) {
+      if(new_from !== CATview.from || new_to !== CATview.to) {
         // refresh the content only if edges within the new zoom have changed
         CATview.refresh_content(new_from, new_to);
       }
@@ -1067,7 +1387,7 @@ const CATview = new function() {
         }
         CATview.brush.move(d3.select('.brush'), [from, to].map(CATview.scale_edges))
         // TODO prevent brushing listeners
-      }      
+      }
     }
     else
       return false;
@@ -1086,7 +1406,7 @@ const CATview = new function() {
     if(CATview.debug)
       console.log('CATview.enable_brush');
 
-    if(CATview.display_brush == false){
+    if(CATview.display_brush === false){
       CATview.display_brush = true;
       // add the brush to CATview
       if(CATview.content)
@@ -1105,7 +1425,7 @@ const CATview = new function() {
     if(CATview.debug)
       console.log('CATview.disable_brush');
 
-    if(CATview.display_brush == true) {
+    if(CATview.display_brush === true) {
       // reset the brush's attributes
       CATview.display_brush = false;
       CATview.brush_from_edge = -1;
@@ -1128,15 +1448,15 @@ const CATview = new function() {
       console.log('CATview.draw_brush');
 
     if(CATview.content){
-      if(CATview.display_brush == true){
+      if(CATview.display_brush === true){
         // remove previous brush
         CATview.content.select(".brush").remove();
         // add the brush to CATview
 
-        if(CATview.vertical == true) {
+        if(CATview.vertical === true) {
           CATview.brush = d3.brushY()
-          .on("brush", CATview.brushing)
-          .on("end", CATview.brush_end);
+            .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]]]);
@@ -1154,8 +1474,8 @@ const CATview = new function() {
         }
 
         CATview.content.append("g")
-        .attr("class", "brush")
-        .call(CATview.brush);
+          .attr("class", "brush")
+          .call(CATview.brush);
       }
       return true;
     }
@@ -1179,7 +1499,7 @@ const CATview = new function() {
   this.brushing = function() {
     if(CATview.debug)
       console.log('CATview.brushing');
-  
+
     // handle inverted edges axis
     let from = CATview.x_inverted ? 1 : 0;
 
@@ -1190,7 +1510,7 @@ const CATview = new function() {
     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;
 
@@ -1200,7 +1520,7 @@ const CATview = new function() {
 
     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;
@@ -1242,9 +1562,9 @@ const CATview = new function() {
 
     // 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){
+    if(brush_range === 0){
       console.log('intervene')
       to_edge = from_edge;
       brush_range = 1;
@@ -1253,10 +1573,10 @@ const CATview = new function() {
     // 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));
+        [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));
+        [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
@@ -1321,7 +1641,7 @@ const CATview = new function() {
   this.add_tool_toggle_search_mode = function(index){
     return CATview.add_tool('f002',
       function(){
-        CATview.search_mode = (CATview.search_mode == 'seg' ? 'col' : 'seg');
+        CATview.search_mode = (CATview.search_mode === 'seg' ? 'col' : 'seg');
         CATview.draw_search_results();},
       index,
       0);
@@ -1450,12 +1770,18 @@ const CATview = new function() {
   // enable/disable console output to debug
   this.toggle_debug = function(debug){
     if(debug != null && typeof(debug) == "boolean")
-        CATview.debug = !debug;
+      CATview.debug = !debug;
 
     CATview.debug = !CATview.debug;
     return true;
   };
 
+  // parse a vaue to float and set precision to avoid rounding errors
+  this.to_float = function(_value){
+    return parseFloat(_value);
+    return parseFloat(parseFloat(_value).toFixed(5));
+  }
+
   // todo
   // - may: method to set search_mode → toggle_search_mode(_mode)
   // - may: method to zoom → zoom_to(_factor)
diff --git a/demo/demo_bottom.html b/demo/demo_bottom.html
index 849851c..1aa9682 100644
--- a/demo/demo_bottom.html
+++ b/demo/demo_bottom.html
@@ -81,6 +81,7 @@
       "Duis at…", "Ut varius…", "Quisque tincidunt…", "Curabitur ac…", "Vivamus vulputate…",
       "Aliquam sagittis…", "Nunc ut…", "Curabitur placerat…", "Aenean molestie…", "Donec quis…",
       "Aliquam sagittis…", "Duis nunc…", "Pellentesque habitant…", "Tincidunt dignissim…", "Nullam non…" ]);
+    CATview.enable_drag();
     CATview.add_tool_invert_names_axis();   // invert-names-axis tool
     CATview.add_tool_invert_edges_axis();   // invert-edges-axis tool
     CATview.add_tool_toggle_search_mode();  // switch-highlight-mode-of-search-results tool
diff --git a/demo/demo_left.html b/demo/demo_left.html
index 8d19397..5e0eabb 100644
--- a/demo/demo_left.html
+++ b/demo/demo_left.html
@@ -82,6 +82,7 @@
       "Duis at…", "Ut varius…", "Quisque tincidunt…", "Curabitur ac…", "Vivamus vulputate…",
       "Aliquam sagittis…", "Nunc ut…", "Curabitur placerat…", "Aenean molestie…", "Donec quis…",
       "Aliquam sagittis…", "Duis nunc…", "Pellentesque habitant…", "Tincidunt dignissim…", "Nullam non…" ]);
+    CATview.enable_drag();
     CATview.add_tool_invert_names_axis();   // invert-names-axis tool
     CATview.add_tool_invert_edges_axis();   // invert-edges-axis tool
     CATview.add_tool_toggle_search_mode();  // switch-highlight-mode-of-search-results tool
diff --git a/demo/demo_right.html b/demo/demo_right.html
index 8bcb7ab..4d751e5 100644
--- a/demo/demo_right.html
+++ b/demo/demo_right.html
@@ -82,6 +82,7 @@
       "Duis at…", "Ut varius…", "Quisque tincidunt…", "Curabitur ac…", "Vivamus vulputate…",
       "Aliquam sagittis…", "Nunc ut…", "Curabitur placerat…", "Aenean molestie…", "Donec quis…",
       "Aliquam sagittis…", "Duis nunc…", "Pellentesque habitant…", "Tincidunt dignissim…", "Nullam non…" ]);
+    CATview.enable_drag();
     CATview.add_tool_invert_names_axis();   // invert-names-axis tool
     CATview.add_tool_invert_edges_axis();   // invert-edges-axis tool
     CATview.add_tool_toggle_search_mode();  // switch-highlight-mode-of-search-results tool
diff --git a/demo/demo_top.html b/demo/demo_top.html
index 0548081..7661647 100644
--- a/demo/demo_top.html
+++ b/demo/demo_top.html
@@ -82,6 +82,7 @@
       "Duis at…", "Ut varius…", "Quisque tincidunt…", "Curabitur ac…", "Vivamus vulputate…",
       "Aliquam sagittis…", "Nunc ut…", "Curabitur placerat…", "Aenean molestie…", "Donec quis…",
       "Aliquam sagittis…", "Duis nunc…", "Pellentesque habitant…", "Tincidunt dignissim…", "Nullam non…" ]);
+    CATview.enable_drag('insert');
     CATview.add_tool_invert_names_axis();   // invert-names-axis tool
     CATview.add_tool_invert_edges_axis();   // invert-edges-axis tool
     CATview.add_tool_toggle_search_mode();  // switch-highlight-mode-of-search-results tool
-- 
GitLab