frommathimportpifromtypingimportAny,Literalfrombokeh.core.propertiesimportexpr,valuefrombokeh.documentimportDocumentfrombokeh.embedimportfile_htmlfrombokeh.modelsimport(Arc,Circle,ColumnDataSource,Plot,PolarTransform,Range1d,Ray,Text)frombokeh.util.browserimportviewxdr=Range1d(start=-1.25,end=1.25)ydr=Range1d(start=-1.25,end=1.25)plot=Plot(x_range=xdr,y_range=ydr,width=600,height=600)plot.toolbar_location=Noneplot.outline_line_color=Nonestart_angle=pi+pi/4end_angle=-pi/4max_kmh=250max_mph=max_kmh*0.621371major_step,minor_step=25,5plot.add_glyph(Circle(x=0,y=0,radius=1.00,fill_color="white",line_color="black"))plot.add_glyph(Circle(x=0,y=0,radius=0.05,fill_color="gray",line_color="black"))plot.add_glyph(Text(x=0,y=+0.15,text=value("km/h"),text_color="red",text_align="center",text_baseline="bottom",text_font_style="bold"))plot.add_glyph(Text(x=0,y=-0.15,text=value("mph"),text_color="blue",text_align="center",text_baseline="top",text_font_style="bold"))defdata(val:float):"""Shorthand to override default units with "data", for e.g. `Ray.length`. """returnvalue(val,units="data")defspeed_to_angle(speed:float,units:str)->float:max_speed=max_kmhifunits=="kmh"elsemax_mphspeed=min(max(speed,0),max_speed)total_angle=start_angle-end_angleangle=total_angle*float(speed)/max_speedreturnstart_angle-angledefadd_needle(speed:float,units:str)->None:angle=speed_to_angle(speed,units)plot.add_glyph(Ray(x=0,y=0,length=data(0.75),angle=angle,line_color="black",line_width=3))plot.add_glyph(Ray(x=0,y=0,length=data(0.10),angle=angle-pi,line_color="black",line_width=3))defadd_gauge(radius:float,max_value:float,length:float,direction:Literal[-1,1],color:Any,major_step:int,minor_step:int)->None:major_angles,minor_angles=[],[]total_angle=start_angle-end_anglemajor_angle_step=float(major_step)/max_value*total_angleminor_angle_step=float(minor_step)/max_value*total_anglemajor_angle=0whilemajor_angle<=total_angle:major_angles.append(start_angle-major_angle)major_angle+=major_angle_stepminor_angle=0whileminor_angle<=total_angle:minor_angles.append(start_angle-minor_angle)minor_angle+=minor_angle_stepmajor_labels=[major_step*ifori,_inenumerate(major_angles)]n=major_step/minor_stepminor_angles=[xfori,xinenumerate(minor_angles)ifi%n!=0]glyph=Arc(x=0,y=0,radius=radius,start_angle=start_angle,end_angle=end_angle,direction="clock",line_color=color,line_width=2)plot.add_glyph(glyph)rotation=0ifdirection==1else-piangles=[angle+rotationforangleinmajor_angles]source=ColumnDataSource(dict(major_angles=major_angles,angle=angles))t=PolarTransform(radius=radius,angle="major_angles")glyph=Ray(x=expr(t.x),y=expr(t.y),length=data(length),angle="angle",line_color=color,line_width=2)plot.add_glyph(source,glyph)angles=[angle+rotationforangleinminor_angles]source=ColumnDataSource(dict(minor_angles=minor_angles,angle=angles))t=PolarTransform(radius=radius,angle="minor_angles")glyph=Ray(x=expr(t.x),y=expr(t.y),length=data(length/2),angle="angle",line_color=color,line_width=1)plot.add_glyph(source,glyph)text_angles=[angle-pi/2forangleinmajor_angles]source=ColumnDataSource(dict(major_angles=major_angles,angle=text_angles,text=major_labels))t=PolarTransform(radius=radius+2*length*direction,angle="major_angles")glyph=Text(x=expr(t.x),y=expr(t.y),angle="angle",text="text",text_align="center",text_baseline="middle")plot.add_glyph(source,glyph)add_gauge(0.75,max_kmh,0.05,+1,"red",major_step,minor_step)add_gauge(0.70,max_mph,0.05,-1,"blue",major_step,minor_step)add_needle(55,"kmh")doc=Document()doc.add_root(plot)if__name__=="__main__":doc.validate()filename="gauges.html"withopen(filename,"w")asf:f.write(file_html(doc,title="Gauges"))print(f"Wrote {filename}")view(filename)