Skip to content

Instantly share code, notes, and snippets.

@cinderblock
Last active December 4, 2019 01:01
Show Gist options
  • Save cinderblock/6f52313a06ad3f8bf0e0290826d1ef10 to your computer and use it in GitHub Desktop.
Save cinderblock/6f52313a06ad3f8bf0e0290826d1ef10 to your computer and use it in GitHub Desktop.
Initial version of a Logarithmic wrapper for rc-slider
import * as React from 'react';
import Slider, { SliderProps } from 'rc-slider';
type NumberMap = (x: number) => number;
type Map = number | boolean | { axisFromReal: NumberMap; realFromAxis: NumberMap };
export default function LogarithmicRCSlider({
map,
steps,
...props
}: Omit<SliderProps, 'step'> & { map?: Map; steps?: number; min?: number; max: number }): JSX.Element {
if (map !== false) {
// Default to simple log/exp
if (map === undefined) map = true;
// Simple log scale
if (map === true) {
map = {
axisFromReal: Math.log,
realFromAxis: Math.exp,
};
}
// Log scale approximation that goes to zero
if (typeof map === 'number') {
if (map < 0) throw new RangeError('Negative maps are not currently allowed');
// Helpful to allow logarithmic scales less than one that still go to zero
const scale = map || 1;
map = {
// Approximation of log/exp that goes to zero
axisFromReal: (real: number): number => {
real *= scale;
return real < 1 ? 0 : Math.log(real);
},
realFromAxis: (axis: number): number => (axis && Math.exp(axis)) / scale,
};
props.min = 0;
}
const { axisFromReal, realFromAxis } = map;
['max', 'min', 'value', 'defaultValue'].forEach(key => {
if (realFromAxis(axisFromReal(props[key])) !== props[key])
throw new RangeError(`Supplied function map pair doesn't equal itself for ${key}: ${props[key]}`);
if (props[key] !== undefined) props[key] = axisFromReal(props[key]);
});
if (steps) {
(props as SliderProps).step = (props.max - props.min) / steps;
}
if (props.marks) {
const next: typeof props.marks = {};
for (const x in props.marks) {
next[axisFromReal(Number(x))] = props.marks[x];
}
props.marks = next;
}
['onBeforeChange', 'onChange', 'onAfterChange'].forEach(key => {
if (props[key] === undefined) return;
const orig = props[key];
props[key] = (value: number): void => orig(realFromAxis(value));
});
if (props.tipFormatter) {
props.tipFormatter = (v: number): ReturnType<typeof props.tipFormatter> => props.tipFormatter(realFromAxis(v));
}
}
return <Slider {...props} />;
}
@cinderblock
Copy link
Author

Usage

The usage is pretty straightforward for straight logrithmic scales.

All options for rc-slider's Slider are available except step because it doesn't makes sense on a logarithmic scale. If you want "stepyness", set steps instead.

Does not generate Marks automatically but will correctly adjust their positions.

Simple logarithmic scale

<LogarithmicRCSlider min={1} max={100} steps={40}  />

Modified Logarithmic - Goes to 0

Simply set map to a number.

<LogarithmicRCSlider map={0} max={255} />
Really small maximums

If you want to go to zero and have a small maximum, we need some scaling to move that discontinuity to an even smaller scale.

<LogarithmicRCSlider map={100} max={0.1} />

Custom map function

Don't like Math.log and Math.exp? Provide your own!

<LogarithmicRCSlider map={{
  axisFromReal: Math.log,
  realFromAxis: Math.exp,
}} max={1000} />

If your provided functions don't match each other at key values, a RangeError will be thrown.

Want to jump back to a linear scale?

<LogarithmicRCSlider map={false} min={-100} />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment