Skip to content

Commit

Permalink
Improve input of manual breaks in classification panel
Browse files Browse the repository at this point in the history
  • Loading branch information
mthh committed Oct 2, 2024
1 parent 9edfe57 commit f2f33e5
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 57 deletions.
156 changes: 105 additions & 51 deletions src/components/Modals/ClassificationPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Imports from solid-js
import {
createSignal, JSX, Match,
onCleanup, onMount,
createSignal, JSX, For,
Match, onCleanup, onMount,
Show, Switch,
} from 'solid-js';

Expand All @@ -12,7 +12,6 @@ import {
getSequentialColors,
PaletteType,
} from 'dicopal';
import toast from 'solid-toast';

// Helpers
import { useI18nContext } from '../../i18n/i18n-solid';
Expand All @@ -21,7 +20,6 @@ import {
classificationMethodHasOption,
getClassifier,
OptionsClassification,
parseUserDefinedBreaks,
prepareStatisticalSummary,
} from '../../helpers/classification';
import { isFiniteNumber } from '../../helpers/common';
Expand All @@ -47,6 +45,74 @@ import {
type CustomPalette,
} from '../../global.d';

// Component for choosing breaks manually
function ManualBreaks(
props: {
currentBreaksInfo: ClassificationParameters,
setCurrentBreaksInfo: (cp: ClassificationParameters) => void,
precision: number,
},
): JSX.Element {
const { LL } = useI18nContext();
return <div class="mb-2">
<For each={props.currentBreaksInfo.breaks}>
{
(b, i) => <input
type="number"
value={round(b, props.precision)}
disabled={i() === 0 || i() === props.currentBreaksInfo.breaks.length - 1}
title={
i() === 0 || i() === props.currentBreaksInfo.breaks.length - 1
? LL().ClassificationPanel.breaksCantBeChanged()
: ''
}
onChange={function (e) {
// We don't want to update the breaks if the user is typing
// a number that is not a valid number or if the value is superior
// to the next break or inferior to the previous break.
const value = +e.target.value;
if (
!Number.isFinite(value)
|| (i() < props.currentBreaksInfo.breaks.length - 1
&& value >= props.currentBreaksInfo.breaks[i() + 1])
|| (i() > 0
&& value <= props.currentBreaksInfo.breaks[i() - 1])
) {
// eslint-disable-next-line no-param-reassign
this.value = round(b, props.precision);
return;
}
const newBreaks = [...props.currentBreaksInfo.breaks];
newBreaks[i()] = value;
props.setCurrentBreaksInfo({
...props.currentBreaksInfo,
breaks: newBreaks,
});
}}
/>
}
</For>
</div>;
}

// Component to display current breaks
// based on currentBreaksInfo
function DisplayBreaks(
props: {
breaks: number[],
precision: number,
},
): JSX.Element {
return <div class="mb-2">
<p>{
props.breaks
.map((d) => round(d, props.precision))
.join(' - ')
}</p>
</div>;
}

// The main component
export default function ClassificationPanel(): JSX.Element {
// Function to recompute the classification given the current options.
// We scope it here to facilitate the use of the signals that are defined below...
Expand Down Expand Up @@ -325,7 +391,7 @@ export default function ClassificationPanel(): JSX.Element {
{
name: LL().ClassificationPanel.classificationMethods.manual(),
value: ClassificationMethod.manual,
options: [OptionsClassification.breaks],
options: [OptionsClassification.breaks, OptionsClassification.numberOfClasses],
},
].filter((d) => d !== null);

Expand Down Expand Up @@ -461,6 +527,15 @@ export default function ClassificationPanel(): JSX.Element {
// eslint-disable-next-line no-param-reassign
event.target.value = `${v}`;
}
if (classificationMethod() === ClassificationMethod.manual) {
setCustomBreaks(
Array.from({ length: v })
.map((_, i) => (
statSummary.minimum + (
i * (statSummary.maximum - statSummary.minimum)) / v))
.concat([statSummary.maximum]),
);
}
setNumberOfClasses(v);
updateClassificationParameters();
}}
Expand Down Expand Up @@ -530,54 +605,33 @@ export default function ClassificationPanel(): JSX.Element {
</div>
</div>
</Show>
<Show when={
classificationMethodHasOption(
OptionsClassification.breaks,
classificationMethod(),
entriesClassificationMethod,
)
}>
<div style={{ 'flex-grow': 5 }}>
<p class="label is-marginless">{ LL().ClassificationPanel.breaksInput() }</p>
<textarea
class={'textarea'}
style={{ 'min-height': '3em', 'max-height': '6em' }}
ref={refTextArea!}
value={currentBreaksInfo().breaks.join(' - ')}
>
</textarea>
<button
class="button"
style={{ width: '100%', height: '2em' }}
onClick={() => {
try {
const b = parseUserDefinedBreaks(
filteredSeries,
refTextArea.value,
statSummary,
);
setCustomBreaks(b);
} catch (e) {
toast.error(LL().ClassificationPanel.errorCustomBreaks(), {
duration: 10000,
});
refTextArea.value = currentBreaksInfo().breaks.join(' - ');
return;
}
updateClassificationParameters();
}}
>
{ LL().ClassificationPanel.validate() }
</button>
</div>
</Show>
</div>
<Show when={
!classificationMethodHasOption(
OptionsClassification.breaks,
classificationMethod(),
entriesClassificationMethod,
)
}>
<DisplayBreaks
breaks={currentBreaksInfo().breaks}
precision={statSummary.precision}
/>
</Show>
<Show when={
classificationMethodHasOption(
OptionsClassification.breaks,
classificationMethod(),
entriesClassificationMethod,
)
}>
<ManualBreaks
currentBreaksInfo={currentBreaksInfo()}
setCurrentBreaksInfo={setCurrentBreaksInfo}
precision={statSummary.precision}
/>
</Show>
<div>
<p>{
currentBreaksInfo().breaks
.map((d) => round(d, statSummary.precision))
.join(' - ')
}</p>
<div class="is-flex">
<div style={{ width: '60%' }}>
<div>
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ const en = {
validate: 'Validate',
missingValues: '{{ One feature without data | ?? features without data }}',
count: 'Features per class',
errorCustomBreaks: 'Error - The class limits entered are invalid. Please enter numerical values separated by dashes to form at least 2 classes.',
breaksCantBeChanged: 'This class boundary cannot be changed because it is automatically calculated.',
lowerLimit: 'Lower limit',
upperLimit: 'Upper limit',
size: 'Size (px)',
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ const fr = {
validate: 'Valider',
missingValues: '{{ Une entité sans données | ?? entités sans données }}',
count: 'Entités par classe',
errorCustomBreaks: 'Erreur - Les limites de classes saisies ne sont pas valides. Veillez à saisir des valeurs numériques séparées par des tirets et permettant de former au minimum 2 classes.',
breaksCantBeChanged: 'Cette limite de classe ne peut pas être modifiée manuellement.',
lowerLimit: 'Limite inférieure',
upperLimit: 'Limite supérieure',
size: 'Size (px)',
Expand Down
8 changes: 4 additions & 4 deletions src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4063,9 +4063,9 @@ type RootTranslation = {
*/
count: string
/**
* E​r​r​o​r​ ​-​ ​T​h​e​ ​c​l​a​s​s​ ​l​i​m​i​t​s​ ​e​n​t​e​r​e​d​ ​a​r​e​ ​i​n​v​a​l​i​d​.​ ​P​l​e​a​s​e​ ​e​n​t​e​r​ ​n​u​m​e​r​i​c​a​l​ ​v​a​l​u​e​s​ ​s​e​p​a​r​a​t​e​d​ ​b​y​ ​d​a​s​h​e​s​ ​t​o​ ​f​o​r​m​ ​a​t​ ​l​e​a​s​t​ ​2​ ​c​l​a​s​s​e​s​.
* T​h​i​s​ ​c​l​a​s​s​ ​b​o​u​n​d​a​r​y​ ​c​a​n​n​o​t​ ​b​e​ ​c​h​a​n​g​e​d​ ​b​e​c​a​u​s​e​ ​i​t​ ​i​s​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​c​a​l​c​u​l​a​t​e​d​.
*/
errorCustomBreaks: string
breaksCantBeChanged: string
/**
* L​o​w​e​r​ ​l​i​m​i​t
*/
Expand Down Expand Up @@ -8389,9 +8389,9 @@ export type TranslationFunctions = {
*/
count: () => LocalizedString
/**
* Error - The class limits entered are invalid. Please enter numerical values separated by dashes to form at least 2 classes.
* This class boundary cannot be changed because it is automatically calculated.
*/
errorCustomBreaks: () => LocalizedString
breaksCantBeChanged: () => LocalizedString
/**
* Lower limit
*/
Expand Down

0 comments on commit f2f33e5

Please sign in to comment.