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.

244 lines (241 loc) 9.68 kB
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 };