Skip to content
Snippets Groups Projects
Commit ef9fe3d4 authored by Lennard Michael Strohmeyer's avatar Lennard Michael Strohmeyer
Browse files

Bugfixes und Anpassungen, BoxPlot-Widget, Erweiterung Grid-Widget

parent 66e252c5
No related branches found
No related tags found
No related merge requests found
Source diff could not be displayed: it is too large. Options to address this: view the blob.
......@@ -26,7 +26,9 @@
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"@polaris/dashboard-sdk": "file:../",
"d3": "^7.8.2"
"d3": "^7.8.5",
"gridstack": "^9.1.0"
}
}
......@@ -33,6 +33,7 @@ export const chartJSBarChartDef = { "chartjs-barchart-widget" :
"sample description",
chartjs_data,
{
chartjs_options: {
scales: {
x: {
title: {
......@@ -48,5 +49,6 @@ export const chartJSBarChartDef = { "chartjs-barchart-widget" :
}
}
}
}
)
}
Source diff could not be displayed: it is too large. Options to address this: view the blob.
......@@ -6,14 +6,14 @@
*/
/*!
* Chart.js v4.3.1
* Chart.js v4.4.0
* https://www.chartjs.org
* (c) 2023 Chart.js Contributors
* Released under the MIT License
*/
/*!
* GridStack 7.1.1
* GridStack 7.3.0
* https://gridstackjs.com/
*
* Copyright (c) 2021-2022 Alain Dumesny
......
......@@ -11,6 +11,9 @@ import { SimpleGroupedBarChartWidget } from "./src/js/charts/simple_grouped_barc
import { ChartJSWidget } from "./src/js/charts/chartjs";
import { HeatMapWidget } from "./src/js/charts/heatmap";
import { GridWidget } from "./src/js/charts/grid";
import { BoxPlotWidget } from "./src/js/charts/boxplot";
import { TextElement} from "./src/js/elements/textelement";
import { TextAreaElement } from "./src/js/elements/textareaelement";
export {
getResult,
......@@ -25,5 +28,8 @@ export {
SimpleGroupedBarChartWidget,
ChartJSWidget,
HeatMapWidget,
GridWidget
GridWidget,
BoxPlotWidget,
TextElement,
TextAreaElement
};
Source diff could not be displayed: it is too large. Options to address this: view the blob.
......@@ -25,6 +25,7 @@
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"chart.js": "^4.3.1",
"d3": "^7.7.0",
"d3-scale-chromatic": "^3.0.0"
......
......@@ -47,6 +47,28 @@
}
}
.question-mark-legacy {
cursor: pointer;
opacity: 0.2;
float: right;
margin-right: 5px;
border: 3px solid black;
border-radius: 15px;
height: 30px;
width: 30px;
text-align: center;
line-height: 22px;
font-family: monospace;
font-weight: bold;
font-size: 27px;
transform: scale(0.8);
-ms-transform: scale(0.8);
-webkit-transform: scale(0.8);
&:hover {
opacity: 0.5;
}
}
.grid-stack-item-content {
overflow: hidden !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
......
import * as d3 from "d3";
import { BaseChartWidget } from "./base";
/*
data = {
min: 0,
max: 100,
q1: 12,
median: 35,
q3: 67,
}
*/
export class BoxPlotWidget extends BaseChartWidget {
constructor(title, description, data, options) {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, description, data, options);
this.marginRight = 10
this.marginBottom = 20
}
plot(divWidth, divHeight, el) {
const [width, height] = this.clearAndScaleSvg(divWidth, divHeight, 5, 10);
this.drawTitle();
/*if (!this.dataIsValid) {
this.showErrorMessage(divWidth, divHeight);
return this.wrapper.innerHTML;
}*/ // TODO
const xScale = d3.scaleLinear()
.domain([this.data.min,this.data.max])
.range([width, 0]);
let center = height / 2.0;
// Show the main horizontal line
this.g
.append("line")
.attr("x1", xScale(this.data.min))
.attr("x2", xScale(this.data.max))
.attr("y1", center )
.attr("y2", center )
.attr("stroke", "black")
// Show the box
this.g
.append("rect")
.attr("x", xScale(this.data.q3))
.attr("y", center - height / 2)
.attr("height", height)
.attr("width", xScale(this.data.q1) - xScale(this.data.q3))
.attr("stroke", "black")
.style("fill", "#69b3a2")
// show median, min and max horizontal lines
this.g
.selectAll("toto")
.data([this.data.min, this.data.median, this.data.max])
.enter()
.append("line")
.attr("x1", function(d){ return(xScale(d))})
.attr("x2", function(d){ return(xScale(d))})
.attr("y1", center - height / 2 )
.attr("y2", center + height / 2 )
.attr("stroke", "black")
return this.wrapper.innerHTML;
}
}
......@@ -7,41 +7,49 @@ export class ChartJSWidget {
this.data = data;
this.options = options;
if(document.querySelector(".chart-title") == null)
{
let temp_child = document.createElement("div");
temp_child.classList.add('chart-title');
document.body.appendChild(temp_child);
}
let temp_child = document.querySelector(".chart-title");
let style = window.getComputedStyle(temp_child);
let font_weight = style.getPropertyValue('font-weight');
let font_size = style.getPropertyValue('font-size');
let font_family = style.getPropertyValue('font-family');
let title_object = {title: {display: true, text: this.title, font: {weight: font_weight, size: font_size, family: font_family}}};
if("plugins" in this.options)
{
this.options = Object.assign(title_object, this.options.plugins)
}
else {
this.options = Object.assign({plugins: title_object}, this.options)
}
this.type = type;
this.dataIsValid = Array.isArray(data) ? data.length > 0 : !!data;
}
plot(divWidth, divHeight, element) {
element.style = "background-color: #f5f5f5; padding: 5px;";
const title_element = document.createElement("div");
title_element.className = "chart-title grid-title";
title_element.innerHTML = this.title;
title_element.style = "margin-bottom: 5px";
if (this.description) {
const question_mark_element = document.createElement("div");
question_mark_element.className = "question-mark-legacy";
question_mark_element.innerHTML = "?";
const randomId = Date.now();
question_mark_element.id = `id-${randomId}`;
title_element.appendChild(question_mark_element);
setTimeout(() => {
const targetEl = document.getElementById(`id-${randomId}`);
if (targetEl)
targetEl.addEventListener("click", (event) => {
this.options.onShowDesc(this.description);
});
});
}
element.appendChild(title_element);
const card_element = document.createElement("div");
card_element.className = "card"
element.appendChild(card_element);
this.canvas = document.createElement("canvas");
this.canvas.id = self.crypto.randomUUID();
element.appendChild(this.canvas);
card_element.appendChild(this.canvas);
this.canvas.style.backgroundColor = '#fff';
this.chart = new Chart(this.canvas.id, {
type: this.type,
data: this.data,
options: this.options
options: this.options.chartjs_options
});
return null;
......
......@@ -13,6 +13,7 @@ import { StackedBarChartWidget } from "./stacked_barchart";
import {TextElement} from "../elements/textelement";
import {TextAreaElement} from "../elements/textareaelement";
import { ScatterChartWidget } from "./scatterchart";
import {BoxPlotWidget} from "./boxplot";
const chart_types = [
......@@ -55,13 +56,31 @@ export class GridWidget extends BaseChartWidget {
//this.drawTitle(); // TODO: override!
if (!this.dataIsValid) {
this.showErrorMessage(divWidth, divHeight);
return this.wrapper.innerHTML;
console.log("Error: Grid data invalid")
//this.showErrorMessage(divWidth, divHeight); // TODO: makes no sense here, display error another way
return "";// this.wrapper.innerHTML;
}
const title_element = document.createElement("div");
title_element.className = "chart-title grid-title";
title_element.innerHTML = this.title;
if (this.description) {
const question_mark_element = document.createElement("div");
question_mark_element.className = "question-mark-legacy";
question_mark_element.innerHTML = "?";
const randomId = Date.now();
question_mark_element.id = `id-${randomId}`;
title_element.appendChild(question_mark_element);
setTimeout(() => {
const targetEl = document.getElementById(`id-${randomId}`);
if (targetEl)
targetEl.addEventListener("click", (event) => {
this.options.onShowDesc(this.description);
});
});
}
el.appendChild(title_element);
el.style = "background-color: #f5f5f5";
......@@ -81,11 +100,6 @@ export class GridWidget extends BaseChartWidget {
else {
widget.style.flexDirection = "row"; // default
}
if(widget.style.flexDirection == "row") {
width /= this.data.length;
} else {
height /= this.data.length;
}
let temp_subtitle = document.createElement("div");
temp_subtitle.className = "chart-subtitle";
......@@ -99,6 +113,15 @@ export class GridWidget extends BaseChartWidget {
cardHolder.appendChild(widget)
el.appendChild(cardHolder);
if(widget.style.flexDirection == "row") {
width -= 12; // TODO Padding / Margins korrekt einberechnen
width /= this.data.length;
} else {
height -= this.data.filter((widget) => widget.name != "").length * 16;
height /= this.data.length;
width -= 12;
}
let firstColumn = true;
for(const chart of this.data)
{
......@@ -138,6 +161,14 @@ export class GridWidget extends BaseChartWidget {
chart.options
);
break;
case "boxplot":
subwidget = new BoxPlotWidget(
"",
"",
chart.data,
chart.options
);
break;
case "chartjs":
subwidget = new ChartJSWidget(
"",
......@@ -146,6 +177,14 @@ export class GridWidget extends BaseChartWidget {
chart.options
);
break;
case "grid":
subwidget = new GridWidget(
"",
"",
chart.data,
chart.options
)
break;
case "grouped_barchart":
subwidget = new GroupedBarChartWidget(
"",
......@@ -223,10 +262,6 @@ export class GridWidget extends BaseChartWidget {
subwidget_div.innerHTML = subwidget_div.innerHTML + generated;
}
} else {
const generated = subwidget.plot(width, height, subwidget_div);
if (generated != null) {
subwidget_div.innerHTML = generated;
}
subwidget_div.querySelector('.chart-title')?.parentElement.remove();
subwidget_div.querySelector('.question-mark')?.parentElement.remove();
......@@ -236,6 +271,12 @@ export class GridWidget extends BaseChartWidget {
subwidget_title.className = "chart-subtitle";
subwidget_title.innerHTML = chart.name;
subwidget_div.appendChild(subwidget_title);
const generated = subwidget.plot(width, height, subwidget_div);
if (generated != null) {
subwidget_div.innerHTML = subwidget_div.innerHTML + generated;
}
}
firstColumn = false;
}
......
......@@ -10,9 +10,9 @@ export class HeatMapWidget extends BaseChartWidget {
}
plot(divWidth, divHeight, el) {
this.marginRight = 50
this.marginBottom = 50
const [width, height] = this.clearAndScaleSvg(divWidth, divHeight,20,40);
this.marginRight = 40
this.marginBottom = 40
const [width, height] = this.clearAndScaleSvg(divWidth, divHeight,20,20);
this.drawTitle();
if (!this.dataIsValid) {
......
......@@ -19,7 +19,15 @@ export class LineChartWidget extends BaseChartWidget {
const [xMin, xMax] = d3.extent(this.data, (d) => d.column1);
const xScale = d3.scaleTime().domain([xMin, xMax]).range([0, width]);
let xScale = null
if(this.options.useTimeScale)
{
xScale = d3.scaleTime().domain([xMin, xMax]).range([0, width]);
}
else
{
xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, width]);
}
this.appendXAxis(xScale, height);
this.appendXAxisLabel(width, height);
......@@ -55,4 +63,19 @@ export class LineChartWidget extends BaseChartWidget {
return this.wrapper.innerHTML;
}
appendXAxis(x, height) {
const xAxis = this.options.ticks
? d3.axisBottom(x).ticks(this.options.ticks)
: d3.axisBottom(x);
if(this.options.useTimeScale)
{
xAxis.tickFormat(d3.timeFormat("%d.%m.%Y %H:%M"))
}
this.g
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
}
}
......@@ -6,6 +6,8 @@ export class PieChartWidget extends BaseChartWidget {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, description, data, options);
this.marginRight = 100
this.marginBottom = 60
}
plot(divWidth, divHeight, el) {
......@@ -20,7 +22,6 @@ export class PieChartWidget extends BaseChartWidget {
}
const [width, height] = this.clearAndScaleSvg(divWidth, divHeight, translateX, translateY);
this.drawTitle();
const radius = Math.min(width, height) / 1.5;
const pie = d3.pie().value(function (d) {
......
......@@ -10,7 +10,7 @@ export class TextAreaElement extends BaseElement {
plot(divWidth, divHeight, element) {
const title_element = document.createElement("textarea");
title_element.placeholder = this.text;
title_element.rows = 5;
title_element.rows = divHeight / 24;
title_element.height = divHeight;
title_element.className = "form-control";
element.appendChild(title_element);
......
......@@ -50,6 +50,7 @@ function plotWidgets(nodes, widgets) {
*/
function plot(node, widget) {
const content = node.el.querySelector(".grid-stack-item-content");
content.innerHTML = "";
const width = content.offsetWidth;
const height = content.offsetHeight;
const generated = widget.plot(width, height, content);
......@@ -65,11 +66,13 @@ function plot(node, widget) {
* @param {*} widgetId
* @returns
*/
function createAvWidgetPlaceholder(widget, widgetId) {
const content = widget.plot(400, 400);
const div = document.createElement("div");
div.innerHTML = `<div class="newWidget grid-stack-item" widgetId="${widgetId}"><div class="grid-stack-item-content">${content}</div></div>`;
return div;
function createAvWidgetPlaceholder(widget, widgetId, node) {
const content = node.querySelector(".grid-stack-item-content");
const generated = widget.plot(400, 400, content);
if(generated != null)
{
content.innerHTML = generated;
}
}
/**
......@@ -79,8 +82,10 @@ function createAvWidgetPlaceholder(widget, widgetId) {
export function drawAvailableWidgets(widgets) {
const availableWidgets = document.getElementById("available-widgets");
for (const widgetId of Object.keys(widgets)) {
const avWidgetDiv = createAvWidgetPlaceholder(widgets[widgetId], widgetId);
availableWidgets.appendChild(avWidgetDiv, widgetId);
const div = document.createElement("div");
availableWidgets.appendChild(div, widgetId);
div.innerHTML = `<div class="newWidget grid-stack-item" widgetId="${widgetId}"><div class="grid-stack-item-content"></div></div>`;
createAvWidgetPlaceholder(widgets[widgetId], widgetId, div);
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment