Best JavaScript code snippet using playwright-internal
select_test.js
Source:select_test.js
1var d3Select = require('../../strict-d3').select;2var d3SelectAll = require('../../strict-d3').selectAll;3var Plotly = require('@lib/index');4var Lib = require('@src/lib');5var click = require('../assets/click');6var doubleClick = require('../assets/double_click');7var DBLCLICKDELAY = require('@src/plot_api/plot_config').dfltConfig.doubleClickDelay;8var createGraphDiv = require('../assets/create_graph_div');9var destroyGraphDiv = require('../assets/destroy_graph_div');10var mouseEvent = require('../assets/mouse_event');11var touchEvent = require('../assets/touch_event');12var LONG_TIMEOUT_INTERVAL = 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL;13var delay = require('../assets/delay');14var sankeyConstants = require('@src/traces/sankey/constants');15function drag(path, options) {16 var len = path.length;17 if(!options) options = {type: 'mouse'};18 Lib.clearThrottle();19 if(options.type === 'touch') {20 touchEvent('touchstart', path[0][0], path[0][1], options);21 path.slice(1, len).forEach(function(pt) {22 Lib.clearThrottle();23 touchEvent('touchmove', pt[0], pt[1], options);24 });25 touchEvent('touchend', path[len - 1][0], path[len - 1][1], options);26 return;27 }28 mouseEvent('mousemove', path[0][0], path[0][1], options);29 mouseEvent('mousedown', path[0][0], path[0][1], options);30 path.slice(1, len).forEach(function(pt) {31 Lib.clearThrottle();32 mouseEvent('mousemove', pt[0], pt[1], options);33 });34 mouseEvent('mouseup', path[len - 1][0], path[len - 1][1], options);35}36function assertSelectionNodes(cornerCnt, outlineCnt, _msg) {37 var msg = _msg ? ' - ' + _msg : '';38 expect(d3SelectAll('.zoomlayer > .zoombox-corners').size())39 .toBe(cornerCnt, 'selection corner count' + msg);40 expect(d3SelectAll('.zoomlayer > .select-outline').size())41 .toBe(outlineCnt, 'selection outline count' + msg);42}43var selectingCnt, selectingData, selectedCnt, selectedData, deselectCnt, doubleClickData;44var selectedPromise, deselectPromise, clickedPromise;45function resetEvents(gd) {46 selectingCnt = 0;47 selectedCnt = 0;48 deselectCnt = 0;49 doubleClickData = null;50 gd.removeAllListeners();51 selectedPromise = new Promise(function(resolve) {52 gd.on('plotly_selecting', function(data) {53 // note that since all of these events test node counts,54 // and all of the other tests at some point check that each of55 // these event handlers was called (via assertEventCounts),56 // we no longer need separate tests that these nodes are created57 // and this way *all* subplot variants get the test.58 assertSelectionNodes(1, 2);59 selectingCnt++;60 selectingData = data;61 });62 gd.on('plotly_selected', function(data) {63 // With click-to-select supported, selection nodes are only64 // in the DOM in certain circumstances.65 if(data &&66 gd._fullLayout.dragmode.indexOf('select') > -1 &&67 gd._fullLayout.dragmode.indexOf('lasso') > -1) {68 assertSelectionNodes(0, 2);69 }70 selectedCnt++;71 selectedData = data;72 resolve();73 });74 });75 deselectPromise = new Promise(function(resolve) {76 gd.on('plotly_deselect', function(data) {77 assertSelectionNodes(0, 0);78 deselectCnt++;79 doubleClickData = data;80 resolve();81 });82 });83 clickedPromise = new Promise(function(resolve) {84 gd.on('plotly_click', function() {85 resolve();86 });87 });88}89function assertEventCounts(selecting, selected, deselect, msg) {90 expect(selectingCnt).toBe(selecting, 'plotly_selecting call count: ' + msg);91 expect(selectedCnt).toBe(selected, 'plotly_selected call count: ' + msg);92 expect(deselectCnt).toBe(deselect, 'plotly_deselect call count: ' + msg);93}94// TODO: in v3, when we get rid of the `plotly_selected->undefined` event, these will95// change to BOXEVENTS = [1, 1, 1], LASSOEVENTS = [4, 1, 1]. See also _run down below96//97// events for box or lasso select mouse moves then a doubleclick98var NOEVENTS = [0, 0, 0];99// deselect used to give an extra plotly_selected event on the first click100// with undefined event data - but now that's gone, since `clickFn` handles this.101var BOXEVENTS = [1, 2, 1];102// assumes 5 points in the lasso path103var LASSOEVENTS = [4, 2, 1];104var SELECT_PATH = [[93, 193], [143, 193]];105var LASSO_PATH = [[316, 171], [318, 239], [335, 243], [328, 169]];106describe('Click-to-select', function() {107 var mock14Pts = {108 '1': { x: 134, y: 116 },109 '7': { x: 270, y: 160 },110 '10': { x: 324, y: 198 },111 '35': { x: 685, y: 341 }112 };113 var gd;114 beforeEach(function() {115 gd = createGraphDiv();116 });117 afterEach(destroyGraphDiv);118 function plotMock14(layoutOpts) {119 var mock = require('@mocks/14.json');120 var defaultLayoutOpts = {121 layout: {122 clickmode: 'event+select',123 dragmode: 'select',124 hovermode: 'closest'125 }126 };127 var mockCopy = Lib.extendDeep(128 {},129 mock,130 defaultLayoutOpts,131 { layout: layoutOpts });132 return Plotly.newPlot(gd, mockCopy.data, mockCopy.layout);133 }134 /**135 * Executes a click and before resets selection event handlers.136 * By default, click is executed with a delay to prevent unwanted double clicks.137 * Returns the `selectedPromise` promise for convenience.138 */139 function _click(x, y, clickOpts, immediate) {140 resetEvents(gd);141 // Too fast subsequent calls of `click` would142 // produce an unwanted double click, thus we need143 // to delay the click.144 if(immediate) {145 click(x, y, clickOpts);146 } else {147 setTimeout(function() {148 click(x, y, clickOpts);149 }, DBLCLICKDELAY * 1.03);150 }151 return selectedPromise;152 }153 function _clickPt(coords, clickOpts, immediate) {154 expect(coords).toBeDefined('coords needs to be defined');155 expect(coords.x).toBeDefined('coords.x needs to be defined');156 expect(coords.y).toBeDefined('coords.y needs to be defined');157 return _click(coords.x, coords.y, clickOpts, immediate);158 }159 /**160 * Convenient helper to execute a click immediately.161 */162 function _immediateClickPt(coords, clickOpts) {163 return _clickPt(coords, clickOpts, true);164 }165 /**166 * Asserting selected points.167 *168 * @param expected can be a point number, an array169 * of point numbers (for a single trace) or an array of point number170 * arrays in case of multiple traces. undefined in an array of arrays171 * is also allowed, e.g. useful when not all traces support selection.172 */173 function assertSelectedPoints(expected) {174 var expectedPtsPerTrace = toArrayOfArrays(expected);175 var expectedPts, traceNum;176 for(traceNum = 0; traceNum < expectedPtsPerTrace.length; traceNum++) {177 expectedPts = expectedPtsPerTrace[traceNum];178 expect(gd._fullData[traceNum].selectedpoints).toEqual(expectedPts);179 expect(gd.data[traceNum].selectedpoints).toEqual(expectedPts);180 }181 function toArrayOfArrays(expected) {182 var isArrayInArray, i;183 if(Array.isArray(expected)) {184 isArrayInArray = false;185 for(i = 0; i < expected.length; i++) {186 if(Array.isArray(expected[i])) {187 isArrayInArray = true;188 break;189 }190 }191 return isArrayInArray ? expected : [expected];192 } else {193 return [[expected]];194 }195 }196 }197 function assertSelectionCleared() {198 gd._fullData.forEach(function(fullDataItem) {199 expect(fullDataItem.selectedpoints).toBeUndefined();200 });201 }202 it('selects a single data point when being clicked', function(done) {203 plotMock14()204 .then(function() { return _immediateClickPt(mock14Pts[7]); })205 .then(function() { assertSelectedPoints(7); })206 .then(done, done.fail);207 });208 describe('clears entire selection when the last selected data point', function() {209 [{210 desc: 'is clicked',211 clickOpts: {}212 }, {213 desc: 'is clicked while add/subtract modifier keys are active',214 clickOpts: { shiftKey: true }215 }].forEach(function(testData) {216 it('' + testData.desc, function(done) {217 plotMock14()218 .then(function() { return _immediateClickPt(mock14Pts[7]); })219 .then(function() {220 assertSelectedPoints(7);221 _clickPt(mock14Pts[7], testData.clickOpts);222 return deselectPromise;223 })224 .then(function() {225 assertSelectionCleared();226 return _clickPt(mock14Pts[35], testData.clickOpts);227 })228 .then(function() {229 assertSelectedPoints(35);230 })231 .then(done, done.fail);232 });233 });234 });235 it('cleanly clears and starts selections although add/subtract mode on', function(done) {236 plotMock14()237 .then(function() {238 return _immediateClickPt(mock14Pts[7]);239 })240 .then(function() {241 assertSelectedPoints(7);242 _clickPt(mock14Pts[7], { shiftKey: true });243 return deselectPromise;244 })245 .then(function() {246 assertSelectionCleared();247 return _clickPt(mock14Pts[35], { shiftKey: true });248 })249 .then(function() {250 assertSelectedPoints(35);251 })252 .then(done, done.fail);253 });254 it('supports adding to an existing selection', function(done) {255 plotMock14()256 .then(function() { return _immediateClickPt(mock14Pts[7]); })257 .then(function() {258 assertSelectedPoints(7);259 return _clickPt(mock14Pts[35], { shiftKey: true });260 })261 .then(function() { assertSelectedPoints([7, 35]); })262 .then(done, done.fail);263 });264 it('supports subtracting from an existing selection', function(done) {265 plotMock14()266 .then(function() { return _immediateClickPt(mock14Pts[7]); })267 .then(function() {268 assertSelectedPoints(7);269 return _clickPt(mock14Pts[35], { shiftKey: true });270 })271 .then(function() {272 assertSelectedPoints([7, 35]);273 return _clickPt(mock14Pts[7], { shiftKey: true });274 })275 .then(function() { assertSelectedPoints(35); })276 .then(done, done.fail);277 });278 it('can be used interchangeably with lasso/box select', function(done) {279 plotMock14()280 .then(function() {281 return _immediateClickPt(mock14Pts[35]);282 })283 .then(function() {284 assertSelectedPoints(35);285 drag(SELECT_PATH, { shiftKey: true });286 })287 .then(function() {288 assertSelectedPoints([0, 1, 35]);289 return _immediateClickPt(mock14Pts[7], { shiftKey: true });290 })291 .then(function() {292 assertSelectedPoints([0, 1, 7, 35]);293 return _clickPt(mock14Pts[1], { shiftKey: true });294 })295 .then(function() {296 assertSelectedPoints([0, 7, 35]);297 return Plotly.relayout(gd, 'dragmode', 'lasso');298 })299 .then(function() {300 assertSelectedPoints([0, 7, 35]);301 drag(LASSO_PATH, { shiftKey: true });302 })303 .then(function() {304 assertSelectedPoints([0, 7, 10, 35]);305 return _clickPt(mock14Pts[10], { shiftKey: true });306 })307 .then(function() {308 assertSelectedPoints([0, 7, 35]);309 drag([[670, 330], [695, 330], [695, 350], [670, 350]],310 { shiftKey: true, altKey: true });311 })312 .then(function() {313 assertSelectedPoints([0, 7]);314 return _clickPt(mock14Pts[35], { shiftKey: true });315 })316 .then(function() {317 assertSelectedPoints([0, 7, 35]);318 return _clickPt(mock14Pts[7]);319 })320 .then(function() {321 assertSelectedPoints([7]);322 return doubleClick(650, 100);323 })324 .then(function() {325 assertSelectionCleared();326 })327 .then(done, done.fail);328 });329 it('@gl works in a multi-trace plot', function(done) {330 Plotly.newPlot(gd, [331 {332 x: [1, 3, 5, 4, 10, 12, 12, 7],333 y: [2, 7, 6, 1, 0, 13, 6, 12],334 type: 'scatter',335 mode: 'markers',336 marker: { size: 20 }337 }, {338 x: [1, 7, 6, 2],339 y: [2, 3, 5, 4],340 type: 'bar'341 }, {342 x: [7, 8, 9, 10],343 y: [7, 9, 13, 21],344 type: 'scattergl',345 mode: 'markers',346 marker: { size: 20 }347 }348 ], {349 width: 400,350 height: 600,351 hovermode: 'closest',352 dragmode: 'select',353 clickmode: 'event+select'354 })355 .then(function() {356 return _click(136, 369, {}, true);357 })358 .then(function() {359 assertSelectedPoints([[1], [], []]);360 return _click(245, 136, { shiftKey: true });361 })362 .then(function() {363 assertSelectedPoints([[1], [], [3]]);364 return _click(183, 470, { shiftKey: true });365 })366 .then(function() {367 assertSelectedPoints([[1], [2], [3]]);368 })369 .then(done, done.fail);370 });371 it('is supported in pan/zoom mode', function(done) {372 plotMock14({ dragmode: 'zoom' })373 .then(function() {374 return _immediateClickPt(mock14Pts[35]);375 })376 .then(function() {377 assertSelectedPoints(35);378 return _clickPt(mock14Pts[7], { shiftKey: true });379 })380 .then(function() {381 assertSelectedPoints([7, 35]);382 return _clickPt(mock14Pts[7], { shiftKey: true });383 })384 .then(function() {385 assertSelectedPoints(35);386 _clickPt(mock14Pts[35], { shiftKey: true });387 return deselectPromise;388 })389 .then(function() {390 assertSelectionCleared();391 return _clickPt(mock14Pts[7], { shiftKey: true });392 })393 .then(function() {394 assertSelectedPoints(7);395 drag([[110, 100], [300, 300]]);396 })397 .then(delay(100))398 .then(function() {399 // persist after zoombox400 assertSelectedPoints(7);401 })402 .then(done, done.fail);403 });404 it('retains selected points when switching between pan and zoom mode', function(done) {405 plotMock14({ dragmode: 'zoom' })406 .then(function() {407 return _immediateClickPt(mock14Pts[35]);408 })409 .then(function() {410 assertSelectedPoints(35);411 return Plotly.relayout(gd, 'dragmode', 'pan');412 })413 .then(function() {414 assertSelectedPoints(35);415 return _clickPt(mock14Pts[7], { shiftKey: true });416 })417 .then(function() {418 assertSelectedPoints([7, 35]);419 return Plotly.relayout(gd, 'dragmode', 'zoom');420 })421 .then(function() {422 assertSelectedPoints([7, 35]);423 return _clickPt(mock14Pts[7], { shiftKey: true });424 })425 .then(function() {426 assertSelectedPoints(35);427 })428 .then(done, done.fail);429 });430 it('@gl is supported by scattergl in pan/zoom mode', function(done) {431 Plotly.newPlot(gd, [432 {433 x: [7, 8, 9, 10],434 y: [7, 9, 13, 21],435 type: 'scattergl',436 mode: 'markers',437 marker: { size: 20 }438 }439 ], {440 width: 400,441 height: 600,442 hovermode: 'closest',443 dragmode: 'zoom',444 clickmode: 'event+select'445 })446 .then(function() {447 return _click(230, 340, {}, true);448 })449 .then(function() {450 assertSelectedPoints(2);451 })452 .then(done, done.fail);453 });454 it('deals correctly with histogram\'s binning in the persistent selection case', function(done) {455 var mock = require('@mocks/histogram_colorscale.json');456 var firstBinPts = [0];457 var secondBinPts = [1, 2];458 var thirdBinPts = [3, 4, 5];459 mock.layout.clickmode = 'event+select';460 Plotly.newPlot(gd, mock.data, mock.layout)461 .then(function() {462 return clickFirstBinImmediately();463 })464 .then(function() {465 assertSelectedPoints(firstBinPts);466 return shiftClickSecondBin();467 })468 .then(function() {469 assertSelectedPoints([].concat(firstBinPts, secondBinPts));470 return shiftClickThirdBin();471 })472 .then(function() {473 assertSelectedPoints([].concat(firstBinPts, secondBinPts, thirdBinPts));474 return clickFirstBin();475 })476 .then(function() {477 assertSelectedPoints([].concat(firstBinPts));478 clickFirstBin();479 return deselectPromise;480 })481 .then(function() {482 assertSelectionCleared();483 })484 .then(done, done.fail);485 function clickFirstBinImmediately() { return _immediateClickPt({ x: 141, y: 358 }); }486 function clickFirstBin() { return _click(141, 358); }487 function shiftClickSecondBin() { return _click(239, 330, { shiftKey: true }); }488 function shiftClickThirdBin() { return _click(351, 347, { shiftKey: true }); }489 });490 it('ignores clicks on boxes in a box trace type', function(done) {491 var mock = Lib.extendDeep({}, require('@mocks/box_grouped_horz.json'));492 mock.layout.clickmode = 'event+select';493 mock.layout.width = 1100;494 mock.layout.height = 450;495 Plotly.newPlot(gd, mock.data, mock.layout)496 .then(function() {497 return clickPtImmediately();498 })499 .then(function() {500 assertSelectedPoints(2);501 clickPt();502 return deselectPromise;503 })504 .then(function() {505 assertSelectionCleared();506 clickBox();507 return clickedPromise;508 })509 .then(function() {510 assertSelectionCleared();511 })512 .then(done, done.fail);513 function clickPtImmediately() { return _immediateClickPt({ x: 610, y: 342 }); }514 function clickPt() { return _clickPt({ x: 610, y: 342 }); }515 function clickBox() { return _clickPt({ x: 565, y: 329 }); }516 });517 describe('is disabled when clickmode does not include \'select\'', function() {518 ['select', 'lasso']519 .forEach(function(dragmode) {520 it('and dragmode is ' + dragmode, function(done) {521 plotMock14({ clickmode: 'event', dragmode: dragmode })522 .then(function() {523 // Still, the plotly_selected event should be thrown,524 // so return promise here525 return _immediateClickPt(mock14Pts[1]);526 })527 .then(function() {528 assertSelectionCleared();529 })530 .then(done, done.fail);531 });532 });533 });534 describe('is disabled when clickmode does not include \'select\'', function() {535 ['pan', 'zoom']536 .forEach(function(dragmode) {537 it('and dragmode is ' + dragmode, function(done) {538 plotMock14({ clickmode: 'event', dragmode: dragmode })539 .then(function() {540 _immediateClickPt(mock14Pts[1]);541 return clickedPromise;542 })543 .then(function() {544 assertSelectionCleared();545 })546 .then(done, done.fail);547 });548 });549 });550 describe('is supported by', function() {551 // On loading mocks:552 // - Note, that `require` function calls are resolved at compile time553 // and thus dynamically concatenated mock paths won't work.554 // - Some mocks don't specify a width and height, so this needs555 // to be set explicitly to ensure click coordinates fit.556 // The non-gl traces: use CI annotation557 [558 testCase('histrogram', require('@mocks/histogram_colorscale.json'), 355, 301, [3, 4, 5]),559 testCase('box', require('@mocks/box_grouped_horz.json'), 610, 342, [[2], [], []],560 { width: 1100, height: 450 }),561 testCase('violin', require('@mocks/violin_grouped.json'), 166, 187, [[3], [], []],562 { width: 1100, height: 450 }),563 testCase('ohlc', require('@mocks/ohlc_first.json'), 669, 165, [9]),564 testCase('candlestick', require('@mocks/finance_style.json'), 331, 162, [[], [5]]),565 testCase('choropleth', require('@mocks/geo_choropleth-text.json'), 440, 163, [6]),566 testCase('scattergeo', require('@mocks/geo_scattergeo-locations.json'), 285, 240, [1]),567 testCase('scatterternary', require('@mocks/ternary_markers.json'), 485, 335, [7]),568 // Note that first trace (carpet) in mock doesn't support selection,569 // thus undefined is expected570 testCase('scattercarpet', require('@mocks/scattercarpet.json'), 532, 178,571 [undefined, [], [], [], [], [], [2]], { width: 1100, height: 450 }),572 // scatterpolar and scatterpolargl do not support pan (the default),573 // so set dragmode to zoom574 testCase('scatterpolar', require('@mocks/polar_scatter.json'), 130, 290,575 [[], [], [], [19], [], []], { dragmode: 'zoom' }),576 ]577 .forEach(function(testCase) {578 it('trace type ' + testCase.label, function(done) {579 _run(testCase, done);580 });581 });582 [583 testCase('scatterpolargl', require('@mocks/glpolar_scatter.json'), 130, 290,584 [[], [], [], [19], [], []], { dragmode: 'zoom' }),585 testCase('splom', require('@mocks/splom_lower.json'), 427, 400, [[], [7], []])586 ]587 .forEach(function(testCase) {588 it('@gl trace type ' + testCase.label, function(done) {589 _run(testCase, done);590 });591 });592 [593 testCase('scattermapbox', require('@mocks/mapbox_0.json'), 650, 195, [[2], []], {},594 { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }),595 testCase('choroplethmapbox', require('@mocks/mapbox_choropleth0.json'), 270, 220, [[0]], {},596 { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN })597 ]598 .forEach(function(testCase) {599 it('@gl trace type ' + testCase.label, function(done) {600 _run(testCase, done);601 });602 });603 function _run(testCase, doneFn) {604 Plotly.newPlot(gd, testCase.mock.data, testCase.mock.layout, testCase.mock.config)605 .then(function() {606 return _immediateClickPt(testCase);607 })608 .then(function() {609 assertSelectedPoints(testCase.expectedPts);610 return Plotly.relayout(gd, 'dragmode', 'lasso');611 })612 .then(function() {613 _clickPt(testCase);614 return deselectPromise;615 })616 .then(function() {617 assertSelectionCleared();618 return _clickPt(testCase);619 })620 .then(function() {621 assertSelectedPoints(testCase.expectedPts);622 })623 .then(doneFn, doneFn.fail);624 }625 });626 it('should maintain style of errorbars after double click cleared selection (bar case)', function(done) {627 Plotly.newPlot(gd, { // Note: this call should be newPlot not plot628 data: [{629 x: [0, 1, 2],630 y: [100, 200, 400],631 type: 'bar',632 marker: {633 color: 'yellow'634 },635 error_y: {636 type: 'sqrt'637 }638 }],639 layout: {640 dragmode: 'select'641 }642 })643 .then(function() {644 var x = 100;645 var y = 100;646 drag([[x, y], [x, y]]); // first empty drag647 return doubleClick(x, y); // then double click648 })649 .then(function() {650 assertSelectionCleared();651 })652 .then(function() {653 d3Select(gd).select('g.plot').each(function() {654 d3Select(this).selectAll('g.errorbar').selectAll('path').each(function() {655 expect(d3Select(this).attr('style'))656 .toBe('vector-effect: non-scaling-stroke; stroke-width: 2px; stroke: rgb(68, 68, 68); stroke-opacity: 1; opacity: 1; fill: rgb(255, 255, 0); fill-opacity: 1;', 'to be visible'657 );658 });659 });660 })661 .then(done, done.fail);662 });663 describe('triggers \'plotly_selected\' before \'plotly_click\'', function() {664 [665 testCase('cartesian', require('@mocks/14.json'), 270, 160, [7]),666 testCase('geo', require('@mocks/geo_scattergeo-locations.json'), 285, 240, [1]),667 testCase('ternary', require('@mocks/ternary_markers.json'), 485, 335, [7]),668 testCase('polar', require('@mocks/polar_scatter.json'), 130, 290,669 [[], [], [], [19], [], []], { dragmode: 'zoom' })670 ].forEach(function(testCase) {671 it('for base plot ' + testCase.label, function(done) {672 _run(testCase, done);673 });674 });675 [676 testCase('mapbox', require('@mocks/mapbox_0.json'), 650, 195, [[2], []], {},677 { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }),678 testCase('mapbox', require('@mocks/mapbox_choropleth0.json'), 270, 220, [[0], []], {},679 { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN })680 ].forEach(function(testCase) {681 it('@gl for base plot ' + testCase.label, function(done) {682 _run(testCase, done);683 });684 });685 function _run(testCase, doneFn) {686 Plotly.newPlot(gd, testCase.mock.data, testCase.mock.layout, testCase.mock.config)687 .then(function() {688 var clickHandlerCalled = false;689 var selectedHandlerCalled = false;690 gd.on('plotly_selected', function() {691 expect(clickHandlerCalled).toBe(false);692 selectedHandlerCalled = true;693 });694 gd.on('plotly_click', function() {695 clickHandlerCalled = true;696 expect(selectedHandlerCalled).toBe(true);697 doneFn();698 });699 return click(testCase.x, testCase.y);700 })701 .then(doneFn, doneFn.fail);702 }703 });704 function testCase(label, mock, x, y, expectedPts, layoutOptions, configOptions) {705 var defaultLayoutOpts = {706 layout: {707 clickmode: 'event+select',708 dragmode: 'pan',709 hovermode: 'closest'710 }711 };712 var customLayoutOptions = {713 layout: layoutOptions714 };715 var customConfigOptions = {716 config: configOptions717 };718 var mockCopy = Lib.extendDeep(719 {},720 mock,721 defaultLayoutOpts,722 customLayoutOptions,723 customConfigOptions);724 return {725 label: label,726 mock: mockCopy,727 layoutOptions: layoutOptions,728 x: x,729 y: y,730 expectedPts: expectedPts,731 configOptions: configOptions732 };733 }734});735describe('Test select box and lasso in general:', function() {736 var mock = require('@mocks/14.json');737 var selectPath = [[93, 193], [143, 193]];738 var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]];739 afterEach(destroyGraphDiv);740 function assertRange(actual, expected) {741 var PRECISION = 4;742 expect(actual.x).toBeCloseToArray(expected.x, PRECISION);743 expect(actual.y).toBeCloseToArray(expected.y, PRECISION);744 }745 function assertEventData(actual, expected, msg) {746 expect(actual.length).toBe(expected.length, msg + ' same number of pts');747 expected.forEach(function(e, i) {748 var a = actual[i];749 var m = msg + ' (pt ' + i + ')';750 expect(a.data).toBeDefined(m + ' has data ref');751 expect(a.fullData).toBeDefined(m + ' has fullData ref');752 expect(Object.keys(a).length - 2).toBe(Object.keys(e).length, m + ' has correct number of keys');753 Object.keys(e).forEach(function(k) {754 expect(a[k]).toBe(e[k], m + ' ' + k);755 });756 });757 }758 describe('select events', function() {759 var mockCopy = Lib.extendDeep({}, mock);760 mockCopy.layout.dragmode = 'select';761 mockCopy.layout.hovermode = 'closest';762 mockCopy.data[0].ids = mockCopy.data[0].x763 .map(function(v) { return 'id-' + v; });764 mockCopy.data[0].customdata = mockCopy.data[0].y765 .map(function(v) { return 'customdata-' + v; });766 addInvisible(mockCopy);767 var gd;768 beforeEach(function(done) {769 gd = createGraphDiv();770 Plotly.newPlot(gd, mockCopy.data, mockCopy.layout)771 .then(done);772 });773 it('should trigger selecting/selected/deselect events', function(done) {774 resetEvents(gd);775 drag(selectPath);776 selectedPromise.then(function() {777 expect(selectedCnt).toBe(1, 'with the correct selected count');778 assertEventData(selectedData.points, [{779 curveNumber: 0,780 pointNumber: 0,781 pointIndex: 0,782 x: 0.002,783 y: 16.25,784 id: 'id-0.002',785 customdata: 'customdata-16.25'786 }, {787 curveNumber: 0,788 pointNumber: 1,789 pointIndex: 1,790 x: 0.004,791 y: 12.5,792 id: 'id-0.004',793 customdata: 'customdata-12.5'794 }], 'with the correct selected points (2)');795 assertRange(selectedData.range, {796 x: [0.002000, 0.0046236],797 y: [0.10209191961595454, 24.512223978291406]798 }, 'with the correct selected range');799 return doubleClick(250, 200);800 })801 .then(deselectPromise)802 .then(function() {803 expect(doubleClickData).toBe(null, 'with the correct deselect data');804 })805 .then(done, done.fail);806 });807 it('should handle add/sub selection', function(done) {808 resetEvents(gd);809 drag(selectPath);810 selectedPromise.then(function() {811 expect(selectingCnt).toBe(1, 'with the correct selecting count');812 assertEventData(selectingData.points, [{813 curveNumber: 0,814 pointNumber: 0,815 pointIndex: 0,816 x: 0.002,817 y: 16.25,818 id: 'id-0.002',819 customdata: 'customdata-16.25'820 }, {821 curveNumber: 0,822 pointNumber: 1,823 pointIndex: 1,824 x: 0.004,825 y: 12.5,826 id: 'id-0.004',827 customdata: 'customdata-12.5'828 }], 'with the correct selecting points (1)');829 assertRange(selectingData.range, {830 x: [0.002000, 0.0046236],831 y: [0.10209191961595454, 24.512223978291406]832 }, 'with the correct selecting range');833 })834 .then(function() {835 // add selection836 drag([[193, 193], [213, 193]], {shiftKey: true});837 })838 .then(function() {839 expect(selectingCnt).toBe(2, 'with the correct selecting count');840 assertEventData(selectingData.points, [{841 curveNumber: 0,842 pointNumber: 0,843 pointIndex: 0,844 x: 0.002,845 y: 16.25,846 id: 'id-0.002',847 customdata: 'customdata-16.25'848 }, {849 curveNumber: 0,850 pointNumber: 1,851 pointIndex: 1,852 x: 0.004,853 y: 12.5,854 id: 'id-0.004',855 customdata: 'customdata-12.5'856 }, {857 curveNumber: 0,858 pointNumber: 4,859 pointIndex: 4,860 x: 0.013,861 y: 6.875,862 id: 'id-0.013',863 customdata: 'customdata-6.875'864 }], 'with the correct selecting points (1)');865 })866 .then(function() {867 // sub selection868 drag([[219, 143], [219, 183]], {altKey: true});869 }).then(function() {870 assertEventData(selectingData.points, [{871 curveNumber: 0,872 pointNumber: 0,873 pointIndex: 0,874 x: 0.002,875 y: 16.25,876 id: 'id-0.002',877 customdata: 'customdata-16.25'878 }, {879 curveNumber: 0,880 pointNumber: 1,881 pointIndex: 1,882 x: 0.004,883 y: 12.5,884 id: 'id-0.004',885 customdata: 'customdata-12.5'886 }], 'with the correct selecting points (1)');887 return doubleClick(250, 200);888 })889 .then(function() {890 expect(doubleClickData).toBe(null, 'with the correct deselect data');891 })892 .then(done, done.fail);893 });894 });895 describe('lasso events', function() {896 var mockCopy = Lib.extendDeep({}, mock);897 mockCopy.layout.dragmode = 'lasso';898 mockCopy.layout.hovermode = 'closest';899 addInvisible(mockCopy);900 var gd;901 beforeEach(function(done) {902 gd = createGraphDiv();903 Plotly.newPlot(gd, mockCopy.data, mockCopy.layout)904 .then(done);905 });906 it('should trigger selecting/selected/deselect events', function(done) {907 resetEvents(gd);908 drag(lassoPath);909 selectedPromise.then(function() {910 expect(selectingCnt).toBe(3, 'with the correct selecting count');911 assertEventData(selectingData.points, [{912 curveNumber: 0,913 pointNumber: 10,914 pointIndex: 10,915 x: 0.099,916 y: 2.75917 }], 'with the correct selecting points (1)');918 expect(selectedCnt).toBe(1, 'with the correct selected count');919 assertEventData(selectedData.points, [{920 curveNumber: 0,921 pointNumber: 10,922 pointIndex: 10,923 x: 0.099,924 y: 2.75,925 }], 'with the correct selected points (2)');926 expect(selectedData.lassoPoints.x).toBeCloseToArray(927 [0.084, 0.087, 0.115, 0.103], 'lasso points x coords');928 expect(selectedData.lassoPoints.y).toBeCloseToArray(929 [4.648, 1.342, 1.247, 4.821], 'lasso points y coords');930 return doubleClick(250, 200);931 })932 .then(deselectPromise)933 .then(function() {934 expect(doubleClickData).toBe(null, 'with the correct deselect data');935 })936 .then(done, done.fail);937 });938 it('should set selected points in graph data', function(done) {939 resetEvents(gd);940 drag(lassoPath);941 selectedPromise.then(function() {942 expect(selectingCnt).toBe(3, 'with the correct selecting count');943 expect(gd.data[0].selectedpoints).toEqual([10]);944 return doubleClick(250, 200);945 })946 .then(deselectPromise)947 .then(function() {948 expect(gd.data[0].selectedpoints).toBeUndefined();949 })950 .then(done, done.fail);951 });952 it('should set selected points in full data', function(done) {953 resetEvents(gd);954 drag(lassoPath);955 selectedPromise.then(function() {956 expect(selectingCnt).toBe(3, 'with the correct selecting count');957 expect(gd._fullData[0].selectedpoints).toEqual([10]);958 return doubleClick(250, 200);959 })960 .then(deselectPromise)961 .then(function() {962 expect(gd._fullData[0].selectedpoints).toBeUndefined();963 })964 .then(done, done.fail);965 });966 it('should trigger selecting/selected/deselect events for touches', function(done) {967 resetEvents(gd);968 drag(lassoPath, {type: 'touch'});969 selectedPromise.then(function() {970 expect(selectingCnt).toBe(3, 'with the correct selecting count');971 assertEventData(selectingData.points, [{972 curveNumber: 0,973 pointNumber: 10,974 pointIndex: 10,975 x: 0.099,976 y: 2.75977 }], 'with the correct selecting points (1)');978 expect(selectedCnt).toBe(1, 'with the correct selected count');979 assertEventData(selectedData.points, [{980 curveNumber: 0,981 pointNumber: 10,982 pointIndex: 10,983 x: 0.099,984 y: 2.75,985 }], 'with the correct selected points (2)');986 return doubleClick(250, 200);987 })988 .then(deselectPromise)989 .then(function() {990 expect(doubleClickData).toBe(null, 'with the correct deselect data');991 })992 .then(done, done.fail);993 });994 });995 it('should skip over non-visible traces', function(done) {996 // note: this tests a mock with one or several invisible traces997 // the invisible traces in the other tests test for multiple998 // traces, with some visible and some not.999 var mockCopy = Lib.extendDeep({}, mock);1000 mockCopy.layout.dragmode = 'select';1001 var gd = createGraphDiv();1002 function resetAndSelect() {1003 resetEvents(gd);1004 drag(selectPath);1005 return selectedPromise;1006 }1007 function resetAndLasso() {1008 resetEvents(gd);1009 drag(lassoPath);1010 return selectedPromise;1011 }1012 function checkPointCount(cnt, msg) {1013 expect((selectedData.points || []).length).toBe(cnt, msg);1014 }1015 Plotly.newPlot(gd, mockCopy.data, mockCopy.layout)1016 .then(resetAndSelect)1017 .then(function() {1018 checkPointCount(2, '(case 0)');1019 return Plotly.restyle(gd, 'visible', 'legendonly');1020 })1021 .then(resetAndSelect)1022 .then(function() {1023 checkPointCount(0, '(legendonly case)');1024 return Plotly.restyle(gd, 'visible', true);1025 })1026 .then(resetAndSelect)1027 .then(function() {1028 checkPointCount(2, '(back to case 0)');1029 return Plotly.relayout(gd, 'dragmode', 'lasso');1030 })1031 .then(resetAndLasso)1032 .then(function() {1033 checkPointCount(1, '(case 0 lasso)');1034 return Plotly.restyle(gd, 'visible', 'legendonly');1035 })1036 .then(resetAndSelect)1037 .then(function() {1038 checkPointCount(0, '(lasso legendonly case)');1039 return Plotly.restyle(gd, 'visible', true);1040 })1041 .then(resetAndLasso)1042 .then(function() {1043 checkPointCount(1, '(back to lasso case 0)');1044 mockCopy = Lib.extendDeep({}, mock);1045 mockCopy.layout.dragmode = 'select';1046 mockCopy.data[0].visible = false;1047 addInvisible(mockCopy);1048 return Plotly.newPlot(gd, mockCopy);1049 })1050 .then(resetAndSelect)1051 .then(function() {1052 checkPointCount(0, '(multiple invisible traces select)');1053 return Plotly.relayout(gd, 'dragmode', 'lasso');1054 })1055 .then(resetAndLasso)1056 .then(function() {1057 checkPointCount(0, '(multiple invisible traces lasso)');1058 })1059 .then(done, done.fail);1060 });1061 it('should skip over BADNUM items', function(done) {1062 var data = [{1063 mode: 'markers',1064 x: [null, undefined, NaN, 0, 'NA'],1065 y: [NaN, null, undefined, 0, 'NA']1066 }];1067 var layout = {1068 dragmode: 'select',1069 width: 400,1070 heigth: 400,1071 };1072 var gd = createGraphDiv();1073 Plotly.newPlot(gd, data, layout).then(function() {1074 resetEvents(gd);1075 drag([[100, 100], [300, 300]]);1076 return selectedPromise;1077 })1078 .then(function() {1079 expect(selectedData.points.length).toBe(1);1080 expect(selectedData.points[0].x).toBe(0);1081 expect(selectedData.points[0].y).toBe(0);1082 return Plotly.relayout(gd, 'dragmode', 'lasso');1083 })1084 .then(function() {1085 resetEvents(gd);1086 drag([[100, 100], [100, 300], [300, 300], [300, 100], [100, 100]]);1087 return selectedPromise;1088 })1089 .then(function() {1090 expect(selectedData.points.length).toBe(1);1091 expect(selectedData.points[0].x).toBe(0);1092 expect(selectedData.points[0].y).toBe(0);1093 })1094 .then(done, done.fail);1095 });1096 it('scroll zoom should clear selection regions', function(done) {1097 var gd = createGraphDiv();1098 var mockCopy = Lib.extendDeep({}, mock);1099 mockCopy.layout.dragmode = 'select';1100 mockCopy.config = {scrollZoom: true};1101 function _drag() {1102 resetEvents(gd);1103 drag(selectPath);1104 return selectedPromise;1105 }1106 function _scroll() {1107 mouseEvent('mousemove', selectPath[0][0], selectPath[0][1]);1108 mouseEvent('scroll', selectPath[0][0], selectPath[0][1], {deltaX: 0, deltaY: -20});1109 }1110 Plotly.newPlot(gd, mockCopy)1111 .then(_drag)1112 .then(_scroll)1113 .then(function() {1114 assertSelectionNodes(0, 0);1115 })1116 .then(_drag)1117 .then(_scroll)1118 .then(function() {1119 // make sure it works the 2nd time aroung1120 assertSelectionNodes(0, 0);1121 })1122 .then(done, done.fail);1123 });1124 describe('should return correct range data on dragmode *select*', function() {1125 var specs = [{1126 axType: 'linear',1127 rng: [-0.6208, 0.8375]1128 }, {1129 axType: 'log',1130 rng: [0.2394, 6.8785]1131 }, {1132 axType: 'date',1133 rng: ['2000-01-20 19:48', '2000-04-06 01:48']1134 }, {1135 axType: 'category',1136 rng: [-0.6208, 0.8375]1137 }, {1138 axType: 'multicategory',1139 rng: [-0.6208, 0.8375]1140 }];1141 specs.forEach(function(s) {1142 it('- on ' + s.axType + ' axes', function(done) {1143 var gd = createGraphDiv();1144 Plotly.newPlot(gd, [], {1145 xaxis: {type: s.axType},1146 dragmode: 'select',1147 width: 400,1148 height: 4001149 })1150 .then(function() {1151 resetEvents(gd);1152 drag(selectPath);1153 return selectedPromise;1154 })1155 .then(function() {1156 expect(selectedData.range.x).toBeCloseToArray(s.rng, 2);1157 })1158 .then(done, done.fail);1159 });1160 });1161 });1162 describe('should return correct range data on dragmode *lasso*', function() {1163 var specs = [{1164 axType: 'linear',1165 pts: [5.883, 5.941, 6, 6]1166 }, {1167 axType: 'log',1168 pts: [764422.2742, 874312.4580, 1000000, 1000000]1169 }, {1170 axType: 'date',1171 pts: ['2000-12-25 21:36', '2000-12-28 22:48', '2001-01-01', '2001-01-01']1172 }, {1173 axType: 'category',1174 pts: [5.8833, 5.9416, 6, 6]1175 }, {1176 axType: 'multicategory',1177 pts: [5.8833, 5.9416, 6, 6]1178 }];1179 specs.forEach(function(s) {1180 it('- on ' + s.axType + ' axes', function(done) {1181 var gd = createGraphDiv();1182 Plotly.newPlot(gd, [], {1183 xaxis: {type: s.axType},1184 dragmode: 'lasso',1185 width: 400,1186 height: 4001187 })1188 .then(function() {1189 resetEvents(gd);1190 drag(lassoPath);1191 return selectedPromise;1192 })1193 .then(function() {1194 expect(selectedData.lassoPoints.x).toBeCloseToArray(s.pts, 2);1195 })1196 .then(done, done.fail);1197 });1198 });1199 });1200 it('should have their selection outlines cleared during *axrange* relayout calls', function(done) {1201 var gd = createGraphDiv();1202 var fig = Lib.extendDeep({}, mock);1203 fig.layout.dragmode = 'select';1204 function _drag() {1205 resetEvents(gd);1206 drag(selectPath);1207 return selectedPromise;1208 }1209 Plotly.newPlot(gd, fig)1210 .then(_drag)1211 .then(function() { assertSelectionNodes(0, 2, 'after drag 1'); })1212 .then(function() { return Plotly.relayout(gd, 'xaxis.range', [-5, 5]); })1213 .then(function() { assertSelectionNodes(0, 0, 'after axrange relayout'); })1214 .then(_drag)1215 .then(function() { assertSelectionNodes(0, 2, 'after drag 2'); })1216 .then(done, done.fail);1217 });1218 it('should select the right data with the corresponding select direction', function(done) {1219 var gd = createGraphDiv();1220 // drag around just the center point, but if we have a selectdirection we may1221 // get either the ones to the left and right or above and below1222 var selectPath = [[175, 175], [225, 225]];1223 function selectDrag() {1224 resetEvents(gd);1225 drag(selectPath);1226 return selectedPromise;1227 }1228 function assertSelectedPointNumbers(pointNumbers) {1229 var pts = selectedData.points;1230 expect(pts.length).toBe(pointNumbers.length);1231 pointNumbers.forEach(function(pointNumber, i) {1232 expect(pts[i].pointNumber).toBe(pointNumber);1233 });1234 }1235 Plotly.newPlot(gd, [{1236 x: [1, 1, 1, 2, 2, 2, 3, 3, 3],1237 y: [1, 2, 3, 1, 2, 3, 1, 2, 3],1238 mode: 'markers'1239 }], {1240 width: 400,1241 height: 400,1242 dragmode: 'select',1243 margin: {l: 100, r: 100, t: 100, b: 100},1244 xaxis: {range: [0, 4]},1245 yaxis: {range: [0, 4]}1246 })1247 .then(selectDrag)1248 .then(function() {1249 expect(gd._fullLayout.selectdirection).toBe('any');1250 assertSelectedPointNumbers([4]);1251 return Plotly.relayout(gd, {selectdirection: 'h'});1252 })1253 .then(selectDrag)1254 .then(function() {1255 assertSelectedPointNumbers([3, 4, 5]);1256 return Plotly.relayout(gd, {selectdirection: 'v'});1257 })1258 .then(selectDrag)1259 .then(function() {1260 assertSelectedPointNumbers([1, 4, 7]);1261 return Plotly.relayout(gd, {selectdirection: 'd'});1262 })1263 .then(selectDrag)1264 .then(function() {1265 assertSelectedPointNumbers([4]);1266 })1267 .then(done, done.fail);1268 });1269 it('@flaky should cleanly clear and restart selections on double click when add/subtract mode on', function(done) {1270 var gd = createGraphDiv();1271 var fig = Lib.extendDeep({}, require('@mocks/0.json'));1272 fig.layout.dragmode = 'select';1273 Plotly.newPlot(gd, fig)1274 .then(function() {1275 return drag([[350, 100], [400, 400]]);1276 })1277 .then(function() {1278 _assertSelectedPoints([49, 50, 51, 52, 53, 54, 55, 56, 57]);1279 // Note: although Shift has no behavioral effect on clearing a selection1280 // with a double click, users might hold the Shift key by accident.1281 // This test ensures selection is cleared as expected although1282 // the Shift key is held and no selection state is retained in any way.1283 return doubleClick(500, 200, { shiftKey: true });1284 })1285 .then(function() {1286 _assertSelectedPoints(null);1287 return drag([[450, 100], [500, 400]], { shiftKey: true });1288 })1289 .then(function() {1290 _assertSelectedPoints([67, 68, 69, 70, 71, 72, 73, 74]);1291 })1292 .then(done, done.fail);1293 function _assertSelectedPoints(selPts) {1294 if(selPts) {1295 expect(gd.data[0].selectedpoints).toEqual(selPts);1296 } else {1297 expect('selectedpoints' in gd.data[0]).toBe(false);1298 }1299 }1300 });1301 it('should clear selected points on double click only on pan/lasso modes', function(done) {1302 var gd = createGraphDiv();1303 var fig = Lib.extendDeep({}, require('@mocks/0.json'));1304 fig.data = [fig.data[0]];1305 fig.layout.xaxis.autorange = false;1306 fig.layout.xaxis.range = [2, 8];1307 fig.layout.yaxis.autorange = false;1308 fig.layout.yaxis.range = [0, 3];1309 fig.layout.hovermode = 'closest';1310 function _assert(msg, exp) {1311 expect(gd.layout.xaxis.range)1312 .toBeCloseToArray(exp.xrng, 2, 'xaxis range - ' + msg);1313 expect(gd.layout.yaxis.range)1314 .toBeCloseToArray(exp.yrng, 2, 'yaxis range - ' + msg);1315 if(exp.selpts === null) {1316 expect('selectedpoints' in gd.data[0])1317 .toBe(false, 'cleared selectedpoints - ' + msg);1318 } else {1319 expect(gd.data[0].selectedpoints)1320 .toBeCloseToArray(exp.selpts, 2, 'selectedpoints - ' + msg);1321 }1322 }1323 Plotly.newPlot(gd, fig).then(function() {1324 _assert('base', {1325 xrng: [2, 8],1326 yrng: [0, 3],1327 selpts: null1328 });1329 return Plotly.relayout(gd, 'xaxis.range', [0, 10]);1330 })1331 .then(function() {1332 _assert('after xrng relayout', {1333 xrng: [0, 10],1334 yrng: [0, 3],1335 selpts: null1336 });1337 return doubleClick(200, 200);1338 })1339 .then(function() {1340 _assert('after double-click under dragmode zoom', {1341 xrng: [2, 8],1342 yrng: [0, 3],1343 selpts: null1344 });1345 return Plotly.relayout(gd, 'dragmode', 'select');1346 })1347 .then(function() {1348 _assert('after relayout to select', {1349 xrng: [2, 8],1350 yrng: [0, 3],1351 selpts: null1352 });1353 return drag([[100, 100], [400, 400]]);1354 })1355 .then(function() {1356 _assert('after selection', {1357 xrng: [2, 8],1358 yrng: [0, 3],1359 selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1360 });1361 return doubleClick(200, 200);1362 })1363 .then(function() {1364 _assert('after double-click under dragmode select', {1365 xrng: [2, 8],1366 yrng: [0, 3],1367 selpts: null1368 });1369 return drag([[100, 100], [400, 400]]);1370 })1371 .then(function() {1372 _assert('after selection 2', {1373 xrng: [2, 8],1374 yrng: [0, 3],1375 selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1376 });1377 return Plotly.relayout(gd, 'dragmode', 'pan');1378 })1379 .then(function() {1380 _assert('after relayout to pan', {1381 xrng: [2, 8],1382 yrng: [0, 3],1383 selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1384 });1385 return Plotly.relayout(gd, 'yaxis.range', [0, 20]);1386 })1387 .then(function() {1388 _assert('after yrng relayout', {1389 xrng: [2, 8],1390 yrng: [0, 20],1391 selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1392 });1393 return doubleClick(200, 200);1394 })1395 .then(function() {1396 _assert('after double-click under dragmode pan', {1397 xrng: [2, 8],1398 yrng: [0, 3],1399 // N.B. does not clear selection!1400 selpts: [40, 41, 42, 43, 44, 45, 46, 47, 48]1401 });1402 })1403 .then(done, done.fail);1404 });1405 it('should remember selection polygons from previous select/lasso mode', function(done) {1406 var gd = createGraphDiv();1407 var path1 = [[150, 150], [170, 170]];1408 var path2 = [[193, 193], [213, 193]];1409 var fig = Lib.extendDeep({}, mock);1410 fig.layout.margin = {l: 0, t: 0, r: 0, b: 0};1411 fig.layout.width = 500;1412 fig.layout.height = 500;1413 fig.layout.dragmode = 'select';1414 fig.config = {scrollZoom: true};1415 // d attr to array of segment [x,y]1416 function outline2coords(outline) {1417 if(!outline.size()) return [[]];1418 return outline.attr('d')1419 .replace(/Z/g, '')1420 .split('M')1421 .filter(Boolean)1422 .map(function(s) {1423 return s.split('L')1424 .map(function(s) { return s.split(',').map(Number); });1425 })1426 .reduce(function(a, b) { return a.concat(b); });1427 }1428 function _assert(msg, exp) {1429 var outline = d3Select(gd).select('.zoomlayer').select('.select-outline-1');1430 if(exp.outline) {1431 expect(outline2coords(outline)).toBeCloseTo2DArray(exp.outline, 2, msg);1432 } else {1433 assertSelectionNodes(0, 0, msg);1434 }1435 }1436 function _drag(path, opts) {1437 return function() {1438 resetEvents(gd);1439 drag(path, opts);1440 return selectedPromise;1441 };1442 }1443 Plotly.newPlot(gd, fig)1444 .then(function() { _assert('base', {outline: false}); })1445 .then(_drag(path1))1446 .then(function() {1447 _assert('select path1', {1448 outline: [[150, 150], [150, 170], [170, 170], [170, 150]]1449 });1450 })1451 .then(_drag(path2))1452 .then(function() {1453 _assert('select path2', {1454 outline: [[193, 0], [193, 500], [213, 500], [213, 0]]1455 });1456 })1457 .then(_drag(path1))1458 .then(_drag(path2, {shiftKey: true}))1459 .then(function() {1460 _assert('select path1+path2', {1461 outline: [1462 [170, 170], [170, 150], [150, 150], [150, 170],1463 [213, 500], [213, 0], [193, 0], [193, 500]1464 ]1465 });1466 })1467 .then(function() {1468 return Plotly.relayout(gd, 'dragmode', 'lasso');1469 })1470 .then(function() {1471 // N.B. all relayout calls clear the selection outline at the moment,1472 // perhaps we could make an exception for select <-> lasso ?1473 _assert('after relayout -> lasso', {outline: false});1474 })1475 .then(_drag(lassoPath, {shiftKey: true}))1476 .then(function() {1477 // merged with previous 'select' polygon1478 _assert('after shift lasso', {1479 outline: [1480 [170, 170], [170, 150], [150, 150], [150, 170],1481 [213, 500], [213, 0], [193, 0], [193, 500],1482 [335, 243], [328, 169], [316, 171], [318, 239]1483 ]1484 });1485 })1486 .then(_drag(lassoPath))1487 .then(function() {1488 _assert('after lasso (no-shift)', {1489 outline: [[316, 171], [318, 239], [335, 243], [328, 169]]1490 });1491 })1492 .then(function() {1493 return Plotly.relayout(gd, 'dragmode', 'pan');1494 })1495 .then(function() {1496 _assert('after relayout -> pan', {outline: false});1497 drag(path2);1498 _assert('after pan', {outline: false});1499 return Plotly.relayout(gd, 'dragmode', 'select');1500 })1501 .then(function() {1502 _assert('after relayout back to select', {outline: false});1503 })1504 .then(_drag(path1, {shiftKey: true}))1505 .then(function() {1506 // this used to merged 'lasso' polygons before (see #2669)1507 _assert('shift select path1 after pan', {1508 outline: [[150, 150], [150, 170], [170, 170], [170, 150]]1509 });1510 })1511 .then(_drag(path2, {shiftKey: true}))1512 .then(function() {1513 _assert('shift select path1+path2 after pan', {1514 outline: [1515 [170, 170], [170, 150], [150, 150], [150, 170],1516 [213, 500], [213, 0], [193, 0], [193, 500]1517 ]1518 });1519 })1520 .then(function() {1521 mouseEvent('mousemove', 200, 200);1522 mouseEvent('scroll', 200, 200, {deltaX: 0, deltaY: -20});1523 })1524 .then(_drag(path1, {shiftKey: true}))1525 .then(function() {1526 _assert('shift select path1 after scroll', {1527 outline: [[150, 150], [150, 170], [170, 170], [170, 150]]1528 });1529 })1530 .then(done, done.fail);1531 });1532});1533describe('Test select box and lasso per trace:', function() {1534 var gd;1535 beforeEach(function() {1536 gd = createGraphDiv();1537 spyOn(Lib, 'error');1538 });1539 afterEach(destroyGraphDiv);1540 function makeAssertPoints(keys) {1541 var callNumber = 0;1542 return function(expected) {1543 var msg = '(call #' + callNumber + ') ';1544 var pts = (selectedData || {}).points || [];1545 expect(pts.length).toBe(expected.length, msg + 'selected points length');1546 pts.forEach(function(p, i) {1547 var e = expected[i] || [];1548 keys.forEach(function(k, j) {1549 var msgFull = msg + 'selected pt ' + i + ' - ' + k + ' val';1550 if(typeof p[k] === 'number' && typeof e[j] === 'number') {1551 expect(p[k]).toBeCloseTo(e[j], 1, msgFull);1552 } else if(Array.isArray(p[k]) && Array.isArray(e[j])) {1553 expect(p[k]).toBeCloseToArray(e[j], 1, msgFull);1554 } else {1555 expect(p[k]).toBe(e[j], msgFull);1556 }1557 });1558 });1559 callNumber++;1560 };1561 }1562 function makeAssertSelectedPoints() {1563 var callNumber = 0;1564 return function(expected) {1565 var msg = '(call #' + callNumber + ') ';1566 gd.data.forEach(function(trace, i) {1567 var msgFull = msg + 'selectedpoints array for trace ' + i;1568 var actual = trace.selectedpoints;1569 if(expected[i]) {1570 expect(actual).toBeCloseToArray(expected[i], 1, msgFull);1571 } else {1572 expect(actual).toBe(undefined, 1, msgFull);1573 }1574 });1575 callNumber++;1576 };1577 }1578 function makeAssertRanges(subplot, tol) {1579 tol = tol || 1;1580 var callNumber = 0;1581 return function(expected) {1582 var msg = '(call #' + callNumber + ') select box range ';1583 var ranges = selectedData.range || {};1584 if(subplot) {1585 expect(ranges[subplot] || [])1586 .toBeCloseTo2DArray(expected, tol, msg + 'for ' + subplot);1587 } else {1588 expect(ranges.x || [])1589 .toBeCloseToArray(expected[0], tol, msg + 'x coords');1590 expect(ranges.y || [])1591 .toBeCloseToArray(expected[1], tol, msg + 'y coords');1592 }1593 callNumber++;1594 };1595 }1596 function makeAssertLassoPoints(subplot, tol) {1597 tol = tol || 1;1598 var callNumber = 0;1599 return function(expected) {1600 var msg = '(call #' + callNumber + ') lasso points ';1601 var lassoPoints = selectedData.lassoPoints || {};1602 if(subplot) {1603 expect(lassoPoints[subplot] || [])1604 .toBeCloseTo2DArray(expected, tol, msg + 'for ' + subplot);1605 } else {1606 expect(lassoPoints.x || [])1607 .toBeCloseToArray(expected[0], tol, msg + 'x coords');1608 expect(lassoPoints.y || [])1609 .toBeCloseToArray(expected[1], tol, msg + 'y coords');1610 }1611 callNumber++;1612 };1613 }1614 function transformPlot(gd, transformString) {1615 gd.style.webkitTransform = transformString;1616 gd.style.MozTransform = transformString;1617 gd.style.msTransform = transformString;1618 gd.style.OTransform = transformString;1619 gd.style.transform = transformString;1620 }1621 var cssTransform = 'translate(-25%, -25%) scale(0.5)';1622 function _run(hasCssTransform, dragPath, afterDragFn, dblClickPos, eventCounts, msg) {1623 afterDragFn = afterDragFn || function() {};1624 dblClickPos = dblClickPos || [250, 200];1625 var scale = 1;1626 if(hasCssTransform) {1627 scale = 0.5;1628 }1629 dblClickPos[0] *= scale;1630 dblClickPos[1] *= scale;1631 for(var i = 0; i < dragPath.length; i++) {1632 for(var j = 0; j < dragPath[i].length; j++) {1633 dragPath[i][j] *= scale;1634 }1635 }1636 resetEvents(gd);1637 assertSelectionNodes(0, 0);1638 drag(dragPath);1639 return (eventCounts[0] ? selectedPromise : Promise.resolve())1640 .then(afterDragFn)1641 .then(function() {1642 // TODO: in v3 when we remove the `plotly_selecting->undefined` the Math.max(...)1643 // in the middle here will turn into just eventCounts[1].1644 // It's just here because one of the selected events is generated during1645 // doubleclick so hasn't happened yet when we're testing this.1646 assertEventCounts(eventCounts[0], Math.max(0, eventCounts[1] - 1), 0, msg + ' (before dblclick)');1647 return doubleClick(dblClickPos[0], dblClickPos[1]);1648 })1649 .then(eventCounts[2] ? deselectPromise : Promise.resolve())1650 .then(function() {1651 assertEventCounts(eventCounts[0], eventCounts[1], eventCounts[2], msg + ' (after dblclick)');1652 expect(Lib.error).not.toHaveBeenCalled();1653 });1654 }1655 [false, true].forEach(function(hasCssTransform) {1656 it('should work on scatterternary traces, hasCssTransform: ' + hasCssTransform, function(done) {1657 var assertPoints = makeAssertPoints(['a', 'b', 'c']);1658 var assertSelectedPoints = makeAssertSelectedPoints();1659 var fig = Lib.extendDeep({}, require('@mocks/ternary_simple'));1660 fig.layout.width = 800;1661 fig.layout.dragmode = 'select';1662 addInvisible(fig);1663 Plotly.newPlot(gd, fig)1664 .then(function() {1665 if(hasCssTransform) transformPlot(gd, cssTransform);1666 return _run(hasCssTransform,1667 [[400, 200], [445, 235]],1668 function() {1669 assertPoints([[0.5, 0.25, 0.25]]);1670 assertSelectedPoints({0: [0]});1671 },1672 [380, 180],1673 BOXEVENTS, 'scatterternary select'1674 );1675 })1676 .then(function() {1677 return Plotly.relayout(gd, 'dragmode', 'lasso');1678 })1679 .then(function() {1680 return _run(hasCssTransform,1681 [[400, 200], [445, 200], [445, 235], [400, 235], [400, 200]],1682 function() {1683 assertPoints([[0.5, 0.25, 0.25]]);1684 assertSelectedPoints({0: [0]});1685 },1686 [380, 180],1687 LASSOEVENTS, 'scatterternary lasso'1688 );1689 })1690 .then(function() {1691 // should work after a relayout too1692 return Plotly.relayout(gd, 'width', 400);1693 })1694 .then(function() {1695 return _run(hasCssTransform,1696 [[200, 200], [230, 200], [230, 230], [200, 230], [200, 200]],1697 function() {1698 assertPoints([[0.5, 0.25, 0.25]]);1699 assertSelectedPoints({0: [0]});1700 },1701 [180, 180],1702 LASSOEVENTS, 'scatterternary lasso after relayout'1703 );1704 })1705 .then(done, done.fail);1706 });1707 });1708 [false, true].forEach(function(hasCssTransform) {1709 it('should work on scattercarpet traces, hasCssTransform: ' + hasCssTransform, function(done) {1710 var assertPoints = makeAssertPoints(['a', 'b']);1711 var assertSelectedPoints = makeAssertSelectedPoints();1712 var fig = Lib.extendDeep({}, require('@mocks/scattercarpet'));1713 delete fig.data[6].selectedpoints;1714 fig.layout.dragmode = 'select';1715 addInvisible(fig);1716 Plotly.newPlot(gd, fig)1717 .then(function() {1718 if(hasCssTransform) transformPlot(gd, cssTransform);1719 return _run(hasCssTransform,1720 [[300, 200], [400, 250]],1721 function() {1722 assertPoints([[0.2, 1.5]]);1723 assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []});1724 },1725 null, BOXEVENTS, 'scattercarpet select'1726 );1727 })1728 .then(function() {1729 return Plotly.relayout(gd, 'dragmode', 'lasso');1730 })1731 .then(function() {1732 return _run(hasCssTransform,1733 [[300, 200], [400, 200], [400, 250], [300, 250], [300, 200]],1734 function() {1735 assertPoints([[0.2, 1.5]]);1736 assertSelectedPoints({1: [], 2: [], 3: [], 4: [], 5: [1], 6: []});1737 },1738 null, LASSOEVENTS, 'scattercarpet lasso'1739 );1740 })1741 .then(done, done.fail);1742 });1743 });1744 [false, true].forEach(function(hasCssTransform) {1745 it('@gl should work on scattermapbox traces, hasCssTransform: ' + hasCssTransform, function(done) {1746 var assertPoints = makeAssertPoints(['lon', 'lat']);1747 var assertRanges = makeAssertRanges('mapbox');1748 var assertLassoPoints = makeAssertLassoPoints('mapbox');1749 var assertSelectedPoints = makeAssertSelectedPoints();1750 var fig = Lib.extendDeep({}, require('@mocks/mapbox_bubbles-text'));1751 fig.data[0].lon.push(null);1752 fig.data[0].lat.push(null);1753 fig.layout.dragmode = 'select';1754 fig.config = {1755 mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN1756 };1757 addInvisible(fig);1758 Plotly.newPlot(gd, fig)1759 .then(function() {1760 if(hasCssTransform) transformPlot(gd, cssTransform);1761 return _run(hasCssTransform,1762 [[370, 120], [500, 200]],1763 function() {1764 assertPoints([[30, 30]]);1765 assertRanges([[21.99, 34.55], [38.14, 25.98]]);1766 assertSelectedPoints({0: [2]});1767 },1768 null, BOXEVENTS, 'scattermapbox select'1769 );1770 })1771 .then(function() {1772 return Plotly.relayout(gd, 'dragmode', 'lasso');1773 })1774 .then(function() {1775 return _run(hasCssTransform,1776 [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1777 function() {1778 assertPoints([[20, 20]]);1779 assertSelectedPoints({0: [1]});1780 assertLassoPoints([1781 [13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97]1782 ]);1783 },1784 null, LASSOEVENTS, 'scattermapbox lasso'1785 );1786 })1787 .then(function() {1788 // make selection handlers don't get called in 'pan' dragmode1789 return Plotly.relayout(gd, 'dragmode', 'pan');1790 })1791 .then(function() {1792 return _run(hasCssTransform,1793 [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattermapbox pan'1794 );1795 })1796 .then(done, done.fail);1797 }, LONG_TIMEOUT_INTERVAL);1798 });1799 [false, true].forEach(function(hasCssTransform) {1800 it('@gl should work on choroplethmapbox traces, hasCssTransform: ' + hasCssTransform, function(done) {1801 var assertPoints = makeAssertPoints(['location', 'z']);1802 var assertRanges = makeAssertRanges('mapbox');1803 var assertLassoPoints = makeAssertLassoPoints('mapbox');1804 var assertSelectedPoints = makeAssertSelectedPoints();1805 var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth0.json'));1806 fig.data[0].locations.push(null);1807 fig.layout.dragmode = 'select';1808 fig.config = {1809 mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN1810 };1811 addInvisible(fig);1812 Plotly.newPlot(gd, fig)1813 .then(function() {1814 if(hasCssTransform) transformPlot(gd, cssTransform);1815 return _run(hasCssTransform,1816 [[150, 150], [300, 300]],1817 function() {1818 assertPoints([['NY', 10]]);1819 assertRanges([[-83.38, 46.13], [-74.06, 39.29]]);1820 assertSelectedPoints({0: [0]});1821 },1822 null, BOXEVENTS, 'choroplethmapbox select'1823 );1824 })1825 .then(function() {1826 return Plotly.relayout(gd, 'dragmode', 'lasso');1827 })1828 .then(function() {1829 return _run(hasCssTransform,1830 [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1831 function() {1832 assertPoints([['MA', 20]]);1833 assertSelectedPoints({0: [1]});1834 assertLassoPoints([1835 [-74.06, 43.936], [-74.06, 39.293], [-67.84, 39.293],1836 [-67.84, 43.936], [-74.06, 43.936]1837 ]);1838 },1839 null, LASSOEVENTS, 'choroplethmapbox lasso'1840 );1841 })1842 .then(done, done.fail);1843 }, LONG_TIMEOUT_INTERVAL);1844 });1845 [false, true].forEach(function(hasCssTransform) {1846 it('should work on scattergeo traces, hasCssTransform: ' + hasCssTransform, function(done) {1847 var assertPoints = makeAssertPoints(['lon', 'lat']);1848 var assertSelectedPoints = makeAssertSelectedPoints();1849 var assertRanges = makeAssertRanges('geo');1850 var assertLassoPoints = makeAssertLassoPoints('geo');1851 function assertNodeOpacity(exp) {1852 var traces = d3Select(gd).selectAll('.scatterlayer > .trace');1853 expect(traces.size()).toBe(Object.keys(exp).length, 'correct # of trace <g>');1854 traces.each(function(_, i) {1855 d3Select(this).selectAll('path.point').each(function(_, j) {1856 expect(Number(this.style.opacity))1857 .toBe(exp[i][j], 'node opacity - trace ' + i + ' pt ' + j);1858 });1859 });1860 }1861 var fig = {1862 data: [{1863 type: 'scattergeo',1864 lon: [10, 20, 30, null],1865 lat: [10, 20, 30, null]1866 }, {1867 type: 'scattergeo',1868 lon: [-10, -20, -30],1869 lat: [10, 20, 30]1870 }],1871 layout: {1872 showlegend: false,1873 dragmode: 'select',1874 width: 800,1875 height: 6001876 }1877 };1878 addInvisible(fig);1879 Plotly.newPlot(gd, fig)1880 .then(function() {1881 if(hasCssTransform) transformPlot(gd, cssTransform);1882 return _run(hasCssTransform,1883 [[350, 200], [450, 400]],1884 function() {1885 assertPoints([[10, 10], [20, 20], [-10, 10], [-20, 20]]);1886 assertSelectedPoints({0: [0, 1], 1: [0, 1]});1887 assertNodeOpacity({0: [1, 1, 0.2], 1: [1, 1, 0.2]});1888 assertRanges([[-28.13, 61.88], [28.13, -50.64]]);1889 },1890 null, BOXEVENTS, 'scattergeo select'1891 );1892 })1893 .then(function() {1894 return Plotly.relayout(gd, 'dragmode', 'lasso');1895 })1896 .then(function() {1897 return _run(hasCssTransform,1898 [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1899 function() {1900 assertPoints([[-10, 10], [-20, 20], [-30, 30]]);1901 assertSelectedPoints({0: [], 1: [0, 1, 2]});1902 assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]});1903 assertLassoPoints([1904 [-56.25, 61.88], [-56.24, 5.63], [0, 5.63], [0, 61.88], [-56.25, 61.88]1905 ]);1906 },1907 null, LASSOEVENTS, 'scattergeo lasso'1908 );1909 })1910 .then(function() {1911 // some projection types can't handle BADNUM during c2p,1912 // make they are skipped here1913 return Plotly.relayout(gd, 'geo.projection.type', 'robinson');1914 })1915 .then(function() {1916 return _run(hasCssTransform,1917 [[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]],1918 function() {1919 assertPoints([[-10, 10], [-20, 20], [-30, 30]]);1920 assertSelectedPoints({0: [], 1: [0, 1, 2]});1921 assertNodeOpacity({0: [0.2, 0.2, 0.2], 1: [1, 1, 1]});1922 assertLassoPoints([1923 [-67.40, 55.07], [-56.33, 4.968], [0, 4.968], [0, 55.07], [-67.40, 55.07]1924 ]);1925 },1926 null, LASSOEVENTS, 'scattergeo lasso (on robinson projection)'1927 );1928 })1929 .then(function() {1930 // make sure selection handlers don't get called in 'pan' dragmode1931 return Plotly.relayout(gd, 'dragmode', 'pan');1932 })1933 .then(function() {1934 return _run(hasCssTransform,1935 [[370, 120], [500, 200]], null, null, NOEVENTS, 'scattergeo pan'1936 );1937 })1938 .then(done, done.fail);1939 }, LONG_TIMEOUT_INTERVAL);1940 });1941 [false, true].forEach(function(hasCssTransform) {1942 it('should work on scatterpolar traces, hasCssTransform: ' + hasCssTransform, function(done) {1943 var assertPoints = makeAssertPoints(['r', 'theta']);1944 var assertSelectedPoints = makeAssertSelectedPoints();1945 var fig = Lib.extendDeep({}, require('@mocks/polar_subplots'));1946 fig.layout.width = 800;1947 fig.layout.dragmode = 'select';1948 addInvisible(fig);1949 Plotly.newPlot(gd, fig)1950 .then(function() {1951 if(hasCssTransform) transformPlot(gd, cssTransform);1952 return _run(hasCssTransform,1953 [[150, 150], [350, 250]],1954 function() {1955 assertPoints([[1, 0], [2, 45]]);1956 assertSelectedPoints({0: [0, 1]});1957 },1958 [200, 200],1959 BOXEVENTS, 'scatterpolar select'1960 );1961 })1962 .then(function() {1963 return Plotly.relayout(gd, 'dragmode', 'lasso');1964 })1965 .then(function() {1966 return _run(hasCssTransform,1967 [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]],1968 function() {1969 assertPoints([[1, 0], [2, 45]]);1970 assertSelectedPoints({0: [0, 1]});1971 },1972 [200, 200],1973 LASSOEVENTS, 'scatterpolar lasso'1974 );1975 })1976 .then(done, done.fail);1977 });1978 });1979 [false, true].forEach(function(hasCssTransform) {1980 it('should work on scattersmith traces, hasCssTransform: ' + hasCssTransform, function(done) {1981 var assertPoints = makeAssertPoints(['real', 'imag']);1982 var assertSelectedPoints = makeAssertSelectedPoints();1983 var fig = Lib.extendDeep({}, require('@mocks/smith_basic.json'));1984 fig.layout.dragmode = 'select';1985 addInvisible(fig);1986 Plotly.newPlot(gd, fig)1987 .then(function() {1988 if(hasCssTransform) transformPlot(gd, cssTransform);1989 return _run(hasCssTransform,1990 [[260, 260], [460, 460]],1991 function() {1992 assertPoints([[1, 0]]);1993 assertSelectedPoints({0: [2]});1994 },1995 [360, 360],1996 BOXEVENTS, 'scattersmith select'1997 );1998 })1999 .then(function() {2000 return Plotly.relayout(gd, 'dragmode', 'lasso');2001 })2002 .then(function() {2003 return _run(hasCssTransform,2004 [[260, 260], [260, 460], [460, 460], [460, 260], [260, 260]],2005 function() {2006 assertPoints([[1, 0]]);2007 assertSelectedPoints({0: [2]});2008 },2009 [360, 360],2010 LASSOEVENTS, 'scattersmith lasso'2011 );2012 })2013 .then(done, done.fail);2014 });2015 });2016 [false, true].forEach(function(hasCssTransform) {2017 it('should work on barpolar traces, hasCssTransform: ' + hasCssTransform, function(done) {2018 var assertPoints = makeAssertPoints(['r', 'theta']);2019 var assertSelectedPoints = makeAssertSelectedPoints();2020 var fig = Lib.extendDeep({}, require('@mocks/polar_wind-rose.json'));2021 fig.layout.showlegend = false;2022 fig.layout.width = 500;2023 fig.layout.height = 500;2024 fig.layout.dragmode = 'select';2025 addInvisible(fig);2026 Plotly.newPlot(gd, fig)2027 .then(function() {2028 if(hasCssTransform) transformPlot(gd, cssTransform);2029 return _run(hasCssTransform,2030 [[150, 150], [250, 250]],2031 function() {2032 assertPoints([2033 [62.5, 'N-W'], [55, 'N-W'], [40, 'North'],2034 [40, 'N-W'], [20, 'North'], [22.5, 'N-W']2035 ]);2036 assertSelectedPoints({2037 0: [7],2038 1: [7],2039 2: [0, 7],2040 3: [0, 7]2041 });2042 },2043 [200, 200],2044 BOXEVENTS, 'barpolar select'2045 );2046 })2047 .then(function() {2048 return Plotly.relayout(gd, 'dragmode', 'lasso');2049 })2050 .then(function() {2051 return _run(hasCssTransform,2052 [[150, 150], [350, 150], [350, 250], [150, 250], [150, 150]],2053 function() {2054 assertPoints([2055 [62.5, 'N-W'], [50, 'N-E'], [55, 'N-W'], [40, 'North'],2056 [30, 'N-E'], [40, 'N-W'], [20, 'North'], [7.5, 'N-E'], [22.5, 'N-W']2057 ]);2058 assertSelectedPoints({2059 0: [7],2060 1: [1, 7],2061 2: [0, 1, 7],2062 3: [0, 1, 7]2063 });2064 },2065 [200, 200],2066 LASSOEVENTS, 'barpolar lasso'2067 );2068 })2069 .then(done, done.fail);2070 });2071 });2072 [false, true].forEach(function(hasCssTransform) {2073 it('should work on choropleth traces, hasCssTransform: ' + hasCssTransform, function(done) {2074 var assertPoints = makeAssertPoints(['location', 'z']);2075 var assertSelectedPoints = makeAssertSelectedPoints();2076 var assertRanges = makeAssertRanges('geo', -0.5);2077 var assertLassoPoints = makeAssertLassoPoints('geo', -0.5);2078 var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-text'));2079 fig.layout.width = 870;2080 fig.layout.height = 450;2081 fig.layout.dragmode = 'select';2082 fig.layout.geo.scope = 'europe';2083 addInvisible(fig, false);2084 // add a trace with no locations which will then make trace invisible, lacking DOM elements2085 var emptyChoroplethTrace = Lib.extendDeep({}, fig.data[0]);2086 emptyChoroplethTrace.text = [];2087 emptyChoroplethTrace.locations = [];2088 emptyChoroplethTrace.z = [];2089 fig.data.push(emptyChoroplethTrace);2090 Plotly.newPlot(gd, fig)2091 .then(function() {2092 if(hasCssTransform) transformPlot(gd, cssTransform);2093 return _run(hasCssTransform,2094 [[350, 200], [400, 250]],2095 function() {2096 assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]);2097 assertSelectedPoints({0: [43, 54]});2098 assertRanges([[-19.11, 63.06], [7.31, 53.72]]);2099 },2100 [280, 190],2101 BOXEVENTS, 'choropleth select'2102 );2103 })2104 .then(function() {2105 return Plotly.relayout(gd, 'dragmode', 'lasso');2106 })2107 .then(function() {2108 return _run(hasCssTransform,2109 [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]],2110 function() {2111 assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]);2112 assertSelectedPoints({0: [43, 54]});2113 assertLassoPoints([2114 [-19.11, 63.06], [5.50, 65.25], [7.31, 53.72], [-12.90, 51.70], [-19.11, 63.06]2115 ]);2116 },2117 [280, 190],2118 LASSOEVENTS, 'choropleth lasso'2119 );2120 })2121 .then(function() {2122 // make selection handlers don't get called in 'pan' dragmode2123 return Plotly.relayout(gd, 'dragmode', 'pan');2124 })2125 .then(function() {2126 return _run(hasCssTransform,2127 [[370, 120], [500, 200]], null, [200, 180], NOEVENTS, 'choropleth pan'2128 );2129 })2130 .then(done, done.fail);2131 }, LONG_TIMEOUT_INTERVAL);2132 });2133 [false, true].forEach(function(hasCssTransform) {2134 it('should work for waterfall traces, hasCssTransform: ' + hasCssTransform, function(done) {2135 var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2136 var assertSelectedPoints = makeAssertSelectedPoints();2137 var assertRanges = makeAssertRanges();2138 var assertLassoPoints = makeAssertLassoPoints();2139 var fig = Lib.extendDeep({}, require('@mocks/waterfall_profit-loss_2018_positive-negative'));2140 fig.layout.dragmode = 'lasso';2141 addInvisible(fig);2142 Plotly.newPlot(gd, fig)2143 .then(function() {2144 if(hasCssTransform) transformPlot(gd, cssTransform);2145 return _run(hasCssTransform,2146 [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]],2147 function() {2148 assertPoints([2149 [0, 281, 'Purchases'],2150 [0, 269, 'Material expenses'],2151 [0, 191, 'Personnel expenses'],2152 [0, 179, 'Other expenses']2153 ]);2154 assertSelectedPoints({2155 0: [5, 6, 7, 8]2156 });2157 assertLassoPoints([2158 [288.8086, 57.7617, 288.8086, 519.8555, 404.3321],2159 [4.33870, 6.7580, 9.1774, 6.75806, 5.54838]2160 ]);2161 },2162 null, LASSOEVENTS, 'waterfall lasso'2163 );2164 })2165 .then(function() {2166 return Plotly.relayout(gd, 'dragmode', 'select');2167 })2168 .then(function() {2169 return _run(hasCssTransform,2170 [[300, 300], [400, 400]],2171 function() {2172 assertPoints([2173 [0, 281, 'Purchases'],2174 [0, 269, 'Material expenses']2175 ]);2176 assertSelectedPoints({2177 0: [5, 6]2178 });2179 assertRanges([2180 [173.28519, 288.8086],2181 [4.3387, 6.7580]2182 ]);2183 },2184 null, BOXEVENTS, 'waterfall select'2185 );2186 })2187 .then(done, done.fail);2188 });2189 });2190 [false, true].forEach(function(hasCssTransform) {2191 it('should work for funnel traces, hasCssTransform: ' + hasCssTransform, function(done) {2192 var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2193 var assertSelectedPoints = makeAssertSelectedPoints();2194 var assertRanges = makeAssertRanges();2195 var assertLassoPoints = makeAssertLassoPoints();2196 var fig = Lib.extendDeep({}, require('@mocks/funnel_horizontal_group_basic'));2197 fig.layout.dragmode = 'lasso';2198 addInvisible(fig);2199 Plotly.newPlot(gd, fig)2200 .then(function() {2201 if(hasCssTransform) transformPlot(gd, cssTransform);2202 return _run(hasCssTransform,2203 [[400, 300], [200, 400], [400, 500], [600, 400], [500, 350]],2204 function() {2205 assertPoints([2206 [0, 331.5, 'Author: etpinard'],2207 [1, 53.5, 'Pull requests'],2208 [1, 15.5, 'Author: etpinard'],2209 ]);2210 assertSelectedPoints({2211 0: [2],2212 1: [1, 2]2213 });2214 assertLassoPoints([2215 [-161.6974, -1701.6728, -161.6974, 1378.2779, 608.2902],2216 [1.1129, 1.9193, 2.7258, 1.9193, 1.5161]2217 ]);2218 },2219 null, LASSOEVENTS, 'funnel lasso'2220 );2221 })2222 .then(function() {2223 return Plotly.relayout(gd, 'dragmode', 'select');2224 })2225 .then(function() {2226 return _run(hasCssTransform,2227 [[300, 300], [500, 500]],2228 function() {2229 assertPoints([2230 [0, 331.5, 'Author: etpinard'],2231 [1, 53.5, 'Pull requests'],2232 [1, 15.5, 'Author: etpinard']2233 ]);2234 assertSelectedPoints({2235 0: [2],2236 1: [1, 2]2237 });2238 assertRanges([2239 [-931.6851, 608.2902],2240 [1.1129, 2.7258]2241 ]);2242 },2243 null, BOXEVENTS, 'funnel select'2244 );2245 })2246 .then(done, done.fail);2247 });2248 });2249 [false].forEach(function(hasCssTransform) {2250 it('@flaky should work for bar traces, hasCssTransform: ' + hasCssTransform, function(done) {2251 var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2252 var assertSelectedPoints = makeAssertSelectedPoints();2253 var assertRanges = makeAssertRanges();2254 var assertLassoPoints = makeAssertLassoPoints();2255 var fig = Lib.extendDeep({}, require('@mocks/0'));2256 fig.layout.dragmode = 'lasso';2257 addInvisible(fig);2258 Plotly.newPlot(gd, fig)2259 .then(function() {2260 if(hasCssTransform) transformPlot(gd, cssTransform);2261 return _run(hasCssTransform,2262 [[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]],2263 function() {2264 assertPoints([2265 [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336],2266 [0, 5.3, 0.309], [0, 5.4, 0.275], [0, 5.5, 0.235], [0, 5.6, 0.192],2267 [0, 5.7, 0.145],2268 [1, 5.1, 0.485], [1, 5.2, 0.409], [1, 5.3, 0.327],2269 [1, 5.4, 0.24], [1, 5.5, 0.149], [1, 5.6, 0.059],2270 [2, 4.9, 0.473], [2, 5, 0.368], [2, 5.1, 0.258],2271 [2, 5.2, 0.146], [2, 5.3, 0.036]2272 ]);2273 assertSelectedPoints({2274 0: [49, 50, 51, 52, 53, 54, 55, 56, 57],2275 1: [51, 52, 53, 54, 55, 56],2276 2: [49, 50, 51, 52, 53]2277 });2278 assertLassoPoints([2279 [4.87, 5.74, 5.74, 4.87, 4.87],2280 [0.53, 0.53, -0.02, -0.02, 0.53]2281 ]);2282 },2283 null, LASSOEVENTS, 'bar lasso'2284 );2285 })2286 .then(function() {2287 return Plotly.relayout(gd, 'dragmode', 'select');2288 })2289 .then(delay(100))2290 .then(function() {2291 return _run(hasCssTransform,2292 [[350, 200], [370, 220]],2293 function() {2294 assertPoints([2295 [0, 4.9, 0.371], [0, 5, 0.368], [0, 5.1, 0.356], [0, 5.2, 0.336],2296 [1, 5.1, 0.485], [1, 5.2, 0.41],2297 [2, 4.9, 0.473], [2, 5, 0.37]2298 ]);2299 assertSelectedPoints({2300 0: [49, 50, 51, 52],2301 1: [51, 52],2302 2: [49, 50]2303 });2304 assertRanges([[4.87, 5.22], [0.31, 0.53]]);2305 },2306 null, BOXEVENTS, 'bar select'2307 );2308 })2309 .then(function() {2310 // mimic https://github.com/plotly/plotly.js/issues/37952311 return Plotly.relayout(gd, {2312 'xaxis.rangeslider.visible': true,2313 'xaxis.range': [0, 6]2314 });2315 })2316 .then(function() {2317 return _run(hasCssTransform,2318 [[350, 200], [360, 200]],2319 function() {2320 assertPoints([2321 [0, 2.5, -0.429], [1, 2.5, -1.015], [2, 2.5, -1.172],2322 ]);2323 assertSelectedPoints({2324 0: [25],2325 1: [25],2326 2: [25]2327 });2328 assertRanges([[2.434, 2.521], [-1.4355, 2.0555]]);2329 },2330 null, BOXEVENTS, 'bar select (after xaxis.range relayout)'2331 );2332 })2333 .then(done, done.fail);2334 });2335 });2336 [false, true].forEach(function(hasCssTransform) {2337 it('should work for date/category traces, hasCssTransform: ' + hasCssTransform, function(done) {2338 var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']);2339 var assertSelectedPoints = makeAssertSelectedPoints();2340 var fig = {2341 data: [{2342 x: ['2017-01-01', '2017-02-01', '2017-03-01'],2343 y: ['a', 'b', 'c']2344 }, {2345 type: 'bar',2346 x: ['2017-01-01', '2017-02-02', '2017-03-01'],2347 y: ['a', 'b', 'c']2348 }],2349 layout: {2350 dragmode: 'lasso',2351 width: 400,2352 height: 4002353 }2354 };2355 addInvisible(fig);2356 var x0 = 100;2357 var y0 = 100;2358 var x1 = 250;2359 var y1 = 250;2360 Plotly.newPlot(gd, fig)2361 .then(function() {2362 if(hasCssTransform) transformPlot(gd, cssTransform);2363 return _run(hasCssTransform,2364 [[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]],2365 function() {2366 assertPoints([2367 [0, '2017-02-01', 'b'],2368 [1, '2017-02-02', 'b']2369 ]);2370 assertSelectedPoints({0: [1], 1: [1]});2371 },2372 null, LASSOEVENTS, 'date/category lasso'2373 );2374 })2375 .then(function() {2376 return Plotly.relayout(gd, 'dragmode', 'select');2377 })2378 .then(function() {2379 return _run(hasCssTransform,2380 [[x0, y0], [x1, y1]],2381 function() {2382 assertPoints([2383 [0, '2017-02-01', 'b'],2384 [1, '2017-02-02', 'b']2385 ]);2386 assertSelectedPoints({0: [1], 1: [1]});2387 },2388 null, BOXEVENTS, 'date/category select'2389 );2390 })2391 .then(done, done.fail);2392 });2393 });2394 [false, true].forEach(function(hasCssTransform) {2395 it('should work for histogram traces, hasCssTransform: ' + hasCssTransform, function(done) {2396 var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y', 'pointIndices']);2397 var assertSelectedPoints = makeAssertSelectedPoints();2398 var assertRanges = makeAssertRanges();2399 var assertLassoPoints = makeAssertLassoPoints();2400 var fig = Lib.extendDeep({}, require('@mocks/hist_grouped'));2401 fig.layout.dragmode = 'lasso';2402 fig.layout.width = 600;2403 fig.layout.height = 500;2404 addInvisible(fig);2405 Plotly.newPlot(gd, fig)2406 .then(function() {2407 if(hasCssTransform) transformPlot(gd, cssTransform);2408 return _run(hasCssTransform,2409 [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2410 function() {2411 assertPoints([2412 [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]]2413 ]);2414 assertSelectedPoints({0: [3, 4], 1: [1, 2]});2415 assertLassoPoints([2416 [1.66, 3.59, 3.59, 1.66, 1.66],2417 [2.17, 2.17, 0.69, 0.69, 2.17]2418 ]);2419 },2420 null, LASSOEVENTS, 'histogram lasso'2421 );2422 })2423 .then(function() {2424 return Plotly.relayout(gd, 'dragmode', 'select');2425 })2426 .then(function() {2427 return _run(hasCssTransform,2428 [[200, 200], [400, 350]],2429 function() {2430 assertPoints([2431 [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]]2432 ]);2433 assertSelectedPoints({0: [3, 4], 1: [1, 2]});2434 assertRanges([[1.66, 3.59], [0.69, 2.17]]);2435 },2436 null, BOXEVENTS, 'histogram select'2437 );2438 })2439 .then(done, done.fail);2440 });2441 });2442 [false, true].forEach(function(hasCssTransform) {2443 it('should work for box traces, hasCssTransform: ' + hasCssTransform, function(done) {2444 var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']);2445 var assertSelectedPoints = makeAssertSelectedPoints();2446 var assertRanges = makeAssertRanges();2447 var assertLassoPoints = makeAssertLassoPoints();2448 var fig = Lib.extendDeep({}, require('@mocks/box_grouped'));2449 fig.data.forEach(function(trace) {2450 trace.boxpoints = 'all';2451 });2452 fig.layout.dragmode = 'lasso';2453 fig.layout.width = 600;2454 fig.layout.height = 500;2455 fig.layout.xaxis = {range: [-0.565, 1.5]};2456 addInvisible(fig);2457 Plotly.newPlot(gd, fig)2458 .then(function() {2459 if(hasCssTransform) transformPlot(gd, cssTransform);2460 return _run(hasCssTransform,2461 [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2462 function() {2463 assertPoints([2464 [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'],2465 [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'],2466 [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1']2467 ]);2468 assertSelectedPoints({2469 0: [6, 11, 10, 7],2470 1: [11, 8, 6, 10],2471 2: [1, 4, 5]2472 });2473 assertLassoPoints([2474 [0.0423, 1.0546, 1.0546, 0.0423, 0.0423],2475 [0.71, 0.71, 0.1875, 0.1875, 0.71]2476 ]);2477 },2478 null, LASSOEVENTS, 'box lasso'2479 );2480 })2481 .then(function() {2482 return Plotly.relayout(gd, 'dragmode', 'select');2483 })2484 .then(function() {2485 return _run(hasCssTransform,2486 [[200, 200], [400, 350]],2487 function() {2488 assertPoints([2489 [0, 0.2, 'day 2'], [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'],2490 [1, 0.2, 'day 2'], [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'],2491 [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1']2492 ]);2493 assertSelectedPoints({2494 0: [6, 11, 10, 7],2495 1: [11, 8, 6, 10],2496 2: [1, 4, 5]2497 });2498 assertRanges([[0.04235, 1.0546], [0.1875, 0.71]]);2499 },2500 null, BOXEVENTS, 'box select'2501 );2502 })2503 .then(done, done.fail);2504 });2505 });2506 [false, true].forEach(function(hasCssTransform) {2507 it('should work for box traces (q1/median/q3 case), hasCssTransform: ' + hasCssTransform, function(done) {2508 var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']);2509 var assertSelectedPoints = makeAssertSelectedPoints();2510 var fig = {2511 data: [{2512 type: 'box',2513 x0: 'A',2514 q1: [1],2515 median: [2],2516 q3: [3],2517 y: [[0, 1, 2, 3, 4]],2518 pointpos: 0,2519 }],2520 layout: {2521 width: 500,2522 height: 500,2523 dragmode: 'lasso'2524 }2525 };2526 Plotly.newPlot(gd, fig)2527 .then(function() {2528 if(hasCssTransform) transformPlot(gd, cssTransform);2529 return _run(hasCssTransform,2530 [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2531 function() {2532 assertPoints([ [0, 1, undefined], [0, 2, undefined] ]);2533 assertSelectedPoints({ 0: [[0, 1], [0, 2]] });2534 },2535 null, LASSOEVENTS, 'box lasso'2536 );2537 })2538 .then(function() {2539 return Plotly.relayout(gd, 'dragmode', 'select');2540 })2541 .then(function() {2542 return _run(hasCssTransform,2543 [[200, 200], [400, 300]],2544 function() {2545 assertPoints([ [0, 2, undefined] ]);2546 assertSelectedPoints({ 0: [[0, 2]] });2547 },2548 null, BOXEVENTS, 'box select'2549 );2550 })2551 .then(done, done.fail);2552 });2553 });2554 [false, true].forEach(function(hasCssTransform) {2555 it('should work for violin traces, hasCssTransform: ' + hasCssTransform, function(done) {2556 var assertPoints = makeAssertPoints(['curveNumber', 'y', 'x']);2557 var assertSelectedPoints = makeAssertSelectedPoints();2558 var assertRanges = makeAssertRanges();2559 var assertLassoPoints = makeAssertLassoPoints();2560 var fig = Lib.extendDeep({}, require('@mocks/violin_grouped'));2561 fig.layout.dragmode = 'lasso';2562 fig.layout.width = 600;2563 fig.layout.height = 500;2564 addInvisible(fig);2565 Plotly.newPlot(gd, fig)2566 .then(function() {2567 if(hasCssTransform) transformPlot(gd, cssTransform);2568 return _run(hasCssTransform,2569 [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]],2570 function() {2571 assertPoints([2572 [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'],2573 [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'],2574 [1, 0.9, 'day 2'],2575 [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1']2576 ]);2577 assertSelectedPoints({2578 0: [11, 10, 7, 8],2579 1: [8, 6, 10, 9, 7],2580 2: [1, 4, 5, 3]2581 });2582 assertLassoPoints([2583 [0.07777, 1.0654, 1.0654, 0.07777, 0.07777],2584 [1.02, 1.02, 0.27, 0.27, 1.02]2585 ]);2586 },2587 null, LASSOEVENTS, 'violin lasso'2588 );2589 })2590 .then(function() {2591 return Plotly.relayout(gd, 'dragmode', 'select');2592 })2593 .then(function() {2594 return _run(hasCssTransform,2595 [[200, 200], [400, 350]],2596 function() {2597 assertPoints([2598 [0, 0.3, 'day 2'], [0, 0.5, 'day 2'], [0, 0.7, 'day 2'], [0, 0.9, 'day 2'],2599 [1, 0.5, 'day 2'], [1, 0.7, 'day 2'], [1, 0.7, 'day 2'], [1, 0.8, 'day 2'],2600 [1, 0.9, 'day 2'],2601 [2, 0.3, 'day 1'], [2, 0.6, 'day 1'], [2, 0.6, 'day 1'], [2, 0.9, 'day 1']2602 ]);2603 assertSelectedPoints({2604 0: [11, 10, 7, 8],2605 1: [8, 6, 10, 9, 7],2606 2: [1, 4, 5, 3]2607 });2608 assertRanges([[0.07777, 1.0654], [0.27, 1.02]]);2609 },2610 null, BOXEVENTS, 'violin select'2611 );2612 })2613 .then(done, done.fail);2614 });2615 });2616 [false].forEach(function(hasCssTransform) {2617 ['ohlc', 'candlestick'].forEach(function(type) {2618 it('should work for ' + type + ' traces, hasCssTransform: ' + hasCssTransform, function(done) {2619 var assertPoints = makeAssertPoints(['curveNumber', 'x', 'open', 'high', 'low', 'close']);2620 var assertSelectedPoints = makeAssertSelectedPoints();2621 var assertRanges = makeAssertRanges();2622 var assertLassoPoints = makeAssertLassoPoints();2623 var l0 = 275;2624 var lv0 = '2011-01-03 18:00';2625 var r0 = 325;2626 var rv0 = '2011-01-04 06:00';2627 var l1 = 75;2628 var lv1 = '2011-01-01 18:00';2629 var r1 = 125;2630 var rv1 = '2011-01-02 06:00';2631 var t = 75;2632 var tv = 7.565;2633 var b = 225;2634 var bv = -1.048;2635 function countUnSelectedPaths(selector) {2636 var unselected = 0;2637 d3Select(gd).selectAll(selector).each(function() {2638 var opacity = this.style.opacity;2639 if(opacity < 1) unselected++;2640 });2641 return unselected;2642 }2643 Plotly.newPlot(gd, [{2644 type: type,2645 x: ['2011-01-02', '2011-01-03', '2011-01-04'],2646 open: [1, 2, 3],2647 high: [3, 4, 5],2648 low: [0, 1, 2],2649 close: [0, 3, 2]2650 }], {2651 width: 400,2652 height: 400,2653 margin: {l: 50, r: 50, t: 50, b: 50},2654 yaxis: {range: [-3, 9]},2655 dragmode: 'lasso'2656 })2657 .then(function() {2658 if(hasCssTransform) transformPlot(gd, cssTransform);2659 return _run(hasCssTransform,2660 [[l0, t], [l0, b], [r0, b], [r0, t], [l0, t]],2661 function() {2662 assertPoints([[0, '2011-01-04', 3, 5, 2, 2]]);2663 assertSelectedPoints([[2]]);2664 assertLassoPoints([2665 [lv0, lv0, rv0, rv0, lv0],2666 [tv, bv, bv, tv, tv]2667 ]);2668 expect(countUnSelectedPaths('.cartesianlayer .trace path')).toBe(2);2669 expect(countUnSelectedPaths('.rangeslider-rangeplot .trace path')).toBe(2);2670 },2671 null, LASSOEVENTS, type + ' lasso'2672 );2673 })2674 .then(function() {2675 return Plotly.relayout(gd, 'dragmode', 'select');2676 })2677 .then(function() {2678 return _run(hasCssTransform,2679 [[l1, t], [r1, b]],2680 function() {2681 assertPoints([[0, '2011-01-02', 1, 3, 0, 0]]);2682 assertSelectedPoints([[0]]);2683 assertRanges([[lv1, rv1], [bv, tv]]);2684 },2685 null, BOXEVENTS, type + ' select'2686 );2687 })2688 .then(done, done.fail);2689 });2690 });2691 });2692 [false, true].forEach(function(hasCssTransform) {2693 it('should work on traces with enabled transforms, hasCssTransform: ' + hasCssTransform, function(done) {2694 var assertSelectedPoints = makeAssertSelectedPoints();2695 Plotly.newPlot(gd, [{2696 x: [1, 2, 3, 4, 5],2697 y: [2, 3, 1, 7, 9],2698 marker: {size: [10, 20, 20, 20, 10]},2699 transforms: [{2700 type: 'filter',2701 operation: '>',2702 value: 2,2703 target: 'y'2704 }, {2705 type: 'aggregate',2706 groups: 'marker.size',2707 aggregations: [2708 // 20: 6, 10: 52709 {target: 'x', func: 'sum'},2710 // 20: 5, 10: 92711 {target: 'y', func: 'avg'}2712 ]2713 }]2714 }], {2715 dragmode: 'select',2716 showlegend: false,2717 width: 400,2718 height: 400,2719 margin: {l: 0, t: 0, r: 0, b: 0}2720 })2721 .then(function() {2722 if(hasCssTransform) transformPlot(gd, cssTransform);2723 return _run(hasCssTransform,2724 [[5, 5], [395, 395]],2725 function() {2726 assertSelectedPoints({0: [1, 3, 4]});2727 },2728 [380, 180],2729 BOXEVENTS, 'transformed trace select (all points selected)'2730 );2731 })2732 .then(done, done.fail);2733 });2734 });2735 [false, true].forEach(function(hasCssTransform) {2736 it('should work on scatter/bar traces with text nodes, hasCssTransform: ' + hasCssTransform, function(done) {2737 var assertSelectedPoints = makeAssertSelectedPoints();2738 function assertFillOpacity(exp, msg) {2739 var txtPts = d3Select(gd).select('g.plot').selectAll('text');2740 expect(txtPts.size()).toBe(exp.length, '# of text nodes: ' + msg);2741 txtPts.each(function(_, i) {2742 var act = Number(this.style['fill-opacity']);2743 expect(act).toBe(exp[i], 'node ' + i + ' fill opacity: ' + msg);2744 });2745 }2746 Plotly.newPlot(gd, [{2747 mode: 'markers+text',2748 x: [1, 2, 3],2749 y: [1, 2, 1],2750 text: ['a', 'b', 'c']2751 }, {2752 type: 'bar',2753 x: [1, 2, 3],2754 y: [1, 2, 1],2755 text: ['A', 'B', 'C'],2756 textposition: 'outside'2757 }], {2758 dragmode: 'select',2759 hovermode: 'closest',2760 showlegend: false,2761 width: 400,2762 height: 400,2763 margin: {l: 0, t: 0, r: 0, b: 0}2764 })2765 .then(function() {2766 if(hasCssTransform) transformPlot(gd, cssTransform);2767 return _run(hasCssTransform,2768 [[10, 10], [100, 300]],2769 function() {2770 assertSelectedPoints({0: [0], 1: [0]});2771 assertFillOpacity([1, 0.2, 0.2, 1, 0.2, 0.2], '_run');2772 },2773 [10, 10], BOXEVENTS, 'selecting first scatter/bar text nodes'2774 );2775 })2776 .then(function() {2777 assertFillOpacity([1, 1, 1, 1, 1, 1], 'final');2778 })2779 .then(done, done.fail);2780 });2781 });2782 describe('should work on sankey traces', function() {2783 var waitingTime = sankeyConstants.duration * 2;2784 [false].forEach(function(hasCssTransform) {2785 it('select, hasCssTransform: ' + hasCssTransform, function(done) {2786 var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json'));2787 fig.layout.dragmode = 'select';2788 var dblClickPos = [250, 400];2789 Plotly.newPlot(gd, fig)2790 .then(function() {2791 if(hasCssTransform) transformPlot(gd, cssTransform);2792 // No groups initially2793 expect(gd._fullData[0].node.groups).toEqual([]);2794 })2795 .then(function() {2796 // Grouping the two nodes on the top right2797 return _run(hasCssTransform,2798 [[640, 130], [400, 450]],2799 function() {2800 expect(gd._fullData[0].node.groups).toEqual([[2, 3]], 'failed to group #2 + #3');2801 },2802 dblClickPos, BOXEVENTS, 'for top right nodes #2 and #3'2803 );2804 })2805 .then(delay(waitingTime))2806 .then(function() {2807 // Grouping node #4 and the previous group2808 drag([[715, 400], [300, 110]]);2809 })2810 .then(delay(waitingTime))2811 .then(function() {2812 expect(gd._fullData[0].node.groups).toEqual([[4, 3, 2]], 'failed to group #4 + existing group of #2 and #3');2813 })2814 .then(done, done.fail);2815 });2816 });2817 it('should not work when dragmode is undefined', function(done) {2818 var fig = Lib.extendDeep({}, require('@mocks/sankey_circular.json'));2819 fig.layout.dragmode = undefined;2820 Plotly.newPlot(gd, fig)2821 .then(function() {2822 // No groups initially2823 expect(gd._fullData[0].node.groups).toEqual([]);2824 })2825 .then(function() {2826 // Grouping the two nodes on the top right2827 drag([[640, 130], [400, 450]]);2828 })2829 .then(delay(waitingTime))2830 .then(function() {2831 expect(gd._fullData[0].node.groups).toEqual([]);2832 })2833 .then(done, done.fail);2834 });2835 });2836});2837describe('Test that selections persist:', function() {2838 var gd;2839 beforeEach(function() {2840 gd = createGraphDiv();2841 });2842 afterEach(destroyGraphDiv);2843 function assertPtOpacity(selector, expected) {2844 d3SelectAll(selector).each(function(_, i) {2845 var style = Number(this.style.opacity);2846 expect(style).toBe(expected.style[i], 'style for pt ' + i);2847 });2848 }2849 it('should persist for scatter', function(done) {2850 function _assert(expected) {2851 var selected = gd.calcdata[0].map(function(d) { return d.selected; });2852 expect(selected).toBeCloseToArray(expected.selected, 'selected vals');2853 assertPtOpacity('.point', expected);2854 }2855 Plotly.newPlot(gd, [{2856 x: [1, 2, 3],2857 y: [1, 2, 1]2858 }], {2859 dragmode: 'select',2860 width: 400,2861 height: 400,2862 margin: {l: 0, t: 0, r: 0, b: 0}2863 })2864 .then(function() {2865 resetEvents(gd);2866 drag([[5, 5], [250, 350]]);2867 return selectedPromise;2868 })2869 .then(function() {2870 _assert({2871 selected: [0, 1, 0],2872 style: [0.2, 1, 0.2]2873 });2874 // trigger a recalc2875 Plotly.restyle(gd, 'x', [[10, 20, 30]]);2876 })2877 .then(function() {2878 _assert({2879 selected: [undefined, 1, undefined],2880 style: [0.2, 1, 0.2]2881 });2882 })2883 .then(done, done.fail);2884 });2885 it('should persist for box', function(done) {2886 function _assert(expected) {2887 var selected = gd.calcdata[0][0].pts.map(function(d) { return d.selected; });2888 expect(selected).toBeCloseToArray(expected.cd, 'selected calcdata vals');2889 expect(gd.data[0].selectedpoints).toBeCloseToArray(expected.selectedpoints, 'selectedpoints array');2890 assertPtOpacity('.point', expected);2891 }2892 Plotly.newPlot(gd, [{2893 type: 'box',2894 x0: 0,2895 y: [5, 4, 4, 1, 2, 2, 2, 2, 2, 3, 3, 3],2896 boxpoints: 'all'2897 }], {2898 dragmode: 'select',2899 width: 400,2900 height: 400,2901 margin: {l: 0, t: 0, r: 0, b: 0}2902 })2903 .then(function() {2904 resetEvents(gd);2905 drag([[5, 5], [400, 150]]);2906 return selectedPromise;2907 })2908 .then(function() {2909 _assert({2910 // N.B. pts in calcdata are sorted2911 cd: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],2912 selectedpoints: [1, 2, 0],2913 style: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 1, 1, 1],2914 });2915 // trigger a recalc2916 return Plotly.restyle(gd, 'x0', 1);2917 })2918 .then(function() {2919 _assert({2920 cd: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 1, 1, 1],2921 selectedpoints: [1, 2, 0],2922 style: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 1, 1, 1],2923 });2924 })2925 .then(done, done.fail);2926 });2927 it('should persist for histogram', function(done) {2928 function _assert(expected) {2929 var selected = gd.calcdata[0].map(function(d) { return d.selected; });2930 expect(selected).toBeCloseToArray(expected.selected, 'selected vals');2931 assertPtOpacity('.point > path', expected);2932 }2933 Plotly.newPlot(gd, [{2934 type: 'histogram',2935 x: [1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5],2936 boxpoints: 'all'2937 }], {2938 dragmode: 'select',2939 width: 400,2940 height: 400,2941 margin: {l: 0, t: 0, r: 0, b: 0}2942 })2943 .then(function() {2944 resetEvents(gd);2945 drag([[5, 5], [400, 150]]);2946 return selectedPromise;2947 })2948 .then(function() {2949 _assert({2950 selected: [0, 1, 0, 0, 0],2951 style: [0.2, 1, 0.2, 0.2, 0.2],2952 });2953 // trigger a recalc2954 return Plotly.restyle(gd, 'histfunc', 'sum');2955 })2956 .then(function() {2957 _assert({2958 selected: [undefined, 1, undefined, undefined, undefined],2959 style: [0.2, 1, 0.2, 0.2, 0.2],2960 });2961 })2962 .then(done, done.fail);2963 });2964});2965describe('Test that selection styles propagate to range-slider plot:', function() {2966 var gd;2967 beforeEach(function() {2968 gd = createGraphDiv();2969 });2970 afterEach(destroyGraphDiv);2971 function makeAssertFn(query) {2972 return function(msg, expected) {2973 var gd3 = d3Select(gd);2974 var mainPlot3 = gd3.select('.cartesianlayer');2975 var rangePlot3 = gd3.select('.rangeslider-rangeplot');2976 mainPlot3.selectAll(query).each(function(_, i) {2977 expect(this.style.opacity).toBe(String(expected[i]), msg + ' opacity for mainPlot pt ' + i);2978 });2979 rangePlot3.selectAll(query).each(function(_, i) {2980 expect(this.style.opacity).toBe(String(expected[i]), msg + ' opacity for rangePlot pt ' + i);2981 });2982 };2983 }2984 it('- svg points case', function(done) {2985 var _assert = makeAssertFn('path.point,.point>path');2986 Plotly.newPlot(gd, [2987 { mode: 'markers', x: [1], y: [1] },2988 { type: 'bar', x: [2], y: [2], },2989 { type: 'histogram', x: [3, 3, 3] },2990 { type: 'box', x: [4], y: [4], boxpoints: 'all' },2991 { type: 'violin', x: [5], y: [5], points: 'all' },2992 { type: 'waterfall', x: [6], y: [6]},2993 { type: 'funnel', x: [7], y: [7], orientation: 'v'}2994 ], {2995 dragmode: 'select',2996 xaxis: { rangeslider: {visible: true} },2997 width: 500,2998 height: 500,2999 margin: {l: 10, t: 10, r: 10, b: 10},3000 showlegend: false3001 })3002 .then(function() {3003 _assert('base', [1, 1, 1, 1, 1, 1, 1]);3004 })3005 .then(function() {3006 resetEvents(gd);3007 drag([[20, 20], [40, 40]]);3008 return selectedPromise;3009 })3010 .then(function() {3011 _assert('after empty selection', [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]);3012 })3013 .then(function() { return doubleClick(200, 200); })3014 .then(function() {3015 _assert('after double-click reset', [1, 1, 1, 1, 1, 1, 1]);3016 })3017 .then(done, done.fail);3018 });3019 it('- svg finance case', function(done) {3020 var _assert = makeAssertFn('path.box,.ohlc>path');3021 Plotly.newPlot(gd, [3022 { type: 'ohlc', x: [6], open: [6], high: [6], low: [6], close: [6] },3023 { type: 'candlestick', x: [7], open: [7], high: [7], low: [7], close: [7] },3024 ], {3025 dragmode: 'select',3026 width: 500,3027 height: 500,3028 margin: {l: 10, t: 10, r: 10, b: 10},3029 showlegend: false3030 })3031 .then(function() {3032 _assert('base', [1, 1]);3033 })3034 .then(function() {3035 resetEvents(gd);3036 drag([[20, 20], [40, 40]]);3037 return selectedPromise;3038 })3039 .then(function() {3040 _assert('after empty selection', [0.3, 0.3]);3041 })3042 .then(function() { return doubleClick(200, 200); })3043 .then(function() {3044 _assert('after double-click reset', [1, 1]);3045 })3046 .then(done, done.fail);3047 });3048});3049// to make sure none of the above tests fail with extraneous invisible traces,3050// add a bunch of them here3051function addInvisible(fig, canHaveLegend) {3052 var data = fig.data;3053 var inputData = Lib.extendDeep([], data);3054 for(var i = 0; i < inputData.length; i++) {3055 data.push(Lib.extendDeep({}, inputData[i], {visible: false}));3056 if(canHaveLegend !== false) data.push(Lib.extendDeep({}, inputData[i], {visible: 'legendonly'}));3057 }3058 if(inputData.length === 1 && fig.layout.showlegend !== true) fig.layout.showlegend = false;...
runtime-dom.cjs.js
Source:runtime-dom.cjs.js
...967 const moveClass = props.moveClass || `${props.name || 'v'}-move`;968 // Check if move transition is needed. This check is cached per-instance.969 hasMove =970 hasMove === null971 ? (hasMove = hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass))972 : hasMove;973 if (!hasMove) {974 return;975 }976 // we divide the work into three loops to avoid mixing DOM reads and writes977 // in each iteration - which helps prevent layout thrashing.978 prevChildren.forEach(callPendingCbs);979 prevChildren.forEach(recordPosition);980 const movedChildren = prevChildren.filter(applyTranslation);981 // force reflow to put everything in position982 forceReflow();983 movedChildren.forEach(c => {984 const el = c.el;985 const style = el.style;986 addTransitionClass(el, moveClass);987 style.transform = style.WebkitTransform = style.transitionDuration = '';988 const cb = (el._moveCb = (e) => {989 if (e && e.target !== el) {990 return;991 }992 if (!e || /transform$/.test(e.propertyName)) {993 el.removeEventListener('transitionend', cb);994 el._moveCb = null;995 removeTransitionClass(el, moveClass);996 }997 });998 el.addEventListener('transitionend', cb);999 });1000 });1001 return () => {1002 const rawProps = runtimeCore.toRaw(props);1003 const cssTransitionProps = resolveTransitionProps(rawProps);1004 const tag = rawProps.tag || runtimeCore.Fragment;1005 prevChildren = children;1006 children = slots.default ? slots.default() : [];1007 // handle fragment children case, e.g. v-for1008 if (children.length === 1 && children[0].type === runtimeCore.Fragment) {1009 children = children[0].children;1010 }1011 for (let i = 0; i < children.length; i++) {1012 const child = children[i];1013 if (child.key != null) {1014 runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));1015 }1016 else {1017 runtimeCore.warn(`<TransitionGroup> children must be keyed.`);1018 }1019 }1020 if (prevChildren) {1021 for (let i = 0; i < prevChildren.length; i++) {1022 const child = prevChildren[i];1023 runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));1024 positionMap.set(child, child.el.getBoundingClientRect());1025 }1026 }1027 return runtimeCore.createVNode(tag, null, children);1028 };1029 }1030};1031const TransitionGroup = TransitionGroupImpl;1032{1033 const props = (TransitionGroup.props = {1034 ...TransitionPropsValidators,1035 tag: String,1036 moveClass: String1037 });1038 delete props.mode;1039}1040function callPendingCbs(c) {1041 if (c.el._moveCb) {1042 c.el._moveCb();1043 }1044 if (c.el._enterCb) {1045 c.el._enterCb();1046 }1047}1048function recordPosition(c) {1049 newPositionMap.set(c, c.el.getBoundingClientRect());1050}1051function applyTranslation(c) {1052 const oldPos = positionMap.get(c);1053 const newPos = newPositionMap.get(c);1054 const dx = oldPos.left - newPos.left;1055 const dy = oldPos.top - newPos.top;1056 if (dx || dy) {1057 const s = c.el.style;1058 s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`;1059 s.transitionDuration = '0s';1060 return c;1061 }1062}1063// this is put in a dedicated function to avoid the line from being treeshaken1064function forceReflow() {1065 return document.body.offsetHeight;1066}1067function hasCSSTransform(el, root, moveClass) {1068 // Detect whether an element with the move class applied has1069 // CSS transitions. Since the element may be inside an entering1070 // transition at this very moment, we make a clone of it and remove1071 // all other transition classes applied to ensure only the move class1072 // is applied.1073 const clone = el.cloneNode();1074 if (el._vtc) {1075 el._vtc.forEach(cls => {1076 cls.split(/\s+/).forEach(c => c && clone.classList.remove(c));1077 });1078 }1079 moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c));1080 clone.style.display = 'none';1081 const container = (root.nodeType === 1
...
runtime-dom.esm-bundler.js
Source:runtime-dom.esm-bundler.js
...600 const moveClass = props.moveClass || `${props.name || 'v'}-move`;601 // Check if move transition is needed. This check is cached per-instance.602 hasMove =603 hasMove === null604 ? (hasMove = hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass))605 : hasMove;606 if (!hasMove) {607 return;608 }609 // we divide the work into three loops to avoid mixing DOM reads and writes610 // in each iteration - which helps prevent layout thrashing.611 prevChildren.forEach(callPendingCbs);612 prevChildren.forEach(recordPosition);613 const movedChildren = prevChildren.filter(applyTranslation);614 // force reflow to put everything in position615 forceReflow();616 movedChildren.forEach(c => {617 const el = c.el;618 const style = el.style;619 addTransitionClass(el, moveClass);620 style.transform = style.webkitTransform = style.transitionDuration = '';621 const cb = (el._moveCb = (e) => {622 if (e && e.target !== el) {623 return;624 }625 if (!e || /transform$/.test(e.propertyName)) {626 el.removeEventListener('transitionend', cb);627 el._moveCb = null;628 removeTransitionClass(el, moveClass);629 }630 });631 el.addEventListener('transitionend', cb);632 });633 });634 return () => {635 const rawProps = toRaw(props);636 const cssTransitionProps = resolveTransitionProps(rawProps);637 const tag = rawProps.tag || Fragment;638 prevChildren = children;639 children = getTransitionRawChildren(slots.default ? slots.default() : []);640 for (let i = 0; i < children.length; i++) {641 const child = children[i];642 if (child.key != null) {643 setTransitionHooks(child, resolveTransitionHooks(child, cssTransitionProps, state, instance));644 }645 else if ((process.env.NODE_ENV !== 'production') && child.type !== Comment) {646 warn(`<TransitionGroup> children must be keyed.`);647 }648 }649 if (prevChildren) {650 for (let i = 0; i < prevChildren.length; i++) {651 const child = prevChildren[i];652 setTransitionHooks(child, resolveTransitionHooks(child, cssTransitionProps, state, instance));653 positionMap.set(child, child.el.getBoundingClientRect());654 }655 }656 return createVNode(tag, null, children);657 };658 }659};660function getTransitionRawChildren(children) {661 let ret = [];662 for (let i = 0; i < children.length; i++) {663 const child = children[i];664 // handle fragment children case, e.g. v-for665 if (child.type === Fragment) {666 ret = ret.concat(getTransitionRawChildren(child.children));667 }668 else {669 ret.push(child);670 }671 }672 return ret;673}674// remove mode props as TransitionGroup doesn't support it675delete TransitionGroupImpl.props.mode;676const TransitionGroup = TransitionGroupImpl;677function callPendingCbs(c) {678 const el = c.el;679 if (el._moveCb) {680 el._moveCb();681 }682 if (el._enterCb) {683 el._enterCb();684 }685}686function recordPosition(c) {687 newPositionMap.set(c, c.el.getBoundingClientRect());688}689function applyTranslation(c) {690 const oldPos = positionMap.get(c);691 const newPos = newPositionMap.get(c);692 const dx = oldPos.left - newPos.left;693 const dy = oldPos.top - newPos.top;694 if (dx || dy) {695 const s = c.el.style;696 s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`;697 s.transitionDuration = '0s';698 return c;699 }700}701// this is put in a dedicated function to avoid the line from being treeshaken702function forceReflow() {703 return document.body.offsetHeight;704}705function hasCSSTransform(el, root, moveClass) {706 // Detect whether an element with the move class applied has707 // CSS transitions. Since the element may be inside an entering708 // transition at this very moment, we make a clone of it and remove709 // all other transition classes applied to ensure only the move class710 // is applied.711 const clone = el.cloneNode();712 if (el._vtc) {713 el._vtc.forEach(cls => {714 cls.split(/\s+/).forEach(c => c && clone.classList.remove(c));715 });716 }717 moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c));718 clone.style.display = 'none';719 const container = (root.nodeType === 1
...
runtime-dom.cjs.prod.js
Source:runtime-dom.cjs.prod.js
...925 const moveClass = props.moveClass || `${props.name || 'v'}-move`;926 // Check if move transition is needed. This check is cached per-instance.927 hasMove =928 hasMove === null929 ? (hasMove = hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass))930 : hasMove;931 if (!hasMove) {932 return;933 }934 // we divide the work into three loops to avoid mixing DOM reads and writes935 // in each iteration - which helps prevent layout thrashing.936 prevChildren.forEach(callPendingCbs);937 prevChildren.forEach(recordPosition);938 const movedChildren = prevChildren.filter(applyTranslation);939 // force reflow to put everything in position940 forceReflow();941 movedChildren.forEach(c => {942 const el = c.el;943 const style = el.style;944 addTransitionClass(el, moveClass);945 style.transform = style.WebkitTransform = style.transitionDuration = '';946 const cb = (el._moveCb = (e) => {947 if (e && e.target !== el) {948 return;949 }950 if (!e || /transform$/.test(e.propertyName)) {951 el.removeEventListener('transitionend', cb);952 el._moveCb = null;953 removeTransitionClass(el, moveClass);954 }955 });956 el.addEventListener('transitionend', cb);957 });958 });959 return () => {960 const rawProps = runtimeCore.toRaw(props);961 const cssTransitionProps = resolveTransitionProps(rawProps);962 const tag = rawProps.tag || runtimeCore.Fragment;963 prevChildren = children;964 children = slots.default ? slots.default() : [];965 // handle fragment children case, e.g. v-for966 if (children.length === 1 && children[0].type === runtimeCore.Fragment) {967 children = children[0].children;968 }969 for (let i = 0; i < children.length; i++) {970 const child = children[i];971 if (child.key != null) {972 runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));973 }974 }975 if (prevChildren) {976 for (let i = 0; i < prevChildren.length; i++) {977 const child = prevChildren[i];978 runtimeCore.setTransitionHooks(child, runtimeCore.resolveTransitionHooks(child, cssTransitionProps, state, instance));979 positionMap.set(child, child.el.getBoundingClientRect());980 }981 }982 return runtimeCore.createVNode(tag, null, children);983 };984 }985};986const TransitionGroup = TransitionGroupImpl;987function callPendingCbs(c) {988 if (c.el._moveCb) {989 c.el._moveCb();990 }991 if (c.el._enterCb) {992 c.el._enterCb();993 }994}995function recordPosition(c) {996 newPositionMap.set(c, c.el.getBoundingClientRect());997}998function applyTranslation(c) {999 const oldPos = positionMap.get(c);1000 const newPos = newPositionMap.get(c);1001 const dx = oldPos.left - newPos.left;1002 const dy = oldPos.top - newPos.top;1003 if (dx || dy) {1004 const s = c.el.style;1005 s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`;1006 s.transitionDuration = '0s';1007 return c;1008 }1009}1010// this is put in a dedicated function to avoid the line from being treeshaken1011function forceReflow() {1012 return document.body.offsetHeight;1013}1014function hasCSSTransform(el, root, moveClass) {1015 // Detect whether an element with the move class applied has1016 // CSS transitions. Since the element may be inside an entering1017 // transition at this very moment, we make a clone of it and remove1018 // all other transition classes applied to ensure only the move class1019 // is applied.1020 const clone = el.cloneNode();1021 if (el._vtc) {1022 el._vtc.forEach(cls => {1023 cls.split(/\s+/).forEach(c => c && clone.classList.remove(c));1024 });1025 }1026 moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c));1027 clone.style.display = 'none';1028 const container = (root.nodeType === 1
...
choroplethmapbox_test.js
Source:choroplethmapbox_test.js
1var Plotly = require('@lib/index');2var Plots = require('@src/plots/plots');3var Lib = require('@src/lib');4var loggers = require('@src/lib/loggers');5var convertModule = require('@src/traces/choroplethmapbox/convert');6var MAPBOX_ACCESS_TOKEN = require('@build/credentials.json').MAPBOX_ACCESS_TOKEN;7var createGraphDiv = require('../assets/create_graph_div');8var destroyGraphDiv = require('../assets/destroy_graph_div');9var failTest = require('../assets/fail_test');10var mouseEvent = require('../assets/mouse_event');11var supplyAllDefaults = require('../assets/supply_defaults');12var assertHoverLabelContent = require('../assets/custom_assertions').assertHoverLabelContent;13describe('Test choroplethmapbox defaults:', function() {14 var gd;15 var fullData;16 function _supply(opts, layout) {17 gd = {};18 opts = Array.isArray(opts) ? opts : [opts];19 gd.data = opts.map(function(o) {20 return Lib.extendFlat({type: 'choroplethmapbox'}, o || {});21 });22 gd.layout = layout || {};23 supplyAllDefaults(gd);24 fullData = gd._fullData;25 }26 function expectVisibleFalse(msg) {27 fullData.forEach(function(trace, i) {28 expect(trace.visible).toBe(false, 'visible |trace #' + i + '- ' + msg);29 expect(trace._length).toBe(undefined, '_length |trace #' + i + '- ' + msg);30 });31 }32 it('should set *visible:false* when locations or z or geojson is missing', function() {33 _supply([34 {z: [1], geojson: 'url'},35 {locations: ['a'], geojson: 'url'},36 {locations: ['a'], z: [1]}37 ]);38 expectVisibleFalse();39 });40 it('should set *visible:false* when locations or z is empty', function() {41 _supply([42 {locations: [], z: [1], geojson: 'url'},43 {locations: ['a'], z: [], geojson: 'url'},44 {locations: [], z: [], geojson: 'url'}45 ]);46 expectVisibleFalse();47 });48 it('should accept string (URL) and object *geojson*', function() {49 _supply([50 {name: 'ok during defaults, will fail later', locations: ['a'], z: [1], geojson: 'url'},51 {name: 'ok during defaults, will fail later', locations: ['a'], z: [1], geojson: {}},52 ]);53 fullData.forEach(function(trace, i) {54 expect(trace.visible).toBe(true, 'visible |trace #' + i);55 expect(trace._length).toBe(1, '_length |trace #' + i);56 });57 _supply([58 {name: 'no', locations: ['a'], z: [1], geojson: ''},59 {name: 'no', locations: ['a'], z: [1], geojson: []},60 {name: 'no', locations: ['a'], z: [1], geojson: true}61 ]);62 expectVisibleFalse();63 });64 it('should not coerce *marker.line.color* when *marker.line.width* is *0*', function() {65 _supply([{66 locations: ['CAN', 'USA'],67 z: [1, 2],68 geojson: 'url',69 marker: {70 line: {71 color: 'red',72 width: 073 }74 }75 }]);76 expect(fullData[0].marker.line.width).toBe(0, 'mlw');77 expect(fullData[0].marker.line.color).toBe(undefined, 'mlc');78 });79});80describe('Test choroplethmapbox convert:', function() {81 var geojson0 = function() {82 return {83 type: 'FeatureCollection',84 features: [85 {type: 'Feature', id: 'a', geometry: {type: 'Polygon', coordinates: []}},86 {type: 'Feature', id: 'b', geometry: {type: 'Polygon', coordinates: []}},87 {type: 'Feature', id: 'c', geometry: {type: 'Polygon', coordinates: []}}88 ]89 };90 };91 var base = function() {92 return {93 locations: ['a', 'b', 'c'],94 z: [10, 20, 5],95 geojson: geojson0()96 };97 };98 function pre(trace, layout) {99 var gd = {data: [Lib.extendFlat({type: 'choroplethmapbox'}, trace)]};100 if(layout) gd.layout = layout;101 supplyAllDefaults(gd);102 Plots.doCalcdata(gd, gd._fullData[0]);103 return gd.calcdata[0];104 }105 function _convert(trace) {106 return convertModule.convert(pre(trace));107 }108 function expectBlank(opts, msg) {109 expect(opts.fill.layout.visibility).toBe('none', msg);110 expect(opts.line.layout.visibility).toBe('none', msg);111 expect(opts.geojson).toEqual({type: 'Point', coordinates: []}, msg);112 }113 function extract(opts, k) {114 return opts.geojson.features.map(function(f) { return f.properties[k]; });115 }116 it('should return early when trace is *visible:false*', function() {117 var opts = _convert(Lib.extendFlat(base(), {visible: false}));118 expectBlank(opts);119 });120 it('should return early when trace is has no *_length*', function() {121 var opts = _convert({122 locations: [],123 z: [],124 geojson: geojson0125 });126 expectBlank(opts);127 });128 it('should return early if something goes wrong while fetching a GeoJSON', function() {129 spyOn(loggers, 'error');130 var opts = _convert({131 locations: ['a'], z: [1],132 geojson: 'url'133 });134 expect(loggers.error).toHaveBeenCalledWith('Oops ... something went wrong when fetching url');135 expectBlank(opts);136 });137 describe('should warn when set GeoJSON is not a *FeatureCollection* or a *Feature* type and return early', function() {138 beforeEach(function() { spyOn(loggers, 'warn'); });139 it('- case missing *type* key', function() {140 var opts = _convert({141 locations: ['a'], z: [1],142 geojson: {143 missingType: ''144 }145 });146 expectBlank(opts);147 expect(loggers.warn).toHaveBeenCalledWith([148 'Invalid GeoJSON type none.',149 'Traces with locationmode *geojson-id* only support *FeatureCollection* and *Feature* types.'150 ].join(' '));151 });152 it('- case invalid *type*', function() {153 var opts = _convert({154 locations: ['a'], z: [1],155 geojson: {156 type: 'nop!'157 }158 });159 expectBlank(opts);160 expect(loggers.warn).toHaveBeenCalledWith([161 'Invalid GeoJSON type nop!.',162 'Traces with locationmode *geojson-id* only support *FeatureCollection* and *Feature* types.'163 ].join(' '));164 });165 });166 describe('should log when crossing a GeoJSON geometry that is not a *Polygon* or a *MultiPolygon* type', function() {167 beforeEach(function() { spyOn(loggers, 'log'); });168 it('- case missing geometry *type*', function() {169 var trace = base();170 delete trace.geojson.features[1].geometry.type;171 var opts = _convert(trace);172 expect(opts.geojson.features.length).toBe(2, '# of feature to be rendered');173 expect(loggers.log).toHaveBeenCalledWith([174 'Location b does not have a valid GeoJSON geometry.',175 'Traces with locationmode *geojson-id* only support *Polygon* and *MultiPolygon* geometries.'176 ].join(' '));177 });178 it('- case invalid geometry *type*', function() {179 var trace = base();180 trace.geojson.features[2].geometry.type = 'not-gonna-work';181 var opts = _convert(trace);182 expect(opts.geojson.features.length).toBe(2, '# of feature to be rendered');183 expect(loggers.log).toHaveBeenCalledWith([184 'Location c does not have a valid GeoJSON geometry.',185 'Traces with locationmode *geojson-id* only support *Polygon* and *MultiPolygon* geometries.'186 ].join(' '));187 });188 });189 it('should log when an entry set in *locations* does not a matching feature in the GeoJSON', function() {190 spyOn(loggers, 'log');191 var trace = base();192 trace.locations = ['a', 'b', 'c', 'd'];193 trace.z = [1, 2, 3, 1];194 var opts = _convert(trace);195 expect(opts.geojson.features.length).toBe(3, '# of features to be rendered');196 expect(loggers.log).toHaveBeenCalledWith('Location *d* does not have a matching feature with id-key *id*.');197 });198 describe('should accept numbers as *locations* items', function() {199 function _assert(act) {200 expect(act.fill.layout.visibility).toBe('visible', 'fill layer visibility');201 expect(act.line.layout.visibility).toBe('visible', 'line layer visibility');202 expect(act.geojson.features.length).toBe(3, '# of visible features');203 expect(extract(act, 'fc'))204 .toEqual(['rgb(178, 10, 28)', 'rgb(220, 220, 220)', 'rgb(240, 149, 99)']);205 }206 it('- regular array case', function() {207 var trace = {208 locations: [1, 2, 3],209 z: [20, 10, 2],210 geojson: {211 type: 'FeatureCollection',212 features: [213 {type: 'Feature', id: '1', geometry: {type: 'Polygon', coordinates: []}},214 {type: 'Feature', id: '3', geometry: {type: 'Polygon', coordinates: []}},215 {type: 'Feature', id: '2', geometry: {type: 'Polygon', coordinates: []}}216 ]217 }218 };219 _assert(_convert(trace));220 });221 it('- typed array case', function() {222 var trace = {223 locations: new Float32Array([1, 2, 3]),224 z: new Float32Array([20, 10, 2]),225 geojson: {226 type: 'FeatureCollection',227 features: [228 {type: 'Feature', id: 1, geometry: {type: 'Polygon', coordinates: []}},229 {type: 'Feature', id: 3, geometry: {type: 'Polygon', coordinates: []}},230 {type: 'Feature', id: 2, geometry: {type: 'Polygon', coordinates: []}}231 ]232 }233 };234 _assert(_convert(trace));235 });236 });237 it('should handle *Feature* on 1-item *FeatureCollection* the same way', function() {238 var locations = ['a'];239 var z = [1];240 var feature = {241 type: 'Feature',242 id: 'a',243 geometry: {type: 'Polygon', coordinates: []}244 };245 var opts = _convert({246 locations: locations,247 z: z,248 geojson: feature249 });250 var opts2 = _convert({251 locations: locations,252 z: z,253 geojson: {254 type: 'FeatureCollection',255 features: [feature]256 }257 });258 expect(opts).toEqual(opts2);259 });260 it('should fill stuff in corresponding calcdata items', function() {261 var calcTrace = pre(base());262 var opts = convertModule.convert(calcTrace);263 var fullTrace = calcTrace[0].trace;264 expect(fullTrace._opts).toBe(opts, 'opts ref');265 for(var i = 0; i < calcTrace.length; i++) {266 var cdi = calcTrace[i];267 expect(typeof cdi._polygons).toBe('object', '_polygons |' + i);268 expect(Array.isArray(cdi.ct)).toBe(true, 'ct|' + i);269 expect(typeof cdi.fIn).toBe('object', 'fIn |' + i);270 expect(typeof cdi.fOut).toBe('object', 'fOut |' + i);271 }272 });273 describe('should fill *fill-color* correctly', function() {274 function _assert(act, exp) {275 expect(act.fill.paint['fill-color'])276 .toEqual({type: 'identity', property: 'fc'});277 expect(extract(act, 'fc')).toEqual(exp);278 }279 it('- base case', function() {280 _assert(_convert(base()), [281 'rgb(245, 172, 122)',282 'rgb(178, 10, 28)',283 'rgb(220, 220, 220)'284 ]);285 });286 it('- custom colorscale case', function() {287 var trace = base();288 trace.colorscale = [[0, 'rgb(0, 255, 0)'], [1, 'rgb(0, 0, 255)']];289 trace.zmid = 10;290 _assert(_convert(trace), [291 'rgb(0, 128, 128)',292 'rgb(0, 0, 255)',293 'rgb(0, 191, 64)'294 ]);295 });296 });297 describe('should fill *fill-opacity* correctly', function() {298 function _assertScalar(act, exp) {299 expect(act.fill.paint['fill-opacity']).toBe(exp);300 expect(act.line.paint['line-opacity']).toBe(exp);301 expect(extract(act, 'mo')).toEqual([undefined, undefined, undefined]);302 }303 function _assertArray(act, k, exp) {304 expect(act.fill.paint['fill-opacity']).toEqual({type: 'identity', property: k});305 expect(act.line.paint['line-opacity']).toEqual({type: 'identity', property: k});306 expect(extract(act, k)).toBeCloseToArray(exp, 2);307 }308 function fakeSelect(calcTrace, selectedpoints) {309 if(selectedpoints === null) {310 delete calcTrace[0].trace.selectedpoints;311 } else {312 calcTrace[0].trace.selectedpoints = selectedpoints;313 }314 for(var i = 0; i < calcTrace.length; i++) {315 var cdi = calcTrace[i];316 if(selectedpoints) {317 if(selectedpoints.indexOf(i) !== -1) {318 cdi.selected = 1;319 } else {320 cdi.selected = 0;321 }322 } else {323 delete cdi.selected;324 }325 }326 }327 it('- base case', function() {328 var trace = base();329 trace.marker = {opacity: 0.4};330 _assertScalar(_convert(trace), 0.4);331 });332 it('- arrayOk case', function() {333 var trace = base();334 trace.marker = {opacity: [null, 0.2, -10]};335 _assertArray(_convert(trace), 'mo', [0, 0.2, 0]);336 });337 it('- arrayOk case + bad coordinates', function() {338 var trace = base();339 trace.locations = ['a', null, 'c'];340 trace.marker = {opacity: [-1, 0.2, 0.9]};341 _assertArray(_convert(trace), 'mo', [0, 0.9]);342 });343 it('- selection (base)', function() {344 var trace = base();345 trace.selectedpoints = [1];346 var calcTrace = pre(trace);347 _assertArray(convertModule.convert(calcTrace), 'mo2', [0.2, 1, 0.2]);348 fakeSelect(calcTrace, [1, 2]);349 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.2, 1, 1]);350 fakeSelect(calcTrace, []);351 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.2, 0.2, 0.2]);352 calcTrace[0].trace.unselected = {marker: {opacity: 0}};353 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0, 0, 0]);354 fakeSelect(calcTrace, null);355 _assertScalar(convertModule.convertOnSelect(calcTrace), 1);356 });357 it('- selection of arrayOk marker.opacity', function() {358 var trace = base();359 trace.marker = {opacity: [0.4, 1, 0.8]};360 trace.selectedpoints = [1];361 var calcTrace = pre(trace);362 _assertArray(convertModule.convert(calcTrace), 'mo2', [0.08, 1, 0.16]);363 fakeSelect(calcTrace, [1, 2]);364 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.08, 1, 0.8]);365 calcTrace[0].trace.selected = {marker: {opacity: 1}};366 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.08, 1, 1]);367 fakeSelect(calcTrace, []);368 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0.08, 0.2, 0.16]);369 calcTrace[0].trace.unselected = {marker: {opacity: 0}};370 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo2', [0, 0, 0]);371 fakeSelect(calcTrace, null);372 _assertArray(convertModule.convertOnSelect(calcTrace), 'mo', [0.4, 1, 0.8]);373 });374 });375 describe('should fill *line-color*, *line-width* correctly', function() {376 it('- base case', function() {377 var trace = base();378 trace.marker = {line: {color: 'blue', width: 3}};379 var opts = _convert(trace);380 expect(opts.line.paint['line-color']).toBe('blue');381 expect(opts.line.paint['line-width']).toBe(3);382 expect(extract(opts, 'mlc')).toEqual([undefined, undefined, undefined]);383 expect(extract(opts, 'mlw')).toEqual([undefined, undefined, undefined]);384 });385 it('- arrayOk case', function() {386 var trace = base();387 trace.marker = {388 line: {389 color: ['blue', 'red', 'black'],390 width: [0.1, 2, 10]391 }392 };393 var opts = _convert(trace);394 expect(opts.line.paint['line-color']).toEqual({type: 'identity', property: 'mlc'});395 expect(opts.line.paint['line-width']).toEqual({type: 'identity', property: 'mlw'});396 expect(extract(opts, 'mlc')).toEqual(['blue', 'red', 'black']);397 expect(extract(opts, 'mlw')).toEqual([0.1, 2, 10]);398 });399 });400 it('should find correct centroid (single polygon case)', function() {401 var trace = base();402 var coordsIn = [403 [404 [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],405 [100.0, 1.0], [100.0, 0.0]406 ]407 ];408 trace.geojson.features[0].geometry.coordinates = coordsIn;409 var calcTrace = pre(trace);410 var opts = convertModule.convert(calcTrace);411 expect(opts.geojson.features[0].geometry.coordinates).toBe(coordsIn);412 expect(calcTrace[0].ct).toEqual([100.4, 0.4]);413 });414 it('should find correct centroid (multi-polygon case)', function() {415 var trace = base();416 var coordsIn = [417 [418 // this one has the bigger area419 [[30, 20], [45, 40], [10, 40], [30, 20]]420 ],421 [422 [[15, 5], [40, 10], [10, 20], [5, 10], [15, 5]]423 ]424 ];425 trace.geojson.features[0].geometry.type = 'MultiPolygon';426 trace.geojson.features[0].geometry.coordinates = coordsIn;427 var calcTrace = pre(trace);428 var opts = convertModule.convert(calcTrace);429 expect(opts.geojson.features[0].geometry.coordinates).toBe(coordsIn);430 expect(calcTrace[0].ct).toEqual([28.75, 30]);431 });432});433describe('Test choroplethmapbox hover:', function() {434 var gd;435 afterEach(function(done) {436 Plotly.purge(gd);437 destroyGraphDiv();438 setTimeout(done, 200);439 });440 function transformPlot(gd, transformString) {441 gd.style.webkitTransform = transformString;442 gd.style.MozTransform = transformString;443 gd.style.msTransform = transformString;444 gd.style.OTransform = transformString;445 gd.style.transform = transformString;446 }447 function run(hasCssTransform, s, done) {448 gd = createGraphDiv();449 var scale = 1;450 if(hasCssTransform) {451 scale = 0.5;452 }453 var fig = Lib.extendDeep({},454 s.mock || require('@mocks/mapbox_choropleth0.json')455 );456 if(s.patch) {457 fig = s.patch(fig);458 }459 if(!fig.layout) fig.layout = {};460 if(!fig.layout.mapbox) fig.layout.mapbox = {};461 fig.layout.mapbox.accesstoken = MAPBOX_ACCESS_TOKEN;462 var pos = s.pos || [270, 220];463 return Plotly.newPlot(gd, fig).then(function() {464 if(hasCssTransform) transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');465 var to = setTimeout(function() {466 failTest('no event data received');467 done();468 }, 100);469 gd.on('plotly_hover', function(d) {470 clearTimeout(to);471 assertHoverLabelContent(s);472 var msg = ' - event data ' + s.desc;473 var actual = d.points || [];474 var exp = s.evtPts;475 expect(actual.length).toBe(exp.length, 'pt length' + msg);476 for(var i = 0; i < exp.length; i++) {477 for(var k in exp[i]) {478 var m = 'key ' + k + ' in pt ' + i + msg;479 var matcher = k === 'properties' ? 'toEqual' : 'toBe';480 expect(actual[i][k])[matcher](exp[i][k], m);481 }482 }483 // w/o this purge gets called before484 // hover throttle is complete485 setTimeout(done, 0);486 });487 mouseEvent('mousemove', scale * pos[0], scale * pos[1]);488 })489 .catch(failTest);490 }491 var specs = [{492 desc: 'basic',493 nums: '10',494 name: 'NY',495 evtPts: [{location: 'NY', z: 10, pointNumber: 0, curveNumber: 0, properties: {name: 'New York'}}]496 }, {497 desc: 'with a hovertemplate using values in *properties*',498 patch: function(fig) {499 fig.data.forEach(function(t) {500 t.hovertemplate = '%{z:.3f}<extra>PROP::%{properties.name}</extra>';501 });502 return fig;503 },504 nums: '10.000',505 name: 'PROP::New York',506 evtPts: [{location: 'NY', z: 10, pointNumber: 0, curveNumber: 0, properties: {name: 'New York'}}]507 }, {508 desc: 'with "typeof number" locations[i] and feature id (in *name* label case)',509 patch: function() {510 var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));511 fig.data = [fig.data[1]];512 fig.data[0].locations = [100];513 fig.data[0].geojson.id = 100;514 return fig;515 },516 nums: '10',517 name: '100',518 evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]519 }, {520 desc: 'with "typeof number" locations[i] and feature id (in *nums* label case)',521 patch: function() {522 var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));523 fig.data = [fig.data[1]];524 fig.data[0].locations = [100];525 fig.data[0].geojson.id = 100;526 fig.data[0].hoverinfo = 'location+name';527 return fig;528 },529 nums: '100',530 name: 'trace 0',531 evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]532 }, {533 desc: 'with "typeof number" locations[i] and feature id (hovertemplate case)',534 patch: function() {535 var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));536 fig.data = [fig.data[1]];537 fig.data[0].locations = [100];538 fig.data[0].geojson.id = 100;539 fig.data[0].hovertemplate = '### %{location}<extra>%{ct[0]:.1f} | %{ct[1]:.1f} ###</extra>';540 return fig;541 },542 nums: '### 100',543 name: '-86.7 | 32.0 ###',544 evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]545 }];546 specs.forEach(function(s) {547 [false, true].forEach(function(hasCssTransform) {548 it('@gl should generate correct hover labels ' + s.desc + ', hasCssTransform: ' + hasCssTransform, function(done) {549 run(hasCssTransform, s, done);550 });551 });552 });553});554describe('Test choroplethmapbox interactions:', function() {555 var gd;556 var geojson = {557 type: 'Feature',558 id: 'AL',559 geometry: {560 type: 'Polygon',561 coordinates: [[562 [-87.359296, 35.00118 ], [-85.606675, 34.984749 ], [-85.431413, 34.124869 ], [-85.184951, 32.859696 ],563 [-85.069935, 32.580372 ], [-84.960397, 32.421541 ], [-85.004212, 32.322956 ], [-84.889196, 32.262709 ],564 [-85.058981, 32.13674 ], [-85.053504, 32.01077 ], [-85.141136, 31.840985 ], [-85.042551, 31.539753 ],565 [-85.113751, 31.27686 ], [-85.004212, 31.003013 ], [-85.497137, 30.997536 ], [-87.600282, 30.997536 ],566 [-87.633143, 30.86609 ], [-87.408589, 30.674397 ], [-87.446927, 30.510088 ], [-87.37025, 30.427934 ],567 [-87.518128, 30.280057 ], [-87.655051, 30.247195 ], [-87.90699, 30.411504 ], [-87.934375, 30.657966 ],568 [-88.011052, 30.685351 ], [-88.10416, 30.499135 ], [-88.137022, 30.318396 ], [-88.394438, 30.367688 ],569 [-88.471115, 31.895754 ], [-88.241084, 33.796253 ], [-88.098683, 34.891641 ], [-88.202745, 34.995703 ],570 [-87.359296, 35.00118 ]571 ]]572 }573 };574 beforeEach(function() {575 gd = createGraphDiv();576 });577 afterEach(function(done) {578 Plotly.purge(gd);579 destroyGraphDiv();580 setTimeout(done, 200);581 });582 it('@gl should be able to add and remove traces', function(done) {583 function _assert(msg, exp) {584 var map = gd._fullLayout.mapbox._subplot.map;585 var layers = map.getStyle().layers;586 expect(layers.length).toBe(exp.layerCnt, 'total # of layers |' + msg);587 }588 var trace0 = {589 type: 'choroplethmapbox',590 locations: ['AL'],591 z: [10],592 geojson: geojson593 };594 var trace1 = {595 type: 'choroplethmapbox',596 locations: ['AL'],597 z: [1],598 geojson: geojson,599 marker: {opacity: 0.3}600 };601 Plotly.newPlot(gd,602 [trace0, trace1],603 {mapbox: {style: 'basic'}},604 {mapboxAccessToken: MAPBOX_ACCESS_TOKEN}605 )606 .then(function() {607 _assert('base', { layerCnt: 24 });608 })609 .then(function() { return Plotly.deleteTraces(gd, [0]); })610 .then(function() {611 _assert('w/o trace0', { layerCnt: 22 });612 })613 .then(function() { return Plotly.addTraces(gd, [trace0]); })614 .then(function() {615 _assert('after adding trace0', { layerCnt: 24 });616 })617 .then(done, done.fail);618 });619 it('@gl should be able to restyle *below*', function(done) {620 function getLayerIds() {621 var subplot = gd._fullLayout.mapbox._subplot;622 var layers = subplot.map.getStyle().layers;623 var layerIds = layers.map(function(l) { return l.id; });624 return layerIds;625 }626 Plotly.newPlot(gd, [{627 type: 'choroplethmapbox',628 locations: ['AL'],629 z: [10],630 geojson: geojson,631 uid: 'a'632 }], {}, {mapboxAccessToken: MAPBOX_ACCESS_TOKEN})633 .then(function() {634 expect(getLayerIds()).withContext('default *below*').toEqual([635 'background', 'landuse_overlay_national_park', 'landuse_park',636 'waterway', 'water',637 'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line',638 'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',639 'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',640 'admin_country', 'poi_label', 'road_major_label',641 'place_label_other', 'place_label_city', 'country_label'642 ]);643 })644 .then(function() { return Plotly.restyle(gd, 'below', ''); })645 .then(function() {646 expect(getLayerIds()).withContext('*below* set to \'\'').toEqual([647 'background', 'landuse_overlay_national_park', 'landuse_park',648 'waterway', 'water',649 'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',650 'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',651 'admin_country', 'poi_label', 'road_major_label',652 'place_label_other', 'place_label_city', 'country_label',653 'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line'654 ]);655 })656 .then(function() { return Plotly.restyle(gd, 'below', 'place_label_other'); })657 .then(function() {658 expect(getLayerIds()).withContext('*below* set to same base layer').toEqual([659 'background', 'landuse_overlay_national_park', 'landuse_park',660 'waterway', 'water',661 'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',662 'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',663 'admin_country', 'poi_label', 'road_major_label',664 'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line',665 'place_label_other', 'place_label_city', 'country_label',666 ]);667 })668 .then(function() { return Plotly.restyle(gd, 'below', null); })669 .then(function() {670 expect(getLayerIds()).withContext('back to default *below*').toEqual([671 'background', 'landuse_overlay_national_park', 'landuse_park',672 'waterway', 'water',673 'plotly-trace-layer-a-fill', 'plotly-trace-layer-a-line',674 'building', 'tunnel_minor', 'tunnel_major', 'road_minor', 'road_major',675 'bridge_minor case', 'bridge_major case', 'bridge_minor', 'bridge_major',676 'admin_country', 'poi_label', 'road_major_label',677 'place_label_other', 'place_label_city', 'country_label'678 ]);679 })680 .then(done, done.fail);681 }, 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL);...
choropleth_test.js
Source:choropleth_test.js
1var Choropleth = require('@src/traces/choropleth');2var Plotly = require('@lib/index');3var Plots = require('@src/plots/plots');4var Lib = require('@src/lib');5var loggers = require('@src/lib/loggers');6var d3Select = require('../../strict-d3').select;7var d3SelectAll = require('../../strict-d3').selectAll;8var createGraphDiv = require('../assets/create_graph_div');9var destroyGraphDiv = require('../assets/destroy_graph_div');10var mouseEvent = require('../assets/mouse_event');11var customAssertions = require('../assets/custom_assertions');12var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;13var assertHoverLabelContent = customAssertions.assertHoverLabelContent;14describe('Test choropleth', function() {15 'use strict';16 describe('supplyDefaults', function() {17 var traceIn;18 var traceOut;19 var defaultColor = '#444';20 var layout = {21 font: Plots.layoutAttributes.font,22 _dfltTitle: {colorbar: 'cb'}23 };24 beforeEach(function() {25 traceOut = {};26 });27 it('should set _length based on locations and z but not slice', function() {28 traceIn = {29 locations: ['CAN', 'USA'],30 z: [1, 2, 3]31 };32 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);33 expect(traceOut.z).toEqual([1, 2, 3]);34 expect(traceOut.locations).toEqual(['CAN', 'USA']);35 expect(traceOut._length).toBe(2);36 traceIn = {37 locations: ['CAN', 'USA', 'ALB'],38 z: [1, 2]39 };40 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);41 expect(traceOut.z).toEqual([1, 2]);42 expect(traceOut.locations).toEqual(['CAN', 'USA', 'ALB']);43 expect(traceOut._length).toBe(2);44 });45 it('should make trace invisible if locations is not defined', function() {46 traceIn = {47 z: [1, 2, 3]48 };49 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);50 expect(traceOut.visible).toBe(false);51 });52 it('should make trace invisible if z is not an array', function() {53 traceIn = {54 locations: ['CAN', 'USA'],55 z: 'no gonna work'56 };57 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);58 expect(traceOut.visible).toBe(false);59 });60 it('should not coerce *marker.line.color* when *marker.line.width* is *0*', function() {61 traceIn = {62 locations: ['CAN', 'USA'],63 z: [1, 2],64 marker: {65 line: {66 color: 'red',67 width: 068 }69 }70 };71 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);72 expect(traceOut.marker.line.width).toBe(0, 'mlw');73 expect(traceOut.marker.line.color).toBe(undefined, 'mlc');74 });75 it('should default locationmode to *geojson-id* when a valid *geojson* is provided', function() {76 traceIn = {77 locations: ['CAN', 'USA'],78 z: [1, 2],79 geojson: 'url'80 };81 traceOut = {};82 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);83 expect(traceOut.locationmode).toBe('geojson-id', 'valid url string');84 traceIn = {85 locations: ['CAN', 'USA'],86 z: [1, 2],87 geojson: {}88 };89 traceOut = {};90 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);91 expect(traceOut.locationmode).toBe('geojson-id', 'valid object');92 traceIn = {93 locations: ['CAN', 'USA'],94 z: [1, 2],95 geojson: ''96 };97 traceOut = {};98 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);99 expect(traceOut.locationmode).toBe('ISO-3', 'invalid sting');100 traceIn = {101 locations: ['CAN', 'USA'],102 z: [1, 2],103 geojson: []104 };105 traceOut = {};106 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);107 expect(traceOut.locationmode).toBe('ISO-3', 'invalid object');108 });109 it('should only coerce *featureidkey* when locationmode is *geojson-id', function() {110 traceIn = {111 locations: ['CAN', 'USA'],112 z: [1, 2],113 geojson: 'url',114 featureidkey: 'properties.name'115 };116 traceOut = {};117 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);118 expect(traceOut.featureidkey).toBe('properties.name', 'coerced');119 traceIn = {120 locations: ['CAN', 'USA'],121 z: [1, 2],122 featureidkey: 'properties.name'123 };124 traceOut = {};125 Choropleth.supplyDefaults(traceIn, traceOut, defaultColor, layout);126 expect(traceOut.featureidkey).toBe(undefined, 'NOT coerced');127 });128 });129});130describe('Test choropleth hover:', function() {131 var gd;132 afterEach(destroyGraphDiv);133 function transformPlot(gd, transformString) {134 gd.style.webkitTransform = transformString;135 gd.style.MozTransform = transformString;136 gd.style.msTransform = transformString;137 gd.style.OTransform = transformString;138 gd.style.transform = transformString;139 }140 function run(hasCssTransform, pos, fig, content, style) {141 gd = createGraphDiv();142 var scale = 1;143 style = style || {144 bgcolor: 'rgb(68, 68, 68)',145 bordercolor: 'rgb(255, 255, 255)',146 fontColor: 'rgb(255, 255, 255)',147 fontSize: 13,148 fontFamily: 'Arial'149 };150 return Plotly.newPlot(gd, fig)151 .then(function() {152 if(hasCssTransform) {153 scale = 0.5;154 transformPlot(gd, 'translate(-25%, -25%) scale(0.5)');155 }156 mouseEvent('mousemove', scale * pos[0], scale * pos[1]);157 assertHoverLabelContent({158 nums: content[0],159 name: content[1]160 });161 assertHoverLabelStyle(162 d3Select('g.hovertext'),163 style164 );165 });166 }167 [false, true].forEach(function(hasCssTransform) {168 it('should generate hover label info (base), hasCssTransform: ' + hasCssTransform, function(done) {169 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));170 run(171 hasCssTransform,172 [400, 160],173 fig,174 ['RUS\n10', 'trace 1']175 )176 .then(done, done.fail);177 });178 });179 [false, true].forEach(function(hasCssTransform) {180 it('should use the hovertemplate, hasCssTransform: ' + hasCssTransform, function(done) {181 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));182 fig.data[1].hovertemplate = 'tpl %{z}<extra>x</extra>';183 run(184 hasCssTransform,185 [400, 160],186 fig,187 ['tpl 10', 'x']188 )189 .then(done, done.fail);190 });191 });192 [false, true].forEach(function(hasCssTransform) {193 it('should generate hover label info (\'text\' single value case), hasCssTransform: ' + hasCssTransform, function(done) {194 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));195 fig.data[1].text = 'tExT';196 fig.data[1].hoverinfo = 'text';197 run(198 hasCssTransform,199 [400, 160],200 fig,201 ['tExT', null]202 )203 .then(done, done.fail);204 });205 });206 [false, true].forEach(function(hasCssTransform) {207 it('should generate hover label info (\'text\' array case), hasCssTransform: ' + hasCssTransform, function(done) {208 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));209 fig.data[1].text = ['tExT', 'TeXt', '-text-'];210 fig.data[1].hoverinfo = 'text';211 run(212 hasCssTransform,213 [400, 160],214 fig,215 ['-text-', null]216 )217 .then(done, done.fail);218 });219 });220 [false, true].forEach(function(hasCssTransform) {221 it('should generate hover labels from `hovertext`, hasCssTransform: ' + hasCssTransform, function(done) {222 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));223 fig.data[1].hovertext = ['tExT', 'TeXt', '-text-'];224 fig.data[1].text = ['N', 'O', 'P'];225 fig.data[1].hoverinfo = 'text';226 run(227 hasCssTransform,228 [400, 160],229 fig,230 ['-text-', null]231 )232 .then(done, done.fail);233 });234 });235 [false, true].forEach(function(hasCssTransform) {236 it('should generate hover label with custom styling, hasCssTransform: ' + hasCssTransform, function(done) {237 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));238 fig.data[1].hoverlabel = {239 bgcolor: 'red',240 bordercolor: ['blue', 'black', 'green'],241 font: {family: 'Roboto'}242 };243 run(244 hasCssTransform,245 [400, 160],246 fig,247 ['RUS\n10', 'trace 1'],248 {249 bgcolor: 'rgb(255, 0, 0)',250 bordercolor: 'rgb(0, 128, 0)',251 fontColor: 'rgb(0, 128, 0)',252 fontSize: 13,253 fontFamily: 'Roboto'254 }255 )256 .then(done, done.fail);257 });258 });259 [false, true].forEach(function(hasCssTransform) {260 it('should generate hover label with arrayOk \'hoverinfo\' settings, hasCssTransform: ' + hasCssTransform, function(done) {261 var fig = Lib.extendDeep({}, require('@mocks/geo_first.json'));262 fig.data[1].hoverinfo = ['location', 'z', 'location+name'];263 run(264 hasCssTransform,265 [400, 160],266 fig,267 ['RUS', 'trace 1']268 )269 .then(done, done.fail);270 });271 });272 describe('should preserve z formatting hovetemplate equivalence', function() {273 var base = function() {274 return {275 data: [{276 type: 'choropleth',277 locations: ['RUS'],278 z: [10.021321321432143]279 }]280 };281 };282 var pos = [400, 160];283 var exp = ['10.02132', 'RUS'];284 [false, true].forEach(function(hasCssTransform) {285 it('- base case (truncate z decimals), hasCssTransform: ' + hasCssTransform, function(done) {286 run(hasCssTransform, pos, base(), exp)287 .then(done, done.fail);288 });289 });290 [false, true].forEach(function(hasCssTransform) {291 it('- hovertemplate case (same z truncation), hasCssTransform: ' + hasCssTransform, function(done) {292 var fig = base();293 fig.hovertemplate = '%{z}<extra>%{location}</extra>';294 run(hasCssTransform, pos, fig, exp)295 .then(done, done.fail);296 });297 });298 });299 [false, true].forEach(function(hasCssTransform) {300 it('should include *properties* from input custom geojson, hasCssTransform: ' + hasCssTransform, function(done) {301 var fig = Lib.extendDeep({}, require('@mocks/geo_custom-geojson.json'));302 fig.data = [fig.data[1]];303 fig.data[0].hovertemplate = '%{properties.name}<extra>%{ct[0]:.1f} | %{ct[1]:.1f}</extra>';304 fig.layout.geo.projection = {scale: 20};305 run(hasCssTransform, [300, 200], fig, ['New York', '-75.1 | 42.6'])306 .then(done, done.fail);307 });308 });309});310describe('choropleth drawing', function() {311 var gd;312 beforeEach(function() {313 gd = createGraphDiv();314 });315 afterEach(destroyGraphDiv);316 it('should not throw an error with bad locations', function(done) {317 spyOn(loggers, 'log');318 Plotly.newPlot(gd, [{319 locations: ['canada', 0, null, '', 'utopia'],320 z: [1, 2, 3, 4, 5],321 locationmode: 'country names',322 type: 'choropleth'323 }])324 .then(function() {325 // only utopia logs - others are silently ignored326 expect(loggers.log).toHaveBeenCalledTimes(1);327 })328 .then(done, done.fail);329 });330 it('preserves order after hide/show', function(done) {331 function getIndices() {332 var out = [];333 d3SelectAll('.choropleth').each(function(d) { out.push(d[0].trace.index); });334 return out;335 }336 Plotly.newPlot(gd, [{337 type: 'choropleth',338 locations: ['CAN', 'USA'],339 z: [1, 2]340 }, {341 type: 'choropleth',342 locations: ['CAN', 'USA'],343 z: [2, 1]344 }])345 .then(function() {346 expect(getIndices()).toEqual([0, 1]);347 return Plotly.restyle(gd, 'visible', false, [0]);348 })349 .then(function() {350 expect(getIndices()).toEqual([1]);351 return Plotly.restyle(gd, 'visible', true, [0]);352 })353 .then(function() {354 expect(getIndices()).toEqual([0, 1]);355 })356 .then(done, done.fail);357 });...
TransitionGroup.js
Source:TransitionGroup.js
...36 if (!prevChildren.length) {37 return38 }39 const moveClass = props.moveClass || `${props.name || 'v'}-move`40 if (!hasCSSTransform(prevChildren[0].el, instance.vnode.el, moveClass)) {41 return42 }43 // we divide the work into three loops to avoid mixing DOM reads and writes44 // in each iteration - which helps prevent layout thrashing.45 prevChildren.forEach(callPendingCbs)46 prevChildren.forEach(recordPosition)47 const movedChildren = prevChildren.filter(applyTranslation)48 // force reflow to put everything in position49 forceReflow()50 movedChildren.forEach(c => {51 const el = c.el52 const style = el.style53 addTransitionClass(el, moveClass)54 style.transform = style.webkitTransform = style.transitionDuration = ''...
Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/chromium/crPage');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const hasTransform = await hasCSSTransform(page, 'body', 'translate(0px, 0px)');8 console.log(`Has transform: ${hasTransform}`);9 await browser.close();10})();11const { hasCSSProperty } = require('playwright/lib/server/chromium/crPage');12const { chromium } = require('playwright');13(async () => {14 const browser = await chromium.launch();15 const context = await browser.newContext();16 const page = await context.newPage();17 const hasTransform = await hasCSSProperty(page, 'body', 'transform', 'translate(0px, 0px)');18 console.log(`Has transform: ${hasTransform}`);19 await browser.close();20})();21const { hasCSSProperty } = require('playwright/lib/server/chromium/crPage');22const { chromium } = require('playwright');23(async () => {24 const browser = await chromium.launch();25 const context = await browser.newContext();26 const page = await context.newPage();27 const hasTransform = await hasCSSProperty(page, 'body', 'transform', 'translate(0px, 0px)');28 console.log(`Has transform: ${hasTransform}`);29 await browser.close();30})();31const { hasCSSProperty } = require('playwright/lib/server/chromium/crPage');32const { chromium } = require('playwright');33(async () => {34 const browser = await chromium.launch();35 const context = await browser.newContext();
Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/chromium/crPage');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 await page.waitForSelector('text="Get started"');8 const element = await page.$('text="Get started"');9 const result = await hasCSSTransform(element);10 console.log(result);11 await browser.close();12})();13 at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/webkit/wkConnection.js:41:23)14 at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/chromium/crConnection.js:65:39)15 at ExecutionContext._evaluateInternal (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/ExecutionContext.js:132:48)16 at ExecutionContext.evaluateHandle (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/ExecutionContext.js:69:17)17 at ExecutionContext.evaluate (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/ExecutionContext.js:56:31)18 at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/webkit/wkConnection.js:41:23)19 at CDPSession.send (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/chromium/crConnection.js:65:39)20 at Page._evaluateInternal (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/Page.js:171:48)21 at Page.evaluateHandle (/home/raghav/Desktop/playwright/node_modules/playwright/lib/server/cjs/common/Page.js:108:17)
Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/dom.js');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const element = await page.$('#iframeResult');8 const frame = await element.contentFrame();9 const div = await frame.$('div');10 const result = await hasCSSTransform(div, 'rotateX(50deg)');11 console.log(result);12 await browser.close();13})();
Using AI Code Generation
1const { hasCSSTransform } = require('playwright-core/lib/server/dom.js');2const { chromium } = require('playwright-core');3(async () => {4 const browser = await chromium.launch();5 const page = await browser.newPage();6 const element = await page.$('text=Learn more');7 const result = await hasCSSTransform(element);8 console.log(result);9 await browser.close();10})();
Using AI Code Generation
1const { hasCSSTransform } = require('playwright-core/lib/server/dom.js');2const { chromium } = require('playwright-core');3(async () => {4 const browser = await chromium.launch();5 const page = await browser.newPage();6 const element = await page.$('input[title="Search"]');7 const result = await hasCSSTransform(element);8 console.log(result);9 await browser.close();10})();
Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2const assert = require('assert');3(async () => {4 const browser = await chromium.launch();5 const page = await browser.newPage();6 const hasTransform = await hasCSSTransform(page, 'input[name="q"]');7 assert.equal(hasTransform, true);8 await browser.close();9})();
Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2(async () => {3 const browser = await chromium.launch({headless: false});4 const page = await browser.newPage();5 const element = await page.$('input[name="q"]');6 console.log(await hasCSSTransform(page, element));7 await browser.close();8})();9const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');10(async () => {11 const browser = await chromium.launch({headless: false});12 const page = await browser.newPage();13 const element = await page.$('input[name="q"]');14 await element.evaluate(element => element.style.transform = 'none');15 console.log(await hasCSSTransform(page, element));16 await browser.close();17})();18const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/recorderSupplement');19(async () => {20 const browser = await chromium.launch({headless: false});21 const page = await browser.newPage();22 const element = await page.$('input[name="q"]');23 await element.evaluate(element => element.style.transform = 'rotate(20deg)');24 console.log(await hasCSSTransform(page, element));25 await browser.close();26})();27const { hasCSSTransform } = require('playwright/lib/server/supplements/recorder/rec
Using AI Code Generation
1const { InternalUtils } = require('playwright/lib/utils/internal-utils');2const { hasCSSTransform } = InternalUtils;3const { chromium } = require('playwright');4(async () => {5 const browser = await chromium.launch();6 const context = await browser.newContext();7 const page = await context.newPage();8 const element = await page.$('input');9 console.log(hasCSSTransform(element));10 await browser.close();11})();12const { InternalUtils } = require('playwright/lib/utils/internal-utils');13const { hasCSSTransform } = InternalUtils;14const { chromium } = require('playwright');15(async () => {16 const browser = await chromium.launch();17 const context = await browser.newContext();18 const page = await context.newPage();19 const element = await page.$('input');20 console.log(hasCSSTransform(element));21 await browser.close();22})();23const { InternalUtils } = require('playwright/lib/utils/internal-utils');24const { hasCSSTransform } = InternalUtils;25const { chromium } = require('playwright');26(async () => {27 const browser = await chromium.launch();28 const context = await browser.newContext();29 const page = await context.newPage();30 const element = await page.$('input');31 console.log(hasCSSTransform(element));32 await browser.close();33})();34const { InternalUtils } = require('playwright/lib/utils/internal-utils');35const { hasCSSTransform } = InternalUtils;36const { chromium } = require('playwright');37(async () => {38 const browser = await chromium.launch();39 const context = await browser.newContext();40 const page = await context.newPage();41 const element = await page.$('input');42 console.log(hasCSSTransform(element));43 await browser.close();44})();
Using AI Code Generation
1const { hasCSSTransform } = require('playwright/lib/server/dom.js');2const page = await browser.newPage();3const element = await page.$('input[name="q"]');4const supportsCSSTransforms = await hasCSSTransform(element);5console.log(supportsCSSTransforms);6const { BrowserContext } = require('playwright/lib/server/browserContext.js');7const page = await browser.newPage();8const browserName = await page.context().browserName();9const browserVersion = await page.context().browserVersion();10console.log(browserName);11console.log(browserVersion);12const { hasCSSTransform } = require('playwright/lib/server/dom.js');13const page = await browser.newPage();14const element = await page.$('input[name="q"]');15const supportsCSSTransforms = await hasCSSTransform(element);16console.log(supportsCSSTransforms);
Using AI Code Generation
1const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');2const hasCSSTransform = hasCSSTransform();3const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');4const hasCSSTransform = hasCSSTransform();5const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');6const hasCSSTransform = hasCSSTransform();7const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');8const hasCSSTransform = hasCSSTransform();9const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');10const hasCSSTransform = hasCSSTransform();11const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');12const hasCSSTransform = hasCSSTransform();13const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');14const hasCSSTransform = hasCSSTransform();15const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');16const hasCSSTransform = hasCSSTransform();17const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');18const hasCSSTransform = hasCSSTransform();19const { hasCSSTransform } = require('@playwright/test/lib/server/domSnapshot');20const hasCSSTransform = hasCSSTransform();21const { hasCSSTransform } = require('@playwright/test/lib/server/dom
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!