Skip to content

Commit cf2fdc4

Browse files
authored
Merge pull request #2963 from jonmmease/parcats
Parallel Categories (parcats) trace type for multi dimensional categorical data
2 parents 5c62c8e + 99f9a47 commit cf2fdc4

33 files changed

+5098
-6
lines changed

Diff for: lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Plotly.register([
3838
require('./pointcloud'),
3939
require('./heatmapgl'),
4040
require('./parcoords'),
41-
41+
require('./parcats'),
4242
require('./scattermapbox'),
4343

4444
require('./sankey'),

Diff for: lib/parcats.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = require('../src/traces/parcats');

Diff for: src/components/fx/hover.js

+75
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,81 @@ exports.loneHover = function loneHover(hoverItem, opts) {
153153
return hoverLabel.node();
154154
};
155155

156+
exports.multiHovers = function multiHovers(hoverItems, opts) {
157+
158+
if(!Array.isArray(hoverItems)) {
159+
hoverItems = [hoverItems];
160+
}
161+
162+
var pointsData = hoverItems.map(function(hoverItem) {
163+
return {
164+
color: hoverItem.color || Color.defaultLine,
165+
x0: hoverItem.x0 || hoverItem.x || 0,
166+
x1: hoverItem.x1 || hoverItem.x || 0,
167+
y0: hoverItem.y0 || hoverItem.y || 0,
168+
y1: hoverItem.y1 || hoverItem.y || 0,
169+
xLabel: hoverItem.xLabel,
170+
yLabel: hoverItem.yLabel,
171+
zLabel: hoverItem.zLabel,
172+
text: hoverItem.text,
173+
name: hoverItem.name,
174+
idealAlign: hoverItem.idealAlign,
175+
176+
// optional extra bits of styling
177+
borderColor: hoverItem.borderColor,
178+
fontFamily: hoverItem.fontFamily,
179+
fontSize: hoverItem.fontSize,
180+
fontColor: hoverItem.fontColor,
181+
182+
// filler to make createHoverText happy
183+
trace: {
184+
index: 0,
185+
hoverinfo: ''
186+
},
187+
xa: {_offset: 0},
188+
ya: {_offset: 0},
189+
index: 0
190+
};
191+
});
192+
193+
194+
var container3 = d3.select(opts.container),
195+
outerContainer3 = opts.outerContainer ?
196+
d3.select(opts.outerContainer) : container3;
197+
198+
var fullOpts = {
199+
hovermode: 'closest',
200+
rotateLabels: false,
201+
bgColor: opts.bgColor || Color.background,
202+
container: container3,
203+
outerContainer: outerContainer3
204+
};
205+
206+
var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd);
207+
208+
// Fix vertical overlap
209+
var tooltipSpacing = 5;
210+
var lastBottomY = 0;
211+
hoverLabel
212+
.sort(function(a, b) {return a.y0 - b.y0;})
213+
.each(function(d) {
214+
var topY = d.y0 - d.by / 2;
215+
216+
if((topY - tooltipSpacing) < lastBottomY) {
217+
d.offset = (lastBottomY - topY) + tooltipSpacing;
218+
} else {
219+
d.offset = 0;
220+
}
221+
222+
lastBottomY = topY + d.by + d.offset;
223+
});
224+
225+
226+
alignHoverText(hoverLabel, fullOpts.rotateLabels);
227+
228+
return hoverLabel.node();
229+
};
230+
156231
// The actual implementation is here:
157232
function _hover(gd, evt, subplot, noHoverEvent) {
158233
if(!subplot) subplot = 'xy';

Diff for: src/components/fx/index.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var Lib = require('../../lib');
1313
var dragElement = require('../dragelement');
1414
var helpers = require('./helpers');
1515
var layoutAttributes = require('./layout_attributes');
16+
var hoverModule = require('./hover');
1617

1718
module.exports = {
1819
moduleType: 'component',
@@ -41,10 +42,11 @@ module.exports = {
4142
castHoverOption: castHoverOption,
4243
castHoverinfo: castHoverinfo,
4344

44-
hover: require('./hover').hover,
45+
hover: hoverModule.hover,
4546
unhover: dragElement.unhover,
4647

47-
loneHover: require('./hover').loneHover,
48+
loneHover: hoverModule.loneHover,
49+
multiHovers: hoverModule.multiHovers,
4850
loneUnhover: loneUnhover,
4951

5052
click: require('./click')

Diff for: src/traces/parcats/attributes.js

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var extendFlat = require('../../lib/extend').extendFlat;
12+
var plotAttrs = require('../../plots/attributes');
13+
var fontAttrs = require('../../plots/font_attributes');
14+
var colorAttributes = require('../../components/colorscale/attributes');
15+
var domainAttrs = require('../../plots/domain').attributes;
16+
var scatterAttrs = require('../scatter/attributes');
17+
var scatterLineAttrs = scatterAttrs.line;
18+
var colorbarAttrs = require('../../components/colorbar/attributes');
19+
20+
var line = extendFlat({
21+
editType: 'calc'
22+
}, colorAttributes('line', {editType: 'calc'}),
23+
{
24+
showscale: scatterLineAttrs.showscale,
25+
colorbar: colorbarAttrs,
26+
shape: {
27+
valType: 'enumerated',
28+
values: ['linear', 'hspline'],
29+
dflt: 'linear',
30+
role: 'info',
31+
editType: 'plot',
32+
description: [
33+
'Sets the shape of the paths.',
34+
'If `linear`, paths are composed of straight lines.',
35+
'If `hspline`, paths are composed of horizontal curved splines'
36+
].join(' ')
37+
}
38+
});
39+
40+
module.exports = {
41+
domain: domainAttrs({name: 'parcats', trace: true, editType: 'calc'}),
42+
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
43+
flags: ['count', 'probability'],
44+
editType: 'plot'
45+
// plotAttrs.hoverinfo description is appropriate
46+
}),
47+
hoveron: {
48+
valType: 'enumerated',
49+
values: ['category', 'color', 'dimension'],
50+
dflt: 'category',
51+
role: 'info',
52+
editType: 'plot',
53+
description: [
54+
'Sets the hover interaction mode for the parcats diagram.',
55+
'If `category`, hover interaction take place per category.',
56+
'If `color`, hover interactions take place per color per category.',
57+
'If `dimension`, hover interactions take place across all categories per dimension.'
58+
].join(' ')
59+
},
60+
arrangement: {
61+
valType: 'enumerated',
62+
values: ['perpendicular', 'freeform', 'fixed'],
63+
dflt: 'perpendicular',
64+
role: 'style',
65+
editType: 'plot',
66+
description: [
67+
'Sets the drag interaction mode for categories and dimensions.',
68+
'If `perpendicular`, the categories can only move along a line perpendicular to the paths.',
69+
'If `freeform`, the categories can freely move on the plane.',
70+
'If `fixed`, the categories and dimensions are stationary.'
71+
].join(' ')
72+
},
73+
bundlecolors: {
74+
valType: 'boolean',
75+
dflt: true,
76+
role: 'info',
77+
editType: 'plot',
78+
description: 'Sort paths so that like colors are bundled together within each category.'
79+
},
80+
sortpaths: {
81+
valType: 'enumerated',
82+
values: ['forward', 'backward'],
83+
dflt: 'forward',
84+
role: 'info',
85+
editType: 'plot',
86+
description: [
87+
'Sets the path sorting algorithm.',
88+
'If `forward`, sort paths based on dimension categories from left to right.',
89+
'If `backward`, sort paths based on dimensions categories from right to left.'
90+
].join(' ')
91+
},
92+
labelfont: fontAttrs({
93+
editType: 'calc',
94+
description: 'Sets the font for the `dimension` labels.'
95+
}),
96+
97+
tickfont: fontAttrs({
98+
editType: 'calc',
99+
description: 'Sets the font for the `category` labels.'
100+
}),
101+
102+
dimensions: {
103+
_isLinkedToArray: 'dimension',
104+
label: {
105+
valType: 'string',
106+
role: 'info',
107+
editType: 'calc',
108+
description: 'The shown name of the dimension.'
109+
},
110+
categoryorder: {
111+
valType: 'enumerated',
112+
values: [
113+
'trace', 'category ascending', 'category descending', 'array'
114+
],
115+
dflt: 'trace',
116+
role: 'info',
117+
editType: 'calc',
118+
description: [
119+
'Specifies the ordering logic for the categories in the dimension.',
120+
'By default, plotly uses *trace*, which specifies the order that is present in the data supplied.',
121+
'Set `categoryorder` to *category ascending* or *category descending* if order should be determined by',
122+
'the alphanumerical order of the category names.',
123+
'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category',
124+
'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to',
125+
'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.'
126+
].join(' ')
127+
},
128+
categoryarray: {
129+
valType: 'data_array',
130+
role: 'info',
131+
editType: 'calc',
132+
description: [
133+
'Sets the order in which categories in this dimension appear.',
134+
'Only has an effect if `categoryorder` is set to *array*.',
135+
'Used with `categoryorder`.'
136+
].join(' ')
137+
},
138+
ticktext: {
139+
valType: 'data_array',
140+
role: 'info',
141+
editType: 'calc',
142+
description: [
143+
'Sets alternative tick labels for the categories in this dimension.',
144+
'Only has an effect if `categoryorder` is set to *array*.',
145+
'Should be an array the same length as `categoryarray`',
146+
'Used with `categoryorder`.'
147+
].join(' ')
148+
},
149+
values: {
150+
valType: 'data_array',
151+
role: 'info',
152+
dflt: [],
153+
editType: 'calc',
154+
description: [
155+
'Dimension values. `values[n]` represents the category value of the `n`th point in the dataset,',
156+
'therefore the `values` vector for all dimensions must be the same (longer vectors',
157+
'will be truncated).'
158+
].join(' ')
159+
},
160+
displayindex: {
161+
valType: 'integer',
162+
role: 'info',
163+
editType: 'calc',
164+
description: [
165+
'The display index of dimension, from left to right, zero indexed, defaults to dimension',
166+
'index.'
167+
].join(' ')
168+
},
169+
editType: 'calc',
170+
description: 'The dimensions (variables) of the parallel categories diagram.',
171+
visible: {
172+
valType: 'boolean',
173+
dflt: true,
174+
role: 'info',
175+
editType: 'calc',
176+
description: 'Shows the dimension when set to `true` (the default). Hides the dimension for `false`.'
177+
}
178+
},
179+
180+
line: line,
181+
counts: {
182+
valType: 'number',
183+
min: 0,
184+
dflt: 1,
185+
arrayOk: true,
186+
role: 'info',
187+
editType: 'calc',
188+
description: [
189+
'The number of observations represented by each state. Defaults to 1 so that each state represents',
190+
'one observation'
191+
].join(' ')
192+
},
193+
194+
// Hide unsupported top-level properties from plot-schema
195+
customdata: undefined,
196+
hoverlabel: undefined,
197+
ids: undefined,
198+
legendgroup: undefined,
199+
opacity: undefined,
200+
selectedpoints: undefined,
201+
showlegend: undefined
202+
};

Diff for: src/traces/parcats/base_plot.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var getModuleCalcData = require('../../plots/get_data').getModuleCalcData;
12+
var parcatsPlot = require('./plot');
13+
14+
var PARCATS = 'parcats';
15+
exports.name = PARCATS;
16+
17+
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
18+
19+
var cdModuleAndOthers = getModuleCalcData(gd.calcdata, PARCATS);
20+
21+
if(cdModuleAndOthers.length) {
22+
var calcData = cdModuleAndOthers[0];
23+
parcatsPlot(gd, calcData, transitionOpts, makeOnCompleteCallback);
24+
}
25+
};
26+
27+
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
28+
var hadTable = (oldFullLayout._has && oldFullLayout._has('parcats'));
29+
var hasTable = (newFullLayout._has && newFullLayout._has('parcats'));
30+
31+
if(hadTable && !hasTable) {
32+
oldFullLayout._paperdiv.selectAll('.parcats').remove();
33+
}
34+
};

0 commit comments

Comments
 (0)