123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416typecolor={r:float;g:float;b:float;a:float}typecmap=Viridis|Plasma|Inferno|Magma|Cividis|Coolwarm|GraymoduleColor=structletrgbrgb={r=r/.255.;g=g/.255.;b=b/.255.;a=1.}letblue=rgb31.119.180.letorange=rgb255.127.14.letgreen=rgb44.160.44.letred=rgb214.39.40.letmagenta=rgb148.103.189.(* purple *)letbrown=rgb140.86.75.letpink=rgb227.119.194.letgray=rgb127.127.127.letolive=rgb188.189.34.letcyan=rgb23.190.207.letblack={r=0.;g=0.;b=0.;a=1.}letwhite={r=1.;g=1.;b=1.;a=1.}letlightgray=rgb211.211.211.letdarkgray=rgb169.169.169.letyellow=oliveendmoduleColormap=structletviridis=Viridisletplasma=Plasmaletinferno=Infernoletmagma=Magmaletcividis=Cividisletcoolwarm=Coolwarmletgray=Grayletviridis_lut=[|(* 0.0 *)(0.267004,0.004874,0.329415);(* 0.25 *)(0.190631,0.407061,0.556089);(* 0.5 *)(0.128729,0.563265,0.550194);(* 0.75 *)(0.49259,0.773646,0.389166);(* 1.0 *)(0.993248,0.906157,0.143936);|]letplasma_lut=[|(* 0.0 *)(0.050383,0.029803,0.527975);(* 0.25 *)(0.457721,0.049901,0.71019);(* 0.5 *)(0.78396,0.20376,0.559144);(* 0.75 *)(0.976039,0.479697,0.215519);(* 1.0 *)(0.940015,0.975198,0.131326);|]letinferno_lut=[|(* 0.0 *)(0.001462,0.000466,0.013866);(* 0.25 *)(0.239404,0.052262,0.318459);(* 0.5 *)(0.647059,0.15751,0.138346);(* 0.75 *)(0.974998,0.54451,0.07199);(* 1.0 *)(0.988362,0.998364,0.644924);|]letmagma_lut=[|(* 0.0 *)(0.001462,0.000466,0.013866);(* 0.25 *)(0.263399,0.090783,0.35913);(* 0.5 *)(0.67503,0.180444,0.30488);(* 0.75 *)(0.97875,0.563173,0.30923);(* 1.0 *)(0.988621,0.991277,0.749974);|]letcividis_lut=[|(* 0.0 *)(0.0,0.128113,0.294719);(* 0.25 *)(0.212092,0.31128,0.39814);(* 0.5 *)(0.458655,0.492975,0.40661);(* 0.75 *)(0.724916,0.691162,0.313601);(* 1.0 *)(0.993578,0.98313,0.20794);|]letcoolwarm_lut=[|(* 0.0 *)(0.2298057,0.29871797,0.75368315);(* 0.25 *)(0.41799106,0.51892369,0.89460784);(* 0.5 *)(0.88024788,0.88024788,0.88024788);(* 0.75 *)(0.91587714,0.56176471,0.38039216);(* 1.0 *)(0.70567316,0.01555616,0.15023281);|]letlerpv0v1t=v0+.(t*.(v1-.v0))letapply_lut(lut:(float*float*float)array)(v:float):float*float*float=letn=Array.lengthlut-1inifn<=0thenlut.(0)elseletv_clamped=max0.0(min1.0v)inletf_idx=v_clamped*.float_of_intninletidx0=int_of_floatf_idxinletidx1=minn(idx0+1)inifidx0=nthenlut.(n)elselett=f_idx-.float_of_intidx0inletr0,g0,b0=lut.(idx0)inletr1,g1,b1=lut.(idx1)inletr=lerpr0r1tinletg=lerpg0g1tinletb=lerpb0b1tin(r,g,b)letapply_colormap(t:cmap)(v:float):int*int*int=letr_f,g_f,b_f=matchtwith|Gray->letv_clamped=max0.0(min1.0v)in(v_clamped,v_clamped,v_clamped)|Viridis->apply_lutviridis_lutv|Plasma->apply_lutplasma_lutv|Inferno->apply_lutinferno_lutv|Magma->apply_lutmagma_lutv|Cividis->apply_lutcividis_lutv|Coolwarm->apply_lutcoolwarm_lutvinletclamp_8bitf=max0(min255(int_of_float((f*.255.)+.0.5)))in(clamp_8bitr_f,clamp_8bitg_f,clamp_8bitb_f)endtypeline_style=Solid|Dashed|DashDot|Dotted|Nonetypemarker_style=|Circle|Point|Pixel|Square|Triangle|Plus|Star|Nonetypeplot_style={fmt_color:coloroption;fmt_linewidth:floatoption;fmt_linestyle:line_styleoption;fmt_marker:marker_styleoption;}letplot_style?color?linewidth?linestyle?marker():plot_style={fmt_color=color;fmt_linewidth=linewidth;fmt_linestyle=linestyle;fmt_marker=marker;}typeline2d={xdata:Nx.float32_t;ydata:Nx.float32_t;color:color;linewidth:float;linestyle:line_style;marker:marker_style;label:stringoption;}typeline3d={xdata:Nx.float32_t;ydata:Nx.float32_t;zdata:Nx.float32_t;color:color;linewidth:float;linestyle:line_style;marker:marker_style;label:stringoption;}typescatter={xdata:Nx.float32_t;ydata:Nx.float32_t;s:float;(* Size *)c:color;(* Color *)marker:marker_style;label:stringoption;}typebar={x:Nx.float32_t;height:Nx.float32_t;width:float;bottom:float;color:color;label:stringoption;}typetext={x:float;y:float;content:string;color:color;fontsize:float;}typeimage_data=Uint8_imageofNx.uint8_t|Float32_imageofNx.float32_ttypeimage={data:image_data;shape:intarray;extent:(float*float*float*float)option;cmap:cmapoption;aspect:stringoption;}typeerrorbar_style={yerr:Nx.float32_toption;xerr:Nx.float32_toption;color:color;linewidth:float;capsize:float;}typestep_where=Pre|Post|Midtypestep={xdata:Nx.float32_t;ydata:Nx.float32_t;color:color;linewidth:float;linestyle:line_style;where:step_where;label:stringoption;}typefill_between={xdata:Nx.float32_t;y1data:Nx.float32_t;y2data:Nx.float32_t;color:color;(* Fill color, alpha is part of the color type *)where:Nx.float32_toption;(* Optional mask *)interpolate:bool;(* Handle NaNs by interpolation? *)label:stringoption;}typecontour={x:Nx.float32_t;(* 1D array of x coordinates *)y:Nx.float32_t;(* 1D array of y coordinates *)z:Nx.float32_t;(* 2D array of z values *)levels:floatarray;(* Contour levels *)colors:colorarrayoption;(* Colors for each level *)linewidths:floatarrayoption;(* Line widths for each level *)linestyles:line_stylearrayoption;(* Line styles for each level *)}typecontour_filled={x:Nx.float32_t;(* 1D array of x coordinates *)y:Nx.float32_t;(* 1D array of y coordinates *)z:Nx.float32_t;(* 2D array of z values *)levels:floatarray;(* Contour levels *)cmap:cmapoption;(* Colormap *)alpha:float;(* Alpha transparency *)}(* The main Artist variant type *)typet=|Line2Dofline2d|Line3Dofline3d|Scatterofscatter|Barofbar|Textoftext|Imageofimage|Errorbarofline2d*errorbar_style|Stepofstep|FillBetweenoffill_between|Contourofcontour|ContourFilledofcontour_filledletline2d?(color=Color.blue)?(linewidth=1.5)?(linestyle=Solid)?(marker=None)?labelxy=ifNx.ndimx<>Nx.ndimytheninvalid_arg"Artist.line2d: x and y dimensions must match";Line2D{xdata=x;ydata=y;color;linewidth;linestyle;marker;label}letline3d?(color=Color.blue)?(linewidth=1.5)?(linestyle=Solid)?(marker=None)?labelxyz=ifNx.ndimx<>Nx.ndimy||Nx.ndimx<>Nx.ndimztheninvalid_arg"Artist.line3d: x, y, and z dimensions must match";Line3D{xdata=x;ydata=y;zdata=z;color;linewidth;linestyle;marker;label;}letscatter?(s=20.0)?(c=Color.blue)?(marker=Circle)?labelxy=ifNx.ndimx<>Nx.ndimytheninvalid_arg"Artist.scatter: x and y dimensions must match";Scatter{xdata=x;ydata=y;s;c;marker;label}letbar?(width=0.8)?(bottom=0.0)?(color=Color.blue)?label~heightx=ifNx.sizex<>Nx.sizeheighttheninvalid_arg"Artist.bar: x and height lengths must match";Bar{x;height;width;bottom;color;label}lettext?(color=Color.black)?(fontsize=10.0)~x~ycontent=Text{x;y;content;color;fontsize}letimage:typeab.?extent:float*float*float*float->?cmap:cmap->?aspect:string->(a,b)Nx.t->t=fun?extent?cmap?aspectdata->matchNx.dtypedatawith|Nx.UInt8->letshape=Nx.shapedatainletdata=Uint8_imagedatainImage{data;shape;extent;cmap;aspect}|Nx.Float32->letshape=Nx.shapedatainletdata=Float32_imagedatainImage{data;shape;extent;cmap;aspect}|_->invalid_arg"Artist.image: Unsupported data type. Only UInt8 and Float32 supported"letimage_uint8?extent?cmap?aspectdata=letshape=Nx.shapedatainImage{data=Uint8_imagedata;shape;extent;cmap;aspect}letimage_float32?extent?cmap?aspectdata=letshape=Nx.shapedatainImage{data=Float32_imagedata;shape;extent;cmap;aspect}letstep?(color=Color.blue)?(linewidth=1.5)?(linestyle=Solid)?(where=Post)?labelxy=ifNx.ndimx<>1||Nx.ndimy<>1||Nx.sizex<>Nx.sizeytheninvalid_arg"Artist.step: x and y must be 1D arrays of the same size";Step{xdata=x;ydata=y;color;linewidth;linestyle;where;label}letfill_between?(color=Color.blue)?where?(interpolate=false)?labelxy1y2=ifNx.sizex<>Nx.sizey1||Nx.sizex<>Nx.sizey2theninvalid_arg"Artist.fill_between: x, y1, and y2 must have the same size";(matchwherewith|SomewwhenNx.sizew<>Nx.sizex->invalid_arg"Artist.fill_between: where mask must have same size as x"|_->());FillBetween{xdata=x;y1data=y1;y2data=y2;color;where;interpolate;label}letcontour?colors?linewidths?linestyles~levelsxyz=ifNx.ndimx<>1||Nx.ndimy<>1theninvalid_arg"Artist.contour: x and y must be 1D arrays";ifNx.ndimz<>2theninvalid_arg"Artist.contour: z must be a 2D array";letz_shape=Nx.shapezinifz_shape.(0)<>Nx.sizey||z_shape.(1)<>Nx.sizextheninvalid_arg"Artist.contour: z shape must match (len(y), len(x))";(* Validate optional arrays if provided *)(matchcolorswith|SomecwhenArray.lengthc<>Array.lengthlevels->invalid_arg"Artist.contour: colors array must match levels length"|_->());(matchlinewidthswith|SomelwwhenArray.lengthlw<>Array.lengthlevels->invalid_arg"Artist.contour: linewidths array must match levels length"|_->());(matchlinestyleswith|SomelswhenArray.lengthls<>Array.lengthlevels->invalid_arg"Artist.contour: linestyles array must match levels length"|_->());Contour{x;y;z;levels;colors;linewidths;linestyles}letcontourf?(cmap=Viridis)?(alpha=1.0)~levelsxyz=ifNx.ndimx<>1||Nx.ndimy<>1theninvalid_arg"Artist.contourf: x and y must be 1D arrays";ifNx.ndimz<>2theninvalid_arg"Artist.contourf: z must be a 2D array";letz_shape=Nx.shapezinifz_shape.(0)<>Nx.sizey||z_shape.(1)<>Nx.sizextheninvalid_arg"Artist.contourf: z shape must match (len(y), len(x))";ifalpha<0.0||alpha>1.0theninvalid_arg"Artist.contourf: alpha must be between 0.0 and 1.0";ContourFilled{x;y;z;levels;cmap=Somecmap;alpha}