How many theories does the Atlas include?
- The Atlas visualizes over 325 individual theories, grouped into ten + The Atlas visualizes over 220 individual theories, grouped into ten broad categories.
-
Can I use the Atlas data for research?
-- Yes. Cite the Atlas and the original sources. If you plan wide - redistribution or commercial use, please contact the team for - licensing details. -
-
-
-
-
-
-
= {
// 1.1 Philosophical Theories
'Eliminative': 'Eliminative Materialism / Illusionism',
diff --git a/src/main.ts b/src/main.ts
index feef68b..6d59dc2 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -8,6 +8,7 @@ import { SearchBar } from '@/components/SearchBar'
import { FormPopup } from '@/components/FormPopup'
import { Router } from '@/utils/routing'
import globalState from '@/utils/globalState'
+import analytics from '@/utils/analytics'
import './components/ItemDetailsPanel.scss'
import './components/FormPopup.scss'
@@ -30,7 +31,51 @@ new SearchBar('search-container', router)
const feedbackButton = document.getElementById('feedback-button')
if (feedbackButton) {
- feedbackButton.addEventListener('click', () => formPopup.show())
+ feedbackButton.addEventListener('click', (event) => {
+ const rect = feedbackButton.getBoundingClientRect()
+ analytics.trackClick('feedback_button', {
+ x: event.clientX - rect.left,
+ y: event.clientY - rect.top
+ })
+ formPopup.show()
+ })
+}
+
+const aboutButton = document.getElementById('about-button')
+if (aboutButton) {
+ aboutButton.addEventListener('click', () => {
+ window.scrollBy({
+ top: window.innerHeight,
+ behavior: 'smooth'
+ })
+ })
+
+ // Handle scroll behavior - fade out when scrolling down
+ let lastScrollY = window.scrollY
+ let ticking = false
+
+ const updateAboutButton = () => {
+ const currentScrollY = window.scrollY
+ const scrollThreshold = 100 // Start fading after 100px scroll
+
+ if (currentScrollY > scrollThreshold) {
+ aboutButton.classList.add('fade-out')
+ } else {
+ aboutButton.classList.remove('fade-out')
+ }
+
+ lastScrollY = currentScrollY
+ ticking = false
+ }
+
+ const onScroll = () => {
+ if (!ticking) {
+ requestAnimationFrame(updateAboutButton)
+ ticking = true
+ }
+ }
+
+ window.addEventListener('scroll', onScroll, { passive: true })
}
const logoContainer = document.querySelector('.logo-container')
@@ -44,6 +89,52 @@ if (logoContainer) {
itemDetailsPanel.setCloseCallback(() => router.goHome())
+// Add click tracking for external links
+const addLinkTracking = () => {
+ // GitHub link
+ const githubLink = document.querySelector('.github-link')
+ if (githubLink) {
+ githubLink.addEventListener('click', (event) => {
+ const rect = githubLink.getBoundingClientRect()
+ analytics.trackClick('github_link', {
+ x: (event as MouseEvent).clientX - rect.left,
+ y: (event as MouseEvent).clientY - rect.top
+ }, 'https://github.com')
+ })
+ }
+
+ // Kuhn paper links
+ const kuhnLinks = document.querySelectorAll('a[href*="sciencedirect.com"]')
+ kuhnLinks.forEach(link => {
+ link.addEventListener('click', (event) => {
+ const rect = link.getBoundingClientRect()
+ analytics.trackClick('kuhn_paper_link', {
+ x: (event as MouseEvent).clientX - rect.left,
+ y: (event as MouseEvent).clientY - rect.top
+ }, link.getAttribute('href') || '')
+ })
+ })
+
+ // Twitter/X link
+ const twitterLink = document.querySelector('a[href*="x.com"]')
+ if (twitterLink) {
+ twitterLink.addEventListener('click', (event) => {
+ const rect = twitterLink.getBoundingClientRect()
+ analytics.trackClick('twitter_link', {
+ x: (event as MouseEvent).clientX - rect.left,
+ y: (event as MouseEvent).clientY - rect.top
+ }, twitterLink.getAttribute('href') || '')
+ })
+ }
+}
+
+// Initialize link tracking after DOM is loaded
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', addLinkTracking)
+} else {
+ addLinkTracking()
+}
+
router.setLoadingCallback((category, theory) => {
itemDetailsPanel.showLoading(category, theory)
})
diff --git a/src/styles/main.scss b/src/styles/main.scss
index 3578014..fe3087b 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -6,6 +6,10 @@
font-weight: 400;
}
+body {
+ background: #1b1b1b;
+}
+
.app-container {
display: flex;
height: 100vh;
@@ -91,7 +95,7 @@
button {
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.2);
- color: #3b82f6;
+ color: #649cf9;
padding: 10px 16px;
border-radius: 8px;
cursor: pointer;
@@ -174,6 +178,86 @@
}
}
+.about-btn {
+ position: fixed;
+ bottom: 24px;
+ right: 24px;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ background: rgba(247, 184, 1, 0.1);
+ border: 1px solid rgba(247, 184, 1, 0.5);
+ color: #f7b801;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(10px);
+ opacity: 1;
+ transform: translateY(0);
+
+ &:hover {
+ background: rgba(247, 184, 1, 0.15);
+ border-color: rgba(247, 184, 1, 0.7);
+ transform: translateY(-1px) scale(1.05);
+ }
+
+ &:focus {
+ outline: 2px solid rgba(247, 184, 1, 0.5);
+ outline-offset: 2px;
+ }
+
+ svg {
+ width: 20px;
+ height: 20px;
+ transition: all 0.2s ease;
+ }
+
+ .tooltip {
+ position: absolute;
+ bottom: 50px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(0, 0, 0, 0.9);
+ color: #ffffff;
+ padding: 6px 12px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 500;
+ white-space: nowrap;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.2s ease;
+ pointer-events: none;
+ backdrop-filter: blur(10px);
+ border: 1px solid rgba(247, 184, 1, 0.3);
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ border: 4px solid transparent;
+ border-top-color: rgba(0, 0, 0, 0.9);
+ }
+ }
+
+ &:hover .tooltip {
+ opacity: 1;
+ visibility: visible;
+ transform: translateX(-50%) translateY(-4px);
+ }
+
+ &.fade-out {
+ opacity: 0;
+ transform: translateY(20px);
+ pointer-events: none;
+ }
+}
+
#theory-search {
width: 100%;
padding: 12px 16px 12px 40px;
@@ -263,20 +347,25 @@
.logo-container {
position: fixed;
- top: 16px;
+ top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
+ padding: 0;
+ margin: 0;
.logo {
- width: 120px;
- height: 19px;
+ height: 27px;
+ padding: 6px 10px;
+ margin: 0;
+ backdrop-filter: blur(6px);
+ border-radius: 8px;
}
}
.search-section {
position: fixed;
- top: 50px;
+ top: 70px;
left: 16px;
right: 16px;
width: auto;
@@ -284,7 +373,7 @@
}
.github-link {
- top: 16px;
+ top: 20px;
right: 16px;
width: 36px;
height: 36px;
@@ -303,20 +392,33 @@
width: auto;
max-width: calc(100vw - 32px);
}
+
+ .about-btn {
+ bottom: 16px;
+ right: 16px;
+ width: 36px;
+ height: 36px;
+
+ svg {
+ width: 18px;
+ height: 18px;
+ }
+ }
}
@media (max-width: 480px) {
.logo-container {
- top: 12px;
+ top: 16px;
.logo {
width: 100px;
height: 16px;
+ padding: 10px 14px;
}
}
.search-section {
- top: 40px;
+ top: 60px;
left: 12px;
right: 12px;
}
@@ -329,6 +431,18 @@
font-size: 12px;
}
+ .about-btn {
+ bottom: 12px;
+ right: 12px;
+ width: 32px;
+ height: 32px;
+
+ svg {
+ width: 16px;
+ height: 16px;
+ }
+ }
+
#theory-search {
padding: 10px 12px 10px 36px;
font-size: 13px;
@@ -341,7 +455,7 @@
}
.github-link {
- top: 12px;
+ top: 16px;
right: 12px;
width: 32px;
height: 32px;
diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts
new file mode 100644
index 0000000..101e2dd
--- /dev/null
+++ b/src/utils/analytics.ts
@@ -0,0 +1,103 @@
+import mixpanel from 'mixpanel-browser'
+
+interface AnalyticsConfig {
+ token: string
+ enabled: boolean
+}
+
+class Analytics {
+ private mixpanel: typeof mixpanel | null = null
+ private config: AnalyticsConfig
+
+ constructor() {
+ this.config = {
+ token: import.meta.env.VITE_MIXPANEL_TOKEN || '',
+ enabled: !!import.meta.env.VITE_MIXPANEL_TOKEN && import.meta.env.PROD
+ }
+
+ if (this.config.enabled) {
+ this.initialize()
+ }
+ }
+
+ private initialize() {
+ try {
+ mixpanel.init(this.config.token, {
+ debug: !import.meta.env.PROD,
+ track_pageview: false, // We'll handle page views manually
+ persistence: 'localStorage'
+ })
+ this.mixpanel = mixpanel
+ } catch (error) {
+ console.error('Failed to initialize Mixpanel:', error)
+ }
+ }
+
+ private track(event: string, properties?: Record) {
+ if (!this.mixpanel || !this.config.enabled) {
+ return
+ }
+
+ try {
+ this.mixpanel.track(event, {
+ ...properties,
+ timestamp: new Date().toISOString(),
+ url: window.location.href,
+ user_agent: navigator.userAgent
+ })
+ } catch (error) {
+ console.error('Failed to track event:', error)
+ }
+ }
+
+ // Page view tracking for theory navigation
+ trackPageView(theoryName: string, category: string, subcategory?: string) {
+ this.track('Page View', {
+ theory_name: theoryName,
+ category: category,
+ subcategory: subcategory,
+ page_type: 'theory'
+ })
+ }
+
+ // Click tracking for various elements
+ trackClick(element: string, position?: { x: number; y: number }, linkUrl?: string) {
+ this.track('Click', {
+ element: element,
+ position: position,
+ link_url: linkUrl,
+ click_type: 'interaction'
+ })
+ }
+
+ // Form submission tracking
+ trackFormSubmission(formType: string, success: boolean, errorMessage?: string) {
+ this.track('Form Submission', {
+ form_type: formType,
+ success: success,
+ error_message: errorMessage,
+ submission_type: 'feedback'
+ })
+ }
+
+ // Identify user (optional, for future use)
+ identify(userId: string, properties?: Record) {
+ if (!this.mixpanel || !this.config.enabled) {
+ return
+ }
+
+ try {
+ this.mixpanel.identify(userId)
+ if (properties) {
+ this.mixpanel.people.set(properties)
+ }
+ } catch (error) {
+ console.error('Failed to identify user:', error)
+ }
+ }
+}
+
+// Create singleton instance
+const analytics = new Analytics()
+
+export default analytics
diff --git a/src/utils/apiMock.ts b/src/utils/apiMock.ts
index 82bdb84..77c7349 100644
--- a/src/utils/apiMock.ts
+++ b/src/utils/apiMock.ts
@@ -1,9 +1,6 @@
-// Mock API for development when not using Vercel CLI
export const mockApiSubmit = async (data: { name: string; email: string; message: string }) => {
- // Simulate API delay
await new Promise(resolve => setTimeout(resolve, 1000))
- // Log to console for development
console.log('๐จ Mock form submission:', {
name: data.name || 'Not provided',
email: data.email || 'Not provided',
@@ -11,14 +8,12 @@ export const mockApiSubmit = async (data: { name: string; email: string; message
timestamp: new Date().toISOString()
})
- // Simulate success response
return {
success: true,
message: 'Form submitted successfully (mock)'
}
}
-// Check if we're in development and API is not available
export const isApiAvailable = () => {
return typeof window !== 'undefined' && window.location.hostname !== 'localhost'
}
diff --git a/src/utils/chartUtils.ts b/src/utils/chartUtils.ts
index 27d13ed..318fdc4 100644
--- a/src/utils/chartUtils.ts
+++ b/src/utils/chartUtils.ts
@@ -1,9 +1,6 @@
import type { ChartDataItem } from '@/types/chart'
export const chartUtils = {
- /**
- * Calculate the total value of a chart data tree
- */
calculateTotal(data: ChartDataItem[]): number {
return data.reduce((total, item) => {
const itemValue = item.value || 0
@@ -12,9 +9,6 @@ export const chartUtils = {
}, 0)
},
- /**
- * Flatten chart data into a single array
- */
flattenData(data: ChartDataItem[]): ChartDataItem[] {
const result: ChartDataItem[] = []
@@ -31,9 +25,6 @@ export const chartUtils = {
return result
},
- /**
- * Find a specific item in the chart data
- */
findItem(data: ChartDataItem[], name: string): ChartDataItem | null {
for (const item of data) {
if (item.name === name) {
@@ -47,9 +38,6 @@ export const chartUtils = {
return null
},
- /**
- * Generate random chart data for testing
- */
generateRandomData(depth: number = 3, maxChildren: number = 5): ChartDataItem[] {
const generateNode = (currentDepth: number): ChartDataItem => {
const name = `Node_${Math.random().toString(36).substr(2, 5)}`
diff --git a/src/utils/globalState.ts b/src/utils/globalState.ts
index 2df7f38..2d3d194 100644
--- a/src/utils/globalState.ts
+++ b/src/utils/globalState.ts
@@ -30,7 +30,6 @@ class GlobalState {
})
}
} else {
- // Theory directly under main category
this.theoryMappings.set(subcategory.name, {
name: subcategory.name,
category: mainCategory.name.toLowerCase()
@@ -59,10 +58,8 @@ class GlobalState {
}
}
-// Create singleton instance
const globalState = new GlobalState()
-// Make it available on window for global access
;(window as any).globalState = globalState
export default globalState
diff --git a/src/utils/routing.ts b/src/utils/routing.ts
index 71d0e2c..d35e8b8 100644
--- a/src/utils/routing.ts
+++ b/src/utils/routing.ts
@@ -1,11 +1,9 @@
import type { TheoryData } from '../types/theory'
import { generateSlug } from './slugUtils'
-// Simple cache for theory data
const theoryCache = new Map()
async function loadTheoryByName(theoryName: string): Promise {
- // Check cache first
if (theoryCache.has(theoryName)) {
return theoryCache.get(theoryName)!
}
@@ -18,7 +16,6 @@ async function loadTheoryByName(theoryName: string): Promise {
throw new Error(`Failed to load theory data: ${response.statusText}`)
}
const theoryData = await response.json() as TheoryData
- // Cache the result
theoryCache.set(theoryName, theoryData)
return theoryData
} catch (error) {
@@ -66,13 +63,11 @@ export class Router {
const category = segments[0]
const theory = segments[1]
- // Convert slug back to theory name for loading and callback
const theoryName = theory
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('-')
- // Show loading state with category and theory info
this.onLoading?.(category, theory)
try {
@@ -91,7 +86,6 @@ export class Router {
}
private async loadTheory(category: string, theory: string): Promise {
- // Convert slug back to theory name, preserving dashes
const theoryName = theory
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
${this.buildBreadcrumb(itemName).split(' > ').map((item, index, array) =>
`${item}${index < array.length - 1 ? 'โบ' : ''}`
).join('')}
-
${itemData.heading}
-
${itemName}
Philosopher & Theorist
-
-${itemData.text}
โ๏ธ
@@ -185,7 +174,6 @@ export class ItemDetailsPanel {
CORE ONTOLOGY
@@ -200,7 +188,6 @@ export class ItemDetailsPanel {
CRITIQUE & RELATED THEORIES
@@ -211,7 +198,6 @@ export class ItemDetailsPanel {
IMPLICATIONS
@@ -235,7 +221,6 @@ export class ItemDetailsPanel {
@@ -244,7 +229,6 @@ export class ItemDetailsPanel {
`
}
} else {
- // Fallback for items without data
const infoElement = this.container.querySelector('#item-info')
if (infoElement) {
infoElement.innerHTML = `
@@ -322,8 +306,8 @@ export class ItemDetailsPanel {
}
}
- // Attach FAQ event listeners
this.attachFAQListeners()
+ this.attachLinkTracking()
}
private attachFAQListeners() {
@@ -347,14 +331,25 @@ export class ItemDetailsPanel {
})
}
+ private attachLinkTracking() {
+ const readMoreLinks = this.container.querySelectorAll('a[href="#"]')
+ readMoreLinks.forEach(link => {
+ link.addEventListener('click', (event) => {
+ const rect = link.getBoundingClientRect()
+ analytics.trackClick('read_more_link', {
+ x: (event as MouseEvent).clientX - rect.left,
+ y: (event as MouseEvent).clientY - rect.top
+ }, link.getAttribute('href') || '')
+ })
+ })
+ }
+
private buildBreadcrumb(itemName: string): string {
- // Search for the item in the imported baseData hierarchy
for (const topLevel of baseData) {
for (const secondLevel of topLevel.children) {
if (secondLevel.name === itemName) {
return `${topLevel.name}`
}
- // Check if it's a third-level item
if ('children' in secondLevel && secondLevel.children) {
for (const thirdLevel of secondLevel.children) {
if (thirdLevel.name === itemName) {
@@ -365,7 +360,6 @@ export class ItemDetailsPanel {
}
}
- // Fallback for items not found in the hierarchy
return 'Philosophy'
}
@@ -375,13 +369,11 @@ export class ItemDetailsPanel {
panel?.classList.add('visible')
panel?.setAttribute('aria-hidden', 'false')
- // Update the title
const titleElement = this.container.querySelector('#item-title')
if (titleElement) {
titleElement.textContent = theoryData.id_and_class.theory_title
}
- // Update the content with theory data using MTTS v5.0 format
const infoElement = this.container.querySelector('#item-info')
if (infoElement) {
infoElement.innerHTML = `
@@ -646,6 +638,8 @@ export class ItemDetailsPanel {
`
}
+
+ this.attachLinkTracking()
}
public showError(message: string) {
@@ -654,13 +648,11 @@ export class ItemDetailsPanel {
panel?.classList.add('visible')
panel?.setAttribute('aria-hidden', 'false')
- // Update the title
const titleElement = this.container.querySelector('#item-title')
if (titleElement) {
titleElement.textContent = 'Error'
}
- // Update the content with error message
const infoElement = this.container.querySelector('#item-info')
if (infoElement) {
infoElement.innerHTML = `
@@ -679,22 +671,18 @@ export class ItemDetailsPanel {
panel?.classList.add('visible')
panel?.setAttribute('aria-hidden', 'false')
- // Convert theory slug to display name, preserving dashes
const theoryName = theory
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('-')
- // Convert category slug to display name
const categoryName = category.charAt(0).toUpperCase() + category.slice(1)
- // Update the title
const titleElement = this.container.querySelector('#item-title')
if (titleElement) {
titleElement.textContent = theoryName
}
- // Update the content with loading message and breadcrumb
const infoElement = this.container.querySelector('#item-info')
if (infoElement) {
infoElement.innerHTML = `
diff --git a/src/components/SearchBar.ts b/src/components/SearchBar.ts
index d331179..efd52d9 100644
--- a/src/components/SearchBar.ts
+++ b/src/components/SearchBar.ts
@@ -154,19 +154,16 @@ export class SearchBar {
this.dropdown.classList.add('visible')
this.input.setAttribute('aria-expanded', 'true')
- // Add click handlers to dropdown items
this.dropdown.querySelectorAll('.search-item').forEach((item, index) => {
item.addEventListener('click', () => {
const theoryName = item.getAttribute('data-theory')
if (theoryName) {
- // Try to load the theory directly and let the router handle category detection
this.loadAndNavigateToTheory(theoryName)
this.input.value = ''
this.hideDropdown()
}
})
- // Add keyboard navigation
item.addEventListener('keydown', (e) => {
const keyboardEvent = e as KeyboardEvent
if (keyboardEvent.key === 'Enter' || keyboardEvent.key === ' ') {
diff --git a/src/components/TheoryChart.ts b/src/components/TheoryChart.ts
index e013711..54a3436 100644
--- a/src/components/TheoryChart.ts
+++ b/src/components/TheoryChart.ts
@@ -5,6 +5,7 @@ import { getTheoryFullName } from '@/data/theoryNames'
import { Router } from '@/utils/routing'
import { generateSlug } from '@/utils/slugUtils'
import globalState from '@/utils/globalState'
+import analytics from '@/utils/analytics'
export class TheoryChart {
private chartContainer: ChartContainer
@@ -65,7 +66,6 @@ export class TheoryChart {
if (params.data && params.data.name) {
this.handleTheoryClick(params.data)
} else {
- // Clicked outside chart - hide mobile labels
if (this.isMobile()) {
setMobileLabelVisibility(false)
this.refreshChartWithNewData()
@@ -81,7 +81,6 @@ export class TheoryChart {
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout)
resizeTimeout = window.setTimeout(() => {
- // Reset mobile label visibility on resize
setMobileLabelVisibility(false)
this.chartContainer.setOption(getChartOptions())
}, 100)
@@ -142,7 +141,6 @@ export class TheoryChart {
const chart = this.chartContainer.getChart()
if (!chart) return
- // Dispatch a highlight event to the chart
chart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
@@ -179,16 +177,23 @@ export class TheoryChart {
private handleTheoryClick(theoryData: any) {
if (theoryData.children) {
- // Parent category clicked - show mobile labels if on mobile
- if (this.isMobile()) {
+ if (this.isMobile() && theoryData.name !== 'Materialism') {
setMobileLabelVisibility(true)
this.refreshChartWithNewData()
}
+
+ const chart = this.chartContainer.getChart()
+ if (chart) {
+ chart.dispatchAction({
+ type: 'sunburstRootToNode',
+ targetNodeId: theoryData.name
+ })
+ }
+
this.itemDetailsPanel.hide()
return
}
- // Leaf node clicked - hide mobile labels if on mobile
if (this.isMobile()) {
setMobileLabelVisibility(false)
this.refreshChartWithNewData()
@@ -202,14 +207,22 @@ export class TheoryChart {
if (response.ok) {
const slug = generateSlug(theoryName)
const category = globalState.getTheoryCategory(theoryName) || theoryData.parent?.toLowerCase() || 'neurobiological'
+
+ // Track page view for theory navigation
+ analytics.trackPageView(theoryName, category, theoryData.parent)
+
this.router.navigateToTheory(category, slug)
} else {
- // No JSON data, show generic item details
+ // Track page view for generic theory display
+ const category = theoryData.parent?.toLowerCase() || 'unknown'
+ analytics.trackPageView(theoryName, category, theoryData.parent)
this.itemDetailsPanel.show(theoryData.name)
}
})
.catch(() => {
- // Error loading, show generic item details
+ // Track page view for error case
+ const category = theoryData.parent?.toLowerCase() || 'unknown'
+ analytics.trackPageView(theoryName, category, theoryData.parent)
this.itemDetailsPanel.show(theoryData.name)
})
}
diff --git a/src/config/chartConfig.ts b/src/config/chartConfig.ts
index a6cdd54..2b25d31 100644
--- a/src/config/chartConfig.ts
+++ b/src/config/chartConfig.ts
@@ -1,7 +1,6 @@
import type { EChartsOption } from 'echarts'
import { getTheoryFullName } from '@/data/theoryNames'
-// Color palette - Mystic Depths & Illumination
const mysticPalette = {
mainColors: [
'#03045E', // Midnight Blue
@@ -17,7 +16,6 @@ const mysticPalette = {
]
}
-// Color utility functions
export const colorUtils = {
// Lighten a color by percentage
lighten: (hex: string, percent: number): string => {
@@ -31,7 +29,6 @@ export const colorUtils = {
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1)
},
- // Desaturate a color by percentage
desaturate: (hex: string, percent: number): string => {
const num = parseInt(hex.replace('#', ''), 16)
const R = num >> 16
@@ -45,7 +42,6 @@ export const colorUtils = {
return '#' + (0x1000000 + newR * 0x10000 + newG * 0x100 + newB).toString(16).slice(1)
},
- // Adjust saturation down by percentage
reduceSaturation: (hex: string, percent: number): string => {
const num = parseInt(hex.replace('#', ''), 16)
const R = num >> 16
@@ -66,75 +62,62 @@ export const colorUtils = {
}
}
-// Function to get current palette colors
export const getCurrentPalette = () => mysticPalette
-// Global state for mobile label visibility
let showMobileLabels = false
-// Function to set mobile label visibility
export const setMobileLabelVisibility = (visible: boolean) => {
showMobileLabels = visible
}
-// Function to get mobile label visibility
export const getMobileLabelVisibility = () => showMobileLabels
-// Function to apply colors to data
export const applyPaletteToData = (data: any[]) => {
let mainColorIndex = 0
const applyColors = (items: any[], level: number = 0, parentColor?: string, parentName?: string) => {
- console.log(`Applying colors to items: ${items.length} at level ${level}`)
return items.map((item, index) => {
let color: string
let labelPosition: string | undefined
let showLabel = true
- console.log(`Processing item: ${item.name} at level ${level}`)
-
if (level === 0) {
- // Top level - assign unique color from palette
color = mysticPalette.mainColors[mainColorIndex % mysticPalette.mainColors.length]
mainColorIndex++
- // Top level labels stay inside
labelPosition = undefined
} else if (level === 1 && parentColor) {
- // Second level - lighten by 20-30% with variation based on index
- const lightenAmount = 20 + (index * 3) % 15 // 20-35% variation
+ const lightenAmount = 20 + (index * 3) % 15
color = colorUtils.lighten(parentColor, lightenAmount)
- // For Materialism category, subcategories stay inside
- // For other categories, subcategories go outside
if (parentName === 'Materialism') {
- labelPosition = undefined // Stay inside
+ labelPosition = undefined
} else {
if (isMobile() && !showMobileLabels) {
showLabel = false;
- // labelPosition = undefined;
+ } else if (isMobile() && showMobileLabels) {
+ labelPosition = undefined
+ } else {
+ labelPosition = 'outside'
}
- labelPosition = 'outside' // Go outside
}
} else if (level >= 2 && parentColor) {
- // Third level and beyond - desaturate with variation based on index
- const desatAmount = 25 + (index * 2) % 20 // 25-45% variation
+ const desatAmount = 25 + (index * 2) % 20
color = colorUtils.desaturate(parentColor, desatAmount)
- // Third level and beyond always go outside
- labelPosition = 'outside'
-
- // On mobile, hide labels for the last row (level 4+ - outermost ring)
if (isMobile() && !showMobileLabels) {
showLabel = false;
- // labelPosition = undefined;
+ } else if (isMobile() && showMobileLabels) {
+ labelPosition = undefined
+ } else {
+ labelPosition = 'outside'
}
} else {
- color = '#666666' // Fallback color
+ color = '#666666'
}
const newItem = {
...item,
- parent: parentName, // Add parent name to each item
+ parent: parentName,
itemStyle: { color },
label: showLabel && labelPosition ? {
position: labelPosition,
@@ -155,7 +138,6 @@ export const applyPaletteToData = (data: any[]) => {
return applyColors(data)
}
-// Base data structure without colors
export const baseData = [
{
name: 'Materialism',
@@ -468,28 +450,23 @@ export const baseData = [
}
]
-// Get current data with applied palette
export const getCurrentData = () => {
return applyPaletteToData(baseData)
}
-// Function to refresh chart with current mobile label settings
export const refreshChartData = () => {
return applyPaletteToData(baseData)
}
-// Function to check if device is mobile
const isMobile = () => {
const isMobileDevice = window.innerWidth <= 768
return isMobileDevice
}
-// Function to get chart options with current mobile state
export const getChartOptions = (): EChartsOption => {
return {
backgroundColor: 'transparent',
title: {
- // text: 'C-Atlas Chart',
left: 'center',
textStyle: {
color: '#fff'
@@ -498,7 +475,6 @@ export const getChartOptions = (): EChartsOption => {
tooltip: {
show: true,
formatter: function (params: any) {
- // Don't show tooltip if no parent or if parent is Materialism
if (!params.data || params.data.parent === undefined || params.data.parent === 'Materialism') {
return ''
}
@@ -524,21 +500,15 @@ export const getChartOptions = (): EChartsOption => {
label: {
rotate: 'radial',
show: true,
- // fontSize: 18,
formatter: function (params: any) {
- // console.log(params.data.parent)
- // Make Materialism and Monism titles bigger
if (params.data.parent === undefined) {
return `{title|${params.name}}`
}
return params.name
},
rich: {
- // fontSize: 18,
title: {
- fontSize: 14,
- // fontWeight: 'bold',
- // color: '#FCD771'
+ fontSize: 14
}
}
},
@@ -588,17 +558,14 @@ export const getChartOptions = (): EChartsOption => {
}
}
-// Function to extract all theory names from chart data in order
export const getAllTheoryNames = (): string[] => {
const theoryNames: string[] = []
const extractTheories = (items: any[]) => {
items.forEach(item => {
- // Only add items that don't have children (leaf nodes - actual theories)
if (!item.children) {
theoryNames.push(item.name)
} else {
- // Recursively process children
extractTheories(item.children)
}
})
@@ -608,6 +575,5 @@ export const getAllTheoryNames = (): string[] => {
return theoryNames
}
-// Legacy chart options (kept for backward compatibility)
export const chartOptions: EChartsOption = getChartOptions()
diff --git a/src/data/theoryNames.ts b/src/data/theoryNames.ts
index 9cfd34b..e3bbc2c 100644
--- a/src/data/theoryNames.ts
+++ b/src/data/theoryNames.ts
@@ -1,4 +1,3 @@
-// Static mapping for all theories
const staticTheoryNames: Record