diff --git a/src/lib/services/models/course-utils.ts b/src/lib/services/models/course-utils.ts
index 3a3478f11..0d0c11219 100644
--- a/src/lib/services/models/course-utils.ts
+++ b/src/lib/services/models/course-utils.ts
@@ -2,6 +2,25 @@ import { addIcon } from "$lib/ui/themes/styles/icon-lib";
 import type { Composite, Course, IconNav, Lo, LoType, Topic } from "./lo-types";
 import { filterByType, setShowHide } from "./lo-utils";
 
+function threadToc(lo: Lo, course: Course) {
+  if (lo.type == "topic") {
+    const topic = lo as Topic;
+    topic.toc = [];
+    topic.toc.push(...topic.panels.panelVideos, ...topic.panels.panelTalks, ...topic.panels.panelNotes, ...topic.units.units, ...topic.units.standardLos, ...topic.units.sides);
+
+    topic.toc.forEach((lo) => {
+      lo.parentLo = course;
+      lo.parentTopic = topic;
+      if (lo.type === "unit" || lo.type === "side") {
+        const composite = lo as Composite;
+        composite.los.forEach((subLo) => {
+          subLo.parentTopic = topic;
+        });
+      }
+    });
+  }
+}
+
 export function createToc(course: Course) {
   course.los.forEach((lo) => {
     if (lo.type == "topic") {
diff --git a/src/lib/services/models/lo-tree.ts b/src/lib/services/models/lo-tree.ts
index 42411fee8..9ad885ee9 100644
--- a/src/lib/services/models/lo-tree.ts
+++ b/src/lib/services/models/lo-tree.ts
@@ -1,6 +1,6 @@
 import { isCompositeLo, type Course, type Lo, type Composite, type LoType, type Topic } from "./lo-types";
 import { convertLoToHtml } from "./markdown-utils";
-import { allVideoLos, crumbs, flattenLos, loadIcon, getPanels, getUnits, injectCourseUrl, removeUnknownLos } from "./lo-utils";
+import { allVideoLos, crumbs, flattenLos, loadIcon, getPanels, getUnits, injectCourseUrl, removeUnknownLos, filterByType } from "./lo-utils";
 import { createCompanions, createToc, createWalls, initCalendar, loadPropertyFlags } from "./course-utils";
 
 export function decorateCourseTree(course: Course, courseId: string = "", courseUrl = "") {
@@ -26,12 +26,14 @@ export function decorateCourseTree(course: Course, courseId: string = "", course
   const videoLos = allVideoLos(allLos);
   videoLos.forEach((lo) => course.loIndex.set(lo.video, lo));
   course.topicIndex = new Map<string, Topic>();
-  course.los.forEach((lo) => course.topicIndex.set(lo.route, lo as Topic));
+  //course.los.forEach((lo) => course.topicIndex.set(lo.route, lo as Topic));
+  const topicLos = filterByType(allLos, "topic");
+  topicLos.forEach((lo) => course.topicIndex.set(lo.route, lo as Topic));
 
   loadPropertyFlags(course);
   createCompanions(course);
   createWalls(course);
-  createToc(course);
+  // createToc(course);
   initCalendar(course);
 }
 
@@ -51,6 +53,17 @@ export function decorateLoTree(course: Course, lo: Lo) {
     const compositeLo = lo as Composite;
     compositeLo.panels = getPanels(compositeLo.los);
     compositeLo.units = getUnits(compositeLo.los);
+
+    compositeLo.toc = [];
+    compositeLo.toc.push(
+      ...compositeLo?.panels?.panelVideos,
+      ...compositeLo?.panels?.panelTalks,
+      ...compositeLo?.panels?.panelNotes,
+      ...compositeLo?.units?.units,
+      ...compositeLo?.units?.standardLos,
+      ...compositeLo?.units?.sides
+    );
+
     for (const childLo of compositeLo.los) {
       childLo.parentLo = lo;
       if (compositeLo.los) {
diff --git a/src/lib/services/models/lo-types.ts b/src/lib/services/models/lo-types.ts
index 5f030e77e..b10bdfa3e 100644
--- a/src/lib/services/models/lo-types.ts
+++ b/src/lib/services/models/lo-types.ts
@@ -155,13 +155,13 @@ export type Units = {
 };
 
 export type Composite = Lo & {
+  toc: Lo[];
   los: Lo[]; // child los
   panels: Panels; // child panel los - paneltalks, panelvideos, panelnotes.
   units: Units; // child units, including side units
 };
 
 export type Topic = Composite & {
-  toc: Lo[];
   type: "topic";
 };
 
diff --git a/src/lib/services/types/supabase-metrics.ts b/src/lib/services/types/supabase-metrics.ts
index 0b8b53fa3..55dd6b9c8 100644
--- a/src/lib/services/types/supabase-metrics.ts
+++ b/src/lib/services/types/supabase-metrics.ts
@@ -1,51 +1,206 @@
+export interface CalendarMap {
+  date: string;
+  timeActive: number;
+}
+
+export interface LearningObject {
+  route: string;
+  loTitle: string;
+  parentLoTitle: string | undefined;
+  date: Date;
+  pageLoads: number;
+  timeActive: number;
+  nickname: string;
+}
+
 export interface LearningInteraction {
-    id?: Date;
-    loid?: string,
-    courseid: string,
-    studentid: string,
-    date: Date;
-    pageloads: number;
-    timeactive: number;
+  id?: Date;
+  loid?: string;
+  courseid: string;
+  studentid: string;
+  date: Date;
+  pageloads: number;
+  timeactive: number;
+}
+
+export interface GridConfig {
+  left: string | number;
+  right: string | number;
+  bottom: string | number;
+  top: string | number;
+  width: string | number;
+  height: string | number;
+  containLabel: boolean;
+}
+
+export type ChartType = {
+  type:
+    | "line"
+    | "bar"
+    | "pie"
+    | "scatter"
+    | "effectScatter"
+    | "radar"
+    | "tree"
+    | "treemap"
+    | "sunburst"
+    | "boxplot"
+    | "candlestick"
+    | "heatmap"
+    | "parallel"
+    | "lines"
+    | "graph"
+    | "sankey"
+    | "funnel"
+    | "gauge"
+    | "pictorialBar"
+    | "themeRiver"
+    | "calendar"
+    | "map"
+    | "custom";
 };
 
-export interface HeatMapSeriesData {
-    name: string;
-    type: string;
-    top: string;
-    data: number[][];
-    label: { show: boolean; };
+export interface BackgroundColor {
+  image: HTMLImageElement;
+  repeat: "repeat";
+}
+
+export type HeatmapShowBoolean = {
+  show: boolean;
 };
 
-export interface LearningObject {
-    route: string;
-    loTitle: string;
-    parentLoTitle: string | undefined;
-    date: Date;
-    pageLoads: number;
-    timeActive: number;
-    nickname: string;
+export interface AxisLabel {
+  interval: number;
+  fontSize: number;
+  margin?: number; // Adjust margin to control spacing
+  padding?: number[];
+}
+
+export interface AxisTick {
+  alignWithLabel: boolean;
+}
+
+export interface axisPointer {
+  type: "line" | "shadow" | "none";
+}
+
+export interface XAxis {
+  type: "value" | "category";
+  data: string[] | number[];
+  boundaryGap?: number[];
+  nameLocation?: "start" | "middle" | "center" | "end";
+  splitArea?: HeatmapShowBoolean;
+  axisLabel?: AxisLabel;
+  axisTick?: AxisTick;
+  axisPointer?: axisPointer;
+  position?: "top" | "bottom";
+}
+
+export interface YAxis {
+  type: "value" | "category";
+  data: string[];
+  splitArea?: HeatmapShowBoolean;
+  axisLabel?: AxisLabel;
+}
+
+export interface Color {
+  image: HTMLImageElement;
+  repeat: string;
+}
+
+export interface ItemStyle {
+  color: Color;
+  borderWidth: number;
+  borderColor: string;
+}
+
+export interface Tooltip {
+  position: "top" | "bottom" | "left" | "right";
+  trigger?: "item";
+  formatter?: (param: string | number) => string;
+  //formatter: string; //"{a} <br/>{b}: {c} mins"
+}
+
+export interface Series {
+  type: ChartType;
+  data: number[];
+  itemStyle: ItemStyle;
+}
+
+export interface BoxplotChartConfig {
+  backgroundColor: BackgroundColor;
+  xAxis: XAxis;
+  yAxis: YAxis;
+  series: Series[];
+  tooltip: Tooltip;
+}
+
+export type ChartTitle = {
+  top: string;
+  left: string;
+  text: string;
 };
 
-export interface CalendarMap {
-    date: string;
-    timeActive: number;
+export interface VisualMap {
+  min: number;
+  max: number;
+  calculable: boolean;
+  orient: "horizontal" | "vertical";
+  left: number | string | "center" | "left" | "right";
+  align: "auto" | "left" | "right" | "center";
+  bottom: number | string | "center" | "top" | "bottom";
+}
+
+export interface HeatMapChartConfig {
+  title: ChartTitle;
+  tooltip: Tooltip;
+  backgroundColor: BackgroundColor;
+  grid: GridConfig;
+  xAxis: XAxis;
+  yAxis: YAxis;
+  visualMap: VisualMap;
+  series: HeatMapSeriesData[];
+}
+
+export interface BoxplotTooltip {
+  trigger: "item";
+  formatter: (param: string | number) => string;
+  //formatter: string; //"{a} <br/>{b}: {c} mins"
+}
+
+export interface HeatmapTooltip {
+  position: "top" | "bottom" | "left" | "right";
+  trigger: "item";
+  formatter: string; //"{a} <br/>{b}: {c} mins"
+}
+
+export type NameTypeData = {
+  name: string;
+  type?: string;
 };
 
-export interface LabStepData {
-    aggregatedTimeActive: number;
-    title: string;
-    loType: string;
+export interface HeatMapSeriesData {
+  top: string | number;
+  name: string;
+  type: "heatmap";
+  selectedMode: "single" | "multiple";
+  data: number[][];
+  label: HeatmapShowBoolean;
 }
 
-export interface BoxplotData {
-    value: [number, number, number, number, number];
-    title: string;
-    lowNickname: string;
-    highNickname: string;
+// export type HeatMapSeriesData = NameTypeData & {
+//   data: number[][];
+//   top: string;
+//   selectedMode?: string;
+//   label: { show: boolean };
+// };
+
+export type BoxplotData = NameTypeData & {
+  value: [number, number, number, number, number];
+  lowNickname: string;
+  highNickname: string;
 };
 
-export type OuterPieData = {
-    value: number;
-    name: string;
-    type: string;
-  };
\ No newline at end of file
+export type DrilledDownData = NameTypeData & {
+  value: number;
+};
diff --git a/src/lib/services/utils/supabase-utils.ts b/src/lib/services/utils/supabase-utils.ts
index f7b3ee8ce..b29a0af88 100644
--- a/src/lib/services/utils/supabase-utils.ts
+++ b/src/lib/services/utils/supabase-utils.ts
@@ -73,6 +73,7 @@ export async function getDurationTotal(key: string, table: string, id: string):
 }
 
 export async function insertOrUpdateCalendar(studentId: string, courseId: string) {
+  if(!studentId || !courseId) return;
   const durationPromise = getCalendarDuration(formatDate(new Date()), studentId, courseId);
   const countPromise = getCalendarCount(formatDate(new Date()), studentId, courseId);
   const [timeActive, pageLoads] = await Promise.all([durationPromise, countPromise]);
@@ -205,12 +206,12 @@ export const updateCalendarDuration = async (id: string, studentId: string, cour
 
 export async function storeStudentCourseLearningObjectInSupabase(course: Course, loid: string, lo: Lo, userDetails: User) {
   //   const loTitle = getLoTitle(params)
-  if (userDetails?.user_metadata.full_name === "Anon") return;
+  if (userDetails?.user_metadata?.full_name === "Anon") return;
   // await insertOrUpdateCourse(course);
   // await addOrUpdateStudent(userDetails);
   // await addOrUpdateLo(loid, lo, lo.title);
-  await handleInteractionData(course.courseId, userDetails.user_metadata.user_name, loid, lo);
-  await insertOrUpdateCalendar(userDetails.user_metadata.user_name, course.courseId);
+  await handleInteractionData(course.courseId, userDetails?.user_metadata?.user_name, loid, lo);
+  await insertOrUpdateCalendar(userDetails?.user_metadata?.user_name, course.courseId);
 }
 
 export async function handleInteractionData(courseId: string, studentId: string, loId: string, lo: Lo) {
@@ -290,25 +291,27 @@ export function getSimpleTypesValues(los: Lo[]) {
   return [...notes, ...archives, ...webs, ...githubs, ...panelnotes, ...paneltalks, ...panelVideos, ...talks, ...books, ...labs, ...steps];
 }
 
-export async function getUser(username: string) {
-  return fetch(`https://api.github.com/users/${username}`)
-    .then((response) => response.json())
-    .then((response) => {
-      return response.name;
-    });
+export async function getUserNames(usernames: string[]): Promise<Map<string, string>> {
+  const userMap = new Map<string, string>();
+  for (const username of usernames) {
+    const response = await fetch(`https://api.github.com/users/${username}`);
+    const user = await response.json();
+    if (user.name) {
+      userMap.set(username, user.name);
+    }
+  }
+  return userMap;
 }
 
-export async function getGithubAvatarUrl(username: string) {
-  const url = `https://api.github.com/users/${username}`;
-  try {
+export async function getGithubAvatarUrl(usernames: string[]): Promise<Map<string, string>> {
+  const imageMap = new Map<string, string>();
+  for (const username of usernames) {
+    const url = `https://api.github.com/users/${username}`;
     const response = await fetch(url);
-    if (!response.ok) {
-      throw new Error(`User not found: ${response.status}`);
-    }
     const data = await response.json();
-    return data.avatar_url;
-  } catch (error) {
-    console.error("Error fetching the avatar URL:", error);
-    return null;
+    if (data.avatar_url) {
+      imageMap.set(username, data.avatar_url);
+    }
   }
+  return imageMap;
 }
diff --git a/src/lib/ui/learning-objects/layout/TopicContextPanel.svelte b/src/lib/ui/learning-objects/layout/LoContextPanel.svelte
similarity index 76%
rename from src/lib/ui/learning-objects/layout/TopicContextPanel.svelte
rename to src/lib/ui/learning-objects/layout/LoContextPanel.svelte
index 5447c987e..b9a71426b 100644
--- a/src/lib/ui/learning-objects/layout/TopicContextPanel.svelte
+++ b/src/lib/ui/learning-objects/layout/LoContextPanel.svelte
@@ -1,12 +1,11 @@
 <script lang="ts">
-  import type { Topic } from "$lib/services/models/lo-types";
   import type { Lo } from "$lib/services/models/lo-types";
   import Image from "../../themes/Image.svelte";
   import { currentLo, layout } from "$lib/stores";
   import { onDestroy } from "svelte";
-  import TopicContext from "../structure/TopicContext.svelte";
+  import LoContext from "../structure/LoContext.svelte";
 
-  export let topic: Topic;
+  export let loContext: Lo;
 
   let imageHeight = "";
   let headingText = "";
@@ -29,11 +28,11 @@
 </script>
 
 <div class="card {cardWidths} px-4 py-2">
-  <h3 class="px-4 py-2 text-center {headingText}">{topic?.title}</h3>
+  <h3 class="px-4 py-2 text-center {headingText}">{loContext?.title}</h3>
   <div class="card-body">
     <figure class="flex justify-center p-2">
       <Image {lo} />
     </figure>
-    <TopicContext {topic} />
+    <LoContext lo={loContext} />
   </div>
 </div>
diff --git a/src/lib/ui/learning-objects/structure/Context.svelte b/src/lib/ui/learning-objects/structure/Context.svelte
index d93fd5fd2..b3083bcee 100644
--- a/src/lib/ui/learning-objects/structure/Context.svelte
+++ b/src/lib/ui/learning-objects/structure/Context.svelte
@@ -1,7 +1,13 @@
 <script lang="ts">
   import type { Lo } from "$lib/services/models/lo-types";
-  import TopicContextPanel from "../layout/TopicContextPanel.svelte";
+  import LoContextPanel from "../layout/LoContextPanel.svelte";
   export let lo: Lo;
+  let loContext = lo;
+  if (loContext) {
+    while (loContext.type !== "topic" && loContext.type !== "course") {
+      loContext = loContext.parentLo!;
+    }
+  }
 </script>
 
 <div class="flex w-11/12 mx-auto">
@@ -10,9 +16,9 @@
       <slot />
     {/key}
   </div>
-  {#if lo.parentTopic}
+  {#if loContext}
     <div class="hidden md:block">
-      <TopicContextPanel topic={lo.parentTopic} />
+      <LoContextPanel {loContext} />
     </div>
   {/if}
 </div>
diff --git a/src/lib/ui/learning-objects/structure/CourseContext.svelte b/src/lib/ui/learning-objects/structure/CourseContext.svelte
index 42bf38e48..50b0897c6 100644
--- a/src/lib/ui/learning-objects/structure/CourseContext.svelte
+++ b/src/lib/ui/learning-objects/structure/CourseContext.svelte
@@ -1,6 +1,6 @@
 <script lang="ts">
-  import type { Course } from "$lib/services/models/lo-types";
-  import TopicContext from "./TopicContext.svelte";
+  import { isCompositeLo, type Course } from "$lib/services/models/lo-types";
+  import LoContext from "./LoContext.svelte";
   import { Accordion, AccordionItem } from "@skeletonlabs/skeleton";
 
   export let course: Course;
@@ -8,11 +8,11 @@
 
 <Accordion regionPanel="space-y-0.5">
   {#each course.los as lo}
-    {#if lo.type === "topic" && !lo.hide}
+    {#if !lo.hide}
       <AccordionItem>
         <svelte:fragment slot="summary">{lo.title}</svelte:fragment>
         <svelte:fragment slot="content">
-          <TopicContext topic={lo} />
+          <LoContext {lo} />
         </svelte:fragment>
       </AccordionItem>
     {/if}
diff --git a/src/lib/ui/learning-objects/structure/LoContext.svelte b/src/lib/ui/learning-objects/structure/LoContext.svelte
new file mode 100644
index 000000000..1ddc84590
--- /dev/null
+++ b/src/lib/ui/learning-objects/structure/LoContext.svelte
@@ -0,0 +1,37 @@
+<script lang="ts">
+  import type { Lo } from "$lib/services/models/lo-types";
+  import LoReference from "./LoReference.svelte";
+
+  export let lo: Lo;
+  if (lo?.toc) {
+    lo?.toc.forEach((lo) => {
+      if (lo?.route.endsWith("/")) {
+        lo.route = lo?.route.slice(0, -1);
+      }
+    });
+  }
+</script>
+
+{#each lo?.toc as lo}
+  <LoReference {lo} />
+  {#if lo.toc}
+    {#each lo?.toc as lo}
+      <LoReference {lo} indent={4} />
+      {#if lo.toc}
+        {#each lo?.toc as lo}
+          <LoReference {lo} indent={8} />
+          {#if lo.toc}
+            {#each lo?.toc as lo}
+              <LoReference {lo} indent={12} />
+              {#if lo.toc}
+                {#each lo?.toc as lo}
+                  <LoReference {lo} indent={16} />
+                {/each}
+              {/if}
+            {/each}
+          {/if}
+        {/each}
+      {/if}
+    {/each}
+  {/if}
+{/each}
diff --git a/src/lib/ui/learning-objects/structure/LoReference.svelte b/src/lib/ui/learning-objects/structure/LoReference.svelte
new file mode 100644
index 000000000..6b86d17e0
--- /dev/null
+++ b/src/lib/ui/learning-objects/structure/LoReference.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+  import type { Lo } from "$lib/services/models/lo-types";
+  import Icon from "$lib/ui/themes/icons/Icon.svelte";
+
+  export let lo: Lo;
+  export let indent = 0;
+</script>
+
+<a href={lo?.route} class="flex py-1 pl-{indent}">
+  <Icon type={lo.type} />
+  <span class="ml-2 mb-1"> {@html lo.title} </span>
+  {#if lo.video && lo.type != "panelvideo"}
+    <a class="flex pl-4" href={lo.video}>
+      <Icon type="video" />
+    </a>
+  {/if}
+</a>
diff --git a/src/lib/ui/learning-objects/structure/TopicContext.svelte b/src/lib/ui/learning-objects/structure/TopicContext.svelte
deleted file mode 100644
index 5de40bb23..000000000
--- a/src/lib/ui/learning-objects/structure/TopicContext.svelte
+++ /dev/null
@@ -1,37 +0,0 @@
-<script lang="ts">
-  import type { Topic } from "$lib/services/models/lo-types";
-  import { currentCourse } from "$lib/stores";
-  import Icon from "$lib/ui/themes/icons/Icon.svelte";
-
-  export let topic: Topic;
-</script>
-
-{#each topic?.toc as lo}
-  <a href={lo.type === "unit" ? lo?.parentTopic?.route : lo.type === "side" ? lo?.parentTopic?.route : lo?.route} class="flex py-1">
-    <Icon type={lo.type} />
-    <span class="ml-2 mb-1"> {@html lo.title} </span>
-    {#if lo.video && lo.type != "panelvideo"}
-      <a class="flex pl-1" href={lo.video}>
-        <Icon type="video" />
-      </a>
-    {/if}
-  </a>
-  {#if lo.type != "lab"}
-    {#if lo.los}
-      {#each lo.los as lo}
-        <div class="flex py-1">
-          <a class="inline-flex pl-6" href={lo.route}>
-            <Icon type={lo.type} /> <span class="pl-2"> {lo.title} </span>
-          </a>
-          {#if lo.video && lo.type != "panelvideo"}
-            {#if !$currentCourse.areVideosHidden}
-              <a class="inline-flex pl-2" href={lo.video}>
-                <Icon type="video" />
-              </a>
-            {/if}
-          {/if}
-        </div>
-      {/each}
-    {/if}
-  {/if}
-{/each}
diff --git a/src/lib/ui/themes/icons/Breadcrumbs.svelte b/src/lib/ui/themes/icons/Breadcrumbs.svelte
index ab9109800..3a6f33f23 100644
--- a/src/lib/ui/themes/icons/Breadcrumbs.svelte
+++ b/src/lib/ui/themes/icons/Breadcrumbs.svelte
@@ -1,19 +1,7 @@
 <script lang="ts">
-  import type { Lo } from "$lib/services/models/lo-types";
   import { currentCourse, currentLo } from "$lib/stores";
   import Icon from "./Icon.svelte";
-
   let truncated = [true, true, true, true, true, true, true];
-  let unitId = "";
-
-  function getUnitId(type: string, id: string) {
-    if (type == "unit" || type == "side") {
-      unitId = id;
-    } else {
-      unitId = "";
-    }
-    return unitId;
-  }
 
   function truncate(input: string) {
     if (input.length > 16) {
@@ -29,12 +17,25 @@
     return input;
   }
 
-  let breadCrumbs: Lo[];
+  interface Crumb {
+    route: string;
+    type: string;
+    title: string;
+  }
+  let breadCrumbs: Crumb[] = [];
+
   currentLo.subscribe((lo) => {
-    breadCrumbs = lo.breadCrumbs;
-    if (breadCrumbs.length > 1) {
+    breadCrumbs = [];
+    lo.breadCrumbs?.forEach((lo) => {
+      let route = lo.route;
+      if (route.endsWith("/")) {
+        route = route.slice(0, -1);
+      }
+      breadCrumbs.push({ route: route, type: lo.type, title: lo.title });
+    });
+    if (breadCrumbs.length > 2) {
       if (breadCrumbs[1].type === "unit" || breadCrumbs[1].type === "side") {
-        breadCrumbs.splice(1, 1);
+        breadCrumbs[1].route = breadCrumbs[1].route.replace("topic", "course");
       }
     }
   });
@@ -57,7 +58,7 @@
           <li class="crumb-separator" aria-hidden>&rsaquo;</li>
         {/if}
         <li class="crumb">
-          <a href="{lo.route}{getUnitId(lo.type, lo.id)}" class="!space-x-[-1rem] lg:!space-x-0 inline-flex !text-black dark:!text-white">
+          <a href={lo.route} class="!space-x-[-1rem] lg:!space-x-0 inline-flex !text-black dark:!text-white">
             <span><Icon type={lo.type} tip={`Go to ${lo.title}`} /></span>
             <!-- svelte-ignore a11y-no-static-element-interactions -->
             <span
diff --git a/src/lib/ui/time/supabase/analytics/calendar.ts b/src/lib/ui/time/supabase/analytics/calendar.ts
index 735b44937..adf2facbf 100644
--- a/src/lib/ui/time/supabase/analytics/calendar.ts
+++ b/src/lib/ui/time/supabase/analytics/calendar.ts
@@ -10,7 +10,7 @@ import { tutorsAnalyticsLogo } from "../charts/personlised-logo";
 import type { CalendarMap } from "$lib/services/types/supabase-metrics";
 import type { Course } from "$lib/services/models/lo-types";
 import type { Session } from "@supabase/supabase-js";
-import { getGithubAvatarUrl, getUser } from "$lib/services/utils/supabase-utils";
+import { getGithubAvatarUrl } from "$lib/services/utils/supabase-utils";
 import { generateStudent } from "../../../../../routes/(time)/simulate/generateStudent";
 
 echarts.use([TitleComponent, CalendarComponent, TooltipComponent, VisualMapComponent, HeatmapChart, CanvasRenderer, GraphicComponent]);
@@ -23,17 +23,21 @@ bgPatternImg.src = backgroundPattern;
 
 export class CalendarChart {
   chartRendered: boolean;
-  myChart: any; 
-  chartDom: any; 
+  myChart: any;
+  chartDom: any;
   myCharts: { [key: string]: any };
   medianCalendarRendered: boolean;
+  userNamesUseridsMap: Map<string, string>;
+  userAvatarsUseridsMap: Map<string, string>;
 
-  constructor() {
+  constructor(userAvatarsUseridsMap: Map<string, string>, userNamesUseridsMap: Map<string, string>) {
     this.chartRendered = false;
     this.myChart = null;
     this.chartDom = null;
     this.myCharts = {};
     this.medianCalendarRendered = false;
+    this.userAvatarsUseridsMap = userAvatarsUseridsMap;
+    this.userNamesUseridsMap = userNamesUseridsMap;
   }
 
   createChartContainer(containerId: string) {
@@ -113,7 +117,7 @@ export class CalendarChart {
     //this.clickMonth();
   }
 
-  async renderCombinedChart(course: Course, calendarMap: Map<string, number>, userId: string) {
+  async renderCombinedChart(calendarMap: Map<string, number>, userId: string) {
     const chartContainer = this.getChartContainer(userId);
 
     if (!chartContainer) {
@@ -122,13 +126,21 @@ export class CalendarChart {
     }
 
     const chart = echarts.init(chartContainer);
-
-    //const student = await generateStudent(); //generate fake student
-    const avatarUrl = await getGithubAvatarUrl(userId)
-    const fullName = await getUser(userId)
+    const fullName = this.userNamesUseridsMap.get(userId) || userId;
+    const avatarUrl = this.userAvatarsUseridsMap.get(userId) || "";
     const option = calendarCombined(userId, calendarMap, bgPatternImg, currentRange, avatarUrl, fullName);
 
     chart.setOption(option, true);
+
+    //const fullname = (await getUser(userId)) || userId; //real
+    //const fullname = (await generateStudent()).fullName; //fake
+
+    //const student = await generateStudent(); //generate fake student
+    // const avatarUrl = await getGithubAvatarUrl(userId);
+    // const fullName = await getUser(userId);
+    // const option = calendarCombined(userId, calendarMap, bgPatternImg, currentRange, avatarUrl, fullName);
+
+    // chart.setOption(option, true);
   }
 
   // New method to render the additional calendar for median timeactive values
diff --git a/src/lib/ui/time/supabase/analytics/heatmap/base-heat-map.ts b/src/lib/ui/time/supabase/analytics/heatmap/base-heat-map.ts
new file mode 100644
index 000000000..59a6ebb69
--- /dev/null
+++ b/src/lib/ui/time/supabase/analytics/heatmap/base-heat-map.ts
@@ -0,0 +1,256 @@
+import * as echarts from "echarts/core";
+import { TooltipComponent, GridComponent, VisualMapComponent } from "echarts/components";
+import { HeatmapChart } from "echarts/charts";
+import { CanvasRenderer } from "echarts/renderers";
+import { backgroundPattern } from "../../charts/tutors-charts-background-url";
+import type { Course, Lo } from "$lib/services/models/lo-types";
+import type { Session } from "@supabase/supabase-js";
+import type { HeatMapSeriesData, HeatMapChartConfig } from "$lib/services/types/supabase-metrics";
+import { heatmap, renderCombinedChart } from "../../charts/heatmap-chart";
+
+echarts.use([TooltipComponent, GridComponent, VisualMapComponent, HeatmapChart, CanvasRenderer]);
+
+const bgPatternImg = new Image();
+bgPatternImg.src = backgroundPattern;
+
+export class BaseHeatMapChart<T> {
+  chartRendered = false;
+  chartInstances: Map<echarts.ECharts, echarts.ECharts>;
+  course: Course;
+  session: Session;
+  userIds: string[];
+  userNamesUseridsMap: Map<string, string>;
+  chartInstance: echarts.ECharts | null = null;
+  categories: Set<string> = new Set();
+  yAxisData: string[] = [];
+  series: HeatMapSeriesData = {
+    top: "",
+    name: "",
+    data: [],
+    type: "heatmap",
+    selectedMode: "single",
+    label: {
+      show: true
+    }
+  };
+  multipleUsers: boolean;
+
+  constructor(course: Course, session: Session, userIds: string[], userNamesUseridsMap: Map<string, string>, multipleUsers: boolean) {
+    this.chartInstances = new Map();
+    this.course = course;
+    this.session = session;
+    this.userIds = userIds;
+    this.userNamesUseridsMap = userNamesUseridsMap;
+    this.multipleUsers = multipleUsers;
+  }
+
+  initChart() {
+    if (!this.chartInstance) {
+      // Create a new chart instance if it doesn't exist
+      this.chartInstance = echarts.init(document.getElementById("chart"));
+    } else {
+      // Clear the previous chart to prevent aggregation issues
+      this.chartInstance.clear();
+    }
+  }
+
+  getChartContainer() {
+    const container = document.getElementById("heatmap-container");
+    return container;
+  }
+
+  getCombinedChartContainer() {
+    const container = document.getElementById("combined-heatmap");
+    return container;
+  }
+
+  async getUserFullName(userId: string) {
+    return this.userNamesUseridsMap.get(userId) || userId;
+  }
+
+  async populatePerUserSeriesData(allItems: Lo[], userId: string, index: number, learninObjValue: string): Promise<number[][]> {
+    const totalTimesMap = new Map<string, number>();
+    const titleList: string[] = [];
+    allItems.forEach((item) => {
+      let title: string = "";
+      if (learninObjValue === "lab") {
+        title = item.parentLo?.type === "lab" ? item.parentLo?.title : item.title;
+      } else {
+        if (item.parentTopic?.type === "topic") {
+          title = item.parentTopic?.title;
+        } else if (item.parentLo?.parentTopic?.type === "topic") {
+          title = item.parentLo?.parentTopic?.title;
+        } else {
+          title = item.title;
+        }
+      }
+
+      const timeActive = item.learningRecords?.get(userId)?.timeActive || 0;
+      // Add timeActive to the total time for the step
+      if (totalTimesMap.has(title)) {
+        totalTimesMap.set(title, totalTimesMap.get(title)! + timeActive);
+        titleList.push(title);
+      } else {
+        totalTimesMap.set(title, timeActive);
+        titleList.push(title.trim());
+      }
+    });
+
+    this.categories = new Set(Array.from(totalTimesMap.keys()));
+    const categoriesArray = Array.from(this.categories);
+
+    // Construct seriesData array using the aggregated total times
+    const seriesData: number[][] = Array.from(totalTimesMap.entries()).map(([title, timeActive], stepIndex) => {
+      //return [titleList.indexOf(title.trim()), index, Math.round(timeActive / 2)];
+      return [categoriesArray.indexOf(title), index, Math.floor(timeActive / 2)];
+    });
+
+    //const userFullName = await getUser(userId) || userId;
+
+    return seriesData;
+  }
+
+  async populateAndRenderUsersData(allItems: Lo[], userIds: string[], learninObjValue: string) {
+    const container = this.getChartContainer();
+    if (!container) return;
+
+    let allSeriesData: number[][] = [];
+    const yAxisData: string[] = [];
+
+    for (const [index, userId] of userIds.entries()) {
+      const seriesData = await this.populatePerUserSeriesData(allItems, userId, index, learninObjValue);
+      allSeriesData = allSeriesData.concat(seriesData);
+      const fullName = await this.getUserFullName(userId);
+      yAxisData.push(fullName);
+    }
+
+    this.series = {
+      name: `lab activity for all users`,
+      type: "heatmap",
+      data: allSeriesData,
+      selectedMode: "single",
+      top: "5%",
+      label: {
+        show: true
+      }
+    };
+
+    this.yAxisData = yAxisData;
+    this.renderChart(container, "");
+  }
+
+  async populateAndRenderSingleUserData(session: Session, allItems: Lo[], learninObjValue: string) {
+    const container = this.getChartContainer();
+    if (!container) return;
+
+    const userId = session.user.user_metadata.full_name ?? session.user.user_metadata.user_name;
+    this.yAxisData = [userId];
+
+    const seriesData: number[][] = await this.populatePerUserSeriesData(allItems, session.user.user_metadata.user_name, 0, learninObjValue.valueOf());
+    this.series = {
+      top: "5%",
+      name: `${learninObjValue.valueOf()} Activity`,
+      type: "heatmap",
+      data: seriesData,
+      selectedMode: "single",
+      label: {
+        show: true
+      }
+    };
+
+    this.renderChart(container, "");
+  }
+
+  renderChart(container: HTMLElement, title: string) {
+    this.chartInstance = echarts.init(container);
+    const option: HeatMapChartConfig = heatmap(this.categories, this.yAxisData, this.series, bgPatternImg, title);
+    this.chartInstance.setOption(option);
+    this.chartInstance.resize();
+    this.sortHeatMapValues();
+  }
+
+  prepareCombinedTopicData(allTypes: Lo[], userIds: string[], getTitle: (lo: Lo) => string) {
+    const loActivities = new Map();
+    const container = this.getCombinedChartContainer();
+    if (!container) return;
+    allTypes.forEach((lo) => {
+      const title = getTitle(lo);
+      if (!loActivities.has(title)) {
+        loActivities.set(title, []);
+      }
+
+      lo.learningRecords?.forEach((topic, userId) => {
+        if (userIds.includes(userId)) {
+          loActivities.get(title).push({
+            timeActive: topic.timeActive,
+            nickname: userId
+          });
+        }
+      });
+    });
+
+    const heatmapData = Array.from(loActivities.entries()).map(([title, activities]) => {
+      activities.sort((a: { timeActive: number }, b: { timeActive: number }) => a.timeActive - b.timeActive);
+
+      const addedCount = activities.reduce((acc: number, curr: { timeActive: any }) => acc + curr.timeActive, 0);
+
+      const lowData = activities[0];
+      const highData = activities[activities.length - 1];
+
+      return {
+        value: addedCount,
+        title: title,
+        lowValue: lowData?.timeActive || 0,
+        highValue: highData?.timeActive || 0,
+        lowNickname: lowData?.nickname || "No Interaction",
+        highNickname: highData?.nickname || "No Interaction"
+      };
+    });
+    this.renderCombinedTopicChart(container, heatmapData, "Aggregated Time");
+  }
+
+  renderCombinedTopicChart(container: HTMLElement, heatmapData: any[], title: string) {
+    const chartInstance = echarts.init(container);
+    const option = renderCombinedChart(heatmapData, bgPatternImg, title);
+    chartInstance.setOption(option);
+    chartInstance.resize();
+    this.sortHeatMapValues();
+  }
+
+  async sortHeatMapValues() {
+    if (this.chartInstance !== null) {
+      this.chartInstance.off("click");
+      this.chartInstance.on("click", async (params: { componentType: string; seriesType: string; value: any[] }) => {
+        if (params.componentType === "series" && params.seriesType === "heatmap") {
+          const colIndex = params.value[0]; // Column index of the clicked cell
+          // Extract the data for the clicked column
+          let columnData = this.series.data.filter((item: any[]) => item[0] === colIndex);
+          // Sort the column data by the value (timeActive) in ascending order
+          columnData.sort((a: number[], b: number[]) => a[2] - b[2]);
+          // Reorder yAxisData based on sorted column data
+          const sortedUserIndices = columnData.map((item: any[]) => item[1]);
+          const sortedYAxisData = sortedUserIndices.map((index: string | number) => this.yAxisData[index]);
+          // Reconstruct the series data with sorted y-axis order
+          let newData = this.series.data.map((item: any[]) => {
+            const newIndex = sortedUserIndices.indexOf(item[1]);
+            return [item[0], newIndex, item[2]];
+          });
+          // Update the y-axis data and series data
+          this.yAxisData = sortedYAxisData;
+          this.series.data = newData;
+          // Refresh the chart instance
+          this.chartInstance?.setOption({
+            yAxis: {
+              data: this.yAxisData
+            },
+            series: [
+              {
+                data: this.series.data
+              }
+            ]
+          });
+        }
+      });
+    }
+  }
+}
diff --git a/src/lib/ui/time/supabase/analytics/heatmap/lab-heat-map-chart.ts b/src/lib/ui/time/supabase/analytics/heatmap/lab-heat-map-chart.ts
new file mode 100644
index 000000000..f32124f34
--- /dev/null
+++ b/src/lib/ui/time/supabase/analytics/heatmap/lab-heat-map-chart.ts
@@ -0,0 +1,29 @@
+import { BaseHeatMapChart } from "./base-heat-map";
+import { filterByType } from "$lib/services/models/lo-utils";
+import type { Course, Lo } from "$lib/services/models/lo-types";
+import type { Session } from "@supabase/supabase-js";
+
+export class LabHeatMapChart extends BaseHeatMapChart<number> {
+  labs: Lo[];
+
+  constructor(course: any, session: Session, userIds: string[], userNamesUseridsMap: Map<string, string>, multipleUsers: boolean) {
+    super(course, session, userIds, userNamesUseridsMap, multipleUsers);
+    let labs = filterByType(course.los, "lab");
+    let steps = filterByType(course.los, "step");
+
+    this.labs = [...labs, ...steps];
+  }
+
+  async populateAndRenderData() {
+    if (this.multipleUsers) {
+      await this.populateAndRenderUsersData(this.labs, this.userIds, "lab");
+      this.prepareCombinedTopicData(this.labs, this.userIds, (lo) => (lo.type === "lab" ? lo.title : lo.parentLo!.title));
+    } else {
+      await this.populateAndRenderSingleUserData(this.session, this.labs, "lab");
+    }
+  }
+
+  renderChart(container: HTMLElement) {
+    super.renderChart(container, "Lab Activity: Per Student (click a cell to sort)");
+  }
+}
diff --git a/src/lib/ui/time/supabase/analytics/heatmap/topic-heat-map-chart.ts b/src/lib/ui/time/supabase/analytics/heatmap/topic-heat-map-chart.ts
new file mode 100644
index 000000000..6d384388a
--- /dev/null
+++ b/src/lib/ui/time/supabase/analytics/heatmap/topic-heat-map-chart.ts
@@ -0,0 +1,28 @@
+import { BaseHeatMapChart } from "./base-heat-map";
+import { getCompositeValues, getSimpleTypesValues } from "$lib/services/utils/supabase-utils";
+import type { Course, Lo } from "$lib/services/models/lo-types";
+import type { Session } from "@supabase/supabase-js";
+
+export class TopicHeatMapChart extends BaseHeatMapChart<number> {
+  topics: Lo[];
+
+  constructor(course: Course, session: Session, userIds: string[], userNamesUseridsMap: Map<string, string>, multipleUsers: boolean) {
+    super(course, session, userIds, userNamesUseridsMap, multipleUsers);
+    this.topics = getCompositeValues(course.los).concat(getSimpleTypesValues(course.los));
+  }
+
+  async populateAndRenderData() {
+    if (this.multipleUsers) {
+      await this.populateAndRenderUsersData(this.topics, this.userIds, "topic");
+      this.prepareCombinedTopicData(this.topics, this.userIds, (lo) =>
+        lo.parentTopic?.type === "topic" ? lo.parentTopic.title : lo.parentLo?.parentTopic?.type === "topic" ? lo.parentLo?.parentTopic?.title : lo.title
+      );
+    } else {
+      await this.populateAndRenderSingleUserData(this.session, this.topics, "topic");
+    }
+  }
+
+  renderChart(container: HTMLElement) {
+    super.renderChart(container, "Topic Activity: Per Student (click a cell to sort)");
+  }
+}
diff --git a/src/lib/ui/time/supabase/analytics/lab-box-plot.ts b/src/lib/ui/time/supabase/analytics/lab-box-plot.ts
index 4d6cbe865..f03a5ca78 100644
--- a/src/lib/ui/time/supabase/analytics/lab-box-plot.ts
+++ b/src/lib/ui/time/supabase/analytics/lab-box-plot.ts
@@ -7,9 +7,9 @@ import { backgroundPattern } from "../charts/tutors-charts-background-url";
 import { boxplot, combinedBoxplotChart } from "../charts/boxplot-chart";
 import type { Course } from "$lib/services/models/lo-types";
 import { filterByType } from "$lib/services/models/lo-utils";
-import type { BoxplotData } from "$lib/services/types/supabase-metrics";
+import type { BoxplotChartConfig, BoxplotData } from "$lib/services/types/supabase-metrics";
 import { generateStudent } from "../../../../../routes/(time)/simulate/generateStudent";
-import { getUser } from "$lib/services/utils/supabase-utils";
+// import { getUser } from "$lib/services/utils/supabase-utils";
 
 echarts.use([TitleComponent, TooltipComponent, GridComponent, BoxplotChart, CanvasRenderer]);
 
@@ -19,10 +19,12 @@ bgPatternImg.src = backgroundPattern;
 export class LabBoxPlotChart {
   course: Course;
   userIds: string[];
+  userNamesUseridsMap: Map<string, string>;
 
-  constructor(course: Course, userIds: string[]) {
+  constructor(course: Course, userIds: string[], userNamesUseridsMap: Map<string, string>) {
     this.course = course;
     this.userIds = userIds;
+    this.userNamesUseridsMap = userNamesUseridsMap;
   }
 
   async getName(): Promise<string> {
@@ -54,7 +56,8 @@ export class LabBoxPlotChart {
     const userActivitiesPromises = Array.from(userActivities.entries()).map(async ([userId, activities]) => {
       if (activities.length > 0) {
         //const fullname = await this.getName(); //generate fake name
-        const fullname = (await getUser(userId)) || userId;
+        //const fullname = (await getUser(userId)) || userId;
+        const fullname = this.userNamesUseridsMap.get(userId) || userId;
         activities.sort((a, b) => a - b);
         const min = d3.min(activities) ?? 0;
         const q1 = d3.quantile(activities, 0.25) ?? 0;
@@ -87,7 +90,8 @@ export class LabBoxPlotChart {
       for (const [userId, labRecord] of lab.learningRecords || []) {
         if (this.userIds?.includes(userId) && labRecord.timeActive != null) {
           //const nickname = await this.getName(); //generate fake names
-          const nickname = (await getUser(userId)) || userId;
+          //const nickname = (await getUser(userId)) || userId;
+          const nickname = this.userNamesUseridsMap.get(userId) || userId;
           labActivities.get(title)?.push({
             timeActive: labRecord.timeActive,
             nickname: nickname
@@ -125,7 +129,7 @@ export class LabBoxPlotChart {
 
         boxplotData.push({
           value: [min, q1, median, q3, max],
-          title: title,
+          name: title,
           lowNickname: lowNickname,
           highNickname: highNickname
         });
@@ -139,7 +143,7 @@ export class LabBoxPlotChart {
     if (!container) return;
 
     const chart = echarts.init(container);
-    const option = boxplot(bgPatternImg, userNicknames, boxplotData, "Lab Activity per Student Boxplot");
+    const option: BoxplotChartConfig = boxplot(bgPatternImg, userNicknames, boxplotData, "Lab Activity per Student Boxplot");
     chart.setOption(option);
   }
 
diff --git a/src/lib/ui/time/supabase/analytics/lab-heat-map.ts b/src/lib/ui/time/supabase/analytics/lab-heat-map.ts
deleted file mode 100644
index 69899fc9e..000000000
--- a/src/lib/ui/time/supabase/analytics/lab-heat-map.ts
+++ /dev/null
@@ -1,358 +0,0 @@
-import * as echarts from "echarts/core";
-import { TooltipComponent, GridComponent, VisualMapComponent } from "echarts/components";
-import { HeatmapChart } from "echarts/charts";
-import { CanvasRenderer } from "echarts/renderers";
-import { backgroundPattern } from "../charts/tutors-charts-background-url";
-import { heatmap } from "../charts/heatmap-chart";
-import type { Course, Lo } from "$lib/services/models/lo-types";
-import type { Session } from "@supabase/supabase-js";
-import { filterByType } from "$lib/services/models/lo-utils";
-import type { HeatMapSeriesData } from "$lib/services/types/supabase-metrics";
-import { getUser } from "$lib/services/utils/supabase-utils";
-import { generateStudent } from "../../../../../routes/(time)/simulate/generateStudent";
-
-echarts.use([TooltipComponent, GridComponent, VisualMapComponent, HeatmapChart, CanvasRenderer]);
-
-const bgPatternImg = new Image();
-bgPatternImg.src = backgroundPattern;
-
-export class LabHeatMapChart {
-  chartRendered: boolean = false;
-  chartInstances: Map<any, any>;
-  labs: Lo[] | undefined;
-  categories: Set<string>;
-  yAxisData: string[];
-  course: Course;
-  session: Session;
-  series: HeatMapSeriesData[];
-  los: Lo[];
-  userIds: string[];
-  chartInstance: any;
-
-  constructor(course: Course, session: Session, userIds: string[]) {
-    this.chartRendered = false;
-    this.chartInstances = new Map();
-    this.labs = course.wallMap?.get("lab"); // Array of lab titles
-    this.userIds = userIds;
-    this.categories = new Set();
-    this.yAxisData = [];
-    this.series = [];
-    this.course = course;
-    this.session = session;
-    this.los = filterByType(this.course.los, "lab");
-    this.chartInstance = null;
-  }
-
-  populateUsersData() {
-    if (this.labs) {
-      this.populateLabTitles(this.labs);
-      this.populateAndRenderUsersData(this.course, this.labs, this.userIds);
-    }
-  }
-
-  async populateSingleUserData() {
-    if (this.labs) {
-      this.populateLabTitles(this.labs);
-      this.populateAndRenderSingleUserData(this.session, this.labs);
-    }
-  }
-
-  populateLabTitles(allLabs: Lo[]) {
-    const labTitles = allLabs.map((lab) => lab.title.trim());
-    this.categories = new Set(labTitles);
-  }
-
-  getChartContainer() {
-    const container = document.getElementById("heatmap-container");
-    if (container) {
-      container.style.width = "100%";
-      container.style.height = "100%";
-    }
-    return container;
-  }
-
-  async populatePerUserSeriesData(course: Course, allLabs: Lo[], userId: string, index: number = 0) {
-    const labTitles = allLabs.map((lab: { title: string }) => lab.title.trim());
-    this.categories = new Set(labTitles);
-
-    let labs = filterByType(course.los, "lab");
-    let steps = filterByType(course.los, "step");
-
-    const allLabSteps = [...labs, ...steps];
-
-    // Map to store total timeActive for each step
-    const totalTimesMap = new Map<string, number>();
-
-    // Iterate over allLabSteps to aggregate total timeActive for each step
-    allLabSteps.forEach((step, stepIndex) => {
-      const title = step.parentLo?.type === "lab" ? step.parentLo?.title : step.title;
-      const timeActive = step.learningRecords?.get(userId)?.timeActive || 0;
-
-      // Add timeActive to the total time for the step
-      if (totalTimesMap.has(title)) {
-        totalTimesMap.set(title, totalTimesMap.get(title)! + timeActive);
-      } else {
-        totalTimesMap.set(title, timeActive);
-      }
-    });
-
-    // Construct seriesData array using the aggregated total times
-    const seriesData = Array.from(totalTimesMap.entries()).map(([title, timeActive], stepIndex) => {
-      return [labTitles.indexOf(title.trim()), index, Math.round(timeActive / 2)];
-    });
-
-    //const userFullName = await getUser(userId) || userId;
-    const userFullName = userId;
-
-    return [
-      {
-        name: "Lab Activity for " + userFullName,
-        type: "heatmap",
-        data: seriesData,
-        label: {
-          show: true
-        }
-      }
-    ];
-  }
-
-  async populateAndRenderUsersData(course: Course, allLabs: Lo[], userIds: string[]) {
-    const container = this.getChartContainer();
-    if (!container) return;
-
-    let allSeriesData: HeatMapSeriesData[] = [];
-    let yAxisData: string[] = []; // Array to store yAxis data
-
-    const labTitles = allLabs.map((lab: { title: string }) => lab.title.trim());
-    this.categories = new Set(labTitles);
-
-    for (const [index, userId] of userIds.entries()) {
-      const seriesData = await this.populatePerUserSeriesData(course, allLabs, userId, index);
-      allSeriesData = allSeriesData.concat(seriesData[0].data);
-
-      if (!yAxisData.includes(userId)) {
-        const fullname = await getUser(userId) || userId; //real name
-        //const fullname = (await generateStudent()).fullName; //populate with generated names
-        yAxisData.push(fullname);
-      }
-    }
-
-    this.series = [
-      {
-        name: "Lab Activity For All Users",
-        type: "heatmap",
-        data: allSeriesData || [],
-        label: {
-          show: true
-        }
-      }
-    ];
-
-    this.yAxisData = yAxisData;
-    this.renderChart(container);
-  }
-
-  async populateAndRenderSingleUserData(session: Session, allLabs: Lo[]) {
-    const container = this.getChartContainer();
-    if (!container) return;
-
-    const userId = session.user.user_metadata.full_name ?? session.user.user_metadata.user_name;
-    this.yAxisData = [userId];
-
-    const seriesData = await this.populatePerUserSeriesData(this.course, allLabs, session.user.user_metadata.user_name);
-    this.series = [
-      {
-        top: "5%",
-        name: "Lab Activity",
-        type: "heatmap",
-        data: seriesData[0]?.data || [],
-        label: {
-          show: true
-        }
-      }
-    ];
-
-    this.renderChart(container);
-  }
-
-  renderChart(container: HTMLElement) {
-    this.chartInstance = echarts.init(container);
-    const option = heatmap(this.categories, this.yAxisData, this.series, bgPatternImg, "Lab Time: Per Student (click a cell to sort)");
-    this.chartInstance.setOption(option);
-    this.chartInstance.resize();
-  }
-
-  prepareCombinedLabData(userIds: string[]) {
-    const labActivities = new Map();
-    const labs = filterByType(this.course.los, "lab");
-    const steps = filterByType(this.course.los, "step");
-
-    const allLabSteps = [...labs, ...steps];
-
-    allLabSteps?.forEach((step) => {
-      if (step.learningRecords) {
-        const title = step.parentLo?.type === "lab" ? step.parentLo?.title : step.title;
-        if (!labActivities.has(title)) {
-          labActivities.set(title, []);
-        }
-
-        step.learningRecords.forEach((lab, key) => {
-          if (userIds.includes(key)) {
-            // Push the activity to the corresponding title in labActivities
-            labActivities.get(title).push({
-              timeActive: lab.timeActive,
-              nickname: key
-            });
-          }
-        });
-      }
-    });
-
-    const heatmapData = Array.from(labActivities).map(([title, activities]) => {
-      activities.sort((a: { timeActive: number }, b: { timeActive: number }) => a.timeActive - b.timeActive);
-      const addedCount = activities.reduce((acc: number, curr: { timeActive: number }) => acc + curr.timeActive, 0);
-
-      const lowData = activities[0];
-      const highData = activities[activities.length - 1];
-      return {
-        value: Math.round(addedCount / 2),
-        title: title,
-        lowValue: lowData?.timeActive || 0,
-        highValue: highData?.timeActive || 0,
-        lowNickname: lowData?.nickname || "No Interaction",
-        highNickname: highData?.nickname || "No Interaction"
-      };
-    });
-
-    return heatmapData;
-  }
-
-  async sortHeatMapValues() {
-    if (this.chartInstance !== null) {
-      this.chartInstance.off("click");
-      this.chartInstance.on("click", async (params: { componentType: string; seriesType: string; value: any[] }) => {
-        if (params.componentType === "series" && params.seriesType === "heatmap") {
-          const colIndex = params.value[0]; // Column index of the clicked cell
-          // Extract the data for the clicked column
-          let columnData = this.series[0].data.filter((item: any[]) => item[0] === colIndex);
-          // Sort the column data by the value (timeActive) in ascending order
-          columnData.sort((a: number[], b: number[]) => a[2] - b[2]);
-          // Reorder yAxisData based on sorted column data
-          const sortedUserIndices = columnData.map((item: any[]) => item[1]);
-          const sortedYAxisData = sortedUserIndices.map((index: string | number) => this.yAxisData[index]);
-          // Reconstruct the series data with sorted y-axis order
-          let newData = this.series[0].data.map((item: any[]) => {
-            const newIndex = sortedUserIndices.indexOf(item[1]);
-            return [item[0], newIndex, item[2]];
-          });
-          // Update the y-axis data and series data
-          this.yAxisData = sortedYAxisData;
-          this.series[0].data = newData;
-          // Refresh the chart instance
-          this.chartInstance.setOption({
-            yAxis: {
-              data: this.yAxisData
-            },
-            series: [
-              {
-                data: this.series[0].data
-              }
-            ]
-          });
-        }
-      });
-    }
-  }
-
-  renderCombinedLabChart(container: HTMLElement, labData: any[], chartTitle: string) {
-    if (!labData || labData.length === 0) return;
-
-    const chart = echarts.init(container);
-
-    labData.sort((a, b) => a.title.localeCompare(b.title));
-
-    const heatmapData = labData.map((item, index) => [index, 0, item.value]);
-    const titles = labData.map((item) => item.title);
-
-    // Ensure heatmapData and titles are not empty
-    const maxHeatmapValue = heatmapData.length > 0 ? Math.max(...heatmapData.map((item) => item[2])) : 0;
-
-    const option = {
-      title: {
-        top: "5%",
-        left: "center",
-        text: chartTitle
-      },
-      tooltip: {
-        position: "bottom",
-        formatter: function (params: { dataIndex: any }) {
-          const dataIndex = params.dataIndex;
-          const dataItem = labData[dataIndex];
-          let tipHtml = dataItem.title + "<br />";
-          tipHtml += "Min: " + dataItem.lowValue + " (" + dataItem.lowNickname + ")<br />";
-          tipHtml += "Max: " + dataItem.highValue + " (" + dataItem.highNickname + ")";
-          return tipHtml;
-        }
-      },
-      backgroundColor: {
-        image: bgPatternImg,
-        repeat: "repeat"
-      },
-      grid: {
-        height: "20%",
-        top: "15%"
-      },
-      xAxis: {
-        type: "category",
-        data: titles,
-        splitArea: {
-          show: true
-        },
-        axisLabel: {
-          interval: 1,
-          fontSize: 15
-        },
-        axisPointer: {
-          type: "shadow"
-        },
-        position: "bottom"
-      },
-      yAxis: {
-        type: "category",
-        data: [""], // Single category axis
-        axisLabel: {
-          interval: 0,
-          fontSize: 15
-        }
-      },
-      visualMap: {
-        min: 0,
-        max: maxHeatmapValue, // Ensure this handles empty data gracefully
-        calculable: true,
-        orient: "horizontal",
-        left: "center",
-        bottom: "5%"
-      },
-      series: [
-        {
-          name: "Value",
-          type: "heatmap",
-          data: heatmapData,
-          label: {
-            show: true
-          },
-          emphasis: {
-            itemStyle: {
-              shadowBlur: 10,
-              shadowColor: "rgba(0, 0, 0, 0.5)"
-            }
-          }
-        }
-      ]
-    };
-
-    // Set the option to the chart
-    chart.setOption(option);
-    this.sortHeatMapValues();
-  }
-}
diff --git a/src/lib/ui/time/supabase/analytics/lab-pie.ts b/src/lib/ui/time/supabase/analytics/lab-pie.ts
deleted file mode 100644
index f161972d4..000000000
--- a/src/lib/ui/time/supabase/analytics/lab-pie.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import * as echarts from "echarts/core";
-import { TooltipComponent, LegendComponent, GridComponent } from "echarts/components";
-import { CanvasRenderer } from "echarts/renderers";
-import { PieChart, BarChart } from "echarts/charts";
-import { LabelLayout } from "echarts/features";
-import type { Course, Lo } from "$lib/services/models/lo-types";
-import { backgroundPattern, textureBackground } from "../charts/tutors-charts-background-url";
-import type { Session } from "@supabase/supabase-js";
-import { filterByType } from "$lib/services/models/lo-utils";
-import type { EChartsOption } from "echarts";
-import { piechart } from "../charts/piechart";
-import type { LabStepData, OuterPieData } from "$lib/services/types/supabase-metrics";
-
-echarts.use([TooltipComponent, LegendComponent, PieChart, BarChart, GridComponent, CanvasRenderer, LabelLayout]);
-
-let option: EChartsOption;
-
-const bgTexture = textureBackground;
-const bgPatternSrc = backgroundPattern;
-
-const piePatternImg = new Image();
-piePatternImg.src = bgTexture;
-const bgPatternImg = new Image();
-bgPatternImg.src = bgPatternSrc;
-
-export class LabPieChart {
-  myChart: any;
-  labs: string[];
-  course: Course;
-  session: Session;
-  totalTimesMap: Map<string, LabStepData[]>;
-  labTitleTimesMap: Map<string, number>;
-
-  constructor(course: Course, session: Session) {
-    this.myChart = null;
-    this.labs = [];
-    this.course = course;
-    this.session = session;
-    this.labTitleTimesMap = new Map();
-    this.totalTimesMap = new Map();
-  }
-
-  singleUserPieClick() {
-    if (this.myChart !== null) {
-      // Remove any existing click listeners to prevent multiple handlers
-      this.myChart.off("click");
-      this.myChart.on("click", (params: { seriesName: string; name: string }) => {
-        if (params.seriesName === "Inner Pie") {
-          const outerPieData: OuterPieData[] = []; // Reset outerPieData array
-
-          // Find the corresponding data for the clicked inner pie slice
-          this.totalTimesMap.forEach((steps, topicTitle) => {
-            if (topicTitle === params.name) {
-              steps.forEach((step) => {
-                if (step.aggregatedTimeActive !== 0) {
-                  outerPieData.push({ value: step.aggregatedTimeActive, name: step.title, type: step.loType });
-                }
-              });
-            }
-          });
-          this.populateOuterPieData(outerPieData);
-        }
-      });
-    }
-  }
-
-  populateOuterPieData(outerPieData: OuterPieData[]) {
-    // Update the data for the outer pie chart
-    const chartInstance = echarts.getInstanceByDom(document.getElementById("chart"));
-    if (chartInstance) {
-      chartInstance.setOption({
-        series: [
-          {
-            name: "Outer Pie",
-            data: outerPieData
-          }
-        ]
-      });
-    }
-  }
-
-  renderChart() {
-    if (!this.myChart) {
-      // Create a new chart instance if it doesn't exist
-      this.myChart = echarts.init(document.getElementById("chart"));
-    } else {
-      // Clear the previous chart to prevent aggregation issues
-      this.myChart.clear();
-    }
-
-    this.labTitleTimesMap.clear();
-    this.totalTimesMap.clear();
-
-    let labs = filterByType(this.course.los, "lab");
-    let steps = filterByType(this.course.los, "step");
-
-    const allLabSteps = [...labs, ...steps];
-
-    const updateMaps = (lo: Lo, timeActive: number) => {
-      let topicTitle = lo.type === "lab" ? lo.title : lo.parentLo!.title;
-      let loTitle = lo.title;
-
-      // Add timeActive to the total time for the topic
-      if (this.labTitleTimesMap.has(topicTitle)) {
-        this.labTitleTimesMap.set(topicTitle, this.labTitleTimesMap.get(topicTitle)! + timeActive);
-      } else {
-        this.labTitleTimesMap.set(topicTitle, timeActive);
-      }
-
-      if (!this.totalTimesMap.has(topicTitle)) {
-        this.totalTimesMap.set(topicTitle, []);
-      }
-
-      const existingEntries = this.totalTimesMap.get(topicTitle)!;
-      const existingEntry = existingEntries.find((entry) => entry.title === loTitle);
-
-      if (existingEntry) {
-        existingEntry.aggregatedTimeActive += timeActive;
-      } else {
-        existingEntries.push({ aggregatedTimeActive: timeActive, title: loTitle, loType: lo.type });
-      }
-    };
-
-    allLabSteps.forEach((lo) => {
-      const timeActive = lo.learningRecords?.get(this.session.user.user_metadata.user_name)?.timeActive || 0;
-      updateMaps(lo, timeActive);
-    });
-
-    const singleUserInnerData = Array.from(this.labTitleTimesMap.entries()).map(([title, timeActive]) => ({
-      name: title,
-      value: timeActive
-    }));
-
-    const singleUserOuterData: OuterPieData[] = [];
-    this.totalTimesMap.forEach((steps, topicTitle) => {
-      steps.forEach((step) => {
-        singleUserOuterData.push({
-          name: step.title,
-          value: step.aggregatedTimeActive,
-          type: step.loType
-        });
-      });
-    });
-
-    const option = piechart(bgPatternImg, this.course, [], singleUserInnerData, singleUserOuterData);
-    this.myChart.setOption(option);
-    this.singleUserPieClick();
-  }
-}
diff --git a/src/lib/ui/time/supabase/analytics/piechart/base-pie-chart.ts b/src/lib/ui/time/supabase/analytics/piechart/base-pie-chart.ts
new file mode 100644
index 000000000..3e6fa4e58
--- /dev/null
+++ b/src/lib/ui/time/supabase/analytics/piechart/base-pie-chart.ts
@@ -0,0 +1,123 @@
+import * as echarts from "echarts/core";
+import { TooltipComponent, LegendComponent, GridComponent } from "echarts/components";
+import { CanvasRenderer } from "echarts/renderers";
+import { PieChart, BarChart } from "echarts/charts";
+import { LabelLayout } from "echarts/features";
+import type { Course, Lo } from "$lib/services/models/lo-types";
+import { backgroundPattern, textureBackground } from "../../charts/tutors-charts-background-url";
+import type { Session } from "@supabase/supabase-js";
+import type { DrilledDownData } from "$lib/services/types/supabase-metrics";
+
+echarts.use([TooltipComponent, LegendComponent, PieChart, BarChart, GridComponent, CanvasRenderer, LabelLayout]);
+
+const bgTexture = textureBackground;
+const bgPatternSrc = backgroundPattern;
+
+const piePatternImg = new Image();
+piePatternImg.src = bgTexture;
+const bgPatternImg = new Image();
+bgPatternImg.src = bgPatternSrc;
+
+export class BasePieChart<T> {
+  myChart: any;
+  course: Course;
+  session: Session;
+  totalTimesMap: Map<string, DrilledDownData[]>;
+  titleTimesMap: Map<string, T>;
+
+  constructor(course: Course, session: Session) {
+    this.myChart = null;
+    this.course = course;
+    this.session = session;
+    this.titleTimesMap = new Map();
+    this.totalTimesMap = new Map();
+  }
+
+  initChart() {
+    if (!this.myChart) {
+      // Create a new chart instance if it doesn't exist
+      this.myChart = echarts.init(document.getElementById("chart"));
+    } else {
+      // Clear the previous chart to prevent aggregation issues
+      this.myChart.clear();
+    }
+  }
+
+  handlePieClick() {
+    if (this.myChart !== null) {
+      // Remove any existing click listeners to prevent multiple handlers
+      this.myChart.off("click");
+      this.myChart.on("click", (params: { seriesName: string; name: string }) => {
+        if (params.seriesName === "Inner Pie") {
+          const outerPieData: DrilledDownData[] = []; // Reset outerPieData array
+
+          // Find the corresponding data for the clicked inner pie slice
+          this.totalTimesMap.forEach((steps, title) => {
+            if (title === params.name) {
+              steps.forEach((step) => {
+                if (step.value !== 0) {
+                  outerPieData.push({ value: Math.round(step.value / 2), name: step.name, type: step.type });
+                }
+              });
+            }
+          });
+          this.populateOuterPieData(outerPieData);
+        }
+      });
+    }
+  }
+
+  populateOuterPieData(outerPieData: DrilledDownData[]) {
+    // Update the data for the outer pie chart
+    const element = document.getElementById("chart");
+    if (element) {
+      const chartInstance = echarts.getInstanceByDom(element);
+      if (chartInstance) {
+        chartInstance.setOption({
+          series: [
+            {
+              name: "Outer Pie",
+              data: outerPieData
+            }
+          ]
+        });
+      }
+    }
+  }
+
+  updateMaps(lo: Lo, timeActive: number, getTitle: (lo: Lo) => string) {
+    const title = getTitle(lo);
+    const loTitle = lo.title;
+    // Add timeActive to the total time for the title
+    if (this.titleTimesMap.has(title)) {
+      // casting as unknow increses type safety and then casts as T which is number in this case
+      this.titleTimesMap.set(title, ((this.titleTimesMap.get(title)! as number) + timeActive) as unknown as T);
+    } else {
+      this.titleTimesMap.set(title, timeActive as unknown as T);
+    }
+
+    if (!this.totalTimesMap.has(title)) {
+      this.totalTimesMap.set(title, []);
+    }
+
+    const existingEntries = this.totalTimesMap.get(title)!;
+    const existingEntry = existingEntries.find((entry) => entry.name === loTitle);
+
+    if (existingEntry) {
+      existingEntry.value += timeActive;
+    } else {
+      existingEntries.push({ value: timeActive, name: loTitle, type: lo.type });
+    }
+  }
+
+  renderChart() {
+    this.initChart();
+    this.handlePieClick();
+  }
+
+  setOption(option: any) {
+    if (this.myChart) {
+      this.myChart.setOption(option);
+    }
+  }
+}
diff --git a/src/lib/ui/time/supabase/analytics/piechart/lab-pie-chart.ts b/src/lib/ui/time/supabase/analytics/piechart/lab-pie-chart.ts
new file mode 100644
index 000000000..021005df1
--- /dev/null
+++ b/src/lib/ui/time/supabase/analytics/piechart/lab-pie-chart.ts
@@ -0,0 +1,68 @@
+import * as echarts from "echarts/core";
+import { TooltipComponent, LegendComponent, GridComponent } from "echarts/components";
+import { CanvasRenderer } from "echarts/renderers";
+import { PieChart, BarChart } from "echarts/charts";
+import { LabelLayout } from "echarts/features";
+import type { Course } from "$lib/services/models/lo-types";
+import { backgroundPattern, textureBackground } from "../../charts/tutors-charts-background-url";
+import type { Session } from "@supabase/supabase-js";
+import { filterByType } from "$lib/services/models/lo-utils";
+import { piechart } from "../../charts/piechart";
+import type { DrilledDownData } from "$lib/services/types/supabase-metrics";
+import { BasePieChart } from "./base-pie-chart";
+echarts.use([TooltipComponent, LegendComponent, PieChart, BarChart, GridComponent, CanvasRenderer, LabelLayout]);
+
+const bgTexture = textureBackground;
+const bgPatternSrc = backgroundPattern;
+
+const piePatternImg = new Image();
+piePatternImg.src = bgTexture;
+const bgPatternImg = new Image();
+bgPatternImg.src = bgPatternSrc;
+
+export class LabPieChart extends BasePieChart<number> {
+  labs: string[];
+
+  constructor(course: Course, session: Session) {
+    super(course, session);
+    this.labs = [];
+  }
+
+  renderChart() {
+    super.renderChart(); // Initialise and set up click handlers
+
+    this.titleTimesMap.clear();
+    this.totalTimesMap.clear();
+
+    let labs = filterByType(this.course.los, "lab");
+    let steps = filterByType(this.course.los, "step");
+
+    const allLabSteps = [...labs, ...steps];
+
+    allLabSteps.forEach((lo) => {
+      const timeActive = lo.learningRecords?.get(this.session.user.user_metadata.user_name)?.timeActive || 0;
+      this.updateMaps(lo, timeActive, (lo) => (lo.type === "lab" ? lo.title : lo.parentLo!.title));
+    });
+
+    const singleUserInnerData = Array.from(this.titleTimesMap.entries()).map(([title, timeActive]) => ({
+      name: title,
+      value: Math.round(timeActive / 2)
+    }));
+
+    const singleUserOuterData: DrilledDownData[] = [];
+    this.totalTimesMap.forEach((steps, title) => {
+      steps.forEach((step) => {
+        if (step.type !== undefined) {
+          singleUserOuterData.push({
+            name: step.name,
+            value: Math.round(step.value / 2),
+            type: step.type
+          });
+        }
+      });
+    });
+
+    const option = piechart(bgPatternImg, [], singleUserInnerData, singleUserOuterData);
+    super.setOption(option);
+  }
+}
diff --git a/src/lib/ui/time/supabase/analytics/piechart/topic-pie-chart.ts b/src/lib/ui/time/supabase/analytics/piechart/topic-pie-chart.ts
new file mode 100644
index 000000000..b3db0d506
--- /dev/null
+++ b/src/lib/ui/time/supabase/analytics/piechart/topic-pie-chart.ts
@@ -0,0 +1,88 @@
+import * as echarts from "echarts/core";
+import { TooltipComponent, LegendComponent, GridComponent } from "echarts/components";
+import { CanvasRenderer } from "echarts/renderers";
+import { PieChart, BarChart } from "echarts/charts";
+import { LabelLayout } from "echarts/features";
+import type { Course, Lo } from "$lib/services/models/lo-types";
+import { backgroundPattern, textureBackground } from "../../charts/tutors-charts-background-url";
+import type { Session } from "@supabase/supabase-js";
+import type { DrilledDownData } from "$lib/services/types/supabase-metrics";
+import { BasePieChart } from "./base-pie-chart";
+import { piechart } from "../../charts/piechart";
+import { getCompositeValues, getSimpleTypesValues } from "$lib/services/utils/supabase-utils";
+
+echarts.use([TooltipComponent, LegendComponent, PieChart, BarChart, GridComponent, CanvasRenderer, LabelLayout]);
+
+const bgTexture = textureBackground;
+const bgPatternSrc = backgroundPattern;
+
+const piePatternImg = new Image();
+piePatternImg.src = bgTexture;
+const bgPatternImg = new Image();
+bgPatternImg.src = bgPatternSrc;
+
+export class TopicPieChart extends BasePieChart<number> {
+  topics: string[];
+  userIds: string[];
+  multipleUsers: boolean;
+
+  constructor(course: Course, session: Session, userIds: string[], multipleUsers: boolean) {
+    super(course, session);
+    this.topics = [];
+    this.userIds = userIds;
+    this.multipleUsers = multipleUsers;
+  }
+
+  getOuterPieDataForMultipleUsers(): DrilledDownData[] {
+    const outerPieData: { value: number; name: string }[] = [];
+    this.titleTimesMap.forEach((value, key) => {
+      const existing = outerPieData.find((data) => data.name === key);
+      if (existing) {
+        existing.value += value;
+      } else {
+        value = Math.round(value / 2);
+        outerPieData.push({ value, name: key });
+      }
+    });
+
+    return outerPieData;
+  }
+
+  renderChart() {
+    super.renderChart(); // Initialize and set up click handlers
+
+    const allComposites = getCompositeValues(this.course.los);
+    const allSimpleTypes = getSimpleTypesValues(this.course.los);
+    const allTypes = [...allComposites, ...allSimpleTypes];
+
+    allTypes.forEach((lo) => {
+      if (this.multipleUsers) {
+        this.userIds.forEach((userId) => {
+          const timeActive = lo.learningRecords?.get(userId)?.timeActive || 0;
+          this.updateMaps(lo, timeActive, (lo) =>
+            lo.parentTopic?.type === "topic" ? lo.parentTopic.title : lo.parentLo?.parentTopic?.type === "topic" ? lo.parentLo?.parentTopic?.title : lo.title
+          );
+        });
+      } else {
+        const timeActive = lo.learningRecords?.get(this.session.user.user_metadata.user_name)?.timeActive || 0;
+        this.updateMaps(lo, timeActive, (lo) =>
+          lo.parentTopic?.type === "topic" ? lo.parentTopic.title : lo.parentLo?.parentTopic?.type === "topic" ? lo.parentLo?.parentTopic?.title : lo.title
+        );
+      }
+    });
+
+    if (this.multipleUsers === false) {
+      const singleUserInnerData = Array.from(this.titleTimesMap.entries()).map(([title, timeActive]) => ({
+        name: title,
+        value: Math.round(timeActive / 2)
+      }));
+
+      const option = piechart(bgPatternImg, [], singleUserInnerData, []);
+      super.setOption(option);
+    } else {
+      const allUsersTopicActivity = this.getOuterPieDataForMultipleUsers();
+      const option = piechart(bgPatternImg, allUsersTopicActivity, [], []);
+      super.setOption(option);
+    }
+  }
+}
diff --git a/src/lib/ui/time/supabase/analytics/topic-box-plot.ts b/src/lib/ui/time/supabase/analytics/topic-box-plot.ts
index 06a5d541b..3f03c0cb6 100644
--- a/src/lib/ui/time/supabase/analytics/topic-box-plot.ts
+++ b/src/lib/ui/time/supabase/analytics/topic-box-plot.ts
@@ -5,7 +5,7 @@ import { CanvasRenderer } from "echarts/renderers";
 import { backgroundPattern } from "../charts/tutors-charts-background-url";
 import { boxplot, combinedBoxplotChart } from "../charts/boxplot-chart";
 import type { Course } from "$lib/services/models/lo-types";
-import { getCompositeValues, getSimpleTypesValues, getUser } from "$lib/services/utils/supabase-utils";
+import { getCompositeValues, getSimpleTypesValues } from "$lib/services/utils/supabase-utils";
 import type { BoxplotData } from "$lib/services/types/supabase-metrics";
 import * as d3 from "d3";
 import { generateStudent } from "../../../../../routes/(time)/simulate/generateStudent";
@@ -35,10 +35,12 @@ function calculateBoxplotStats(values: number[]): [number, number, number, numbe
 export class TopicBoxPlotChart {
   course: Course;
   userIds: string[];
+  userNamesUseridsMap: Map<string, string>;
 
-  constructor(course: Course, userIds: string[]) {
+  constructor(course: Course, userIds: string[], userNamesUseridsMap: Map<string, string>) {
     this.course = course;
     this.userIds = userIds;
+    this.userNamesUseridsMap = userNamesUseridsMap;
   }
 
   async getName(): Promise<string> {
@@ -67,7 +69,9 @@ export class TopicBoxPlotChart {
     const userActivitiesPromises = Array.from(userActivities.entries()).map(async ([userId, activities]) => {
       if (activities.length > 0) {
         //const fullname = await this.getName(); //generate fakenames
-        const fullname = (await getUser(userId)) || userId;
+        //const fullname = (await getUser(userId)) || userId;
+        const fullname = this.userNamesUseridsMap.get(userId) || userId;
+
         const [min, q1, median, q3, max] = calculateBoxplotStats(activities);
         boxplotData.push([min, q1, median, q3, max]);
         userNicknames.push(fullname); // replace with userId when changing back
@@ -103,7 +107,9 @@ export class TopicBoxPlotChart {
         const recordsPromises = Array.from(lo.learningRecords.entries()).map(async ([userId, record]) => {
           if (this.userIds.includes(userId)) {
             //const nickname = await this.getName(); //generate a fake name
-            const nickname = (await getUser(userId)) || userId;
+            //const nickname = (await getUser(userId)) || userId;
+            const nickname = this.userNamesUseridsMap.get(userId) || userId;
+
             topicActivities.get(title)!.push({
               timeActive: record.timeActive,
               nickname: nickname // change to userId when changing back
@@ -126,7 +132,7 @@ export class TopicBoxPlotChart {
 
       return {
         value: [min, q1, median, q3, max],
-        title: title,
+        name: title,
         lowNickname: lowNickname,
         highNickname: highNickname
       };
diff --git a/src/lib/ui/time/supabase/analytics/topic-heat-map.ts b/src/lib/ui/time/supabase/analytics/topic-heat-map.ts
deleted file mode 100644
index 8fd4c89b3..000000000
--- a/src/lib/ui/time/supabase/analytics/topic-heat-map.ts
+++ /dev/null
@@ -1,348 +0,0 @@
-import * as echarts from "echarts/core";
-import { TooltipComponent, GridComponent, VisualMapComponent } from "echarts/components";
-import { HeatmapChart } from "echarts/charts";
-import { CanvasRenderer } from "echarts/renderers";
-import { backgroundPattern } from "../charts/tutors-charts-background-url";
-import { heatmap } from "../charts/heatmap-chart";
-import type { Course, Lo, Topic } from "$lib/services/models/lo-types";
-import type { Session } from "@supabase/supabase-js";
-import type { HeatMapSeriesData } from "$lib/services/types/supabase-metrics";
-import { getCompositeValues, getSimpleTypesValues, getUser } from "$lib/services/utils/supabase-utils";
-import { generateStudent } from "../../../../../routes/(time)/simulate/generateStudent";
-
-echarts.use([TooltipComponent, GridComponent, VisualMapComponent, HeatmapChart, CanvasRenderer]);
-
-const bgPatternImg = new Image();
-bgPatternImg.src = backgroundPattern;
-
-export class TopicHeatMapChart {
-  chartRendered: boolean = false;
-  chartInstances: Map<any, any>;
-  course: Course;
-  categories: Set<string>;
-  yAxisData: string[];
-  series: any[];
-  topics: string[];
-  session: Session;
-  userIds: string[];
-  chart: any;
-  chartInstance: any;
-
-  constructor(course: Course, session: Session, userIds: string[]) {
-    this.chartRendered = false;
-    this.chartInstances = new Map();
-    this.topics = course.los.map((topic) => topic.title.trim());
-    this.course = course;
-    this.categories = new Set();
-    this.yAxisData = [];
-    this.series = [];
-    this.session = session;
-    this.userIds = userIds;
-    this.chart = null;
-    this.chartInstance = null;
-  }
-
-  populateUsersData() {
-    this.populateTopicTitles(this.course.los);
-    this.populateAndRenderUsersData(this.course.los, this.userIds);
-  }
-
-  populateSingleUserData() {
-    this.populateTopicTitles(this.course.los);
-    this.populateAndRenderSingleUserData();
-  }
-
-  populateTopicTitles(allTopics: Lo[]) {
-    const topicTitles = allTopics.map((topic) => topic.title.trim());
-    this.categories = new Set(topicTitles);
-  }
-
-  getChartContainer() {
-    const container = document.getElementById("heatmap-container");
-    if (container) {
-      container.style.width = "100%";
-      container.style.height = "100%";
-    }
-    return container;
-  }
-
-  async prepareTopicData(userId: string, index: number = 0) {
-    const allComposites = getCompositeValues(this.course.los);
-    const allSimpleTypes = getSimpleTypesValues(this.course.los);
-    const allTypes = [...allComposites, ...allSimpleTypes];
-
-    const totalTimesMap = new Map<string, number>();
-
-    allTypes.forEach((lo) => {
-      let title: string = "";
-      if (lo.parentTopic?.type === "topic") {
-        title = lo.parentTopic?.title;
-      } else if (lo.parentLo?.parentTopic?.type === "topic") {
-        title = lo.parentLo?.parentTopic?.title;
-      } else {
-        title = lo.title;
-      }
-      const timeActive = lo.learningRecords?.get(userId)?.timeActive || 0;
-
-      if (totalTimesMap.has(title)) {
-        totalTimesMap.set(title, totalTimesMap.get(title)! + timeActive);
-      } else {
-        totalTimesMap.set(title, timeActive);
-      }
-    });
-
-    const topicTitles = this.course.los.map((topic) => topic.title.trim());
-
-    let seriesData = Array.from(totalTimesMap.entries()).map(([title, timeActive]) => {
-      return [topicTitles.indexOf(title.trim()), index, Math.round(timeActive / 2)];
-    });
-
-    seriesData.sort((a, b) => b[2] - a[2]);
-
-    //const userFullName = await getUser(userId) || userId;
-    const userFullName = userId;
-    return [
-      {
-        name: "Topic Activity for " + userFullName,
-        type: "heatmap",
-        data: seriesData,
-        label: {
-          show: true
-        }
-      }
-    ];
-  }
-
-  async populateAndRenderSingleUserData() {
-    const container = this.getChartContainer();
-    if (!container) return;
-    const userId = this.session.user.user_metadata.full_name ?? this.session.user.user_metadata.user_name;
-
-    this.yAxisData = [userId];
-
-    const seriesData = await this.prepareTopicData(this.session.user.user_metadata.user_name);
-
-    this.series = [
-      {
-        name: "Topic Activity",
-        type: "heatmap",
-        data: seriesData[0]?.data || [],
-        selectedMode: "single",
-        top: "5%",
-        label: {
-          show: true
-        }
-      }
-    ];
-
-    this.renderChart(container);
-  }
-
-  async populateAndRenderUsersData(topics: Lo[], usersIds: string[]) {
-    const container = this.getChartContainer();
-    if (!container) return;
-
-    let allSeriesData: HeatMapSeriesData[] = [];
-    let yAxisData: string[] = [];
-    const topicTitles = topics.map((topic: { title: string }) => topic.title.trim());
-    this.categories = new Set(topicTitles);
-
-    for (const [index, userId] of usersIds.entries()) {
-      const seriesData = await this.prepareTopicData(userId, index);
-      allSeriesData = allSeriesData.concat(seriesData[0].data);
-
-      if (!yAxisData.includes(userId)) {
-        // const fullname = await getUser(userId) || userId; //real
-        const fullname = (await generateStudent()).fullName; //fake
-        yAxisData.push(fullname);
-      }
-    }
-
-    allSeriesData.sort((a, b) => b[2] - a[2]);
-
-    this.series = [
-      {
-        name: "Topic Activity For All Users",
-        type: "heatmap",
-        data: allSeriesData || [],
-        selectedMode: "single",
-        label: {
-          show: true
-        }
-      }
-    ];
-
-    this.yAxisData = yAxisData;
-    this.renderChart(container);
-  }
-
-  renderChart(container: HTMLElement | null | undefined) {
-    this.chartInstance = echarts.init(container);
-    const option = heatmap(this.categories, this.yAxisData, this.series, bgPatternImg, "Topic Time: Per Student (click a cell to sort)");
-    this.chartInstance.setOption(option);
-  }
-
-  prepareCombinedTopicData(userIds: string[]) {
-    const topicActivities = new Map();
-    const allComposites = getCompositeValues(this.course.los);
-    const allSimpleTypes = getSimpleTypesValues(this.course.los);
-    const allTypes = [...allComposites, ...allSimpleTypes];
-
-    allTypes.forEach((lo) => {
-      let title: string = "";
-      if (lo.parentTopic?.type === "topic") {
-        title = lo.parentTopic?.title;
-      } else if (lo.parentLo?.parentTopic?.type === "topic") {
-        title = lo.parentLo?.parentTopic?.title;
-      } else {
-        title = lo.title;
-      }
-
-      if (!topicActivities.has(title)) {
-        topicActivities.set(title, []);
-      }
-
-      lo.learningRecords?.forEach((topic, userId) => {
-        if (userIds.includes(userId)) {
-          topicActivities.get(title).push({
-            timeActive: topic.timeActive,
-            nickname: userId
-          });
-        }
-      });
-    });
-
-    const heatmapData = Array.from(topicActivities.entries()).map(([title, activities]) => {
-      activities.sort((a: { timeActive: number }, b: { timeActive: number }) => a.timeActive - b.timeActive);
-
-      const addedCount = activities.reduce((acc: number, curr: { timeActive: any }) => acc + curr.timeActive, 0);
-
-      const lowData = activities[0];
-      const highData = activities[activities.length - 1];
-
-      return {
-        value: addedCount,
-        title: title,
-        lowValue: lowData?.timeActive || 0,
-        highValue: highData?.timeActive || 0,
-        lowNickname: lowData?.nickname || "No Interaction",
-        highNickname: highData?.nickname || "No Interaction"
-      };
-    });
-
-    return heatmapData;
-  }
-
-  async sortHeatMapValues() {
-    if (this.chartInstance !== null) {
-      this.chartInstance.off("click");
-      this.chartInstance.on("click", async (params: { componentType: string; seriesType: string; value: any[] }) => {
-        if (params.componentType === "series" && params.seriesType === "heatmap") {
-          const colIndex = params.value[0]; // Column index of the clicked cell
-          // Extract the data for the clicked column
-          let columnData = this.series[0].data.filter((item: any[]) => item[0] === colIndex);
-          // Sort the column data by the value (timeActive) in ascending order
-          columnData.sort((a: number[], b: number[]) => a[2] - b[2]);
-          // Reorder yAxisData based on sorted column data
-          const sortedUserIndices = columnData.map((item: any[]) => item[1]);
-          const sortedYAxisData = sortedUserIndices.map((index: string | number) => this.yAxisData[index]);
-          // Reconstruct the series data with sorted y-axis order
-          let newData = this.series[0].data.map((item: any[]) => {
-            const newIndex = sortedUserIndices.indexOf(item[1]);
-            return [item[0], newIndex, item[2]];
-          });
-          // Update the y-axis data and series data
-          this.yAxisData = sortedYAxisData;
-          this.series[0].data = newData;
-          // Refresh the chart instance
-          this.chartInstance.setOption({
-            yAxis: {
-              data: this.yAxisData
-            },
-            series: [
-              {
-                data: this.series[0].data
-              }
-            ]
-          });
-        }
-      });
-    }
-  }
-
-  renderCombinedTopicChart(container: HTMLElement, heatmapActivities: any[], chartTitle: string) {
-    this.chart = echarts.init(container);
-
-    const heatmapData = heatmapActivities.map((item, index) => [index, 0, Math.round(item.value / 2)]);
-    const titles = heatmapActivities.map((item) => item.title);
-
-    const option = {
-      title: {
-        top: "15%",
-        left: "center",
-        text: chartTitle
-      },
-      tooltip: {
-        position: "bottom",
-        formatter: function (params: { dataIndex: number }) {
-          const dataIndex = params.dataIndex;
-          const dataItem = heatmapActivities[dataIndex];
-          if (dataItem) {
-            let tipHtml = dataItem.title + "<br />";
-            tipHtml += "Min: " + dataItem.lowValue + " (" + dataItem.lowNickname + ")<br />";
-            tipHtml += "Max: " + dataItem.highValue + " (" + dataItem.highNickname + ")";
-            return tipHtml;
-          }
-          return "";
-        }
-      },
-      backgroundColor: {
-        image: bgPatternImg,
-        repeat: "repeat"
-      },
-      grid: {
-        height: "30%",
-        top: "30%"
-      },
-      xAxis: {
-        type: "category",
-        data: titles
-      },
-      yAxis: {
-        type: "category",
-        data: [""]
-      },
-      axisLabel: {
-        interval: 0,
-        fontSize: 15
-      },
-      visualMap: {
-        min: 0,
-        max: Math.max(...heatmapActivities.map((item) => item.value)),
-        calculable: true,
-        orient: "horizontal",
-        left: "center",
-        bottom: "15%"
-      },
-      series: [
-        {
-          name: "Value",
-          type: "heatmap",
-          data: heatmapData,
-          label: {
-            show: true
-          },
-          emphasis: {
-            itemStyle: {
-              shadowBlur: 10,
-              shadowColor: "rgba(0, 0, 0, 0.5)"
-            }
-          }
-        }
-      ]
-    };
-
-    this.chart.setOption(option);
-    this.sortHeatMapValues();
-  }
-}
diff --git a/src/lib/ui/time/supabase/analytics/topic-pie.ts b/src/lib/ui/time/supabase/analytics/topic-pie.ts
deleted file mode 100644
index 32a245a0a..000000000
--- a/src/lib/ui/time/supabase/analytics/topic-pie.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import * as echarts from "echarts/core";
-import { backgroundPattern } from "../charts/tutors-charts-background-url";
-import { piechart } from "../charts/piechart";
-import type { Course, Lo } from "$lib/services/models/lo-types";
-import type { Session } from "@supabase/supabase-js";
-import { getCompositeValues, getSimpleTypesValues } from "$lib/services/utils/supabase-utils";
-import { PieChart } from "echarts/charts";
-import { TitleComponent, TooltipComponent, LegendComponent } from "echarts/components";
-import { CanvasRenderer } from "echarts/renderers";
-import type { LabStepData, OuterPieData } from "$lib/services/types/supabase-metrics";
-
-echarts.use([TitleComponent, TooltipComponent, LegendComponent, PieChart, CanvasRenderer]);
-
-const bgPatternSrc = backgroundPattern;
-
-const bgPatternImg = new Image();
-bgPatternImg.src = bgPatternSrc;
-
-export class TopicPieChart {
-  myChart: any;
-  topics: string[];
-  course: Course;
-  session: Session;
-  topicTitleTimesMap = new Map<string, number>();
-  totalTimesMap: Map<string, LabStepData[]>;
-  userIds: string[];
-  multipleUsers: boolean;
-
-  constructor(course: Course, session: Session, userIds: string[], multipleUsers: boolean) {
-    this.myChart = null;
-    this.topics = [];
-    this.course = course;
-    this.session = session;
-    this.userIds = userIds;
-    this.topicTitleTimesMap = new Map<string, number>();
-    this.totalTimesMap = new Map<string, LabStepData[]>();
-    this.multipleUsers = multipleUsers;
-  }
-
-  singleUserPieClick() {
-    if (this.myChart !== null) {
-      // Listen to click event on the inner pie chart
-      this.myChart.on("click", (params: { seriesName: string; name: string }) => {
-        if (params.seriesName === "Inner Pie") {
-          let outerPieData: OuterPieData[] = []; // Reset outerPieData array
-
-          // Find the corresponding data for the clicked inner pie slice
-          this.totalTimesMap.forEach((steps, topicTitle) => {
-            if (topicTitle === params.name) {
-              steps.forEach((step) => {
-                if (step.aggregatedTimeActive !== 0) {
-                  outerPieData.push({ value: step.aggregatedTimeActive, name: step.title, type: step.loType });
-                }
-              });
-            }
-          });
-          this.populateOuterPieData(outerPieData);
-        }
-      });
-    }
-  }
-
-  populateOuterPieData(outerPieData: OuterPieData[]) {
-    // Update the data for the outer pie chart
-    const chartInstance = echarts.getInstanceByDom(document.getElementById("chart"));
-    if (chartInstance) {
-      chartInstance.setOption({
-        series: [
-          {
-            name: "Outer Pie",
-            data: outerPieData.filter((topic) => topic.value > 0) || [{}]
-          }
-        ]
-      });
-    }
-  }
-
-  multipleUsersInnerPieClick() {
-    this.myChart.on("click", (params: { seriesName: string; name: any }) => {
-      if (params.seriesName === "Inner Pie") {
-        const outerPieData: OuterPieData[] = [];
-
-        this.totalTimesMap.forEach((steps, topicTitle) => {
-          if (topicTitle === params.name) {
-            steps.forEach((step) => {
-              const existing = outerPieData.find((data) => data.name === step.title);
-              if (existing) {
-                existing.value += step.aggregatedTimeActive;
-              } else {
-                outerPieData.push({ value: step.aggregatedTimeActive, name: step.title, type: step.loType });
-              }
-            });
-          }
-        });
-        this.populateOuterPieData(outerPieData);
-      }
-    });
-  }
-
-  getOuterPieDataForMultipleUsers(): { value: number; name: string }[] {
-    const outerPieData: { value: number; name: string }[] = [];
-    this.topicTitleTimesMap.forEach((value, key) => {
-      const existing = outerPieData.find((data) => data.name === key);
-      if (existing) {
-        existing.value += value;
-      } else {
-        outerPieData.push({ value, name: key });
-      }
-    });
-
-    return outerPieData;
-  }
-
-  renderChart(userIds = null as string[] | null) {
-    if (this.myChart === null) {
-      // If chart instance doesn't exist, create a new one
-      this.myChart = echarts.init(document.getElementById("chart"));
-    }
-
-    const allComposites = getCompositeValues(this.course.los);
-    const allSimpleTypes = getSimpleTypesValues(this.course.los);
-    const allTypes = [...allComposites, ...allSimpleTypes];
-
-    const updateMaps = (lo: Lo, userName: string, timeActive: number) => {
-      let topicTitle = "";
-      let loTitle = lo.title;
-      if (lo.parentTopic?.type === "topic") {
-        topicTitle = lo.parentTopic?.title;
-      } else if (lo.parentLo?.parentTopic?.type === "topic") {
-        topicTitle = lo.parentLo?.parentTopic?.title;
-      } else {
-        topicTitle = lo.title;
-      }
-
-      // Add timeActive to the total time for the topic
-      if (this.topicTitleTimesMap.has(topicTitle)) {
-        this.topicTitleTimesMap.set(topicTitle, this.topicTitleTimesMap.get(topicTitle)! + timeActive);
-      } else {
-        this.topicTitleTimesMap.set(topicTitle, timeActive);
-      }
-
-      if (!this.totalTimesMap.has(topicTitle)) {
-        this.totalTimesMap.set(topicTitle, []);
-      }
-
-      const existingEntries = this.totalTimesMap.get(topicTitle)!;
-      const existingEntry = existingEntries.find((entry) => entry.title === loTitle);
-
-      if (existingEntry) {
-        existingEntry.aggregatedTimeActive += timeActive;
-      } else {
-        existingEntries.push({ aggregatedTimeActive: timeActive, title: loTitle, loType: lo.type });
-      }
-    };
-
-    allTypes.forEach((lo) => {
-      if (userIds && userIds.length > 0) {
-        userIds.forEach((userId) => {
-          const timeActive = lo.learningRecords?.get(userId)?.timeActive || 0;
-          updateMaps(lo, userId, timeActive);
-        });
-      } else {
-        const timeActive = lo.learningRecords?.get(this.session.user.user_metadata.user_name)?.timeActive || 0;
-        updateMaps(lo, this.session.user.user_metadata.user_name, timeActive);
-      }
-    });
-
-    if (this.multipleUsers === false) {
-      const singleUserInnerData = Array.from(this.topicTitleTimesMap.entries()).map(([title, timeActive]) => ({
-        name: title,
-        value: timeActive
-      }));
-
-      const option = piechart(bgPatternImg, this.course, [], singleUserInnerData, []);
-      this.myChart.setOption(option);
-      this.singleUserPieClick();
-    } else {
-      const allUsersTopicActivity = this.getOuterPieDataForMultipleUsers();
-      const option = piechart(bgPatternImg, this.course, allUsersTopicActivity, [], []);
-      this.myChart.setOption(option);
-      this.multipleUsersInnerPieClick();
-    }
-  }
-}
diff --git a/src/lib/ui/time/supabase/charts/barchart.ts b/src/lib/ui/time/supabase/charts/barchart.ts
index 34418421a..2eaf1e171 100644
--- a/src/lib/ui/time/supabase/charts/barchart.ts
+++ b/src/lib/ui/time/supabase/charts/barchart.ts
@@ -1,6 +1,8 @@
+import type { BoxplotChartConfig, ChartType } from "$lib/services/types/supabase-metrics";
 import type { EChartsOption } from "echarts";
 
-export function barchart(piePatternImg: HTMLImageElement, bgPatternImg: HTMLImageElement, chartData: UserMetric[]): EChartsOption {
+export function barchart(piePatternImg: string, bgPatternImg: string, chartData: UserMetric[]): BoxplotChartConfig {
+  let type: ChartType = "bar"; // OK
   return {
     backgroundColor: {
       image: bgPatternImg,
@@ -16,7 +18,7 @@ export function barchart(piePatternImg: HTMLImageElement, bgPatternImg: HTMLImag
     },
     series: [
       {
-        type: "bar",
+        type: type,
         data: chartData.map((user) => Math.round(user.duration / 2) || 0),
         itemStyle: {
           color: {
diff --git a/src/lib/ui/time/supabase/charts/boxplot-chart.ts b/src/lib/ui/time/supabase/charts/boxplot-chart.ts
index d7060ddc2..d418b605f 100644
--- a/src/lib/ui/time/supabase/charts/boxplot-chart.ts
+++ b/src/lib/ui/time/supabase/charts/boxplot-chart.ts
@@ -1,7 +1,7 @@
-import type { BoxplotData } from "$lib/services/types/supabase-metrics";
+import type { BoxplotData, BoxplotChartConfig } from "$lib/services/types/supabase-metrics";
 import * as echarts from "echarts";
 
-export function boxplot(bgPatternImg: HTMLImageElement, userNicknames: string[], boxplotData: number[][], chartTitle: string): echarts.EChartsOption {
+export function boxplot(bgPatternImg: HTMLImageElement, userNicknames: string[], boxplotData: number[][], chartTitle: string): BoxplotChartConfig {
   return {
     title: {
       text: chartTitle
@@ -25,7 +25,8 @@ export function boxplot(bgPatternImg: HTMLImageElement, userNicknames: string[],
       data: userNicknames
     },
     xAxis: {
-      type: "value"
+      type: "value",
+      boundaryGap: [0, 0.3]
     },
     series: [
       {
@@ -54,7 +55,7 @@ export function combinedBoxplotChart(bgPatternImg: HTMLImageElement, boxplotData
       formatter: function (params) {
         const dataIndex = params.dataIndex;
         const dataItem = boxplotData[dataIndex];
-        let tipHtml = `${dataItem.title}<br />`;
+        let tipHtml = `${dataItem.name}<br />`;
         tipHtml += `Min: ${dataItem.value[0]} (${dataItem.lowNickname})<br />`;
         tipHtml += `Q1: ${dataItem.value[1]}<br />`;
         tipHtml += `Median: ${dataItem.value[2]}<br />`;
@@ -65,7 +66,7 @@ export function combinedBoxplotChart(bgPatternImg: HTMLImageElement, boxplotData
     },
     xAxis: {
       type: "category",
-      data: boxplotData.map((item) => item.title), // topic titles
+      data: boxplotData.map((item) => item.name), // topic titles
       boundaryGap: true,
       nameGap: 30,
       splitArea: {
@@ -91,7 +92,7 @@ export function combinedBoxplotChart(bgPatternImg: HTMLImageElement, boxplotData
           formatter: function (params) {
             const dataIndex = params.dataIndex;
             const dataItem = boxplotData[dataIndex];
-            let tipHtml = `${dataItem.title}<br />`;
+            let tipHtml = `${dataItem.name}<br />`;
             tipHtml += `Min: ${dataItem.value[0]} (${dataItem.lowNickname})<br />`;
             tipHtml += `Q1: ${dataItem.value[1]}<br />`;
             tipHtml += `Median: ${dataItem.value[2]}<br />`;
diff --git a/src/lib/ui/time/supabase/charts/heatmap-chart.ts b/src/lib/ui/time/supabase/charts/heatmap-chart.ts
index 5ab8f39e0..724abb148 100644
--- a/src/lib/ui/time/supabase/charts/heatmap-chart.ts
+++ b/src/lib/ui/time/supabase/charts/heatmap-chart.ts
@@ -1,44 +1,40 @@
-import type { HeatMapSeriesData } from "$lib/services/types/supabase-metrics";
-import type { EChartsOption } from "echarts";
+import type { ChartType, GridConfig, HeatMapChartConfig, HeatMapSeriesData } from "$lib/services/types/supabase-metrics";
 
-export function heatmap(categories: Set<string>, yAxisData: string[], series: HeatMapSeriesData[], bgPatternImg: HTMLImageElement, chartTitleString: string): EChartsOption {
+export function heatmap(categories: Set<string>, yAxisData: string[], series: HeatMapSeriesData, bgPatternImg: HTMLImageElement, chartTitleString: string): HeatMapChartConfig {
   let visualmapValue: number;
-  let gridConfig;
+  let seriesArray: HeatMapSeriesData[] = [series];
+  let gridConfig: GridConfig = {
+    left: "30%",
+    right: "30%",
+    bottom: "15%",
+    top: "10%",
+    width: "40%", // Fixed width
+    height: "80%", // Fixed height
+    containLabel: false
+  };
 
-  if (series[0]?.data) {
-    if (series[0].name === "Lab Activity For All Users" || series[0].name === "Topic Activity For All Users") {
+  if (series?.data) {
+    if (series.name === "lab activity for all users" || series.name === "topic activity for all users") {
       gridConfig = {
-        left: "30%",
-        right: "30%",
-        bottom: "15%",
+        left: "15%",
+        right: "10%",
+        bottom: "10%",
         top: "10%",
-        width: "40%", // Fixed width
+        width: "80%", // Fixed width
         height: "80%", // Fixed height
         containLabel: false // Prevent resizing based on labels
       };
     } else {
       gridConfig = {
-        left: "20%",
-        right: "10%",
+        left: "15%",
+        right: "15%",
         bottom: "15%",
         top: "15%",
-        width: "60%", // Fixed width
+        width: "80%", // Fixed width
         height: "80px", // Fixed height
-        containLabel: false // Prevent resizing based on labels
+        containLabel: true // Prevent resizing based on labels
       };
     }
-
-    visualmapValue = series[0].data.length !== 0 ? Math.max(...series[0].data.map((item) => item[2])) : 0;
-  } else if (series?.data) {
-    gridConfig = {
-      left: "30%",
-      right: "30%",
-      bottom: "15%",
-      top: "15%",
-      width: "40%", // Fixed width
-      height: "40%", // Fixed height
-      containLabel: false // Prevent resizing based on labels
-    };
     visualmapValue = series.data.length !== 0 ? Math.max(...series.data.map((item) => item[2])) : 0;
   } else {
     visualmapValue = 0;
@@ -65,8 +61,8 @@ export function heatmap(categories: Set<string>, yAxisData: string[], series: He
         show: true
       },
       axisLabel: {
-        interval: 1,
-        fontSize: 15,
+        interval: 3,
+        fontSize: 12,
         margin: 10 // Adjust margin to control spacing
       },
       axisTick: {
@@ -78,24 +74,97 @@ export function heatmap(categories: Set<string>, yAxisData: string[], series: He
       position: "bottom"
     },
     yAxis: {
-      type: "category",
-      data: yAxisData[0] !== undefined ? yAxisData : "",
-      splitArea: {
-        show: true
-      },
+  type: "category",
+  data: yAxisData[0] !== undefined ? yAxisData : [],
+  splitArea: {
+    show: true
+  },
       axisLabel: {
         interval: 0,
         fontSize: 15,
         padding: [10, 0, 10, 0] // Increase space between rows
       }
-    },
+  },
     visualMap: {
       min: 0,
       max: visualmapValue,
       calculable: true,
       orient: "horizontal",
-      left: "center"
+      left: "center",
+      align: "auto",
+      bottom: 0
+    },
+    series: seriesArray
+  };
+}
+
+export function renderCombinedChart(heatmapActivities: any[], bgPatternImg: HTMLImageElement, chartTitle: string) {
+  const heatmapData = heatmapActivities.map((item, index) => [index, 0, Math.round(item.value / 2)]);
+  const titles = heatmapActivities.map((item) => item.title);
+
+  return {
+    title: {
+      top: "15%",
+      left: "center",
+      text: chartTitle
     },
-    series: series
+    tooltip: {
+      position: "bottom",
+      formatter: function (params: { dataIndex: number }) {
+        const dataIndex = params.dataIndex;
+        const dataItem = heatmapActivities[dataIndex];
+        if (dataItem) {
+          let tipHtml = dataItem.title + "<br />";
+          tipHtml += "Min: " + dataItem.lowValue + " (" + dataItem.lowNickname + ")<br />";
+          tipHtml += "Max: " + dataItem.highValue + " (" + dataItem.highNickname + ")";
+          return tipHtml;
+        }
+        return "";
+      }
+    },
+    backgroundColor: {
+      image: bgPatternImg,
+      repeat: "repeat"
+    },
+    grid: {
+      height: "30%",
+      top: "30%"
+    },
+    xAxis: {
+      type: "category",
+      data: titles
+    },
+    yAxis: {
+      type: "category",
+      data: [""]
+    },
+    axisLabel: {
+      interval: 0,
+      fontSize: 12
+    },
+    visualMap: {
+      min: 0,
+      max: Math.max(...heatmapActivities.map((item) => item.value / 2)),
+      calculable: true,
+      orient: "horizontal",
+      left: "center",
+      bottom: "15%"
+    },
+    series: [
+      {
+        name: "Value",
+        type: "heatmap",
+        data: heatmapData,
+        label: {
+          show: true
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowColor: "rgba(0, 0, 0, 0.5)"
+          }
+        }
+      }
+    ]
   };
 }
diff --git a/src/lib/ui/time/supabase/charts/piechart.ts b/src/lib/ui/time/supabase/charts/piechart.ts
index 2a5b9180a..43e6f151f 100644
--- a/src/lib/ui/time/supabase/charts/piechart.ts
+++ b/src/lib/ui/time/supabase/charts/piechart.ts
@@ -1,12 +1,7 @@
 import type { Course } from "$lib/services/models/lo-types";
+import type { DrilledDownData } from "$lib/services/types/supabase-metrics";
 
-export function piechart(
-  bgPatternImg: HTMLImageElement,
-  course: Course,
-  allUsersTopicActivity: any[],
-  singleUserInnerData: { name: string; value: number }[],
-  singleUserOuterData: { value: number; name: string; type: string }[]
-) {
+export function piechart(bgPatternImg: HTMLImageElement, allUsersTopicActivity: any[], singleUserInnerData: DrilledDownData[], singleUserOuterData: DrilledDownData[]) {
   return {
     tooltip: {
       trigger: "item",
diff --git a/src/lib/ui/time/supabase/views/CalendarView.svelte b/src/lib/ui/time/supabase/views/CalendarView.svelte
index 3de1fab7f..8b0b71c2d 100644
--- a/src/lib/ui/time/supabase/views/CalendarView.svelte
+++ b/src/lib/ui/time/supabase/views/CalendarView.svelte
@@ -6,9 +6,11 @@
   export let timeActiveMap: Map<string, Map<string, number>>;
   export let session: Session;
   export let medianTime: Map<string, number>;
+  export let userNamesUseridsMap: Map<string, string>;
+  export let userAvatarsUseridsMap: Map<string, string>;
 
   let calendarChart: CalendarChart | null;
-  calendarChart = new CalendarChart();
+  calendarChart = new CalendarChart(userAvatarsUseridsMap, userNamesUseridsMap);
 
   onMount(() => {
     renderChart();
@@ -23,9 +25,9 @@
   });
 
   // Re-render the chart when the tab regains focus
-  const handleFocus = () => {
-    renderChart();
-  };
+  // const handleFocus = () => {
+  //   renderChart();
+  // };
 
   // Function to render the chart
   const renderChart = () => {
@@ -37,7 +39,7 @@
   };
 
   // Listen for window focus event to trigger chart refresh
-  window.addEventListener("focus", handleFocus);
+  // window.addEventListener("focus", handleFocus);
 </script>
 
 <div class="h-screen">
diff --git a/src/lib/ui/time/supabase/views/Chart.svelte b/src/lib/ui/time/supabase/views/Chart.svelte
new file mode 100644
index 000000000..561b50872
--- /dev/null
+++ b/src/lib/ui/time/supabase/views/Chart.svelte
@@ -0,0 +1,45 @@
+<script lang="ts">
+  import { onMount, onDestroy } from "svelte";
+  import type { Course } from "$lib/services/models/lo-types";
+  import { LabPieChart } from "../analytics/piechart/lab-pie-chart";
+  import { TopicPieChart } from "../analytics/piechart/topic-pie-chart";
+  import type { Session } from "@supabase/supabase-js";
+
+  export let chartType: 'LabPieChart' | 'TopicPieChart';
+  export let course: Course;
+  export let session: Session;
+  export let userIds: string[] = [];
+  export let multipleUsers: boolean = false;
+
+  let chartInstance: LabPieChart | TopicPieChart | null = null;
+
+  onMount(() => {
+    if (chartType === 'LabPieChart') {
+      chartInstance = new LabPieChart(course, session);
+    } else if (chartType === 'TopicPieChart') {
+      chartInstance = new TopicPieChart(course, session, userIds, multipleUsers);
+    }else{
+      //this would be type never the narraowest type
+      throw new Error(`Invalid chart type: ${chartType}`);
+    }
+    renderChart();
+  });
+
+  const renderChart = () => {
+    if (chartInstance) {
+      chartInstance.renderChart();
+    }
+  };
+
+  onDestroy(() => {
+    if (chartInstance) {
+      chartInstance = null;
+    }
+  });
+
+  window.addEventListener("focus", renderChart);
+</script>
+
+<div class="h-screen">
+  <div id="chart" style="height: 100%; width: 100%;"></div>
+</div>
diff --git a/src/lib/ui/time/supabase/views/HeatMapChart.svelte b/src/lib/ui/time/supabase/views/HeatMapChart.svelte
new file mode 100644
index 000000000..c90816079
--- /dev/null
+++ b/src/lib/ui/time/supabase/views/HeatMapChart.svelte
@@ -0,0 +1,58 @@
+<script lang="ts">
+  import { onMount, onDestroy } from "svelte";
+  import { LabHeatMapChart } from "../analytics/heatmap/lab-heat-map-chart";
+  import { TopicHeatMapChart } from "../analytics/heatmap/topic-heat-map-chart";
+  import type { Course } from "$lib/services/models/lo-types";
+  import type { Session } from "@supabase/supabase-js";
+
+  export let course: Course;
+  export let session: Session;
+  export let userIds: string[] = [];
+  export let userNamesUseridsMap: Map<string, string> = new Map();
+  export let multipleUsers: boolean = false;
+  export let chartType: "LabHeatMap" | "TopicHeatMap";
+
+  let chartInstance: LabHeatMapChart | TopicHeatMapChart | null = null;
+
+  onMount(() => {
+    if (chartType === "LabHeatMap") {
+      chartInstance = new LabHeatMapChart(course, session, userIds, userNamesUseridsMap, multipleUsers);
+    } else if (chartType === "TopicHeatMap") {
+      chartInstance = new TopicHeatMapChart(course, session, userIds, userNamesUseridsMap, multipleUsers);
+    } else {
+      throw new Error(`Invalid chart type: ${chartType}`);
+    }
+    if (multipleUsers) {
+      //combined
+      const element = document.getElementById("combined-heatmap");
+      if (!element) {
+        throw new Error("Element with ID 'combined-heatmap' not found");
+      }
+      chartInstance.renderChart(element!);
+    }
+
+    chartInstance.populateAndRenderData();
+  });
+
+  onDestroy(() => {
+    if (chartInstance) {
+      chartInstance = null;
+    }
+  });
+
+  window.addEventListener("focus", () => {
+    if (chartInstance) {
+      const container = document.getElementById("heatmap-container");
+      chartInstance.renderChart(container!);
+    }
+  });
+</script>
+
+<div class="h-screen flex flex-col">
+  {#if multipleUsers}
+    <div id="heatmap-container" class="h-2/3 w-full overflow-y-scroll"></div>
+    <div id="combined-heatmap" class="h-1/3 w-full overflow-y-scroll"></div>
+  {:else}
+    <div id="heatmap-container" class="h-full w-full overflow-y-scroll"></div>
+  {/if}
+</div>
diff --git a/src/lib/ui/time/supabase/views/InstructorCalendarView.svelte b/src/lib/ui/time/supabase/views/InstructorCalendarView.svelte
index 29cc0ae8c..0827d89f7 100644
--- a/src/lib/ui/time/supabase/views/InstructorCalendarView.svelte
+++ b/src/lib/ui/time/supabase/views/InstructorCalendarView.svelte
@@ -1,16 +1,16 @@
 <script lang="ts">
   import { onDestroy, onMount } from "svelte";
   import { CalendarChart } from "../analytics/calendar";
-  import type { Course } from "$lib/services/models/lo-types";
 
-  export let course: Course;
   export let timeActiveMap: Map<string, Map<string, number>>;
+  export let userAvatarsUseridsMap: Map<string, string>;
+  export let userNamesUseridsMap: Map<string, string>;
   export let userIds: string[];
 
   let calendarChart: CalendarChart | null;
 
   onMount(() => {
-    calendarChart = new CalendarChart();
+    calendarChart = new CalendarChart(userAvatarsUseridsMap, userNamesUseridsMap);
     createAndRenderChart();
   });
 
@@ -26,7 +26,7 @@
     if (timeActiveMap.size > 0) {
       timeActiveMap.forEach((calendarMap, userId) => {
         calendarChart?.createChartContainer(userId);
-        calendarChart?.renderCombinedChart(course, calendarMap, userId);
+        calendarChart?.renderCombinedChart(calendarMap, userId);
       });
     }
   };
diff --git a/src/lib/ui/time/supabase/views/InstructorLabView.svelte b/src/lib/ui/time/supabase/views/InstructorLabView.svelte
index 9a108bba0..d1761034a 100644
--- a/src/lib/ui/time/supabase/views/InstructorLabView.svelte
+++ b/src/lib/ui/time/supabase/views/InstructorLabView.svelte
@@ -1,53 +1,13 @@
 <script lang="ts">
-  import { onDestroy, onMount } from "svelte";
   import type { Course } from "$lib/services/models/lo-types";
-  import { LabHeatMapChart } from "../analytics/lab-heat-map";
+  import LabHeatMapChart from "./HeatMapChart.svelte";
   import type { Session } from "@supabase/supabase-js";
 
   export let course: Course;
   export let session: Session;
   export let userIds: string[];
-  let labHeatMapChart: LabHeatMapChart | null;
-  labHeatMapChart = new LabHeatMapChart(course, session, userIds);
-
-  onMount(() => {
-    labHeatMapChart?.populateUsersData();
-    renderChart();
-  });
-
-  // Destroy the chart instance when the component unmounts
-  onDestroy(() => {
-    if (labHeatMapChart) {
-      // Clean up resources if needed
-      labHeatMapChart = null;
-    }
-  });
-
-  // Re-render the chart when the tab regains focus
-  const handleFocus = () => {
-    renderChart();
-  };
-
-  // Function to render the chart
-  const renderChart = () => {
-    if (labHeatMapChart) {
-      const container = labHeatMapChart.getChartContainer();
-      if (container) labHeatMapChart.renderChart(container);
-
-      const combinedLabData = labHeatMapChart.prepareCombinedLabData(userIds);
-      const element = document.getElementById("combined-heatmap");
-      if (!element) {
-        throw new Error("Element with ID 'combined-heatmap' not found");
-      }
-      labHeatMapChart.renderCombinedLabChart(element, combinedLabData, "Total Time: Labs");
-    }
-  };
-
-  // Listen for window focus event to trigger chart refresh
-  window.addEventListener("focus", handleFocus);
+  export let userNamesUseridsMap: Map<string, string>;
+  export let multipleUsers: boolean = true;
 </script>
 
-<div class="h-screen flex flex-col">
-  <div id="heatmap-container" class="h-3/4 w-full overflow-y-scroll"></div>
-  <div id="combined-heatmap" class="h-1/4 w-full overflow-y-scroll"></div>
-</div>
+<LabHeatMapChart chartType="LabHeatMap" {course} {session} {userIds} {userNamesUseridsMap} {multipleUsers} />
diff --git a/src/lib/ui/time/supabase/views/InstructorLabViewBoxPlot.svelte b/src/lib/ui/time/supabase/views/InstructorLabViewBoxPlot.svelte
index 24eab3b73..bd1b4acc8 100644
--- a/src/lib/ui/time/supabase/views/InstructorLabViewBoxPlot.svelte
+++ b/src/lib/ui/time/supabase/views/InstructorLabViewBoxPlot.svelte
@@ -2,9 +2,11 @@
   import { onMount, onDestroy } from "svelte";
   import { LabBoxPlotChart } from "../analytics/lab-box-plot";
   import type { Course } from "$lib/services/models/lo-types";
+    import { lab } from "d3";
 
   export let course: Course;
   export let userIds: string[];
+  export let userNamesUseridsMap: Map<string, string>;
 
   let labBoxPlot: LabBoxPlotChart | null = null;
 
@@ -24,7 +26,7 @@
   };
 
   onMount(() => {
-    labBoxPlot = new LabBoxPlotChart(course, userIds);
+    labBoxPlot = new LabBoxPlotChart(course, userIds, userNamesUseridsMap);
     renderCharts();
     window.addEventListener("focus", handleFocus);
   });
diff --git a/src/lib/ui/time/supabase/views/InstructorTopicView.svelte b/src/lib/ui/time/supabase/views/InstructorTopicView.svelte
index db3e38d4e..1ce0d3f3e 100644
--- a/src/lib/ui/time/supabase/views/InstructorTopicView.svelte
+++ b/src/lib/ui/time/supabase/views/InstructorTopicView.svelte
@@ -1,54 +1,13 @@
 <script lang="ts">
-  import { onDestroy, onMount } from "svelte";
-  import type { Course, Topic } from "$lib/services/models/lo-types";
-  import { TopicHeatMapChart } from "../analytics/topic-heat-map";
+  import type { Course } from "$lib/services/models/lo-types";
+  import TopicHeatMapChart from "./HeatMapChart.svelte";
   import type { Session } from "@supabase/supabase-js";
 
   export let course: Course;
   export let session: Session;
   export let userIds: string[];
-  let topicHeatMapChart: TopicHeatMapChart | null;
-  topicHeatMapChart = new TopicHeatMapChart(course, session, userIds);
-
-  onMount(() => {
-    topicHeatMapChart?.populateUsersData();
-    renderChart();
-  });
-
-  // Destroy the chart instance when the component unmounts
-  onDestroy(() => {
-    if (topicHeatMapChart) {
-      // Clean up resources if needed
-      topicHeatMapChart = null;
-    }
-  });
-
-  // Re-render the chart when the tab regains focus
-  const handleFocus = () => {
-    renderChart();
-  };
-
-  // Function to render the chart
-  const renderChart = () => {
-    if (topicHeatMapChart) {
-      const container = topicHeatMapChart.getChartContainer();
-      topicHeatMapChart.renderChart(container);
-
-      //combined
-      const combinedTopicData = topicHeatMapChart.prepareCombinedTopicData(userIds);
-      const element = document.getElementById("combined-heatmap");
-      if (!element) {
-        throw new Error("Element with ID 'combined-heatmap' not found");
-      }
-      topicHeatMapChart.renderCombinedTopicChart(element, combinedTopicData, "Total Time: Topics");
-    }
-  };
-
-  // Listen for window focus event to trigger chart refresh
-  window.addEventListener("focus", handleFocus);
+  export let userNamesUseridsMap: Map<string, string>;
+  export let multipleUsers: boolean = true;
 </script>
 
-<div class="h-screen flex flex-col">
-  <div id="heatmap-container" class="h-1/2 w-full overflow-y-scroll"></div>
-  <div id="combined-heatmap" class="h-1/2 w-full overflow-y-scroll"></div>
-</div>
+<TopicHeatMapChart chartType="TopicHeatMap" {course} {session} {userIds} {userNamesUseridsMap} {multipleUsers} />
diff --git a/src/lib/ui/time/supabase/views/InstructorTopicViewBoxPlot.svelte b/src/lib/ui/time/supabase/views/InstructorTopicViewBoxPlot.svelte
index fc7bc89e1..84c7c0eb8 100644
--- a/src/lib/ui/time/supabase/views/InstructorTopicViewBoxPlot.svelte
+++ b/src/lib/ui/time/supabase/views/InstructorTopicViewBoxPlot.svelte
@@ -5,12 +5,13 @@
 
   export let course: Course;
   export let userIds: string[];
+  export let userNamesUseridsMap: Map<string, string>;
 
   let topicBoxPlotChart: TopicBoxPlotChart | null;
 
   // Initialise the charts and render them when the component mounts
   onMount(() => {
-    topicBoxPlotChart = new TopicBoxPlotChart(course, userIds);
+    topicBoxPlotChart = new TopicBoxPlotChart(course, userIds, userNamesUseridsMap);
     renderCharts();
   });
 
diff --git a/src/lib/ui/time/supabase/views/InstructorTopicViewPieChart.svelte b/src/lib/ui/time/supabase/views/InstructorTopicViewPieChart.svelte
index 2c77561e1..4d7494e3f 100644
--- a/src/lib/ui/time/supabase/views/InstructorTopicViewPieChart.svelte
+++ b/src/lib/ui/time/supabase/views/InstructorTopicViewPieChart.svelte
@@ -1,36 +1,12 @@
 <script lang="ts">
-  import { onMount, onDestroy } from "svelte";
-  import type { Course, Topic } from "$lib/services/models/lo-types";
-  import { TopicPieChart } from "../analytics/topic-pie";
-    import type { Session } from "@supabase/supabase-js";
+  import type { Course } from "$lib/services/models/lo-types";
+  import type { Session } from "@supabase/supabase-js";
+  import Chart from './Chart.svelte';
 
   export let course: Course;
   export let session: Session;
   export let userIds: string[];
   const multipleUsers = true;
-
-  let topicPieChart: TopicPieChart | null;
-
-  onMount(() => {
-    topicPieChart = new TopicPieChart(course, session, userIds, multipleUsers);
-    renderChart();
-  });
-
-  const renderChart = () => {
-    if (topicPieChart) {
-      topicPieChart.renderChart(userIds);
-    }
-  };
-
-  onDestroy(() => {
-    if (topicPieChart) {
-      topicPieChart = null;
-    }
-  });
-
-  window.addEventListener("focus", renderChart);
 </script>
 
-<div class="h-screen">
-  <div id={"chart"} style="height: 100%; width:100%"></div>
-</div>
+<Chart chartType="TopicPieChart" {course} {session} {userIds} {multipleUsers} />
\ No newline at end of file
diff --git a/src/lib/ui/time/supabase/views/LabView.svelte b/src/lib/ui/time/supabase/views/LabView.svelte
index 8f259aa76..9ba6873f7 100644
--- a/src/lib/ui/time/supabase/views/LabView.svelte
+++ b/src/lib/ui/time/supabase/views/LabView.svelte
@@ -1,45 +1,13 @@
 <script lang="ts">
-  import { onDestroy, onMount } from "svelte";
-  import type { Course, Lo } from "$lib/services/models/lo-types";
-  import { LabHeatMapChart } from "../analytics/lab-heat-map";
-    import type { Session } from "@supabase/supabase-js";
+  import type { Course } from "$lib/services/models/lo-types";
+  import LabHeatMapChart from "./HeatMapChart.svelte";
+  import type { Session } from "@supabase/supabase-js";
 
   export let course: Course;
-  export let session: Session
+  export let session: Session;
   export let userIds: string[];
-
-  let labHeatMapChart: LabHeatMapChart | null;
-  labHeatMapChart = new LabHeatMapChart(course, session, userIds);
-
-  onMount(() => {
-    labHeatMapChart?.populateSingleUserData();
-    renderChart();
-  });
-
-  // Destroy the chart instance when the component unmounts
-  onDestroy(() => {
-    if (labHeatMapChart) {
-      labHeatMapChart = null;
-    }
-  });
-
-  // Re-render the chart when the tab regains focus
-  const handleFocus = () => {
-    renderChart();
-  };
-
-  // Function to render the chart
-  const renderChart = () => {
-    if (labHeatMapChart && course) {
-      const container = labHeatMapChart.getChartContainer();
-      labHeatMapChart.renderChart(container!);
-    }
-  };
-
-  // Listen for window focus event to trigger chart refresh
-  window.addEventListener("focus", handleFocus);
+  export let userNamesUseridsMap: Map<string, string>;
+  export let multipleUsers: boolean = false;
 </script>
 
-<div class="h-screen">
-  <div id={"heatmap-container"} style="height: 100%; width:100%"></div>
-</div>
+<LabHeatMapChart chartType="LabHeatMap" {course} {session} {userIds} {userNamesUseridsMap} {multipleUsers} />
diff --git a/src/lib/ui/time/supabase/views/LabViewPieChart.svelte b/src/lib/ui/time/supabase/views/LabViewPieChart.svelte
index bb9a12ec3..2df6422ad 100644
--- a/src/lib/ui/time/supabase/views/LabViewPieChart.svelte
+++ b/src/lib/ui/time/supabase/views/LabViewPieChart.svelte
@@ -1,41 +1,10 @@
 <script lang="ts">
-  import { onMount, onDestroy } from "svelte";
-  import type { Course, Lo } from "$lib/services/models/lo-types";
-  import { LabPieChart } from "../analytics/lab-pie";
+  import type { Course } from "$lib/services/models/lo-types";
   import type { Session } from "@supabase/supabase-js";
-  export let course: Course;
-  export let session:Session;
-
-  let labPieChart: LabPieChart | null;
-
-  onMount(async () => {
-    labPieChart = new LabPieChart(course, session);
-    labPieChart.renderChart();
-  });
-
-  // Destroy the chart instance when the component unmounts
-  onDestroy(() => {
-    if (labPieChart) {
-      labPieChart = null;
-    }
-  });
+  import Chart from './Chart.svelte';
 
-  // Function to render the chart
-  const renderChart = () => {
-    if (labPieChart) {
-      labPieChart.renderChart();
-    }
-  };
-
-  // Re-render the chart when the tab regains focus
-  const handleFocus = () => {
-    renderChart();
-  };
-
-  // Listen for window focus event to trigger chart refresh
-  window.addEventListener("focus", handleFocus);
+  export let course: Course;
+  export let session: Session;
 </script>
 
-<div class="h-screen">
-  <div id="chart" style="height: 100%; width:100%"></div>
-</div>
+<Chart chartType="LabPieChart" {course} {session} />
\ No newline at end of file
diff --git a/src/lib/ui/time/supabase/views/TopicView.svelte b/src/lib/ui/time/supabase/views/TopicView.svelte
index ed9425144..eac538bd8 100644
--- a/src/lib/ui/time/supabase/views/TopicView.svelte
+++ b/src/lib/ui/time/supabase/views/TopicView.svelte
@@ -1,39 +1,13 @@
 <script lang="ts">
-    import { onDestroy, onMount } from "svelte";
-    import type {  Course, Topic } from "$lib/services/models/lo-types";
-    import { TopicHeatMapChart } from "../analytics/topic-heat-map";
-    import type { Session } from "@supabase/supabase-js";
-
-    export let course: Course;
-    export let session: Session;
-    export let userIds: string[] = [];
-
-    let topicHeatMapChart: TopicHeatMapChart | null;
-    topicHeatMapChart = new TopicHeatMapChart(course, session, userIds);
-  
-    onMount(() => {
-      topicHeatMapChart?.populateSingleUserData();
-      renderChart();
-    });
-
-    onDestroy(() => {
-      if (topicHeatMapChart) {
-        topicHeatMapChart = null;
-      }
-    });
-
-    const renderChart = () => {
-      if (topicHeatMapChart && course) {
-        const container = topicHeatMapChart.getChartContainer();
-        topicHeatMapChart.renderChart(container);
-      }
-    };
-
-    window.addEventListener('focus', renderChart);
-  </script>
-
-  <div class="h-screen">
-    <div id={"heatmap-container"} style="height: 100%; width:100%"></div>
-</div>
-
-  
\ No newline at end of file
+  import type { Course } from "$lib/services/models/lo-types";
+  import TopicHeatMapChart from "./HeatMapChart.svelte";
+  import type { Session } from "@supabase/supabase-js";
+
+  export let course: Course;
+  export let session: Session;
+  export let userIds: string[];
+  export let userNamesUseridsMap: Map<string, string>;
+  export let multipleUsers: boolean = false;
+</script>
+
+<TopicHeatMapChart chartType="TopicHeatMap" {course} {session} {userIds} {userNamesUseridsMap} {multipleUsers} />
diff --git a/src/lib/ui/time/supabase/views/TopicViewPieChart.svelte b/src/lib/ui/time/supabase/views/TopicViewPieChart.svelte
index e3f5ad6c8..f836a44cd 100644
--- a/src/lib/ui/time/supabase/views/TopicViewPieChart.svelte
+++ b/src/lib/ui/time/supabase/views/TopicViewPieChart.svelte
@@ -1,36 +1,12 @@
 <script lang="ts">
-  import { onDestroy, onMount } from "svelte";
   import type { Course } from "$lib/services/models/lo-types";
-  import { TopicPieChart } from "../analytics/topic-pie";
-    import type { Session } from "@supabase/supabase-js";
+  import type { Session } from "@supabase/supabase-js";
+  import Chart from './Chart.svelte';
 
   export let course: Course;
   export let session: Session;
   export let userIds: string[];
   const multipleUsers = false;
-  let topicPieChart: TopicPieChart | null;
-  topicPieChart = new TopicPieChart(course, session, userIds, multipleUsers);
-
-  onMount(async () => {
-      topicPieChart?.renderChart();
-    
-  });
-
-  const renderChart = () => {
-    if (topicPieChart) {
-      topicPieChart.renderChart();
-    }
-  };
-
-  onDestroy(() => {
-      if (topicPieChart) {
-        topicPieChart = null;
-      }
-    });
-
-  window.addEventListener("focus", renderChart);
 </script>
 
-<div class="h-screen">
-  <div id="chart" style="height: 100%; width:100%"></div>
-</div>
+<Chart chartType="TopicPieChart" {course} {session} {userIds} {multipleUsers} />
diff --git a/src/routes/(course-reader)/topic/[courseid]/[...loid]/+page.ts b/src/routes/(course-reader)/topic/[courseid]/[...loid]/+page.ts
index 7bc78cc3b..8648a5f4a 100644
--- a/src/routes/(course-reader)/topic/[courseid]/[...loid]/+page.ts
+++ b/src/routes/(course-reader)/topic/[courseid]/[...loid]/+page.ts
@@ -1,33 +1,14 @@
 import type { PageLoad } from "./$types";
 import { courseService } from "$lib/services/course";
-import { currentLo } from "$lib/stores";
-import type { Composite, Topic } from "$lib/services/models/lo-types";
+import type { Topic } from "$lib/services/models/lo-types";
 
 export const ssr = false;
 
 export const load: PageLoad = async ({ params, url, fetch }) => {
   let topicId = url.pathname;
-  let unitId = "";
-  let unitPos = topicId.indexOf("/unit");
-  if (unitPos !== -1) {
-    unitId = topicId.slice(unitPos + 1);
-    topicId = topicId.slice(0, unitPos);
-  }
-  let sidePos = topicId.indexOf("/side");
-  if (sidePos !== -1) {
-    unitId = topicId.slice(sidePos + 1);
-    topicId = topicId.slice(0, sidePos);
-  }
-  const topic = await courseService.readTopic(params.courseid, topicId, fetch) as Topic;
-  if (unitPos !== -1) {
-    const unitLo = topic.los.filter((lo) => lo.id == unitId);
-    currentLo.set(unitLo[0]);
-  } else {
-    currentLo.set(topic);
-    unitId = "";
-  }
+  const topic = (await courseService.readTopic(params.courseid, topicId, fetch)) as Topic;
 
   return {
-    topic: topic,
+    topic: topic
   };
 };
diff --git a/src/routes/(time)/next-time/[courseid]/+page.svelte b/src/routes/(time)/next-time/[courseid]/+page.svelte
index 572754409..326fc9e0d 100644
--- a/src/routes/(time)/next-time/[courseid]/+page.svelte
+++ b/src/routes/(time)/next-time/[courseid]/+page.svelte
@@ -57,90 +57,163 @@
   }
 </script>
 
-<style>
-  .active {
-    @apply bg-gray-200 font-bold;
-  }
-</style>
-
 <div class="flex">
   <!-- Side Tabs -->
   <div class="flex flex-col w-1/6 border-r border-gray-300">
-    <button type="button" class="p-4 text-left" class:active={$storeTab === 'Calendar'} on:click={() => handleTabChange('Calendar')} on:keydown={(e) => e.key === 'Enter' && handleTabChange('Calendar')}>Calendar</button>
-    <button type="button" class="p-4 text-left" class:active={$storeTab === 'Topics'} on:click={() => handleTabChange('Topics')} on:keydown={(e) => e.key === 'Enter' && handleTabChange('Topics')}>Topics</button>
-    <button type="button" class="p-4 text-left" class:active={$storeTab === 'Labs'} on:click={() => handleTabChange('Labs')} on:keydown={(e) => e.key === 'Enter' && handleTabChange('Labs')}>Labs</button>
+    <button
+      type="button"
+      class="p-4 text-left"
+      class:active={$storeTab === "Calendar"}
+      on:click={() => handleTabChange("Calendar")}
+      on:keydown={(e) => e.key === "Enter" && handleTabChange("Calendar")}>Calendar</button
+    >
+    <button
+      type="button"
+      class="p-4 text-left"
+      class:active={$storeTab === "Topics"}
+      on:click={() => handleTabChange("Topics")}
+      on:keydown={(e) => e.key === "Enter" && handleTabChange("Topics")}>Topics</button
+    >
+    <button
+      type="button"
+      class="p-4 text-left"
+      class:active={$storeTab === "Labs"}
+      on:click={() => handleTabChange("Labs")}
+      on:keydown={(e) => e.key === "Enter" && handleTabChange("Labs")}>Labs</button
+    >
   </div>
   <!-- Content Area -->
   <div class="flex-grow p-4 w-4/5">
     <!-- Top Tabs for Sub-items -->
     <div class="flex border-b border-gray-300 mb-4">
-      {#if $storeTab === 'Topics'}
+      {#if $storeTab === "Topics"}
         {#if instructorMode}
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'InstructorTopicView'} on:click={() => handleSubTabChange('InstructorTopicView')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('InstructorTopicView')}>Instructor Topic Time Heat-Map</button>
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'InstructorTopicViewPieChart'} on:click={() => handleSubTabChange('InstructorTopicViewPieChart')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('InstructorTopicViewPieChart')}>Instructor Topic Time Pie-Chart</button>
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'InstructorTopicViewBoxPlot'} on:click={() => handleSubTabChange('InstructorTopicViewBoxPlot')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('InstructorTopicViewBoxPlot')}>Instructor Topic Box Plot Chart</button>
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "InstructorTopicView"}
+            on:click={() => handleSubTabChange("InstructorTopicView")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("InstructorTopicView")}>Instructor Topic Time Heat-Map</button
+          >
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "InstructorTopicViewPieChart"}
+            on:click={() => handleSubTabChange("InstructorTopicViewPieChart")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("InstructorTopicViewPieChart")}>Instructor Topic Time Pie-Chart</button
+          >
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "InstructorTopicViewBoxPlot"}
+            on:click={() => handleSubTabChange("InstructorTopicViewBoxPlot")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("InstructorTopicViewBoxPlot")}>Instructor Topic Box Plot Chart</button
+          >
         {:else}
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'TopicView'} on:click={() => handleSubTabChange('TopicView')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('TopicView')}>Topic Time Heat-Map</button>
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'TopicViewPieChart'} on:click={() => handleSubTabChange('TopicViewPieChart')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('TopicViewPieChart')}>Topic Time Pie-Chart</button>
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "TopicView"}
+            on:click={() => handleSubTabChange("TopicView")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("TopicView")}>Topic Time Heat-Map</button
+          >
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "TopicViewPieChart"}
+            on:click={() => handleSubTabChange("TopicViewPieChart")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("TopicViewPieChart")}>Topic Time Pie-Chart</button
+          >
         {/if}
       {/if}
-      {#if $storeTab === 'Labs'}
+      {#if $storeTab === "Labs"}
         {#if instructorMode}
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'InstructorLabView'} on:click={() => handleSubTabChange('InstructorLabView')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('InstructorLabView')}>Instructor Lab Time Heat-Map</button>
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'InstructorLabViewBoxPlot'} on:click={() => handleSubTabChange('InstructorLabViewBoxPlot')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('InstructorLabViewBoxPlot')}>Instructor Lab Time Box Plot Chart</button>
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "InstructorLabView"}
+            on:click={() => handleSubTabChange("InstructorLabView")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("InstructorLabView")}>Instructor Lab Time Heat-Map</button
+          >
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "InstructorLabViewBoxPlot"}
+            on:click={() => handleSubTabChange("InstructorLabViewBoxPlot")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("InstructorLabViewBoxPlot")}>Instructor Lab Time Box Plot Chart</button
+          >
         {:else}
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'LabView'} on:click={() => handleSubTabChange('LabView')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('LabView')}>Lab Time Heat-Map</button>
-          <button type="button" class="p-2 text-left" class:active={$storeSubTab === 'LabViewPieChart'} on:click={() => handleSubTabChange('LabViewPieChart')} on:keydown={(e) => e.key === 'Enter' && handleSubTabChange('LabViewPieChart')}>Lab Time Pie-Chart</button>
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "LabView"}
+            on:click={() => handleSubTabChange("LabView")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("LabView")}>Lab Time Heat-Map</button
+          >
+          <button
+            type="button"
+            class="p-2 text-left"
+            class:active={$storeSubTab === "LabViewPieChart"}
+            on:click={() => handleSubTabChange("LabViewPieChart")}
+            on:keydown={(e) => e.key === "Enter" && handleSubTabChange("LabViewPieChart")}>Lab Time Pie-Chart</button
+          >
         {/if}
       {/if}
     </div>
     <!-- Main Content -->
     <div class="w-full">
-      {#if $storeTab === 'Calendar'}
+      {#if $storeTab === "Calendar"}
         {#if instructorMode}
-          <NewInstructorCalendarTime course={data.course} timeActiveMap={data.timeActiveMap} userIds={data.calendarIds} />
+          <NewInstructorCalendarTime
+            timeActiveMap={data.timeActiveMap}
+            userIds={data.calendarIds}
+            userNamesUseridsMap={data.userNamesUseridsMap}
+            userAvatarsUseridsMap={data.userAvatarsUseridsMap}
+          />
         {:else}
           <CalendarView timeActiveMap={data.timeActiveMap} session={data.session} medianTime={data.medianTime} />
         {/if}
-      {:else if $storeTab === 'Topics'}
+      {:else if $storeTab === "Topics"}
         {#if instructorMode}
-          {#if $storeSubTab === 'InstructorTopicView'}
-            <InstructorTopicView course={data.course} session={data.session} userIds={data.userIds} />
-          {:else if $storeSubTab === 'InstructorTopicViewPieChart'}
+          {#if $storeSubTab === "InstructorTopicView"}
+            <InstructorTopicView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
+          {:else if $storeSubTab === "InstructorTopicViewPieChart"}
             <InstructorTopicViewPieChart course={data.course} session={data.session} userIds={data.userIds} />
-          {:else if $storeSubTab === 'InstructorTopicViewBoxPlot'}
-            <InstructorTopicViewBoxPlot course={data.course} userIds={data.userIds} />
+          {:else if $storeSubTab === "InstructorTopicViewBoxPlot"}
+            <InstructorTopicViewBoxPlot course={data.course} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
           {:else}
-            <InstructorTopicView course={data.course} session={data.session} userIds={data.userIds} />
+            <InstructorTopicView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
           {/if}
+        {:else if $storeSubTab === "TopicView"}
+          <TopicView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
+        {:else if $storeSubTab === "TopicViewPieChart"}
+          <TopicViewPieChart course={data.course} session={data.session} userIds={data.userIds} />
         {:else}
-          {#if $storeSubTab === 'TopicView'}
-            <TopicView course={data.course} session={data.session} userIds={data.userIds} />
-          {:else if $storeSubTab === 'TopicViewPieChart'}
-            <TopicViewPieChart course={data.course} session={data.session} userIds={data.userIds} />
-          {:else}
-            <TopicView course={data.course} session={data.session} userIds={data.userIds} />
-          {/if}
+          <TopicView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
         {/if}
-      {:else if $storeTab === 'Labs'}
+      {:else if $storeTab === "Labs"}
         {#if instructorMode}
-          {#if $storeSubTab === 'InstructorLabView'}
-            <InstructorLabView course={data.course} session={data.session} userIds={data.userIds} />
-          {:else if $storeSubTab === 'InstructorLabViewBoxPlot'}
-            <InstructorLabViewBoxPlot course={data.course} userIds={data.userIds} />
+          {#if $storeSubTab === "InstructorLabView"}
+            <InstructorLabView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
+          {:else if $storeSubTab === "InstructorLabViewBoxPlot"}
+            <InstructorLabViewBoxPlot course={data.course} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
           {:else}
-            <InstructorLabView course={data.course} session={data.session} userIds={data.userIds} />
+            <InstructorLabView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
           {/if}
+        {:else if $storeSubTab === "LabView"}
+          <LabView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
+        {:else if $storeSubTab === "LabViewPieChart"}
+          <LabViewPieChart course={data.course} session={data.session} />
         {:else}
-          {#if $storeSubTab === 'LabView'}
-            <LabView course={data.course} session={data.session} userIds={data.userIds} />
-          {:else if $storeSubTab === 'LabViewPieChart'}
-            <LabViewPieChart course={data.course} session={data.session} />
-          {:else}
-            <LabView course={data.course} session={data.session} userIds={data.userIds} />
-          {/if}
+          <LabView course={data.course} session={data.session} userIds={data.userIds} userNamesUseridsMap={data.userNamesUseridsMap} />
         {/if}
       {/if}
     </div>
   </div>
 </div>
+
+<style>
+  .active {
+    @apply bg-gray-200 font-bold;
+  }
+</style>
diff --git a/src/routes/(time)/next-time/[courseid]/+page.ts b/src/routes/(time)/next-time/[courseid]/+page.ts
index 6c495f389..037245b4a 100644
--- a/src/routes/(time)/next-time/[courseid]/+page.ts
+++ b/src/routes/(time)/next-time/[courseid]/+page.ts
@@ -3,7 +3,7 @@ import { courseService } from "$lib/services/course";
 import type { Course } from "$lib/services/models/lo-types";
 import { aggregateTimeActiveByDate, decorateLearningRecords, fetchLearningInteractions } from "$lib/services/utils/supabase-metrics";
 import type { LearningInteraction } from "$lib/services/types/supabase-metrics";
-import { getCalendarDataForAll, getMedianTimeActivePerDate } from "$lib/services/utils/supabase-utils";
+import { getCalendarDataForAll, getGithubAvatarUrl, getMedianTimeActivePerDate, getUserNames } from "$lib/services/utils/supabase-utils";
 
 export const ssr = false;
 
@@ -13,6 +13,8 @@ export const load: PageLoad = async ({ parent, params, fetch }) => {
     const course: Course = await courseService.readCourse(params.courseid, fetch);
     const metrics: LearningInteraction[] = await fetchLearningInteractions(course);
     const userIds: string[] = [...new Set(metrics.map((m: LearningInteraction) => m.studentid))] as string[];
+    const userNamesUseridsMap: Map<string, string> = await getUserNames(userIds);
+    const userAvatarsUseridsMap: Map<string, string> = await getGithubAvatarUrl(userIds);
     const records: LearningInteraction[] = await getCalendarDataForAll(course.courseId);
     const aggregatedData = await aggregateTimeActiveByDate(records); 
     const medianCalendarTime = await getMedianTimeActivePerDate(course.courseId);
@@ -38,6 +40,8 @@ export const load: PageLoad = async ({ parent, params, fetch }) => {
     return {
       course: course,
       userIds: userIds,
+      userNamesUseridsMap: userNamesUseridsMap,
+      userAvatarsUseridsMap: userAvatarsUseridsMap,
       calendarIds: calendarIds, // Because of mismatch data in the dbs (reintroduce calendar)
       session: data.session,
       timeActiveMap: aggregatedData,