User: # Inputs

## Current File
Here is the file I'm looking at. It might be truncated from above and below and, if so, is centered around my cursor.
```src/utils/cultureCockpitMock.ts
import { ChartData } from "chart.js";
import _ from "lodash";
import { CSSProperties } from "react";
import { Dimension_Enum, GetQuestionnairesQuery, LatestQuestionnaireQuery } from "api/graphql_ch_admin_user";
import dayjs from "dayjs";
import LocalizedFormat from "dayjs/plugin/localizedFormat";
import { lighten } from "polished";
import { Analytics_Daily_Event_Summary_2, GetSpheresQuery } from "api/graphql_user";
import { DateValueType } from "react-tailwindcss-datepicker";
dayjs.extend(LocalizedFormat);

export type Dimension = "P" | "A" | "I";
export type Culture_Cockpit_Dropdown = {
  // its "any" type, due to spheres_overview
  value: any;
  label: string;
} | null;
export type EventsSummary = {
  bucket?: Date;
  channel_id: string;
  sphere_id: string;
  shape_id: string;
  post_id: string | null;
  post_publish_date: Date;
  summary: string;
};

export type ChartType = "sphere" | "shape";

export const color_perception = "rgba(127, 114, 245,1)";
export const color_perception_o = "rgba(127, 114, 245, 0.65)";
export const color_accessibility = "rgba(143, 240, 171,1)";
export const color_accessibility_o = "rgba(143, 240, 171, 0.8)";
export const color_importance = "rgba(197, 197, 197,1)";
export const color_importance_o = "rgba(197, 197, 197, 0.8)";
export const color_reference_benchmark = "rgb(255,37,27)";

export const charts_colors = ["#75EA89", "#55E2CA", "#5D58F9", "#77C4E5", "#399E90", "#6596CE"];

export const spheres_overview = { value: "", label: "Spheres overview" };

export const culture_cockpit_legend = [
  {
    id: "perception",
    title: "Perception",
    color: color_perception,
  },
  {
    id: "accessibility",
    title: "Accessibility",
    color: color_accessibility,
  },
  {
    id: "importance",
    title: "Importance",
    color: color_importance,
  },
  {
    id: "reference",
    title: "Reference Benchmark",
    color: color_reference_benchmark,
  },
];

function labelSplitter(labels: (string | undefined)[]) {
  const splitLongWords = (word: string): string[] => {
    if (word.length <= 15) return [word];

    const parts: string[] = [];
    let remaining = word;

    while (remaining.length > 15) {
      parts.push(remaining.slice(0, 15) + "-");
      remaining = remaining.slice(15);
    }

    parts.push(remaining);
    return parts;
  };

  return labels.map((phrase) => {
    return phrase
      ?.split(" ")
      .filter((word) => word.trim() !== "")
      .flatMap((word) => splitLongWords(word));
  });
}

// Culture cockpit polar chart (right) styles.
export function chartStyles(dimension: Dimension): CSSProperties {
  return {
    position: "absolute",
    ...(dimension === "P" && { zIndex: 3 }),
    ...(dimension === "A" && { zIndex: 2 }),
    ...(dimension === "I" && { zIndex: 1 }),
    width: 500,
    minWidth: 500,
    maxWidth: 500,
    height: 500,
    minHeight: 500,
    maxHeight: 500,

    top: "50%",
    left: "50%",
    ...(dimension === "P" && { transform: "translate(-50%, -50%)" }),
    ...(dimension === "A" && { transform: "translate(-50%, -50%) rotate(6deg)" }),
    ...(dimension === "I" && { transform: "translate(-50%, -50%) rotate(-6deg)" }),
  };
}

export const options_label_chart = {
  elements: {
    arc: {
      offset: -160,
    },
  },
  responsive: true,
  maintainAspectRatio: true,
  scales: {
    r: {
      min: -4,
      max: 10,
      beginAtZero: true,
      ticks: {
        display: false, // 0-10 scale,
        beginAtZero: true,
        stepSize: 1,
      },
      grid: {
        display: true,
        color: () => "rgba(0,0,0,0)",
      },
      angleLines: {
        display: false,
      },
      pointLabels: {
        display: true,
        callback: function (label: any) {
          if (typeof label !== 'string') {
            return label.match(/.{1,10}/g)?.join('\n');
          }
          return label;
        },
        centerPointLabels: true,
        padding: -80,
        font: function () {
          return { size: 10, weight: 'bold' };
        },
      },
    },
  },
  plugins: {
    legend: {
      display: false,
    },
    datalabels: {
      display: false,
      anchor: "end",
      align: "start",
      color: "white",
      font: {
        // weight: "bold",
        size: 24,
      },
    },
    tooltip: {
      enabled: false,
    },
  },
};

// Polar chart outer labels styles.
export const label_chart_styles: CSSProperties = {
  position: "absolute",

  zIndex: 0,
  minWidth: 700,
  maxWidth: 700,
  minHeight: 700,
  maxHeight: 700,

  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%)",
};

const genColor = (dimension: Dimension_Enum) => {
  if (dimension === "P")
    return {
      normal: color_perception,
      opacity: color_perception_o,
    };
  else if (dimension === "A")
    return {
      normal: color_accessibility,
      opacity: color_accessibility_o,
    };
  else if (dimension === "I")
    return {
      normal: color_importance,
      opacity: color_importance_o,
    };
};

const dimensions = [
  {
    label: "Perception",
    type: "P",
  },
  {
    label: "Accessibility",
    type: "A",
  },
  {
    label: "Importance",
    type: "I",
  },
];

export function generateChartData(
  labels: (string | undefined)[],
  data: LatestQuestionnaireQuery["questionnaire"][0]["questionnaire_results"],
  selected_dropdown_sphere_id?: string
): ChartData<"polarArea", number[], string>[] {
  /*
   * Generate polar chart data from questionnaire
   * Its generated based on each dimension, sphere and shape.
   * In sphere overview, we show averaged values of that sphere
   * and single sphere view, we show that sphere's shape dimension values
   *
   */

  if (!selected_dropdown_sphere_id) {
    let values_length = 0;
    const group_dimension = _.groupBy(data, "dimension");
    const calc_avg_per_sphere = _.mapValues(group_dimension, (items) => {
      const group_by_sphere = _.groupBy(items, "sphere_id");

      return _.map(group_by_sphere, (sphere) => {
        return Number(_.meanBy(sphere, "value").toFixed(1));
      });
    });

    const chart = dimensions.map((dimension, index) => {
      const dimension_answers = data.filter((answer) => answer.dimension === dimension.type);

      const group_sphere = _.groupBy(dimension_answers, "sphere_id");

      const avg_spheres = _.mapValues(group_sphere, (items) => {
        const total = _.sumBy(items, "value");
        const average = total / items.length;
        return Number(average.toFixed(1));
      });
      const averages = Object.values(avg_spheres);
      const color = genColor(dimension.type as Dimension_Enum);

      values_length = averages.length;

      if (index === 0) {
        return {
          labels: new Array(averages.length).fill(""),
          datasets: dimensions.map((d, i) => {
            const color = genColor(d.type as Dimension_Enum);
            return {
              label: d.label,
              backgroundColor: i === 0 ? color?.opacity : "rgba(0,0,0,0)",
              borderColor: color?.normal,
              borderWidth: 0,
              data: calc_avg_per_sphere[d.type],
              spacing: 160,
            };
          }),
        };
      }

      return {
        labels: new Array(averages.length).fill(""),
        datasets: [
          {
            label: dimension.label,
            backgroundColor: color?.opacity,
            borderColor: color?.normal,
            data: averages,
            spacing: 160,
            borderWidth: 0,
            borderRadius: 5,
          },
        ],
      };
    });
    const placeholder_data = new Array(values_length).fill(9);
    chart.push({
      labels: labelSplitter(labels),
      datasets: [
        {
          label: "a",
          backgroundColor: "rgba(255,0,0,0.0)",
          borderColor: "rgba(255,0,0,0.0)",
          data: placeholder_data,
          spacing: 160,
          borderWidth: 1,
          borderRadius: 5,
        },
      ],
    });

    return chart;
  } else {
    const dimension_results_filter = data.filter((answer) => answer.sphere_id === selected_dropdown_sphere_id);
    const grouped = _.groupBy(dimension_results_filter, "dimension");
    const sorted_by_code = _.mapValues(grouped, (row) => _.sortBy(row, (dimension) => dimension?.shape?.code));
    const result = _.mapValues(sorted_by_code, (group) => group.map((item) => item.value));
    const questionnaire_questions = _.mapValues(sorted_by_code, (group) => group.map((item) => item.question));

    let values_length = 0;

    const chart = dimensions.map((dimension, index) => {
      const dimension_answers = data.filter(
        (answer) => answer.dimension === dimension.type && answer.sphere_id === selected_dropdown_sphere_id
      );
      const dimension_answers_sorted = _.sortBy(dimension_answers, (row) => row?.shape?.code);

      const color = genColor(dimension.type as Dimension_Enum);
      const values = dimension_answers_sorted.map((a) => a.value);

      values_length = values.length;

      if (index === 0) {
        return {
          labels: new Array(values.length).fill(""),
          datasets: dimensions.map((d, i) => {
            const color = genColor(d.type as Dimension_Enum);

            const backgroundColor = result[d.type]?.map((res) => {
              if (i !== 0 || res === 0) {
                return "rgba(0,0,0,0)";
              } else {
                return color?.opacity;
              }
            });

            return {
              label: d.label,
              backgroundColor,
              borderColor: color?.normal,
              borderWidth: 0,
              data: result[d.type],
              spacing: 160,
              // custom data below
              question: questionnaire_questions[d.type],
            };
          }),
        };
      }

      const backgroundColor = values?.map((res) => {
        if (res === 0) {
          return "rgba(0,0,0,0)";
        } else {
          return color?.opacity;
        }
      });

      return {
        labels: new Array(values.length).fill(""),
        datasets: [
          {
            label: dimension.label,
            backgroundColor,
            borderColor: color?.normal,
            borderWidth: 0,
            data: values,
            spacing: 160,
          },
        ],
      };
    });

    const placeholder_data = new Array(values_length).fill(9);
    chart.push({
      labels: labelSplitter(labels),
      datasets: [
        {
          label: "a",
          backgroundColor: ["rgba(255,0,0,0.0)"],
          borderColor: "rgba(255,0,0,0.0)",
          borderWidth: 0,
          data: placeholder_data,
          spacing: 160,
        },
      ],
    });

    return chart;
  }
}

// @@@@@ 2nd PART ADDITIONS @@@@@

export const line_chart_options = {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    legend: {
      position: "top",
    },
  },
  scales: {
    x: {
      display: true,
    },
    y: {
      beginAtZero: true,
      display: true,
      pointLabels: {
        display: false,
      },
      min: 0,
      max: 10,
    },
  },
};

export function sampleThreeElements(arr: (number | string)[], numItems = 3): (number | string)[] {
  if (arr.length <= numItems) {
    return arr; // Return full array if its length is less than or equal to numItems
  }

  const result = [];
  const first = arr[0];
  const last = arr[arr.length - 1];

  result.push(first); // Always push the first element

  if (numItems === 2) {
    result.push(last); // If numItems is 2, just return [first, last]
    return result;
  }

  if (numItems === 3) {
    const middle = arr[Math.floor(arr.length / 2)];
    result.push(middle, last); // For 3, return [first, middle, last]
    return result;
  }

  // For numItems >= 4, pick items evenly from the array
  const step = (arr.length - 1) / (numItems - 1); // Step to select items evenly
  for (let i = 1; i < numItems - 1; i++) {
    const index = Math.round(step * i);
    result.push(arr[index]);
  }

  result.push(last); // Always include the last element
  return result;
}

function downsampleWithAveraging(arr: number[], sampleSize: number): number[] {
  const sampledArray = [];
  const groupSize = arr.length / sampleSize;

  for (let i = 0; i < sampleSize; i++) {
    const start = Math.floor(i * groupSize);
    const end = Math.min(Math.floor((i + 1) * groupSize), arr.length);
    const group = arr.slice(start, end);

    // Calculate the average for the group
    if (group.length > 0) {
      const avg = group.reduce((acc, val) => acc + val, 0) / group.length;
      sampledArray.push(avg);
    }
  }

  // If the input array has fewer elements than the sample size, duplicate last element to fill
  while (sampledArray.length < sampleSize) {
    sampledArray.push(sampledArray[sampledArray.length - 1]);
  }

  return sampledArray;
}

function stratifiedSampling(arr: number[], sampleSize: number): number[] {
  const sampledArray = [];
  const groupSize = arr.length / sampleSize;

  for (let i = 0; i < sampleSize; i++) {
    const index = Math.floor(i * groupSize);
    sampledArray.push(arr[index]);
  }

  // If the input array has fewer elements than the sample size, duplicate last element to fill
  while (sampledArray.length < sampleSize) {
    sampledArray.push(arr[arr.length - 1]); // duplicate the last element
  }

  return sampledArray;
}

function sampleProjectionData(data: number[], maxPoints = 100) {
  const totalPoints = data?.length;

  // If the dataset is already less than or equal to 100 points, return it as is.
  if (totalPoints <= maxPoints) {
    return data;
  }

  // Calculate the step size (how many points to skip between each sampled point).
  const step = Math.floor(totalPoints / maxPoints);

  // Create a new sampled array by picking every nth point.
  const sampledData = [];
  for (let i = 0; i < maxPoints; i++) {
    const index = Math.floor(i * step); // Calculate the exact index to pick
    sampledData.push(data[index]);
  }

  // If the sampledData array is less than maxPoints (due to rounding), add the last data point.
  if (sampledData.length < maxPoints) {
    sampledData.push(data[totalPoints - 1]);
  }

  return sampledData;
}

type ChartOptions = {
  label: string;
  data: {
    x: number;
    y: any;
  }[];
  fill: boolean;
  borderColor: string;
  backgroundColor: string;
  tension: number;
  pointStyle: string;
  pointRadius: number;
  borderWidth: number;
}[];
function generate_chart_data_by_shape(shape: GetQuestionnairesQuery["questionnaire"][0]["questionnaire_results"]): {
  data: ChartOptions;
  status: number;
} {
  const questionnaires_grouped = _.groupBy(shape, "dimension");
  const shape_length = shape?.length / 3;
  const step = 100 / (shape_length - 1);
  let status = 0;

  const questionnaire_results_dataset = _.mapValues(questionnaires_grouped, (row) => {
    let plot_color = color_perception;
    row?.map((r, index) => {
      if (r?.dimension === Dimension_Enum.P && row?.length - 1 === index) {
        status = r?.value;
      }

      if (r?.dimension === Dimension_Enum.A) plot_color = color_accessibility;
      else if (r?.dimension === Dimension_Enum.I) plot_color = color_importance;
    });
    const data = row?.map((r, index) => ({ x: step * index, y: r?.value }));

    return {
      label: "",
      data: data,
      fill: false,
      borderColor: plot_color,
      backgroundColor: lighten(0.2, plot_color),
      tension: 0,
      pointStyle: "rect",
      pointRadius: 0,
      borderWidth: 2,
    };
  });

  return {
    data: Object.values(questionnaire_results_dataset),
    status,
  };
}

export const generateSphereShapeChartData = (
  data: any[],
  type: ChartType,
  component?: "projection" | "questionnaire"
) => {
  const vals = data?.map((s) => parseFloat(s.summary.toFixed(1)));
  const labels = data?.map((s) => dayjs(s?.post_publish_date).format("MMM YYYY"));

  // Futher experiments with sampling can be done. Seems like stratifiedSampling gives good results overall

  // const sample_values = sampleProjectionData(vals)?.map((val, index) => ({ x: index, y: val }));
  const sample_values2 = stratifiedSampling(vals, 100)?.map((val, index) => ({ x: index, y: val }));
  // const sample_values3 = downsampleWithAveraging(vals, 100)?.map((val, index) => ({ x: index, y: val }));

  const sample_date = sampleThreeElements(labels);

  return {
    labels: sample_date,
    datasets: [
      {
        label: type === "shape" ? data?.[0]?.shape_name : "Average Progression",
        data: sample_values2,
        fill: type === "shape" || component === "questionnaire" ? false : true,
        borderColor: color_perception,
        backgroundColor: lighten(0.2, color_perception),
        tension: 0.4,
        pointStyle: "rect",
        pointRadius: 0,
        borderWidth: 1,
      },
    ],
  };
};

export const generateQuestionnaireComparisonChartData = (
  data: any[],
  questionnaire_results_arranged: GetQuestionnairesQuery["questionnaire"][0]["questionnaire_results"]
) => {
  const vals = data?.map((s) => parseFloat(s.summary.toFixed(1)));
  const labels = data?.map((s) => dayjs(s?.post_publish_date).format("MMM YYYY"));

  const sample_date = sampleThreeElements(labels, questionnaire_results_arranged?.length / 3);

  const questionnaire_results = generate_chart_data_by_shape(questionnaire_results_arranged);
  let questionnaire_results_status: "increase" | "decrease" = "increase";

  const projection_sampled = sampleProjectionData(vals);
  const projection_data = projection_sampled?.map((val, index) => {
    if (
      index === projection_sampled?.length - 1 &&
      questionnaire_results?.status &&
      questionnaire_results?.status < val
    ) {
      questionnaire_results_status = "decrease";
    }
    return { x: index, y: val };
  });

  return {
    labels: sample_date,
    datasets: [
      {
        label: data?.[0]?.shape_name,
        data: projection_data,
        fill: false,
        borderColor: questionnaire_results_status === "increase" ? color_perception : "red",
        backgroundColor: lighten(0.2, color_perception),
        tension: 0.4,
        pointStyle: "rect",
        pointRadius: 0,
        borderWidth: 1,
      },
      ...questionnaire_results?.data,
    ],
  };
};

export function calc_zwopr_score(
  questionnaire_results: LatestQuestionnaireQuery["questionnaire"][0]["questionnaire_results"]
) {
  const data = questionnaire_results?.filter((d) => d.shape?.enabled);

  const perception_answers = data.filter((answer) => answer.dimension === Dimension_Enum.P && answer?.shape?.enabled);
  const sort_shape_code = _.sortBy(perception_answers, (answer) => answer?.shape?.code);
  const group_spheres = _.groupBy(sort_shape_code, "sphere_id");
  const avg_spheres = _.mapValues(group_spheres, (items) => {
    const total = _.sumBy(items, "value");
    const average = total / items.length;
    return Number(average.toFixed(1));
  });
  const averages = Object.values(avg_spheres);
  const zwopr_score = Number(_.meanBy(averages).toFixed(1)) ?? 0;

  return zwopr_score;
}

export function calc_projection(
  questionnaire_results: GetQuestionnairesQuery["questionnaire"][0]["questionnaire_results"],
  questionnaire_uploaded_at: any,
  spheres: GetSpheresQuery["sphere"],
  data?: Analytics_Daily_Event_Summary_2[],
  channel_id?: string,
  until_date?: any
) {
  /**
   * Projection calculated for daily basis, starting from questionnaire created_at or
   * a specific "until_date" (param) due to jump from one questionnaire to another calculation of multiple questionnaires
   *
   * Start value is the shape's perception value and start date is questionnaire created_at or until_date (param)
   * For each day, we get specific shape and that shape's events.
   * For that day, if we have an event, we increase the summary value (see details below)
   * if we dont have an event, we do a small deduction.
   * And this calculation goes cumulatively, we take previous day's value and do next day's calculation.
   */
  const DEDUCTION = 0.008;
  const NUM_EMPLOYEES = 100;
  const CONSTANT = 15;
  const yesterday = until_date ? dayjs(until_date) : dayjs().subtract(1, "day");
  const holder = [];
  const perception_shapes = questionnaire_results?.filter((res) => res.dimension === Dimension_Enum.P);

  if (
    questionnaire_results &&
    perception_shapes &&
    perception_shapes?.length &&
    questionnaire_uploaded_at &&
    // data &&
    // Array.isArray(data) &&
    // data.length &&
    channel_id &&
    spheres
  ) {
    for (let i = 0; i < perception_shapes.length; i++) {
      const posts: EventsSummary[] = [];
      const shape = perception_shapes[i];
      const shape_enabled_date = shape?.shape?.enabled_date;

      for (
        let date = questionnaire_uploaded_at;
        date.isBefore(yesterday) || date.isSame(yesterday);
        date = date.add(1, "day")
      ) {
        const event_shape = data?.filter((event) => event?.shape_id === shape?.shape_id && shape?.shape?.enabled);

        if (!shape?.shape?.enabled) {
          break;
        }

        const event_for_date = event_shape?.find((event) => dayjs(event?.bucket).isSame(date, "day"));

        const DIVISOR = NUM_EMPLOYEES * CONSTANT;

        const summary = dayjs(date).isSame(questionnaire_uploaded_at, "day")
          ? shape?.value
          : posts[posts?.length - 1]?.summary;

        const before_enabling_shape = dayjs(date).isSameOrBefore(shape_enabled_date);

        if (event_for_date && event_shape) {
          event_shape
            ?.filter((event) => dayjs(event?.bucket).isSame(date, "day"))
            ?.map((event) => {
              const summary = dayjs(date).isSame(questionnaire_uploaded_at, "day")
                ? shape?.value
                : posts[posts?.length - 1]?.summary;

              posts.push({
                post_id: null,
                summary: Number(event?.summary) / DIVISOR + summary,
                post_publish_date: date.toISOString(),
                sphere_id: event?.sphere_id,
                shape_id: event?.shape_id,
                channel_id: event?.channel_id,
              });
            });
        } else {
          const _summary =
            dayjs(date).isSame(questionnaire_uploaded_at, "day") || before_enabling_shape
              ? summary
              : summary - DEDUCTION;
          posts.push({
            post_id: null,
            summary: _summary,
            post_publish_date: date?.toISOString(),
            sphere_id: shape?.sphere_id,
            shape_id: shape?.shape_id,
            channel_id: channel_id,
          });
        }
      }
      holder.push(posts);
    }
    const flatten = _.flatten(holder);
    const group_sphere = _.groupBy(flatten, "sphere_id");
    const name_sphere_shape = _.map(group_sphere, (sphere, key) => {
      const sphere_name = spheres.find((s) => s.id === key)?.name;
      const shape_name = sphere?.map((s) => {
        const name = spheres
          .filter((sp) => sp.id === s.sphere_id)?.[0]
          ?.shapes.find((sp) => sp.id === s.shape_id)?.name;

        return { ...s, shape_name: name, sphere_name };
      });

      return { [key]: shape_name };
    });
    const grouped_spheres_shapes = name_sphere_shape.map((obj) => {
      const grouped_obj: any = {};
      Object.entries(obj).forEach(([key, arr]) => {
        grouped_obj[key] = _.groupBy(arr, "shape_id");
      });
      return grouped_obj;
    });
    return grouped_spheres_shapes;
  } else {
    return [];
  }
}

export function calc_projection_sphere(posts_container: any[]) {
  if (posts_container?.length) {
    return _.map(posts_container, (subDicts, uuid) => {
      // Flatten all objects from deeply nested sub-dictionaries
      const flattenedData = _.flatMap(
        subDicts,
        (subDict) => _.flatMap(subDict, (items) => items) // One more level of flattening
      );

      // Group by the day extracted from 'post_publish_date'
      const groupedByDate = _.groupBy(flattenedData, (item) => {
        const date = new Date(item.post_publish_date);
        return date.toISOString().split("T")[0]; // Extract date part only
      });

      // Calculate average summary for each day
      const dailyAverages = _.map(groupedByDate, (items, date) => {
        const avgSummary = _.meanBy(items, "summary");
        return { post_publish_date: date, summary: avgSummary };
      });

      // Return the UUID along with its daily averages
      return { [Object.keys(subDicts)[0]]: dailyAverages };
    });
  }
  return [];
}

export function get_questionnaires_between(
  questionnaires: GetQuestionnairesQuery["questionnaire"],
  currentFilter?: Culture_Cockpit_Dropdown,
  compareFilter?: Culture_Cockpit_Dropdown
) {
  const current_questionnaire = questionnaires?.find((q) => q.id === currentFilter?.value);
  const compare_questionnaire = questionnaires?.find((q) => q.id === compareFilter?.value);

  const date_current = dayjs(current_questionnaire?.created_at);
  const date_compare = dayjs(compare_questionnaire?.created_at);

  const [start_date, end_date] = date_current.isBefore(date_compare)
    ? [date_current, date_compare]
    : [date_compare, date_current];

  const is_questionnaires_between = questionnaires.filter((q) => {
    const createdAt = dayjs(q.created_at);
    return createdAt.isAfter(start_date) && createdAt.isBefore(end_date);
  });

  return is_questionnaires_between;
}

export function calc_questionnaire_comparison_projection(
  questionnaires: GetQuestionnairesQuery["questionnaire"],
  currentFilter?: Culture_Cockpit_Dropdown,
  compareFilter?: Culture_Cockpit_Dropdown,
  spheres?: GetSpheresQuery["sphere"],
  data?: Analytics_Daily_Event_Summary_2[],
  channel_id?: string,
  selected_dropdown_sphere_id?: string
) {
  const current_questionnaire = questionnaires?.find((q) => q.id === currentFilter?.value);
  const compare_questionnaire = questionnaires?.find((q) => q.id === compareFilter?.value);

  if (current_questionnaire && compare_questionnaire && spheres) {
    const questionnaires_between = get_questionnaires_between(questionnaires, currentFilter, compareFilter);

    let questionnaires_comparison: GetQuestionnairesQuery["questionnaire"] = [];
    questionnaires_comparison.push(current_questionnaire);
    if (questionnaires_between?.length) {
      questionnaires_comparison.push(...questionnaires_between);
    }
    questionnaires_comparison.push(compare_questionnaire);
    const shapes = _.flatten(spheres?.map((sp) => sp.shapes));

    return questionnaires_comparison.map((q, index) => {
      let data_filtered_date = data?.filter((event) => {
        const event_date = dayjs(event?.bucket);
        if (index === 0) {
          const yesterday = dayjs().subtract(1, "day");

          return event_date.isAfter(q?.created_at) && event_date.isBefore(yesterday);
        } else {
          const previous_questionnaire = questionnaires_comparison[index - 1];
          return event_date.isAfter(q?.created_at) && event_date.isBefore(previous_questionnaire?.created_at);
        }
      });

      const non_existing_shapes = shapes?.filter((sh) => {
        return !data_filtered_date?.find((ev) => ev?.shape_id === sh.id);
      });
      const non_existing_spheres_shapes = non_existing_shapes?.map((sh) => {
        const sphere = spheres?.find((sp) => sp?.shapes?.some((shape) => shape?.id === sh.id));
        return {
          sphere_id: sphere?.id,
          shape_id: sh?.id,
        };
      });
      const filler_events: EventsSummary[] = non_existing_spheres_shapes?.map((item) => {
        return {
          channel_id: channel_id ?? "",
          post_id: null,
          post_publish_date: dayjs().toDate(),
          shape_id: item?.shape_id,
          sphere_id: item?.sphere_id,
          summary: "0",
        };
      });

      if (data_filtered_date) {
        data_filtered_date.push(...filler_events);
        _.sortBy(data_filtered_date, (ev) => dayjs(ev?.bucket));
      }

      if (index === 0) {
        const calculated_projection = calc_projection(
          q?.questionnaire_results,
          dayjs(q.created_at),
          spheres,
          data_filtered_date,
          channel_id
        );
        if (selected_dropdown_sphere_id) {
          return _.filter(calculated_projection, (obj) => _.has(obj, selected_dropdown_sphere_id));
        }

        return calculated_projection;
      } else {
        const previous_questionnaire = questionnaires_comparison[index - 1];

        const calculated_projection = calc_projection(
          q?.questionnaire_results,
          dayjs(q.created_at),
          spheres,
          data_filtered_date,
          channel_id,
          previous_questionnaire?.created_at
        );
        if (selected_dropdown_sphere_id) {
          return _.filter(calculated_projection, (obj) => _.has(obj, selected_dropdown_sphere_id));
        }
        return calculated_projection;
      }
    });
  } else return [];
}

export function calc_questionnaire_comparison_projection_merged(projections: any) {
  const merged: any = {};

  const flattened = _.flatten(projections.reverse());

  flattened.forEach((item: any) => {
    _.forEach(item, (value, key) => {
      merged[key] = _.mergeWith(merged[key], value, (objValue, srcValue) => {
        if (_.isArray(objValue)) {
          return objValue.concat(srcValue);
        }
      });
    });
  });
  return _.map(merged, (value, key) => ({ [key]: value }));
}

export function calc_avg_per_sphere(sphere: { [x: string]: any }) {
  const normalizedData: any = {};

  // Iterate over each key in the dictionary
  _.forEach(sphere, (items) => {
    // For each array inside the sub-object
    items.forEach((item: any) => {
      // Normalize the date (ignore time, only keep YYYY-MM-DD)
      const normalizedDate = dayjs(item.post_publish_date).format("YYYY-MM-DD");

      // If this date is not in the normalizedData yet, initialize it
      if (!normalizedData[normalizedDate]) {
        normalizedData[normalizedDate] = {
          totalSummary: 0,
          count: 0,
        };
      }

      // Add the summary to the total and increment the count
      normalizedData[normalizedDate].totalSummary += item.summary;
      normalizedData[normalizedDate].count += 1;
    });
  });

  // Calculate the average for each date
  const averages = _.map(normalizedData, (value, date) => {
    return {
      date,
      average: value.totalSummary / value.count,
    };
  });

  return averages;
}

type CalcAvgPerSphereType = {
  created_at: string;
  results: {
    dimension: string;
    avg: number;
  }[];
}[];
export function calc_questionnaires_avg_per_sphere(
  data: GetQuestionnairesQuery["questionnaire"],
  sphere_id: string
): CalcAvgPerSphereType {
  // Filter the array based on the sphere_id
  const filteredData = data.filter((item) => item.questionnaire_results.some((q) => q.sphere_id === sphere_id));

  // Process data day-by-day and per dimension
  const groupedResults = _(filteredData)
    // Group by 'created_at' day (using dayjs to normalize the timestamp to just a date)
    .groupBy((item) => dayjs(item.created_at).format("YYYY-MM-DD"))
    .map((itemsPerDay, day) => {
      // Collect all questionnaire_results from the items for the day
      const resultsForDay = _.flatMap(itemsPerDay, (item) =>
        item.questionnaire_results.filter((q) => q.sphere_id === sphere_id)
      );

      // Now group by 'dimension' within this day
      const resultsByDimension = _.groupBy(resultsForDay, "dimension");

      // Calculate the average value for each dimension and prepare the "results" array
      const results = _.map(resultsByDimension, (dimensionResults, dimension) => {
        const total = _.sumBy(dimensionResults, "value");
        const count = dimensionResults.length;
        const average = total / count;
        return {
          dimension: dimension,
          avg: parseFloat(average.toFixed(1)), // Round to 1 decimal and convert back to number
        };
      });

      // Return the structure with 'created_at' and 'results'
      return {
        created_at: day, // Keep the day as the formatted string
        results: results,
      };
    })
    .value();

  return groupedResults;
}

export function generate_chart_data_per_sphere(
  data: CalcAvgPerSphereType,
  labels: (string | number)[],
  num_of_questionnaires: number,
  projection_status?: "increase" | "decrease"
) {
  const step = 100 / (num_of_questionnaires - 1);

  const avg_results = _.flatten(data?.map((q) => q?.results));
  const avg_results_grouped = _.groupBy(avg_results, "dimension");

  return {
    labels: labels,
    datasets: _.map(avg_results_grouped, (q, index) => {
      let plot_color = color_perception;
      q?.map((r) => {
        if (r?.dimension === Dimension_Enum.P && projection_status === "decrease") plot_color = "red";
        if (r?.dimension === Dimension_Enum.A) plot_color = color_accessibility;
        else if (r?.dimension === Dimension_Enum.I) plot_color = color_importance;
      });
      return {
        label: "",
        data: q?.map((r, index) => ({ x: step * index, y: r?.avg })),
        fill: false,
        borderColor: plot_color,
        backgroundColor: lighten(0.2, color_perception),
        tension: 0.4,
        pointStyle: "rect",
        pointRadius: 0,
        borderWidth: 1,
      };
    }),
  };
}

export function calc_projection_with_questionnaires(
  questionnaires: GetQuestionnairesQuery["questionnaire"],
  spheres?: GetSpheresQuery["sphere"],
  data?: Analytics_Daily_Event_Summary_2[],
  channel_id?: string,
  selected_dropdown_sphere_id?: string
) {
  if (spheres && channel_id) {
    let questionnaires_comparison = questionnaires;
    const shapes = _.flatten(spheres?.map((sp) => sp.shapes));

    return questionnaires_comparison.map((q, index) => {
      let data_filtered_date = data?.filter((event) => {
        const event_date = dayjs(event?.bucket);
        if (index === 0) {
          const yesterday = dayjs().subtract(1, "day");

          return event_date.isAfter(q?.created_at) && event_date.isBefore(yesterday);
        } else {
          const previous_questionnaire = questionnaires_comparison[index - 1];
          return event_date.isAfter(q?.created_at) && event_date.isBefore(previous_questionnaire?.created_at);
        }
      });

      const non_existing_shapes = shapes?.filter((sh) => {
        return !data_filtered_date?.find((ev) => ev?.shape_id === sh.id);
      });
      const non_existing_spheres_shapes = non_existing_shapes?.map((sh) => {
        const sphere = spheres?.find((sp) => sp?.shapes?.some((shape) => shape?.id === sh.id));
        return {
          sphere_id: sphere?.id,
          shape_id: sh?.id,
        };
      });
      const filler_events: EventsSummary[] = non_existing_spheres_shapes?.map((item) => {
        return {
          channel_id: channel_id ?? "",
          post_id: null,
          post_publish_date: dayjs().toDate(),
          shape_id: item?.shape_id,
          sphere_id: item?.sphere_id,
          summary: "0",
        };
      });

      if (data_filtered_date) {
        data_filtered_date.push(...filler_events);
        _.sortBy(data_filtered_date, (ev) => dayjs(ev?.bucket));
      }

      if (index === 0) {
        const calculated_projection = calc_projection(
          q?.questionnaire_results,
          dayjs(q.created_at),
          spheres,
          data_filtered_date,
          channel_id
        );
        if (selected_dropdown_sphere_id) {
          return _.filter(calculated_projection, (obj) => _.has(obj, selected_dropdown_sphere_id));
        }

        return calculated_projection;
      } else {
        const previous_questionnaire = questionnaires_comparison[index - 1];

        const calculated_projection = calc_projection(
          q?.questionnaire_results,
          dayjs(q.created_at),
          spheres,
          data_filtered_date,
          channel_id,
          previous_questionnaire?.created_at
        );
        if (selected_dropdown_sphere_id) {
          return _.filter(calculated_projection, (obj) => _.has(obj, selected_dropdown_sphere_id));
        }
        return calculated_projection;
      }
    });
  } else return [];
}

export function filter_projections_by_custom_date(projections: any[], customFilterDate: DateValueType) {
  const start_date = customFilterDate?.startDate;
  const end_date = customFilterDate?.endDate;

  const startDate = dayjs(start_date);
  const endDate = dayjs(end_date);

  return projections.map((post) => {
    let filteredPost = {};

    _.forOwn(post, (subDict, postUUID) => {
      let filteredSubDict = {};

      _.forOwn(subDict, (postArray, subUUID) => {
        const filteredPosts = postArray.filter((postObj: any) => {
          const postDate = dayjs(postObj.post_publish_date);
          return postDate.isBetween(startDate, endDate, null, "[]");
        });

        if (filteredPosts.length > 0) {
          // @ts-expect-error
          filteredSubDict[subUUID] = filteredPosts;
        }
      });

      if (!_.isEmpty(filteredSubDict)) {
        // @ts-expect-error
        filteredPost[postUUID] = filteredSubDict;
      }
    });

    return filteredPost;
  });
}


```


User: ```typescript:src/utils/cultureCockpitMock.ts
export const options_label_chart = {
  elements: {
    arc: {
      offset: -160,
    },
  },
  responsive: true,
  maintainAspectRatio: true,
  scales: {
    r: {
      min: -4,
      max: 10,
      beginAtZero: true,
      ticks: {
        display: false, // 0-10 scale,
        beginAtZero: true,
        stepSize: 1,
      },
      grid: {
        display: true,
        color: () => "rgba(0,0,0,0)",
      },
      angleLines: {
        display: false,
      },
      pointLabels: {
        display: true,
        callback: function (label: any) {
          if (typeof label !== 'string') {
            return label;
          }
        },
        centerPointLabels: true,
        padding: -80,
        font: function () {
          return { size: 10, weight: 'bold' };
        },
      },
    },
  },
  plugins: {
    legend: {
      display: false,
    },
    datalabels: {
      display: false,
      anchor: "end",
      align: "start",
      color: "white",
      font: {
        // weight: "bold",
        size: 24,
      },
    },
    tooltip: {
      enabled: false,
    },
  },
};
```


How do I split the labels from the position I want instead of automatically
Assistant: 

To customize the splitting of labels in your chart configuration, especially for the `options_label_chart` object, you can modify the `callback` function within `pointLabels`. This function can be adjusted to split the label based on your specific requirements, such as splitting from a certain position or condition. 

For instance, if you want to split the label after a certain number of characters, you can implement a custom logic inside the `callback` function. Here's an example that splits the label after every 10 characters and adds a line break (`\n`), which will be recognized by Chart.js to display the label on multiple lines:

```typescript:src/utils/cultureCockpitMock.ts
export const options_label_chart = {
  // ... existing configuration ...
  scales: {
    r: {
      // ... other scale settings ...
      pointLabels: {
        display: true,
        callback: function (label: any) {
          if (typeof label === 'string') {
            // Split the label every 10 characters
            return label.match(/.{1,10}/g)?.join('\n');
          }
          return label;
        },
        // ... other pointLabels settings ...
      },
    },
  },
  // ... remaining configuration ...
};
```


In this example, the regular expression `/.{1,10}/g` is used to match every sequence of up to 10 characters in the label string. The `join('\n')` then combines these sequences into a single string with line breaks, which Chart.js will render as multi-line labels.

This approach allows you to have more control over how labels are displayed, especially when dealing with long strings that could clutter the chart. Adjust the number `10` in the regular expression to change the split position according to your needs.
User: 

TypeError: label.match is not a function

I get this error
Assistant: