mpld3: Bridging Matplotlib and D3js

http://mpld3.github.io

Jake VanderPlas

@jakevdp

If you haven't noticed, IPython notebook is

awesome for interactive data analysis


In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

In [2]:
x, y = np.random.normal(0, 1, [2, 100])
c, s = 800 * np.random.random([2, 100])

fig = plt.figure()
points = plt.scatter(x, y, c=c, s=s, alpha=0.3)
plt.grid(color="lightgray");


The Solution: translate matplotlib to D3js!


In [3]:
import mpld3
mpld3.enable_notebook()
fig


Out[3]:

What is this actually doing?

Using the mplexporter package to scrape info from the matplotlib object

Outputs a JSON representation of the figure


In [4]:
mpld3.fig_to_dict(fig)


Out[4]:
{'axes': [{'axes': [{'fontsize': 10.0,
     'grid': {'alpha': 1.0,
      'color': '#D3D3D3',
      'dasharray': '2,2',
      'gridOn': True},
     'nticks': 8,
     'position': 'bottom',
     'scale': 'linear',
     'tickformat': None,
     'tickvalues': None},
    {'fontsize': 10.0,
     'grid': {'alpha': 1.0,
      'color': '#D3D3D3',
      'dasharray': '2,2',
      'gridOn': True},
     'nticks': 7,
     'position': 'left',
     'scale': 'linear',
     'tickformat': None,
     'tickvalues': None}],
   'axesbg': '#FFFFFF',
   'axesbgalpha': None,
   'bbox': (0.125, 0.125, 0.77500000000000002, 0.77500000000000002),
   'collections': [{'alphas': [0.3],
     'edgecolors': ['#000000'],
     'edgewidths': (1.0,),
     'facecolors': ['#00A4FF',
      '#D10000',
      '#7CFF79',
      '#0000AC',
      '#70FF86',
      '#0000CC',
      '#FFA300',
      '#0088FF',
      '#FF6B00',
      '#C7FF2F',
      '#FF7600',
      '#008CFF',
      '#FF5C00',
      '#E80000',
      '#0000FA',
      '#F4F802',
      '#FF3F00',
      '#0000BA',
      '#FF9F00',
      '#E0FF15',
      '#86FF70',
      '#FF2800',
      '#0FF8E7',
      '#003CFF',
      '#D50000',
      '#0000A8',
      '#0000E3',
      '#00C0FF',
      '#76FF80',
      '#FF9F00',
      '#FE1200',
      '#003CFF',
      '#F10700',
      '#FAF000',
      '#00ACFF',
      '#0020FF',
      '#39FFBD',
      '#90FF66',
      '#0000EC',
      '#EDFF08',
      '#00007F',
      '#0094FF',
      '#76FF80',
      '#FFA600',
      '#0000FF',
      '#0020FF',
      '#0094FF',
      '#22FFD4',
      '#A0FF56',
      '#CAFF2C',
      '#29FFCD',
      '#F10700',
      '#FF4D00',
      '#002CFF',
      '#B7FF3F',
      '#FFC400',
      '#0000B6',
      '#AC0000',
      '#3FFFB7',
      '#FFCB00',
      '#FF2100',
      '#FF1D00',
      '#DDFF18',
      '#46FFB0',
      '#00008D',
      '#FA0F00',
      '#C80000',
      '#0000A8',
      '#29FFCD',
      '#0014FF',
      '#880000',
      '#FF4A00',
      '#E80000',
      '#FF7E00',
      '#FF5100',
      '#0000FF',
      '#D50000',
      '#0000BA',
      '#00E0FA',
      '#FF2800',
      '#B0FF46',
      '#25FFD0',
      '#0084FF',
      '#F4F802',
      '#FF9B00',
      '#25FFD0',
      '#FFA600',
      '#3FFFB7',
      '#0040FF',
      '#AC0000',
      '#42FFB3',
      '#80FF76',
      '#0078FF',
      '#FA0F00',
      '#FFE500',
      '#FF6B00',
      '#4FFFA6',
      '#FF9F00',
      '#7F0000',
      '#FF5C00'],
     'id': '716664397444752',
     'offsetcoordinates': 'data',
     'offsets': 'data01',
     'pathcoordinates': 'display',
     'paths': [([[0.0, -0.5],
        [0.13260155, -0.5],
        [0.25978993539242673, -0.44731684579412084],
        [0.3535533905932738, -0.3535533905932738],
        [0.44731684579412084, -0.25978993539242673],
        [0.5, -0.13260155],
        [0.5, 0.0],
        [0.5, 0.13260155],
        [0.44731684579412084, 0.25978993539242673],
        [0.3535533905932738, 0.3535533905932738],
        [0.25978993539242673, 0.44731684579412084],
        [0.13260155, 0.5],
        [0.0, 0.5],
        [-0.13260155, 0.5],
        [-0.25978993539242673, 0.44731684579412084],
        [-0.3535533905932738, 0.3535533905932738],
        [-0.44731684579412084, 0.25978993539242673],
        [-0.5, 0.13260155],
        [-0.5, 0.0],
        [-0.5, -0.13260155],
        [-0.44731684579412084, -0.25978993539242673],
        [-0.3535533905932738, -0.3535533905932738],
        [-0.25978993539242673, -0.44731684579412084],
        [-0.13260155, -0.5],
        [0.0, -0.5]],
       ['M', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'Z'])],
     'pathtransforms': [[19.92093989207512,
       0.0,
       0.0,
       19.92093989207512,
       0.0,
       0.0],
      [8.44356996879559, 0.0, 0.0, 8.44356996879559, 0.0, 0.0],
      [18.732953788195122, 0.0, 0.0, 18.732953788195122, 0.0, 0.0],
      [25.94379469422006, 0.0, 0.0, 25.94379469422006, 0.0, 0.0],
      [20.68963515539038, 0.0, 0.0, 20.68963515539038, 0.0, 0.0],
      [30.74438242726398, 0.0, 0.0, 30.74438242726398, 0.0, 0.0],
      [23.134089226319563, 0.0, 0.0, 23.134089226319563, 0.0, 0.0],
      [17.10938476288781, 0.0, 0.0, 17.10938476288781, 0.0, 0.0],
      [25.16852582688653, 0.0, 0.0, 25.16852582688653, 0.0, 0.0],
      [29.061335867734385, 0.0, 0.0, 29.061335867734385, 0.0, 0.0],
      [24.15914745169886, 0.0, 0.0, 24.15914745169886, 0.0, 0.0],
      [29.784951796430416, 0.0, 0.0, 29.784951796430416, 0.0, 0.0],
      [21.838174476116937, 0.0, 0.0, 21.838174476116937, 0.0, 0.0],
      [26.111161869292246, 0.0, 0.0, 26.111161869292246, 0.0, 0.0],
      [25.264185579548208, 0.0, 0.0, 25.264185579548208, 0.0, 0.0],
      [26.87508039776932, 0.0, 0.0, 26.87508039776932, 0.0, 0.0],
      [26.95376410501852, 0.0, 0.0, 26.95376410501852, 0.0, 0.0],
      [21.763298428899073, 0.0, 0.0, 21.763298428899073, 0.0, 0.0],
      [28.70051197182572, 0.0, 0.0, 28.70051197182572, 0.0, 0.0],
      [18.466747752498314, 0.0, 0.0, 18.466747752498314, 0.0, 0.0],
      [6.740730075751437, 0.0, 0.0, 6.740730075751437, 0.0, 0.0],
      [27.41291103298046, 0.0, 0.0, 27.41291103298046, 0.0, 0.0],
      [19.467752398124283, 0.0, 0.0, 19.467752398124283, 0.0, 0.0],
      [25.981244699722897, 0.0, 0.0, 25.981244699722897, 0.0, 0.0],
      [30.891046944097972, 0.0, 0.0, 30.891046944097972, 0.0, 0.0],
      [29.8609215544213, 0.0, 0.0, 29.8609215544213, 0.0, 0.0],
      [30.95350562620045, 0.0, 0.0, 30.95350562620045, 0.0, 0.0],
      [28.9969876727636, 0.0, 0.0, 28.9969876727636, 0.0, 0.0],
      [21.133631286908454, 0.0, 0.0, 21.133631286908454, 0.0, 0.0],
      [15.451131327026438, 0.0, 0.0, 15.451131327026438, 0.0, 0.0],
      [20.82378444522176, 0.0, 0.0, 20.82378444522176, 0.0, 0.0],
      [27.076887485773007, 0.0, 0.0, 27.076887485773007, 0.0, 0.0],
      [8.859302615904118, 0.0, 0.0, 8.859302615904118, 0.0, 0.0],
      [19.284049809099628, 0.0, 0.0, 19.284049809099628, 0.0, 0.0],
      [24.93642300600889, 0.0, 0.0, 24.93642300600889, 0.0, 0.0],
      [1.3012472052262019, 0.0, 0.0, 1.3012472052262019, 0.0, 0.0],
      [5.135653317932228, 0.0, 0.0, 5.135653317932228, 0.0, 0.0],
      [18.609796190425513, 0.0, 0.0, 18.609796190425513, 0.0, 0.0],
      [8.799266640628286, 0.0, 0.0, 8.799266640628286, 0.0, 0.0],
      [29.219561734269305, 0.0, 0.0, 29.219561734269305, 0.0, 0.0],
      [25.280120172120192, 0.0, 0.0, 25.280120172120192, 0.0, 0.0],
      [31.045526021496364, 0.0, 0.0, 31.045526021496364, 0.0, 0.0],
      [27.797020917871286, 0.0, 0.0, 27.797020917871286, 0.0, 0.0],
      [22.703663860029256, 0.0, 0.0, 22.703663860029256, 0.0, 0.0],
      [21.087100808141944, 0.0, 0.0, 21.087100808141944, 0.0, 0.0],
      [30.397536056458723, 0.0, 0.0, 30.397536056458723, 0.0, 0.0],
      [23.41667760138332, 0.0, 0.0, 23.41667760138332, 0.0, 0.0],
      [27.714239931166166, 0.0, 0.0, 27.714239931166166, 0.0, 0.0],
      [22.071011940932706, 0.0, 0.0, 22.071011940932706, 0.0, 0.0],
      [17.11486507477631, 0.0, 0.0, 17.11486507477631, 0.0, 0.0],
      [3.193564068634321, 0.0, 0.0, 3.193564068634321, 0.0, 0.0],
      [27.555106839786998, 0.0, 0.0, 27.555106839786998, 0.0, 0.0],
      [18.847930091349447, 0.0, 0.0, 18.847930091349447, 0.0, 0.0],
      [25.47245756404889, 0.0, 0.0, 25.47245756404889, 0.0, 0.0],
      [17.52727979794004, 0.0, 0.0, 17.52727979794004, 0.0, 0.0],
      [19.235692564890673, 0.0, 0.0, 19.235692564890673, 0.0, 0.0],
      [16.888574739797463, 0.0, 0.0, 16.888574739797463, 0.0, 0.0],
      [24.738372170139453, 0.0, 0.0, 24.738372170139453, 0.0, 0.0],
      [16.92350126251868, 0.0, 0.0, 16.92350126251868, 0.0, 0.0],
      [15.606676713872295, 0.0, 0.0, 15.606676713872295, 0.0, 0.0],
      [20.008034943968074, 0.0, 0.0, 20.008034943968074, 0.0, 0.0],
      [20.729807018085747, 0.0, 0.0, 20.729807018085747, 0.0, 0.0],
      [9.623343935318879, 0.0, 0.0, 9.623343935318879, 0.0, 0.0],
      [22.658154609053806, 0.0, 0.0, 22.658154609053806, 0.0, 0.0],
      [28.85427189105514, 0.0, 0.0, 28.85427189105514, 0.0, 0.0],
      [20.020116444595256, 0.0, 0.0, 20.020116444595256, 0.0, 0.0],
      [26.10358794295735, 0.0, 0.0, 26.10358794295735, 0.0, 0.0],
      [10.29060203780369, 0.0, 0.0, 10.29060203780369, 0.0, 0.0],
      [28.556428482361596, 0.0, 0.0, 28.556428482361596, 0.0, 0.0],
      [12.95208074878877, 0.0, 0.0, 12.95208074878877, 0.0, 0.0],
      [28.629062521819062, 0.0, 0.0, 28.629062521819062, 0.0, 0.0],
      [9.678164047628435, 0.0, 0.0, 9.678164047628435, 0.0, 0.0],
      [23.087007389768686, 0.0, 0.0, 23.087007389768686, 0.0, 0.0],
      [24.35165095487651, 0.0, 0.0, 24.35165095487651, 0.0, 0.0],
      [6.631845723439543, 0.0, 0.0, 6.631845723439543, 0.0, 0.0],
      [31.138569355101026, 0.0, 0.0, 31.138569355101026, 0.0, 0.0],
      [31.125126012259955, 0.0, 0.0, 31.125126012259955, 0.0, 0.0],
      [23.198589173931428, 0.0, 0.0, 23.198589173931428, 0.0, 0.0],
      [29.099005464381474, 0.0, 0.0, 29.099005464381474, 0.0, 0.0],
      [12.208514033782032, 0.0, 0.0, 12.208514033782032, 0.0, 0.0],
      [8.220983200130823, 0.0, 0.0, 8.220983200130823, 0.0, 0.0],
      [24.75955520160389, 0.0, 0.0, 24.75955520160389, 0.0, 0.0],
      [20.52967132339422, 0.0, 0.0, 20.52967132339422, 0.0, 0.0],
      [30.777924104500325, 0.0, 0.0, 30.777924104500325, 0.0, 0.0],
      [12.369885393918805, 0.0, 0.0, 12.369885393918805, 0.0, 0.0],
      [20.235912371061968, 0.0, 0.0, 20.235912371061968, 0.0, 0.0],
      [14.542839903272785, 0.0, 0.0, 14.542839903272785, 0.0, 0.0],
      [18.794692331090882, 0.0, 0.0, 18.794692331090882, 0.0, 0.0],
      [20.261279232421163, 0.0, 0.0, 20.261279232421163, 0.0, 0.0],
      [22.380933225620208, 0.0, 0.0, 22.380933225620208, 0.0, 0.0],
      [21.38652029306681, 0.0, 0.0, 21.38652029306681, 0.0, 0.0],
      [23.33108488532768, 0.0, 0.0, 23.33108488532768, 0.0, 0.0],
      [22.22428725360176, 0.0, 0.0, 22.22428725360176, 0.0, 0.0],
      [30.730171209412738, 0.0, 0.0, 30.730171209412738, 0.0, 0.0],
      [16.33968989209393, 0.0, 0.0, 16.33968989209393, 0.0, 0.0],
      [26.017827128893668, 0.0, 0.0, 26.017827128893668, 0.0, 0.0],
      [20.45249471692601, 0.0, 0.0, 20.45249471692601, 0.0, 0.0],
      [26.23764384593936, 0.0, 0.0, 26.23764384593936, 0.0, 0.0],
      [18.54297720958381, 0.0, 0.0, 18.54297720958381, 0.0, 0.0],
      [23.651946516558716, 0.0, 0.0, 23.651946516558716, 0.0, 0.0]],
     'xindex': 0,
     'yindex': 1,
     'zorder': 1}],
   'id': '716664397311952',
   'images': [],
   'lines': [],
   'markers': [],
   'paths': [],
   'sharex': [],
   'sharey': [],
   'texts': [],
   'xdomain': (-4.0, 3.0),
   'xlim': (-4.0, 3.0),
   'xscale': 'linear',
   'ydomain': (-3.0, 3.0),
   'ylim': (-3.0, 3.0),
   'yscale': 'linear',
   'zoomable': True}],
 'data': {'data01': [[0.8734258836402906, -0.22184760866563308],
   [-0.15494530604024948, 0.9256560182730929],
   [0.07590134282815923, 0.05761325083459935],
   [0.02620380072264082, 0.7532742623713418],
   [-1.011960484773117, -0.93260880707872],
   [-0.4414203338208444, 0.8616881368716901],
   [0.32369644066279596, -0.46287794843527197],
   [0.6167366323712494, 0.3515650410833242],
   [0.49551558521780714, -0.9632309907798584],
   [-0.8828996522815002, -0.961885983136154],
   [-0.050626368286408346, -1.8921671406426455],
   [0.07664038346891443, -0.0422775749185277],
   [0.39048189574061803, -1.026525574784487],
   [-0.037986515128924654, 0.026235167031678122],
   [0.683919621003351, -1.2381846199266187],
   [-1.0563326136976894, 0.3920949579281722],
   [0.8822254424126245, 1.0543554484817061],
   [0.18292783868724052, -1.106882761229403],
   [0.23122683360422375, -0.9096149245942952],
   [-0.39182975766141415, -1.334836144962068],
   [-0.4513694116328683, -0.5209247273195993],
   [1.3040157815879876, 0.4804932907827059],
   [-0.2921933627367762, -1.833842170557586],
   [-0.5094631042332776, -0.9335878278686992],
   [0.407066542541278, 0.7164828693186136],
   [-0.7955750332092445, 0.3293271009680591],
   [0.3885075080904744, 0.9988980969433916],
   [0.07651040571172239, 0.05855232752260041],
   [-0.4880375827822248, 0.729891306442975],
   [0.09659582515389678, 0.7524574408930911],
   [-1.2518577702057787, -0.6416624196251539],
   [2.096178040340456, 1.524978874141583],
   [0.5664623753769941, 1.0249729932525535],
   [0.5677912275135016, -0.7280683949281407],
   [-0.6821769866201434, -2.6672939783429244],
   [-0.0369905209260721, 0.5260436785079805],
   [-0.12955389345585966, -0.5633742549089914],
   [-0.755809682970359, 1.2192529504966865],
   [0.677289638219098, 0.09966789481049884],
   [-1.6437839615957837, 0.08130213228188482],
   [0.12809823210211457, -1.8167616204622847],
   [1.1542897275423125, -0.5131728271294826],
   [-1.1847880417304228, -0.3340501686959032],
   [-0.33961038133658444, 2.100218745063198],
   [-0.2451156206782224, 0.30026134051844455],
   [1.2756556705585487, -0.8358391131464024],
   [1.0081714950631957, -0.6338039797884031],
   [0.8996306057023036, 0.6121456381450234],
   [-0.411067625010576, -1.0638392065759585],
   [-1.0683700425094342, -0.6667623865529205],
   [-2.859195890517072, -1.1774959632638768],
   [1.117885108263187, 0.9284087465203913],
   [0.27290118568726474, 0.4702007185011954],
   [-0.31111990618132407, -0.5288198570380818],
   [-0.8143901005968739, -0.1189624661197023],
   [-0.4778554845658697, 1.1602401736784635],
   [0.6895439596791872, 0.31217934539964665],
   [-0.741997618772488, 0.5244211609300995],
   [0.5951267828689004, -0.9846144558642771],
   [-0.3110718343999984, 0.6323073738145044],
   [-0.6310624156992264, 0.14837179339461512],
   [-0.3113797926899484, -2.4107691156183364],
   [2.346987338220112, 0.33704641423110854],
   [0.7434354021678722, 0.3365792892438828],
   [0.4693525806359409, -0.3855737763000076],
   [-2.162865080436464, -0.7160652817374227],
   [2.1537149934469455, 0.5204881245870694],
   [-1.063320741953729, 1.6746807955615572],
   [-1.2248193138367844, -0.3853712429534577],
   [-0.3489397701854473, 0.7283895869160394],
   [0.6229627523940046, 0.8803350014649179],
   [0.8055472728682526, 0.39660423229294717],
   [0.32487419780184745, 0.45161224195654925],
   [-0.5825111308714315, 0.16310336961695124],
   [-0.0256263630364359, 0.9016448494352092],
   [0.992991712078224, -0.7923875515753104],
   [0.22482604156764321, 0.23850619032119988],
   [0.8052825685225968, -2.001120780347424],
   [-1.1175730131077468, 1.3061160806072103],
   [-0.21007842630654647, 0.7887709472869571],
   [1.7046763639485885, 0.6806539163227077],
   [0.7135454887399546, 0.7734836703643214],
   [0.6691958573980318, 0.7059110963184605],
   [-1.3856440454617749, -0.653328097675063],
   [-1.9499648936593548, -0.19474229892981598],
   [-1.038320255566956, -1.693041726361187],
   [0.4129614425092695, 0.5741306374886969],
   [1.2558332527424902, 0.5887979921171563],
   [0.36001215262028535, 2.340246535743467],
   [1.7050361060675685, 1.2071476655672178],
   [0.8314268032429676, 1.055443214280702],
   [0.47491069852591883, -1.0698707103866978],
   [-1.37063107924194, 1.0493361245168846],
   [0.4187421861874867, -0.07519103464293732],
   [-0.15943677908417392, 0.04957551528068927],
   [1.3057983191011364, 0.10412644999244096],
   [0.16609151011035725, -0.2638275841960097],
   [0.8483163950944246, 2.261269839160054],
   [1.1574535905275418, -2.478765536065613],
   [0.45165080139076924, -0.7578847604960319]]},
 'height': 320.0,
 'id': '716664397312016',
 'plugins': [{'type': 'reset'},
  {'button': True, 'enabled': False, 'type': 'zoom'},
  {'button': True, 'enabled': False, 'type': 'boxzoom'}],
 'width': 480.0}

This is interpreted by mpld3.js

~1500 line JS library

mpld3 produces a pure client-side view of a Matplotlib plot

No requirement for a server-side callback!

Easy to embed on any static website

But there's more... mpld3 allows you to add plugins:

Plugins = endless possibilities for interactive behavior!

Tooltip Plugin:


In [5]:
from mpld3 import plugins

labels = ['Point {0}'.format(i) for i in range(100)]

tooltips = plugins.PointLabelTooltip(points, labels)
plugins.connect(fig, tooltips)
fig


Out[5]:

Linked Brushing plugin:


In [6]:
from sklearn.datasets import load_iris
iris = load_iris()

# dither the data for clearer plotting
iris.data += 0.1 * np.random.random(iris.data.shape)

fig, ax = plt.subplots(4, 4, sharex="col", sharey="row", figsize=(8, 8))
fig.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95,
                    hspace=0.1, wspace=0.1)

for i in range(4):
    for j in range(4):
        points = ax[3 - i, j].scatter(iris.data[:, j], iris.data[:, i],
                                      c=iris.target, s=40, alpha=0.6)

# remove tick labels
for axi in ax.flat:
    for axis in [axi.xaxis, axi.yaxis]:
        axis.set_major_formatter(plt.NullFormatter())

# Here we connect the linked brush plugin
plugins.connect(fig, plugins.LinkedBrush(points))


Custom Plugins:

Just write a bit of Python + JS


In [7]:
from mpld3 import plugins, utils


class HighlightLines(plugins.PluginBase):
    """A plugin to highlight lines on hover"""

    JAVASCRIPT = """
    mpld3.register_plugin("linehighlight", LineHighlightPlugin);
    LineHighlightPlugin.prototype = Object.create(mpld3.Plugin.prototype);
    LineHighlightPlugin.prototype.constructor = LineHighlightPlugin;
    LineHighlightPlugin.prototype.requiredProps = ["line_ids"];
    LineHighlightPlugin.prototype.defaultProps = {alpha_bg:0.3, alpha_fg:1.0}
    function LineHighlightPlugin(fig, props){
        mpld3.Plugin.call(this, fig, props);
    };

    LineHighlightPlugin.prototype.draw = function(){
      for(var i=0; i<this.props.line_ids.length; i++){
         var obj = mpld3.get_element(this.props.line_ids[i]),
             alpha_fg = this.props.alpha_fg;
             alpha_bg = this.props.alpha_bg;
         obj.elements()
             .on("mouseover", function(d, i){
                            d3.select(this).transition().duration(50)
                              .style("stroke-opacity", alpha_fg); })
             .on("mouseout", function(d, i){
                            d3.select(this).transition().duration(200)
                              .style("stroke-opacity", alpha_bg); });
      }
    };
    """
    def __init__(self, lines):
        self.lines = lines
        self.dict_ = {"type": "linehighlight",
                      "line_ids": [utils.get_id(line) for line in lines],
                      "alpha_bg": lines[0].get_alpha(),
                      "alpha_fg": 1.0}


x = np.linspace(0, 10, 100)
y = 0.1 * (np.random.random((50, 100)) - 0.5)
y = y.cumsum(1)

fig, ax = plt.subplots(subplot_kw={'xticks': [], 'yticks': []})
lines = ax.plot(x, y.T, color='blue', lw=4, alpha=0.1)
plugins.connect(fig, HighlightLines(lines))


Check it out! – http://mpld3.github.io

Jake VanderPlas

@jakevdp


In [8]:
# CSS formatting: run this first!
from IPython.display import HTML
HTML("""
<style>
h1 {text-align:center}
h2 {text-align:center}
h3 {text-align:center}
</style>
""")


Out[8]: