Skip to content

Commit 7fbeb70

Browse files
committed
Add transposition logic, and use custom functions for joinSrcs and splitSrcs
1 parent e2f3d32 commit 7fbeb70

File tree

5 files changed

+186
-37
lines changed

5 files changed

+186
-37
lines changed

src/EditorControls.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class EditorControls extends Component {
2828
return {
2929
advancedTraceTypeSelector: this.props.advancedTraceTypeSelector,
3030
config: gd._context,
31+
customSrcHandling: this.props.customSrcHandling,
3132
data: gd.data,
3233
dataSources: this.props.dataSources,
3334
dataSourceOptions: this.props.dataSourceOptions,
@@ -280,6 +281,7 @@ EditorControls.propTypes = {
280281
beforeUpdateTraces: PropTypes.func,
281282
children: PropTypes.node,
282283
className: PropTypes.string,
284+
customSrcHandling: PropTypes.object,
283285
dataSourceOptionRenderer: PropTypes.func,
284286
dataSourceOptions: PropTypes.array,
285287
dataSources: PropTypes.object,
@@ -289,8 +291,8 @@ EditorControls.propTypes = {
289291
locale: PropTypes.string,
290292
onUpdate: PropTypes.func,
291293
plotly: PropTypes.object,
292-
traceTypesConfig: PropTypes.object,
293294
showFieldTooltips: PropTypes.bool,
295+
traceTypesConfig: PropTypes.object,
294296
};
295297

296298
EditorControls.defaultProps = {
@@ -306,6 +308,7 @@ EditorControls.defaultProps = {
306308
EditorControls.childContextTypes = {
307309
advancedTraceTypeSelector: PropTypes.bool,
308310
config: PropTypes.object,
311+
customSrcHandling: PropTypes.object,
309312
data: PropTypes.array,
310313
dataSourceOptionRenderer: PropTypes.func,
311314
dataSourceOptions: PropTypes.array,

src/PlotlyEditor.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class PlotlyEditor extends Component {
2525
traceTypesConfig={this.props.traceTypesConfig}
2626
dictionaries={this.props.dictionaries}
2727
showFieldTooltips={this.props.showFieldTooltips}
28+
customSrcHandling={this.props.customSrcHandling}
2829
>
2930
{this.props.children}
3031
</EditorControls>
@@ -70,6 +71,7 @@ PlotlyEditor.propTypes = {
7071
divId: PropTypes.string,
7172
hideControls: PropTypes.bool,
7273
showFieldTooltips: PropTypes.bool,
74+
customSrcHandling: PropTypes.object,
7375
};
7476

7577
PlotlyEditor.defaultProps = {

src/components/fields/DataSelector.js

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import React, {Component} from 'react';
44
import Field from './Field';
55
import nestedProperty from 'plotly.js/src/lib/nested_property';
6-
import {connectToContainer} from 'lib';
6+
import {connectToContainer, maybeAdjustSrc, maybeTransposeData} from 'lib';
77

88
export function attributeIsData(meta = {}) {
99
return meta.valType === 'data_array' || meta.arrayOk;
@@ -33,50 +33,59 @@ export class UnconnectedDataSelector extends Component {
3333
if (props.container) {
3434
this.is2D =
3535
(props.attr === 'z' &&
36-
['contour', 'heatmap', 'surface', 'heatmapgl'].includes(
37-
props.container.type
38-
)) ||
36+
[
37+
'contour',
38+
'contourgl',
39+
'heatmap',
40+
'heatmapgl',
41+
'surface',
42+
'carpet',
43+
'contourcarpet',
44+
].includes(props.container.type)) ||
3945
(props.container.type === 'table' && props.attr !== 'columnorder');
4046
}
47+
48+
if (
49+
this.is2D &&
50+
this.fullValue &&
51+
this.fullValue.length &&
52+
this.context.customSrcHandling
53+
) {
54+
this.fullValue = this.context.customSrcHandling.splitSrcs(this.fullValue);
55+
}
4156
}
4257

4358
updatePlot(value) {
4459
if (!this.props.updateContainer) {
4560
return;
4661
}
62+
4763
const update = {};
64+
let data;
4865

4966
if (Array.isArray(value)) {
50-
update[this.props.attr] = value
67+
data = value
5168
.filter(v => Array.isArray(this.dataSources[v]))
5269
.map(v => this.dataSources[v]);
53-
54-
// Table traces have many configuration options,
55-
// The below attributes can be 2d or 1d and will affect the plot differently
56-
// EX:
57-
// header.values = ['Jan', 'Feb', 'Mar'] => will put data in a row
58-
// header.values = [['Jan', 1], ['Feb', 2], ['Mar', 3]] => will create 3 columns
59-
// 1d arrays affect columns
60-
// 2d arrays affect rows within each column
61-
62-
if (
63-
this.props.container.type === 'table' &&
64-
value.length === 1 &&
65-
[
66-
'header.values',
67-
'header.font.color',
68-
'header.font.size',
69-
'header.fill.color',
70-
'columnwidth',
71-
].includes(this.props.attr)
72-
) {
73-
update[this.props.attr] = update[this.props.attr][0];
74-
}
7570
} else {
76-
update[this.props.attr] = this.dataSources[value] || null;
71+
data = this.dataSources[value] || null;
7772
}
78-
update[this.srcAttr] = value;
7973

74+
update[this.props.attr] = maybeTransposeData(
75+
data,
76+
this.srcAttr,
77+
this.props.container.type
78+
);
79+
update[this.srcAttr] = maybeAdjustSrc(
80+
value,
81+
this.srcAttr,
82+
this.props.container.type,
83+
{
84+
joinSrcs: this.context.customSrcHandling
85+
? this.context.customSrcHandling.joinSrcs
86+
: null,
87+
}
88+
);
8089
this.props.updateContainer(update);
8190
}
8291

@@ -122,6 +131,7 @@ UnconnectedDataSelector.contextTypes = {
122131
dataSourceOptions: PropTypes.array,
123132
dataSourceValueRenderer: PropTypes.func,
124133
dataSourceOptionRenderer: PropTypes.func,
134+
customSrcHandling: PropTypes.object,
125135
};
126136

127137
function modifyPlotProps(props, context, plotProps) {

src/lib/dereference.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import walkObject from './walkObject';
2+
import {maybeTransposeData} from './index';
23

34
const SRC_ATTR_PATTERN = /src$/;
45

@@ -7,28 +8,42 @@ export default function dereference(
78
dataSources,
89
config = {deleteKeys: false}
910
) {
10-
const replacer = (key, parent) => {
11+
const replacer = (key, parent, srcPath) => {
1112
if (!SRC_ATTR_PATTERN.test(key)) {
1213
return;
1314
}
1415

15-
const srcRef = parent[key];
16-
const data = dataSources[srcRef];
1716
const dataKey = key.replace(SRC_ATTR_PATTERN, '');
17+
const traceType = parent.type;
1818

19-
if (config.deleteKeys && !(srcRef in dataSources)) {
20-
delete parent[dataKey];
21-
return;
19+
// making this into an array to more easily lookup 1d and 2d srcs in dataSourceOptions
20+
const srcRef = config.splitSrcs
21+
? config.splitSrcs(parent[key])
22+
: Array.isArray(parent[key])
23+
? parent[key]
24+
: [parent[key]];
25+
26+
let data = srcRef.map(ref => {
27+
if (config.deleteKeys && !(ref in dataSources)) {
28+
delete parent[dataKey];
29+
}
30+
return dataSources[ref];
31+
});
32+
33+
// remove extra data wrapping
34+
if (srcRef.length === 1) {
35+
data = data[0];
2236
}
2337

2438
if (!Array.isArray(data)) {
2539
return;
2640
}
2741

28-
parent[dataKey] = data;
42+
parent[dataKey] = maybeTransposeData(data, srcPath, traceType);
2943
};
3044

3145
walkObject(container, replacer, {
3246
walkArraysMatchingKeys: ['data', 'transforms'],
47+
pathType: 'nestedProperty',
3348
});
3449
}

src/lib/index.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,122 @@ function renderTraceIcon(trace, prefix = 'Plot') {
6464
: PlotlyIcons.PlotLineIcon;
6565
}
6666

67+
function transpose(originalArray) {
68+
// if we want to transpose a uni dimensional array
69+
if (originalArray.every(a => !Array.isArray(a))) {
70+
return originalArray.map(a => [a]);
71+
}
72+
73+
let longestArrayItem = Array.isArray(originalArray[0])
74+
? originalArray[0].length
75+
: 1;
76+
77+
originalArray.forEach(a => {
78+
// if it's not an array, it's a string
79+
const length = Array.isArray(a) ? a.length : 1;
80+
if (length > longestArrayItem) {
81+
longestArrayItem = length;
82+
}
83+
});
84+
85+
const newArray = new Array(longestArrayItem);
86+
87+
for (let outerIndex = 0; outerIndex < originalArray.length; outerIndex++) {
88+
if (!Array.isArray(originalArray[outerIndex])) {
89+
originalArray[outerIndex] = [originalArray[outerIndex]];
90+
}
91+
92+
for (let innerIndex = 0; innerIndex < longestArrayItem; innerIndex++) {
93+
// ensure we have an array to push to
94+
if (!Array.isArray(newArray[innerIndex])) {
95+
newArray[innerIndex] = [];
96+
}
97+
98+
const value = originalArray[outerIndex][innerIndex]
99+
? originalArray[outerIndex][innerIndex]
100+
: null;
101+
newArray[innerIndex].push(value);
102+
}
103+
}
104+
105+
return newArray;
106+
}
107+
108+
const specialTableCase = (traceType, srcAttributePath) => {
109+
/* Just more user friendly
110+
* Table traces have many configuration options,
111+
* The below attributes can be 2d or 1d and will affect the plot differently
112+
* EX:
113+
* header.values = ['Jan', 'Feb', 'Mar'] => will put data in a row
114+
* header.values = [['Jan', 1], ['Feb', 2], ['Mar', 3]] => will create 3 columns
115+
* 1d arrays affect columns
116+
* 2d arrays affect rows within each column
117+
*/
118+
return (
119+
traceType === 'table' &&
120+
[
121+
'header.valuessrc',
122+
'header.font.colorsrc',
123+
'header.font.sizesrc',
124+
'header.fill.colorsrc',
125+
'columnwidthsrc',
126+
].some(a => srcAttributePath.endsWith(a))
127+
);
128+
};
129+
130+
function maybeTransposeData(data, srcAttributePath, traceType) {
131+
if (!data || (Array.isArray(data) && data.length === 0)) {
132+
return null;
133+
}
134+
135+
const isTransposable2DArray =
136+
srcAttributePath.endsWith('zsrc') &&
137+
[
138+
'contour',
139+
'contourgl',
140+
'heatmap',
141+
'heatmapgl',
142+
'surface',
143+
'carpet',
144+
'contourcarpet',
145+
].includes(traceType);
146+
147+
if (isTransposable2DArray) {
148+
return transpose(data);
149+
}
150+
151+
if (
152+
specialTableCase(traceType, srcAttributePath) &&
153+
Array.isArray(data[0]) &&
154+
data.length === 1
155+
) {
156+
return data[0];
157+
}
158+
159+
return data;
160+
}
161+
162+
function maybeAdjustSrc(src, srcAttributePath, traceType, config) {
163+
if (!src || (Array.isArray(src) && src.length === 0)) {
164+
return null;
165+
}
166+
167+
if (Array.isArray(src)) {
168+
if (src.length > 1 && config && config.joinSrcs) {
169+
return config.joinSrcs(src, traceType);
170+
}
171+
172+
if (
173+
(specialTableCase(traceType, srcAttributePath) && src.length === 1) ||
174+
src.length === 1
175+
) {
176+
return src[0];
177+
}
178+
}
179+
180+
return src;
181+
}
182+
67183
export {
68184
axisIdToAxisName,
69185
bem,
@@ -96,11 +212,14 @@ export {
96212
isPlainObject,
97213
localize,
98214
localizeString,
215+
maybeAdjustSrc,
216+
maybeTransposeData,
99217
plotlyTraceToCustomTrace,
100218
renderTraceIcon,
101219
unpackPlotProps,
102220
walkObject,
103221
tooLight,
104222
striptags,
105223
traceTypeToAxisType,
224+
transpose,
106225
};

0 commit comments

Comments
 (0)