Skip to content
Snippets Groups Projects
Commit be001a50 authored by Max Lou's avatar Max Lou
Browse files

Adding nested dashboards

parent 001c49f8
No related branches found
No related tags found
No related merge requests found
......@@ -19,6 +19,22 @@
stroke-width: 1.5;
}
.listening-rect {
stroke: grey;
fill: white;
}
.question-mark {
cursor: pointer;
opacity: 0.2;
transform: scale(0.05);
-ms-transform: scale(0.05);
-webkit-transform: scale(0.05);
&: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);
......@@ -29,9 +45,13 @@
opacity: 0.5;
}
.subgrid {
margin-right: 20px;
}
/* make nested grid have slightly darker bg take almost all space (need some to tell them apart) so items inside can have similar to external size+margin */
.grid-stack > .grid-stack-item.grid-stack-sub-grid > .grid-stack-item-content {
background: rgba(0, 0, 0, 0.1);
// background: rgba(0, 0, 0, 0.1);
inset: 0 2px;
}
.grid-stack.grid-stack-nested {
......
......@@ -2,10 +2,10 @@ import * as d3 from "d3";
import { BaseChartWidget } from "./base";
export class AreaChartWidget extends BaseChartWidget {
constructor(title = "", data, options = {}) {
constructor(title = "", description, data, options = {}) {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, data, options);
super(title, description, data, options);
}
plot(divWidth, divHeight) {
......
......@@ -2,10 +2,10 @@ import * as d3 from "d3";
import { BaseChartWidget } from "./base";
export class BarChartWidget extends BaseChartWidget {
constructor(title, data, options) {
constructor(title, description, data, options) {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, data, options);
super(title, description, data, options);
}
plot(divWidth, divHeight) {
......@@ -29,10 +29,7 @@ export class BarChartWidget extends BaseChartWidget {
this.appendXAxisLabel(width, height);
this.appendYAxisLabel();
this.g
.append("g")
.attr("transform", "translate(0, 0)")
.call(d3.axisLeft(yScale));
this.g.append("g").attr("transform", "translate(0, 0)").call(d3.axisLeft(yScale));
const color = d3.scaleOrdinal().range(this.colorRange);
......
......@@ -52,8 +52,9 @@ export class BaseChartWidget {
marginBottom = 150;
colorRange = COLOR_RANGE_2;
constructor(title, data, options = {}) {
constructor(title, description, data, options = {}) {
this.title = title;
this.description = description;
this.data = data;
this.options = options;
this.dataIsValid = Array.isArray(data) ? data.length > 0 : !!data;
......@@ -88,14 +89,34 @@ export class BaseChartWidget {
.attr("y", 25)
.attr("class", "chart-title")
.text(this.title);
if (this.description) {
const randomId = Date.now();
this.svg
.append("g")
.attr("transform", `translate(${this.svg.attr("width") - 40}, 5)`)
.append("path")
.attr("class", "question-mark")
.attr("id", `id-${randomId}`)
// Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License)
.attr(
"d",
"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 448c-110.532 0-200-89.431-200-200 0-110.495 89.472-200 200-200 110.491 0 200 89.471 200 200 0 110.53-89.431 200-200 200zm107.244-255.2c0 67.052-72.421 68.084-72.421 92.863V300c0 6.627-5.373 12-12 12h-45.647c-6.627 0-12-5.373-12-12v-8.659c0-35.745 27.1-50.034 47.579-61.516 17.561-9.845 28.324-16.541 28.324-29.579 0-17.246-21.999-28.693-39.784-28.693-23.189 0-33.894 10.977-48.942 29.969-4.057 5.12-11.46 6.071-16.666 2.124l-27.824-21.098c-5.107-3.872-6.251-11.066-2.644-16.363C184.846 131.491 214.94 112 261.794 112c49.071 0 101.45 38.304 101.45 88.8zM298 368c0 23.159-18.841 42-42 42s-42-18.841-42-42 18.841-42 42-42 42 18.841 42 42z"
);
setTimeout(() => {
const targetEl = document.getElementById(`id-${randomId}`);
if (targetEl)
targetEl.addEventListener("click", (event) => {
this.options.onShowDesc(this.description);
});
});
}
}
appendXAxis(x, height) {
const xAxis = this.options.ticks
? d3
.axisBottom(x)
.ticks(this.options.ticks)
.tickFormat(d3.timeFormat("%d.%m.%Y %H:%M"))
? d3.axisBottom(x).ticks(this.options.ticks).tickFormat(d3.timeFormat("%d.%m.%Y %H:%M"))
: d3.axisBottom(x).tickFormat(d3.timeFormat("%d.%m.%Y %H:%M"));
this.g
......
......@@ -2,10 +2,10 @@ import * as d3 from "d3";
import { BaseChartWidget } from "./base";
export class LineChartWidget extends BaseChartWidget {
constructor(title = "", data, options = {}) {
constructor(title = "", description, data, options = {}) {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, data, options);
super(title, description, data, options);
}
plot(divWidth, divHeight) {
......
......@@ -2,10 +2,10 @@ import * as d3 from "d3";
import { BaseChartWidget } from "./base";
export class PieChartWidget extends BaseChartWidget {
constructor(title = "", data, options = {}) {
constructor(title = "", description, data, options = {}) {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, data, options);
super(title, description, data, options);
}
plot(divWidth, divHeight) {
......@@ -18,12 +18,7 @@ export class PieChartWidget extends BaseChartWidget {
this.showErrorMessage(divWidth, divHeight);
return this.wrapper.innerHTML;
}
const [width, height] = this.clearAndScaleSvg(
divWidth,
divHeight,
translateX,
translateY
);
const [width, height] = this.clearAndScaleSvg(divWidth, divHeight, translateX, translateY);
this.drawTitle();
const radius = Math.min(width, height) / 1.5;
......@@ -42,12 +37,7 @@ export class PieChartWidget extends BaseChartWidget {
.outerRadius(radius)
.innerRadius(radius - 80);
const arc = this.g
.selectAll(".arc")
.data(pie(this.data))
.enter()
.append("g")
.attr("class", "arc");
const arc = this.g.selectAll(".arc").data(pie(this.data)).enter().append("g").attr("class", "arc");
const color = d3.scaleOrdinal().domain(this.data).range(this.colorRange);
......@@ -61,7 +51,13 @@ export class PieChartWidget extends BaseChartWidget {
arc
.append("text")
.attr("transform", function (d) {
return"translate(" + ( (radius + 50) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + ", " + ( -1 * (radius - 10) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +")"
return (
"translate(" +
(radius + 50) * Math.sin((d.endAngle - d.startAngle) / 2 + d.startAngle) +
", " +
-1 * (radius - 10) * Math.cos((d.endAngle - d.startAngle) / 2 + d.startAngle) +
")"
);
})
.attr("dy", ".5em")
.text(function (d) {
......
......@@ -2,10 +2,10 @@ import * as d3 from "d3";
import { BaseChartWidget } from "./base";
export class StackedBarChartWidget extends BaseChartWidget {
constructor(title, data, groups, options) {
constructor(title, description, data, groups, options) {
if (options.transform) data = (data ?? []).map(options.transform);
super(title, data, options);
super(title, description, data, options);
this.groups = groups;
}
......@@ -21,10 +21,7 @@ export class StackedBarChartWidget extends BaseChartWidget {
const subgroups = Object.keys(this.data["overall"][0]);
const groups = Object.keys(this.data);
const data = Object.keys(this.data).reduce(
(acc, key) => [...acc, { ...this.data[key][0], group: key }],
[]
);
const data = Object.keys(this.data).reduce((acc, key) => [...acc, { ...this.data[key][0], group: key }], []);
const stackedData = d3.stack().keys(subgroups)(data);
......@@ -32,10 +29,7 @@ export class StackedBarChartWidget extends BaseChartWidget {
const max = stackedData[stackedData.length - 1][0][1];
const x = d3.scaleBand().domain(groups).range([0, width]).padding([0.2]);
this.g
.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x).tickSizeOuter(0));
this.g.append("g").attr("transform", `translate(0, ${height})`).call(d3.axisBottom(x).tickSizeOuter(0));
const y = d3.scaleLinear().domain([min, max]).range([height, 0]);
this.g.append("g").call(d3.axisLeft(y));
......
......@@ -87,6 +87,7 @@ getResult(TOKEN, URL).then((data) => {
const widgets = {
"second-widget": new BarChartWidget(
"Statements H5P",
data["count_h5p_statements"]?.description,
data["count_h5p_statements"]?.latest_result,
{
xAxisLabel: "Datum",
......@@ -99,6 +100,7 @@ getResult(TOKEN, URL).then((data) => {
),
"fourth-widget": new PieChartWidget(
"Persönliche Statement Verteilung",
data["h5p_statements_distribution"]?.description,
data["h5p_statements_distribution"]?.latest_result,
{
showLegend: true,
......@@ -108,6 +110,7 @@ getResult(TOKEN, URL).then((data) => {
),
"sixth-widget": new LineChartWidget(
"Semesterabschluss",
data["collect_h5p_count_statements"]?.description,
data["collect_h5p_count_statements"]?.latest_result,
{
xAxisLabel: "Monat",
......@@ -120,6 +123,7 @@ getResult(TOKEN, URL).then((data) => {
),
"seventh-widget": new CourseRatingChart(
"Bewertungen für Kurse",
data["random_course_rating"]?.description,
data["random_course_rating"]?.latest_result,
{
xAxisLabel: "Note",
......@@ -128,6 +132,7 @@ getResult(TOKEN, URL).then((data) => {
),
"eigth-widget": new AreaChartWidget(
"Statements H5P",
data["collect_h5p_count_statements"]?.description,
data["collect_h5p_count_statements"]?.latest_result,
{
xAxisLabel: "Monat",
......@@ -140,6 +145,7 @@ getResult(TOKEN, URL).then((data) => {
),
"ninth-widget": new BarChartWidget(
"Statements Moodle",
data["count_moodle_statements"]?.description,
data["count_moodle_statements"]?.latest_result,
{
xAxisLabel: "Datum",
......@@ -152,6 +158,7 @@ getResult(TOKEN, URL).then((data) => {
),
"tenth-widget": new StackedBarChartWidget(
"Statements",
data["collect_counts_all_providers"]?.description,
data["collect_counts_all_providers"]?.latest_result,
["overall"],
{
......@@ -162,6 +169,7 @@ getResult(TOKEN, URL).then((data) => {
),
"eleventh-widget": new BarChartWidget(
"Personal H5P xAPI Statements",
data["h5p_count_user_statements"]?.description,
data["h5p_count_user_statements"]?.latest_result,
{
xAxisLabel: "Datum",
......
......@@ -16,7 +16,7 @@ export async function getResult(token, url) {
});
return await response.json();
} catch (error) {
throw Error("Fetching result from backend failed. Please check URL and TOKEN settings.")
throw Error("Fetching result from backend failed. Please check URL and TOKEN settings.");
}
}
......@@ -27,14 +27,19 @@ export async function getResult(token, url) {
*/
function plotWidgets(nodes, widgets) {
for (const node of nodes) {
const widget = widgets[node.widgetId];
if (widget) {
plot(node, widget);
} else if (node.el.getAttribute("widgetId")) {
const widgetId = node.el.getAttribute("widgetId");
plot(node, widgets[widgetId]);
if (node.subGrid) {
// Handle sub grid
plotWidgets(node.subGrid.engine.nodes, widgets);
} else {
console.error(`Couldn't find widget by id ${node.widgetId}`);
const widget = widgets[node.widgetId];
if (widget) {
plot(node, widget);
} else if (node.el.getAttribute("widgetId")) {
const widgetId = node.el.getAttribute("widgetId");
plot(node, widgets[widgetId]);
} else {
console.error(`Couldn't find widget by id ${node.widgetId}`);
}
}
}
}
......@@ -46,6 +51,7 @@ function plotWidgets(nodes, widgets) {
* @param {*} widget
*/
function plot(node, widget) {
console.log(node.el)
const content = node.el.querySelector(".grid-stack-item-content");
const width = content.offsetWidth;
const height = content.offsetHeight;
......@@ -110,25 +116,65 @@ export function initGrid(widgets, items) {
cellHeight: 70,
acceptWidgets: true,
removable: ".dropzone-remove",
minRow: 2,
subGrid: {
disableOneColumnMode: true,
minRow: 2,
cellHeight: 70,
margin: 5,
acceptWidgets: true, // will accept .grid-stack-item by default
locked: true,
noResize: true,
noMove: true,
removable: ".dropzone-remove",
},
subGridDynamic: true,
});
// Enables widget resize and movement on grid
function enableWidgetMoveAndResize() {
grid.enableResize(true);
grid.enableMove(true);
grid.engine.nodes.map((e) => {
if (e.subGrid) {
e.subGrid.enableResize(true);
e.subGrid.enableMove(true);
}
});
}
// Disables widget resize and movement on grid
function disableWidgetMoveAndResize() {
grid.enableResize(false);
grid.enableMove(false);
grid.engine.nodes.map((e) => {
if (e.subGrid) {
e.subGrid.enableResize(false);
e.subGrid.enableMove(false);
}
});
}
disableWidgetMoveAndResize();
grid.load(items);
addResizeListener(grid);
plotWidgets(grid.engine.nodes, widgets);
disableWidgetMoveAndResize();
//// Add resize listener for subgrid
grid.engine.nodes.map((e) => {
if (e.subGrid) {
e.subGrid.on("resizestop", function (event, el) {
console.log("sub grid resize");
const items = e.subGrid.getGridItems()
console.log("items", items);
//plotWidgets(e.subGrid.engine.nodes, widgets);
plotWidgets(grid.engine.nodes, widgets);
});
}
});
// Rerender all widgets on widget removal
grid.on("removed change", function (e, items) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment