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.
422 lines (399 loc) • 17.5 kB
text/typescript
import { html } from "lit";
import { customElement } from "lit/decorators.js";
import {
csvToDataset,
randomColor,
randomNumber,
randomString,
readFileInput,
} from "../../functions";
import style from "./DatasetInput.style";
import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import SlColorPicker from "@shoelace-style/shoelace/dist/components/color-picker/color-picker.js";
import IconTrash from "bootstrap-icons/icons/trash.svg";
import IconPlusCircle from "bootstrap-icons/icons/plus-circle.svg";
import { FileUploadButton } from "../FileUploadButton";
import StateComponent from "../../stateComponent";
export class DatasetInput extends StateComponent {
addDataset() {
const newDataset = {
name: randomString(),
color: randomColor(),
colors: this.sharedState.chart.datasets.sets[0].colors,
data: Array(this.sharedState.chart.datasets.sets[0].data.length)
.fill("")
.map(() => randomNumber()),
};
this.sharedState = {
...this.sharedState,
chart: {
...this.sharedState.chart,
datasets: {
...this.sharedState.chart.datasets,
sets: [...this.sharedState.chart.datasets.sets, newDataset],
},
},
};
this.dispatchStateChange();
}
addColumn() {
const updatedSets = this.sharedState.chart.datasets.sets.map((dataset) => {
return {
...dataset,
data: [...dataset.data, randomNumber()],
colors: [...dataset.colors, randomColor()],
};
});
const updatedLabels = [
...this.sharedState.chart.datasets.labels,
randomString(),
];
this.sharedState = {
...this.sharedState,
chart: {
...this.sharedState.chart,
datasets: {
...this.sharedState.chart.datasets,
sets: updatedSets,
labels: updatedLabels,
},
},
};
this.dispatchStateChange();
}
deleteColumn(i: number) {
const updatedSets = this.sharedState.chart.datasets.sets.map((dataset) => {
return {
...dataset,
data: dataset.data.filter((_, j) => j !== i),
colors: dataset.colors.filter((_, j) => j !== i),
};
});
const updatedLabels = this.sharedState.chart.datasets.labels.filter(
(_, j) => j !== i
);
this.sharedState = {
...this.sharedState,
chart: {
...this.sharedState.chart,
datasets: {
...this.sharedState.chart.datasets,
sets: updatedSets,
labels: updatedLabels,
},
},
};
this.dispatchStateChange();
}
deleteDataset(i: number) {
const updatedSets = this.sharedState.chart.datasets.sets.filter(
(_, j) => j !== i
);
this.sharedState = {
...this.sharedState,
chart: {
...this.sharedState.chart,
datasets: {
...this.sharedState.chart.datasets,
sets: updatedSets,
},
},
};
this.dispatchStateChange();
}
render() {
if (this.sharedState.chart.datasets.sets.length === 0)
return html`<sl-button =${this.addDataset}>Add Dataset</sl-button>`;
return html`<div class="table-wrapper">
<div class="table-scroll-container">
<div class="table-layout-container">
<table>
<thead>
<tr>
<th>Labels</th>
${this.sharedState.chart.datasets.labels.map(
(label, i) =>
html`<th>
<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"
class="input"
value="${label}"
-input=${(e: any) => {
const newLabels =
this.sharedState.chart.datasets.labels.map(
(label, index) =>
index === i ? e.target.value : label
);
this.sharedState = {
...this.sharedState, // spread the existing sharedState
chart: {
...this.sharedState.chart, // spread the existing chart
datasets: {
...this.sharedState.chart.datasets, // spread the existing datasets
labels: newLabels, // update the labels array immutably
},
},
};
this.dispatchStateChange();
}}
></sl-input>
</div>
<sl-icon-button
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.addRemoveData
)}
src=${IconTrash}
label="Settings"
=${() => this.deleteColumn(i)}
></sl-icon-button>
</div>
</th>`
)}
</tr>
</thead>
<tbody>
${this.sharedState.chart.datasets.sets.map(
(dataset, i) => html`
<tr>
<td>
<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"
=${() => this.deleteDataset(i)}
></sl-icon-button>
<div class="input-wrapper">
<sl-input
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
id="input"
type="text"
class="input"
value="${dataset.name}"
-input=${(e: any) => {
const newSets =
this.sharedState.chart.datasets.sets.map(
(set, index) =>
index === i
? { ...set, name: e.target.value }
: set
);
this.sharedState = {
...this.sharedState, // spread the existing sharedState
chart: {
...this.sharedState.chart, // spread the existing chart
datasets: {
...this.sharedState.chart.datasets, // spread the existing datasets
sets: newSets, // update the sets array immutably
},
},
};
this.dispatchStateChange();
}}
></sl-input>
</div>
<sl-color-picker
size="small"
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
value="${this.sharedState.chart.datasets.sets[i]
.color}"
-input=${(e: any) => {
const newSets =
this.sharedState.chart.datasets.sets.map(
(set, index) =>
index === i
? { ...set, color: e.target.value }
: set
);
this.sharedState = {
...this.sharedState, // spread the existing sharedState
chart: {
...this.sharedState.chart, // spread the existing chart
datasets: {
...this.sharedState.chart.datasets, // spread the existing datasets
sets: newSets, // update the sets array immutably
},
},
};
this.dispatchStateChange();
}}
></sl-color-picker>
</div>
</td>
${dataset.data.map(
(value, j) =>
html`<td>
<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-${i}-${j}"
type="number"
class="input"
value="${value}"
-input=${(e: any) => {
const newSets =
this.sharedState.chart.datasets.sets.map(
(set, index) => {
if (index === i) {
// Update the specific data point in the dataset
const newData = set.data.map(
(dataPoint, dataIndex) =>
dataIndex === j
? Number(e.target.value)
: dataPoint
);
// Return the updated set with the new data array
return { ...set, data: newData };
}
// Return the original set if the index doesn't match
return set;
}
);
this.sharedState = {
...this.sharedState, // spread the existing sharedState
chart: {
...this.sharedState.chart, // spread the existing chart
datasets: {
...this.sharedState.chart.datasets, // spread the existing datasets
sets: newSets, // update the sets array immutably with new data
},
},
};
this.dispatchStateChange();
}}
></sl-input>
</div>
<sl-color-picker
size="small"
?disabled=${!(
this.sharedState.editable ||
this.sharedState.teacherOptions.changeData
)}
value="${this.sharedState.chart.datasets.sets[i]
.colors[j]}"
-input=${(e: any) => {
const newSets =
this.sharedState.chart.datasets.sets.map(
(set, setIndex) => {
if (setIndex === i) {
// Update the specific color at index j in the colors array
const newColors = set.colors.map(
(color, colorIndex) =>
colorIndex === j
? e.target.value
: color
);
// Return the updated set with the new colors array
return {
...set,
colors: newColors,
};
}
// Return the original set if the index doesn't match
return set;
}
);
this.sharedState = {
...this.sharedState, // spread the existing sharedState
chart: {
...this.sharedState.chart, // spread the existing chart
datasets: {
...this.sharedState.chart.datasets, // spread the existing datasets
sets: newSets, // update the sets array immutably with new colors
},
},
};
this.dispatchStateChange();
}}
></sl-color-picker>
</div>
</td>`
)}
</tr>
`
)}
</tbody>
</table>
${this.sharedState.editable ||
this.sharedState.teacherOptions.addRemoveData
? html`
<sl-icon-button
=${this.addColumn}
src=${IconPlusCircle}
label="Add Column"
></sl-icon-button>
<sl-icon-button
=${this.addDataset}
src=${IconPlusCircle}
label="Add Dataset"
></sl-icon-button>
<br />
<wwci-file-upload-button
=${async (e) => {
const fileContent = await readFileInput(e.detail.value);
const { datasets, title } = csvToDataset(fileContent);
// Create a new immutable sharedState object
this.sharedState = {
...this.sharedState, // spread the existing sharedState
chart: {
...this.sharedState.chart, // spread the existing chart
datasets: {
...this.sharedState.chart.datasets, // spread the existing datasets
sets: datasets.sets, // update the sets array with the new datasets
labels: datasets.labels, // update labels if necessary (optional, depends on your csvToDataset result)
},
title: title, // update the title immutably
},
};
this.dispatchStateChange();
}}
></wwci-file-upload-button>
`
: html``}
</div>
</div>
</div>`;
}
static styles = style;
public static get scopedElements() {
return {
"sl-icon-button": SlIconButton,
"sl-input": SlInput,
"sl-color-picker": SlColorPicker,
"wwci-file-upload-button": FileUploadButton,
};
}
}
declare global {
interface HTMLElementTagNameMap {
"wwci-dataset-input": DatasetInput;
}
}