I am building a business health dashboard. There are 100 questions, and these are aggregated into categories of health. Ultimately we add and/or average these values to report health and progress. So each question belongs to a category, and category’ belong to a questionnaire. We use count and average equations to sum up the activity. We are counting ‘Not Started’ ie 0, or C’ompleted’ ie 100. Anything >0 and <100 is Work In Progress. After some YT inspiration and Chat GPT work we came up with this. We are pretty happy with it! Some sensitive information is privatised here. Happy to share more if anyone is interested. besaas.co
Hi @David22 - I’m guessing I was your YouTube inspiration and that you’re David White, who I connected with on LinkedIn today
Welcome to the forum
Thanks, Nice to be connected. If anyone wants the code for this, just give me a shout.
Hi David
Would you please be kind enough to share your code as I was never able to get the code supplied by Julian Kirkness working properly in our case?
Dean
Hi, can you share this code?
Is be interested in the code for your dashboard … in particular the percentage bars. Already have a couple of uses in mind.
Sorry if I missed previous code requests!
I’ve added the code. Hope it makes sense, I did that almost 12 months ago.
We did use some ChatGPT to assist.
We have a view that computes a % value.
The code just adds new HTML to that view, and uses the computed% to render the progress bars.
I am sure there is a better, simpler, faster way to do this. Thats what we did, worked for us.
Hope it helps other members in the community!
// Define the colors as hex strings
var redHex = '#ef4444'; // tw: red-500
var greenHex = '#84cc16'; // tw: lime-500
var leftCol = "#fdba74"; // tw: orange-400
var centerCol = "#facc15"; // tw: yellow-400
var rightCol = "#4ade80"; // tw: green-400
var textColor = '#737373'; // Color for the overlaid text
// SVG icons for use in cards
var shieldAlertIcon = function(color) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="${color}" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12c5.16-1.26 9-6.45 9-12V5M11 7h2v6h-2m0 2h2v2h-2"/></svg>`;
};
var shieldCheckIcon = function(color) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="${color}" d="m10 17l-4-4l1.41-1.41L10 14.17l6.59-6.59L18 9m-6-8L3 5v6c0 5.55 3.84 10.74 9 12c5.16-1.26 9-6.45 9-12V5l-9-4Z"/></svg>`;
};
var rocketLaunchIcon = function(color) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="${color}" d="m13.13 22.19l-1.63-3.83c1.57-.58 3.04-1.36 4.4-2.27l-2.77 6.1M5.64 12.5l-3.83-1.63l6.1-2.77C7 9.46 6.22 10.93 5.64 12.5M21.61 2.39S16.66.269 11 5.93c-2.19 2.19-3.5 4.6-4.35 6.71c-.28.75-.09 1.57.46 2.13l2.13 2.12c.55.56 1.37.74 2.12.46A19.1 19.1 0 0 0 18.07 13c5.66-5.66 3.54-10.61 3.54-10.61m-7.07 7.07c-.78-.78-.78-2.05 0-2.83s2.05-.78 2.83 0c.77.78.78 2.05 0 2.83c-.78.78-2.05.78-2.83 0m-5.66 7.07l-1.41-1.41l1.41 1.41M6.24 22l3.64-3.64c-.34-.09-.67-.24-.97-.45L4.83 22h1.41M2 22h1.41l4.77-4.76l-1.42-1.41L2 20.59V22m0-2.83l4.09-4.08c-.21-.3-.36-.62-.45-.97L2 17.76v1.41Z"/></svg>`;
};
var headLightBulkIcon = function(color) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="${color}" d="M13 3C9.23 3 6.19 5.95 6 9.66l-1.92 2.53c-.24.31 0 .81.42.81H6v3c0 1.11.89 2 2 2h1v3h7v-4.69c2.37-1.12 4-3.51 4-6.31c0-3.86-3.12-7-7-7m1 11h-2v-1h2v1m1.6-4.5a3 3 0 0 1-1.1 1.08V12h-3v-1.42c-1.43-.83-1.93-2.66-1.1-4.08s2.67-1.94 4.1-1.12s1.93 2.67 1.1 4.12Z"/></svg>`;
};
var chartDonutIcon = function(color) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="${color}" d="M13 2.05v3.03c3.39.49 6 3.39 6 6.92c0 .9-.18 1.75-.5 2.54l2.62 1.53c.56-1.24.88-2.62.88-4.07c0-5.18-3.95-9.45-9-9.95M12 19a7 7 0 0 1-7-7c0-3.53 2.61-6.43 6-6.92V2.05c-5.06.5-9 4.76-9 9.95a10 10 0 0 0 10 10c3.3 0 6.23-1.61 8.05-4.09l-2.6-1.53A6.89 6.89 0 0 1 12 19Z"/></svg>`;
};
// Function to interpolate between two colors
function interpolateColor(color1, color2, factor) {
var result = color1.slice(1).match(/.{2}/g).map((hex, index) => {
var value1 = parseInt(hex, 16);
var value2 = parseInt(color2.slice(1).match(/.{2}/g)[index], 16);
var value = Math.round(value1 + factor * (value2 - value1)).toString(16);
return value.length === 1 ? '0' + value : value;
});
return `#${result.join('')}`;
}
// Function to apply progress indicator to a specific view and field
function applyProgressIndicator(viewId, fieldKey) {
// Iterate through each row of data
$(`.${fieldKey}`).each(function() {
// Get the result value
var result = $(this).text().trim();
// Parse the result to a number
var value = parseFloat(result);
// Skip if the result is not a number
if (isNaN(value)) {
return;
}
// Determine the content based on the value
var content;
if (value === 0) {
content = `<div style="width: 100px; height: 20px; background-color: ${redHex}; color: white; text-align: center; line-height: 20px; border-radius: 5px;">Not Started</div>`;
} else if (value === 100) {
content = `<div style="width: 100px; height: 20px; background-color: ${greenHex}; color: white; text-align: center; line-height: 20px; border-radius: 5px;">Complete!</div>`;
} else {
var color;
if (value < 50) {
color = interpolateColor(leftCol, centerCol, value / 50);
} else {
color = interpolateColor(centerCol, rightCol, (value - 50) / 50);
}
content = `<svg width="100" height="20">
<rect width="100" height="20" fill="#f0f0f0" rx="5" ry="5" />
<rect width="${(value / 100) * 100}" height="20" fill="${color}" rx="5" ry="5" />
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="${textColor}" font-size="12">${value}</text>
</svg>`;
}
// Replace the text with the content
$(this).html(content);
});
}
// Apply the function to business report
$(document).on('knack-view-render.view_4', function(event, view, data) {
applyProgressIndicator('view_4', 'field_22');
});
$(document).on('knack-view-render.view_13', function(event, view, data) {
applyProgressIndicator('view_13', 'field_22');
});
// Apply to Marketing Report
// $(document).on('knack-view-render.view_24', function(event, view, data) {
// applyProgressIndicator('view_24', 'field_22');
// });
// $(document).on('knack-view-render.view_21', function(event, view, data) {
// applyProgressIndicator('view_21', 'field_22');
// });
$(document).on('knack-view-render.view_15', function(event, view, data) {
applyProgressIndicator('view_15', 'field_17');
});
// major catr table marketing health
$(document).on('knack-view-render.view_21', function(event, view, data) {
applyProgressIndicator('view_21', 'field_27');
applyProgressIndicator('view_21', 'field_28');
// Apply the custom CSS to hide the pagination info text
$('<style>.kn-entries-summary { display: none !important; }</style>').appendTo('head');
});
// major table categories business health
$(document).on('knack-view-render.view_2', function(event, view, data) {
applyProgressIndicator('view_5', 'field_27');
applyProgressIndicator('view_5', 'field_28');
// Apply the custom CSS to hide the pagination info text
$('<style>.kn-entries-summary { display: none !important; }</style>').appendTo('head');
});
// Business Health Report
$(document).on('knack-view-render.view_5', function(event, view, data) {
// $('#view_5 .view-header').hide();
$('#view_5 .kn-table-wrapper').hide();
applyProgressIndicator('view_5', 'field_35');
applyProgressIndicator('view_5', 'field_36');
// Function to add a card with a progress bar
function addProgressCard(fieldValue, borderColor, textColor, label, icon) {
fieldValue = Math.round(fieldValue); // Round the value to the nearest whole number
return `
<div style="display: flex; align-items: center; background-color: white; color: ${textColor}; padding: 10px; border: 2px solid ${borderColor}; border-radius: 5px; margin: 5px; ">
<div style="padding: 0 10px; display: flex; justify-content: center; align-items: center;">
${icon(borderColor)}
</div>
<div style="margin-left: 10px; flex-grow: 1;">
<div>${label}</div>
<svg width="90%" height="20" style="margin-top: 5px;">
<rect width="100%" height="20" fill="#f0f0f0" rx="5" ry="5" />
<rect width="${fieldValue}%" height="20" fill="${borderColor}" rx="5" ry="5" />
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#737373" font-size="12">${fieldValue}%</text>
</svg>
</div>
</div>
`;
}
// Function to add a card with an icon and text
function addCard(fieldValue, borderColor, textColor, label, icon) {
return `
<div style="display: flex; align-items: center; background-color: white; color: ${textColor}; padding: 10px; border: 2px solid ${borderColor}; border-radius: 5px; margin: 5px; ">
<div style="padding: 0 10px; display: flex; justify-content: center; align-items: center;">
${icon(borderColor)}
</div>
<div style="margin-left: 10px;">
<div>${label}</div>
<div style="font-size: 1.5em; font-weight: bold;">${fieldValue}</div>
</div>
</div>
`;
}
// Card configurations
var cards = [
{
fieldKey: 'field_63_raw',
label: 'Not Started %',
color: redHex,
icon: shieldAlertIcon
},
{
fieldKey: 'field_64_raw',
label: 'WIP %',
color: '#fbbf24', // tw: yellow-400
icon: rocketLaunchIcon
},
{
fieldKey: 'field_65_raw',
label: 'Complete %',
color: greenHex,
icon: shieldCheckIcon
},
{
fieldKey: 'field_35_raw',
label: 'Progress of Started',
color: '#a78bfa',
isProgress: true,
icon: headLightBulkIcon
},
{
fieldKey: 'field_36_raw',
label: 'Progress Overall',
color: '#ec4899',
isProgress: true,
icon: chartDonutIcon
}
];
// Create the cards HTML
var rowHtml = '<div style="display: flex; justify-content: space-between; margin-top: 20px; margin-right: 20px;">';
cards.forEach(function(card) {
var fieldValue = Math.round(data[0][card.fieldKey]); // Round the value to the nearest whole number
if (card.isProgress) {
rowHtml += `<div style="flex: 1;">${addProgressCard(fieldValue, card.color, card.color, card.label, card.icon)}</div>`;
} else {
rowHtml += `<div style="flex: 1;">${addCard(fieldValue, card.color, card.color, card.label, card.icon)}</div>`;
}
});
// Add empty columns if less than 5 cards
for (var i = cards.length; i < 5; i++) {
rowHtml += '<div style="flex: 1;"></div>';
}
rowHtml += '</div>';
// Append the row to the view
$('#' + view.key).append(rowHtml);
});
// Marketing Health Event
$(document).on('knack-view-render.view_24', function(event, view, data) {
// $('#view_5 .view-header').hide();
$('#view_24 .kn-table-wrapper').hide();
applyProgressIndicator('view_24', 'field_35');
applyProgressIndicator('view_24', 'field_36');
// Function to add a card with a progress bar
function addProgressCard(fieldValue, borderColor, textColor, label, icon) {
fieldValue = Math.round(fieldValue); // Round the value to the nearest whole number
return `
<div style="display: flex; align-items: center; background-color: white; color: ${textColor}; padding: 10px; border: 2px solid ${borderColor}; border-radius: 5px; margin: 5px; ">
<div style="padding: 0 10px; display: flex; justify-content: center; align-items: center;">
${icon(borderColor)}
</div>
<div style="margin-left: 10px; flex-grow: 1;">
<div>${label}</div>
<svg width="90%" height="20" style="margin-top: 5px;">
<rect width="100%" height="20" fill="#f0f0f0" rx="5" ry="5" />
<rect width="${fieldValue}%" height="20" fill="${borderColor}" rx="5" ry="5" />
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#737373" font-size="12">${fieldValue}%</text>
</svg>
</div>
</div>
`;
}
// Function to add a card with an icon and text
function addCard(fieldValue, borderColor, textColor, label, icon) {
return `
<div style="display: flex; align-items: center; background-color: white; color: ${textColor}; padding: 10px; border: 2px solid ${borderColor}; border-radius: 5px; margin: 5px; ">
<div style="padding: 0 10px; display: flex; justify-content: center; align-items: center;">
${icon(borderColor)}
</div>
<div style="margin-left: 10px;">
<div>${label}</div>
<div style="font-size: 1.5em; font-weight: bold;">${fieldValue}</div>
</div>
</div>
`;
}
// Card configurations
var cards = [
{
fieldKey: 'field_63_raw',
label: 'Not Started %',
color: redHex,
icon: shieldAlertIcon
},
{
fieldKey: 'field_64_raw',
label: 'WIP %',
color: '#fbbf24', // tw: yellow-400
icon: rocketLaunchIcon
},
{
fieldKey: 'field_65_raw',
label: 'Complete %',
color: greenHex,
icon: shieldCheckIcon
},
{
fieldKey: 'field_35_raw',
label: 'Progress of Started',
color: '#a78bfa',
isProgress: true,
icon: headLightBulkIcon
},
{
fieldKey: 'field_36_raw',
label: 'Progress Overall',
color: '#ec4899',
isProgress: true,
icon: chartDonutIcon
}
];
// Create the cards HTML
var rowHtml = '<div style="display: flex; justify-content: space-between; margin-top: 20px; margin-right: 20px;">';
cards.forEach(function(card) {
var fieldValue = Math.round(data[0][card.fieldKey]); // Round the value to the nearest whole number
if (card.isProgress) {
rowHtml += `<div style="flex: 1;">${addProgressCard(fieldValue, card.color, card.color, card.label, card.icon)}</div>`;
} else {
rowHtml += `<div style="flex: 1;">${addCard(fieldValue, card.color, card.color, card.label, card.icon)}</div>`;
}
});
// Add empty columns if less than 5 cards
for (var i = cards.length; i < 5; i++) {
rowHtml += '<div style="flex: 1;"></div>';
}
rowHtml += '</div>';
// Append the row to the view
$('#' + view.key).append(rowHtml);
});
Thanks for that, I’ll take a look at this over the weekend.