|
| 1 | +/** |
| 2 | +* Copyright 2012-2017, Plotly, Inc. |
| 3 | +* All rights reserved. |
| 4 | +* |
| 5 | +* This source code is licensed under the MIT license found in the |
| 6 | +* LICENSE file in the root directory of this source tree. |
| 7 | +*/ |
| 8 | + |
| 9 | +'use strict'; |
| 10 | + |
| 11 | +var Plotly = require('../../plotly'); |
| 12 | +var Lib = require('../../lib'); |
| 13 | +var Registry = require('../../registry'); |
| 14 | + |
| 15 | +var SHOWISOLATETIP = true; |
| 16 | + |
| 17 | +module.exports = function handleClick(g, gd, numClicks) { |
| 18 | + if(gd._dragged || gd._editing) return; |
| 19 | + |
| 20 | + var hiddenSlices = gd._fullLayout.hiddenlabels ? |
| 21 | + gd._fullLayout.hiddenlabels.slice() : |
| 22 | + []; |
| 23 | + |
| 24 | + var legendItem = g.data()[0][0]; |
| 25 | + var fullData = gd._fullData; |
| 26 | + var fullTrace = legendItem.trace; |
| 27 | + var legendgroup = fullTrace.legendgroup; |
| 28 | + |
| 29 | + var i, j, kcont, key, keys, val; |
| 30 | + var attrUpdate = {}; |
| 31 | + var attrIndices = []; |
| 32 | + var carrs = []; |
| 33 | + var carrIdx = []; |
| 34 | + |
| 35 | + function insertUpdate(traceIndex, key, value) { |
| 36 | + var attrIndex = attrIndices.indexOf(traceIndex); |
| 37 | + var valueArray = attrUpdate[key]; |
| 38 | + if(!valueArray) { |
| 39 | + valueArray = attrUpdate[key] = []; |
| 40 | + } |
| 41 | + |
| 42 | + if(attrIndices.indexOf(traceIndex) === -1) { |
| 43 | + attrIndices.push(traceIndex); |
| 44 | + attrIndex = attrIndices.length - 1; |
| 45 | + } |
| 46 | + |
| 47 | + valueArray[attrIndex] = value; |
| 48 | + |
| 49 | + return attrIndex; |
| 50 | + } |
| 51 | + |
| 52 | + function setVisibility(fullTrace, visibility) { |
| 53 | + var fullInput = fullTrace._fullInput; |
| 54 | + if(Registry.hasTransform(fullInput, 'groupby')) { |
| 55 | + var kcont = carrs[fullInput.index]; |
| 56 | + if(!kcont) { |
| 57 | + var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby'); |
| 58 | + var lastGroupbyIndex = groupbyIndices[groupbyIndices.length - 1]; |
| 59 | + kcont = Lib.keyedContainer(fullInput, 'transforms[' + lastGroupbyIndex + '].styles', 'target', 'value.visible'); |
| 60 | + carrs[fullInput.index] = kcont; |
| 61 | + } |
| 62 | + |
| 63 | + var curState = kcont.get(fullTrace._group); |
| 64 | + |
| 65 | + // If not specified, assume visible. This happens if there are other style |
| 66 | + // properties set for a group but not the visibility. There are many similar |
| 67 | + // ways to do this (e.g. why not just `curState = fullTrace.visible`??? The |
| 68 | + // answer is: because it breaks other things like groupby trace names in |
| 69 | + // subtle ways.) |
| 70 | + if(curState === undefined) { |
| 71 | + curState = true; |
| 72 | + } |
| 73 | + |
| 74 | + if(curState !== false) { |
| 75 | + // true -> legendonly. All others toggle to true: |
| 76 | + kcont.set(fullTrace._group, visibility); |
| 77 | + } |
| 78 | + carrIdx[fullInput.index] = insertUpdate(fullInput.index, 'visible', fullInput.visible === false ? false : true); |
| 79 | + } else { |
| 80 | + // false -> false (not possible since will not be visible in legend) |
| 81 | + // true -> legendonly |
| 82 | + // legendonly -> true |
| 83 | + var nextVisibility = fullInput.visible === false ? false : visibility; |
| 84 | + |
| 85 | + insertUpdate(fullInput.index, 'visible', nextVisibility); |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + if(numClicks === 1 && SHOWISOLATETIP && gd.data && gd._context.showTips) { |
| 90 | + Lib.notifier('Double click on legend to isolate individual trace', 'long'); |
| 91 | + SHOWISOLATETIP = false; |
| 92 | + } else { |
| 93 | + SHOWISOLATETIP = false; |
| 94 | + } |
| 95 | + |
| 96 | + if(Registry.traceIs(fullTrace, 'pie')) { |
| 97 | + var thisLabel = legendItem.label, |
| 98 | + thisLabelIndex = hiddenSlices.indexOf(thisLabel); |
| 99 | + |
| 100 | + if(numClicks === 1) { |
| 101 | + if(thisLabelIndex === -1) hiddenSlices.push(thisLabel); |
| 102 | + else hiddenSlices.splice(thisLabelIndex, 1); |
| 103 | + } else if(numClicks === 2) { |
| 104 | + hiddenSlices = []; |
| 105 | + gd.calcdata[0].forEach(function(d) { |
| 106 | + if(thisLabel !== d.label) { |
| 107 | + hiddenSlices.push(d.label); |
| 108 | + } |
| 109 | + }); |
| 110 | + if(gd._fullLayout.hiddenlabels && gd._fullLayout.hiddenlabels.length === hiddenSlices.length && thisLabelIndex === -1) { |
| 111 | + hiddenSlices = []; |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + Plotly.relayout(gd, 'hiddenlabels', hiddenSlices); |
| 116 | + } else { |
| 117 | + var hasLegendgroup = legendgroup && legendgroup.length; |
| 118 | + var traceIndicesInGroup = []; |
| 119 | + var tracei; |
| 120 | + if(hasLegendgroup) { |
| 121 | + for(i = 0; i < fullData.length; i++) { |
| 122 | + tracei = fullData[i]; |
| 123 | + if(!tracei.visible) continue; |
| 124 | + if(tracei.legendgroup === legendgroup) { |
| 125 | + traceIndicesInGroup.push(i); |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + if(numClicks === 1) { |
| 131 | + var nextVisibility; |
| 132 | + |
| 133 | + switch(fullTrace.visible) { |
| 134 | + case true: |
| 135 | + nextVisibility = 'legendonly'; |
| 136 | + break; |
| 137 | + case false: |
| 138 | + nextVisibility = false; |
| 139 | + break; |
| 140 | + case 'legendonly': |
| 141 | + nextVisibility = true; |
| 142 | + break; |
| 143 | + } |
| 144 | + |
| 145 | + if(hasLegendgroup) { |
| 146 | + for(i = 0; i < fullData.length; i++) { |
| 147 | + if(fullData[i].visible !== false && fullData[i].legendgroup === legendgroup) { |
| 148 | + setVisibility(fullData[i], nextVisibility); |
| 149 | + } |
| 150 | + } |
| 151 | + } else { |
| 152 | + setVisibility(fullTrace, nextVisibility); |
| 153 | + } |
| 154 | + } else if(numClicks === 2) { |
| 155 | + // Compute the clicked index. expandedIndex does what we want for expanded traces |
| 156 | + // but also culls hidden traces. That means we have some work to do. |
| 157 | + var isClicked, isInGroup, otherState; |
| 158 | + var isIsolated = true; |
| 159 | + for(i = 0; i < fullData.length; i++) { |
| 160 | + isClicked = fullData[i] === fullTrace; |
| 161 | + if(isClicked) continue; |
| 162 | + |
| 163 | + isInGroup = (hasLegendgroup && fullData[i].legendgroup === legendgroup); |
| 164 | + |
| 165 | + if(!isInGroup && fullData[i].visible === true && !Registry.traceIs(fullData[i], 'notLegendIsolatable')) { |
| 166 | + isIsolated = false; |
| 167 | + break; |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + for(i = 0; i < fullData.length; i++) { |
| 172 | + // False is sticky; we don't change it. |
| 173 | + if(fullData[i].visible === false) continue; |
| 174 | + |
| 175 | + if(Registry.traceIs(fullData[i], 'notLegendIsolatable')) { |
| 176 | + continue; |
| 177 | + } |
| 178 | + |
| 179 | + switch(fullTrace.visible) { |
| 180 | + case 'legendonly': |
| 181 | + setVisibility(fullData[i], true); |
| 182 | + break; |
| 183 | + case true: |
| 184 | + otherState = isIsolated ? true : 'legendonly'; |
| 185 | + isClicked = fullData[i] === fullTrace; |
| 186 | + isInGroup = isClicked || (hasLegendgroup && fullData[i].legendgroup === legendgroup); |
| 187 | + setVisibility(fullData[i], isInGroup ? true : otherState); |
| 188 | + break; |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + for(i = 0; i < carrs.length; i++) { |
| 194 | + kcont = carrs[i]; |
| 195 | + if(!kcont) continue; |
| 196 | + var update = kcont.constructUpdate(); |
| 197 | + |
| 198 | + var updateKeys = Object.keys(update); |
| 199 | + for(j = 0; j < updateKeys.length; j++) { |
| 200 | + key = updateKeys[j]; |
| 201 | + val = attrUpdate[key] = attrUpdate[key] || []; |
| 202 | + val[carrIdx[i]] = update[key]; |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + // The length of the value arrays should be equal and any unspecified |
| 207 | + // values should be explicitly undefined for them to get properly culled |
| 208 | + // as updates and not accidentally reset to the default value. This fills |
| 209 | + // out sparse arrays with the required number of undefined values: |
| 210 | + keys = Object.keys(attrUpdate); |
| 211 | + for(i = 0; i < keys.length; i++) { |
| 212 | + key = keys[i]; |
| 213 | + for(j = 0; j < attrIndices.length; j++) { |
| 214 | + // Use hasOwnPropety to protect against falsey values: |
| 215 | + if(!attrUpdate[key].hasOwnProperty(j)) { |
| 216 | + attrUpdate[key][j] = undefined; |
| 217 | + } |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + Plotly.restyle(gd, attrUpdate, attrIndices); |
| 222 | + } |
| 223 | +}; |
0 commit comments