@@ -207,14 +207,6 @@ export function stripemojis(string) {
207
207
return string . replace ( / [ ^ \p{ L} \p{ N} \p{ P} \p{ Z} ^ $ \n ] / gu, "" )
208
208
}
209
209
210
- /**Chartist */
211
- export async function chartist ( ) {
212
- const css = `<style data-optimizable="true">${ await fs . readFile ( paths . join ( __module ( import . meta. url ) , "../../../node_modules" , "node-chartist/dist/main.css" ) ) . catch ( _ => "" ) } </style>`
213
- const { default : nodechartist } = await import ( url . pathToFileURL ( paths . join ( __module ( import . meta. url ) , "../../../node_modules" , "/node-chartist/lib/index.js" ) ) )
214
- return ( await nodechartist ( ...arguments ) )
215
- . replace ( / c l a s s = " c t - c h a r t - l i n e " > / , `class="ct-chart-line">${ css } ` )
216
- }
217
-
218
210
/**Language analyzer (single file) */
219
211
export async function language ( { filename, patch} ) {
220
212
console . debug ( `metrics/language > ${ filename } ` )
@@ -816,3 +808,193 @@ export class D3node {
816
808
return this . element . select ( "svg" ) . node ( ) ?. outerHTML || ""
817
809
}
818
810
}
811
+
812
+ /** Graph utilities */
813
+ export const Graph = {
814
+ /**Timeline graph */
815
+ timeline ( ) {
816
+ return this . graph ( "time" , ...arguments )
817
+ } ,
818
+ /**Line graph */
819
+ line ( ) {
820
+ return this . graph ( "line" , ...arguments )
821
+ } ,
822
+ /**Basic Graph */
823
+ graph ( type , data , { area = true , points = true , text = true , low = NaN , high = NaN , match = null , labels = null , width = 480 , height = 315 , ticks = 0 } = { } ) {
824
+ //Generate SVG
825
+ const margin = { top :10 , left :10 , right :10 , bottom :45 }
826
+ const d3n = new D3node ( )
827
+ const svg = d3n . createSVG ( width , height )
828
+
829
+ //Data
830
+ const X = data . map ( ( { x} ) => x )
831
+ const start = X . at ( 0 )
832
+ const end = X . at ( - 1 )
833
+ const Y = data . map ( ( { y} ) => y )
834
+ const extremum = Math . max ( ...Y )
835
+ high = ! Number . isNaN ( high ) ? high : extremum
836
+ low = ! Number . isNaN ( low ) ? low : 0
837
+ const T = data . map ( ( { text} , i ) => text ?? Y [ i ] )
838
+
839
+ //Time range
840
+ const x = ( type === "time" ? d3 . scaleTime ( ) : d3 . scaleLinear ( ) )
841
+ . domain ( [ start , end ] )
842
+ . range ( [ margin . top , width - margin . left - margin . right ] )
843
+ let xticks = d3 . axisBottom ( x ) . tickSizeOuter ( 0 )
844
+ if ( labels )
845
+ xticks = xticks . tickFormat ( ( _ , i ) => labels [ i ] )
846
+ if ( ticks )
847
+ xticks = xticks . ticks ( ticks )
848
+ svg . append ( "g" )
849
+ . attr ( "transform" , `translate(${ margin . left } ,${ height - margin . bottom } )` )
850
+ . call ( xticks )
851
+ . call ( g => g . select ( ".domain" ) . attr ( "stroke" , "rgba(127, 127, 127, .8)" ) )
852
+ . call ( g => g . selectAll ( ".tick line" ) . attr ( "stroke-opacity" , 0.5 ) )
853
+ . selectAll ( "text" )
854
+ . attr ( "transform" , "translate(-5,5) rotate(-45)" )
855
+ . style ( "text-anchor" , "end" )
856
+ . style ( "font-size" , 20 )
857
+ . attr ( "fill" , "rgba(127, 127, 127, .8)" )
858
+
859
+ //Data range
860
+ const y = d3 . scaleLinear ( )
861
+ . domain ( [ high , low ] )
862
+ . range ( [ margin . left , height - margin . top - margin . bottom ] )
863
+ svg . append ( "g" )
864
+ . attr ( "transform" , `translate(${ margin . left } ,${ margin . top } )` )
865
+ . call ( d3 . axisRight ( y ) . ticks ( Math . round ( height / 50 ) ) . tickSize ( width - margin . left - margin . right ) )
866
+ . call ( g => g . select ( ".domain" ) . remove ( ) )
867
+ . call ( g => g . selectAll ( ".tick line" ) . attr ( "stroke-opacity" , 0.5 ) . attr ( "stroke-dasharray" , "2,2" ) )
868
+ . call ( g => g . selectAll ( ".tick text" ) . attr ( "x" , 0 ) . attr ( "dy" , - 4 ) )
869
+ . selectAll ( "text" )
870
+ . style ( "font-size" , 20 )
871
+ . attr ( "fill" , "rgba(127, 127, 127, .8)" )
872
+
873
+ //Generate graph line
874
+ const datum = Y . map ( ( y , i ) => [ X . at ( i ) , y ] )
875
+ const tdatum = Y . map ( ( y , i ) => [ X . at ( i ) , y , T [ i ] ] )
876
+ const xticked = xticks . scale ( ) . ticks ( xticks . ticks ( ) [ 0 ] )
877
+ const yticked = match ?. ( tdatum , xticked ) ?? tdatum
878
+ svg . append ( "path" )
879
+ . datum ( datum )
880
+ . attr ( "transform" , `translate(${ margin . left } ,${ margin . top } )` )
881
+ . attr (
882
+ "d" ,
883
+ d3 . line ( )
884
+ . curve ( d3 . curveLinear )
885
+ . x ( d => x ( d [ 0 ] ) )
886
+ . y ( d => y ( d [ 1 ] ) )
887
+ )
888
+ . attr ( "fill" , "transparent" )
889
+ . attr ( "stroke" , "#87ceeb" )
890
+ . attr ( "stroke-width" , 2 )
891
+
892
+ //Generate graph area
893
+ if ( area ) {
894
+ svg . append ( "path" )
895
+ . datum ( datum )
896
+ . attr ( "transform" , `translate(${ margin . left } ,${ margin . top } )` )
897
+ . attr (
898
+ "d" ,
899
+ d3 . area ( )
900
+ . curve ( d3 . curveLinear )
901
+ . x ( d => x ( d [ 0 ] ) )
902
+ . y0 ( d => y ( d [ 1 ] ) )
903
+ . y1 ( ( ) => y ( low ) ) ,
904
+ )
905
+ . attr ( "fill" , "rgba(88, 166, 255, .1)" )
906
+ }
907
+
908
+ //Generate graph points
909
+ if ( points ) {
910
+ svg . append ( "g" )
911
+ . selectAll ( "circle" )
912
+ . data ( yticked )
913
+ . join ( "circle" )
914
+ . attr ( "transform" , `translate(${ margin . left } ,${ margin . top } )` )
915
+ . attr ( "cx" , d => x ( d [ 0 ] ) )
916
+ . attr ( "cy" , d => y ( d [ 1 ] ) )
917
+ . attr ( "r" , 2 )
918
+ . attr ( "fill" , "#106cbc" )
919
+ }
920
+
921
+ //Generate graph text
922
+ if ( text ) {
923
+ svg . append ( "g" )
924
+ . attr ( "fill" , "currentColor" )
925
+ . attr ( "text-anchor" , "middle" )
926
+ . attr ( "font-family" , "sans-serif" )
927
+ . attr ( "font-size" , 10 )
928
+ . attr ( "stroke" , "white" )
929
+ . attr ( "stroke-linejoin" , "round" )
930
+ . attr ( "stroke-width" , 4 )
931
+ . attr ( "paint-order" , "stroke fill" )
932
+ . selectAll ( "text" )
933
+ . data ( yticked )
934
+ . join ( "text" )
935
+ . attr ( "transform" , `translate(${ margin . left } ,${ margin . top - 4 } )` )
936
+ . attr ( "x" , d => x ( d [ 0 ] ) )
937
+ . attr ( "y" , d => y ( d [ 1 ] ) )
938
+ . text ( d => d [ 2 ] ? d [ 2 ] : "" )
939
+ . attr ( "fill" , "rgba(127, 127, 127, .8)" )
940
+ }
941
+
942
+ return d3n . svgString ( )
943
+ } ,
944
+ /**Pie Graph */
945
+ pie ( data , { colors, width = 480 , height = 315 } = { } ) {
946
+ //Generate SVG
947
+ const radius = Math . min ( width , height ) / 2
948
+ const d3n = new D3node ( )
949
+ const svg = d3n . createSVG ( width , height )
950
+
951
+ //Data
952
+ const K = Object . keys ( data )
953
+ const V = Object . values ( data )
954
+ const I = d3 . range ( K . length ) . filter ( i => ! Number . isNaN ( V [ i ] ) )
955
+
956
+ //Construct arcs
957
+ const color = d3 . scaleOrdinal ( K , d3 . schemeSpectral [ K . length ] )
958
+ const arcs = d3 . pie ( ) . padAngle ( 1 / radius ) . sort ( null ) . value ( i => V [ i ] ) ( I )
959
+ const arc = d3 . arc ( ) . innerRadius ( 0 ) . outerRadius ( radius )
960
+ const labels = d3 . arc ( ) . innerRadius ( radius / 2 ) . outerRadius ( radius / 2 )
961
+
962
+ svg . append ( "g" )
963
+ . attr ( "transform" , `translate(${ width / 2 } ,${ height / 2 } )` )
964
+ . attr ( "stroke" , "white" )
965
+ . attr ( "stroke-width" , 1 )
966
+ . attr ( "stroke-linejoin" , "round" )
967
+ . selectAll ( "path" )
968
+ . data ( arcs )
969
+ . join ( "path" )
970
+ . attr ( "fill" , d => colors ?. [ K [ d . data ] ] ?? color ( K [ d . data ] ) )
971
+ . attr ( "d" , arc )
972
+ . append ( "title" )
973
+ . text ( d => `${ K [ d . data ] } \n${ V [ d . data ] } ` )
974
+
975
+ svg . append ( "g" )
976
+ . attr ( "transform" , `translate(${ width / 2 } ,${ height / 2 } )` )
977
+ . attr ( "font-family" , "sans-serif" )
978
+ . attr ( "font-size" , 12 )
979
+ . attr ( "text-anchor" , "middle" )
980
+ . attr ( "fill" , "white" )
981
+ . attr ( "stroke" , "rbga(0,0,0,.9)" )
982
+ . attr ( "paint-order" , "stroke fill" )
983
+ . selectAll ( "text" )
984
+ . data ( arcs )
985
+ . join ( "text" )
986
+ . attr ( "transform" , d => `translate(${ labels . centroid ( d ) } )` )
987
+ . selectAll ( "tspan" )
988
+ . data ( d => {
989
+ const lines = `${ K [ d . data ] } \n${ V [ d . data ] } ` . split ( / \n / )
990
+ return ( d . endAngle - d . startAngle ) > 0.25 ? lines : lines . slice ( 0 , 1 )
991
+ } )
992
+ . join ( "tspan" )
993
+ . attr ( "x" , 0 )
994
+ . attr ( "y" , ( _ , i ) => `${ i * 1.1 } em` )
995
+ . attr ( "font-weight" , ( _ , i ) => i ? null : "bold" )
996
+ . text ( d => d )
997
+
998
+ return d3n . svgString ( )
999
+ }
1000
+ }
0 commit comments