Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d386b2d

Browse files
committed
Introduce fixed size shapes plotly#2193
- Shapes (line, rect, circle as well as path) can now be sized by pixel while being positioned relative to data. - New shape attributes `xsizemode`, `xanchor`, `ysizemode` and `yanchor` introduced and semantics of `x0`, `x1`, `y0` and `y1` extended. - Shapes can be sized by pixel on one axis and sized by data on the other. - Fixed size shapes can be moved. - Fixed size lines, rectangles and circles can be resized, paths not. - Fixed size shapes are fully taken into account when an axis is in auto-range mode.
1 parent 49f7fb0 commit d386b2d

File tree

5 files changed

+808
-75
lines changed

5 files changed

+808
-75
lines changed

src/components/shapes/attributes.js

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,20 @@ module.exports = {
3535
'Specifies the shape type to be drawn.',
3636

3737
'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)',
38+
'with respect to the axes\' sizing mode.',
3839

3940
'If *circle*, a circle is drawn from',
4041
'((`x0`+`x1`)/2, (`y0`+`y1`)/2))',
4142
'with radius',
4243
'(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)',
44+
'with respect to the axes\' sizing mode.',
4345

4446
'If *rect*, a rectangle is drawn linking',
4547
'(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)',
48+
'with respect to the axes\' sizing mode.',
4649

47-
'If *path*, draw a custom SVG path using `path`.'
50+
'If *path*, draw a custom SVG path using `path`.',
51+
'with respect to the axes\' sizing mode.'
4852
].join(' ')
4953
},
5054

@@ -61,7 +65,7 @@ module.exports = {
6165
description: [
6266
'Sets the shape\'s x coordinate axis.',
6367
'If set to an x axis id (e.g. *x* or *x2*), the `x` position',
64-
'refers to an x coordinate',
68+
'refers to an x coordinate.',
6569
'If set to *paper*, the `x` position refers to the distance from',
6670
'the left side of the plotting area in normalized coordinates',
6771
'where *0* (*1*) corresponds to the left (right) side.',
@@ -71,13 +75,41 @@ module.exports = {
7175
'the date to unix time in milliseconds.'
7276
].join(' ')
7377
}),
78+
xsizemode: {
79+
valType: 'enumerated',
80+
values: ['data', 'pixel'],
81+
dflt: 'data',
82+
role: 'info',
83+
editType: 'calcIfAutorange+arraydraw',
84+
description: [
85+
'Sets the shapes\'s sizing mode along the x axis.',
86+
'If set to *data*, `x0`, `x1` and x coordinates within `path` refer to',
87+
'data values on the x axis.',
88+
'If set to *pixel*, `xanchor` specifies the x position in terms',
89+
'of data but `x0`, `x1` and x coordinates within `path`',
90+
'are pixels relative to `xanchor`. This way, the shape can have',
91+
'a fixed width while maintaining a position relative to data.'
92+
].join(' ')
93+
},
94+
xanchor: {
95+
valType: 'any',
96+
role: 'info',
97+
editType: 'calcIfAutorange+arraydraw',
98+
description: [
99+
'Only relevant in conjunction with `xsizemode` set to *pixel*.',
100+
'Specifies the anchor point on the x axis to which `x0`, `x1`',
101+
'and x coordinates within `path` are relative to.',
102+
'E.g. useful to attach a pixel sized shape to a certain data value.',
103+
'No effect when `xsizemode` not set to *pixel*.'
104+
].join(' ')
105+
},
74106
x0: {
75107
valType: 'any',
76108
role: 'info',
77109
editType: 'calcIfAutorange+arraydraw',
78110
description: [
79111
'Sets the shape\'s starting x position.',
80-
'See `type` for more info.'
112+
'See `type` and `xsizemode` for more info.'
81113
].join(' ')
82114
},
83115
x1: {
@@ -86,7 +118,7 @@ module.exports = {
86118
editType: 'calcIfAutorange+arraydraw',
87119
description: [
88120
'Sets the shape\'s end x position.',
89-
'See `type` for more info.'
121+
'See `type` and `xsizemode` for more info.'
90122
].join(' ')
91123
},
92124

@@ -100,13 +132,41 @@ module.exports = {
100132
'where *0* (*1*) corresponds to the bottom (top).'
101133
].join(' ')
102134
}),
135+
ysizemode: {
136+
valType: 'enumerated',
137+
values: ['data', 'pixel'],
138+
dflt: 'data',
139+
role: 'info',
140+
editType: 'calcIfAutorange+arraydraw',
141+
description: [
142+
'Sets the shapes\'s sizing mode along the y axis.',
143+
'If set to *data*, `y0`, `y1` and y coordinates within `path` refer to',
144+
'data values on the y axis.',
145+
'If set to *pixel*, `yanchor` specifies the y position in terms',
146+
'of data but `y0`, `y1` and y coordinates within `path`',
147+
'are pixels relative to `yanchor`. This way, the shape can have',
148+
'a fixed height while maintaining a position relative to data.'
149+
].join(' ')
150+
},
151+
yanchor: {
152+
valType: 'any',
153+
role: 'info',
154+
editType: 'calcIfAutorange+arraydraw',
155+
description: [
156+
'Only relevant in conjunction with `ysizemode` set to *pixel*.',
157+
'Specifies the anchor point on the y axis to which `y0`, `y1`',
158+
'and y coordinates within `path` are relative to.',
159+
'E.g. useful to attach a pixel sized shape to a certain data value.',
160+
'No effect when `ysizemode` not set to *pixel*.'
161+
].join(' ')
162+
},
103163
y0: {
104164
valType: 'any',
105165
role: 'info',
106166
editType: 'calcIfAutorange+arraydraw',
107167
description: [
108168
'Sets the shape\'s starting y position.',
109-
'See `type` for more info.'
169+
'See `type` and `ysizemode` for more info.'
110170
].join(' ')
111171
},
112172
y1: {
@@ -115,7 +175,7 @@ module.exports = {
115175
editType: 'calcIfAutorange+arraydraw',
116176
description: [
117177
'Sets the shape\'s end y position.',
118-
'See `type` for more info.'
178+
'See `type` and `ysizemode` for more info.'
119179
].join(' ')
120180
},
121181

@@ -124,8 +184,11 @@ module.exports = {
124184
role: 'info',
125185
editType: 'calcIfAutorange+arraydraw',
126186
description: [
127-
'For `type` *path* - a valid SVG path but with the pixel values',
128-
'replaced by data values. There are a few restrictions / quirks',
187+
'For `type` *path* - a valid SVG path with the pixel values',
188+
'replaced by data values in `xsizemode`/`ysizemode` being *data*',
189+
'and taken unmodified as pixels relative to `xanchor` and `yanchor`',
190+
'in case of *pixel* size mode.',
191+
'There are a few restrictions / quirks',
129192
'only absolute instructions, not relative. So the allowed segments',
130193
'are: M, L, H, V, Q, C, T, S, and Z',
131194
'arcs (A) are not allowed because radius rx and ry are relative.',

src/components/shapes/calc_autorange.js

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,79 @@ module.exports = function calcAutorange(gd) {
2323
if(!shapeList.length || !gd._fullData.length) return;
2424

2525
for(var i = 0; i < shapeList.length; i++) {
26-
var shape = shapeList[i],
27-
ppad = shape.line.width / 2;
26+
var shape = shapeList[i];
2827

2928
var ax, bounds;
3029

3130
if(shape.xref !== 'paper') {
31+
var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0,
32+
vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1;
3233
ax = Axes.getFromId(gd, shape.xref);
33-
bounds = shapeBounds(ax, shape.x0, shape.x1, shape.path, constants.paramIsX);
34-
if(bounds) Axes.expand(ax, bounds, {ppad: ppad});
34+
35+
bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX);
36+
37+
if(bounds) Axes.expand(ax, bounds, calcXPaddingOptions(shape));
3538
}
3639

3740
if(shape.yref !== 'paper') {
41+
var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0,
42+
vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1;
3843
ax = Axes.getFromId(gd, shape.yref);
39-
bounds = shapeBounds(ax, shape.y0, shape.y1, shape.path, constants.paramIsY);
40-
if(bounds) Axes.expand(ax, bounds, {ppad: ppad});
44+
45+
bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY);
46+
if(bounds) Axes.expand(ax, bounds, calcYPaddingOptions(shape));
4147
}
4248
}
4349
};
4450

51+
function calcXPaddingOptions(shape) {
52+
return calcPaddingOptions(shape.line.width, shape.xsizemode, shape.x0, shape.x1, shape.path, false);
53+
}
54+
55+
function calcYPaddingOptions(shape) {
56+
return calcPaddingOptions(shape.line.width, shape.ysizemode, shape.y0, shape.y1, shape.path, true);
57+
}
58+
59+
function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
60+
var ppad = lineWidth / 2,
61+
axisDirectionReverted = isYAxis;
62+
63+
if(sizeMode === 'pixel') {
64+
var coords = path ?
65+
extractPathCoords(path, isYAxis ? constants.paramIsY : constants.paramIsX) :
66+
[v0, v1];
67+
var maxValue = Lib.aggNums(Math.max, null, coords),
68+
minValue = Lib.aggNums(Math.min, null, coords),
69+
beforePad = minValue < 0 ? Math.abs(minValue) + ppad : ppad,
70+
afterPad = maxValue > 0 ? maxValue + ppad : ppad;
71+
72+
return {
73+
ppad: ppad,
74+
ppadplus: axisDirectionReverted ? beforePad : afterPad,
75+
ppadminus: axisDirectionReverted ? afterPad : beforePad
76+
};
77+
} else {
78+
return {ppad: ppad};
79+
}
80+
}
81+
82+
function extractPathCoords(path, paramsToUse) {
83+
var extractedCoordinates = [];
84+
85+
var segments = path.match(constants.segmentRE);
86+
segments.forEach(function(segment) {
87+
var relevantParamIdx = paramsToUse[segment.charAt(0)].drawn;
88+
if(relevantParamIdx === undefined) return;
89+
90+
var params = segment.substr(1).match(constants.paramRE);
91+
if(!params || params.length < relevantParamIdx) return;
92+
93+
extractedCoordinates.push(params[relevantParamIdx]);
94+
});
95+
96+
return extractedCoordinates;
97+
}
98+
4599
function shapeBounds(ax, v0, v1, path, paramsToUse) {
46100
var convertVal = (ax.type === 'category') ? ax.r2c : ax.d2c;
47101

0 commit comments

Comments
 (0)