Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parallel Categories (parcats) trace type for multi dimensional categorical data #2963

Merged
merged 51 commits into from
Oct 1, 2018
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1a8a0f0
Initial parcats trace implementation
Nov 27, 2017
406cd24
color attribute fixes for rebase on 1.39.3
jonmmease Jul 27, 2018
ee7fd17
lint fixes
jonmmease Jul 27, 2018
3e43ef3
Remove customHovers, replace with loneHover
jonmmease Aug 7, 2018
0c9c75f
Bring back customHovers
jonmmease Aug 7, 2018
7b75100
Renamed `parcats.marker` -> `parcats.line`
jonmmease Aug 7, 2018
d2c5ae8
Use plots/domain and handleDomainDefaults
jonmmease Aug 8, 2018
a34dafa
Rename displayInd -> displayindex
jonmmease Aug 8, 2018
71e212b
Convert to simplified colorbar logic
jonmmease Aug 8, 2018
6797a83
Remove maxDimensionCount check
jonmmease Aug 9, 2018
2c14168
Cleanup supplyDefaults and add visible dimension property
jonmmease Aug 9, 2018
c8e3cc9
Added support for dimensions with visible=false
jonmmease Aug 10, 2018
6a5c20e
Fixed failing test (needed to rename displayInd -> displayindex)
jonmmease Aug 10, 2018
22346d0
Added mock with color hovermode
jonmmease Aug 10, 2018
3680084
Replace tooltip with hoverinfo
Aug 15, 2018
2d07f4d
Added `arrangement` property that is very similar to the sankey trace
Aug 15, 2018
7f90fc1
WIP towards categoryorder/categoryarray/categorylabels
Aug 16, 2018
5e60062
Full support for categoryorder, categoryarray, and categorylabels
Aug 17, 2018
66c90fa
Fixed tests to use new `categoryorder`, `categoryarray`, `categorylab…
Aug 20, 2018
69f6922
Add 'dimension' hovermode that uses multi-hoverlabel logic
Aug 20, 2018
f2aa9b9
Added labelfont and categorylabelfont top-level attributes
Aug 21, 2018
4eb5317
Review / cleanup attribute descriptions
Aug 21, 2018
a897388
Add `counts` attribute to parcats_hovermode_dimension mock
Aug 21, 2018
98d76ee
Refactor dimension and category dragging tests and test arrangements
Aug 23, 2018
66f21fe
Add tests for clicking on category and path with/without hoverinfo skip
Aug 23, 2018
30ca2ec
Add tests for clicking on category and path with hovermode 'color'
Sep 1, 2018
69311fc
Implement plotly_unhover events
Sep 1, 2018
b3b73a6
Add tests for hovering on category and paths
Sep 1, 2018
fed71e7
eslint fixes
Sep 1, 2018
9361cc4
Fix up attribute description specifications
Sep 1, 2018
6ca2d66
Fix test comments
Sep 1, 2018
2a51ca9
Added new baseline images for parcats mocks
Sep 4, 2018
948bf0a
Fix headers (it *was* 2017 when I started this!)
Sep 4, 2018
b1ef6c9
markerColorscale no longer used
Sep 5, 2018
5a19499
Rename categorylabels -> ticktext and categorylabelfont -> tickfont
Sep 5, 2018
1f6a2e0
Update baseline image for parcats_reordered (Added "One"/"Two" labels)
Sep 7, 2018
340ae53
eslint fixes
Sep 7, 2018
9b66bee
Support Rich text / Latex in category labels (ticktext)
Sep 10, 2018
870ff82
eslint fixes
Sep 11, 2018
7e4a6ad
Handle line.color as TypedArray
Sep 12, 2018
e7edebd
hovermode -> hoveron
Sep 13, 2018
20bc21b
Baseline images for hovermode -> hoveron conversion
Sep 13, 2018
273d2d0
Hide unsupported top-level properties from plot-schema
Sep 24, 2018
e8ded39
Merge remote-tracking branch 'upstream/master' into parcats
Sep 24, 2018
4bc3d35
Fix plotschema tests in the presence of potentially undefined schema …
Sep 24, 2018
2684d69
Fix path tooltip direction when graph div is offset from left margin
Sep 25, 2018
267c750
Increase default font size slightly
Sep 25, 2018
2100d96
Remove redundant require('./hover') operations
Sep 28, 2018
eba1b4a
Cleanup line defaults logic
Sep 28, 2018
4117612
Create `parcats_grid_subplots` mock with 4 grid-based subplots
Sep 28, 2018
99f9a47
Use layout.colorway[0] as the default color for parcats traces
jonmmease Sep 28, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Plotly.register([
require('./pointcloud'),
require('./heatmapgl'),
require('./parcoords'),

require('./parcats'),
require('./scattermapbox'),

require('./sankey'),
Expand Down
11 changes: 11 additions & 0 deletions lib/parcats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = require('../src/traces/parcats');
75 changes: 75 additions & 0 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,81 @@ exports.loneHover = function loneHover(hoverItem, opts) {
return hoverLabel.node();
};

exports.multiHovers = function multiHovers(hoverItems, opts) {

if(!Array.isArray(hoverItems)) {
hoverItems = [hoverItems];
}

var pointsData = hoverItems.map(function(hoverItem) {
return {
color: hoverItem.color || Color.defaultLine,
x0: hoverItem.x0 || hoverItem.x || 0,
x1: hoverItem.x1 || hoverItem.x || 0,
y0: hoverItem.y0 || hoverItem.y || 0,
y1: hoverItem.y1 || hoverItem.y || 0,
xLabel: hoverItem.xLabel,
yLabel: hoverItem.yLabel,
zLabel: hoverItem.zLabel,
text: hoverItem.text,
name: hoverItem.name,
idealAlign: hoverItem.idealAlign,

// optional extra bits of styling
borderColor: hoverItem.borderColor,
fontFamily: hoverItem.fontFamily,
fontSize: hoverItem.fontSize,
fontColor: hoverItem.fontColor,

// filler to make createHoverText happy
trace: {
index: 0,
hoverinfo: ''
},
xa: {_offset: 0},
ya: {_offset: 0},
index: 0
};
});


var container3 = d3.select(opts.container),
outerContainer3 = opts.outerContainer ?
d3.select(opts.outerContainer) : container3;

var fullOpts = {
hovermode: 'closest',
rotateLabels: false,
bgColor: opts.bgColor || Color.background,
container: container3,
outerContainer: outerContainer3
};

var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd);

// Fix vertical overlap
var tooltipSpacing = 5;
var lastBottomY = 0;
hoverLabel
.sort(function(a, b) {return a.y0 - b.y0;})
.each(function(d) {
var topY = d.y0 - d.by / 2;

if((topY - tooltipSpacing) < lastBottomY) {
d.offset = (lastBottomY - topY) + tooltipSpacing;
} else {
d.offset = 0;
}

lastBottomY = topY + d.by + d.offset;
});


alignHoverText(hoverLabel, fullOpts.rotateLabels);

return hoverLabel.node();
};

// The actual implementation is here:
function _hover(gd, evt, subplot, noHoverEvent) {
if(!subplot) subplot = 'xy';
Expand Down
1 change: 1 addition & 0 deletions src/components/fx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module.exports = {
unhover: dragElement.unhover,

loneHover: require('./hover').loneHover,
multiHovers: require('./hover').multiHovers,
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
loneUnhover: loneUnhover,

click: require('./click')
Expand Down
202 changes: 202 additions & 0 deletions src/traces/parcats/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var extendFlat = require('../../lib/extend').extendFlat;
var plotAttrs = require('../../plots/attributes');
var fontAttrs = require('../../plots/font_attributes');
var colorAttributes = require('../../components/colorscale/attributes');
var domainAttrs = require('../../plots/domain').attributes;
var scatterAttrs = require('../scatter/attributes');
var scatterLineAttrs = scatterAttrs.line;
var colorbarAttrs = require('../../components/colorbar/attributes');

var line = extendFlat({
editType: 'calc'
}, colorAttributes('line', {editType: 'calc'}),
{
showscale: scatterLineAttrs.showscale,
colorbar: colorbarAttrs,
shape: {
valType: 'enumerated',
values: ['linear', 'hspline'],
dflt: 'linear',
role: 'info',
editType: 'plot',
description: [
'Sets the shape of the paths.',
'If `linear`, paths are composed of straight lines.',
'If `hspline`, paths are composed of horizontal curved splines'
].join(' ')
}
});

module.exports = {
domain: domainAttrs({name: 'parcats', trace: true, editType: 'calc'}),
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
flags: ['count', 'probability'],
editType: 'plot'
// plotAttrs.hoverinfo description is appropriate
}),
hoveron: {
valType: 'enumerated',
values: ['category', 'color', 'dimension'],
dflt: 'category',
role: 'info',
editType: 'plot',
description: [
'Sets the hover interaction mode for the parcats diagram.',
'If `category`, hover interaction take place per category.',
'If `color`, hover interactions take place per color per category.',
'If `dimension`, hover interactions take place across all categories per dimension.'
].join(' ')
},
arrangement: {
valType: 'enumerated',
values: ['perpendicular', 'freeform', 'fixed'],
dflt: 'perpendicular',
role: 'style',
editType: 'plot',
description: [
'Sets the drag interaction mode for categories and dimensions.',
'If `perpendicular`, the categories can only move along a line perpendicular to the paths.',
'If `freeform`, the categories can freely move on the plane.',
'If `fixed`, the categories and dimensions are stationary.'
].join(' ')
},
bundlecolors: {
valType: 'boolean',
dflt: true,
role: 'info',
editType: 'plot',
description: 'Sort paths so that like colors are bundled together within each category.'
},
sortpaths: {
valType: 'enumerated',
values: ['forward', 'backward'],
dflt: 'forward',
role: 'info',
editType: 'plot',
description: [
'Sets the path sorting algorithm.',
'If `forward`, sort paths based on dimension categories from left to right.',
'If `backward`, sort paths based on dimensions categories from right to left.'
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
].join(' ')
},
labelfont: fontAttrs({
editType: 'calc',
description: 'Sets the font for the `dimension` labels.'
}),

tickfont: fontAttrs({
editType: 'calc',
description: 'Sets the font for the `category` labels.'
}),

dimensions: {
_isLinkedToArray: 'dimension',
label: {
valType: 'string',
role: 'info',
editType: 'calc',
description: 'The shown name of the dimension.'
},
categoryorder: {
valType: 'enumerated',
values: [
'trace', 'category ascending', 'category descending', 'array'
],
dflt: 'trace',
role: 'info',
editType: 'calc',
description: [
'Specifies the ordering logic for the categories in the dimension.',
'By default, plotly uses *trace*, which specifies the order that is present in the data supplied.',
'Set `categoryorder` to *category ascending* or *category descending* if order should be determined by',
'the alphanumerical order of the category names.',
'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category',
'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to',
'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.'
].join(' ')
},
categoryarray: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets the order in which categories in this dimension appear.',
'Only has an effect if `categoryorder` is set to *array*.',
'Used with `categoryorder`.'
].join(' ')
},
ticktext: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets alternative tick labels for the categories in this dimension.',
'Only has an effect if `categoryorder` is set to *array*.',
'Should be an array the same length as `categoryarray`',
'Used with `categoryorder`.'
].join(' ')
},
values: {
valType: 'data_array',
role: 'info',
dflt: [],
editType: 'calc',
description: [
'Dimension values. `values[n]` represents the category value of the `n`th point in the dataset,',
'therefore the `values` vector for all dimensions must be the same (longer vectors',
'will be truncated).'
].join(' ')
},
displayindex: {
valType: 'integer',
role: 'info',
editType: 'calc',
description: [
'The display index of dimension, from left to right, zero indexed, defaults to dimension',
'index.'
].join(' ')
},
editType: 'calc',
description: 'The dimensions (variables) of the parallel categories diagram.',
visible: {
valType: 'boolean',
dflt: true,
role: 'info',
editType: 'calc',
description: 'Shows the dimension when set to `true` (the default). Hides the dimension for `false`.'
}
},

line: line,
counts: {
valType: 'number',
min: 0,
dflt: 1,
arrayOk: true,
role: 'info',
editType: 'calc',
description: [
'The number of observations represented by each state. Defaults to 1 so that each state represents',
'one observation'
].join(' ')
},

// Hide unsupported top-level properties from plot-schema
customdata: undefined,
hoverlabel: undefined,
ids: undefined,
legendgroup: undefined,
opacity: undefined,
selectedpoints: undefined,
showlegend: undefined
};
34 changes: 34 additions & 0 deletions src/traces/parcats/base_plot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var getModuleCalcData = require('../../plots/get_data').getModuleCalcData;
var parcatsPlot = require('./plot');

var PARCATS = 'parcats';
exports.name = PARCATS;

exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {

var cdModuleAndOthers = getModuleCalcData(gd.calcdata, PARCATS);

if(cdModuleAndOthers.length) {
var calcData = cdModuleAndOthers[0];
parcatsPlot(gd, calcData, transitionOpts, makeOnCompleteCallback);
}
};

exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var hadTable = (oldFullLayout._has && oldFullLayout._has('parcats'));
var hasTable = (newFullLayout._has && newFullLayout._has('parcats'));

if(hadTable && !hasTable) {
oldFullLayout._paperdiv.selectAll('.parcats').remove();
}
};
Loading