@@ -347,13 +347,20 @@ function generateProbeSvgString(
347347
348348 const elements : string [ ] = [ ] ;
349349
350+ // Round emitted coordinates to 2 decimals: sub-pixel precision is invisible
351+ // but keeps the markup compact and readable.
352+ const r2 = ( n : number ) => Math . round ( n * 100 ) / 100 ;
353+
350354 // Probe contour: technical line-art — a faint cool wash so the shank reads as
351355 // a region, with a thin precise outline. No fill gradient or shadow.
352356 if ( probe . probe_planar_contour && probe . probe_planar_contour . length > 1 ) {
353357 const points = probe . probe_planar_contour
354- . map ( ( p ) => projectPoint ( p ) . join ( "," ) )
358+ . map ( ( p ) => {
359+ const [ px , py ] = projectPoint ( p ) ;
360+ return `${ r2 ( px ) } ,${ r2 ( py ) } ` ;
361+ } )
355362 . join ( " " ) ;
356- const strokeWidth = Math . max ( 1 , Math . min ( 1.6 , 2 * ( scale / 120 ) ) ) ;
363+ const strokeWidth = r2 ( Math . max ( 1 , Math . min ( 1.6 , 2 * ( scale / 120 ) ) ) ) ;
357364 elements . push (
358365 `<polygon points="${ points } " fill="rgba(51, 65, 85, 0.05)" stroke="rgb(51, 65, 85)" stroke-opacity="0.9" stroke-width="${ strokeWidth } " stroke-linejoin="round"/>`
359366 ) ;
@@ -363,36 +370,35 @@ function generateProbeSvgString(
363370 const contactShapes = probe . contact_shapes ?? [ ] ;
364371 const contactShapeParams = probe . contact_shape_params ?? [ ] ;
365372
366- // Helper to generate one contact SVG element: flat gold (the electrode
367- // convention) with a defined bronze outline, no gradient or shadow .
368- const contactStrokeWidth = Math . max ( 1 , Math . min ( 1.8 , 2.5 * ( scale / 150 ) ) ) ;
373+ // Helper to generate one contact's geometry. The flat- gold style (fill,
374+ // bronze outline) is applied once on the wrapping <g>, not per element .
375+ // Rectangular pads get lightly rounded corners.
369376 const generateContactSvg = (
370377 x : number ,
371378 y : number ,
372379 shape : string ,
373380 params : ContactShapeParams
374381 ) : string => {
375- const common = `fill="rgb(212, 175, 55)" stroke="rgb(110, 80, 25)" stroke-opacity="0.9" stroke-width="${ contactStrokeWidth } "` ;
376382 switch ( shape ) {
377383 case "circle" : {
378384 const radius = ( params . radius ?? 5 ) * scale ;
379- return `<circle cx="${ x } " cy="${ y } " r="${ radius } " ${ common } />` ;
385+ return `<circle cx="${ r2 ( x ) } " cy="${ r2 ( y ) } " r="${ r2 ( radius ) } " />` ;
380386 }
381387 case "square" : {
382388 const side = ( params . width ?? 10 ) * scale ;
383- const r = side * 0.12 ;
384- return `<rect x="${ x - side / 2 } " y="${ y - side / 2 } " width="${ side } " height="${ side } " rx="${ r } " ry="${ r } " ${ common } />` ;
389+ const rr = r2 ( side * 0.12 ) ;
390+ return `<rect x="${ r2 ( x - side / 2 ) } " y="${ r2 ( y - side / 2 ) } " width="${ r2 ( side ) } " height="${ r2 ( side ) } " rx="${ rr } " ry="${ rr } " />` ;
385391 }
386392 case "rect" : {
387393 const w = ( params . width ?? 10 ) * scale ;
388394 const h = ( params . height ?? 15 ) * scale ;
389- const r = Math . min ( w , h ) * 0.12 ;
390- return `<rect x="${ x - w / 2 } " y="${ y - h / 2 } " width="${ w } " height="${ h } " rx="${ r } " ry="${ r } " ${ common } />` ;
395+ const rr = r2 ( Math . min ( w , h ) * 0.12 ) ;
396+ return `<rect x="${ r2 ( x - w / 2 ) } " y="${ r2 ( y - h / 2 ) } " width="${ r2 ( w ) } " height="${ r2 ( h ) } " rx="${ rr } " ry="${ rr } " />` ;
391397 }
392398 default : {
393- // Unknown shape: a small plain gold dot.
399+ // Unknown shape: a small plain dot.
394400 const markerSize = Math . max ( 3 , Math . min ( 10 , 7 * ( scale / 100 ) ) ) ;
395- return `<circle cx="${ x } " cy="${ y } " r="${ markerSize * 0.4 } " ${ common } />` ;
401+ return `<circle cx="${ r2 ( x ) } " cy="${ r2 ( y ) } " r="${ r2 ( markerSize * 0.4 ) } " />` ;
396402 }
397403 }
398404 } ;
@@ -410,13 +416,21 @@ function generateProbeSvgString(
410416 y >= - frameMargin &&
411417 y <= heightPx + frameMargin ;
412418
419+ // All contacts share one flat-gold style, set once on a group wrapper.
420+ const contactEls : string [ ] = [ ] ;
413421 contactPositions . forEach ( ( position , index ) => {
414422 const [ x , y ] = projectPoint ( position ) ;
415423 if ( ! isContactInFrame ( x , y ) ) return ;
416424 const shape = contactShapes [ index ] ?? "" ;
417425 const params = contactShapeParams [ index ] ?? { } ;
418- elements . push ( generateContactSvg ( x , y , shape , params ) ) ;
426+ contactEls . push ( generateContactSvg ( x , y , shape , params ) ) ;
419427 } ) ;
428+ if ( contactEls . length > 0 ) {
429+ const contactStrokeWidth = r2 ( Math . max ( 1 , Math . min ( 1.8 , 2.5 * ( scale / 150 ) ) ) ) ;
430+ elements . push (
431+ `<g fill="rgb(212, 175, 55)" stroke="rgb(110, 80, 25)" stroke-opacity="0.9" stroke-width="${ contactStrokeWidth } ">\n${ contactEls . join ( "\n" ) } \n</g>`
432+ ) ;
433+ }
420434
421435 // Scale bar (L-shaped, bottom-left corner)
422436 if ( showScaleBar ) {
@@ -434,26 +448,26 @@ function generateProbeSvgString(
434448 const tickSize = 4 ;
435449
436450 const label = scaleBarUm >= 1000 ? `${ scaleBarUm / 1000 } mm` : `${ scaleBarUm } μm` ;
437- const strokeStyle = "rgba(15, 23, 42, 0.9)" ;
438-
439- // L shape path
440- elements . push (
441- `<path d="M${ cornerX } ,${ cornerY } L${ cornerX } ,${ cornerY - scaleBarPixels } M${ cornerX } ,${ cornerY } L${ cornerX + scaleBarPixels } ,${ cornerY } " stroke="${ strokeStyle } " stroke-width="2" stroke-linecap="square" fill="none"/>`
442- ) ;
443-
444- // End ticks
445- elements . push (
446- `<path d="M${ cornerX - tickSize } ,${ cornerY - scaleBarPixels } L${ cornerX + tickSize } ,${ cornerY - scaleBarPixels } M${ cornerX + scaleBarPixels } ,${ cornerY - tickSize } L${ cornerX + scaleBarPixels } ,${ cornerY + tickSize } " stroke="${ strokeStyle } " stroke-width="2" fill="none"/>`
447- ) ;
451+ const col = "rgba(15, 23, 42, 0.9)" ;
452+ const x0 = r2 ( cornerX ) ;
453+ const y0 = r2 ( cornerY ) ;
454+ const xEnd = r2 ( cornerX + scaleBarPixels ) ;
455+ const yTop = r2 ( cornerY - scaleBarPixels ) ;
448456
449- // X label (below horizontal arm)
457+ // L shape + end ticks, sharing one stroke style on a group.
450458 elements . push (
451- `<text x="${ cornerX + scaleBarPixels / 2 } " y="${ cornerY + 16 } " text-anchor="middle" font-family="Inter, sans-serif" font-size="11" fill="${ strokeStyle } ">${ label } </text>`
459+ `<g stroke="${ col } " stroke-width="2" fill="none">` +
460+ `<path d="M${ x0 } ,${ y0 } L${ x0 } ,${ yTop } M${ x0 } ,${ y0 } L${ xEnd } ,${ y0 } " stroke-linecap="square"/>` +
461+ `<path d="M${ r2 ( cornerX - tickSize ) } ,${ yTop } L${ r2 ( cornerX + tickSize ) } ,${ yTop } M${ xEnd } ,${ r2 ( cornerY - tickSize ) } L${ xEnd } ,${ r2 ( cornerY + tickSize ) } "/>` +
462+ `</g>`
452463 ) ;
453464
454- // Y label (rotated, to the left of vertical arm)
465+ // Both labels share one text style on a group.
455466 elements . push (
456- `<text x="${ cornerX - 6 } " y="${ cornerY - scaleBarPixels / 2 } " text-anchor="middle" font-family="Inter, sans-serif" font-size="11" fill="${ strokeStyle } " transform="rotate(-90, ${ cornerX - 6 } , ${ cornerY - scaleBarPixels / 2 } )">${ label } </text>`
467+ `<g fill="${ col } " font-family="Inter, sans-serif" font-size="11" text-anchor="middle">` +
468+ `<text x="${ r2 ( cornerX + scaleBarPixels / 2 ) } " y="${ r2 ( cornerY + 16 ) } ">${ label } </text>` +
469+ `<text x="${ r2 ( cornerX - 6 ) } " y="${ r2 ( cornerY - scaleBarPixels / 2 ) } " transform="rotate(-90, ${ r2 ( cornerX - 6 ) } , ${ r2 ( cornerY - scaleBarPixels / 2 ) } )">${ label } </text>` +
470+ `</g>`
457471 ) ;
458472 }
459473
0 commit comments