Generate interactive network graph of movie appearances

    Output is an html page, rendered to 'movie_network.html' in the output directory.

def movie_network():
    """Generate interactive network graph of movie appearances

    Output is an html page, rendered to 'movie_network.html' in the output directory.
    """
    # page template
    template = jenv.get_template("movie_network.html")

    # container for template context
    context = dict()

    # a database client/session to run queries in
    cl = client.get_client()
    session = cl.create_session()

    #
    # query data
    #

    # get all Movies
    query = session.query(models.Movie.id,
                          models.Movie.name,
                          models.Movie.url,
                          models.Movie.budget_inflation_adjusted,
                          models.Movie.imdb_rating)
    movies = cl.df_query(query)

    # get all Movie Appearances
    query = session.query(models.MovieAppearance.movie_id,
                          models.MovieAppearance.character_id)
    appearances = cl.df_query(query)

    # get all Characters that have movie appearances
    query = session.query(models.Character.id,
                          models.Character.url,
                          models.Character.name) \
        .filter(models.Character.id.in_([int(i) for i in appearances['character_id'].unique()]))
    characters = cl.df_query(query)

    #
    # transform to network graph
    #

    graph = dict(nodes=[],
                 graph=[],  # this stays empty
                 links=[],
                 directed=False,
                 multigraph=True)

    # containers for lookups from movie/character IDs to node IDs
    movie_node_id = dict()
    character_node_id = dict()

    # normalization for movie node size: 100 = max budget
    movie_size_factor = 100. / movies['budget_inflation_adjusted'].max()

    # nodes for movies
    for _, data in movies.iterrows():
        movie_node_id[data['id']] = len(graph['nodes'])
        # noinspection PyTypeChecker
        graph['nodes'].append(dict(id=data['name'],
                                   size=max(5., data['budget_inflation_adjusted'] * movie_size_factor),
                                   score=data['imdb_rating'] / 10.,
                                   type='square',
                                   url="http://marvel.wikia.com" + data['url']))

    # nodes for characters
    for _, data in characters.iterrows():
        character_node_id[data['id']] = len(graph['nodes'])
        # noinspection PyTypeChecker
        graph['nodes'].append(dict(id=data['name'],
                                   size=10,
                                   type='circle',
                                   url="http://marvel.wikia.com" + data['url']))

    # links: movie appearances
    for _, data in appearances.iterrows():
        # noinspection PyTypeChecker
        graph['links'].append(dict(source=movie_node_id[data['movie_id']],
                                   target=character_node_id[data['character_id']]))

    context['graph'] = json.dumps(graph, indent=4)

    #
    # render template
    #

    out_file = path.join(out_dir, "movie_network.html")
    html_content = template.render(**context)
    with open(out_file, 'w') as f:
        f.write(html_content)

    # done, clean up
    plt.close('all')
    session.close()