UNPKG

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
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