diff --git a/pd/nw/css/default.css b/pd/nw/css/default.css
index ea5ea5da9a31f6bdc2d6545e40fcb06a8a72dfd0..da5b7ad6d899dfb3713ab621aa5f5db4802aa9d4 100644
--- a/pd/nw/css/default.css
+++ b/pd/nw/css/default.css
@@ -20,6 +20,13 @@ body {
     font-family: "DejaVu Sans Mono";
 }
 
+/* delete the default scrollbars, and let's make our own */
+body::-webkit-scrollbar {
+    width: 0px;
+    height: 0px;
+    background: transparent;
+}
+
 .noselect {
     -webkit-touch-callout: none;
     -webkit-user-select: none;
diff --git a/pd/nw/index.js b/pd/nw/index.js
index 6e9da613c3d48c96ffb10ddc1ee07eee7a9542da..0361f595a9f7488770f1f81f4ffc2654d448a438 100644
--- a/pd/nw/index.js
+++ b/pd/nw/index.js
@@ -408,7 +408,8 @@ function nw_create_window(cid, type, width, height, xpos, ypos, attr_array) {
         // the window.  Ideally we would just get rid of the canvas menu
         // altogether to simplify things. But we'd have to add some kind of
         // widget for the "Put" menu.
-        height: height + 23,
+        // ico@vt.edu: on 0.46.2 this is now 25, go figure...
+        height: height + 25,
         x: xpos,
         y: ypos
     }, function (new_win) {
diff --git a/pd/nw/pd_canvas.html b/pd/nw/pd_canvas.html
index 5b6da1ff2d362252e9e38dbd5d5a75b087490679..6c2753b8775a52ec9c7360ae64f55b05aefb0956 100644
--- a/pd/nw/pd_canvas.html
+++ b/pd/nw/pd_canvas.html
@@ -76,6 +76,8 @@
         </button>
       </div>
     </dialog>
+	<div id="hscroll" style="background-color: #00000044; position: fixed; left: 2px; bottom: 2px; border-radius: 0px; width: 10px; height: 5px; visibility: hidden;"></div>
+	<div id="vscroll" style="background-color: #00000044; position: fixed; right: 2px; top: 2px; border-radius: 0px; width: 5px; height: 10px; visibility: hidden;"></div>
     <script type="text/javascript" src="./pd_canvas.js"></script>
   </body>
 </html>
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index d2fb09295342f414dc8ab45e2d84e14cc51c0cad..6009838e427c97d726d8208a2125975d93411035 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -233,6 +233,13 @@ var canvas_events = (function() {
                 return false;
             },
             mousedown: function(evt) {
+                // ico@vt.edu capture middle click for a different type of scroll
+                // currently disabled due to problem with scrollBy and zoom
+                /*if (evt.which == 2)
+                {
+                    evt.stopPropagation();
+                    evt.preventDefault();                
+                }*/
                 var target_id, resize_type;
                 if (target_is_scrollbar(evt)) {
                     return;
@@ -521,6 +528,64 @@ var canvas_events = (function() {
                 }
                 canvas_events[canvas_events.get_previous_state()]();
             },
+            hscroll_mouseup: function(evt) {
+                canvas_events[canvas_events.get_previous_state()]();
+            },
+            hscroll_mousemove: function(evt) {
+                if (evt.movementX != 0) {
+                    //console.log("move: " + e.movementX);
+                    
+                    var hscroll = document.getElementById("hscroll");
+                    var svg_elem = document.getElementById("patchsvg");
+                    
+                    var min_width = document.body.clientWidth + 3;
+                    var width = svg_elem.getAttribute('width');
+                    var xScrollSize;
+                    
+                    xScrollSize = hscroll.offsetWidth;
+                  
+                    var xTranslate = evt.movementX *
+                        ((width - min_width)/(min_width - xScrollSize)) *
+                        (evt.movementX > 0 ? 1 : 0.75);
+                    if (xTranslate > 0 && xTranslate < 1) {
+                        xTranslate = 1;
+                    }
+                    if (xTranslate < 0 && xTranslate > -1) {
+                        xTranslate = -1;
+                    }
+                    //console.log(xTranslate);
+                    window.scrollBy(xTranslate, 0);
+                }
+            },
+            vscroll_mouseup: function(evt) {
+                canvas_events[canvas_events.get_previous_state()]();
+            },
+            vscroll_mousemove: function(evt) {
+                if (evt.movementY != 0) {
+                    //console.log("move: " + e.movementY);
+                    
+                    var vscroll = document.getElementById("vscroll");
+                    var svg_elem = document.getElementById("patchsvg");
+                    
+                    var min_height = document.body.clientHeight + 3;
+                    var height = svg_elem.getAttribute('height');
+                    var yScrollSize;
+                    
+                    yScrollSize = vscroll.offsetHeight;
+                  
+                    var yTranslate = evt.movementY *
+                        ((height - min_height)/(min_height - yScrollSize)) *
+                        (evt.movementY > 0 ? 2 : 1.5);
+                    if (yTranslate > 0 && yTranslate < 1) {
+                        yTranslate = 1;
+                    }
+                    if (yTranslate < 0 && yTranslate > -1) {
+                        yTranslate = -1;
+                    }
+                    //console.log(yTranslate);
+                    window.scrollBy(0, yTranslate);
+                }
+            },
             dropdown_menu_keydown: function(evt) {
                 var select_elem = document.querySelector("#dropdown_list"),
                     li;
@@ -729,6 +794,16 @@ var canvas_events = (function() {
             document.addEventListener("mouseup",
                 events.iemgui_label_mouseup, false);
         },
+        hscroll_drag: function() {
+            canvas_events.none();
+            document.addEventListener("mouseup", events.hscroll_mouseup, false);
+            document.addEventListener("mousemove", events.hscroll_mousemove, false);
+        },
+        vscroll_drag: function() {
+            canvas_events.none();
+            document.addEventListener("mouseup", events.vscroll_mouseup, false);
+            document.addEventListener("mousemove", events.vscroll_mousemove, false);
+        },
         text: function() {
             canvas_events.none();
 
@@ -770,6 +845,9 @@ var canvas_events = (function() {
             document.addEventListener("keydown", events.find_keydown, false);
             state = "search";
         },
+        update_scrollbars: function() {
+            pdgui.gui_update_scrollbars(name);
+        },
         register: function(n) {
             name = n;
         },
@@ -887,6 +965,13 @@ var canvas_events = (function() {
                     console.log("tried to save something");
                 }, false
             );
+                       
+            // add listener for the scrollbars
+            document.getElementById("hscroll").
+                addEventListener("mousedown", canvas_events.hscroll_drag, false);
+            document.getElementById("vscroll").
+                addEventListener("mousedown", canvas_events.vscroll_drag, false);
+            
             // Whoa-- huge workaround! Right now we're getting
             // the popup menu the way Pd Vanilla does it:
             // 1) send a mouse(down) message to Pd
@@ -1030,15 +1115,16 @@ var canvas_events = (function() {
             gui.Window.get().on("maximize", function() {
                 pdgui.gui_canvas_get_scroll(name);
             });
-            gui.Window.get().on("unmaximize", function() {
+            gui.Window.get().on("restore", function() {
                 pdgui.gui_canvas_get_scroll(name);
             });
             gui.Window.get().on("resize", function() {
+                pdgui.post("resize");
                 pdgui.gui_canvas_get_scroll(name);
             });
             gui.Window.get().on("focus", function() {
                 nw_window_focus_callback(name);
-            });
+            });            
             gui.Window.get().on("blur", function() {
                 nw_window_blur_callback(name);
             });
@@ -1047,6 +1133,12 @@ var canvas_events = (function() {
                 pdgui.pdsend(name, "setbounds", x, y,
                     x + w.width, y + w.height);
             });
+            
+            // map onscroll event
+            document.addEventListener("scroll", function() {
+                pdgui.gui_update_scrollbars(name);
+            });
+            
             // set minimum window size
             gui.Window.get().setMinimumSize(150, 100);
         }
@@ -1514,6 +1606,7 @@ function nw_create_patch_window_menus(gui, w, name) {
         click: function() {
             var win = gui.Window.get();
             win.toggleFullscreen();
+            pdgui.gui_canvas_get_scroll(name);
         }
     });
 
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index a24a6f714f1e53b5b07a87d0f22fa5ad1f7ff6ca..b3567c4df4b0b6b33853b7461fbc8ae30d64ae19 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -814,16 +814,23 @@ function canvas_check_geometry(cid) {
     var win_w = patchwin[cid].width,
         // "23" is a kludge to account for the menubar size.  See comment
         // in nw_create_window of index.js
-        win_h = patchwin[cid].height - 23,
+        // ico@vt.edu in 0.46.2 this is now 25 pixels, so I guess
+        // it is now officially kludge^2
+        win_h = patchwin[cid].height - 25,
         win_x = patchwin[cid].x,
         win_y = patchwin[cid].y,
         cnv_width = patchwin[cid].window.innerWidth,
-        cnv_height = patchwin[cid].window.innerHeight - 23;
+        cnv_height = patchwin[cid].window.innerHeight - 25;
     // We're reusing win_x and win_y below, as it
     // shouldn't make a difference to the bounds
-    // algorithm in Pd
+    // algorithm in Pd (ico@vt.edu: this is not true anymore)
+    //post("relocate " + pd_geo_string(cnv_width, cnv_height, win_x, win_y) + " " +
+    //       pd_geo_string(cnv_width, cnv_height, win_x, win_y));
+    // ico@vt.edu: replaced first pd_geo_string's first two args (originally
+    // win_x and win_y with cnv_width and cnv_height + 25 to ensure the window
+    // reopens exactly how it was saved)
     pdsend(cid, "relocate",
-           pd_geo_string(win_w, win_h, win_x, win_y),
+           pd_geo_string(cnv_width, cnv_height + 25, win_x, win_y),
            pd_geo_string(cnv_width, cnv_height, win_x, win_y)
     );
 }
@@ -1412,7 +1419,8 @@ var scroll = {},
     last_focused, // last focused canvas (doesn't include Pd window or dialogs)
     loading = {},
     title_queue= {}, // ugly kluge to work around an ugly race condition
-    popup_menu = {};
+    popup_menu = {},
+    toplevel_scalars = {};
 
     var patchwin = {}; // object filled with cid: [Window object] pairs
     var dialogwin = {}; // object filled with did: [Window object] pairs
@@ -1574,9 +1582,13 @@ function create_window(cid, type, width, height, xpos, ypos, attr_array) {
 }
 
 // create a new canvas
-function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir, dirty_flag, hide_scroll, hide_menu, cargs) {
+function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir, dirty_flag, hide_scroll, hide_menu, has_toplevel_scalars, cargs) {
     // hack for buggy tcl popups... should go away for node-webkit
     //reset_ctrl_on_popup_window
+    
+    // ico@vt.edu: added has_toplevel_scalars, which is primarily
+    // being used for detecting toplevel garrays but may need to be
+    // expanded to also deal with scalars
 
     // local vars for window-specific behavior
     // visibility of menu and scrollbars, plus canvas background
@@ -1597,6 +1609,7 @@ function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir,
     redo[cid] = false;
     font[cid] = 10;
     doscroll[cid] = 0;
+    toplevel_scalars[cid] = has_toplevel_scalars;
     // geometry is just the x/y screen offset "+xoff+yoff"
     geometry = geometry.slice(1);   // remove the leading "+"
     geometry = geometry.split("+"); // x/y screen offset (in pixels)
@@ -2604,6 +2617,10 @@ function gui_gobj_select(cid, tag) {
 function gui_gobj_deselect(cid, tag) {
     gui(cid).get_gobj(tag, function(e) {
         e.classList.remove("selected");
+        // ico@vt.edu: check for scroll in case the handle disappears
+        // during deselect. LATER: make handles always fit inside the
+        // object, so this won't be necessary
+        gui_canvas_get_scroll(cid);
     });
 }
 
@@ -3430,15 +3447,88 @@ function gui_mycanvas_coords(cid, tag, vis_width, vis_height, select_width, sele
     });
 }
 
+/* this creates a group immediately below the patchsvg object */
 function gui_scalar_new(cid, tag, isselected, t1, t2, t3, t4, t5, t6,
-    is_toplevel) {
+    is_toplevel, plot_style) {
     var g;
     // we should probably use gui_gobj_new here, but we"re doing some initial
     // scaling that normal gobjs don't need...
+    //post("gui_scalar_new " + t1 + " " + t2 +
+    //    " " + t3 + " " + t4 + " " + t5 + " " + t6);
+        
+    /* ico@vt.edu HACKTASCTIC: calculating scrollbars is throwing 0.997 for
+       plots drawn inside the subpatch and it is a result of the -1 in the 
+       (min_width - 1) / width call inside canvas_params. Yet, if we don't
+       call this, we don't have nice flush scrollbars with the regular edges.
+       This is why here we make a hacklicious hack and simply hide hscrollbar
+       since the scroll is not doing anything anyhow.
+       
+       After further testing, it seems that the aforesaid margin is a hit'n'miss
+       depending on the patch, so we will disable this and make the aforesaid
+       canvas_params equation min_width / width.
+
+    if (is_toplevel === 1) {
+        gui(cid).get_elem("hscroll", function(elem) {
+            elem.style.setProperty("display", "none");
+        });
+        gui(cid).get_elem("vscroll", function(elem) {
+            elem.style.setProperty("display", "none");
+        });
+    }*/
+      
     gui(cid).get_elem("patchsvg", function(svg_elem) {
         var matrix, transform_string, selection_rect;
-        matrix = [t1,t2,t3,t4,t5,t6];
-        transform_string = "matrix(" + matrix.join() + ")";
+        if (is_toplevel === 1) {
+            // here we deal with weird scrollbar offsets and
+            // inconsistencies for the various plot styles.
+            // the matrix format is xscale, 0, 0, yscale, width, height
+            // we don't use the matrix for the bar graph since it is
+            // difficult to get the right ratio, so we do the manual
+            // translate and scale instead.
+            // cases are: 0=points, 1=plot, 2=bezier, 3=bars
+            switch (plot_style) {
+                case 0:
+                    matrix = [t1,t2,t3,t4,t5,t6+0.5];
+                    break;
+                case 1:
+                    matrix = [t1,t2,t3,t4,t5,t6+1.5];
+                    break;
+                case 2:
+                    matrix = [t1,t2,t3,t4,t5,t6+1.5];
+                    break;
+                case 3:
+                    //matrix = [t1*.995,t2,t3,t4+1,t5+0.5,t6-2];
+                    matrix = 0;
+                    transform_string = "translate(" + 0 +
+                        "," + (t6+1) + ") scale(" + t1 + "," + t4 + ")";
+                    //post("transform_string = " + transform_string);
+                    break;                  
+            }
+        }        
+        else {
+            switch (plot_style) {
+                case 0:
+                    matrix = [t1,t2,t3,t4,t5,t6+0.5];
+                    break;
+                case 1:
+                    matrix = [t1,t2,t3,t4,t5,t6+1.5];
+                    break;
+                case 2:
+                    matrix = [t1,t2,t3,t4,t5,t6+1.5];
+                    break;
+                case 3:
+                    //matrix = [t1,t2,t3,t4+1,t5+0.5,t6+0.5];
+                    matrix = 0;
+                    transform_string = "translate(" + (t5+(t1 < 1 ? 0.5 : 1.5)) +
+                        "," + (t6+1) + ") scale(" + t1 + "," + t4 + ")";
+                    //post("transform_string = " + transform_string);
+                    break;
+            }
+        }
+        
+        if (matrix !== 0) {
+            transform_string = "matrix(" + matrix.join() + ")";
+        }
         g = create_item(cid, "g", {
             id: tag + "gobj",
             transform: transform_string,
@@ -3516,6 +3606,7 @@ function gui_draw_vis(cid, type, attr_array, tag_array) {
     gui(cid).get_elem(tag_array[0])
     .append(function(frag) {
         var item;
+        post("gui_draw_vis cid=" + cid + " type=" + type + " attr_array=" + attr_array);
         attr_array.push("id", tag_array[1]);
         item = create_item(cid, type, attr_array);
         frag.appendChild(item);
@@ -3617,6 +3708,7 @@ function gui_draw_configure(cid, tag, attr, val) {
 // the default behavior.
 function gui_draw_viewbox(cid, tag, attr, val) {
     // Value will be an empty array if the user provided no values
+    post("gui_draw_viewbox cid=" + cid + " tag=" + tag + " attr=" + attr + " val=" + val);
     gui(cid).get_elem("patchsvg", function(svg_elem) {
         if (val.length) {
             gui_draw_configure(cid, tag, attr, val)
@@ -4676,8 +4768,9 @@ function zoom_kludge(zoom_level) {
 function gui_canvas_popup(cid, xpos, ypos, canprop, canopen, isobject) {
     // Get page coords for top of window, in case we're scrolled
     gui(cid).get_nw_window(function(nw_win) {
-        var win_left = nw_win.window.document.body.scrollLeft,
-            win_top = nw_win.window.document.body.scrollTop,
+        // ico@vt.edu updated win_left and win_top for the 0.46.2
+        var win_left = nw_win.window.scrollX,
+            win_top = nw_win.window.scrollY,
             zoom_level = nw_win.zoomLevel, // these were used to work
             zfactor,                       // around an old nw.js popup pos
                                            // bug. Now it's only necessary
@@ -5742,11 +5835,64 @@ function gui_undo_menu(cid, undo_text, redo_text) {
     });
 }
 
+function zoom_level_to_chrome_percent(nw_win) {
+    var zoom = nw_win.zoomLevel;
+    switch (zoom) {
+        case -7:
+            zoom = 4;
+            break;
+        case -6:
+            zoom = 100/33;
+            break;
+        case -5:
+            zoom = 2;
+            break;
+        case -4:
+            zoom = 100/67;
+            break;
+        case -3:
+            zoom = 100/75;
+            break;
+        case -2:
+            zoom = 100/80;
+            break;
+        case -1:
+            zoom = 100/90;
+            break;
+        case 0:
+            zoom = 1;
+            break;
+        case 1:
+            zoom = 100/110;
+            break;
+        case 2:
+            zoom = 100/125;
+            break;
+        case 3:
+            zoom = 100/150;
+            break;
+        case 4:
+            zoom = 100/175;
+            break;
+        case 5:
+            zoom = 100/200;
+            break;
+        case 6:
+            zoom = 100/250;
+            break;
+        case 7:
+            zoom = 100/300;
+            break;  
+    }
+    return zoom;
+}
+
 // leverages the get_nw_window method in the callers...
 function canvas_params(nw_win)
 {
     // calculate the canvas parameters (svg bounding box and window geometry)
     // for do_getscroll and do_optimalzoom
+    //post("nw_win=" + nw_win + " " + nw_win.window + " " + nw_win.window.document);
     var bbox, width, height, min_width, min_height, x, y, svg_elem;
     svg_elem = nw_win.window.document.getElementById("patchsvg");
     bbox = svg_elem.getBBox();
@@ -5760,22 +5906,18 @@ function canvas_params(nw_win)
     x = bbox.x > 0 ? 0 : bbox.x,
     y = bbox.y > 0 ? 0 : bbox.y;
 
-    // The svg "overflow" attribute on an <svg> seems to be buggy-- for example,
-    // you can't trigger a mouseover event for a <rect> that is outside of the
-    // explicit bounds of the svg.
-    // To deal with this, we want to set the svg width/height to always be
-    // at least as large as the browser's viewport. There are a few ways to
-    // do this this, like documentElement.clientWidth, but window.innerWidth
-    // seems to give the best results.
-    // However, there is either a bug or some strange behavior regarding
-    // the viewport size: setting both the height and width of an <svg> to
-    // the viewport height/width will display the scrollbars. The height or
-    // width must be set to 4 less than the viewport size in order to keep
-    // the scrollbars from appearing. Here, we just subtract 4 from both
-    // of them. This could lead to some problems with event handlers but I
-    // haven't had a problem with it yet.
-    min_width = nw_win.window.innerWidth - 4;
-    min_height = nw_win.window.innerHeight - 4;
+    // ico@vt.edu: adjust body width and height to match patchsvg to ensure
+    // scrollbars only come up when we are indeed inside svg and not before
+    // with extra margins around. This is accurate to a pixel on nw 0.47.0.
+    // This is also needed when maximizing and restoring the window in order
+    // to trigger resizing of scrollbars.
+    min_width = nw_win.window.innerWidth;
+    min_height = nw_win.window.innerHeight;
+    
+    var body_elem = nw_win.window.document.body;
+    body_elem.style.width = min_width + "px";
+    body_elem.style.height = min_height + "px";
+
     // Since we don't do any transformations on the patchsvg,
     // let's try just using ints for the height/width/viewBox
     // to keep things simple.
@@ -5785,11 +5927,78 @@ function canvas_params(nw_win)
     min_height |= 0;
     x |= 0;
     y |= 0;
+
+    /* ico@vt.edu: now let's draw/update our own scrollbars, so that we
+       don't have to deal with the window size nonsense caused by the
+       built-in ones... */  
+    // zoom var is used to compensate for the zoom level and keep
+    // the scrollbars the same height
+    var zoom = zoom_level_to_chrome_percent(nw_win);
+    var yScrollSize, yScrollTopOffset;
+    var vscroll = nw_win.window.document.getElementById("vscroll");
+    yScrollSize = min_height / height; // used to be (min_height - 1) / height
+    yScrollTopOffset = Math.floor((nw_win.window.scrollY / height) * (min_height + 3));
+    
+    // yScrollSize reflects the amount of the patch we currently see,
+    // so if it drops below 1, that means we need our scrollbars 
+    if (yScrollSize < 1) {
+        var yHeight = Math.floor(yScrollSize * (min_height + 3));
+        vscroll.style.setProperty("height", (yHeight - 6) + "px");
+        vscroll.style.setProperty("top", (yScrollTopOffset + 2) + "px");
+        vscroll.style.setProperty("-webkit-clip-path",
+            "polygon(0px 0px, 5px 0px, 5px " + (yHeight - 6) +
+            "px, 0px " + (yHeight - 11) + "px, 0px 5px)");
+        vscroll.style.setProperty("width", (5 * zoom) + "px");
+        vscroll.style.setProperty("right", (2 * zoom) + "px");
+        vscroll.style.setProperty("visibility", "visible");
+    } else {
+        vscroll.style.setProperty("visibility", "hidden");
+    }
+    
+    var xScrollSize, xScrollLeftOffset;
+    var hscroll = nw_win.window.document.getElementById("hscroll");
+    xScrollSize = min_width / width; // used to be (min_width - 1) / width
+    xScrollLeftOffset = Math.floor((nw_win.window.scrollX / width) * (min_width + 3));
+
+    if (xScrollSize < 1) {
+        var xWidth = Math.floor(xScrollSize * (min_width + 3));
+        hscroll.style.setProperty("width", (xWidth - 6) + "px");
+        hscroll.style.setProperty("left", (xScrollLeftOffset + 2) + "px");
+        hscroll.style.setProperty("-webkit-clip-path",
+            "polygon(0px 0px, " + (xWidth - 11) + "px 0px, " +
+            (xWidth - 6) + "px 5px, 0px 5px)");
+        hscroll.style.setProperty("height", (5 * zoom) + "px");
+        hscroll.style.setProperty("bottom", (2 * zoom) + "px");
+        hscroll.style.setProperty("visibility", "visible");
+    } else {
+        hscroll.style.setProperty("visibility", "hidden");    
+    }
+    
+    //post("x=" + xScrollSize + " y=" + yScrollSize);
+    
     return { x: x, y: y, w: width, h: height,
              mw: min_width, mh: min_height };
 }
 
-function do_getscroll(cid) {
+
+// ico@vt.edu:
+// the timeout is a bad hack and does not solve the problem consistently
+// even on a fast computer, while also slowing down the overall user 
+// experience. As such, ti is disabled and left here for reference.
+/*var pd_getscroll_var = {};
+
+function pd_do_getscroll(cid) {
+    if (!pd_getscroll_var[cid]) {
+        pd_getscroll_var[cid] = setTimeout(function() {
+            do_getscroll(cid, 0);
+            pd_getscroll_var[cid] = null;
+        }, 250);
+    }
+}
+
+exports.pd_do_getscroll = pd_do_getscroll;*/
+
+function do_getscroll(cid, checkgeom) {
     // Since we're throttling these getscroll calls, they can happen after
     // the patch has been closed. We remove the cid from the patchwin
     // object on close, so we can just check to see if our Window object has
@@ -5797,6 +6006,11 @@ function do_getscroll(cid) {
     // This is an awfully bad pattern. The whole scroll-checking mechanism
     // needs to be rethought, but in the meantime this should prevent any
     // errors wrt the rendering context disappearing.
+    //post("do_getscroll " + checkgeom);
+    if (checkgeom == 1) {
+        canvas_check_geometry(cid);
+        return;
+    }
     gui(cid).get_nw_window(function(nw_win) {
         var svg_elem = nw_win.window.document.getElementById("patchsvg");
         var { x: x, y: y, w: width, h: height,
@@ -5820,7 +6034,11 @@ function do_getscroll(cid) {
     });
 }
 
+exports.do_getscroll = do_getscroll;
+
 var getscroll_var = {};
+var checkgeom_and_getscroll_var = {};
+var overriding_getscroll_var = {};
 
 // We use a setTimeout here for two reasons:
 // 1. nw.js has a nasty Renderer bug  when you try to modify the
@@ -5835,16 +6053,51 @@ var getscroll_var = {};
 //    graphics from displaying until the user releases the mouse,
 //    which would be a buggy UI
 function gui_canvas_get_scroll(cid) {
+    //post("win=" + cid);
+    //win_width = win.style.width;
+    //win_height = win.style.height;
+    if (toplevel_scalars[cid]) {
+        // we have scalars, so let's override the previous call
+        // because this will be a cpu intensive redraw, so we
+        // should do it only when the action that requested it
+        // is either done or stopped for long enough for the
+        // recalculation to happen
+        if (getscroll_var[cid]) {
+            clearTimeout(getscroll_var[cid]);
+            getscroll_var[cid] = null;
+        }
+    }
     if (!getscroll_var[cid]) {
-        getscroll_var[cid] = setTimeout(function() {
-            do_getscroll(cid);
+        getscroll_var[cid] = setTimeout(function() {              
+            do_getscroll(cid, toplevel_scalars[cid]);
             getscroll_var[cid] = null;
-        }, 250);
+        }, 50);
     }
 }
 
 exports.gui_canvas_get_scroll = gui_canvas_get_scroll;
 
+/* ico@vt.edu: here is one alternative getscroll call, it focuses on
+   overriding the previous call, so the getscroll is more delayed. This
+   is useful when manipulating a plot with a mouse, for instance, so that
+   we prevent excessive getscroll calls which can be rather cpu intensive.
+*/
+
+function gui_canvas_get_overriding_scroll(cid) {
+    //post("win=" + cid);
+    //win_width = win.style.width;
+    //win_height = win.style.height;
+    if (overriding_getscroll_var[cid]) {
+        clearTimeout(overriding_getscroll_var[cid]);
+    }
+    overriding_getscroll_var[cid] = setTimeout(function() {
+        do_getscroll(cid, 0);
+        overriding_getscroll_var[cid] = null;
+    }, 100);
+}
+
+exports.gui_canvas_get_overriding_scroll = gui_canvas_get_overriding_scroll;
+
 function do_optimalzoom(cid, hflag, vflag) {
     // determine an optimal zoom level that makes the entire patch fit within
     // the window
@@ -6008,3 +6261,73 @@ function gui_pddplink_open(filename, dir) {
         post("pddplink: error: file not found: " + filename);
     }
 }
+
+
+/* ico@vt.edu: this function is run when we scroll with a mouse wheel,
+   a touchpad (e.g. two-finger scroll), or some other HID. It is
+   linked from the pd_canvas.js and called from the garray_fittograph 1
+   call when we are resizing the plot toplevel window to avoid race condition.
+*/
+function gui_update_scrollbars(cid) {
+    //post("gui_update_scrollbars " + cid);
+    gui(cid).get_nw_window(function(nw_win) {
+        var hscroll = nw_win.window.document.getElementById("hscroll");
+        var vscroll = nw_win.window.document.getElementById("vscroll");
+        var svg_elem = nw_win.window.document.getElementById("patchsvg");
+        
+        if (vscroll.style.visibility == "visible")
+        {
+            var height, min_height;  
+            min_height = nw_win.window.innerHeight + 3;
+            height = svg_elem.getAttribute('height');
+            
+            var yScrollSize, yScrollTopOffset;
+            yScrollSize = (min_height - 4) / height;
+            yScrollTopOffset = Math.floor((nw_win.window.scrollY / height) * min_height);
+            
+            if (yScrollSize < 1) {
+                var yHeight = Math.floor(yScrollSize * min_height);
+                vscroll.style.setProperty("height", (yHeight - 6) + "px");
+                vscroll.style.setProperty("top", (yScrollTopOffset + 2) + "px");
+                vscroll.style.setProperty("-webkit-clip-path",
+                    "polygon(0px 0px, 5px 0px, 5px " + (yHeight - 6) +
+                    "px, 0px " + (yHeight - 11) + "px, 0px 5px)");
+                vscroll.style.setProperty("visibility", "visible");
+            } else {
+                vscroll.style.setProperty("visibility", "hidden");    
+            }
+        }
+
+        if (hscroll.style.visibility == "visible")
+        {
+            var min_width = nw_win.window.innerWidth + 3;
+            var width = svg_elem.getAttribute('width');
+            var xScrollSize, xScrollTopOffset;
+            
+            xScrollSize = (min_width - 4) / width;
+            xScrollTopOffset = Math.floor((nw_win.window.scrollX / width) * min_width);
+            
+            /* console.log("win_width=" + min_width + " bbox=" +
+                width + " xScrollSize=" + (xScrollSize * min_width) +
+                " topOffset=" + xScrollTopOffset); */
+
+            if (xScrollSize < 1) {
+                var xWidth = Math.floor(xScrollSize * min_width);
+                hscroll.style.setProperty("width", (xWidth - 6) + "px");
+                hscroll.style.setProperty("left", (xScrollTopOffset + 2) + "px");
+                hscroll.style.setProperty("-webkit-clip-path",
+                    "polygon(0px 0px, " + (xWidth - 11) + "px 0px, " +
+                    (xWidth - 6) + "px 5px, 0px 5px)");
+                hscroll.style.setProperty("visibility", "visible");
+            } else {
+                hscroll.style.setProperty("visibility", "hidden");    
+            }
+        }
+        // for future reference
+        //nw_win.document.getElementById("hscroll").
+        //    style.setProperty("visibility", "visible");
+        //console.log("width="+width);
+    });
+}
+
+exports.gui_update_scrollbars = gui_update_scrollbars;
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index d3ed51dab44bf74937fcc6d2a66a9f6b9094c246..1cf072df1179d239037484ed6ace020fb5da397c 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -713,16 +713,38 @@ void canvas_args_to_string(char *namebuf, t_canvas *x)
         strcpy(namebuf, " (");
         for (i = 0; i < env->ce_argc; i++)
         {
-            if (strlen(namebuf) > MAXPDSTRING/2 - 5)
+            if (strlen(namebuf) > MAXPDSTRING / 2 - 5)
                 break;
             if (i != 0)
                 strcat(namebuf, " ");
-            atom_string(&env->ce_argv[i], namebuf + strlen(namebuf), 
-                MAXPDSTRING/2);
+            atom_string(&env->ce_argv[i], namebuf + strlen(namebuf),
+                MAXPDSTRING / 2);
         }
         strcat(namebuf, ")");
     }
-    else namebuf[0] = 0;
+    else
+    {
+        namebuf[0] = 0;
+        t_gobj *g = NULL;
+        t_garray *a = NULL;
+        t_symbol *arrayname;
+        int found = 0, res;
+        for (g = x->gl_list; g; g = g->g_next)
+        {
+
+            if (pd_class(&g->g_pd) == garray_class)
+            {
+                res = garray_getname((t_garray *)g, &arrayname);
+                if (found)
+                {
+                    strcat(namebuf, " ");
+                }
+                strcat(namebuf, arrayname->s_name);
+                found++;
+                //post("found=%d %s %s", found, arrayname->s_name, namebuf);
+            }
+        }
+    }
 }
 
 void canvas_reflecttitle(t_canvas *x)
@@ -1314,8 +1336,8 @@ static void canvas_relocate(t_canvas *x, t_symbol *canvasgeom,
         < 4 ||
         sscanf(topgeom->s_name, "%dx%d+%d+%d", &tw, &th, &txpix, &typix) < 4)
         bug("canvas_relocate");
-            /* for some reason this is initially called with cw=ch=1 so
-            we just suppress that here. */
+    /* for some reason this is initially called with cw=ch=1 so
+    we just suppress that here. */
     if (cw > 5 && ch > 5)
         canvas_dosetbounds(x, txpix, typix,
             txpix + cw, typix + ch);
@@ -1325,24 +1347,44 @@ static void canvas_relocate(t_canvas *x, t_symbol *canvasgeom,
     t_array *a = NULL;
     int  num_elem = 0;
 
+    //int found_garray = 0;
+
     for (g = x->gl_list; g; g = g->g_next)
     {
         //fprintf(stderr, "searching\n");
-
+        //post("searching");
         //for subpatch garrays
         if (pd_class(&g->g_pd) == garray_class)
         {
             //fprintf(stderr,"found ya\n");
+            //post("found ya");
             ga = (t_garray *)g;
             if (ga)
             {
                 a = garray_getarray(ga);
                 num_elem = a->a_n;
                 garray_fittograph(ga, num_elem, 1);
+                //found_garray = 1;
             }
         }
     }
     canvas_checkconfig(x);
+
+    // ico@vt.edu:
+    // Here we update only scrollbars to avoid race condition
+    // caused by doing gui_canvas_get_scroll which in turn
+    // calls canvas_relocate to ensure garrays in subpatches
+    // are properly updated when those windows are resized.
+    // given that the scroll update will happen likely faster
+    // than the return value from the backend, we do this to
+    // get rid of the stale scrollbars, e.g. when making the
+    // window smaller (at first the scrollbars are there because
+    // the garray has not been redrawn yet, and then we update
+    // scrollbars once again here below.
+    //if (found_garray == 1) {
+        //post("found garray");
+    gui_vmess("do_getscroll", "xi", x, 0);
+    //}
 }
 
 void canvas_popabstraction(t_canvas *x)
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index cc98c265ebed858a603eaead924e56a07925b445..4258618a15b8734d44d8161909cf814af658c5b7 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -172,7 +172,7 @@ static void canvas_nlet_conf (t_canvas *x, int type) {
 
 void canvas_getscroll (t_canvas *x) {
     //sys_vgui("pdtk_canvas_getscroll .x%lx.c\n",(long)x);
-    gui_vmess("gui_canvas_get_scroll", "x", x);
+    gui_vmess("gui_canvas_get_overriding_scroll", "x", x);
 }
 
 /* ---------------- generic widget behavior ------------------------- */
@@ -2299,7 +2299,11 @@ static void canvas_rightclick(t_canvas *x, int xpos, int ypos, t_gobj *y_sel)
        otherwise they end-up being dirty without visible notification
        besides, why would one mess with their properties without
        seeing what is inside them? CURRENTLY DISABLED */
+    //post("canvas_rightclick %d", (y && pd_class(&y->g_pd) == garray_class ? 1 : 0));
     canprop = (!y || (y && class_getpropertiesfn(pd_class(&y->g_pd)))
+               // ico@vt.edu: the following ensures that even if we are clicking on
+               // a garray, we should still be able to get the canvas properties
+                || (y && pd_class(&y->g_pd) == garray_class)
                /*&& !canvas_isabstraction( ((t_glist*)y) )*/ );
     canopen = (y && zgetfn(&y->g_pd, gensym("menu-open")));
     /* we add an extra check for scalars to enable the "Open" button
@@ -2478,9 +2482,12 @@ void canvas_vis(t_canvas *x, t_floatarg f)
                 canvas_destroy_editor(x);
             }
             //fprintf(stderr,"new\n");
+            /* ico@vt.edu: here we use hasarray only for toplevel garrays
+               because they require check_config every time resize is invoked.
+               We may need to expand this to include scalars, as well. */
             canvas_create_editor(x);
             canvas_args_to_string(argsbuf, x);
-            gui_vmess("gui_canvas_new", "xiisiissiiis",
+            gui_vmess("gui_canvas_new", "xiisiissiiiis",
                 x,
                 (int)(x->gl_screenx2 - x->gl_screenx1),
                 (int)(x->gl_screeny2 - x->gl_screeny1),
@@ -2492,6 +2499,7 @@ void canvas_vis(t_canvas *x, t_floatarg f)
                 x->gl_dirty,
                 x->gl_noscroll,
                 x->gl_nomenu,
+                canvas_hasarray(x),
                 argsbuf);
 
             /* It looks like this font size call is no longer needed,
@@ -2618,13 +2626,7 @@ void canvas_setgraph(t_glist *x, int flag, int nogoprect)
 
         // check if we have array inside GOP, if so,
         // make sure hidetext is always hidden no matter what
-        t_gobj *g = x->gl_list;
-        int hasarray = 0;
-        while (g)
-        {
-            if (pd_class(&g->g_pd) == garray_class) hasarray = 1;
-            g = g->g_next;
-        }
+        int hasarray = canvas_hasarray(x);
         if (hasarray) x->gl_hidetext = 1;
 
         if (!nogoprect && !x->gl_goprect && !hasarray)
@@ -2810,13 +2812,7 @@ static void canvas_donecanvasdialog(t_glist *x,
 
     // check if we have array inside GOP, if so,
     // make sure GOP/hidetext is always enabled no matter what
-    t_gobj *g = x->gl_list;
-    int hasarray = 0;
-    while (g)
-    {
-        if (pd_class(&g->g_pd) == garray_class) hasarray = 1;
-        g = g->g_next;
-    }
+    int hasarray = canvas_hasarray(x);
     if (hasarray && graphme != 3)
     {
         graphme = 3; //gop flag + bit-shifted hidetext
@@ -3233,6 +3229,7 @@ void canvas_doclick(t_canvas *x, int xpos, int ypos, int which,
        to array_motion so that we can update corresponding send when
        the array has been changed */
     array_garray = NULL;
+    //post("canvas_doclick %d", doit);
 
     t_gobj *y;
     int shiftmod, runmode, altmod, doublemod = 0, rightclick,
diff --git a/pd/src/g_graph.c b/pd/src/g_graph.c
index fcaa0b44babeb15240c7b6d364fdce636f56a37a..d05c9c1081c47b338423f7d35d2b57902577a082 100644
--- a/pd/src/g_graph.c
+++ b/pd/src/g_graph.c
@@ -725,13 +725,34 @@ t_float glist_pixelstoy(t_glist *x, t_float ypix)
     /* convert an x coordinate value to an x pixel location in window */
 t_float glist_xtopixels(t_glist *x, t_float xval)
 {
+    // ico@vt.edu: used to deal with the bar graph
+    t_float plot_offset = 0;
+    t_gobj *g = x->gl_list;
+
     if (!x->gl_isgraph)
         return ((xval - x->gl_x1) / (x->gl_x2 - x->gl_x1));
     else if (x->gl_isgraph && x->gl_havewindow)
-        return (x->gl_screenx2 - x->gl_screenx1) * 
-            (xval - x->gl_x1) / (x->gl_x2 - x->gl_x1);
+    {
+        if (g != NULL && g->g_pd == garray_class)
+        {
+            t_garray *g_a = (t_garray *)g;
+            if (garray_get_style(g_a) == PLOTSTYLE_BARS)
+                plot_offset = 10;
+        }
+        //fprintf(stderr, "xtopixels xval=%f gl_x1=%f gl_x2=%f screenx1=%d screenx2=%d\n",
+        //    xval, x->gl_x1, x->gl_x2, x->gl_screenx1, x->gl_screenx2);
+        return (x->gl_screenx2 - x->gl_screenx1) *
+            (xval - x->gl_x1 - plot_offset) / (x->gl_x2 - x->gl_x1);
+    }
     else
     {
+        /* ico@vt.edu: some really stupid code to compensate for the fact
+           that the svg stroke featue adds unaccounted width to the bars */
+        if (g != NULL && g->g_pd == garray_class)
+        {
+            if (garray_get_style((t_garray *)g) == PLOTSTYLE_BARS)
+                plot_offset = 2;
+        }
         int x1, y1, x2, y2;
         if (!x->gl_owner)
             bug("glist_pixelstox");
@@ -742,13 +763,62 @@ t_float glist_xtopixels(t_glist *x, t_float xval)
 
 t_float glist_ytopixels(t_glist *x, t_float yval)
 {
+    t_float plot_offset = 0;
+    t_gobj *g = x->gl_list;
+
     if (!x->gl_isgraph)
         return ((yval - x->gl_y1) / (x->gl_y2 - x->gl_y1));
     else if (x->gl_isgraph && x->gl_havewindow)
-        return (x->gl_screeny2 - x->gl_screeny1) * 
-                (yval - x->gl_y1) / (x->gl_y2 - x->gl_y1);
+    {
+        if (g != NULL && g->g_pd == garray_class)
+        {
+            /*t_garray *g_a = (t_garray *)g;
+            if (garray_get_style((t_garray *)g) == PLOTSTYLE_POLY ||
+                    garray_get_style((t_garray *)g) == PLOTSTYLE_BEZ)*/
+            switch (garray_get_style((t_garray *)g))
+            {
+            case PLOTSTYLE_POINTS:
+                plot_offset = 2;
+                break;
+            case PLOTSTYLE_POLY:
+                plot_offset = 2;
+                break;
+            case PLOTSTYLE_BEZ:
+                plot_offset = 2;
+                break;
+            case PLOTSTYLE_BARS:
+                plot_offset = 2;
+                break;
+            }
+        }
+        return (x->gl_screeny2 - x->gl_screeny1 - plot_offset) *
+            (yval - x->gl_y1) / (x->gl_y2 - x->gl_y1);
+    }
     else 
     {
+        /* ico@vt.edu: some really stupid code to compensate for the fact
+           that the poly and bezier tend to overlap the GOP edges */
+        if (g != NULL && g->g_pd == garray_class)
+        {
+            /*t_garray *g_a = (t_garray *)g;
+            if (garray_get_style((t_garray *)g) == PLOTSTYLE_POLY ||
+                    garray_get_style((t_garray *)g) == PLOTSTYLE_BEZ)*/
+            switch (garray_get_style((t_garray *)g))
+            {
+                case PLOTSTYLE_POINTS:
+                    plot_offset = 2;
+                    break;
+                case PLOTSTYLE_POLY:
+                    plot_offset = 2;
+                    break;
+                case PLOTSTYLE_BEZ:
+                    plot_offset = 2;
+                    break;
+                case PLOTSTYLE_BARS:
+                    plot_offset = 2;
+                    break;
+            }
+        }
         int x1, y1, x2, y2;
         if (!x->gl_owner)
             bug("glist_pixelstoy");
diff --git a/pd/src/g_scalar.c b/pd/src/g_scalar.c
index 984accf40c4d82eeeb45a4253d3def5e9d2bf26d..441bb158ec428df3fc10149085679023d22f9d5b 100644
--- a/pd/src/g_scalar.c
+++ b/pd/src/g_scalar.c
@@ -1028,6 +1028,26 @@ static void scalar_vis(t_gobj *z, t_glist *owner, int vis)
     {
         t_float xscale = glist_xtopixels(owner, 1) - glist_xtopixels(owner, 0);
         t_float yscale = glist_ytopixels(owner, 1) - glist_ytopixels(owner, 0);
+        // this has been moved into pdgui.js gui_scalar_new, leaving here just
+        // in case the other implementation proves problematic
+        //t_float nw_yoffset = 0;
+        /*switch (plot_style)
+        {
+            case 0:
+                nw_yoffset = -0.5;
+                yscale += 1;
+                break;
+            case 1:
+                nw_yoffset = 1.5;
+                break;
+            case 2:
+                nw_yoffset = 1.5;
+                break;
+            case 3:
+                nw_yoffset = 0.5;
+                yscale += 1;
+                break;
+        }*/
         /* we translate the .scalar%lx group to displace it on the tk side.
            This is the outermost group for the scalar, something like a
            poor man's viewport.
@@ -1040,14 +1060,15 @@ static void scalar_vis(t_gobj *z, t_glist *owner, int vis)
            understand "None"-- instead we must send an empty symbol.) */
         char tagbuf[MAXPDSTRING];
         sprintf(tagbuf, "scalar%lx", (long unsigned int)x->sc_vec);
-        gui_vmess("gui_scalar_new", "xsiffffiii",
-            glist_getcanvas(owner), 
+        gui_vmess("gui_scalar_new", "xsiffffffii",
+            glist_getcanvas(owner),
             tagbuf,
             glist_isselected(owner, &x->sc_gobj),
             xscale, 0.0, 0.0, yscale,
-            (int)glist_xtopixels(owner, basex),
-            (int)glist_ytopixels(owner, basey),
-            glist_istoplevel(owner));
+            glist_xtopixels(owner, basex),
+            glist_ytopixels(owner, basey),
+            glist_istoplevel(owner),
+            plot_style);
         char groupbuf[MAXPDSTRING];
         // Quick hack to make gui_scalar_draw_group more general (so we
         // don't have to tack on "gobj" manually)
@@ -1267,7 +1288,7 @@ int scalar_doclick(t_word *data, t_template *template, t_scalar *sc,
 static int scalar_click(t_gobj *z, struct _glist *owner,
     int xpix, int ypix, int shift, int alt, int dbl, int doit)
 {
-    //fprintf(stderr,"scalar_click %d %d\n", xpix, ypix);
+    //post("scalar_click %d %d %d", xpix, ypix, doit);
     t_scalar *x = (t_scalar *)z;
 
     x->sc_bboxcache = 0;