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,213 lines (1,177 loc) • 172 kB
text/typescript
import { html, PropertyValues } from "lit";
import * as d3 from "d3";
import { customElement, property } from "lit/decorators.js";
import { OptionDefinition, Definition } from "./definition";
import SlRange from "@shoelace-style/shoelace/dist/components/range/range.js";
import SlButton from "@shoelace-style/shoelace/dist/components/button/button.js";
import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.js";
import SlPopup from "@shoelace-style/shoelace/dist/components/popup/popup.component.js";
import SlMenu from "@shoelace-style/shoelace/dist/components/menu/menu.js";
import SlMenuItem from "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js";
import SlTooltip from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.component.js";
import IconPluslg from "bootstrap-icons/icons/plus-lg.svg";
import IconHandIndex from "bootstrap-icons/icons/hand-index.svg";
import IconEye from "bootstrap-icons/icons/eye.svg";
import IconPencil from "bootstrap-icons/icons/pencil.svg";
import IconFilter from "bootstrap-icons/icons/filter.svg";
import {
addBrushingListeners,
addDragingPointListeners,
addDrawLineListeners,
addGridListeners,
addPointListeners,
addRegressionLine,
calculateCorrelation,
calculateForRotation,
deleteLineListeners,
deletePointListeners,
drawScatterplot,
findIndexForLine,
selectPointByClickingListeners,
updateLineAndPoints,
updateColorForFilter,
drawScatterplot_singleDataset,
drawScatterplot_singleDataset2,
customFormatForBigNumber,
roundNumber,
clampLabel,
clampLabelForXAxis,
createNewTooltip,
} from "./drawScatterplot";
import style from "./scatterplot.style";
import StateComponent from "../../../stateComponent";
import { generateColorWheel } from "../../../functions";
export interface PointOnlyNumbers {
x: number;
y: number;
selected?: boolean;
color?: string;
id?: number;
scatter_titel?: string;
}
export interface Point {
x: number | string;
y: number | string;
selected?: boolean;
offset?: number;
color?: string;
id?: number;
scatter_titel?: string;
}
export interface OneDimentionalPoint {
p: number | string;
selected?: boolean;
offset?: number;
color?: string;
id?: number;
scatter_titel?: string;
}
export interface OneDimentionalNumberPoint {
p: number;
selected?: boolean;
offset?: number;
color?: string;
id?: number;
scatter_titel?: string;
}
@customElement("scatterplot-chart")
export class ScatterplotChart extends StateComponent {
static styles = style;
@property({ type: Object, attribute: true, reflect: true })
accessor definition = Definition;
@property({ type: Boolean, attribute: true, reflect: true })
accessor addNode: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor selectNode: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor selectByClicking: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor selectByBrushing: boolean;
@property({ type: Number, attribute: true, reflect: true })
accessor selectedIndextoAddNode: number;
@property({ type: Boolean, attribute: true, reflect: true })
accessor deselectByBrushing: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor editShowing: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor hideSelected: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor selectAll: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor deleteSelected: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor showAll: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor drawLine: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor filterOn: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor hoverCursorChange: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor isDragging: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor animation: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor showGrid: boolean;
@property({ type: String, attribute: true, reflect: true })
accessor showRegressionLine: string;
@property({ type: Boolean, attribute: true, reflect: true })
accessor showCorrelation: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor hoverTooltip: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor showOutliers: boolean;
@property({ type: Boolean, attribute: true, reflect: true })
accessor showMean: boolean;
connectedCallback() {
super.connectedCallback();
this.sharedState.chart.optionDefinition = OptionDefinition;
this.sharedState.chart.options = Object.entries(OptionDefinition).reduce(
(acc, [key, value]) => {
acc[key] = this.sharedState.chart.options[key] ?? value.default;
return acc;
},
{} as any
) as ChartOptions<OptionDefinition>;
}
// Lifecycle method that gets called after the component is updated
protected updated(_changedProperties: PropertyValues) {
super.updated(_changedProperties);
// Check if sharedState or any other relevant properties have changed
if (
_changedProperties.has("animation") ||
_changedProperties.has("showGrid") ||
_changedProperties.has("showCorrelation") ||
_changedProperties.has("showOutliers") ||
_changedProperties.has("showMean") ||
_changedProperties.has("showRegressionLine") ||
_changedProperties.has("hoverTooltip") ||
_changedProperties.get("sharedState")?.scatterDatasets.title !==
this.sharedState.scatterDatasets.title
) {
if (!this.isDragging) {
// Add a condition if the line is not dragged
this.updateScatterplot();
}
}
}
render() {
this.animation = this.sharedState.chart.options.animation as boolean;
this.hoverTooltip = this.sharedState.chart.options.hoverTooltip as boolean;
this.showGrid = this.sharedState.chart.options.showGrid as boolean;
this.showCorrelation = this.sharedState.chart.options
.showCorrelation as boolean;
this.showOutliers = this.sharedState.chart.options.showOutliers as boolean;
this.showMean = this.sharedState.chart.options.showMean as boolean;
this.showRegressionLine = this.sharedState.regressionType;
const firstSelectedDataset =
this.sharedState.scatterDatasets.sets.length > 0
? this.sharedState.scatterDatasets.sets[
this.sharedState.scatterDatasets.selected.dataset_indexes[0]
]
: undefined;
const x_axis_type =
this.sharedState.scatterDatasets.selected.axis.x === "None"
? "none"
: firstSelectedDataset?.typeOfEachData[
firstSelectedDataset?.labels.indexOf(
this.sharedState.scatterDatasets.selected.axis.x
)
];
const y_axis_type =
this.sharedState.scatterDatasets.selected.axis.y === "None"
? "none"
: firstSelectedDataset?.typeOfEachData[
firstSelectedDataset?.labels.indexOf(
this.sharedState.scatterDatasets.selected.axis.y
)
];
const lineTableVisible =
this.sharedState.lineDatasets.sets.length > 0 &&
x_axis_type === "number" &&
y_axis_type === "number";
const regressionTableVisible =
(this.sharedState.regressionType !== "none" ||
this.sharedState.chart.options.showCorrelation) &&
x_axis_type === "number" &&
y_axis_type === "number" &&
this.sharedState.scatterDatasets.sets.length !== 0 &&
this.selectedDatasets_length !== 0 &&
this.totalDataLength !== 0;
const filterTableVisible =
this.sharedState.scatterDatasets.selected.dataset_indexes.length === 1 &&
firstSelectedDataset?.filter &&
firstSelectedDataset?.filter?.label !== "" &&
firstSelectedDataset?.filter?.type !== "none" &&
firstSelectedDataset?.filter?.index_in_labels !== -1 &&
firstSelectedDataset.dimensional_data.length !== 0 &&
(x_axis_type !== "none" || y_axis_type !== "none");
// If the animation is on, deactivate the addNode, selectByClicking, selectByBrushing, deselectByBrushing, drawLine
if (this.animation) {
this.selectedIndextoAddNode = undefined;
this.selectByClicking = false;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.drawLine = false;
this.hoverCursorChange = true;
}
return html`<div class="scatterplot-root">
<div class="scatterplot-wrapper">
<div class="scatterplot-svg"></div>
<div class="right-side">
<div class="button-container">
<sl-popup
placement="bottom"
strategy="fixed"
.active="${this.addNode}"
distance="7"
>
<span slot="anchor">
<sl-tooltip
hoist
placement="top-center"
content=${
this.sharedState.chart.options.animation
? "To use this option, you must first disable the animation"
: this.sharedState.scatterDatasets.sets.length === 0
? "To use this option, you must first add dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 0
? "To use this option, you must select at least one dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 1 &&
this.sharedState.lineDatasets.sets.length === 0
? "To use this option, you must first add at least one data point to the selected dataset table"
: this.totalDataLength === 0 &&
this.selectedDatasets_length > 1 &&
this.sharedState.lineDatasets.sets.length === 0
? "To use this option, you must first add at least one data point to the selected dataset tables"
: x_axis_type !== "number" || y_axis_type !== "number"
? "The x and y axis must be numbers to use this option"
: "Add a new data point"
}
>
<sl-button
variant=${
this.selectedIndextoAddNode >= 0 ? "primary" : "default"
}
size="small"
circle
id="add-point-button"
@click=${(e) => {
if (
this.drawLine ||
this.selectByClicking ||
this.selectByBrushing ||
this.deselectByBrushing
) {
this.drawLine = false;
this.selectByClicking = false;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.updateScatterplot();
}
if (this.selectedIndextoAddNode >= 0) {
this.selectedIndextoAddNode = undefined;
this.addNode = false;
this.hoverCursorChange = true;
this.updateScatterplot();
} else {
this.addNode = !this.addNode;
}
// no popup when there is only one selected dataset
if (
this.sharedState.scatterDatasets.selected
.dataset_indexes.length === 1
) {
this.addNode = false;
this.selectedIndextoAddNode =
this.sharedState.scatterDatasets.selected.dataset_indexes[0];
this.hoverCursorChange = false;
this.updateScatterplot();
}
e.target.focus();
}}
.disabled=${
this.sharedState.chart.options.animation ||
(this.totalDataLength === 0 &&
this.sharedState.lineDatasets.sets.length === 0) ||
x_axis_type !== "number" ||
y_axis_type !== "number"
}
@blur=${() => (this.addNode = false)}
>
<sl-icon src=${IconPluslg} label="Data" style="pointer-events: none;"></sl-icon>
</sl-button>
</sl-tooltip>
</span>
<div class="popup-content">
${
this.sharedState.scatterDatasets.sets.length !== 0
? this.sharedState.scatterDatasets.selected.dataset_indexes.map(
(dataset_index) => {
const dataset =
this.sharedState.scatterDatasets.sets[
dataset_index
];
return html`
<sl-menu-item
type="checkbox"
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
if (
this.selectedIndextoAddNode === dataset_index
) {
this.selectedIndextoAddNode = undefined;
} else {
this.selectedIndextoAddNode = dataset_index;
this.hoverCursorChange = false;
this.updateScatterplot();
}
}}
.checked=${this.selectedIndextoAddNode ===
dataset_index}
>
A data point of
<span style="color: ${dataset.color};"
>${dataset.name}</span
>
</sl-menu-item>
`;
}
)
: html``
}
</div>
</sl-popup>
<sl-popup
placement="bottom"
strategy="fixed"
.active=${this.selectNode}
distance="7"
>
<span slot="anchor">
<sl-tooltip
hoist
placement="top-center"
content=${
this.sharedState.chart.options.animation
? "To use this option, you must first disable the animation"
: this.sharedState.scatterDatasets.sets.length === 0
? "To use this option, you must first add dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 0
? "To use this option, you must select at least one dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 1
? "To use this option, you must first add at least one data point to the selected dataset table"
: this.totalDataLength === 0 &&
this.selectedDatasets_length > 1
? "To use this option, you must first add at least one data point to the selected dataset tables"
: x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1
? "To use this option, you must first select the x or y axis"
: (x_axis_type !== "number" ||
y_axis_type !== "number") &&
this.selectedDatasets_length > 1
? "The x and y axis must be numbers to use this option"
: "Select data points"
}
:
"(De)select data points"}
>
<sl-button
variant=${
this.selectByBrushing ||
this.selectByClicking ||
this.deselectByBrushing
? "primary"
: "default"
}
size="small"
circle
id="select-point-button"
.disabled=${
this.totalDataLength === 0 ||
this.sharedState.chart.options.animation ||
(x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1) ||
((x_axis_type !== "number" || y_axis_type !== "number") &&
this.selectedDatasets_length > 1)
}
@click=${(e) => {
if (this.selectedIndextoAddNode >= 0 || this.drawLine) {
this.selectedIndextoAddNode = undefined;
this.drawLine = false;
this.updateScatterplot();
}
if (
this.selectByBrushing ||
this.selectByClicking ||
this.deselectByBrushing
) {
this.selectByClicking = false;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.selectNode = false;
this.updateScatterplot();
} else {
this.selectNode = !this.selectNode;
}
e.target.focus();
}}
@blur=${() => (this.selectNode = false)}
>
<sl-icon src=${IconHandIndex} label="Selection" style="pointer-events: none;"></sl-icon>
</sl-button>
</sl-tooltip>
</span>
<div class="popup-content">
<sl-button
id="select-by-clicking"
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.selectByClicking = true;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.deleteSelected = false;
this.hideSelected = false;
this.showAll = false;
this.hoverCursorChange = false;
this.updateScatterplot();
}}
>
(De)select By Clicking
</sl-button>
<sl-button
id="select-by-brushing"
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.selectByBrushing = true;
this.selectByClicking = false;
this.deselectByBrushing = false;
this.deleteSelected = false;
this.hideSelected = false;
this.showAll = false;
this.updateScatterplot();
}}
>
Select By Brushing
</sl-button>
<sl-button
id="deselect-by-brushing"
.disabled=${this.selectedDataLength === 0}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.deselectByBrushing = true;
this.selectByBrushing = false;
this.selectByClicking = false;
this.deleteSelected = false;
this.hideSelected = false;
this.showAll = false;
this.updateScatterplot();
}}
>
Deselect By Brushing
</sl-button>
</div>
</sl-popup>
<sl-tooltip
hoist
placement=${
this.sharedState.chart.options.animation ||
x_axis_type !== "number" ||
y_axis_type !== "number" ||
this.totalDataLength === 0
? "top-end"
: "top-center"
}
content=${
this.sharedState.chart.options.animation
? "To use this option, you must first disable the animation"
: this.sharedState.scatterDatasets.sets.length === 0
? "To use this option, you must first add dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 0
? "To use this option, you must select at least one dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 1
? "To use this option, you must first add at least one data point to the selected dataset table"
: this.totalDataLength === 0 &&
this.selectedDatasets_length > 1
? "To use this option, you must first add at least one data point to the selected dataset tables"
: x_axis_type !== "number" || y_axis_type !== "number"
? "The x and y axis must be numbers to use this option"
: "Draw a line in the scatterplot"
}
>
<sl-button
size="small"
circle
id="draw-line-button"
@click=${() => {
this.selectedIndextoAddNode = undefined;
this.selectByClicking = false;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.hoverCursorChange = false;
this.drawLine = !this.drawLine;
this.updateScatterplot();
}}
variant=${this.drawLine ? "primary" : "default"}
.disabled=${
this.sharedState.chart.options.animation ||
this.totalDataLength === 0 ||
x_axis_type !== "number" ||
y_axis_type !== "number"
}
>
<sl-icon src=${IconPencil} label="Draw"></sl-icon>
</sl-button>
</sl-tooltip>
<sl-popup
placement="bottom"
strategy="fixed"
.active=${this.editShowing}
distance="7"
>
<span slot="anchor">
<sl-tooltip
hoist
placement=${
this.totalDataLength === 0 ||
this.sharedState.chart.options.animation ||
(x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1) ||
((x_axis_type !== "number" || y_axis_type !== "number") &&
this.selectedDatasets_length > 1)
? "top-end"
: "top-center"
}
content=${
this.sharedState.chart.options.animation
? "To use this option, you must first disable the animation"
: this.sharedState.scatterDatasets.sets.length === 0
? "To use this option, you must first add dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 0
? "To use this option, you must select at least one dataset"
: this.totalDataLength === 0 &&
this.selectedDatasets_length === 1
? "To use this option, you must first add at least one data point to the selected dataset table"
: this.totalDataLength === 0 &&
this.selectedDatasets_length > 1
? "To use this option, you must first add at least one data point to the selected dataset tables"
: x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1
? "To use this option, you must first select the x or y axis"
: (x_axis_type !== "number" ||
y_axis_type !== "number") &&
this.selectedDatasets_length > 1
? "The x and y axis must be numbers to use this option"
: "Show data points"
}
>
<sl-button
variant="default"
size="small"
circle
id="edit-button"
.disabled=${
this.totalDataLength === 0 ||
this.sharedState.chart.options.animation ||
(x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1) ||
((x_axis_type !== "number" || y_axis_type !== "number") &&
this.selectedDatasets_length > 1)
}
@click=${(e) => {
if (
this.selectedIndextoAddNode >= 0 ||
this.selectByClicking ||
this.selectByBrushing ||
this.deselectByBrushing ||
this.drawLine
) {
this.selectedIndextoAddNode = undefined;
this.selectByClicking = false;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.drawLine = false;
this.updateScatterplot();
}
this.editShowing = !this.editShowing;
e.target.focus();
}}
@blur=${() => (this.editShowing = false)}
>
<sl-icon src=${IconEye} label="View" style="pointer-events: none;"></sl-icon>
</sl-button>
</sl-tooltip>
</span>
<div class="popup-content">
<sl-menu-item
type="checkbox"
?checked=${this.hideSelected}
.disabled=${this.selectedDataLength === 0}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.hideSelected = !this.hideSelected;
this.hideSelected ? (this.showAll = false) : null;
!this.hideSelected ? (this.showAll = true) : null;
this.updateScatterplot();
}}
>Hide Selected Points</sl-menu-item
>
<sl-menu-item
type="checkbox"
?checked=${this.showAll}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.showAll = !this.showAll;
if (this.showAll) {
this.hideSelected = false;
}
this.updateScatterplot();
}}
>Show All Points</sl-menu-item
>
<sl-menu-item
type="checkbox"
?checked=${this.selectAll}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.selectAll = !this.selectAll;
if (this.selectAll) {
this.sharedState.scatterDatasets.sets.forEach(
(dataset, index) => {
for (
let i = dataset.dimensional_data.length - 1;
i >= 0;
i--
) {
if (
this.sharedState.scatterDatasets.selected.dataset_indexes.includes(
index
)
) {
if (!dataset.dimensional_data[i].selected) {
dataset.dimensional_data[i].selected = true;
}
}
}
}
);
} else {
// deselect all points
this.sharedState.scatterDatasets.sets.forEach(
(dataset, index) => {
for (
let i = dataset.dimensional_data.length - 1;
i >= 0;
i--
) {
if (
this.sharedState.scatterDatasets.selected.dataset_indexes.includes(
index
)
) {
if (dataset.dimensional_data[i].selected) {
dataset.dimensional_data[i].selected = false;
}
}
}
}
);
}
this.updateScatterplot();
}}
>Select All Points</sl-menu-item
>
<sl-menu-item
type="checkbox"
?checked=${this.deleteSelected}
.disabled=${this.selectedDataLength === 0}
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0) return;
this.deleteSelected = !this.deleteSelected;
if (this.deleteSelected) {
this.sharedState.scatterDatasets.sets.forEach(
(dataset, index) => {
for (
let i = dataset.dimensional_data.length - 1;
i >= 0;
i--
) {
if (
this.sharedState.scatterDatasets.selected.dataset_indexes.includes(
index
)
) {
if (dataset.dimensional_data[i].selected) {
dataset.dimensional_data.splice(i, 1);
dataset.ids.splice(i, 1);
}
}
}
}
);
}
this.updateScatterplot();
}}
>Delete Selected Points</sl-menu-item
>
</div>
</sl-popup>
<sl-popup
placement="bottom"
strategy="fixed"
.active=${this.filterOn}
distance="7"
>
<span slot="anchor">
<sl-tooltip
hoist
placement=${"top-end"}
content=${
this.sharedState.chart.options.animation
? "To use this option, you must first disable the animation"
: this.sharedState.scatterDatasets.sets.length === 0
? "To use this option, you must first add dataset"
: this.selectedDatasets_length !== 1
? "To use this option, you must first select only one dataset"
: this.totalDataLength === 0
? "To use this option, you must first add at least one data point to the selected dataset table"
: x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1
? "To use this option, you must first select the x or y axis"
: "Group data points"
}
>
<sl-button
variant="default"
size="small"
circle
id="filter-button"
@click=${(e) => {
if (
this.selectedIndextoAddNode >= 0 ||
this.selectByClicking ||
this.selectByBrushing ||
this.deselectByBrushing ||
this.drawLine
) {
this.selectedIndextoAddNode = undefined;
this.selectByClicking = false;
this.selectByBrushing = false;
this.deselectByBrushing = false;
this.drawLine = false;
}
this.filterOn = !this.filterOn;
e.target.focus();
}}
.disabled=${
this.sharedState.chart.options.animation ||
this.selectedDatasets_length !== 1 ||
this.totalDataLength === 0 ||
(x_axis_type === "none" &&
y_axis_type === "none" &&
this.selectedDatasets_length === 1)
}
@blur=${() => (this.filterOn = false)}
>
<sl-icon src=${IconFilter} label="Filter" style="pointer-events: none;"></sl-icon>
</sl-button>
</sl-tooltip>
</span>
<div class="popup-content">
${(firstSelectedDataset?.labels ?? []).map(
(label, i) =>
html`
<sl-menu-item
type="checkbox"
@mousedown=${(e: MouseEvent) => {
if (e.button !== 0 || !firstSelectedDataset) return;
e.preventDefault();
e.stopPropagation();
const index =
firstSelectedDataset.filter.label === label
? -1
: i;
if (index !== -1) {
firstSelectedDataset.filter.label = label;
firstSelectedDataset.filter.index_in_labels = index;
const type =
firstSelectedDataset.typeOfEachData[index];
firstSelectedDataset.filter.type =
type === "number" ? "number" : "string";
updateColorForFilter(i, type, firstSelectedDataset);
} else {
firstSelectedDataset.filter.label = "";
firstSelectedDataset.filter.index_in_labels = -1;
firstSelectedDataset.filter.type = "none";
firstSelectedDataset.dimensional_data.forEach(
(d) => {
d.color = firstSelectedDataset.color;
}
);
}
this.filterOn = false;
this.updateScatterplot();
}}
.checked=${firstSelectedDataset?.filter.label === label}
>
${label}
</sl-menu-item>
`
)}
</div>
</sl-popup>
</div>
<div
class="table-wrapper ${
regressionTableVisible ? "visible" : "hidden"
}"
style="max-height: ${
this.sharedState.chart.options.animation &&
regressionTableVisible &&
!lineTableVisible &&
!filterTableVisible
? "19rem"
: regressionTableVisible &&
!lineTableVisible &&
!filterTableVisible
? "23rem"
: this.sharedState.chart.options.animation &&
regressionTableVisible &&
(!lineTableVisible || !filterTableVisible)
? "8.5rem"
: regressionTableVisible &&
(!lineTableVisible || !filterTableVisible)
? "11rem"
: this.sharedState.chart.options.animation
? "3.8rem"
: "4.3rem"
};"
>
<table>
<thead>
<tr>
<th class="th_regression">Regression</th>
<th class="th_correlation">R</th>
</tr>
</thead>
<tbody class="regression-formulas"></tbody>
</table>
</div>
<div
class="table-wrapper ${lineTableVisible ? "visible" : "hidden"}
"
style="max-height: ${
this.sharedState.chart.options.animation &&
lineTableVisible &&
!regressionTableVisible &&
!filterTableVisible
? "19rem"
: lineTableVisible &&
!regressionTableVisible &&
!filterTableVisible
? "23rem"
: this.sharedState.chart.options.animation &&
lineTableVisible &&
(!regressionTableVisible || !filterTableVisible)
? "8rem"
: lineTableVisible &&
(!regressionTableVisible || !filterTableVisible)
? "10.5rem"
: this.sharedState.chart.options.animation
? "6.5rem"
: "9rem"
};"
>
<table>
<thead>
<tr>
<th class="th_line">Line</th>
</tr>
</thead>
<tbody class="line-formulas"></tbody>
</table>
</div>
<div
class="table-wrapper ${filterTableVisible ? "visible" : "hidden"}"
style="max-height: ${
this.sharedState.chart.options.animation &&
filterTableVisible &&
!regressionTableVisible &&
!lineTableVisible
? "19rem"
: filterTableVisible &&
!regressionTableVisible &&
!lineTableVisible
? "23rem"
: this.sharedState.chart.options.animation &&
filterTableVisible &&
(!regressionTableVisible || !lineTableVisible)
? "8.5rem"
: filterTableVisible &&
(!regressionTableVisible || !lineTableVisible)
? "11rem"
: this.sharedState.chart.options.animation
? "6.5rem"
: "9rem"
};"
>
<div class="legend-container ${
filterTableVisible && firstSelectedDataset?.filter.type === "string"
? "visible"
: "hidden"
}">
<h2 style="font-size: 12px; text-align: center;">${
filterTableVisible &&
firstSelectedDataset?.filter.type === "string"
? clampLabel(firstSelectedDataset?.filter.label, 15)
: ""
}</h2>
${
filterTableVisible && firstSelectedDataset?.filter.type === "string"
? html` <div class="legend-items"></div> `
: html``
}
</div>
<h2 style="font-size: 12px; text-align: center;" ${
filterTableVisible && firstSelectedDataset?.filter.type === "number"
? "display: visible;"
: "display: none;"
}">
${
filterTableVisible &&
firstSelectedDataset?.filter.type === "number"
? clampLabel(firstSelectedDataset?.filter.label, 15)
: ""
}
</h2>
${
filterTableVisible && firstSelectedDataset?.filter.type === "number"
? html`
<div class="gradient-bar-container">
<div class="gradient-bar" id="gradient-bar"></div>
<div class="ticks">
<div class="tick"></div>
<div class="tick"></div>
<div class="tick"></div>
<div class="tick"></div>
<div class="tick"></div>
<div class="tick"></div>
</div>
<div class="gradient-bar-labels">
<span id="min-value"
>${firstSelectedDataset?.filter?.rangesandcolors
? firstSelectedDataset?.filter?.rangesandcolors[0]
?.range.min >
10 ** 4
? customFormatForBigNumber(
firstSelectedDataset?.filter?.rangesandcolors[0]
?.range.min,
2
)
: roundNumber(
firstSelectedDataset?.filter?.rangesandcolors[0]
?.range.min,
2
)
: "4"}</span
>
<!-- Min value -->
<span id="max-value"
>${firstSelectedDataset?.filter?.rangesandcolors
? firstSelectedDataset?.filter?.rangesandcolors[
firstSelectedDataset?.filter?.rangesandcolors
.length - 1
]?.range.max >
10 ** 4
? customFormatForBigNumber(
firstSelectedDataset?.filter?.rangesandcolors[
firstSelectedDataset?.filter?.rangesandcolors
.length - 1
]?.range.max,
2
)
: roundNumber(
firstSelectedDataset?.filter?.rangesandcolors[
firstSelectedDataset?.filter?.rangesandcolors
.length - 1
]?.range.max,
2
)
: "4"}</span
>
<!-- Max value -->
</div>
</div>
`
: html``
}
</div>
${
this.sharedState.chart.options.animation
? html`<sl-range
id="animationRange"
.value=${this.definition.animationTime}
@sl-change=${(e: any) => {
this.definition.animationTime = e.target.value;
this.updatePlot();
}}
min="1000"
max="5000"
step="500"
label="Animation Time"
></sl-range>`
: html``
}
</div>
</div>
</div>`;
}
get totalDataLength() {
let length = 0;
if (this.sharedState.scatterDatasets.sets.length !== 0) {
this.sharedState.scatterDatasets.selected.dataset_indexes.forEach(
(dataset_index) => {
length +=
this.sharedState.scatterDatasets.sets[dataset_index]
?.dimensional_data.length;
}
);
}
return length;
}
get selectedDatasets_length() {
return this.sharedState.scatterDatasets.selected.dataset_indexes.length;
}
// Get the length of the selected data points
get selectedDataLength() {
let length = 0;
this.sharedState.scatterDatasets.sets.forEach((dataset) => {
dataset.dimensional_data.forEach((point) => {
if (point.selected) {
length++;
}
});
});
return length;
}
// Get the length of the unselected data points
get unselectedDataLength() {
let length = 0;
this.sharedState.scatterDatasets.sets.forEach((dataset) => {
dataset.dimensional_data.forEach((point) => {
if (!point.selected) {
length++;
}
});
});
return length;
}
firstUpdated() {
this.addNode = false;
this.selectNode = false;
this.selectByBrushing = false;
this.selectByClicking = false;
this.deselectByBrushing = false;
this.selectedIndextoAddNode = undefined;
this.hideSelected = false;
this.selectAll = false;
this.deleteSelected = false;
this.showAll = true;
this.editShowing = false;
this.drawLine = false;
this.filterOn = false;
this.hoverCursorChange = true;
this.isDragging = false;
this.createScatterPlot();
}
//updatePlot for new animation time
updatePlot() {
const animationRange = this.shadowRoot.querySelector(
"#animationRange"
) as HTMLInputElement;
this.definition.animationTime = Number(animationRange.value);
// delete the old scatter plot
const root = this.shadowRoot.querySelector(".scatterplot-root");
d3.select(root).selectAll("svg").remove();
// delete the left over tooltip
d3.select(root).selectAll(".tooltip").remove();
this.createScatterPlot(); // Redraw the scatter plot with the new animation time
this.dispatchStateChange();
}
// i is index of the selected label to filter
//Update the scatterplot after dragging a point
updateScatterplot() {
if (!this.hideSelected) {
this.showAll = true;
}
const root = this.shadowRoot.querySelector(".scatterplot-root");
d3.select(root).selectAll("svg").remove();
d3.select(root).selectAll(".tooltip").remove();
d3.select(root).selectAll("path.line").remove();
this.createScatterPlot();
this.dispatchStateChange();
}
createScatterPlot() {
const selectedDatasets = this.sharedState.scatterDatasets.sets.filter(
(_, i) =>
this.sharedState.scatterDatasets.selected.dataset_indexes
.sort()
.includes(i)
);
const x_axis_type =
this.sharedState.scatterDatasets.sets.length === 0
? "noData"
: selectedDatasets.length === 0
? "noSelectedDataset"
: this.totalDataLength === 0 && selectedDatasets.length !== 0
? "noDataInSelectedDatasets"
: this.sharedState.scatterDatasets.selected.axis.x === "None"
? "none"
: selectedDatasets.length > 0
? selectedDatasets[0].typeOfEachData[
selectedDatasets[0].labels.indexOf(
this.sharedState.scatterDatasets.selected.axis.x
)
]
: undefined;
const y_axis_type =
this.sharedState.scatterDatasets.sets.length === 0
? "noData"
: selectedDatasets.length === 0
? "noSelectedDataset"
: this.totalDataLength === 0 && selectedDatasets.length !== 0
? "noDataInSelectedDatasets"
: this.sharedState.scatterDatasets.selected.axis.y === "None"
? "none"
: selectedDatasets.length > 0
? selectedDatasets[0].typeOfEachData[
selectedDatasets[0].labels.indexOf(
this.sharedState.scatterDatasets.selected.axis.y
)
]
: undefined;
const root = this.shadowRoot.querySelector(".scatterplot-svg");
let animationTime = Number(this.definition.animationTime);
if (
(x_axis_type === "number" && y_axis_type === "number") ||
this.sharedState.scatterDatasets.selected.dataset_indexes.length > 1 ||
(x_axis_type === "noSelectedDataset" &&
y_axis_type === "noSelectedDataset") ||
(x_axis_type === "noData" && y_axis_type === "noData") ||
(x_axis_type === "noDataInSelectedDatasets" &&
y_axis_type === "noDataInSelectedDatasets")
) {
const axisLabe