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.
244 lines (241 loc) • 9.68 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { html, css } from "lit";
import * as d3 from "d3";
import { customElement } from "lit/decorators.js";
import { SharedTestStateMixin } from "../../../state";
import SlRange from "@shoelace-style/shoelace/dist/components/range/range.js";
import { LitElementWw } from "@webwriter/lit";
let ScatterplotAdvancedChart = class ScatterplotAdvancedChart extends SharedTestStateMixin(LitElementWw) {
render() {
return html `<div class="scatterplot-root">
<div class="scatterplot-wrapper">
<div class="descriptions">
<div class="regression-formula"></div>
<div class="correlations"></div>
</div>
</div>
</div>`;
}
firstUpdated() {
this.createScatterPlot();
}
createScatterPlot() {
const datasets = [];
// Get the 2 dimentiional data from the shared state
const indexOfX = this.sharedState.scatterplotAdvanced.scatterplotdatasetsAdvanced.labels.indexOf(this.sharedState.scatterplotAdvanced.selectedX);
const indexOfY = this.sharedState.scatterplotAdvanced.scatterplotdatasetsAdvanced.labels.indexOf(this.sharedState.scatterplotAdvanced.selectedY);
this.sharedState.scatterplotAdvanced.scatterplotdatasetsAdvanced.sets.forEach((dataset) => {
let datasets_x;
let datasets_y;
let dimensional_data;
dataset.data.forEach((d, i) => {
if (i === indexOfX) {
datasets_x.push(d);
}
if (i === indexOfY) {
datasets_y.push(d);
}
});
// extract each x in dataset_x and y in dataset_y to build { x: number | string; y: number | string }
datasets_x.forEach((d, i) => {
dimensional_data.push({ x: d, y: datasets_y[i] });
});
datasets.push(dimensional_data);
});
// get the max value of the dataset
let maxX = 0;
let maxY = 0;
let minX = 0;
let minY = 0;
datasets.forEach((dataset) => {
if (typeof dataset[0].x === "number") {
Math.max(Number(...dataset.map((d) => d.x))) > maxX
? (maxX = Math.max(Number(...dataset.map((d) => d.x))))
: maxX;
Math.min(Number(...dataset.map((d) => d.x))) < minX
? (minX = Math.min(Number(...dataset.map((d) => d.x))))
: minX;
}
if (typeof dataset[0].y === "number") {
Math.max(Number(...dataset.map((d) => d.y))) > maxY
? (maxY = Math.max(Number(...dataset.map((d) => d.y))))
: maxY;
Math.min(Number(...dataset.map((d) => d.y))) < minY
? (minY = Math.min(Number(...dataset.map((d) => d.y))))
: minY;
}
});
// Add colors
datasets.map((dataset, index) => {
dataset.map((d) => (d.color =
this.sharedState.scatterplotdatasets.sets[index].scatter_color));
});
// Add labels
datasets.map((dataset, index) => {
dataset.map((d, i) => (d.label = this.sharedState.scatterplotdatasets.sets[index].labels[i]));
});
// Add scatterplot title
datasets.map((dataset, index) => {
dataset.map((d) => (d.scatter_titel =
this.sharedState.scatterplotdatasets.sets[index].scatter_titel));
});
let xlabel = String(this.sharedState.scatterplotdatasets.axisLabels.x);
let ylabel = String(this.sharedState.scatterplotdatasets.axisLabels.y);
const roundingFactorX = 10 ** Math.floor(Math.log10(maxX));
maxX = Math.ceil(maxX / roundingFactorX) * roundingFactorX;
const roundingFactorY = 10 ** Math.floor(Math.log10(maxY));
maxY = Math.ceil(maxY / roundingFactorY) * roundingFactorY;
// set the dimensions and margins of the graph
const margin = { top: 10, right: 30, bottom: 50, left: 70 }, width = 550 - margin.left - margin.right, height = 400 - margin.top - margin.bottom;
const root = this.shadowRoot.querySelector(".scatterplot-wrapper");
// append the svg object to the body of the page
const svg = d3
.select(root)
.append("svg")
.attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add X axis
let x;
if (typeof datasets[0][0].x === "number") {
x = d3.scaleLinear().domain([minX, maxX]).range([0, width]);
}
else {
x = d3
.scaleBand()
.domain(datasets.forEach((dataset) => dataset.map((d) => d.x)))
.range([0, width]);
}
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg
.append("text")
.attr("class", "x label")
.attr("text-anchor", "middle")
.attr("x", width / 2)
.attr("y", height + 40)
.attr("style", "text-align: center;")
.text(xlabel);
//TODO: Fix position of y label with long text eg: Y LabelX LabelX LabelX LabelX Label
// Add Y axis
const y = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
svg
.append("text")
.attr("class", "y label")
.attr("text-anchor", "middle")
.attr("x", -((height + margin.bottom) / 2)) // to my x axis
.attr("y", -40) // to my y axis
.attr("transform", "rotate(-90)")
.text(ylabel);
// For normal scatter plot
let scatter = svg
.append("g")
.attr("clip-path", "url(#clip)")
.selectAll("circle")
.data(datasets.flat())
.enter()
.append("circle")
.attr("cx", (d) => x(d.x))
.attr("cy", (d) => y(d.y))
.attr("r", 7)
.style("fill", (d) => d.color ?? "#417ca1")
.style("stroke", "white")
.style("opacity", 0.5);
// Add tooltip for data points
const tooltip = d3
.select(root)
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("position", "fixed")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px");
// Three function that change the tooltip when user hover / move / leave a cell
const mouseover = function (_e, d) {
// TODO: get point data of point that we are currently hovering
tooltip
.style("opacity", 1)
.html(d.label + "<br/>" + xlabel + ": " + d.x + " " + ylabel + ": " + d.y);
};
const mousemove = function (e) {
tooltip
// It is important to put the + 10: other wise the tooltip is exactly where the point is an it creates a weird effect
.style("left", e.x + 10 + "px")
.style("top", e.y + 10 + "px");
};
const mouseleave = function () {
tooltip.transition().duration(200).style("opacity", 0);
};
scatter
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
}
static get scopedElements() {
return {
"sl-range": SlRange,
};
}
};
ScatterplotAdvancedChart.styles = css `
:host {
width: 100%;
}
.scatterplot-root {
width: 100%;
}
.scatterplot-wrapper {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.descriptions {
width: 100%;
display: flex;
justify-content: space-between;
gap: 16px;
}
.regression-formula, .correlations {
display: flex;
flex-direction: column;
font-size: 12px;
}
svg {
border: 1px solid black;
flex: 1 0 0
width: 100%;
}
text {
font-family: Helvetica;
font-size: 12px;
color: #333;
}
g {
font-size: 12px;
}
.selected {
opacity: 1 !important;
stroke: black !important;
stroke-width: 1px !important;
}
`;
ScatterplotAdvancedChart = __decorate([
customElement("scatterplot-advanced-chart")
// TODO: fix type mismatch between lit-sm and @webwriter/lit
], ScatterplotAdvancedChart);
export { ScatterplotAdvancedChart };