Skip to content

Commit 304174a

Browse files
committed
Add current value output to sliders
1 parent 8fe1842 commit 304174a

File tree

4 files changed

+132
-11
lines changed

4 files changed

+132
-11
lines changed

src/components/sliders/attributes.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,50 @@ module.exports = {
187187
},
188188
},
189189

190+
currentvalue: {
191+
visible: {
192+
valType: 'boolean',
193+
dflt: true,
194+
description: [
195+
'Shows the currently-selected value above the slider.'
196+
].join(' ')
197+
},
198+
199+
xanchor: {
200+
valType: 'enumerated',
201+
values: ['left', 'center', 'right'],
202+
dflt: 'left',
203+
description: [
204+
'The alignment of the value readout relative to the length of the slider.'
205+
].join(' ')
206+
},
207+
208+
offset: {
209+
valType: 'number',
210+
dflt: 10,
211+
role: 'info',
212+
description: [
213+
'The amount of space, in pixels, between the current value label',
214+
'and the slider.'
215+
]
216+
},
217+
218+
prefix: {
219+
valType: 'string',
220+
role: 'info',
221+
description: [
222+
'When `currentvalue.visible` is true, this sets the prefix of the lable. If provided,',
223+
'it will be joined to the current value with a single space between.'
224+
].join(' ')
225+
},
226+
227+
font: extendFlat({}, fontAttrs, {
228+
description: 'Sets the font of the current value lable text.'
229+
}),
230+
},
231+
190232
font: extendFlat({}, fontAttrs, {
191-
description: 'Sets the font of the slider button text.'
233+
description: 'Sets the font of the slider step labels.'
192234
}),
193235

194236
bgcolor: {

src/components/sliders/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module.exports = {
2828
labelsClass: 'slider-labels',
2929
labelGroupClass: 'slider-label-group',
3030
labelClass: 'slider-label',
31+
currentValueClass: 'slider-current-value',
3132

3233
railHeight: 5,
3334

@@ -87,4 +88,8 @@ module.exports = {
8788
minorTickOffset: 25,
8889
minorTickColor: '#333',
8990
minorTickLength: 4,
91+
92+
// Extra space below the current value label:
93+
currentValuePadding: 8,
94+
currentValueInset: 0,
9095
};

src/components/sliders/defaults.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ function sliderDefaults(sliderIn, sliderOut, layoutOut) {
6565
coerce('pad.b');
6666
coerce('pad.l');
6767

68+
coerce('currentvalue.visible');
69+
coerce('currentvalue.xanchor');
70+
coerce('currentvalue.prefix');
71+
coerce('currentvalue.offset');
72+
6873
coerce('updateevent');
6974
coerce('updatevalue');
7075

71-
if(!sliderIn.transition) {
72-
sliderIn.transition = {};
73-
}
74-
7576
coerce('transition.duration');
7677
coerce('transition.easing');
7778

@@ -84,6 +85,7 @@ function sliderDefaults(sliderIn, sliderOut, layoutOut) {
8485
}
8586

8687
Lib.coerceFont(coerce, 'font', layoutOut.font);
88+
Lib.coerceFont(coerce, 'currentvalue.font', layoutOut.font);
8789

8890
coerce('bgcolor', layoutOut.paper_bgcolor);
8991
coerce('bordercolor');

src/components/sliders/draw.js

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,26 @@ function findDimensions(gd, sliderOpts) {
127127
constants.gripHeight
128128
);
129129

130+
sliderOpts.currentValueMaxWidth = 0;
131+
sliderOpts.currentValueHeight = 0;
132+
sliderOpts.currentValueTotalHeight = 0;
133+
134+
if(sliderOpts.currentvalue.visible) {
135+
// Get the dimensions of the current value label:
136+
var dummyGroup = gd._tester.append('g');
137+
138+
sliderLabels.each(function(stepOpts) {
139+
var curValPrefix = drawCurrentValue(dummyGroup, sliderOpts, stepOpts.label);
140+
var curValSize = curValPrefix.node() && Drawing.bBox(curValPrefix.node());
141+
sliderOpts.currentValueMaxWidth = Math.max(sliderOpts.currentValueMaxWidth, Math.ceil(curValSize.width));
142+
sliderOpts.currentValueHeight = Math.max(sliderOpts.currentValueHeight, Math.ceil(curValSize.height));
143+
});
144+
145+
sliderOpts.currentValueTotalHeight = sliderOpts.currentValueHeight + sliderOpts.currentvalue.offset;
146+
147+
dummyGroup.remove();
148+
}
149+
130150
var graphSize = gd._fullLayout._size;
131151
sliderOpts.lx = graphSize.l + graphSize.w * sliderOpts.x;
132152
sliderOpts.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y);
@@ -153,7 +173,7 @@ function findDimensions(gd, sliderOpts) {
153173
sliderOpts.labelStride = Math.max(1, Math.ceil(computedSpacePerLabel / availableSpacePerLabel));
154174
sliderOpts.labelHeight = labelHeight;
155175

156-
sliderOpts.height = constants.tickOffset + constants.tickLength + constants.labelOffset + sliderOpts.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b;
176+
sliderOpts.height = sliderOpts.currentValueTotalHeight + constants.tickOffset + constants.tickLength + constants.labelOffset + sliderOpts.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b;
157177

158178
var xanchor = 'left';
159179
if(anchorUtils.isRightAnchor(sliderOpts)) {
@@ -193,6 +213,7 @@ function findDimensions(gd, sliderOpts) {
193213
function drawSlider(gd, sliderGroup, sliderOpts) {
194214
// These are carefully ordered for proper z-ordering:
195215
sliderGroup
216+
.call(drawCurrentValue, sliderOpts)
196217
.call(drawRail, sliderOpts)
197218
.call(drawLabelGroup, sliderOpts)
198219
.call(drawTicks, sliderOpts)
@@ -210,6 +231,53 @@ function drawSlider(gd, sliderGroup, sliderOpts) {
210231
setActive(gd, sliderGroup, sliderOpts, sliderOpts.active, true, false);
211232
}
212233

234+
function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) {
235+
if(!sliderOpts.currentvalue.visible) return;
236+
237+
var x0, textAnchor;
238+
var text = sliderGroup.selectAll('text')
239+
.data([0]);
240+
241+
switch(sliderOpts.currentvalue.xanchor) {
242+
case 'right':
243+
// This is anchored left and adjusted by the width of the longest label
244+
// so that the prefix doesn't move. The goal of this is to emphasize
245+
// what's actually changing and make the update less distracting.
246+
x0 = sliderOpts.inputAreaLength - constants.currentValueInset - sliderOpts.currentValueMaxWidth;
247+
textAnchor = 'left';
248+
break;
249+
case 'center':
250+
x0 = sliderOpts.inputAreaLength * 0.5;
251+
textAnchor = 'middle';
252+
break;
253+
default:
254+
x0 = constants.currentValueInset;
255+
textAnchor = 'left';
256+
}
257+
258+
text.enter().append('text')
259+
.classed(constants.labelClass, true)
260+
.classed('user-select-none', true)
261+
.attr('text-anchor', textAnchor);
262+
263+
var str = sliderOpts.currentvalue.prefix ? (sliderOpts.currentvalue.prefix + ' ') : '';
264+
265+
if(typeof valueOverride === 'string') {
266+
str += valueOverride;
267+
} else {
268+
var curVal = sliderOpts.steps[sliderOpts.active].label;
269+
str += curVal;
270+
}
271+
272+
text.call(Drawing.font, sliderOpts.currentvalue.font)
273+
.text(str)
274+
.call(svgTextUtils.convertToTspans);
275+
276+
Lib.setTranslate(text, x0, sliderOpts.currentValueHeight);
277+
278+
return text;
279+
}
280+
213281
function removeListeners(gd, sliderGroup, sliderOpts) {
214282
var listeners = sliderOpts._input.listeners;
215283
var eventNames = sliderOpts._input.eventNames;
@@ -310,7 +378,7 @@ function drawLabelGroup(sliderGroup, sliderOpts) {
310378

311379
Lib.setTranslate(item,
312380
normalizedValueToPosition(sliderOpts, d.fraction),
313-
constants.tickOffset + constants.tickLength + sliderOpts.labelHeight + constants.labelOffset
381+
constants.tickOffset + constants.tickLength + sliderOpts.labelHeight + constants.labelOffset + sliderOpts.currentValueTotalHeight
314382
);
315383
});
316384

@@ -346,6 +414,7 @@ function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition)
346414
var step = sliderOpts.steps[sliderOpts.active];
347415

348416
sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), doTransition);
417+
sliderGroup.call(drawCurrentValue, sliderOpts);
349418

350419
if(step && step.method && doCallback) {
351420
if(sliderGroup._nextMethod) {
@@ -429,7 +498,7 @@ function drawTicks(sliderGroup, sliderOpts) {
429498

430499
Lib.setTranslate(item,
431500
normalizedValueToPosition(sliderOpts, i / (sliderOpts.steps.length - 1)) - 0.5 * constants.tickWidth,
432-
isMajor ? constants.tickOffset : constants.minorTickOffset
501+
(isMajor ? constants.tickOffset : constants.minorTickOffset) + sliderOpts.currentValueTotalHeight
433502
);
434503
});
435504

@@ -462,7 +531,7 @@ function setGripPosition(sliderGroup, sliderOpts, position, doTransition) {
462531

463532
// Lib.setTranslate doesn't work here becasue of the transition duck-typing.
464533
// It's also not necessary because there are no other transitions to preserve.
465-
el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + 0 + ')');
534+
el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts.currentValueTotalHeight) + ')');
466535
}
467536

468537
// Convert a number from [0-1] to a pixel position relative to the slider group container:
@@ -492,7 +561,7 @@ function drawTouchRect(sliderGroup, gd, sliderOpts) {
492561
.call(Color.fill, constants.gripBgColor)
493562
.attr('opacity', 0);
494563

495-
Lib.setTranslate(rect, 0, 0);
564+
Lib.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight);
496565
}
497566

498567
function drawRail(sliderGroup, sliderOpts) {
@@ -515,7 +584,10 @@ function drawRail(sliderGroup, sliderOpts) {
515584
.call(Color.fill, constants.railBgColor)
516585
.style('stroke-width', '1px');
517586

518-
Lib.setTranslate(rect, constants.railInset, (sliderOpts.inputAreaWidth - constants.railWidth) * 0.5);
587+
Lib.setTranslate(rect,
588+
constants.railInset,
589+
(sliderOpts.inputAreaWidth - constants.railWidth) * 0.5 + sliderOpts.currentValueTotalHeight
590+
);
519591
}
520592

521593
function clearPushMargins(gd) {

0 commit comments

Comments
 (0)