Skip to main content

Overview

The Dev Showcase project uses Chart.js to visualize vocational assessment data and skill proficiency metrics. The charts manager handles multiple chart types including radar charts for RIASEC profiles, cognitive assessments, and horizontal bar charts for various skill categories.

Chart Types

Radar Charts

RIASEC vocational profiles and cognitive assessments

Bar Charts

Skill proficiency, interests, and career recommendations

Dynamic Data

Real-time updates based on language selection

RIASEC Vocational Chart

Overview

The RIASEC (Holland Codes) chart visualizes six vocational personality types:
  • Realistic: Practical, hands-on work
  • Investigative: Analytical, scientific thinking
  • Artistic: Creative, expressive activities
  • Social: Helping, teaching, counseling
  • Enterprising: Leading, persuading, business
  • Conventional: Organizing, data management

Color Configuration

charts-manager.js (lines 11-18)
const RIASEC_COLORS = {
    realistic: { border: '#c2185b', fill: 'rgba(194,24,91,0.18)', point: '#c2185b' },
    investigative: { border: '#e91e63', fill: 'rgba(233,30,99,0.18)', point: '#e91e63' },
    artistic: { border: '#ffb74d', fill: 'rgba(255,183,77,0.18)', point: '#ffb74d' },
    social: { border: '#ff9800', fill: 'rgba(255,152,0,0.18)', point: '#ff9800' },
    enterprising: { border: '#ff7043', fill: 'rgba(255,112,67,0.22)', point: '#ff7043' },
    conventional: { border: '#ec407a', fill: 'rgba(236,64,122,0.15)', point: '#ec407a' }
};

Data Structure

The radar chart builds datasets from translation files:
charts-manager.js (lines 28-55)
const buildRadarData = () => {
    const voc = getVoc();
    const nums = getChartData()?.riasec ?? {};

    const makeDs = (key, col) => ({
        label: voc.datasets?.[key] ?? key,
        data: nums[key] ?? [],
        backgroundColor: col.fill,
        borderColor: col.border,
        borderWidth: 1.5,
        pointBackgroundColor: col.point,
        pointBorderColor: '#fff',
        pointRadius: 3,
        pointHoverRadius: 5
    });

    return {
        labels: voc.radarLabels ?? [],
        datasets: [
            makeDs('realistic', RIASEC_COLORS.realistic),
            makeDs('investigative', RIASEC_COLORS.investigative),
            makeDs('artistic', RIASEC_COLORS.artistic),
            makeDs('social', RIASEC_COLORS.social),
            makeDs('enterprising', RIASEC_COLORS.enterprising),
            makeDs('conventional', RIASEC_COLORS.conventional)
        ]
    };
};

Chart Options

charts-manager.js (lines 115-136)
const radarOptions = {
    responsive: true, 
    maintainAspectRatio: false,
    plugins: {
        legend: { display: false },
        tooltip: {
            backgroundColor: 'rgba(10,3,47,0.92)', 
            titleColor: '#fff',
            bodyColor: 'rgba(255,255,255,0.85)', 
            borderColor: 'rgba(255,255,255,0.15)',
            borderWidth: 1, 
            padding: 10, 
            cornerRadius: 8,
            callbacks: { 
                label: ctx => `${ctx.dataset.label}: ${ctx.parsed.r}%` 
            }
        }
    },
    scales: {
        r: {
            beginAtZero: true, 
            max: 100,
            ticks: { 
                stepSize: 20, 
                color: 'rgba(0,0,0,0)', 
                backdropColor: 'transparent', 
                font: { size: 9 } 
            },
            grid: { 
                color: 'rgba(255,255,255,0.15)', 
                circular: true 
            },
            pointLabels: { 
                color: 'rgb(160,161,163)', 
                font: { size: 12, weight: '600' }, 
                padding: 12 
            },
            angleLines: { 
                color: 'rgba(255,255,255,0.12)' 
            }
        }
    },
    animation: { 
        duration: 800, 
        easing: 'easeInOutQuart' 
    }
};

Cognitive Assessment Chart

Purpose

The cognitive chart compares personal scores against general averages across multiple cognitive dimensions.

Color Scheme

charts-manager.js (lines 20-23)
const COGNITIVE_COLORS = {
    mine: { border: '#4fc3f7', fill: 'rgba(79,195,247,0.22)', point: '#4fc3f7' },
    avg: { border: '#f06292', fill: 'rgba(240,98,146,0.15)', point: '#f06292' }
};

Data Builder

charts-manager.js (lines 57-82)
const buildCognitiveData = () => {
    const voc = getVoc();
    const nums = getChartData()?.cognitive ?? {};

    const makeDs = (key, label, col, extra = {}) => ({
        label,
        data: nums[key] ?? [],
        backgroundColor: col.fill,
        borderColor: col.border,
        borderWidth: 2,
        pointBackgroundColor: col.point,
        pointBorderColor: '#ffffff',
        pointRadius: 5,
        pointHoverRadius: 7,
        pointBorderWidth: 2,
        ...extra
    });

    return {
        labels: voc.cognitiveLabels ?? [],
        datasets: [
            makeDs('mine', voc.cognitiveMe ?? 'My Score', COGNITIVE_COLORS.mine),
            makeDs('avg', voc.cognitiveAvg ?? 'General Average', COGNITIVE_COLORS.avg, { 
                borderDash: [6, 3], 
                pointRadius: 4, 
                pointHoverRadius: 6 
            })
        ]
    };
};
The average dataset uses a dashed border (borderDash: [6, 3]) to distinguish it visually from the personal score.

Bar Charts

Bar Chart Categories

The charts manager handles seven different bar chart types:
  1. Top Skills - Highest proficiency areas
  2. Top Interests - Primary interest areas
  3. Top Careers - Best career matches
  4. Low Skills - Areas for development
  5. Low Interests - Lower interest areas
  6. Low Careers - Less suitable careers
  7. Occupational Values - Work value preferences

Color Scheme

charts-manager.js (lines 25-26)
const BAR_TOP = { bg: 'rgba(79,195,247,0.75)', border: 'rgba(79,195,247,1)' };
const BAR_LOW = { bg: 'rgba(236,64,122,0.70)', border: 'rgba(236,64,122,1)' };
  • Blue (BAR_TOP): Positive metrics (top skills, interests, careers)
  • Pink (BAR_LOW): Areas for improvement (low skills, interests)

Data Structure

charts-manager.js (lines 94-113)
const buildBarData = () => {
    const voc = getVoc();
    const labels = voc.barLabels ?? {};
    const nums = getChartData()?.bars ?? {};

    const make = (key, color) => ({
        labels: labels[key] ?? [],
        datasets: makeBarDataset(nums[key] ?? [], color)
    });

    return {
        topSkills: make('topSkills', BAR_TOP),
        topInterests: make('topInterests', BAR_TOP),
        topCareers: make('topCareers', BAR_TOP),
        lowSkills: make('lowSkills', BAR_LOW),
        lowInterests: make('lowInterests', BAR_LOW),
        lowCareers: make('lowCareers', BAR_LOW),
        valoresOcupacionales: make('valoresOcupacionales', BAR_TOP)
    };
};

Bar Chart Options

charts-manager.js (lines 161-186)
const barOptions = {
    indexAxis: 'y', // Horizontal bars
    responsive: true, 
    maintainAspectRatio: false,
    plugins: {
        legend: { display: false },
        tooltip: {
            backgroundColor: 'rgba(10,3,47,0.92)', 
            padding: 12, 
            titleColor: '#fff',
            bodyColor: 'rgba(255,255,255,0.85)', 
            borderColor: 'rgba(255,255,255,0.15)',
            borderWidth: 1, 
            cornerRadius: 8,
            callbacks: { 
                label: ctx => '  ' + ctx.parsed.x.toFixed(1) + '%' 
            }
        }
    },
    scales: {
        x: {
            beginAtZero: true, 
            max: 100,
            grid: { 
                color: 'rgba(255,255,255,0.06)', 
                drawBorder: false 
            },
            border: { display: false },
            ticks: { 
                color: 'rgba(255,255,255,0.45)', 
                font: { size: 10 }, 
                callback: v => v + '%', 
                maxTicksLimit: 5 
            }
        },
        y: {
            grid: { display: false }, 
            border: { display: false },
            ticks: { 
                color: 'rgba(255,255,255,0.9)', 
                font: { size: 11, weight: '600' }, 
                padding: 6 
            }
        }
    },
    layout: { padding: { right: 8 } },
    animation: { 
        duration: 700, 
        easing: 'easeInOutQuart' 
    }
};

Chart Lifecycle Management

Section-Based Chart Display

Charts are created and destroyed based on the active section:
charts-manager.js (lines 188-193)
const SECTION_BAR_CHARTS = {
    profile: ['topSkillsChart', 'topInterestsChart', 'topCareersChart',
        'lowSkillsChart', 'lowInterestsChart', 'lowCareersChart'],
    aptitudes: ['valoresOcupacionalesChart'],
    proyecto: []
};

Creation and Destruction

charts-manager.js (lines 243-265)
const createBarCharts = () => {
    const sectionKey = getActiveSectionKey();
    const allowedIds = SECTION_BAR_CHARTS[sectionKey] ?? [];
    if (!allowedIds.length) return;

    const bar = buildBarData();

    const ALL_PAIRS = [
        ['topSkillsChart', bar.topSkills],
        ['topInterestsChart', bar.topInterests],
        ['topCareersChart', bar.topCareers],
        ['lowSkillsChart', bar.lowSkills],
        ['lowInterestsChart', bar.lowInterests],
        ['lowCareersChart', bar.lowCareers],
        ['valoresOcupacionalesChart', bar.valoresOcupacionales]
    ];

    ALL_PAIRS.forEach(([id, data]) => {
        if (!allowedIds.includes(id)) return;
        const ctx = document.getElementById(id);
        if (ctx) barCharts[id] = new Chart(ctx, { type: 'bar', data, options: barOptions });
    });
};

Language Support

Translation Function

charts-manager.js (lines 7-9)
const getT = () => window.currentTranslations ?? {};
const getVoc = () => getT()?.skills?.vocational ?? {};
const getChartData = () => getT()?.chartsData ?? {};

Data from Language Files

Chart labels and data come from JSON translation files:
en.json (example structure)
{
  "chartsData": {
    "riasec": {
      "realistic": [75, 82, 68, 90, 78, 85],
      "investigative": [88, 92, 85, 90, 87, 89],
      "artistic": [65, 70, 60, 72, 68, 75],
      "social": [78, 80, 75, 85, 82, 88],
      "enterprising": [70, 68, 72, 75, 73, 80],
      "conventional": [82, 85, 80, 88, 84, 90]
    },
    "cognitive": {
      "mine": [45, 52, 48, 50, 46, 53],
      "avg": [40, 42, 41, 43, 40, 44]
    },
    "bars": {
      "topSkills": [92, 88, 85, 82, 78],
      "topInterests": [90, 87, 84, 80, 76],
      "topCareers": [88, 85, 82, 79, 75]
    }
  },
  "skills": {
    "vocational": {
      "radarLabels": ["Problem Solving", "Creativity", "Leadership", ...],
      "cognitiveLabels": ["Verbal", "Numerical", "Spatial", ...],
      "barLabels": {
        "topSkills": ["Programming", "Data Analysis", "Web Development", ...]
      }
    }
  }
}

Global Refresh Function

Charts are refreshed when language changes:
charts-manager.js (lines 267-274)
window.refreshVocationalCharts = () => {
    const statsContent = document.getElementById('vocational-stats-content');
    if (!statsContent?.classList.contains('active')) return;
    updateRadarLabels();
    updateCognitiveLabels();
    destroyBarCharts();
    createBarCharts();
};

MutationObserver Pattern

Charts are automatically created/destroyed when sections become visible:
charts-manager.js (lines 296-320)
if (vocationalStatsContent) {
    let wasActive = vocationalStatsContent.classList.contains('active');

    const observer = new MutationObserver(() => {
        const isActive = vocationalStatsContent.classList.contains('active');

        if (!wasActive && isActive) {
            setTimeout(() => {
                createRadar();
                destroyBarCharts();
                createBarCharts();
                syncCognitiveChart();
            }, 150);
        } else if (wasActive && !isActive) {
            destroyBarCharts();
            destroyRadar();
            destroyCognitiveChart();
        }

        wasActive = isActive;
    });

    observer.observe(vocationalStatsContent, { 
        attributes: true, 
        attributeFilter: ['class'] 
    });
}
The 150ms delay ensures the DOM has fully updated before creating charts.

Performance Considerations

Charts are properly destroyed before creating new ones to prevent memory leaks:
Object.values(barCharts).forEach(chart => chart?.destroy?.());
Charts are only created when their section becomes visible, reducing initial page load:
if (!radarChart) return; // Skip if already created
Only relevant charts for each section are rendered:
const allowedIds = SECTION_BAR_CHARTS[sectionKey] ?? [];
if (!allowedIds.includes(id)) return;

HTML Canvas Elements

Charts render to canvas elements in the HTML:
<canvas id="vocationalRadarChart" width="400" height="400"></canvas>
<canvas id="cognitiveRadarChart" width="400" height="400"></canvas>
<canvas id="topSkillsChart" width="600" height="300"></canvas>

Dependencies

The charts manager requires:
  • Chart.js (v3+)
  • Language translation system (window.currentTranslations)
  • Proper HTML structure with canvas elements
Ensure Chart.js is loaded before the charts-manager.js script.

Build docs developers (and LLMs) love