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('
'); } -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