webwriter-chart
Version:
ww-chart is an interactive data visualization widget for the WebWriter tool that implements various charts and diagrams for static and exploratory data visualization.
1,140 lines (1,090 loc) • 54.6 kB
text/typescript
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import {
randomColor,
//csvToDataset,
randomNumber,
randomString,
//readFileInput,
} from "../../functions";
import style from "./ScatterDatasetInput.style";
import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js";
import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.component.js";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import SlColorPicker from "@shoelace-style/shoelace/dist/components/color-picker/color-picker.component.js";
import SlPopup from "@shoelace-style/shoelace/dist/components/popup/popup.component.js";
import SlMenuItem from "@shoelace-style/shoelace/dist/components/menu-item/menu-item.component.js";
import IconTrash from "bootstrap-icons/icons/trash.svg";
import IconPlusCircle from "bootstrap-icons/icons/plus-circle.svg";
import IconCaretdown from "bootstrap-icons/icons/caret-down.svg";
import "../FileUploadButton";
import { SelectDataset } from "./SelectDataset";
import { updateColorForFilter } from "../../helper-components/Charts/Scatterplot/drawScatterplot";
import StateComponent from "../../stateComponent";
@customElement("wwci-scatterdataset-input")
export class ScatterDatasetInput extends StateComponent {
// Define properties with appropriate types
@property({ type: Boolean, attribute: true, reflect: true })
accessor addDatasetPopupOpen = false;
@property({ type: Number, attribute: true, reflect: true })
accessor addColumnPopupOpenIndex = -1;
@property({ type: Number, attribute: true, reflect: true })
accessor openColumnTypePopupIndexInSet = -1;
@property({ type: Number, attribute: true, reflect: true })
accessor openColumnTypePopupIndexInLabels = -1;
addNewDataset() {
const newDataset = {
color: randomColor(),
name: randomString(),
ids: [],
labels: [],
typeOfEachData: [],
dimensional_data: [],
filter: { label: "", type: "none", index_in_labels: -1 },
};
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: [...this.sharedState.scatterDatasets.sets, newDataset],
},
};
this.dispatchStateChange();
}
addNumericColumn(index_inset: number) {
const updatedSets = this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === index_inset) {
return {
...dataset,
labels: [...dataset.labels, randomString()],
typeOfEachData: [...dataset.typeOfEachData, "number"],
dimensional_data: dataset.dimensional_data.map((data) => ({
...data,
data: [...data.data, randomNumber()],
})),
};
}
return dataset;
}
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.dispatchStateChange();
}
addStringColumn(index_inset: number) {
const updatedSets = this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === index_inset) {
return {
...dataset,
labels: [...dataset.labels, randomString()],
typeOfEachData: [...dataset.typeOfEachData, "string"],
dimensional_data: dataset.dimensional_data.map((data) => ({
...data,
data: [...data.data, randomString()],
})),
};
}
return dataset;
}
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.dispatchStateChange();
}
addRow(index_inset: number) {
const newId =
Math.max(0, ...this.sharedState.scatterDatasets.sets[index_inset].ids) +
1;
const newRow = {
data: this.sharedState.scatterDatasets.sets[
index_inset
].typeOfEachData.map((d) =>
d === "number" ? randomNumber() : randomString()
),
selected: false,
};
const updatedSets = this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === index_inset) {
return {
...dataset,
ids: [...dataset.ids, newId],
dimensional_data: [...dataset.dimensional_data, newRow],
};
}
return dataset;
}
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.updateFilter(index_inset);
this.dispatchStateChange();
}
updateFilter(index_inset: number) {
if (
this.sharedState.scatterDatasets.sets[index_inset].filter &&
this.sharedState.scatterDatasets.sets[index_inset].filter?.label &&
this.sharedState.scatterDatasets.sets[index_inset].filter
?.index_in_labels !== -1 &&
this.sharedState.scatterDatasets.sets[index_inset].filter?.type !== "none"
) {
this.sharedState.scatterDatasets.sets[index_inset].filter.type ===
"number"
? updateColorForFilter(
this.sharedState.scatterDatasets.sets[index_inset].filter
.index_in_labels,
"number",
this.sharedState.scatterDatasets.sets[index_inset]
)
: updateColorForFilter(
this.sharedState.scatterDatasets.sets[index_inset].filter
.index_in_labels,
"string",
this.sharedState.scatterDatasets.sets[index_inset]
);
}
}
deleteRow(index_inset: number, index_indata_scatterplot: number) {
const updatedSets = this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === index_inset) {
return {
...dataset,
ids: dataset.ids.filter(
(_, idx) => idx !== index_indata_scatterplot
),
dimensional_data: dataset.dimensional_data.filter(
(_, idx) => idx !== index_indata_scatterplot
),
};
}
return dataset;
}
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.updateFilter(index_inset);
this.dispatchStateChange();
}
deleteTableDataset(index_inset: number) {
const updatedSets = this.sharedState.scatterDatasets.sets.filter(
(_, i) => i !== index_inset
);
let updatedDatasetIndexes =
this.sharedState.scatterDatasets.selected.dataset_indexes.filter(
(index) => index !== index_inset
);
// make index > index_inset -1
updatedDatasetIndexes = updatedDatasetIndexes.map((index) =>
index > index_inset ? index - 1 : index
);
// if there is only dataset, remove line datasets and selected axis to None
if (
index_inset ===
this.sharedState.scatterDatasets.selected.dataset_indexes[0] &&
this.sharedState.scatterDatasets.selected.dataset_indexes.length === 1
) {
updatedDatasetIndexes = [];
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
selected: {
...this.sharedState.scatterDatasets.selected,
axis: {
x: "None",
y: "None",
},
},
},
};
// when only one dataset => remove line datasets
this.sharedState.lineDatasets.sets = [];
}
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
selected: {
...this.sharedState.scatterDatasets.selected,
dataset_indexes: updatedDatasetIndexes,
},
},
};
this.dispatchStateChange();
}
recoverColorForFilter(index_inset: number) {
const selectedDataset = this.sharedState.scatterDatasets.sets[index_inset];
const updatedFilter = selectedDataset.dimensional_data.map((d) => ({
...d,
color: selectedDataset.color,
}));
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: this.sharedState.scatterDatasets.sets.map((dataset, i) => {
if (i === index_inset) {
return {
...dataset,
dimensional_data: updatedFilter,
};
}
return dataset;
}),
},
};
}
deleteColumn(index_inset: number, index_inlabels: number) {
// recover color for filter if the column was selected as filter
if (
this.sharedState.scatterDatasets.sets[index_inset].filter
?.index_in_labels === index_inlabels
) {
this.recoverColorForFilter(index_inset);
}
const updatedSets = this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === index_inset) {
return {
...dataset,
labels: dataset.labels.filter((_, idx) => idx !== index_inlabels),
typeOfEachData: dataset.typeOfEachData.filter(
(_, idx) => idx !== index_inlabels
),
dimensional_data: dataset.dimensional_data.map((data) => ({
...data,
data: data.data.filter((_, idx) => idx !== index_inlabels),
})),
filter:
dataset.filter.index_in_labels === index_inlabels
? {
label: "",
type: "none",
index_in_labels: -1,
}
: dataset.filter,
};
}
return dataset;
}
);
const updatedSelectedAxis = {
...this.sharedState.scatterDatasets.selected.axis,
};
// In case the deleted column was selected as x or y axis
if (
this.sharedState.scatterDatasets.selected.dataset_indexes.includes(
index_inset
)
) {
const deletedLabel =
this.sharedState.scatterDatasets.sets[index_inset].labels[
index_inlabels
];
if (
this.sharedState.scatterDatasets.selected.axis.x === deletedLabel ||
this.sharedState.scatterDatasets.selected.axis.y === deletedLabel
) {
// if there are multiple datasets, deselect current dataset
if (
this.sharedState.scatterDatasets.selected.dataset_indexes.length > 1
) {
const updatedDatasetIndexes =
this.sharedState.scatterDatasets.selected.dataset_indexes.filter(
(index) => index !== index_inset
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
selected: {
...this.sharedState.scatterDatasets.selected,
dataset_indexes: updatedDatasetIndexes,
},
},
};
// if there is only one dataset, deselect the axis
} else {
if (
this.sharedState.scatterDatasets.selected.axis.x === deletedLabel
) {
updatedSelectedAxis.x = "None";
} else {
updatedSelectedAxis.y = "None";
}
}
}
}
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
selected: {
...this.sharedState.scatterDatasets.selected,
axis: updatedSelectedAxis,
},
},
};
this.dispatchStateChange();
}
changeColor(color: string, index_inset: number) {
const updatedSets = this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === index_inset) {
return { ...dataset, color: color };
}
return dataset;
}
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
// ensure the color updated, when no filter is selected
if (
!this.sharedState.scatterDatasets.sets[index_inset].filter ||
this.sharedState.scatterDatasets.sets[index_inset].filter.type ===
"none" ||
this.sharedState.scatterDatasets.sets[index_inset].filter
.index_in_labels === -1
) {
this.recoverColorForFilter(index_inset);
}
this.dispatchStateChange();
}
// !important index_inset is the index of the dataset and index_inset = this.sharedState.scatterDatasets.sets.length - 1 - index_reverse
render() {
return html`${this.sharedState.teacherOptions.addRemoveData
? html`<div class="btn-wrapper">
<sl-button
slot="anchor"
label="Add ${this.sharedState.scatterDatasets.sets.length === 0
? "first"
: "new"} Dataset"
@click=${() => {
this.addNewDataset();
}}
>Add
${this.sharedState.scatterDatasets.sets.length === 0
? "First"
: "New"}
Dataset</sl-button
>
<wwci-select-dataset
.sharedState=${this.sharedState}
></wwci-select-dataset>
</div>`
: html``}
${this.sharedState.scatterDatasets.sets
.slice()
.reverse()
.map(
(each_dataset, index_reverse) => html`
<div class="table-wrapper">
<table>
<thead>
<tr>
<th class="table-cell-name">
<div
style="display: flex; flex-direction: row; align-items: center"
>
<sl-color-picker
size="small"
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
value="${
this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
].color
}"
@sl-input=${(e: any) =>
this.changeColor(
e.target.value,
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
)}
></sl-color-picker>
<sl-icon-button
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.addRemoveData
)}
src=${IconTrash}
label="Settings"
@click=${(e) => {
e.preventDefault(); // Prevent default selection behavior
e.stopPropagation(); // Stop propagation to avoid other side effects
this.deleteTableDataset(
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
);
}}
></sl-icon-button>
<div class="input-wrapper">
<sl-input
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
type="text"
class="input"
value="${
this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.sets
.length -
1 -
index_reverse
].name
}"
@sl-input=${(e: any) => {
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (
i ===
this.sharedState.scatterDatasets.sets
.length -
1 -
index_reverse
) {
return {
...dataset,
name: e.target.value,
};
}
return dataset;
}
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.dispatchStateChange();
}}
></sl-input>
</div>
</div>
</th>
${this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
].labels.map(
(label, index_inlabels) =>
html`<th class="table-cell-name">
<div
style="display: flex; flex-direction: row; align-items: center"
>
<div class="input-wrapper">
<sl-input
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
type="text"
value="${label}"
@sl-input=${(e: any) => {
let newValue = String(e.target.value);
const targetIndex =
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse;
const targetDataset =
this.sharedState.scatterDatasets.sets[
targetIndex
];
// Check if we need to update the filter and color
let updatedFilter = targetDataset.filter;
if (
targetDataset.filter?.index_in_labels ===
index_inlabels
) {
updatedFilter = {
...targetDataset.filter,
label: newValue,
};
}
const updatedIndexes = [
...this.sharedState.scatterDatasets.selected
.dataset_indexes,
];
// check for the selected axis
if (
this.sharedState.scatterDatasets.selected
.dataset_indexes.length === 1 &&
this.sharedState.scatterDatasets.selected
.dataset_indexes[0] === targetIndex
) {
if (
this.sharedState.scatterDatasets.selected.axis
.x === label
) {
this.sharedState.scatterDatasets.selected.axis.x =
newValue;
} else if (
this.sharedState.scatterDatasets.selected.axis
.y === label
) {
this.sharedState.scatterDatasets.selected.axis.y =
newValue;
}
if (
this.sharedState.scatterDatasets.selected.axis
.x ===
this.sharedState.scatterDatasets.selected.axis
.y
) {
this.sharedState.scatterDatasets.selected.axis.y =
"None";
this.sharedState.scatterDatasets.selected.axis.x =
"None";
}
} else if (
this.sharedState.scatterDatasets.selected
.dataset_indexes.length > 1 &&
this.sharedState.scatterDatasets.selected.dataset_indexes.includes(
targetIndex
)
) {
if (
this.sharedState.scatterDatasets.selected.axis
.x === label ||
this.sharedState.scatterDatasets.selected.axis
.y === label
) {
const index =
updatedIndexes.indexOf(targetIndex);
updatedIndexes.splice(index, 1);
}
}
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(dataset, i) => {
if (i === targetIndex) {
const updatedLabels = dataset.labels.map(
(label, labelIndex) =>
labelIndex === index_inlabels
? newValue
: label
);
return {
...dataset,
labels: updatedLabels, // Update labels immutably
filter: updatedFilter,
};
}
return dataset;
}
);
const updatedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
selected: {
...this.sharedState.scatterDatasets
.selected,
dataset_indexes: updatedIndexes,
},
},
};
this.sharedState = updatedState;
this.dispatchStateChange();
}}
@sl-blur=${() => {
const targetIndex =
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse;
const dataset =
this.sharedState.scatterDatasets.sets[
targetIndex
];
const labels = dataset.labels;
const label = labels[index_inlabels];
// if the label is "" or " ", then set it to "Column"
if (label === "" || label === " ") {
const updatedLabels = labels.map((l, i) =>
i === index_inlabels ? "newAttribute" : l
);
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(set, i) =>
i === targetIndex
? { ...set, labels: updatedLabels }
: set
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
// if one of the axis is "" or " ", then set it to "newAttribute"
if (
this.sharedState.scatterDatasets.selected.axis
.x === label
) {
this.sharedState.scatterDatasets.selected.axis.x =
"newAttribute";
} else if (
this.sharedState.scatterDatasets.selected.axis
.y === label
) {
this.sharedState.scatterDatasets.selected.axis.y =
"newAttribute";
}
this.dispatchStateChange();
}
if (
labels.some(
(l, i) => l === label && i !== index_inlabels
)
) {
const updatedLabels = labels.map((l, i) =>
i === index_inlabels ? `${label}(1)` : l
);
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(set, i) =>
i === targetIndex
? { ...set, labels: updatedLabels }
: set
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.dispatchStateChange();
}
}}
></sl-input>
</div>
<sl-popup
placement="bottom"
strategy="fixed"
.active="${this.openColumnTypePopupIndexInLabels ===
index_inlabels &&
this.openColumnTypePopupIndexInSet ===
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse}"
>
<sl-icon-button
slot="anchor"
src=${IconCaretdown}
label="Toggle dropdown"
@click=${(e) => {
this.openColumnTypePopupIndexInLabels =
index_inlabels;
this.openColumnTypePopupIndexInSet =
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse;
e.target.focus();
}}
@blur="${() =>
(this.openColumnTypePopupIndexInSet =
-1 &&
(this.openColumnTypePopupIndexInLabels = -1))}"
></sl-icon-button>
<div class="dropdown-content">
<sl-menu-item
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
e.preventDefault();
e.stopPropagation();
this.deleteColumn(
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse,
index_inlabels
);
this.openColumnTypePopupIndexInLabels = -1;
this.openColumnTypePopupIndexInSet = -1;
}}
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.addRemoveData
)}
>
<sl-icon-button
slot="prefix"
src=${IconTrash}
label="Settings"
></sl-icon-button>
Delete Column
</sl-menu-item>
<sl-menu-item
type="checkbox"
.checked=${this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
].typeOfEachData[index_inlabels] === "number"
? false
: true}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
e.preventDefault(); // Prevent default selection behavior
e.stopPropagation(); // Stop propagation to avoid other side effects
const targetIndex =
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse;
const targetDataset =
this.sharedState.scatterDatasets.sets[
targetIndex
];
// Update the typeOfEachData array immutably
const updatedTypeOfEachData =
targetDataset.typeOfEachData.map((type, i) =>
i === index_inlabels ? "string" : type
);
// Update the dimensional_data immutably
const updatedDimensionalData =
targetDataset.dimensional_data.map(
(data) => ({
...data,
data: data.data.map((d, i) =>
i === index_inlabels ? d.toString() : d
),
})
);
let updatedFilter = targetDataset.filter;
// check if we need to update the filter and color
if (
targetDataset.filter?.label === label &&
targetDataset.filter?.index_in_labels ===
index_inlabels
) {
updatedFilter = {
...targetDataset.filter,
type: "string",
};
}
// Update the sets array immutably
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(dataset, i) =>
i === targetIndex
? {
...dataset,
typeOfEachData:
updatedTypeOfEachData,
dimensional_data:
updatedDimensionalData,
filter: updatedFilter,
}
: dataset
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.updateFilter(targetIndex);
this.dispatchStateChange();
}}
class="button-datatype-category"
>Convert column values to
categorical</sl-menu-item
>
<sl-menu-item
type="checkbox"
.checked=${this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
].typeOfEachData[index_inlabels] === "number"
? true
: false}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
const targetIndex =
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse;
const targetDataset =
this.sharedState.scatterDatasets.sets[
targetIndex
];
// Update the typeOfEachData array immutably
const updatedTypeOfEachData =
targetDataset.typeOfEachData.map((type, i) =>
i === index_inlabels ? "number" : type
);
// Update the dimensional_data immutably
const updatedDimensionalData =
targetDataset.dimensional_data.map(
(data) => ({
...data,
data: data.data.map((d, i) =>
i === index_inlabels
? Number(d)
? Number(d)
: 0
: d
),
})
);
// Check if we need to update the filter and color
let updatedFilter = targetDataset.filter;
if (
targetDataset.filter?.label === label &&
targetDataset.filter?.index_in_labels ===
index_inlabels
) {
updatedFilter = {
...targetDataset.filter,
type: "number",
};
}
// Update the sets array immutably
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(dataset, i) =>
i === targetIndex
? {
...dataset,
typeOfEachData:
updatedTypeOfEachData,
dimensional_data:
updatedDimensionalData,
filter: updatedFilter,
}
: dataset
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.updateFilter(targetIndex);
this.dispatchStateChange();
}}
class="button-datatype-number"
>Convert column values to numeric</sl-menu-item
>
</div>
</sl-popup>
</div>
</th>`
)}
</tr>
</thead>
<tbody>
${each_dataset.dimensional_data.map(
(each_data_scatterplot, index_inscatter) => html`
<tr>
<td class="table-cell-id">
<div
style="display: flex; flex-direction: row; align-items: center"
>
<sl-icon-button
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.addRemoveData
)}
src=${IconTrash}
label="Settings"
@click=${(e) => {
e.preventDefault(); // Prevent default selection behavior
e.stopPropagation(); // Stop propagation to avoid other side effects
this.deleteRow(
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse,
index_inscatter
);
}}
></sl-icon-button>
<div class="input-wrapper">
<sl-input
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
id="input"
type="text"
class="input"
value="${each_dataset.ids[index_inscatter]}"
@sl-input=${(e: any) => {
e.target.value = e.target.value.replaceAll(
/[^\d.,\-]+/g,
""
);
const newNumber = Number(e.target.value);
if (isNaN(newNumber)) return;
const targetIndex =
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse;
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(dataset, i) =>
i === targetIndex
? {
...dataset,
ids: dataset.ids.map((id, idx) =>
idx === index_inscatter
? newNumber
: id
),
}
: dataset
);
this.sharedState = {
...this.sharedState,
scatterDatasets: {
...this.sharedState.scatterDatasets,
sets: updatedSets,
},
};
this.dispatchStateChange();
}}
></sl-input>
</div>
</div>
</td>
${each_data_scatterplot.data.map(
(data, index_indata) => html`
<td
class="${this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.sets.length -
1 -
index_reverse
].typeOfEachData[index_indata] === "number"
? "table-cell-number"
: "table-cell-category"}"
>
<div
style="display: flex; flex-direction: row; align-items: center"
>
<div class="input-wrapper">
<sl-input
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
id="input-x-${this.sharedState.scatterDatasets
.sets.length -
1 -
index_reverse}-${index_inscatter}-${index_indata}"
type="text"
value="${data}"
@sl-input=${(e: any) => {
const targetIndex =
this.sharedState.scatterDatasets.sets
.length -
1 -
index_reverse;
const targetDataset =
this.sharedState.scatterDatasets.sets[
targetIndex
];
const updatedDimensionalData =
targetDataset.dimensional_data.map(
(dimData, idxScatter) => {
if (idxScatter !== index_inscatter)
return dimData;
const updatedData = dimData.data.map(
(data, idx) =>
idx === index_indata
? each_dataset.typeOfEachData[
index_indata
] === "number"
? (() => {
e.target.value =
e.target.value.replaceAll(
/[^\d.,\-]+/g,
""
);
const number = Number(
e.target.value
);
if (isNaN(number)) {
return data;
}
return number;
})()
: String(e.target.value)
: data
);
return {
...dimData,
data: updatedData,
};
}
);
let updatedFilter = targetDataset.filter;
// Update the filter and color if the column is selected as filter
if (
targetDataset.filter?.label ===
each_dataset.labels[index_indata] &&
targetDataset.filter?.index_in_labels ===
index_indata
) {
updatedFilter = {
...targetDataset.filter,
type:
each_dataset.typeOfEachData[
index_indata
] === "number"
? "number"
: "string",
};
}
const updatedSets =
this.sharedState.scatterDatasets.sets.map(
(dataset, i) =>
i === targetIndex
? {
...dataset,
dimensional_data:
updatedD