@@ -13,7 +13,10 @@ var d3 = require('d3');
13
13
var isNumeric = require ( 'fast-isnumeric' ) ;
14
14
15
15
var Lib = require ( '../../lib' ) ;
16
+ var svgTextUtils = require ( '../../lib/svg_text_utils' ) ;
17
+
16
18
var Color = require ( '../../components/color' ) ;
19
+ var Drawing = require ( '../../components/drawing' ) ;
17
20
var ErrorBars = require ( '../../components/errorbars' ) ;
18
21
19
22
var arraysToCalcdata = require ( './arrays_to_calcdata' ) ;
@@ -42,9 +45,9 @@ module.exports = function plot(gd, plotinfo, cdbar) {
42
45
43
46
arraysToCalcdata ( d ) ;
44
47
45
- d3 . select ( this ) . selectAll ( 'path ' )
48
+ d3 . select ( this ) . selectAll ( 'g.point ' )
46
49
. data ( Lib . identity )
47
- . enter ( ) . append ( 'path' )
50
+ . enter ( ) . append ( 'g' ) . classed ( 'point' , true )
48
51
. each ( function ( di , i ) {
49
52
// now display the bar
50
53
// clipped xf/yf (2nd arg true): non-positive
@@ -75,15 +78,18 @@ module.exports = function plot(gd, plotinfo, cdbar) {
75
78
d3 . select ( this ) . remove ( ) ;
76
79
return ;
77
80
}
81
+
78
82
var lw = ( di . mlw + 1 || trace . marker . line . width + 1 ||
79
83
( di . trace ? di . trace . marker . line . width : 0 ) + 1 ) - 1 ,
80
84
offset = d3 . round ( ( lw / 2 ) % 1 , 2 ) ;
85
+
81
86
function roundWithLine ( v ) {
82
87
// if there are explicit gaps, don't round,
83
88
// it can make the gaps look crappy
84
89
return ( fullLayout . bargap === 0 && fullLayout . bargroupgap === 0 ) ?
85
90
d3 . round ( Math . round ( v ) - offset , 2 ) : v ;
86
91
}
92
+
87
93
function expandToVisible ( v , vc ) {
88
94
// if it's not in danger of disappearing entirely,
89
95
// round more precisely
@@ -93,6 +99,7 @@ module.exports = function plot(gd, plotinfo, cdbar) {
93
99
// its neighbor
94
100
( v > vc ? Math . ceil ( v ) : Math . floor ( v ) ) ;
95
101
}
102
+
96
103
if ( ! gd . _context . staticPlot ) {
97
104
// if bars are not fully opaque or they have a line
98
105
// around them, round to integer pixels, mainly for
@@ -108,12 +115,274 @@ module.exports = function plot(gd, plotinfo, cdbar) {
108
115
y0 = fixpx ( y0 , y1 ) ;
109
116
y1 = fixpx ( y1 , y0 ) ;
110
117
}
111
- d3 . select ( this ) . attr ( 'd' ,
118
+
119
+ // append bar path and text
120
+ var bar = d3 . select ( this ) ;
121
+
122
+ bar . append ( 'path' ) . attr ( 'd' ,
112
123
'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z' ) ;
124
+
125
+ appendBarText ( gd , bar , d , i , x0 , x1 , y0 , y1 ) ;
113
126
} ) ;
114
127
} ) ;
115
128
116
129
// error bars are on the top
117
130
bartraces . call ( ErrorBars . plot , plotinfo ) ;
118
131
119
132
} ;
133
+
134
+ function appendBarText ( gd , bar , calcTrace , i , x0 , x1 , y0 , y1 ) {
135
+ var trace = calcTrace [ 0 ] . trace ;
136
+
137
+ // get bar text
138
+ var traceText = trace . text ;
139
+ if ( ! traceText ) return ;
140
+
141
+ var text ;
142
+ if ( Array . isArray ( traceText ) ) {
143
+ if ( i >= traceText . length ) return ;
144
+ text = traceText [ i ] ;
145
+ }
146
+ else {
147
+ text = traceText ;
148
+ }
149
+
150
+ // get text position
151
+ var traceTextPosition = trace . textposition ,
152
+ textPosition ;
153
+ if ( Array . isArray ( traceTextPosition ) ) {
154
+ if ( i >= traceTextPosition . length ) return ;
155
+ textPosition = traceTextPosition [ i ] ;
156
+ }
157
+ else {
158
+ textPosition = traceTextPosition ;
159
+ }
160
+
161
+ if ( textPosition === 'none' ) return ;
162
+
163
+ var barWidth = Math . abs ( x1 - x0 ) ,
164
+ barHeight = Math . abs ( y1 - y0 ) ,
165
+ barIsTooSmall = ( barWidth < 8 || barHeight < 8 ) ,
166
+
167
+ barmode = gd . _fullLayout . barmode ,
168
+ inStackMode = ( barmode === 'stack' ) ,
169
+ inRelativeMode = ( barmode === 'relative' ) ,
170
+ inStackOrRelativeMode = inStackMode || inRelativeMode ,
171
+
172
+ calcBar = calcTrace [ i ] ,
173
+ isOutmostBar = ! inStackOrRelativeMode || calcBar . _outmost ;
174
+
175
+ if ( textPosition === 'auto' ) {
176
+ textPosition = ( barIsTooSmall && isOutmostBar ) ? 'outside' : 'inside' ;
177
+ }
178
+
179
+ if ( textPosition === 'outside' ) {
180
+ if ( ! isOutmostBar ) textPosition = 'inside' ;
181
+ }
182
+
183
+ if ( textPosition === 'inside' ) {
184
+ if ( barIsTooSmall ) return ;
185
+ }
186
+
187
+
188
+ // get text font
189
+ var textFont ;
190
+
191
+ if ( textPosition === 'outside' ) {
192
+ var traceOutsideTextFont = trace . outsidetextfont ;
193
+ if ( Array . isArray ( traceOutsideTextFont ) ) {
194
+ if ( i >= traceOutsideTextFont . length ) return ;
195
+ textFont = traceOutsideTextFont [ i ] ;
196
+ }
197
+ else {
198
+ textFont = traceOutsideTextFont ;
199
+ }
200
+ }
201
+ else {
202
+ var traceInsideTextFont = trace . insidetextfont ;
203
+ if ( Array . isArray ( traceInsideTextFont ) ) {
204
+ if ( i >= traceInsideTextFont . length ) return ;
205
+ textFont = traceInsideTextFont [ i ] ;
206
+ }
207
+ else {
208
+ textFont = traceInsideTextFont ;
209
+ }
210
+ }
211
+
212
+ if ( ! textFont ) {
213
+ var traceTextFont = trace . textfont ;
214
+ if ( Array . isArray ( traceTextFont ) ) {
215
+ if ( i >= traceTextFont . length ) return ;
216
+ textFont = traceTextFont [ i ] ;
217
+ }
218
+ else {
219
+ textFont = traceTextFont ;
220
+ }
221
+ }
222
+
223
+ if ( ! textFont ) {
224
+ textFont = gd . _fullLayout . font ;
225
+ }
226
+
227
+ // append bar text
228
+ var textSelection = bar . append ( 'text' )
229
+ // prohibit tex interpretation until we can handle
230
+ // tex and regular text together
231
+ . attr ( 'data-notex' , 1 )
232
+ . text ( text )
233
+ . attr ( {
234
+ 'class' : 'bartext' ,
235
+ transform : '' ,
236
+ 'data-bb' : '' ,
237
+ 'text-anchor' : 'middle' ,
238
+ x : 0 ,
239
+ y : 0
240
+ } )
241
+ . call ( Drawing . font , textFont ) ;
242
+
243
+ textSelection . call ( svgTextUtils . convertToTspans ) ;
244
+ textSelection . selectAll ( 'tspan.line' ) . attr ( { x : 0 , y : 0 } ) ;
245
+
246
+ // position bar text
247
+ var textBB = Drawing . bBox ( textSelection . node ( ) ) ,
248
+ textWidth = textBB . width ,
249
+ textHeight = textBB . height ;
250
+ if ( ! textWidth || ! textHeight ) {
251
+ textSelection . remove ( ) ;
252
+ return ;
253
+ }
254
+
255
+ // compute translate transform
256
+ var transform ;
257
+ if ( textPosition === 'outside' ) {
258
+ transform = getTransformToMoveOutsideBar ( x0 , x1 , y0 , y1 , textBB ,
259
+ trace . orientation ) ;
260
+ }
261
+ else {
262
+ transform = getTransformToMoveInsideBar ( x0 , x1 , y0 , y1 , textBB ) ;
263
+ }
264
+
265
+ textSelection . attr ( 'transform' , transform ) ;
266
+ }
267
+
268
+ function getTransformToMoveInsideBar ( x0 , x1 , y0 , y1 , textBB ) {
269
+ // compute text and target positions
270
+ var barWidth = Math . abs ( x1 - x0 ) ,
271
+ barHeight = Math . abs ( y1 - y0 ) ,
272
+ textWidth = textBB . width ,
273
+ textHeight = textBB . height ,
274
+ barX = ( x0 + x1 ) / 2 ,
275
+ barY = ( y0 + y1 ) / 2 ,
276
+ textX = ( textBB . left + textBB . right ) / 2 ,
277
+ textY = ( textBB . top + textBB . bottom ) / 2 ;
278
+
279
+ // apply target padding
280
+ var targetWidth = 0.95 * barWidth ,
281
+ targetHeight = 0.95 * barHeight ;
282
+
283
+ return getTransform (
284
+ textX , textY , textWidth , textHeight ,
285
+ barX , barY , targetWidth , targetHeight ) ;
286
+ }
287
+
288
+ function getTransformToMoveOutsideBar ( x0 , x1 , y0 , y1 , textBB , orientation ) {
289
+
290
+ // compute text and target positions
291
+ var textWidth = textBB . width ,
292
+ textHeight = textBB . height ,
293
+ textX = ( textBB . left + textBB . right ) / 2 ,
294
+ textY = ( textBB . top + textBB . bottom ) / 2 ;
295
+
296
+ var targetWidth , targetHeight ,
297
+ targetX , targetY ;
298
+ if ( orientation === 'h' ) {
299
+ if ( x1 < x0 ) {
300
+ // bar end is on the left hand side
301
+ targetWidth = 2 + textWidth ; // padding included
302
+ targetHeight = Math . abs ( y1 - y0 ) ;
303
+ targetX = x1 - targetWidth / 2 ;
304
+ targetY = ( y0 + y1 ) / 2 ;
305
+ }
306
+ else {
307
+ targetWidth = 2 + textWidth ; // padding included
308
+ targetHeight = Math . abs ( y1 - y0 ) ;
309
+ targetX = x1 + targetWidth / 2 ;
310
+ targetY = ( y0 + y1 ) / 2 ;
311
+ }
312
+ }
313
+ else {
314
+ if ( y1 > y0 ) {
315
+ // bar end is on the bottom
316
+ targetWidth = Math . abs ( x1 - x0 ) ;
317
+ targetHeight = 2 + textHeight ; // padding included
318
+ targetX = ( x0 + x1 ) / 2 ;
319
+ targetY = y1 + targetHeight / 2 ;
320
+ }
321
+ else {
322
+ targetWidth = Math . abs ( x1 - x0 ) ;
323
+ targetHeight = 2 + textHeight ; // padding included
324
+ targetX = ( x0 + x1 ) / 2 ;
325
+ targetY = y1 - targetHeight / 2 ;
326
+ }
327
+ }
328
+
329
+ return getTransform (
330
+ textX , textY , textWidth , textHeight ,
331
+ targetX , targetY , targetWidth , targetHeight ) ;
332
+ }
333
+
334
+ /**
335
+ * Compute SVG transform to move a text box into a target box
336
+ *
337
+ * @param {number } textX X pixel coord of the text box center
338
+ * @param {number } textY Y pixel coord of the text box center
339
+ * @param {number } textWidth text box width
340
+ * @param {number } textHeight text box height
341
+ * @param {number } targetX X pixel coord of the target box center
342
+ * @param {number } targetY Y pixel coord of the target box center
343
+ * @param {number } targetWidth target box width
344
+ * @param {number } targetHeight target box height
345
+ *
346
+ * @returns {string } SVG transform
347
+ */
348
+ function getTransform (
349
+ textX , textY , textWidth , textHeight ,
350
+ targetX , targetY , targetWidth , targetHeight ) {
351
+
352
+ // compute translate transform
353
+ var translateX = targetX - textX ,
354
+ translateY = targetY - textY ,
355
+ translate = 'translate(' + translateX + ' ' + translateY + ')' ;
356
+
357
+ // if bar text doesn't fit, compute rotate and scale transforms
358
+ var doesntFit = ( textWidth > targetWidth || textHeight > targetHeight ) ,
359
+ rotate , scale , scaleX , scaleY ;
360
+
361
+ if ( doesntFit ) {
362
+ var textIsHorizontal = ( textWidth > textHeight ) ,
363
+ targetIsHorizontal = ( targetWidth > targetHeight ) ;
364
+ if ( textIsHorizontal !== targetIsHorizontal ) {
365
+ rotate = 'rotate(-90 ' + textX + ' ' + textY + ')' ;
366
+ scaleX = targetWidth / textHeight ;
367
+ scaleY = targetHeight / textWidth ;
368
+ }
369
+ else {
370
+ scaleX = targetWidth / textWidth ;
371
+ scaleY = targetHeight / textHeight ;
372
+ }
373
+
374
+ if ( scaleX > 1 ) scaleX = 1 ;
375
+ if ( scaleY > 1 ) scaleY = 1 ;
376
+
377
+ if ( scaleX !== 1 || scaleY !== 1 ) {
378
+ scale = 'scale(' + scaleX + ' ' + scaleY + ')' ;
379
+ }
380
+ }
381
+
382
+ // compute transform
383
+ var transform = translate ;
384
+ if ( scale ) transform += ' ' + scale ;
385
+ if ( rotate ) transform += ' ' + rotate ;
386
+
387
+ return transform ;
388
+ }
0 commit comments