Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drewkiimon/8217f2e2d48dc2c44a313a6657bf5055 to your computer and use it in GitHub Desktop.
Save drewkiimon/8217f2e2d48dc2c44a313a6657bf5055 to your computer and use it in GitHub Desktop.
[Highcharts] Grouped Stacked Bar Chart v2
const testJSON = [
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Basic multiplication",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Basic division",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Add within 1000",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Represent multiplication on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Subtract within 1000",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Unit fractions on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Fractions on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Find 1 on the number line",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Division with groups of objects",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Equivalent fractions on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Identify quadrilaterals",
"Current Skill Mastery Level": "Familiar",
"Skill Mastery Points": 50
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Write numbers in different forms",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Basic multiplication",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Basic division",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Add within 1000",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Represent multiplication on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Subtract within 1000",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Unit fractions on the number line",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Fractions on the number line",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Find 1 on the number line",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Division with groups of objects",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_395089960259557425388489",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Round to nearest 10 or 100",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Basic multiplication",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Basic division",
"Current Skill Mastery Level": "Familiar",
"Skill Mastery Points": 50
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Compare fractions with the same numerator or denominator",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Add within 1000",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Represent multiplication on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Subtract within 1000",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Unit fractions on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Fractions on the number line",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_430035501016994887914888",
"Rostered Grade": "4th Grade",
"Course Name": "Get ready for 4th grade",
"Skill Name": "Division with groups of objects",
"Current Skill Mastery Level": "Familiar",
"Skill Mastery Points": 50
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Multiply without regrouping",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Divide multi-digit numbers by 2, 3, 4, and 5 (remainders)",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Multiply with regrouping",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Write mixed numbers and improper fractions",
"Current Skill Mastery Level": "Attempted",
"Skill Mastery Points": 0
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Write decimals as fractions",
"Current Skill Mastery Level": "Familiar",
"Skill Mastery Points": 50
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Multiply 2-digit numbers",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Divide multi-digit numbers by 6, 7, 8, and 9 (remainders)",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Equivalent fractions",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Decimals on the number line: tenths",
"Current Skill Mastery Level": "Mastered",
"Skill Mastery Points": 100
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Decimals on the number line: hundredths",
"Current Skill Mastery Level": "Proficient",
"Skill Mastery Points": 80
},
{
"Student Name": "kaid_1152800740810591955688313",
"Rostered Grade": "4th Grade",
"Course Name": "4th grade",
"Skill Name": "Multiplication and division word problems",
"Current Skill Mastery Level": "Familiar",
"Skill Mastery Points": 50
}
];
const images = [
"https://i.pinimg.com/originals/24/73/d9/2473d94cb7d607b461ecece38a0100bf.jpg",
"https://i.ibb.co/b3wVNYP/graph.png",
"https://tests4geeks.com/content/img/smp/java-programming-test-answer-1.png",
"https://smb.ibsrv.net/imageresizer/image/blog_images/1200x1200/59846/176287/0044181001582748537.jpg",
"https://www.incimages.com/uploaded_files/image/1920x1080/getty_464672063_2000160020009280233_339359.jpg",
"https://math.temple.edu/~reich/Fib/fibfamily.gif"
];
const randomNumberFromZeroToMax = (max) => Math.floor(Math.random() * max);
const formatDataForSeries = (data) => {
const courses = {};
const formattedData = [];
const returnedSkills = [];
const returnedCourses = [];
const drilldownData = {};
for (var i = 0; i <= data.length - 1; i++) {
const course = data[i]["Course Name"];
const skill = data[i]["Skill Name"];
const mastery = data[i]["Current Skill Mastery Level"];
const kaid = data[i]["Student Name"];
const masteryPoints = data[i]["Skill Mastery Points"];
if (!courses[course]) {
courses[course] = {};
drilldownData[course] = {};
returnedCourses.push(course);
}
if (!courses[course][skill]) {
courses[course][skill] = {};
drilldownData[course][skill] = {};
returnedSkills.push(skill);
}
if (!courses[course][skill][mastery]) {
courses[course][skill][mastery] = {
name: skill,
y: 0,
course: course,
mastery: mastery,
image: images[randomNumberFromZeroToMax(images.length)]
};
drilldownData[course][skill][mastery] = [];
}
courses[course][skill][mastery].y++;
drilldownData[course][skill][mastery].push([kaid, masteryPoints]);
}
const courseNames = Object.keys(courses);
const masteryContainer = {};
for (var i = 0; i <= courseNames.length - 1; i++) {
let c = courseNames[i];
let skills = Object.keys(courses[c]);
for (var j = 0; j <= skills.length - 1; j++) {
let s = skills[j];
let masteryLevelsForCourseNames = courses[c][s];
let levels = Object.keys(masteryLevelsForCourseNames);
if (!masteryContainer["Mastered"]) {
masteryContainer["Mastered"] = {
name: "Mastered",
data: [],
color: "#808080"
};
}
if (!masteryContainer["Familiar"]) {
masteryContainer["Familiar"] = {
name: "Familiar",
data: [],
color: "#D3D3D3"
};
}
if (!masteryContainer["Proficient"]) {
masteryContainer["Proficient"] = {
name: "Proficient",
data: [],
color: "#A9A9A9"
};
}
if (!masteryContainer["Attempted this skill"]) {
masteryContainer["Attempted this skill"] = {
name: "Attempted this skill",
data: [],
color: "#F5F5F5"
};
}
masteryContainer["Mastered"].data.push(
masteryLevelsForCourseNames["Mastered"] || 0
);
masteryContainer["Familiar"].data.push(
masteryLevelsForCourseNames["Familiar"] || 0
);
masteryContainer["Proficient"].data.push(
masteryLevelsForCourseNames["Proficient"] || 0
);
masteryContainer["Attempted this skill"].data.push(
masteryLevelsForCourseNames["Attempted this skill"] || 0
);
}
}
return [
returnedCourses,
returnedSkills,
Object.values(masteryContainer),
courses,
drilldownData
];
};
const [courses, skills, data, master, drilldownData] = formatDataForSeries(
testJSON
);
console.log(courses, skills, data, master, drilldownData);
const createBands = (master) => {
const bands = [];
const courseBands = Object.keys(master);
var start = -0.5;
for (var i = 0; i <= courseBands.length - 1; i++) {
const c = courseBands[i];
const count = Object.keys(master[c]).length;
bands.push({
borderWidth: 1,
from: start,
to: start + count,
color: "#f7f8fa",
label: {
align: "left",
text: `<div id='band${i}' class='band'><b>${courses[i]}</b><br/><span>${count} Skills</span></div>`,
useHTML: true,
verticalAlign: "bottom"
}
});
start += count;
}
return bands;
};
const createPlotLines = (master) => {
const plotLines = [];
const courseBands = Object.keys(master);
var start = -0.5;
for (var i = 0; i <= courseBands.length - 1; i++) {
const c = courseBands[i];
const count = Object.keys(master[c]).length;
plotLines.push({
color: "black",
width: 1,
value: (start += count),
dashStyle: "ShortDot",
zIndex: 10
});
start += count;
}
plotLines.pop(); // do not need last line
return plotLines;
};
const chart = Highcharts.chart("container", {
chart: {
type: "column",
animation: false,
backgroundColor: "#f7f8fa",
events: {
load: function () {
const bands = document.getElementsByClassName("highcharts-plot-band");
for (var i = 0; i <= bands.length - 1; i++) {
let band = bands[i];
let bandElements = band.getBoundingClientRect();
let bandWidth = bandElements.width;
const bandLabel = document.getElementById("band" + i);
bandLabel.style.width = bandWidth - 16 + "px";
}
},
redraw: function () {
const bands = document.getElementsByClassName("highcharts-plot-band");
for (var i = 0; i <= bands.length - 1; i++) {
let band = bands[i];
let bandElements = band.getBoundingClientRect();
let bandWidth = bandElements.width;
const bandLabel = document.getElementById("band" + (i + 1));
bandLabel.style.width = bandWidth - 16 + "px";
}
}
}
},
credits: { enabled: false },
title: {
text: ""
},
tooltip: {
backgroundColor: null,
borderWidth: 0,
hideDelay: 100,
shadow: false,
useHTML: true,
outside: true,
positioner: function (boxWidth, boxHeight, point) {
const { plotX, plotY, h } = point;
return { x: plotX + 50, y: plotY };
},
formatter: function (tooltip) {
const {
point: { color },
series: { name: masteryLevel, data },
x,
y
} = this;
const { course, image } = data[x];
return `
<span class="tooltip-header">${course}</span><br/>
<span class="tooltip-subheader">${skills[x]}</span><br/>
<div class="tooltip-info">
<div class="tooltip-box" style="background-color: ${color}"></div><span class="tooltip-category">${masteryLevel}</span>: <span class="tooltip-value">${y}</span>
</div>
<img class="tooltip-image" src="${image}" alt="graph image"/>
`;
},
style: {
padding: 0
}
},
plotOptions: {
column: {
groupPadding: 0,
pointPadding: 0.15,
borderWidth: 0,
stacking: "normal",
states: {
inactive: {
enabled: false
},
hover: {
color: "#1865F2"
}
},
events: {
click: function (event) {
console.log(this, event);
}
}
},
series: {
// pointWidth: 26
}
},
legend: {
align: "right",
verticalAlign: "top",
layout: "vertical",
title: { text: "MASTERY LEVELS" },
backgroundColor: "white",
padding: 20,
x: -50,
y: 0,
symbolPadding: 15,
symbolRadius: 2,
itemMarginBottom: 8,
borderColor: "lightgray",
borderWidth: 1,
borderRadius: 2,
shadow: true
},
xAxis: {
title: {
text: "UNITS",
align: "right"
},
labels: {
enabled: false
},
plotLines: createPlotLines(master),
plotBands: createBands(master),
tickWidth: 0
},
yAxis: {
title: { enabled: false },
// allowDecimals: false,
// min: 2 * 10,
// max: 300 * 1.1, // gives some buffer at top
gridLineWidth: 0,
tickAmount: 13,
tickWidth: 1,
tickLength: 16,
offset: 20,
labels: {
align: "left",
x: 4,
y: 4
}
},
series: data
});
.highcharts-tooltip > span {
background: rgba(255, 255, 255, 0.85);
border: 1px solid silver;
border-radius: 3px;
box-shadow: 1px 1px 2px #888;
padding: 24px;
}
.tooltip-header {
font-family: Lato;
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: 16px;
letter-spacing: 0.6px;
text-align: left;
color: rgba(33, 36, 44, 0.64);
margin-bottom: 4px;
}
.tooltip-subheader {
font-family: Lato;
font-size: 16px;
font-style: normal;
font-weight: 700;
line-height: 22px;
letter-spacing: 0em;
text-align: left;
color: background: rgba(0, 0, 0, 1);
margin-bottom: 8px;
}
.tooltip-info {
height: 8px;
border-bottom: 1px solid rgba(33, 36, 44, 0.16);
padding-bottom: 24px;
}
.tooltip-box {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 2px;
margin-right: 8px;
}
.tooltip-category {
font-family: Lato;
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: 16px;
letter-spacing: 0em;
text-align: left;
}
.tooltip-value {
font-family: Lato;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0em;
text-align: left;
}
.tooltip-image {
max-width: 230px;
margin-top: 14px;
}
.band {
overflow: hidden;
text-overflow: ellipsis;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment