Skip to content

Instantly share code, notes, and snippets.

@uwezi
Last active September 16, 2025 07:33
Show Gist options
  • Save uwezi/65d3a635674757388484e66c305ea29b to your computer and use it in GitHub Desktop.
Save uwezi/65d3a635674757388484e66c305ea29b to your computer and use it in GitHub Desktop.
[NPN transistor] Current flows in a common-emitter NPN transistor. #manim #animation #physics #electronics
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
from manim import*
class AnalogMeter(VGroup):
def __init__(self,
value = 0,
width=4,
height=3.5,
corner_radius=0.5,
outer_stroke_width=5,
outer_stroke_color=GRAY,
frame_color=BLACK,
scale_aspect=4/2.5,
scale_background_color=0xffffcc,
scale_color=0x000099,
scale_range=[0,10,2],
scale_mtics=5,
scale_format="{:.0f}",
scale_fontsize=18,
scale_unit="mA",
scale_unit_fontsize=28,
scale_unit_pos=(0.5,0.4),
pointer_color=BLACK,
pointer_stroke_width=3,
frame_width=0.2,
**kwargs
):
self.frame = RoundedRectangle(
width=width, height=height,
corner_radius=corner_radius,
)
self.scale_background = RoundedRectangle(
width=width-2*frame_width, height=(width-2*frame_width)/scale_aspect,
corner_radius=corner_radius-frame_width if frame_width<corner_radius else 0.1,
fill_opacity=1.0, fill_color=scale_background_color,
stroke_width=0,
).set_z_index(0)
self.scale_background.shift(self.frame.get_top()-self.scale_background.get_top()+frame_width*DOWN)
self.frame = Difference(
self.frame,
self.scale_background
).set_stroke(
width=outer_stroke_width, color=outer_stroke_color,
).set_fill(
opacity=1.0, color=frame_color
).set_z_index(2)
self.pivot = VGroup(
Circle(
radius=frame_width/2,
stroke_width=outer_stroke_width/2, color=outer_stroke_color,
),
Line(
frame_width/2*LEFT,
frame_width/2*RIGHT,
stroke_width=outer_stroke_width,
).rotate(30*DEGREES)
).set_z_index(3)
self.pivot.shift((self.frame.get_bottom()+self.scale_background.get_bottom())/2-self.pivot[0].get_center())
self.value = ValueTracker(value)
self.pointer = Line(
ORIGIN,1.3*0.5*self.scale_background.width*RIGHT,
color = pointer_color,
stroke_width=pointer_stroke_width,
).shift(
self.pivot[0].get_center()
).rotate(
135*DEGREES, about_point=self.pivot[0].get_center()
).set_z_index(1)
self.scale_values = np.arange(start=scale_range[0],stop=scale_range[1]+scale_range[2]/2,step=scale_range[2])
self.scale_arc = VGroup(
Arc(
radius=0.9*self.pointer.get_length(),
start_angle=45*DEGREES,
angle=90*DEGREES,
arc_center=self.pivot.get_center(),
stroke_width=3,
stroke_color=scale_color,
),
*[
VGroup(
Line(
self.pointer.point_from_proportion(0.85),
self.pointer.point_from_proportion(0.95),
stroke_width=3,
stroke_color=scale_color,
),
MathTex(
scale_format.format(value),
font_size=scale_fontsize,
color=scale_color,
).rotate(45*DEGREES).shift(self.pointer.point_from_proportion(1))
).rotate(-i*90*DEGREES/(len(self.scale_values)-1), about_point=self.pivot.get_center())
for i,value in enumerate(self.scale_values)
],
*[
VGroup(
Line(
self.pointer.point_from_proportion(0.90),
self.pointer.point_from_proportion(0.95),
stroke_width=1,
stroke_color=scale_color,
),
).rotate(-(i+j/scale_mtics)*90*DEGREES/(len(self.scale_values)-1), about_point=self.pivot.get_center())
for i in range(len(self.scale_values)-1) for j in range(scale_mtics)
],
Tex(
scale_unit, font_size=scale_unit_fontsize,color=scale_color
).shift(
[
self.scale_background.get_left()[0]+scale_unit_pos[0]*self.scale_background.width,
self.scale_background.get_bottom()[1]+scale_unit_pos[1]*self.scale_background.height,
0
]
),
).set_z_index(1)
super().__init__(
self.frame,
self.scale_background,
self.pivot,
self.pointer,
self.scale_arc,
**kwargs
)
self.add_updater(self.updater, call_updater=True)
def updater(self, mobj):
newangle = 135*DEGREES-90*DEGREES*self.value.get_value()
self.pointer.rotate(
angle=newangle-self.pointer.get_angle(),
about_point=self.pivot[0].get_center()
)
def scale(self, scale_factor, scale_stroke = True, **kwargs):
return super().scale(scale_factor=scale_factor, scale_stroke=scale_stroke, **kwargs)
class metertest(Scene):
def construct(self):
ampmeter = AnalogMeter(scale_color=0x003399, scale_unit="µA", scale_range=[0,100,20])
voltmeter = AnalogMeter(scale_color=0xb30000, scale_unit="V").next_to(ampmeter,RIGHT)
self.add(ampmeter,voltmeter)
for _ in range(5):
self.play(
ampmeter.value.animate.set_value(np.random.uniform(0,1)),
voltmeter.value.animate.set_value(np.random.uniform(0,1)),
)
self.wait()
class Charge(Mobject):
def __init__(self, object=None, color=RED, symbol=r"\bfseries +", symbol_color=WHITE, diameter=0.2, trace=None, pos=0, speed=1, **kwargs):
super().__init__(**kwargs)
self.pos = pos
self.speed = speed
self.trace = trace
if object == None:
self.add(Dot(color=color, radius=diameter/2))
if symbol != None:
self.add(Tex(symbol, color=symbol_color).scale_to_fit_width(0.8*diameter))
else:
self.add(object.copy())
self.add_updater(self.updater, call_updater=True)
def updater(self, mobj, dt):
self.pos = (self.pos+dt*self.speed) % 1.0
self.move_to(self.trace.point_from_proportion(self.pos))
class carriers2(Scene):
def construct(self):
trace1 = RoundedRectangle(width=5,height=2.5, stroke_width=1).reverse_points().shift(2.5*LEFT+1.25*DOWN)
trace2 = RoundedRectangle(width=5,height=5, stroke_width=1).shift(2.5*RIGHT)
dia = .15
obj = VGroup(
Line(dia/2.4*LEFT, dia/2.4*RIGHT, stroke_width=dia*20, cap_style=CapStyleType.ROUND,color=WHITE).set_z_index(2),
Line(dia/2.4*DOWN, dia/2.4*UP, stroke_width=dia*20, cap_style=CapStyleType.ROUND,color=WHITE).set_z_index(2),
*[
Dot(radius=r, fill_opacity=1 if r<(dia/2) else np.exp(-(r-dia)/(dia/10)), color=PURE_RED)
for r in np.linspace(0,2*dia,30)
]
)
charges1 = Group(
*[
Charge(object=obj, trace=trace1, pos=i*1/10, speed=0.1)
for i in range(10)
]
)
charges2 = Group(
*[
Charge(object=obj, trace=trace2, pos=i*1/40, speed=0.1)
for i in range(40)
]
)
self.add(trace1, charges1, trace2, charges2)
self.wait(5)
class npn_currents(Scene):
def construct(self):
dia = .15
chrg = VGroup(
Line(dia/2.4*LEFT, dia/2.4*RIGHT, stroke_width=dia*20, cap_style=CapStyleType.ROUND,color=WHITE).set_z_index(2),
Line(dia/2.4*DOWN, dia/2.4*UP, stroke_width=dia*20, cap_style=CapStyleType.ROUND,color=WHITE).set_z_index(2),
*[
Dot(radius=r, fill_opacity=1 if r<(dia/2) else np.exp(-(r-dia)/(dia/10)), color=PURE_RED)
for r in np.linspace(0,2*dia,30)
]
)
#npl = NumberPlane().add_coordinates()
#self.add(npl)
schematics = SVGMobject("bilder/20250606_npn.svg").scale_to_fit_height(5)
schematics.set_color(WHITE)
self.add(schematics)
base_ameter = AnalogMeter(
value = 0.5,
scale_color=0x003399,
scale_unit="µA", scale_range=[0,100,20],
scale_fontsize=30,
scale_unit_fontsize=45,
).scale_to_fit_width(1.5).move_to([-3,0.4,0]).set_z_index(5)
base_vmeter = AnalogMeter(
value = .65,
scale_color=0xb30000,
scale_unit="V", scale_range=[0,1,0.2],
scale_format="{:.1f}",
scale_fontsize=30,
scale_unit_fontsize=45,
).scale_to_fit_width(1.5).move_to([-1.5,-1.2,0]).set_z_index(5)
base_vmeter_cables = VGroup(
Arrow(
[-1.5,-1,0],
[-1.5,schematics[60].get_end()[1],0],
buff=0,
color=RED,
),
Arrow(
[-1.5,-1,0],
[-1.5,schematics[62].get_center()[1],0],
buff=0,
color=RED,
),
).set_z_index(-2)
collector_ameter = AnalogMeter(
value=0.7,
scale_color=0x003399,
scale_unit="mA", scale_range=[0,10,2],
scale_fontsize=30,
scale_unit_fontsize=45,
).scale_to_fit_width(1.5).move_to([2.5,2.8,0]).set_z_index(5)
self.add(base_ameter, base_vmeter, collector_ameter,base_vmeter_cables)
wire_base = VMobject().set_points_as_corners(
[
schematics[0].get_bottom(),
*schematics[60].get_all_points(),
schematics[55].get_start(),
]
)
wire_baseemitter = VMobject().set_points_as_corners(
[
schematics[55].get_start(),
schematics[54].get_start(),
schematics[54].get_end(),
schematics[64].get_start(),
schematics[64].get_end(),
schematics[65].get_center(),
schematics[66].get_center(),
schematics[1].get_end(),
]
)
wire_collector = VMobject().set_points_as_corners(
[
schematics[15].get_start(),
schematics[15].get_end(),
*schematics[68].get_all_points(),
schematics[53].get_end(),
schematics[53].get_start(),
]
)
wire_collectoremitter = VMobject().set_points_as_corners(
[
schematics[53].get_start(),
schematics[54].get_start(),
schematics[54].get_end(),
schematics[65].get_center(),
schematics[67].get_center(),
schematics[18].get_end(),
]
)
charges1 = Group(
*[
Charge(object=chrg, trace=wire_base, pos=i*1/4, speed=0.2)
for i in range(4)
]
)
charges2 = Group(
*[
Charge(object=chrg, trace=wire_baseemitter, pos=i*1/5, speed=0.2)
for i in range(5)
]
)
charges3 = Group(
*[
Charge(object=chrg, trace=wire_collector, pos=i*1/20, speed=0.2)
for i in range(20)
]
)
charges4 = Group(
*[
Charge(object=chrg, trace=wire_collectoremitter, pos=i*1/20, speed=0.2)
for i in range(20)
]
)
self.add(charges1,charges2,charges3,charges4)
self.wait(5)
class index(Scene):
def construct(self):
for x in range(-7, 8):
for y in range(-4, 5):
if (x==0) and (y==0):
self.add(Dot(np.array([x, y, 0]),radius=0.03, color=RED))
else:
self.add(Dot(np.array([x, y, 0]),radius=0.03, color=DARK_GREY))
circuit = SVGMobject(r"bilder/20250606_npn.svg",
stroke_color=WHITE, stroke_width=3, fill_color=WHITE,width=10)
self.add(circuit)
#self.wait(2)
bbox = Rectangle(height=circuit.height, width=circuit.width).move_to(circuit.get_center(),ORIGIN)
bbox.scale(1.4)
loc = bbox.get_critical_point(UL)
w = bbox.width
h = bbox.height
cf = 2*w + 2*h
dl = cf / (len(circuit)+3)
dir = [dl,0,0]
edge = 0
positions = []
for i in range(len(circuit)):
positions.append(loc)
loc = loc + dir
if (edge == 0) and (loc[0] > bbox.get_critical_point(UP+RIGHT)[0]):
edge = 1
loc = loc - dir
dir = [0,-dl,0]
loc = loc + dir
if (edge == 1) and (loc[1] < bbox.get_critical_point(DOWN+RIGHT)[1]):
edge = 2
loc = loc - dir
dir = [-dl,0,0]
loc = loc + dir
if (edge == 2) and (loc[0] < bbox.get_critical_point(DOWN+LEFT)[0]):
edge = 3
loc = loc - dir
dir = [0,+dl,0]
loc = loc + dir
for i in range(len(circuit)):
shortest = 1e6
found = 0
for j in range(len(positions)):
dist = np.sqrt((circuit[i].get_center()[0]-positions[j][0])**2 + (circuit[i].get_center()[1]-positions[j][1])**2)
if dist < shortest:
shortest = dist
found = j
color = [BLUE,GREEN,RED,YELLOW,PURPLE][i%5]
circuit[i].set_color(color)
txt = Text("{}".format(i)).scale(0.15).move_to(positions[found]).set_color(color)
line = Line(circuit[i].get_center(),end=txt.get_center(), stroke_width=1).set_color(color)
self.add(Tex(r"S", font_size=10, color=color).move_to(circuit[i].get_start()))
self.add(Tex(r"E", font_size=10, color=color).move_to(circuit[i].get_end()))
self.add(line)
self.add(txt)
# self.wait(1)
positions.pop(found)

Animation of the current flow in a common-emitter connected NPN transistor. Work in progress...

npn_currents_ManimCE_v0 19 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment