@@ -1902,13 +1902,14 @@ function renderGraphNew(data, container, mode) {
19021902 . attr ( 'stroke-width' , d => d . type === 'subject' ? 2.5 : 1 )
19031903 . style ( 'transition' , 'filter 0.2s, fill-opacity 0.2s' ) ;
19041904
1905- // Labels
1905+ // Labels — fill comes from the .graph-label CSS rule (theme-aware),
1906+ // stroke halo keeps them legible on both dark and light backgrounds.
19061907 node . append ( 'text' )
19071908 . attr ( 'class' , 'graph-label' )
19081909 . attr ( 'dy' , d => d . r + 14 )
19091910 . attr ( 'text-anchor' , 'middle' )
19101911 . attr ( 'font-size' , d => d . type === 'subject' ? 13 : 10 )
1911- . attr ( 'font-weight' , d => d . type === 'subject' ? 700 : 400 )
1912+ . attr ( 'font-weight' , d => d . type === 'subject' ? 700 : 500 )
19121913 . text ( d => d . id ) ;
19131914
19141915 // Weight badges for concepts
@@ -2533,6 +2534,16 @@ function getSubjColors() {
25332534 return colors ;
25342535}
25352536
2537+ // Theme-aware chart colors. Reads CSS variables so charts stay readable
2538+ // whether the page is on the dark base palette or the BDFZ light parchment refresh.
2539+ function getChartColors ( ) {
2540+ const cs = getComputedStyle ( document . documentElement ) ;
2541+ const text = ( cs . getPropertyValue ( '--text' ) || '' ) . trim ( ) || '#3a3530' ;
2542+ const dim = ( cs . getPropertyValue ( '--text-dim' ) || '' ) . trim ( ) || '#7a716a' ;
2543+ const muted = ( cs . getPropertyValue ( '--text-muted' ) || '' ) . trim ( ) || '#a89d96' ;
2544+ return { text, dim, muted } ;
2545+ }
2546+
25362547async function loadInsights ( ) {
25372548 if ( insightsLoaded ) return ;
25382549 insightsLoaded = true ;
@@ -2604,14 +2615,17 @@ function renderFreqBars(container, data) {
26042615 . transition ( ) . duration ( 600 ) . delay ( ( d , i ) => i * 20 )
26052616 . attr ( 'width' , d => x ( d . count ) ) ;
26062617
2618+ const chartColors = getChartColors ( ) ;
2619+
26072620 // Labels
26082621 g . selectAll ( '.bar-label' ) . data ( data ) . join ( 'text' )
26092622 . attr ( 'class' , 'bar-label' )
26102623 . attr ( 'x' , d => x ( d . count ) + 5 )
26112624 . attr ( 'y' , d => y ( d . term ) + y . bandwidth ( ) / 2 )
26122625 . attr ( 'dy' , '0.35em' )
2613- . attr ( 'fill' , '#a0a0c0' )
2626+ . attr ( 'fill' , chartColors . dim )
26142627 . attr ( 'font-size' , '11px' )
2628+ . attr ( 'font-weight' , '600' )
26152629 . text ( d => d . count . toLocaleString ( ) ) ;
26162630
26172631 // Y axis labels
@@ -2621,8 +2635,9 @@ function renderFreqBars(container, data) {
26212635 . attr ( 'y' , d => y ( d . term ) + y . bandwidth ( ) / 2 )
26222636 . attr ( 'dy' , '0.35em' )
26232637 . attr ( 'text-anchor' , 'end' )
2624- . attr ( 'fill' , '#e0e0f0' )
2638+ . attr ( 'fill' , chartColors . text )
26252639 . attr ( 'font-size' , '12px' )
2640+ . attr ( 'font-weight' , '600' )
26262641 . attr ( 'cursor' , 'pointer' )
26272642 . text ( d => d . term )
26282643 . on ( 'click' , ( e , d ) => {
@@ -2648,13 +2663,13 @@ async function loadHeatmap() {
26482663function getHeatmapTextStyle ( fillColor ) {
26492664 const color = d3 . color ( fillColor ) ;
26502665 if ( ! color ) {
2651- return { fill : '#f8fbff ' , stroke : 'rgba(0,0,0 ,0.35 )' } ;
2666+ return { fill : '#3a3530 ' , stroke : 'rgba(255,255,255 ,0.85 )' } ;
26522667 }
26532668 const luminance = ( 0.299 * color . r + 0.587 * color . g + 0.114 * color . b ) / 255 ;
2654- if ( luminance >= 0.66 ) {
2655- return { fill : '#17202a ' , stroke : 'rgba(255,255,255,0.55 )' } ;
2669+ if ( luminance >= 0.55 ) {
2670+ return { fill : '#1f2933 ' , stroke : 'rgba(255,255,255,0.85 )' } ;
26562671 }
2657- return { fill : '#f8fbff ' , stroke : 'rgba(0,0,0,0.35 )' } ;
2672+ return { fill : '#fbfaf5 ' , stroke : 'rgba(0,0,0,0.45 )' } ;
26582673}
26592674
26602675function renderHeatmap ( container , data ) {
@@ -2708,33 +2723,43 @@ function renderHeatmap(container, data) {
27082723 }
27092724 }
27102725
2711- // Axis labels
2726+ // Axis labels — subject-tinted text with stroke halo so it stays
2727+ // legible against any background luminance.
2728+ const axisFallback = getChartColors ( ) . text ;
27122729 subjects . forEach ( ( s , i ) => {
2730+ const tint = getSubjColors ( ) [ s ] || axisFallback ;
27132731 svg . append ( 'text' )
27142732 . attr ( 'x' , margin . left + i * cellSize + cellSize / 2 - 1 )
27152733 . attr ( 'y' , margin . top - 8 )
27162734 . attr ( 'text-anchor' , 'middle' )
2717- . attr ( 'fill' , getSubjColors ( ) [ s ] || '#ccc' )
2735+ . attr ( 'fill' , tint )
2736+ . attr ( 'stroke' , 'rgba(255, 255, 255, 0.7)' )
2737+ . attr ( 'stroke-width' , 3 )
2738+ . attr ( 'paint-order' , 'stroke' )
27182739 . attr ( 'font-size' , '12px' )
2719- . attr ( 'font-weight' , '500 ' )
2740+ . attr ( 'font-weight' , '700 ' )
27202741 . text ( s ) ;
27212742 svg . append ( 'text' )
27222743 . attr ( 'x' , margin . left - 8 )
27232744 . attr ( 'y' , margin . top + i * cellSize + cellSize / 2 )
27242745 . attr ( 'dy' , '0.35em' )
27252746 . attr ( 'text-anchor' , 'end' )
2726- . attr ( 'fill' , getSubjColors ( ) [ s ] || '#ccc' )
2747+ . attr ( 'fill' , tint )
2748+ . attr ( 'stroke' , 'rgba(255, 255, 255, 0.7)' )
2749+ . attr ( 'stroke-width' , 3 )
2750+ . attr ( 'paint-order' , 'stroke' )
27272751 . attr ( 'font-size' , '12px' )
2728- . attr ( 'font-weight' , '500 ' )
2752+ . attr ( 'font-weight' , '700 ' )
27292753 . text ( s ) ;
27302754 } ) ;
27312755
27322756 // Caption
27332757 svg . append ( 'text' )
27342758 . attr ( 'x' , W / 2 ) . attr ( 'y' , H + 5 )
27352759 . attr ( 'text-anchor' , 'middle' )
2736- . attr ( 'fill' , '#888' )
2760+ . attr ( 'fill' , getChartColors ( ) . dim )
27372761 . attr ( 'font-size' , '11px' )
2762+ . attr ( 'font-weight' , '600' )
27382763 . text ( `共 ${ data . total_concepts } 个跨学科学术术语` ) ;
27392764}
27402765
@@ -2814,13 +2839,16 @@ function renderBreadth(container, concepts) {
28142839 . transition ( ) . duration ( 500 ) . delay ( ( d , i ) => i * 15 )
28152840 . attr ( 'width' , d => x ( d . subjects ) ) ;
28162841
2842+ const breadthColors = getChartColors ( ) ;
2843+
28172844 g . selectAll ( '.breadth-val' ) . data ( concepts ) . join ( 'text' )
28182845 . attr ( 'class' , 'breadth-val' )
28192846 . attr ( 'x' , d => x ( d . subjects ) + 5 )
28202847 . attr ( 'y' , d => y ( d . term ) + y . bandwidth ( ) / 2 )
28212848 . attr ( 'dy' , '0.35em' )
2822- . attr ( 'fill' , '#a0a0c0' )
2849+ . attr ( 'fill' , breadthColors . dim )
28232850 . attr ( 'font-size' , '11px' )
2851+ . attr ( 'font-weight' , '600' )
28242852 . text ( d => `${ d . subjects } 科` ) ;
28252853
28262854 svg . selectAll ( '.breadth-label' ) . data ( concepts ) . join ( 'text' )
@@ -2829,8 +2857,9 @@ function renderBreadth(container, concepts) {
28292857 . attr ( 'y' , d => y ( d . term ) + y . bandwidth ( ) / 2 )
28302858 . attr ( 'dy' , '0.35em' )
28312859 . attr ( 'text-anchor' , 'end' )
2832- . attr ( 'fill' , '#e0e0f0' )
2860+ . attr ( 'fill' , breadthColors . text )
28332861 . attr ( 'font-size' , '12px' )
2862+ . attr ( 'font-weight' , '600' )
28342863 . attr ( 'cursor' , 'pointer' )
28352864 . text ( d => d . term )
28362865 . on ( 'click' , ( e , d ) => {
@@ -2905,12 +2934,16 @@ function renderSearchSubgraph(container, data) {
29052934 . attr ( 'stroke' , d => d . type === 'center' ? '#a29bfe' : 'none' )
29062935 . attr ( 'stroke-width' , 2 ) ;
29072936
2937+ const subgraphColors = getChartColors ( ) ;
29082938 node . append ( 'text' )
29092939 . attr ( 'dy' , d => d . type === 'center' ? 28 : 22 )
29102940 . attr ( 'text-anchor' , 'middle' )
2911- . attr ( 'fill' , '#e0e0f0' )
2941+ . attr ( 'fill' , subgraphColors . text )
2942+ . attr ( 'stroke' , 'rgba(255, 255, 255, 0.9)' )
2943+ . attr ( 'stroke-width' , 2.4 )
2944+ . attr ( 'paint-order' , 'stroke' )
29122945 . attr ( 'font-size' , d => d . type === 'center' ? '13px' : d . type === 'subject' ? '12px' : '10px' )
2913- . attr ( 'font-weight' , d => d . type === 'center' ? '700' : '400 ' )
2946+ . attr ( 'font-weight' , d => d . type === 'center' ? '700' : '500 ' )
29142947 . text ( d => d . id ) ;
29152948
29162949 sim . on ( 'tick' , ( ) => {
0 commit comments