@@ -47,12 +47,11 @@ const DISTINCT_COLORS = [
4747
4848// Constants for layout
4949const NODE_PADDING = 20 ; // padding inside node
50- const NODE_MIN_WIDTH = 150 ;
50+ const NODE_MIN_WIDTH = 180 ; // 10em assuming 1em = 16px
5151const NODE_MAX_WIDTH = 350 ;
5252const CHAR_WIDTH = 8 ; // approximate character width for normal font
5353const CHAR_WIDTH_SMALL = 6 ; // approximate character width for smaller font (11px)
54- const NODE_BASE_HEIGHT = 80 ; // base height for node
55- const NODE_BASE_HEIGHT_SMALL = 65 ; // base height for node with small font
54+ const NODE_BASE_HEIGHT = 50 ; // base height for node
5655const VERTICAL_GAP = 50 ; // vertical gap between nodes in same layer
5756const LAYER_GAP = 225 ; // horizontal gap between layers (increased for clarity)
5857
@@ -63,12 +62,11 @@ const calculateNodeWidth = (label: string): number => {
6362} ;
6463
6564// Calculate node height (can expand for multi-line text)
66- // useSmallFont: for CO/PO nodes with text > 150 chars
67- const calculateNodeHeight = ( label : string , width : number , useSmallFont : boolean = false ) : number => {
68- const charWidth = useSmallFont ? CHAR_WIDTH_SMALL : CHAR_WIDTH ;
69- const baseHeight = useSmallFont ? NODE_BASE_HEIGHT_SMALL : NODE_BASE_HEIGHT ;
70- const lineHeight = useSmallFont ? 14 : 20 ; // smaller line height for small font
71-
65+ // If isTruncatedPO is true, use a smaller base height to avoid extra space
66+ const calculateNodeHeight = ( label : string , width : number , isTruncatedPO ?: boolean ) : number => {
67+ const charWidth = CHAR_WIDTH ;
68+ const baseHeight = isTruncatedPO ? 40 : NODE_BASE_HEIGHT ;
69+ const lineHeight = 20 ;
7270 const charsPerLine = Math . floor ( ( width - NODE_PADDING * 2 ) / charWidth ) ;
7371 const lines = Math . ceil ( label . length / charsPerLine ) ;
7472 return baseHeight + Math . max ( 0 , lines - 1 ) * lineHeight ;
@@ -229,30 +227,31 @@ const convertToNodes = (
229227 // CC nodes: normal font
230228 const ccDimensions = sortedCC . map ( node => {
231229 const width = calculateNodeWidth ( node . name ) ;
232- const height = calculateNodeHeight ( node . name , width , false ) ;
230+ const height = calculateNodeHeight ( node . name , width ) ;
233231 return { width, height } ;
234232 } ) ;
235233
236- // CO nodes: use small font if text > 150 chars
234+ // CO nodes: use truncated label and reduced base height if text > 85 chars
237235 const coDimensions = sortedCO . map ( node => {
238236 const width = calculateNodeWidth ( node . name ) ;
239- const useSmallFont = node . name . length > 150 ;
240- const height = calculateNodeHeight ( node . name , width , useSmallFont ) ;
237+ const isTruncated = node . name . length > 85 ;
238+ const displayLabel = isTruncated ? node . name . slice ( 0 , 85 ) + '…' : node . name ;
239+ const height = calculateNodeHeight ( displayLabel , width , isTruncated ) ;
241240 return { width, height } ;
242241 } ) ;
243242
244- // PO nodes: use small font if text > 150 chars
243+ // PO nodes: use truncated label for height if text > 85 chars, and reduce base height for truncated
245244 const poDimensions = sortedPO . map ( node => {
246245 const width = calculateNodeWidth ( node . name ) ;
247- const useSmallFont = node . name . length > 150 ;
248- const height = calculateNodeHeight ( node . name , width , useSmallFont ) ;
246+ const isTruncated = node . name . length > 85 ;
247+ const displayLabel = isTruncated ? node . name . slice ( 0 , 85 ) + '…' : node . name ;
248+ const height = calculateNodeHeight ( displayLabel , width , isTruncated ) ;
249249 return { width, height } ;
250250 } ) ;
251251
252252 // 3. Calculate max width per layer for alignment
253253 const ccMaxWidth = ccDimensions . length > 0 ? Math . max ( ...ccDimensions . map ( d => d . width ) ) : NODE_MIN_WIDTH ;
254254 const coMaxWidth = coDimensions . length > 0 ? Math . max ( ...coDimensions . map ( d => d . width ) ) : NODE_MIN_WIDTH ;
255- const poMaxWidth = poDimensions . length > 0 ? Math . max ( ...poDimensions . map ( d => d . width ) ) : NODE_MIN_WIDTH ;
256255
257256 // 4. Calculate total height per layer
258257 const ccTotalHeight = ccDimensions . reduce ( ( sum , d ) => sum + d . height + VERTICAL_GAP , - VERTICAL_GAP ) ;
@@ -393,6 +392,13 @@ const convertToEdges = (data: GetNodesResponse): Edge[] => {
393392 const color = DISTINCT_COLORS [ colorIndex % DISTINCT_COLORS . length ] ;
394393 colorIndex ++ ;
395394
395+ // Make edge thickness depend on weight (e.g. 1-5 maps to 2-6px)
396+ const minStroke = 1.5 ;
397+ const maxStroke = 4 ;
398+ const minWeight = 1 ;
399+ const maxWeight = 5 ;
400+ const weight = Math . max ( minWeight , Math . min ( maxWeight , rel . weight ) ) ;
401+ const strokeWidth = minStroke + ( ( weight - minWeight ) / ( maxWeight - minWeight ) ) * ( maxStroke - minStroke ) ;
396402 edges . push ( {
397403 id : `e-${ rel . relation_id } ` ,
398404 source : srcId ,
@@ -402,7 +408,7 @@ const convertToEdges = (data: GetNodesResponse): Edge[] => {
402408 animated : true ,
403409 style : {
404410 stroke : color ,
405- strokeWidth : 2 ,
411+ strokeWidth,
406412 } ,
407413 labelStyle : {
408414 fill : color ,
@@ -729,10 +735,15 @@ export default function MainGraph() {
729735 weightValue
730736 ) ;
731737
738+ // Use same thickness logic as convertToEdges
739+ const minStroke = 1.5 ;
740+ const maxStroke = 4 ;
741+ const minWeight = 1 ;
742+ const maxWeight = 5 ;
743+ const weight = Math . max ( minWeight , Math . min ( maxWeight , weightValue ) ) ;
744+ const strokeWidth = minStroke + ( ( weight - minWeight ) / ( maxWeight - minWeight ) ) * ( maxStroke - minStroke ) ;
732745 const sourceLayer = getNodeLayer ( pendingConnection . source ) ;
733- const edgeColor =
734- sourceLayer === "course_content" ? "#1976d2" : "#388e3c" ;
735-
746+ const edgeColor = sourceLayer === "course_content" ? "#1976d2" : "#388e3c" ;
736747 const newEdge : Edge = {
737748 id : `e-${ response . relation_id } ` ,
738749 source : pendingConnection . source ,
@@ -745,7 +756,7 @@ export default function MainGraph() {
745756 animated : true ,
746757 style : {
747758 stroke : edgeColor ,
748- strokeWidth : 2 ,
759+ strokeWidth,
749760 } ,
750761 labelStyle : {
751762 fill : edgeColor ,
@@ -757,7 +768,6 @@ export default function MainGraph() {
757768 fillOpacity : 0.9 ,
758769 } ,
759770 } ;
760-
761771 setEdges ( ( eds ) => addEdge ( newEdge , eds ) ) ;
762772 closeWeightModal ( ) ;
763773 } else if ( modalMode === "edit" && editingEdge ) {
@@ -770,18 +780,28 @@ export default function MainGraph() {
770780
771781 await updateRelation ( relationId , weightValue ) ;
772782
783+ // Use same thickness logic as convertToEdges
784+ const minStroke = 1.5 ;
785+ const maxStroke = 4 ;
786+ const minWeight = 1 ;
787+ const maxWeight = 5 ;
788+ const weight = Math . max ( minWeight , Math . min ( maxWeight , weightValue ) ) ;
789+ const strokeWidth = minStroke + ( ( weight - minWeight ) / ( maxWeight - minWeight ) ) * ( maxStroke - minStroke ) ;
773790 setEdges ( ( prev ) =>
774791 prev . map ( ( e ) =>
775792 e . id === editingEdge . id
776793 ? {
777794 ...e ,
778795 label : `${ weightValue } ` ,
779796 data : { ...e . data , weight : weightValue } ,
797+ style : {
798+ ...( e . style || { } ) ,
799+ strokeWidth,
800+ } ,
780801 }
781802 : e
782803 )
783804 ) ;
784-
785805 closeWeightModal ( ) ;
786806 }
787807 } catch ( err ) {
0 commit comments