From 04603d46f550fc5b1d042bee129c7a7fd961fa33 Mon Sep 17 00:00:00 2001
From: archmoj <mojtaba@plot.ly>
Date: Fri, 2 Aug 2019 13:07:58 -0400
Subject: [PATCH] make sunburst plot functions reusable for treemap

---
 src/traces/sunburst/helpers.js | 132 ++++++++++++++++++++++++++++
 src/traces/sunburst/plot.js    | 156 +++++++--------------------------
 2 files changed, 163 insertions(+), 125 deletions(-)
 create mode 100644 src/traces/sunburst/helpers.js

diff --git a/src/traces/sunburst/helpers.js b/src/traces/sunburst/helpers.js
new file mode 100644
index 00000000000..a475e31a3f8
--- /dev/null
+++ b/src/traces/sunburst/helpers.js
@@ -0,0 +1,132 @@
+/**
+* Copyright 2012-2019, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+var Lib = require('../../lib');
+var Color = require('../../components/color');
+var setCursor = require('../../lib/setcursor');
+var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
+
+exports.makeEventData = function(pt, trace) {
+    var cdi = pt.data.data;
+
+    var out = {
+        curveNumber: trace.index,
+        pointNumber: cdi.i,
+        data: trace._input,
+        fullData: trace,
+
+        // TODO more things like 'children', 'siblings', 'hierarchy?
+    };
+
+    appendArrayPointValue(out, trace, cdi.i);
+
+    return out;
+};
+
+exports.findEntryWithLevel = function(hierarchy, level) {
+    var out;
+    if(level) {
+        hierarchy.eachAfter(function(pt) {
+            if(exports.getPtId(pt) === level) {
+                return out = pt.copy();
+            }
+        });
+    }
+    return out || hierarchy;
+};
+
+exports.findEntryWithChild = function(hierarchy, childId) {
+    var out;
+    hierarchy.eachAfter(function(pt) {
+        var children = pt.children || [];
+        for(var i = 0; i < children.length; i++) {
+            var child = children[i];
+            if(exports.getPtId(child) === childId) {
+                return out = pt.copy();
+            }
+        }
+    });
+    return out || hierarchy;
+};
+
+exports.isHierachyRoot = function(pt) {
+    var cdi = pt.data.data;
+    return cdi.pid === '';
+};
+
+exports.isEntry = function(pt) {
+    return !pt.parent;
+};
+
+exports.isLeaf = function(pt) {
+    return !pt.children;
+};
+
+exports.getPtId = function(pt) {
+    var cdi = pt.data.data;
+    return cdi.id;
+};
+
+exports.setSliceCursor = function(sliceTop, gd, opts) {
+    var pt = sliceTop.datum();
+    var isTransitioning = (opts || {}).isTransitioning;
+    setCursor(sliceTop, (isTransitioning || exports.isLeaf(pt) || exports.isHierachyRoot(pt)) ? null : 'pointer');
+};
+
+exports.determineOutsideTextFont = function(trace, pt, layoutFont) {
+    var cdi = pt.data.data;
+    var ptNumber = cdi.i;
+
+    var color = Lib.castOption(trace, ptNumber, 'outsidetextfont.color') ||
+        Lib.castOption(trace, ptNumber, 'textfont.color') ||
+        layoutFont.color;
+
+    var family = Lib.castOption(trace, ptNumber, 'outsidetextfont.family') ||
+        Lib.castOption(trace, ptNumber, 'textfont.family') ||
+        layoutFont.family;
+
+    var size = Lib.castOption(trace, ptNumber, 'outsidetextfont.size') ||
+        Lib.castOption(trace, ptNumber, 'textfont.size') ||
+        layoutFont.size;
+
+    return {
+        color: color,
+        family: family,
+        size: size
+    };
+};
+
+exports.determineInsideTextFont = function(trace, pt, layoutFont) {
+    var cdi = pt.data.data;
+    var ptNumber = cdi.i;
+
+    var customColor = Lib.castOption(trace, ptNumber, 'insidetextfont.color');
+    if(!customColor && trace._input.textfont) {
+        // Why not simply using trace.textfont? Because if not set, it
+        // defaults to layout.font which has a default color. But if
+        // textfont.color and insidetextfont.color don't supply a value,
+        // a contrasting color shall be used.
+        customColor = Lib.castOption(trace._input, ptNumber, 'textfont.color');
+    }
+
+    var family = Lib.castOption(trace, ptNumber, 'insidetextfont.family') ||
+        Lib.castOption(trace, ptNumber, 'textfont.family') ||
+        layoutFont.family;
+
+    var size = Lib.castOption(trace, ptNumber, 'insidetextfont.size') ||
+        Lib.castOption(trace, ptNumber, 'textfont.size') ||
+        layoutFont.size;
+
+    return {
+        color: customColor || Color.contrast(cdi.color),
+        family: family,
+        size: size
+    };
+};
diff --git a/src/traces/sunburst/plot.js b/src/traces/sunburst/plot.js
index df35feb912a..0a0eed4b4ba 100644
--- a/src/traces/sunburst/plot.js
+++ b/src/traces/sunburst/plot.js
@@ -13,19 +13,17 @@ var d3Hierarchy = require('d3-hierarchy');
 
 var Registry = require('../../registry');
 var Fx = require('../../components/fx');
-var Color = require('../../components/color');
 var Drawing = require('../../components/drawing');
 var Lib = require('../../lib');
 var Events = require('../../lib/events');
 var svgTextUtils = require('../../lib/svg_text_utils');
-var setCursor = require('../../lib/setcursor');
-var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
 
 var transformInsideText = require('../pie/plot').transformInsideText;
 var formatPieValue = require('../pie/helpers').formatPieValue;
 var styleOne = require('./style').styleOne;
 
 var constants = require('./constants');
+var helpers = require('./helpers');
 
 module.exports = function(gd, cdmodule, transitionOpts, makeOnCompleteCallback) {
     var fullLayout = gd._fullLayout;
@@ -115,7 +113,7 @@ function plotOne(gd, cd, element, transitionOpts) {
     if(hasTransition) {
         // Important: do this before binding new sliceData!
         slices.each(function(pt) {
-            prevLookup[getPtId(pt)] = {
+            prevLookup[helpers.getPtId(pt)] = {
                 rpx0: pt.rpx0,
                 rpx1: pt.rpx1,
                 x0: pt.x0,
@@ -123,7 +121,7 @@ function plotOne(gd, cd, element, transitionOpts) {
                 transform: pt.transform
             };
 
-            if(!prevEntry && isEntry(pt)) {
+            if(!prevEntry && helpers.isEntry(pt)) {
                 prevEntry = pt;
             }
         });
@@ -137,7 +135,7 @@ function plotOne(gd, cd, element, transitionOpts) {
     var cutoff = maxDepth;
 
     // N.B. handle multiple-root special case
-    if(cd0.hasMultipleRoots && isHierachyRoot(entry)) {
+    if(cd0.hasMultipleRoots && helpers.isHierachyRoot(entry)) {
         sliceData = sliceData.slice(1);
         maxHeight -= 1;
         yOffset = 1;
@@ -158,7 +156,7 @@ function plotOne(gd, cd, element, transitionOpts) {
     var transTextX = function(d) { return cx + d.pxmid[0] * d.transform.rCenter + (d.transform.x || 0); };
     var transTextY = function(d) { return cy + d.pxmid[1] * d.transform.rCenter + (d.transform.y || 0); };
 
-    slices = slices.data(sliceData, function(pt) { return getPtId(pt); });
+    slices = slices.data(sliceData, function(pt) { return helpers.getPtId(pt); });
 
     slices.enter().append('g')
         .classed('slice', true);
@@ -187,9 +185,9 @@ function plotOne(gd, cd, element, transitionOpts) {
     // next x1 (i.e. sector end angle) of previous entry
     var nextX1ofPrevEntry = null;
     if(hasTransition && prevEntry) {
-        var prevEntryId = getPtId(prevEntry);
+        var prevEntryId = helpers.getPtId(prevEntry);
         slices.each(function(pt) {
-            if(nextX1ofPrevEntry === null && (getPtId(pt) === prevEntryId)) {
+            if(nextX1ofPrevEntry === null && (helpers.getPtId(pt) === prevEntryId)) {
                 nextX1ofPrevEntry = pt.x1;
             }
         });
@@ -201,7 +199,7 @@ function plotOne(gd, cd, element, transitionOpts) {
             // N.B. gd._transitioning is (still) *true* by the time
             // transition updates get hare
             var sliceTop = d3.select(this);
-            setSliceCursor(sliceTop, gd, {isTransitioning: false});
+            helpers.setSliceCursor(sliceTop, gd, {isTransitioning: false});
         });
     }
 
@@ -232,7 +230,7 @@ function plotOne(gd, cd, element, transitionOpts) {
 
         sliceTop
             .call(attachFxHandlers, gd, cd)
-            .call(setSliceCursor, gd, {isTransitioning: gd._transitioning});
+            .call(helpers.setSliceCursor, gd, {isTransitioning: gd._transitioning});
 
         slicePath.call(styleOne, pt, trace);
 
@@ -246,9 +244,9 @@ function plotOne(gd, cd, element, transitionOpts) {
         sliceText.text(formatSliceLabel(pt, trace, fullLayout))
             .classed('slicetext', true)
             .attr('text-anchor', 'middle')
-            .call(Drawing.font, isHierachyRoot(pt) ?
-              determineOutsideTextFont(trace, pt, fullLayout.font) :
-              determineInsideTextFont(trace, pt, fullLayout.font))
+            .call(Drawing.font, helpers.isHierachyRoot(pt) ?
+              helpers.determineOutsideTextFont(trace, pt, fullLayout.font) :
+              helpers.determineInsideTextFont(trace, pt, fullLayout.font))
             .call(svgTextUtils.convertToTspans, gd);
 
         // position the text relative to the slice
@@ -278,9 +276,9 @@ function plotOne(gd, cd, element, transitionOpts) {
     });
 
     function makeExitSliceInterpolator(pt) {
-        var id = getPtId(pt);
+        var id = helpers.getPtId(pt);
         var prev = prevLookup[id];
-        var entryPrev = prevLookup[getPtId(entry)];
+        var entryPrev = prevLookup[helpers.getPtId(entry)];
         var next;
 
         if(entryPrev) {
@@ -293,16 +291,16 @@ function plotOne(gd, cd, element, transitionOpts) {
             // this happens when maxdepth is set, when leaves must
             // be removed and the rootPt is new (i.e. does not have a 'prev' object)
             var parent;
-            var parentId = getPtId(pt.parent);
+            var parentId = helpers.getPtId(pt.parent);
             slices.each(function(pt2) {
-                if(getPtId(pt2) === parentId) {
+                if(helpers.getPtId(pt2) === parentId) {
                     return parent = pt2;
                 }
             });
             var parentChildren = parent.children;
             var ci;
             parentChildren.forEach(function(pt2, i) {
-                if(getPtId(pt2) === id) {
+                if(helpers.getPtId(pt2) === id) {
                     return ci = i;
                 }
             });
@@ -318,7 +316,7 @@ function plotOne(gd, cd, element, transitionOpts) {
     }
 
     function makeUpdateSliceIntepolator(pt) {
-        var prev0 = prevLookup[getPtId(pt)];
+        var prev0 = prevLookup[helpers.getPtId(pt)];
         var prev;
         var next = {x0: pt.x0, x1: pt.x1, rpx0: pt.rpx0, rpx1: pt.rpx1};
 
@@ -357,7 +355,7 @@ function plotOne(gd, cd, element, transitionOpts) {
     }
 
     function makeUpdateTextInterpolar(pt) {
-        var prev0 = prevLookup[getPtId(pt)];
+        var prev0 = prevLookup[helpers.getPtId(pt)];
         var prev;
         var transform = pt.transform;
 
@@ -445,7 +443,7 @@ function plotOne(gd, cd, element, transitionOpts) {
 
     function interpX0X1FromParent(pt) {
         var parent = pt.parent;
-        var parentPrev = prevLookup[getPtId(parent)];
+        var parentPrev = prevLookup[helpers.getPtId(parent)];
         var out = {};
 
         if(parentPrev) {
@@ -477,7 +475,7 @@ function findEntryWithLevel(hierarchy, level) {
     var out;
     if(level) {
         hierarchy.eachAfter(function(pt) {
-            if(getPtId(pt) === level) {
+            if(helpers.getPtId(pt) === level) {
                 return out = pt.copy();
             }
         });
@@ -491,7 +489,7 @@ function findEntryWithChild(hierarchy, childId) {
         var children = pt.children || [];
         for(var i = 0; i < children.length; i++) {
             var child = children[i];
-            if(getPtId(child) === childId) {
+            if(helpers.getPtId(child) === childId) {
                 return out = pt.copy();
             }
         }
@@ -499,30 +497,6 @@ function findEntryWithChild(hierarchy, childId) {
     return out || hierarchy;
 }
 
-function isHierachyRoot(pt) {
-    var cdi = pt.data.data;
-    return cdi.pid === '';
-}
-
-function isEntry(pt) {
-    return !pt.parent;
-}
-
-function isLeaf(pt) {
-    return !pt.children;
-}
-
-function getPtId(pt) {
-    var cdi = pt.data.data;
-    return cdi.id;
-}
-
-function setSliceCursor(sliceTop, gd, opts) {
-    var pt = sliceTop.datum();
-    var isTransitioning = (opts || {}).isTransitioning;
-    setCursor(sliceTop, (isTransitioning || isLeaf(pt) || isHierachyRoot(pt)) ? null : 'pointer');
-}
-
 function attachFxHandlers(sliceTop, gd, cd) {
     var cd0 = cd[0];
     var trace = cd0.trace;
@@ -600,7 +574,7 @@ function attachFxHandlers(sliceTop, gd, cd) {
                 textAlign: _cast('hoverlabel.align'),
                 hovertemplate: hovertemplate,
                 hovertemplateLabels: hoverPt,
-                eventData: [makeEventData(pt, traceNow)]
+                eventData: [helpers.makeEventData(pt, traceNow)]
             }, {
                 container: fullLayoutNow._hoverlayer.node(),
                 outerContainer: fullLayoutNow._paper.node(),
@@ -612,7 +586,7 @@ function attachFxHandlers(sliceTop, gd, cd) {
 
         trace._hasHoverEvent = true;
         gd.emit('plotly_hover', {
-            points: [makeEventData(pt, traceNow)],
+            points: [helpers.makeEventData(pt, traceNow)],
             event: d3.event
         });
     });
@@ -625,7 +599,7 @@ function attachFxHandlers(sliceTop, gd, cd) {
         if(trace._hasHoverEvent) {
             evt.originalEvent = d3.event;
             gd.emit('plotly_unhover', {
-                points: [makeEventData(pt, traceNow)],
+                points: [helpers.makeEventData(pt, traceNow)],
                 event: d3.event
             });
             trace._hasHoverEvent = false;
@@ -647,15 +621,15 @@ function attachFxHandlers(sliceTop, gd, cd) {
         var traceNow = gd._fullData[trace.index];
 
         var clickVal = Events.triggerHandler(gd, 'plotly_sunburstclick', {
-            points: [makeEventData(pt, traceNow)],
+            points: [helpers.makeEventData(pt, traceNow)],
             event: d3.event
         });
 
         // 'regular' click event when sunburstclick is disabled or when
         // clikcin on leaves or the hierarchy root
-        if(clickVal === false || isLeaf(pt) || isHierachyRoot(pt)) {
+        if(clickVal === false || helpers.isLeaf(pt) || helpers.isHierachyRoot(pt)) {
             if(fullLayoutNow.hovermode) {
-                gd._hoverdata = [makeEventData(pt, traceNow)];
+                gd._hoverdata = [helpers.makeEventData(pt, traceNow)];
                 Fx.click(gd, d3.event);
             }
             return;
@@ -673,13 +647,13 @@ function attachFxHandlers(sliceTop, gd, cd) {
         Registry.call('_storeDirectGUIEdit', traceNow, fullLayoutNow._tracePreGUI[traceNow.uid], {level: traceNow.level});
 
         var hierarchy = cd0.hierarchy;
-        var id = getPtId(pt);
-        var nextEntry = isEntry(pt) ?
+        var id = helpers.getPtId(pt);
+        var nextEntry = helpers.isEntry(pt) ?
             findEntryWithChild(hierarchy, id) :
             findEntryWithLevel(hierarchy, id);
 
         var frame = {
-            data: [{level: getPtId(nextEntry)}],
+            data: [{level: helpers.getPtId(nextEntry)}],
             traces: [trace.index]
         };
 
@@ -701,23 +675,6 @@ function attachFxHandlers(sliceTop, gd, cd) {
     });
 }
 
-function makeEventData(pt, trace) {
-    var cdi = pt.data.data;
-
-    var out = {
-        curveNumber: trace.index,
-        pointNumber: cdi.i,
-        data: trace._input,
-        fullData: trace,
-
-        // TODO more things like 'children', 'siblings', 'hierarchy?
-    };
-
-    appendArrayPointValue(out, trace, cdi.i);
-
-    return out;
-}
-
 function formatSliceLabel(pt, trace, fullLayout) {
     var textinfo = trace.textinfo;
 
@@ -745,57 +702,6 @@ function formatSliceLabel(pt, trace, fullLayout) {
     return thisText.join('<br>');
 }
 
-function determineOutsideTextFont(trace, pt, layoutFont) {
-    var cdi = pt.data.data;
-    var ptNumber = cdi.i;
-
-    var color = Lib.castOption(trace, ptNumber, 'outsidetextfont.color') ||
-        Lib.castOption(trace, ptNumber, 'textfont.color') ||
-        layoutFont.color;
-
-    var family = Lib.castOption(trace, ptNumber, 'outsidetextfont.family') ||
-        Lib.castOption(trace, ptNumber, 'textfont.family') ||
-        layoutFont.family;
-
-    var size = Lib.castOption(trace, ptNumber, 'outsidetextfont.size') ||
-        Lib.castOption(trace, ptNumber, 'textfont.size') ||
-        layoutFont.size;
-
-    return {
-        color: color,
-        family: family,
-        size: size
-    };
-}
-
-function determineInsideTextFont(trace, pt, layoutFont) {
-    var cdi = pt.data.data;
-    var ptNumber = cdi.i;
-
-    var customColor = Lib.castOption(trace, ptNumber, 'insidetextfont.color');
-    if(!customColor && trace._input.textfont) {
-        // Why not simply using trace.textfont? Because if not set, it
-        // defaults to layout.font which has a default color. But if
-        // textfont.color and insidetextfont.color don't supply a value,
-        // a contrasting color shall be used.
-        customColor = Lib.castOption(trace._input, ptNumber, 'textfont.color');
-    }
-
-    var family = Lib.castOption(trace, ptNumber, 'insidetextfont.family') ||
-        Lib.castOption(trace, ptNumber, 'textfont.family') ||
-        layoutFont.family;
-
-    var size = Lib.castOption(trace, ptNumber, 'insidetextfont.size') ||
-        Lib.castOption(trace, ptNumber, 'textfont.size') ||
-        layoutFont.size;
-
-    return {
-        color: customColor || Color.contrast(cdi.color),
-        family: family,
-        size: size
-    };
-}
-
 function getInscribedRadiusFraction(pt) {
     if(pt.rpx0 === 0 && Lib.isFullCircle([pt.x0, pt.x1])) {
         // special case of 100% with no hole