Skip to content

Commit 49b8141

Browse files
committed
labels on contourcarpet
also a bugfix to findBestTextLocation - surprised that didn't break anything before!
1 parent 7f086e0 commit 49b8141

File tree

4 files changed

+193
-27
lines changed

4 files changed

+193
-27
lines changed

src/traces/contour/plot.js

+19-19
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) {
280280
// In this case we'll remove the lines after making the labels.
281281
var linegroup = exports.createLines(lineContainer, showLines || showLabels, pathinfo);
282282

283-
var lineClip = createLineClip(lineContainer, clipLinesForLabels,
283+
var lineClip = exports.createLineClip(lineContainer, clipLinesForLabels,
284284
gd._fullLayout._defs, cd0.trace.uid);
285285

286286
var labelGroup = plotgroup.selectAll('g.contourlabels')
@@ -299,7 +299,7 @@ function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) {
299299
// invalidate the getTextLocation cache in case paths changed
300300
Lib.clearLocationCache();
301301

302-
var contourFormat = labelFormatter(contours, cd0.t.cb, gd._fullLayout);
302+
var contourFormat = exports.labelFormatter(contours, cd0.t.cb, gd._fullLayout);
303303

304304
var dummyText = Drawing.tester.append('text')
305305
.attr('data-notex', 1)
@@ -326,7 +326,7 @@ function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) {
326326
Math.max(1, pathinfo.length / constants.LABELINCREASE);
327327

328328
linegroup.each(function(d) {
329-
var textOpts = calcTextOpts(d.level, contourFormat, dummyText, gd);
329+
var textOpts = exports.calcTextOpts(d.level, contourFormat, dummyText, gd);
330330

331331
d3.select(this).selectAll('path').each(function() {
332332
var path = this;
@@ -339,19 +339,19 @@ function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) {
339339
constants.LABELMAX);
340340

341341
for(var i = 0; i < maxLabels; i++) {
342-
var loc = findBestTextLocation(path, pathBounds, textOpts,
342+
var loc = exports.findBestTextLocation(path, pathBounds, textOpts,
343343
labelData, bounds);
344344

345345
if(!loc) break;
346346

347-
addLabelData(loc, textOpts, labelData, labelClipPathData);
347+
exports.addLabelData(loc, textOpts, labelData, labelClipPathData);
348348
}
349349
});
350350
});
351351

352352
dummyText.remove();
353353

354-
drawLabels(labelGroup, labelData, gd, lineClip,
354+
exports.drawLabels(labelGroup, labelData, gd, lineClip,
355355
clipLinesForLabels ? labelClipPathData : null);
356356
}
357357

@@ -403,7 +403,7 @@ exports.createLines = function(lineContainer, makeLines, pathinfo) {
403403
return linegroup;
404404
};
405405

406-
function createLineClip(lineContainer, clipLinesForLabels, defs, uid) {
406+
exports.createLineClip = function(lineContainer, clipLinesForLabels, defs, uid) {
407407
var clipId = clipLinesForLabels ? ('clipline' + uid) : null;
408408

409409
var lineClip = defs.select('.clips').selectAll('#' + clipId)
@@ -417,9 +417,9 @@ function createLineClip(lineContainer, clipLinesForLabels, defs, uid) {
417417
Drawing.setClipUrl(lineContainer, clipId);
418418

419419
return lineClip;
420-
}
420+
};
421421

422-
function labelFormatter(contours, colorbar, fullLayout) {
422+
exports.labelFormatter = function(contours, colorbar, fullLayout) {
423423
if(contours.labelformat) {
424424
return d3.format(contours.labelformat);
425425
}
@@ -446,9 +446,9 @@ function labelFormatter(contours, colorbar, fullLayout) {
446446
return Axes.tickText(formatAxis, v).text;
447447
};
448448
}
449-
}
449+
};
450450

451-
function calcTextOpts(level, contourFormat, dummyText, gd) {
451+
exports.calcTextOpts = function(level, contourFormat, dummyText, gd) {
452452
var text = contourFormat(level);
453453
dummyText.text(text)
454454
.call(svgTextUtils.convertToTspans, gd);
@@ -461,9 +461,9 @@ function calcTextOpts(level, contourFormat, dummyText, gd) {
461461
level: level,
462462
dy: (bBox.top + bBox.bottom) / 2
463463
};
464-
}
464+
};
465465

466-
function findBestTextLocation(path, pathBounds, textOpts, labelData, plotBounds) {
466+
exports.findBestTextLocation = function(path, pathBounds, textOpts, labelData, plotBounds) {
467467
var textWidth = textOpts.width;
468468

469469
var p0, dp, pMax, pMin, loc;
@@ -493,12 +493,12 @@ function findBestTextLocation(path, pathBounds, textOpts, labelData, plotBounds)
493493

494494
// subsequent iterations just look half steps away from the
495495
// best we found in the previous iteration
496-
p0 = pMin - dp / 2;
497496
if(j) dp /= 2;
497+
p0 = pMin - dp / 2;
498498
pMax = p0 + dp * 1.5;
499499
}
500500
if(cost <= costConstants.MAXCOST) return loc;
501-
}
501+
};
502502

503503
/*
504504
* locationCost: a cost function for label locations
@@ -557,7 +557,7 @@ function locationCost(loc, textOpts, labelData, bounds) {
557557
return cost;
558558
}
559559

560-
function addLabelData(loc, textOpts, labelData, labelClipPathData) {
560+
exports.addLabelData = function(loc, textOpts, labelData, labelClipPathData) {
561561
var halfWidth = textOpts.width / 2;
562562
var halfHeight = textOpts.height / 2;
563563

@@ -590,9 +590,9 @@ function addLabelData(loc, textOpts, labelData, labelClipPathData) {
590590
});
591591

592592
labelClipPathData.push(bBoxPts);
593-
}
593+
};
594594

595-
function drawLabels(labelGroup, labelData, gd, lineClip, labelClipPathData) {
595+
exports.drawLabels = function(labelGroup, labelData, gd, lineClip, labelClipPathData) {
596596
var labels = labelGroup.selectAll('text')
597597
.data(labelData, function(d) {
598598
return d.text + ',' + d.x + ',' + d.y + ',' + d.theta;
@@ -628,7 +628,7 @@ function drawLabels(labelGroup, labelData, gd, lineClip, labelClipPathData) {
628628
lineClipPath.enter().append('path');
629629
lineClipPath.attr('d', clipPath);
630630
}
631-
}
631+
};
632632

633633
function clipGaps(plotGroup, plotinfo, defs, cd0, perimeter) {
634634
var clipId = 'clip' + cd0.trace.uid;

src/traces/contourcarpet/plot.js

+165-4
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ var d3 = require('d3');
1212
var map1dArray = require('../carpet/map_1d_array');
1313
var makepath = require('../carpet/makepath');
1414
var Drawing = require('../../components/drawing');
15+
var Lib = require('../../lib');
1516

1617
var makeCrossings = require('../contour/make_crossings');
1718
var findAllPaths = require('../contour/find_all_paths');
1819
var contourPlot = require('../contour/plot');
20+
var constants = require('../contour/constants');
1921
var convertToConstraints = require('./convert_to_constraints');
2022
var joinAllPaths = require('./join_all_paths');
2123
var emptyPathinfo = require('./empty_pathinfo');
2224
var mapPathinfo = require('./map_pathinfo');
2325
var lookupCarpet = require('../carpet/lookup_carpetid');
2426
var closeBoundaries = require('./close_boundaries');
2527

28+
2629
module.exports = function plot(gd, plotinfo, cdcontours) {
2730
for(var i = 0; i < cdcontours.length; i++) {
2831
plotOne(gd, plotinfo, cdcontours[i]);
@@ -116,20 +119,178 @@ function plotOne(gd, plotinfo, cd) {
116119
makeFills(trace, plotGroup, xa, ya, pathinfo, perimeter, ab2p, carpet, carpetcd, contours.coloring, boundaryPath);
117120

118121
// Draw contour lines:
119-
makeLines(plotGroup, pathinfo, contours);
122+
makeLinesAndLabels(plotGroup, pathinfo, gd, cd[0], contours, plotinfo, carpet);
120123

121124
// Clip the boundary of the plot
122125
Drawing.setClipUrl(plotGroup, carpet._clipPathId);
123126
}
124127

125-
function makeLines(plotgroup, pathinfo, contours) {
128+
function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, plotinfo, carpet) {
126129
var lineContainer = plotgroup.selectAll('g.contourlines').data([0]);
127130

128131
lineContainer.enter().append('g')
129132
.classed('contourlines', true);
130133

131-
contourPlot.createLines(lineContainer,
132-
contours.showlines !== false, pathinfo);
134+
var showLines = contours.showlines !== false;
135+
var showLabels = contours.showlabels;
136+
var clipLinesForLabels = showLines && showLabels;
137+
138+
// Even if we're not going to show lines, we need to create them
139+
// if we're showing labels, because the fill paths include the perimeter
140+
// so can't be used to position the labels correctly.
141+
// In this case we'll remove the lines after making the labels.
142+
var linegroup = contourPlot.createLines(lineContainer, showLines || showLabels, pathinfo);
143+
144+
var lineClip = contourPlot.createLineClip(lineContainer, clipLinesForLabels,
145+
gd._fullLayout._defs, cd0.trace.uid);
146+
147+
var labelGroup = plotgroup.selectAll('g.contourlabels')
148+
.data(showLabels ? [0] : []);
149+
150+
labelGroup.exit().remove();
151+
152+
labelGroup.enter().append('g')
153+
.classed('contourlabels', true);
154+
155+
if(showLabels) {
156+
var xa = plotinfo.xaxis;
157+
var ya = plotinfo.yaxis;
158+
var xLen = xa._length;
159+
var yLen = ya._length;
160+
// for simplicity use the xy box for label clipping outline.
161+
var labelClipPathData = [[
162+
[0, 0],
163+
[xLen, 0],
164+
[xLen, yLen],
165+
[0, yLen]
166+
]];
167+
168+
169+
var labelData = [];
170+
171+
// invalidate the getTextLocation cache in case paths changed
172+
Lib.clearLocationCache();
173+
174+
var contourFormat = contourPlot.labelFormatter(contours, cd0.t.cb, gd._fullLayout);
175+
176+
var dummyText = Drawing.tester.append('text')
177+
.attr('data-notex', 1)
178+
.call(Drawing.font, contours.labelfont);
179+
180+
// for now at least, contourcarpet pushes labels only away from
181+
// the xy box edges, not the edges of the carpet.
182+
// TODO: is this OK?
183+
var bounds = {
184+
left: 0,
185+
right: xLen,
186+
center: xLen / 2,
187+
top: 0,
188+
bottom: yLen,
189+
middle: yLen / 2
190+
};
191+
192+
var plotDiagonal = Math.sqrt(xLen * xLen + yLen * yLen);
193+
194+
// the path length to use to scale the number of labels to draw:
195+
var normLength = constants.LABELDISTANCE * plotDiagonal /
196+
Math.max(1, pathinfo.length / constants.LABELINCREASE);
197+
198+
linegroup.each(function(d) {
199+
var textOpts = contourPlot.calcTextOpts(d.level, contourFormat, dummyText, gd);
200+
201+
d3.select(this).selectAll('path').each(function(pathData) {
202+
var path = this;
203+
var pathBounds = Lib.getVisibleSegment(path, bounds, textOpts.height / 2);
204+
if(!pathBounds) return;
205+
206+
constrainToCarpet(path, pathData, d, pathBounds, carpet, textOpts.height);
207+
208+
if(pathBounds.len < (textOpts.width + textOpts.height) * constants.LABELMIN) return;
209+
210+
var maxLabels = Math.min(Math.ceil(pathBounds.len / normLength),
211+
constants.LABELMAX);
212+
213+
for(var i = 0; i < maxLabels; i++) {
214+
var loc = contourPlot.findBestTextLocation(path, pathBounds, textOpts,
215+
labelData, bounds);
216+
217+
if(!loc) break;
218+
219+
contourPlot.addLabelData(loc, textOpts, labelData, labelClipPathData);
220+
}
221+
});
222+
});
223+
224+
dummyText.remove();
225+
226+
contourPlot.drawLabels(labelGroup, labelData, gd, lineClip,
227+
clipLinesForLabels ? labelClipPathData : null);
228+
}
229+
230+
if(showLabels && !showLines) linegroup.remove();
231+
}
232+
233+
// figure out if this path goes off the edge of the carpet
234+
// and shorten the part we call visible to keep labels away from the edge
235+
function constrainToCarpet(path, pathData, levelData, pathBounds, carpet, textHeight) {
236+
var pathABData;
237+
for(var i = 0; i < levelData.pedgepaths.length; i++) {
238+
if(pathData === levelData.pedgepaths[i]) {
239+
pathABData = levelData.edgepaths[i];
240+
}
241+
}
242+
if(!pathABData) return;
243+
244+
var aMin = carpet.a[0];
245+
var aMax = carpet.a[carpet.a.length - 1];
246+
var bMin = carpet.b[0];
247+
var bMax = carpet.b[carpet.b.length - 1];
248+
249+
function getOffset(abPt, pathVector) {
250+
var offset = 0;
251+
var edgeVector;
252+
var dAB = 0.1;
253+
if(Math.abs(abPt[0] - aMin) < dAB || Math.abs(abPt[0] - aMax) < dAB) {
254+
edgeVector = normalizeVector(carpet.dxydb_rough(abPt[0], abPt[1], dAB));
255+
offset = Math.max(offset, textHeight * vectorTan(pathVector, edgeVector) / 2);
256+
}
257+
258+
if(Math.abs(abPt[1] - bMin) < dAB || Math.abs(abPt[1] - bMax) < dAB) {
259+
edgeVector = normalizeVector(carpet.dxyda_rough(abPt[0], abPt[1], dAB));
260+
offset = Math.max(offset, textHeight * vectorTan(pathVector, edgeVector) / 2);
261+
}
262+
return offset;
263+
}
264+
265+
var startVector = getUnitVector(path, 0, 1);
266+
var endVector = getUnitVector(path, pathBounds.total, pathBounds.total - 1);
267+
var minStart = getOffset(pathABData[0], startVector);
268+
var maxEnd = pathBounds.total - getOffset(pathABData[pathABData.length - 1], endVector);
269+
270+
if(pathBounds.min < minStart) pathBounds.min = minStart;
271+
if(pathBounds.max > maxEnd) pathBounds.max = maxEnd;
272+
273+
pathBounds.len = pathBounds.max - pathBounds.min;
274+
}
275+
276+
function getUnitVector(path, p0, p1) {
277+
var pt0 = path.getPointAtLength(p0);
278+
var pt1 = path.getPointAtLength(p1);
279+
var dx = pt1.x - pt0.x;
280+
var dy = pt1.y - pt0.y;
281+
var len = Math.sqrt(dx * dx + dy * dy);
282+
return [dx / len, dy / len];
283+
}
284+
285+
function normalizeVector(v) {
286+
var len = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
287+
return [v[0] / len, v[1] / len];
288+
}
289+
290+
function vectorTan(v0, v1) {
291+
var cos = Math.abs(v0[0] * v1[0] + v0[1] * v1[1]);
292+
var sin = Math.sqrt(1 - cos * cos);
293+
return sin / cos;
133294
}
134295

135296
function makeBackground(plotgroup, clipsegments, xaxis, yaxis, isConstraint, coloring) {
7.43 KB
Loading

test/image/mocks/cheater_contour.json

+9-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"contours":{
88
"start":1,
99
"end":14,
10-
"size":1
10+
"size":1,
11+
"showlabels": true,
12+
"labelfont": {"size": 9, "color": "#ff0"}
1113
},
1214
"line":{
1315
"width":2,
@@ -54,7 +56,8 @@
5456
"start":1,
5557
"end":14,
5658
"size":1,
57-
"coloring": "lines"
59+
"coloring": "lines",
60+
"showlabels": true
5861
},
5962
"line":{
6063
"width":2
@@ -148,10 +151,12 @@
148151
"contours":{
149152
"start":-5,
150153
"end":20,
151-
"size":1
154+
"size":1,
155+
"showlabels": true
152156
},
153157
"line":{
154-
"width":2
158+
"width":2,
159+
"color": "#fff"
155160
},
156161
"colorbar": {
157162
"len": 0.4,

0 commit comments

Comments
 (0)