Infor
else{
heatdisp=""
}
- var button = ` | `
- var markup = ""+JSONdata.id+" | "+JSONdata.name+" | "+annodisp+" | "+heatdisp+" | "+button+"
"
+ var button = ` | `
+ const visualization_button = `
+
+ | `
+ var markup = ""+JSONdata.id+" | "+JSONdata.name+" | "+annodisp+" | "+heatdisp+" | "+button+visualization_button+" |
"
table.append(markup);
}
+function openView(e) {
+const oid = e.dataset.id;
+console.log(oid);
+ if (oid) {
+ window.location.href = `./visualization-dashboard.html?slideId=${sanitize(oid)}`;
+ } else {
+ alert('No Data Id');
+ }
+}
+
+function sanitize(string) {
+ string = string || '';
+ const map = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ '\'': ''',
+ '/': '/',
+ };
+ const reg = /[&<>"'/]/ig;
+ return string.replace(reg, (match) => (map[match]));
+}
function openDetails(tag){
document.getElementById('detail-dialog').style.display = 'block';
document.getElementById('detail-dialog').style.opacity = '1';
@@ -231,9 +259,9 @@ Infor
table.append(content);
addAnnotations(allSlides[count].annotations);
addHeatmaps(allSlides[count].heatmap);
- console.log(allSlides[count]);
+ console.log(allSlides[count],count);
}
-
+
function addAnnotations(content){
if(content.length===0){
return;
@@ -443,7 +471,7 @@ Infor
addbody(JSONdata);
});
});
- // console.log(JSONdata);
+ // console.log(JSONdata);
}
});
}
diff --git a/apps/visualization-dashboard.css b/apps/visualization-dashboard.css
new file mode 100644
index 000000000..7d4c67181
--- /dev/null
+++ b/apps/visualization-dashboard.css
@@ -0,0 +1,10 @@
+body {
+ background-color: #cff4fc;
+ margin: 0;
+ font-family: Arial, sans-serif;
+}
+
+.material-icons {
+ font-size: 24px;
+ vertical-align: middle;
+}
diff --git a/apps/visualization-dashboard.html b/apps/visualization-dashboard.html
new file mode 100644
index 000000000..d5fdbc3dc
--- /dev/null
+++ b/apps/visualization-dashboard.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+ Responsive Cards with Modal Expansion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/visualization-dashboard/cardContainer.css b/components/visualization-dashboard/cardContainer.css
new file mode 100644
index 000000000..294fcb8db
--- /dev/null
+++ b/components/visualization-dashboard/cardContainer.css
@@ -0,0 +1,185 @@
+/* Expand button */
+.expand-btn {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background-color: #f8f9fa;
+ color: #515555;
+ border: none;
+ padding: 5px 10px;
+ cursor: pointer;
+ border-radius: 5px;
+ font-size: 14px;
+}
+
+.expand-btn:hover {
+ background-color: #a0a0a0;
+}
+
+/* Background color of the entire screen */
+body {
+background-color: #ddd;
+margin: 0;
+font-family: Arial, sans-serif;
+}
+
+/* Container for cards */
+.card-container {
+padding: 20px;
+box-sizing: border-box;
+margin: 0 auto;
+}
+
+/* Style for rows */
+.row {
+display: flex;
+justify-content: space-between;
+}
+
+/* Common style for each card */
+.card {
+background-color: #fcfcfc;
+border: 1px solid #ddd;
+border-radius: 2px;
+padding: 20px;
+box-sizing: border-box;
+text-align: left;
+margin: 4px;
+overflow: hidden;
+position: relative;
+display: flex;
+flex-direction: column;
+justify-content: flex-start;
+align-items: flex-start;
+}
+
+/* Style for cards in the upper row */
+.card-1 {
+width: 40%;
+display: flex;
+flex-direction: column;
+justify-content: space-between;
+}
+
+.card-2, .card-3, .card-4 {
+width: 20%;
+}
+
+.card-2, .card-3 {
+align-items: center;
+justify-content: center;
+}
+
+/* Style for cards in the lower row */
+.card-5, .card-6 {
+width: 50%;
+height: 80%;
+display: flex;
+flex-direction: column;
+justify-content: flex-start;
+}
+
+/* Text positioning adjustments */
+.card-1 {
+margin-bottom: 5px;
+}
+.card h3 {
+margin-bottom: 18px;
+text-align: left;
+}
+
+.card h4 {
+margin: 0;
+text-align: left;
+}
+
+/* Text overflow handling */
+.card h4, .card h1, .card h2, .card h3 {
+display: block;
+overflow: hidden;
+text-overflow: ellipsis;
+white-space: nowrap;
+}
+
+.card:hover h4, .card:hover h1, .card:hover h2 {
+white-space: normal;
+}
+
+/* Number display style */
+.number {
+font-size: 80px;
+font-weight: bold;
+text-align: center;
+display: flex;
+justify-content: center;
+align-items: center;
+width: 100%;
+height: 100%;
+}
+
+/* Expand button */
+.expand-btn {
+position: absolute;
+top: 10px;
+right: 10px;
+background-color: #f8f9fa;
+color: #515555;
+border: none;
+padding: 5px 10px;
+cursor: pointer;
+border-radius: 5px;
+font-size: 14px;
+}
+
+.expand-btn:hover {
+background-color: #a0a0a0;
+}
+
+.card-expand-icon {
+color: #515555;
+}
+
+/* Centered content with red background in card */
+.card-content {
+width: 100%;
+display: flex;
+justify-content: center;
+margin-top: 10px;
+}
+
+.centered-content-small {
+background-color: #fcfcfc;
+color: #fcfcfc;
+padding: 10px;
+border-radius: 4px;
+width: 80%;
+text-align: center;
+margin: 10px 0;
+}
+
+.centered-content {
+background-color: #fcfcfc;
+color: #fcfcfc;
+padding: 10px;
+border-radius: 4px;
+width: 80%;
+height: 35vh;
+text-align: center;
+margin: 10px auto;
+align-self: center;
+}
+
+/* Media query for displaying in a single column on smartphones and iPads */
+@media (max-width: 768px) {
+.row {
+ flex-direction: column;
+ align-items: center;
+}
+
+.card {
+ margin: 10px 0;
+ width: 90%;
+ max-width: 400px;
+ min-width: 300px;
+}
+}
diff --git a/components/visualization-dashboard/cardContainer.js b/components/visualization-dashboard/cardContainer.js
new file mode 100644
index 000000000..f48fc2fb4
--- /dev/null
+++ b/components/visualization-dashboard/cardContainer.js
@@ -0,0 +1,73 @@
+(function() {
+ // Synopsis
+ var json = {
+ id: getVisualizationData.id,
+ name: getVisualizationData.name,
+ annotations: getVisualizationData.annotations.length,
+ heatmaps: getVisualizationData.heatmap.length,
+ };
+
+ const cardContainer = `
+
+
+
+
Slide Name
+ ${json.name}
+ Slide Id
+ ${json.id}
+
+
+
Number of Annotations
+
${json.annotations}
+
+
+
Number of Heatmaps
+
${json.heatmaps}
+
+
+
+
Users of Annotations
+
+
+
+
+
+
+
+
+
Draw Annotation Count vs Zooming
+
+
+
+
+
+
+
+
Preset Labels vs Preset Labels count
+
+
+
+
+
+
+
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', cardContainer);
+
+ // Prepare Data
+ let drawAnnotationData = prepareDrawAnnotationData(getVisualizationData);
+ let presetLabelsData = preparePresetLabelsData(getVisualizationData);
+ let usersOfAnnotationsData = prepareUsersOfAnnotationsData(getVisualizationData);
+
+ // Create Chart
+ createAnnotationZoomingChart('drawAnnotationChart', drawAnnotationData);
+ createPresetLabelsChart('presetLabelsDataChart', presetLabelsData);
+ createUsersOfAnnotationsChart('usersOfAnnotationsChart', usersOfAnnotationsData);
+})();
diff --git a/components/visualization-dashboard/chartData.js b/components/visualization-dashboard/chartData.js
new file mode 100644
index 000000000..0079d5514
--- /dev/null
+++ b/components/visualization-dashboard/chartData.js
@@ -0,0 +1,89 @@
+// DrawAnnotationData
+function prepareDrawAnnotationData(getVisualizationData) {
+ let DrawAnnotationData = [];
+
+ // Get initial data
+ getVisualizationData.annotations.map((d) => {
+ d.geometries.features.map((detailData)=>{
+ if (detailData.viewerStates) {
+ DrawAnnotationData.push(roundToSecondDecimalPlace(detailData.viewerStates.z));
+ }
+ });
+ });
+ let result = countOccurrences(DrawAnnotationData);
+
+ return result;
+}
+// Function to round to decimal places
+function roundToSecondDecimalPlace(num) {
+ return Math.round(num * 100) / 100;
+}
+
+function countOccurrences(arr) {
+ // Create objects for counting
+ let countMap = {};
+
+ // Count each element in the array
+ arr.forEach(function(value) {
+ if (countMap[value] === undefined) {
+ countMap[value] = 1;
+ } else {
+ countMap[value]++;
+ }
+ });
+
+ // Convert the result to a 2-dimensional array
+ let result = [];
+ for (let key in countMap) {
+ if (countMap.hasOwnProperty(key)) {
+ result.push([parseFloat(key), countMap[key]]);
+ }
+ };
+
+ return result;
+}
+
+// Preset Labels annotations
+function preparePresetLabelsData(getVisualizationData) {
+ let initialData = [];
+ // Get initial data
+ getVisualizationData.annotations.map((d) => {
+ if (d.properties.annotations.name == d.properties.annotations.notes) {
+ initialData.push(d.properties.annotations.name);
+ };
+ });
+ let result = countOccurrencesFromString(initialData);
+ return result;
+}
+
+function countOccurrencesFromString(arr) {
+ let countMap = {};
+
+ arr.forEach(function(value) {
+ if (typeof value !== 'string') {
+ return;
+ }
+
+ if (countMap[value] === undefined) {
+ countMap[value] = 1;
+ } else {
+ countMap[value]++;
+ }
+ });
+ let countArray = Object.entries(countMap).map(([key, value]) => [key, value]);
+
+ return countArray;
+}
+
+// Users of Annotations
+function prepareUsersOfAnnotationsData(getVisualizationData) {
+ let initialData = [];
+ // Get initial data
+ getVisualizationData.annotations.map((d) => {
+ if (d.creator.length !== 0) {
+ initialData.push(d.creator);
+ };
+ });
+ let result = countOccurrencesFromString(initialData);
+ return result;
+}
diff --git a/components/visualization-dashboard/chartSetup.js b/components/visualization-dashboard/chartSetup.js
new file mode 100644
index 000000000..801564680
--- /dev/null
+++ b/components/visualization-dashboard/chartSetup.js
@@ -0,0 +1,224 @@
+function createUsersOfAnnotationsChart(id, data) {
+ // Get the canvas to draw the pie chart
+ const ctx = document.getElementById(id);
+
+ // Separate the labels and values from the data
+ const labels = data.map((item) => item[0]);
+ const values = data.map((item) => item[1]);
+
+ // Define the colors for the pie chart
+ const backgroundColors = [
+ 'rgba(23, 162, 184, 0.95)',
+ 'rgba(23, 142, 184, 0.95)',
+ 'rgba(23, 123, 184, 0.95)',
+ 'rgba(23, 104, 184, 0.95)',
+ 'rgba(23, 181, 184, 0.95)',
+ 'rgba(23, 184, 167, 0.95)',
+ 'rgba(23, 184, 148, 0.95)',
+ ];
+ const borderColors = [
+ 'rgba(23, 162, 184, 1)', // rgba(23, 162, 184)
+ 'rgba(23, 142, 184, 1)', // rgba(23, 142, 184)
+ 'rgba(23, 123, 184, 1)', // rgba(23, 123, 184)
+ 'rgba(23, 104, 184, 1)', // rgba(23, 104, 184)
+ 'rgba(23, 181, 184, 1)', // rgba(23, 181, 184)
+ 'rgba(23, 184, 167, 1)', // rgba(23, 184, 167)
+ 'rgba(23, 184, 148, 1)', // rgba(23, 184, 148)
+ ];
+
+ // Data set for the pie chart
+ const chartData = {
+ labels: labels,
+ datasets: [{
+ data: values,
+ backgroundColor: backgroundColors.slice(0, values.length),
+ borderColor: borderColors.slice(0, values.length),
+ borderWidth: 1,
+ }],
+ };
+
+ // Options for the pie chart
+ const options = {
+ plugins: {
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ const label = context.label || '';
+ const value = context.parsed || 0;
+ const total = context.chart._metasets[context.datasetIndex].total;
+ const percentage = ((value / total) * 100).toFixed(2) + '%';
+ return 'user:' + label + ': ' + value + '(' + percentage + ')';
+ },
+ },
+ },
+ },
+ };
+
+ // Draw the pie chart
+ new Chart(ctx, {
+ type: 'pie',
+ data: chartData,
+ options: options,
+ });
+}
+
+// Graph
+function createAnnotationZoomingChart(id, result) {
+ const ctx = document.getElementById(id);
+ const aa = result;
+ // Define data
+ var data = {
+ datasets: [{
+ label: 'Human:Draw Annotation ',
+ data: aa.map((item) => ({x: item[0], y: item[1]})),
+ backgroundColor: 'rgba(23, 162, 184, 1)',
+ pointRadius: 5,
+ }],
+ };
+
+ // Get the maximum value of the data set and add 1 to the maximum value
+ var maxYValue = Math.max(...data.datasets[0].data.map((d)=> d.y)) + 1;
+
+ // Setting options
+ var options = {
+ plugins: {
+ title: {
+ display: false,
+ text: 'Draw Annotation Count vs zooming',
+ },
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ var xValue = context.raw.x;
+ var yValue = context.raw.y;
+ return '(count, zooming) = (' + yValue + ', ' + xValue + ')';
+ },
+ },
+ },
+ },
+ scales: {
+ x: {
+ type: 'linear',
+ position: 'bottom',
+ title: {
+ display: true,
+ text: 'Zooming',
+ },
+ },
+ y: {
+ beginAtZero: true, // Set vertical axis to start from 0
+ title: {
+ display: true,
+ text: 'Draw Annotation Count',
+ },
+ ticks: {
+ stepSize: 1, // Set the step size displayed on the vertical axis to 1
+ callback: function(value) {
+ if (value % 1 === 0) {
+ return value;
+ }
+ },
+ },
+ max: maxYValue,
+ },
+ },
+ };
+
+ // Create the graph
+ new Chart(ctx, {
+ type: 'scatter',
+ data: data,
+ options: options,
+ });
+};
+
+function createPresetLabelsChart(id, result) {
+ const ctx = document.getElementById(id);
+ const aa = result;
+ // console.log('id, result', id, result);
+ // Define data
+ var data = {
+ labels: aa.map((item) => item[0]),
+ datasets: [{
+ label: 'Preset Labels Counts',
+ data: aa.map((item) => item[1]),
+ backgroundColor: [
+ 'rgba(23, 162, 184, 0.95)',
+ 'rgba(23, 142, 184, 0.95)',
+ 'rgba(23, 123, 184, 0.95)',
+ 'rgba(23, 104, 184, 0.95)',
+ 'rgba(23, 181, 184, 0.95)',
+ 'rgba(23, 184, 167, 0.95)',
+ 'rgba(23, 184, 148, 0.95)',
+ ],
+ borderColor: [
+ 'rgba(23, 162, 184, 1)', // rgba(23, 162, 184)
+ 'rgba(23, 142, 184, 1)', // rgba(23, 142, 184)
+ 'rgba(23, 123, 184, 1)', // rgba(23, 123, 184)
+ 'rgba(23, 104, 184, 1)', // rgba(23, 104, 184)
+ 'rgba(23, 181, 184, 1)', // rgba(23, 181, 184)
+ 'rgba(23, 184, 167, 1)', // rgba(23, 184, 167)
+ 'rgba(23, 184, 148, 1)', // rgba(23, 184, 148)
+ ],
+ borderWidth: 1,
+ }],
+ };
+
+ // Get the maximum value of the dataset and add 1 to it
+ var maxYValue = Math.max(...data.datasets[0].data) + 1;
+
+ // Set options
+ var options = {
+ plugins: {
+ title: {
+ display: false,
+ text: 'Preset Labels vs Preset Labels count',
+ },
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ var label = context.dataset.label || '';
+ if (label) {
+ label += ': ';
+ }
+ if (context.parsed.y !== null) {
+ label += context.parsed.y;
+ }
+ return label;
+ },
+ },
+ },
+ },
+ scales: {
+ x: {
+ title: {
+ display: false,
+ text: 'Preset labels',
+ },
+ },
+ y: {
+ beginAtZero: true,
+ title: {
+ display: true,
+ text: 'Preset labels count',
+ },
+ ticks: {
+ stepSize: 1,
+ callback: function(value) {
+ if (value % 1 === 0) {
+ return value;
+ }
+ },
+ },
+ max: maxYValue,
+ },
+ },
+ };
+
+ // Create the chart
+ new Chart(ctx, {
+ type: 'bar',
+ data: data,
+ options: options,
+ });
+}
diff --git a/components/visualization-dashboard/modal.css b/components/visualization-dashboard/modal.css
new file mode 100644
index 000000000..cb0cf5a4f
--- /dev/null
+++ b/components/visualization-dashboard/modal.css
@@ -0,0 +1,46 @@
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 1000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0, 0, 0, 0.8);
+}
+
+.modal-content {
+ background-color: #f8f9fa;
+ margin: 5% auto;
+ padding: 20px;
+ border: 1px solid #ddd;
+ border-radius: 10px;
+ width: 80%;
+ height: 75%;
+ max-width: 1200px;
+ position: relative;
+}
+
+.close {
+ position: absolute;
+ top: 10px;
+ right: 20px;
+ color: #aaa;
+ font-size: 28px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.close:hover,
+.close:focus {
+ color: #000;
+ text-decoration: none;
+}
+
+#modal-chart {
+ max-width: 100%;
+ max-height: 600px;
+ width: auto;
+ height: auto;
+}
diff --git a/components/visualization-dashboard/modal.js b/components/visualization-dashboard/modal.js
new file mode 100644
index 000000000..3408f2a13
--- /dev/null
+++ b/components/visualization-dashboard/modal.js
@@ -0,0 +1,59 @@
+document.addEventListener('DOMContentLoaded', function() {
+ const modal = `
+
+ `;
+ document.body.insertAdjacentHTML('beforeend', modal);
+});
+
+function openModal(cardId) {
+ const modal = document.getElementById('modal');
+ console.log('cardId', cardId);
+
+ // Remove the existing chart and create a new canvas
+ const modalBody = document.getElementById('modal-body');
+ modalBody.innerHTML = '';
+
+
+ // Render the chart inside the modal
+ switch (cardId) {
+ case 'drawAnnotationChart':
+ createAnnotationZoomingChart('modal-chart', prepareDrawAnnotationData(getVisualizationData));
+ console.log('cardId', cardId);
+ break;
+ case 'presetLabelsDataChart':
+ createPresetLabelsChart('modal-chart', preparePresetLabelsData(getVisualizationData));
+ console.log('cardId', cardId);
+ break;
+ case 'usersOfAnnotationsChart':
+ createUsersOfAnnotationsChart('modal-chart', prepareUsersOfAnnotationsData(getVisualizationData));
+ console.log('cardId', cardId);
+ break;
+ default:
+ console.log('No valid chart selected');
+ break;
+ }
+
+
+ // Display the modal
+ modal.style.display = 'block';
+}
+
+function closeModal() {
+ const modal = document.getElementById('modal');
+ modal.style.display = 'none';
+}
+
+// Close the modal when clicking outside of it
+window.onclick = function(event) {
+ const modal = document.getElementById('modal');
+ if (event.target == modal) {
+ closeModal();
+ }
+};
diff --git a/components/visualization-dashboard/navbar.css b/components/visualization-dashboard/navbar.css
new file mode 100644
index 000000000..3bcbfc416
--- /dev/null
+++ b/components/visualization-dashboard/navbar.css
@@ -0,0 +1,37 @@
+.navbar {
+ background-color: #055160;
+ color: white;
+ padding: 15px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.navbar h1 {
+ margin: 0;
+ font-size: 24px;
+}
+
+.navbar nav ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+}
+
+.navbar nav ul li {
+ margin-left: 20px;
+}
+
+.navbar nav ul li a {
+ color: white;
+ text-decoration: none;
+ font-size: 18px;
+}
+
+.material-icons {
+ font-size: 17px;
+ color: white;;
+ margin-right: 1px;
+ margin-bottom: 6px;
+}
diff --git a/components/visualization-dashboard/navbar.js b/components/visualization-dashboard/navbar.js
new file mode 100644
index 000000000..271b3bc12
--- /dev/null
+++ b/components/visualization-dashboard/navbar.js
@@ -0,0 +1,13 @@
+document.addEventListener('DOMContentLoaded', function() {
+ const navbar = `
+
+
Visualization DashBoard
+
+
+ `;
+ document.body.insertAdjacentHTML('afterbegin', navbar);
+});
diff --git a/components/visualization-dashboard/visualization-init.js b/components/visualization-dashboard/visualization-init.js
new file mode 100644
index 000000000..5343dc44d
--- /dev/null
+++ b/components/visualization-dashboard/visualization-init.js
@@ -0,0 +1,51 @@
+function test() {
+ console.log('result', getVisualizationData);
+};
+
+async function initialize() {
+ function getQueryParam(param) {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get(param);
+ }
+
+ const slideId = getQueryParam('slideId');
+
+ const store = new Store('../data/');
+ try {
+ const data = await store.findSlide();
+ if (data.length == 0) {
+ var div = document.querySelector('.container');
+ div.textContent = `No Data Found ... x _ x`;
+ div.classList = `text-center p-4`;
+ return;
+ }
+ for (var i = 0; i < data.length; i++) {
+ const JSONdata={};
+ JSONdata.id=data[i]._id.$oid;
+ JSONdata.name=data[i].name;
+ JSONdata.displayed=true;
+ if (data[i].filter) {
+ JSONdata.filterList = JSON.parse(data[i].filter.replace(/'/g, '"'));
+ if (!JSONdata.filterList.some((filter) => (filters.indexOf(filter) > - 1))) {
+ JSONdata.filterList = ['Others'];
+ }
+ } else {
+ JSONdata.filterList = ['Public'];
+ }
+ try {
+ const dataq = await store.fetchMark(JSONdata.id);
+ JSONdata.annotations=dataq;
+ const dataqt = await store.fetchHeatMap(JSONdata.id);
+ JSONdata.heatmap=dataqt;
+
+ if (slideId == JSONdata.id) {
+ getVisualizationData = {...JSONdata};
+ }
+ } catch (error) {
+ console.error('Error finding slides:', error);
+ }
+ }
+ } catch (error) {
+ console.error('Error finding slides:', error);
+ };
+}