Skip to content

Instantly share code, notes, and snippets.

@HHammond
Last active August 29, 2015 14:02
Show Gist options
  • Save HHammond/7a78d35b34d85406aa60 to your computer and use it in GitHub Desktop.
Save HHammond/7a78d35b34d85406aa60 to your computer and use it in GitHub Desktop.
A quick tutorial on customizing the IPython notebook.
{
"metadata": {
"name": "",
"signature": "sha256:ae12cadbe088aa2df4c44761da5172223e468a824bc68e52b427b2314b428942"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Theming IPython"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"IPython is an awesome tool, but I've found that figuring out how to customize the look and feel of the notebook isn't easy. My goal here is to demonstrate how you can customize the look of your notebooks using a single `.css` file and use that theme wherever your notebooks go."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Customizing the Notebook"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, to make sure that the required CSS files are created, follow the instructions [here](http://nbviewer.ipython.org/github/Carreau/posts/blob/master/Blog1.ipynb#The-answer-:-custom.css).\n",
"\n",
"The interactive notebook has a CSS override file hidden inside of your IPython profile (if you don't know how to locate it, run `ipython locate`) located at:\n",
"\n",
"```bash\n",
"IPYTHONDIR/profile_default/static/custom/custom.css\n",
"```\n",
"\n",
"or on unix-based systems:\n",
"\n",
"```bash\n",
"~/.ipython/profile_default/static/custom/custom.css\n",
"```\n",
"\n",
"Anything inside this file gets priority over the default IPython theme and any CSS modifications made in this tutorial are saved inside of that file."
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Fixing the Typography"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There's a great tutorial on updating the IPython typography by [here](http://slendermeans.org/better-typography-for-ipython-notebooks.html).\n",
"\n",
"I used this tutorial in creating my own CSS theme with a few tweaks (mostly fonts and muting a few interface elements). Here's the code that I used for styling the typography of my own notebook:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```css\n",
"@import url('http://fonts.googleapis.com/css?family=Crimson+Text');\n",
"@import url('http://fonts.googleapis.com/css?family=Kameron');\n",
"@import url('http://fonts.googleapis.com/css?family=Lato:200');\n",
"@import url('http://fonts.googleapis.com/css?family=Lato:300');\n",
"@import url('http://fonts.googleapis.com/css?family=Lato:400');\n",
"@import url('http://fonts.googleapis.com/css?family=Source+Code+Pro');\n",
"\n",
"/* Change code font */\n",
".CodeMirror pre {\n",
" font-family: 'Source Code Pro', Consolas, monocco, monospace;\n",
"}\n",
"\n",
"div.input_area {\n",
" border-color: rgba(0,0,0,0.10);\n",
" background: rbga(0,0,0,0.5);\n",
"}\n",
"\n",
"div.text_cell {\n",
" max-width: 105ex; /* instead of 100%, */\n",
"}\n",
"\n",
"div.text_cell_render {\n",
" font-family: \"Crimson Text\";\n",
" font-size: 12pt;\n",
" line-height: 145%; /* added for some line spacing of text. */\n",
"}\n",
"\n",
"div.text_cell_render h1,\n",
"div.text_cell_render h2,\n",
"div.text_cell_render h3,\n",
"div.text_cell_render h4,\n",
"div.text_cell_render h5,\n",
"div.text_cell_render h6 {\n",
" font-family: 'Kameron';\n",
" font-weight: 300;\n",
"}\n",
"\n",
"div.text_cell_render h1 {\n",
" font-size: 24pt;\n",
"}\n",
"\n",
"div.text_cell_render h2 {\n",
" font-size: 18pt;\n",
"}\n",
"\n",
"div.text_cell_render h3 {\n",
" font-size: 14pt;\n",
"}\n",
"\n",
".rendered_html pre,\n",
".rendered_html code {\n",
" font-size: medium;\n",
"}\n",
"\n",
".rendered_html ol {\n",
" list-style:decimal;\n",
" margin: 1em 2em;\n",
"}\n",
"```"
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Changing the Interface"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, I wanted to flatten out the look of the interface. Knowing that IPython uses [Bootstrap](http://getbootstrap.com/) makes modifying things easier, and the rest can be done just using the Google Chrome developer tools. \n",
"\n",
"Here are the tweaks that I thought were important for making my notebook mine:\n",
"\n",
"* Flatten the toolbar at the top\n",
" * Flatten buttons\n",
" * Remove gradients\n",
" * Make fonts less distracting\n",
"* Mute cells visually\n",
" * Reduce border\n",
" * Reduce colours (use drop shadow for selected cell\n",
" * Mute input prompt\n",
" \n",
"Here's the remainder of my customization code:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```css\n",
".prompt.input_prompt {\n",
" color: rgba(0,0,0,0.5);\n",
"}\n",
"\n",
".cell.command_mode.selected {\n",
" border-color: rgba(0,0,0,0.1);\n",
"}\n",
"\n",
".cell.edit_mode.selected {\n",
" border-color: rgba(0,0,0,0.15);\n",
" box-shadow: 0px 0px 5px #f0f0f0;\n",
" -webkit-box-shadow: 0px 0px 5px #f0f0f0;\n",
"}\n",
"\n",
"div.output_scroll {\n",
" -webkit-box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);\n",
" box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);\n",
" border-radious: 2px;\n",
"}\n",
"\n",
"#menubar .navbar-inner {\n",
" background: #fff;\n",
" -webkit-box-shadow: none;\n",
" box-shadow: none;\n",
" border-radius: 0;\n",
" border: none;\n",
" font-family: lato;\n",
" font-weight: 400;\n",
"}\n",
"\n",
".navbar-fixed-top .navbar-inner,\n",
".navbar-static-top .navbar-inner {\n",
" box-shadow: none;\n",
" -webkit-box-shadow: none;\n",
" border: none;\n",
"}\n",
"\n",
"div#notebook_panel {\n",
" box-shadow: none;\n",
" -webkit-box-shadow: none;\n",
" border-top: none;\n",
"}\n",
"\n",
"div#notebook {\n",
" border-top: 1px solid rgba(0,0,0,0.15);\n",
"}\n",
"\n",
"#menubar .navbar .navbar-inner,\n",
".toolbar-inner {\n",
" padding-left: 0;\n",
" padding-right: 0;\n",
"}\n",
"\n",
"#checkpoint_status,\n",
"#autosave_status {\n",
" color: rgba(0,0,0,0.5);\n",
"}\n",
"\n",
"#header {\n",
" font-family: lato;\n",
"}\n",
"\n",
"#notebook_name {\n",
" font-weight: 200;\n",
"}\n",
"\n",
"/* \n",
" This is a lazy fix, we *should* fix the \n",
" background for each Bootstrap button type\n",
"*/\n",
"#site * .btn {\n",
" background: #fafafa;\n",
" -webkit-box-shadow: none;\n",
" box-shadow: none;\n",
"}\n",
"```"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Customizing CSS in NBViewer"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In NBViewer (and notebooks) I haven't yet found an elegent way to customize CSS. There is a popular but ugly/dangerous way which involves injecting custom css into each cell. Even the solution I'm posting here I think is poor for the reasons given [here](http://nbviewer.ipython.org/github/Carreau/posts/blob/master/Blog1.ipynb#CSS-In-a-markdown-cell); it's likely to break on future versions and is tedious to insert.\n",
"\n",
"My current working solution was borrwed from [Cam DP](https://twitter.com/Cmrn_DP) and his book *[Bayesian Methods for Hackers](http://nbviewer.ipython.org/github/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers/blob/master/Chapter1_Introduction/Chapter1_Introduction.ipynb)*. The idea is that if there is a `<style>` tag in IPython, the notebook renders it and NBViewer also renders it. I modified Cam's code to use the default IPython profile's `custom.css` file.\n",
"\n",
"Just run the following code snippet in the bottom of any IPython notebook:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from IPython import utils\n",
"from IPython.core.display import HTML\n",
"import os\n",
"def css_styling():\n",
" \"\"\"Load default custom.css file from ipython profile\"\"\"\n",
" base = utils.path.get_ipython_dir()\n",
" styles = \"<style>\\n%s\\n</style>\" % (open(os.path.join(base,'profile_default/static/custom/custom.css'),'r').read())\n",
" return HTML(styles)\n",
"css_styling()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<style>\n",
"@import url('http://fonts.googleapis.com/css?family=Crimson+Text');\n",
"@import url('http://fonts.googleapis.com/css?family=Kameron');\n",
"@import url('http://fonts.googleapis.com/css?family=Lato:200');\n",
"@import url('http://fonts.googleapis.com/css?family=Lato:300');\n",
"@import url('http://fonts.googleapis.com/css?family=Lato:400');\n",
"@import url('http://fonts/googleapis.com/css?family=Source+Code+Pro');\n",
"\n",
"/* Change code font */\n",
".CodeMirror pre {\n",
" font-family: 'Source Code Pro', Consolas, monocco, monospace;\n",
"}\n",
"\n",
"div.input_area {\n",
" border-color: rgba(0,0,0,0.10);\n",
" background: rbga(0,0,0,0.5);\n",
"}\n",
"\n",
"div.text_cell {\n",
" max-width: 105ex; /* instead of 100%, */\n",
"}\n",
"\n",
"div.text_cell_render {\n",
" font-family: \"Crimson Text\";\n",
" font-size: 12pt;\n",
" line-height: 145%; /* added for some line spacing of text. */\n",
" /*width: 105ex; /* instead of 'inherit' for shorter lines */\n",
"}\n",
"\n",
"div.text_cell_render h1,\n",
"div.text_cell_render h2,\n",
"div.text_cell_render h3,\n",
"div.text_cell_render h4,\n",
"div.text_cell_render h5,\n",
"div.text_cell_render h6 {\n",
" font-family: 'Kameron';\n",
" font-weight: 300;\n",
"}\n",
"\n",
"div.text_cell_render h1 {\n",
" font-size: 24pt;\n",
"}\n",
"\n",
"div.text_cell_render h2 {\n",
" font-size: 18pt;\n",
"}\n",
"\n",
"div.text_cell_render h3 {\n",
" font-size: 14pt;\n",
"}\n",
"\n",
".rendered_html pre,\n",
".rendered_html code {\n",
" font-size: medium;\n",
"}\n",
"\n",
".rendered_html ol {\n",
" list-style:decimal; \n",
" margin: 1em 2em;\n",
"}\n",
"\n",
".prompt.input_prompt {\n",
" color: rgba(0,0,0,0.5);\n",
"}\n",
"\n",
".cell.command_mode.selected {\n",
" border-color: rgba(0,0,0,0.1);\n",
"}\n",
"\n",
".cell.edit_mode.selected {\n",
" border-color: rgba(0,0,0,0.15);\n",
" box-shadow: 0px 0px 5px #f0f0f0;\n",
" -webkit-box-shadow: 0px 0px 5px #f0f0f0;\n",
"} \n",
"\n",
"div.output_scroll {\n",
" -webkit-box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);\n",
" box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);\n",
" border-radious: 2px;\n",
"}\n",
"\n",
"#menubar .navbar-inner {\n",
" background: #fff;\n",
" -webkit-box-shadow: none;\n",
" box-shadow: none;\n",
" border-radius: 0;\n",
" border: none;\n",
" font-family: lato;\n",
" font-weight: 400;\n",
"}\n",
"\n",
".navbar-fixed-top .navbar-inner, \n",
".navbar-static-top .navbar-inner {\n",
" box-shadow: none;\n",
" -webkit-box-shadow: none;\n",
" border: none;\n",
"}\n",
"\n",
"div#notebook_panel {\n",
" box-shadow: none;\n",
" -webkit-box-shadow: none;\n",
" border-top: none; \n",
"}\n",
"\n",
"div#notebook {\n",
" border-top: 1px solid rgba(0,0,0,0.15);\n",
"}\n",
"\n",
"#menubar .navbar .navbar-inner,\n",
".toolbar-inner {\n",
" padding-left: 0;\n",
" padding-right: 0;\n",
"}\n",
"\n",
"#checkpoint_status,\n",
"#autosave_status {\n",
" color: rgba(0,0,0,0.5);\n",
"}\n",
"\n",
"#header {\n",
" font-family: lato;\n",
"}\n",
"\n",
"#notebook_name {\n",
" font-weight: 200;\n",
"}\n",
"\n",
"#site * .btn {\n",
" background: #fafafa;\n",
" -webkit-box-shadow: none; \n",
" box-shadow: none;\n",
"}\n",
"\n",
"\n",
"</style>"
],
"metadata": {},
"output_type": "pyout",
"prompt_number": 1,
"text": [
"<IPython.core.display.HTML at 0x102e214d0>"
]
}
],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There is [one curious feature](https://github.com/ipython/nbviewer/blob/master/nbviewer/render.py#L39-L66) in the code for NBVeiwer. This code actually checks the notebook metadata (which is nice and clean) and looks for a css theme (a specified css file, but only in the servers `/static/css/theme/` directory. The unfortunate part of this is that you can only use Cam's theme from *Bayesian Methods* or one other linear algebra notebook theme (`cdp_1.css` or `css_linalg.css` respectively, and you need to remove the `.css` bit when specifying a theme). "
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Theming NBConvert"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Making NBConvert do what you want is one of the trickiest bits about theming IPython. Again, the above code snippet makes NBConvert do exactly what we wanted. You can also specify a `custom.css` file in the directory that you export your notebooks in and that file will automatically overwrite default styles.\n",
"\n",
"Creating an NBConvert theme is a little bit more involved. NBConvert uses Jinja2 for templates. I'm not going to get into creating NBConvert templates right now because I think the default battery is pretty good for most cases. [Minrk](https://twitter.com/minrk) has some great examples of how to create a template [here](https://github.com/ipython/nbconvert-examples). When creating a template you don't always need the config file each time, just the `.tpl` file and specifying the output format.\n",
"\n",
"The command needed to generate from a custom NBConvert is (from the directory containing custom_template):\n",
"\n",
"```bash\n",
"ipython nbconvert --template custom_template.tpl <notebook>\n",
"```\n",
"\n",
"followed by:\n",
"\n",
"```bash\n",
"ln .ipython/profile_default/static/custom/custom.css ./custom.css\n",
"```\n",
"\n",
"This will convert your notebook and create a symbolic link (again, unix-based only) so that your nbconverted notebooks will change when you modify your profile's `custom.css` file."
]
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment