Skip to content

Commit c328243

Browse files
Made a test for domain referencing shapes
It can easily be wrapped in jasmine directives. Before I do this I want to abstract it a bit to make the same code work for annotations and images.
1 parent 0f204ad commit c328243

File tree

8 files changed

+255
-23
lines changed

8 files changed

+255
-23
lines changed

src/plots/cartesian/axis_ids.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ var constants = require('./constants');
1818
// completely in favor of just 'x' if it weren't ingrained in the API etc.
1919
exports.id2name = function id2name(id) {
2020
if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return;
21-
var axNum = id.substr(1);
22-
// axNum could be ' ' if domain specified and axis omitted number
23-
if(axNum === '1' || axNum === ' ') axNum = '';
21+
var axNum = id.split(' ')[0].substr(1);
22+
if(axNum === '1') axNum = '';
2423
return id.charAt(0) + 'axis' + axNum;
2524
};
2625

test/domain_ref_shapes_test.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv='Content-Type' content='text/html:charset=utf-8' />
5+
</head>
6+
<body>
7+
<script src="domain_ref_shapes_test-min.js"></script>
8+
</body>
9+
</html>

test/domain_ref_shapes_test.js

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
var Plotly = require('../lib/index');
2+
var d3 = require('d3');
3+
var createGraphDiv = require('../test/jasmine/assets/create_graph_div');
4+
var destroyGraphDiv = require('../test/jasmine/assets/destroy_graph_div');
5+
var pixelCalc = require('../test/jasmine/assets/pixel_calc');
6+
var getSVGElemScreenBBox = require('../test/jasmine/assets/get_svg_elem_screen_bbox');
7+
var Lib = require('../src/lib');
8+
var Axes = require('../src/plots/cartesian/axes');
9+
var axisIds = require('../src/plots/cartesian/axis_ids');
10+
11+
// NOTE: this tolerance is in pixels
12+
var EQUALITY_TOLERANCE = 1e-2;
13+
14+
var DEBUG = true;
15+
16+
var it = function(s,f) {
17+
console.log('testing ' + s);
18+
f(function() { console.log(s + ' is done.'); });
19+
}
20+
21+
// acts on an Object representing a shape which could be a line or a rect
22+
function shapeFromShapePos(shape,axletter,axnum,shapepos) {
23+
shape[axletter+'0'] = shapepos.value[0];
24+
shape[axletter+'1'] = shapepos.value[1];
25+
if (shapepos.ref === 'range') {
26+
shape[axletter+'ref'] = axletter + axnum;
27+
} else if (shapepos.ref === 'domain') {
28+
shape[axletter+'ref'] = axletter + axnum + ' domain';
29+
} else if (shapepos.ref === 'paper') {
30+
shape[axletter+'ref'] = 'paper';
31+
}
32+
}
33+
34+
// axid is e.g., 'x', 'y2' etc.
35+
function logAxisIfAxType(layoutIn,layoutOut,axid,axtype) {
36+
if (axtype === 'log') {
37+
var axname = axisIds.id2name(axid);
38+
var axis = {...layoutIn[axname]};
39+
axis.type = 'log';
40+
axis.range = axis.range.map(Math.log10);
41+
layoutOut[axname] = axis;
42+
}
43+
}
44+
45+
46+
// axref can be xref or yref
47+
// c can be x0, x1, y0, y1
48+
function mapShapeCoordToPixel(layout,axref,shape,c) {
49+
var reftype = Axes.getRefType(shape[axref]);
50+
var axletter = axref[0];
51+
var ret;
52+
if (reftype === 'range') {
53+
var axis = axisIds.id2name(shape[axref]);
54+
ret = pixelCalc.mapRangeToPixel(layout, axis, shape[c]);
55+
} else if (reftype === 'domain') {
56+
var axis = axisIds.id2name(shape[axref]);
57+
ret = pixelCalc.mapDomainToPixel(layout, axis, shape[c]);
58+
} else if (reftype === 'paper') {
59+
var axis = axref[0];
60+
ret = pixelCalc.mapPaperToPixel(layout, axis, shape[c]);
61+
}
62+
return ret;
63+
}
64+
65+
// compute the bounding box of the shape so that it can be compared with the SVG
66+
// bounding box
67+
function shapeToBBox(layout,shape) {
68+
var bbox = {};
69+
var x1;
70+
var y1;
71+
// map x coordinates
72+
bbox.x = mapShapeCoordToPixel(layout,'xref',shape,'x0');
73+
x1 = mapShapeCoordToPixel(layout,'xref',shape,'x1');
74+
// SVG bounding boxes have x,y referring to top left corner, but here we are
75+
// specifying shapes where y0 refers to the bottom left corner like
76+
// Plotly.js, so we swap y0 and y1
77+
bbox.y = mapShapeCoordToPixel(layout,'yref',shape,'y1');
78+
y1 = mapShapeCoordToPixel(layout,'yref',shape,'y0');
79+
bbox.width = x1 - bbox.x;
80+
bbox.height = y1 - bbox.y;
81+
return bbox;
82+
}
83+
84+
function coordsEq(a,b) {
85+
return Math.abs(a - b) < EQUALITY_TOLERANCE;
86+
}
87+
88+
function compareBBoxes(a,b) {
89+
return ['x','y','width','height'].map(
90+
(k,)=>coordsEq(a[k],b[k])).reduce(
91+
(l,r)=>l&&r,
92+
true);
93+
}
94+
95+
// gets the SVG bounding box of the shape and checks it against what mapToPixel
96+
// gives
97+
function checkShapePosition(gd,shape) {
98+
var shapePath = d3.selectAll('path').filter(function () {
99+
return this.style.stroke === shape.line.color;
100+
}).node();
101+
var shapePathBBox = getSVGElemScreenBBox(shapePath);
102+
var shapeBBox = shapeToBBox(gd.layout,shape);
103+
var ret = compareBBoxes(shapeBBox,shapePathBBox);
104+
if (DEBUG) {
105+
console.log('SVG BBox',shapePathBBox);
106+
console.log('shape BBox',shapeBBox);
107+
}
108+
return ret;
109+
}
110+
111+
// some made-up values for testing
112+
var shapePositionsX = [
113+
{
114+
// shapes referring to data
115+
ref: 'range',
116+
// two values for rects
117+
value: [2,3]
118+
},
119+
{
120+
// shapes referring to domains
121+
ref: 'domain',
122+
value: [0.2,0.75],
123+
},
124+
{
125+
// shapes referring to paper
126+
ref: 'paper',
127+
value: [0.25, 0.8]
128+
}
129+
];
130+
var shapePositionsY = [
131+
{
132+
// shapes referring to data
133+
ref: 'range',
134+
// two values for rects
135+
value: [1,2]
136+
},
137+
{
138+
// shapes referring to domains
139+
ref: 'domain',
140+
value: [0.25,0.7],
141+
},
142+
{
143+
// shapes referring to paper
144+
ref: 'paper',
145+
value: [0.2, 0.85]
146+
}
147+
];
148+
var axisTypes = [ 'linear', 'log' ];
149+
// Test on 'x', 'y', 'x2', 'y2' axes
150+
// TODO the 'paper' position references are tested twice when once would
151+
// suffice.
152+
var axNum = ['','2'];
153+
// only test line and rect for now
154+
var shapeType = ['line','rect'];
155+
// this color chosen so it can easily be found with d3
156+
var shapeColor = 'rgb(50, 100, 150)';
157+
var testDomRefShapeCombo = function(combo) {
158+
var xAxNum = combo[0];
159+
var xaxisType = combo[1];
160+
var xshapePos = combo[2];
161+
var yAxNum = combo[3];
162+
var yaxisType = combo[4];
163+
var yshapePos = combo[5];
164+
var shapeType = combo[6];
165+
it('should draw a ' + shapeType
166+
+ ' for x' + xAxNum + ' of type '
167+
+ xaxisType
168+
+ ' with a value referencing '
169+
+ xshapePos.ref
170+
+ ' and for y' + yAxNum + ' of type '
171+
+ yaxisType
172+
+ ' with a value referencing '
173+
+ yshapePos.ref,
174+
function (done) {
175+
var gd = createGraphDiv();
176+
var mock = Lib.extendDeep({},
177+
require('../test/image/mocks/domain_ref_base.json'));
178+
if (DEBUG) {
179+
console.log(combo);
180+
}
181+
Plotly.newPlot(gd, mock)
182+
var shape = {
183+
type: shapeType,
184+
line: { color: shapeColor }
185+
};
186+
shapeFromShapePos(shape,'x',xAxNum,xshapePos);
187+
shapeFromShapePos(shape,'y',yAxNum,yshapePos);
188+
var layout = {shapes: [shape]};
189+
// change to log axes if need be
190+
logAxisIfAxType(gd.layout,layout,'x'+xAxNum,xaxisType);
191+
logAxisIfAxType(gd.layout,layout,'y'+yAxNum,yaxisType);
192+
Plotly.relayout(gd,layout);
193+
console.log(checkShapePosition(gd,shape));
194+
destroyGraphDiv();
195+
});
196+
}
197+
198+
// Test correct shape positions
199+
function test_correct_shape_positions () {
200+
var iterable = require('extra-iterable');
201+
// for both x and y axes
202+
var testCombos = [...iterable.cartesianProduct([
203+
axNum,axisTypes,shapePositionsX,axNum,axisTypes,shapePositionsY,shapeType
204+
])];
205+
// map all the combinations to a shape definition and check this shape is
206+
// placed properly
207+
testCombos.forEach(testDomRefShapeCombo);
208+
}
209+
210+
test_correct_shape_positions();

test/domain_ref_test.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html>
33
<head>
44
<meta http-equiv='Content-Type' content='text/html:charset=utf-8' />
5+
<script src="../node_modules/d3/d3.min.js"></script>
56
</head>
67
<body>
78
<script src="domain_ref_test-min.js"></script>

test/domain_ref_test.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@ var dothething = function() {
2121
};
2222
Plotly.newPlot(gd,mock)
2323
.then(function () {
24+
var xaxis2 = {...gd.layout.xaxis2};
25+
var yaxis2 = {...gd.layout.yaxis2};
26+
xaxis2.type = 'log';
27+
xaxis2.range = xaxis2.range.map(Math.log10);
28+
yaxis2.type = 'log';
29+
yaxis2.range = yaxis2.range.map(Math.log10);
2430
var layout = {
25-
shapes: [shape]
31+
shapes: [shape],
32+
xaxis2: xaxis2,
33+
yaxis2: yaxis2
2634
}
2735
return layout;
2836
})
@@ -33,11 +41,17 @@ var dothething = function() {
3341
var shapePath = d3.selectAll('path').filter(function () {
3442
return this.style.stroke === shapeColor;
3543
}).node();
36-
console.log(getSVGElemScreenBBox(shapePath));
44+
var bbox = getSVGElemScreenBBox(shapePath)
45+
console.log(bbox);
46+
console.log('property names',Object.keys(bbox));
3747
console.log('x0',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0));
3848
console.log('x1',pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x1));
3949
console.log('y0',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y0));
4050
console.log('y1',pixelCalc.mapRangeToPixel(gd.layout, 'yaxis2', shape.y1));
51+
console.log('bbox.x0 - shape.x0',
52+
bbox.x -
53+
pixelCalc.mapRangeToPixel(gd.layout, 'xaxis2', shape.x0)
54+
);
4155
});
4256
}
4357

test/image/mocks/domain_ref_base.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
"yaxis": "y",
88
"line": { "color": "rgb(127,127,127)" }
99
},{
10-
"x": [3, 1],
10+
"x": [1, 3],
1111
"y": [1, 2],
1212
"type": "scatter",
1313
"xaxis": "x2",
1414
"yaxis": "y2",
1515
"line": { "color": "rgb(127,127,127)" }
16-
}],
16+
}
17+
],
1718
"layout": {
1819
"xaxis": {
1920
"domain": [0,0.75],
@@ -25,11 +26,13 @@
2526
},
2627
"xaxis2": {
2728
"domain": [0.75,1],
28-
"range": [0, 4]
29+
"range": [0, 4],
30+
"anchor": "y2"
2931
},
3032
"yaxis2": {
3133
"domain": [.4,1],
32-
"range": [0, 3]
34+
"range": [0, 3],
35+
"anchor": "x2"
3336
},
3437
"margin": { "l": 100, "r": 100, "t": 100, "b": 100, "autoexpand": false },
3538
"width": 400,

test/jasmine/assets/get_rect_center.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
var SVGTools = require('./svg_tools');
4+
35

46
/**
57
* Get the screen coordinates of the center of
@@ -18,7 +20,7 @@ module.exports = function getRectCenter(rect) {
1820

1921
// Taken from: http://stackoverflow.com/a/5835212/4068492
2022
function getRectScreenCoords(rect) {
21-
var svg = findParentSVG(rect);
23+
var svg = SVGTools.findParentSVG(rect);
2224
var pt = svg.createSVGPoint();
2325
var corners = {};
2426
var matrix = rect.getScreenCTM();
@@ -35,13 +37,3 @@ function getRectScreenCoords(rect) {
3537

3638
return corners;
3739
}
38-
39-
function findParentSVG(node) {
40-
var parentNode = node.parentNode;
41-
42-
if(parentNode.tagName === 'svg') {
43-
return parentNode;
44-
} else {
45-
return findParentSVG(parentNode);
46-
}
47-
}

test/jasmine/assets/pixel_calc.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
'use strict'
22

3-
// TODO: Something is wrong with the y values?
4-
53
// Calculate the pixel values from various objects
64

75
// {layout} is where the margins are obtained from
@@ -29,12 +27,15 @@ function mapToPixelHelper(layout, axis, domain, d) {
2927
dim = 'height';
3028
lower = 'b';
3129
upper = 't';
32-
d = 1 - d;
3330
} else {
3431
throw "Bad axis letter: " + axis;
3532
}
3633
var plotwidth = layout[dim] - layout.margin[lower] - layout.margin[upper];
3734
var domwidth = (domain[1] - domain[0]) * plotwidth;
35+
if (dim === 'height') {
36+
// y-axes relative to bottom of plot in plotly.js
37+
return layout[dim] - (layout.margin[lower] + domain[0] * plotwidth + domwidth * d);
38+
}
3839
return layout.margin[lower] + domain[0] * plotwidth + domwidth * d;
3940
}
4041

@@ -50,6 +51,9 @@ function mapDomainToPixel(layout, axis, d) {
5051

5152
// Here axis must have the same form as in layout, e.g., xaxis, yaxis2, etc.
5253
function mapRangeToPixel(layout, axis, r) {
54+
if (layout[axis].type === 'log') {
55+
r = Math.log10(r);
56+
}
5357
var d = (r - layout[axis].range[0]) / (layout[axis].range[1] - layout[axis].range[0]);
5458
return mapDomainToPixel(layout, axis, d);
5559
}

0 commit comments

Comments
 (0)