Skip to content

Commit

Permalink
Improve selection of number of classes for nested means
Browse files Browse the repository at this point in the history
Fixes #145
  • Loading branch information
mthh committed Oct 17, 2024
1 parent 6f0a361 commit b25e992
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 8 deletions.
89 changes: 89 additions & 0 deletions src/components/Inputs/InputConstrainedSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Imports from solid-js
import {
createEffect,
createSignal,
type JSX,
on,
onCleanup,
onMount,
Show,
} from 'solid-js';

// Imports from NoUiSlider
import noUiSlider from 'nouislider';
import { type API as NoUiSliderApi } from 'nouislider';
import 'nouislider/dist/nouislider.css';

// Custom styles
import '../../styles/NoUiSliderBulma.css';

interface InputConstrainedSliderProps {
// The label of the slider.
label?: string;
// The current value of the slider.
value: number;
// The values that the slider can take (such as 2, 4, 8 and 16).
allowedValues: number[];
// The function to format the value of the slider.
formater?: (value: number) => string;
// The function to call when the value of the slider changes.
onChange: (number: number) => void;
// Controls the bar between the handles or the edges of the slider
connect?: 'lower' | 'upper' | boolean | undefined;
// Width of the slider.
width?: number;
}

export default function InputConstrainedSlider(props: InputConstrainedSliderProps): JSX.Element {
let refSliderNode: HTMLDivElement;
let slider: NoUiSliderApi;

const [
currentValue,
setCurrentValue,
] = createSignal(props.value); // eslint-disable-line solid/reactivity

const createSlider = () => {
slider = noUiSlider.create(refSliderNode, {
start: props.value,
step: 1,
range: {
min: 0,
max: props.allowedValues.length - 1,
},
format: {
to: (value) => props.allowedValues[value],
from: (value) => props.allowedValues.indexOf(+value),
},
});

slider.on('update', (values, handle) => {
props.onChange(+values[handle]);
setCurrentValue(+values[handle]);
});
};

onMount(() => {
createSlider();
});

onCleanup(() => {
if (slider) slider.destroy();
});
return <div class="field pr-2 mr-4" style={{ 'margin-bottom': 'none' }}>
<Show when={props.label}>
<label class="label">{props.label}</label>
</Show>
<div class="output mr-5 ml-2">
{
props.formater
? props.formater(currentValue())
: currentValue()
}
</div>
<div class="control">
<div class="slider slider-styled is-info" ref={refSliderNode!} style={{ width: `${props.width || 120}px` }}>
</div>
</div>
</div>;
}
47 changes: 39 additions & 8 deletions src/components/Modals/ClassificationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
type ClassificationParameters,
type CustomPalette,
} from '../../global.d';
import InputConstrainedSlider from '../Inputs/InputConstrainedSlider.tsx';

// Component for choosing breaks manually
function ManualBreaks(
Expand Down Expand Up @@ -704,7 +705,7 @@ export default function ClassificationPanel(): JSX.Element {
{
name: LL().ClassificationPanel.classificationMethods.nestedMeans(),
value: ClassificationMethod.nestedMeans,
options: [OptionsClassification.numberOfClasses],
options: [OptionsClassification.constrainedNumberOfClasses],
},
{
name: LL().ClassificationPanel.classificationMethods.headTail(),
Expand Down Expand Up @@ -813,12 +814,23 @@ export default function ClassificationPanel(): JSX.Element {
.find((d) => d.value === classificationMethod())!
}
onChange={(value) => {
const oldClassificationMethod = classificationMethod();
setClassificationMethod(value as ClassificationMethod);
// If the classification method is nestedMeans, we need to force
// the number of classes to be a power of 2.
if (value === ClassificationMethod.nestedMeans) {
setNumberOfClasses(2 ** Math.round(Math.log2(numberOfClasses())));
}
// If the previous classification method was nestedMeans and the
// number of classes was 16, and the user is not switching to
// manual classification, we return to a more reasonable number.
if (
oldClassificationMethod === ClassificationMethod.nestedMeans
&& classificationMethod() !== ClassificationMethod.manual
&& numberOfClasses() === 16
) {
setNumberOfClasses(Mmin(d3.thresholdSturges(filteredSeries), 9));
}
updateClassificationParameters();
}}
/>
Expand Down Expand Up @@ -854,13 +866,13 @@ export default function ClassificationPanel(): JSX.Element {
event.target.value = '2';
v = 2;
}
// If the current method is 'nestedMeans', we need to force
// the number of classes to be a power of 2.
if (classificationMethod() === ClassificationMethod.nestedMeans) {
v = 2 ** Math.round(Math.log2(v));
// eslint-disable-next-line no-param-reassign
event.target.value = `${v}`;
}
// // If the current method is 'nestedMeans', we need to force
// // the number of classes to be a power of 2.
// if (classificationMethod() === ClassificationMethod.nestedMeans) {
// v = 2 ** Math.round(Math.log2(v));
// // eslint-disable-next-line no-param-reassign
// event.target.value = `${v}`;
// }
if (classificationMethod() === ClassificationMethod.manual) {
setCustomBreaks(
Array.from({ length: v })
Expand All @@ -876,6 +888,25 @@ export default function ClassificationPanel(): JSX.Element {
/>
</div>
</Show>
<Show when={
classificationMethodHasOption(
OptionsClassification.constrainedNumberOfClasses,
classificationMethod(),
entriesClassificationMethod,
)
}>
<div style={{ position: 'relative', right: '140px' }}>
<p class="label is-marginless">{LL().ClassificationPanel.numberOfClasses()}</p>
<InputConstrainedSlider
value={numberOfClasses()}
allowedValues={[2, 4, 8, 16]}
onChange={(v) => {
setNumberOfClasses(v);
updateClassificationParameters();
}}
/>
</div>
</Show>
<Show when={
classificationMethodHasOption(
OptionsClassification.amplitude,
Expand Down
1 change: 1 addition & 0 deletions src/helpers/classification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export enum OptionsClassification {
amplitude,
meanPosition,
breaks,
constrainedNumberOfClasses,
}

export const classificationMethodHasOption = (
Expand Down

0 comments on commit b25e992

Please sign in to comment.