In [1]:
from sessions import *
from venueordering import *
from pylatex_textboxes import *
from formatting_solver import *

In [2]:
import itertools as it
import pandas as pd
import datetime

In [3]:
df = pd.read_excel("con_data/items_03-28-2018.xlsx", )
df.loc[112,:]


Out[3]:
Title                           Foz Meadows' Reading, and Q and A session
Description             Guest of Honour, Foz Meadows, will give a read...
Short Title                                                           NaN
Format                                                            Keynote
Duration                                                               90
Day                                                                Friday
Time                                                                12:00
Room                                                     East Grand River
Venue                                                   Pan Pacific Perth
Tracks                                                               Main
Parent                                                                NaN
Participants                                        Foz Meadows,PRK . (M)
Other Participants                                                    NaN
Notes for Organizers                                                  NaN
Notes for Speakers                                                    NaN
Name: 112, dtype: object

In [4]:
day2date = {
    "Thursday":datetime.date(2018,3,29),
    "Friday":datetime.date(2018,3,30),
    "Saturday":datetime.date(2018,3,31),
    "Sunday":datetime.date(2018,4,1),
    "Monday":datetime.date(2018,4,2),
}

def load_grenadine_session(prog_item):
    title = prog_item['Title']
    print(title)
    id = prog_item.name
    
    date = day2date[prog_item['Day']]
    start_time = datetime.datetime.strptime(prog_item["Time"], "%H:%M").time()

    start = datetime.datetime.combine(date, start_time)
    end = start + datetime.timedelta(minutes=int(prog_item["Duration"]))
    
    people = prog_item["Participants"]
    
    tags = prog_item["Tracks"].split(",")
    tags.append(prog_item["Format"])
    
    if title=="Lunch" or title=="Dinner":
        tags.append("Break")
            

    venues = [prog_item['Room']]
            
    description = prog_item['Description']
    return session(id, start, end, title, tags, people, venues, description)


def fetch_sessions(path):
    df = pd.read_excel(path,keep_default_na=False) #keep_default_na stops emptystring being converted to nan
    return [load_grenadine_session(df.loc[ii,:]) 
            for ii in range(len(df))
            if len(df.loc[ii,"Day"])>0 #must have Day set
           ]

In [5]:
def all_venues(sessions):
    return set().union(*(ss.venues for ss in sessions))

def replace_room_with_rooms(sessions, old, *news):
    for sess in sessions:
        if old in sess.venues:
            #print(sess.title)
            sess.venues.remove(old)
            sess.venues.update(news)
    
    sessions
    

"Find multiple copies of events for different rooms at the same time, and convert them to single events in spanning rooms"
def bridge_rooms(sessions):
    bridged_sessions = []
    
    sessions.sort(key=lambda x: (x.start, x.end, x.title))
    for _, matchs in it.groupby(sessions, lambda x: (x.start, x.end, x.title)):
        matchs = list(matchs) #GoldPlate: this doesn't have to be done
        head = matchs[0]
        tail = matchs[1:]
        for sess in tail:
            head.venues.update(sess.venues)
        bridged_sessions.append(head)
        
    
    sessions[:]=bridged_sessions[:]
    return sessions

def fill_empty_room_slots(sessions):
    venues = all_venues(sessions)
    for ss in sessions:
        if len(ss.venues)==0:
            print(ss.title)
            ss.venues = venues
    sessions

In [6]:
sessions = fetch_sessions("con_data/items_03-28-2018.xlsx")


Opening Ceremony
Open console gaming
Con-going 101
Swancon movie screenings of the past year: the good, the bad and the unwatchable.
Spotlight on CSI
Classic Who: Season 10. Jon Pertwee  From Three Doctors to The Green Death
Music in Fandom: Making Everything Even Better
Reliving Our Childhoods- The influence of the 70s and 80s on SF
Nuke's SF Trivia Quiz - “Klaatu barada nikto!”
Geek singalong!
Welcome to Swancon
Open console gaming
Believing Impossible Things
Run Through Banner
Mario party
Live action board games!
DIY electronics - building your own future without the IOT
That was the year that was: 1988
How to be the Doctor’s Companion- Practical skills to take into space.
Mad Max Plushies
Captain America vs America (and Australia)
Foz Meadows' Reading, and Q and A session
Plush Toy Wars
Lunch
DS Fun-o-rama
Let's talk about Star Wars
Data is Beautiful: Data visualisation can tell a story
SF movies that engage the brain
Jewelry Making
Australian TV: Early days to Cleverman
Making your cosplay glow
Quiet Time
D20s, halberds and online streaming
Fighting Arcade fun
Beyond the Binary: Alternatives to patriarchy and other binary models of society
Miyazaki: a retrospective
Prancercising
Mammoth Speculations
From Ecocatastrophe to Anthropocene fiction: full circle or new terrain?
Graphic Art in the family room
Never, ever underestimate a library
Kdrama with Candice
How stars work, what happens over a star's lifetime, and why
Authorial Voice and Writing the Other
Australian Landscapes as Characters
Horror Movie: It's the 6.30 News
Dinner
Here be dragons...?
Evolution of Humanity
Remembering LeGuin
Left 4 Dead Marathon
Safe Spaces LARP
Online safety Facebook, cyber-security and identity theft
Worst Movies We Ever Saw
Beard or no beard?
Welcome to Swancon
art show set up
Open console gaming
Paranormal Romance vs Urban Fantasy
Quiet spaces
Young people in publishing
Mining the Mundane
Kit Kat Jenga
Art show
Mario Kart
Live action board games!
Play 'DropMix'!
Laser Maze
NovelPersepctive: a machine learning reading tool
Writing the Romantic
Changing Nature of Fan Communities
Common Mistakes of Early Career Writers, and How Not to Make Them- writing workshop
Writing is a Marathon, not a Sprint
Engaging Your Readers
Book Reading
Technohorror
Book launch: Survival by Rachel Watts
Harry Potter Yoga
Ryan J Griffen Guest of Honour event
Project Runway Swancon
Lunch
Smash Bros
Aurealis Awards Ceremony
Thirteen
Fannish Speed Friend-ating
Aurealis Awards Ceremony
Aurealis Award Ceremony
Post it Art
UNISFA is how old?!
Science fiction, fantasy and animation from the 1950's -1990's with an emphasis on the 1980's
Finish the Drawing
Contemporary SF illustration and storyboarding
Real People, Imaginary Stories
Disney from the kids' seats
Quiet Time
Barb and Emily judge the art show
Strong Beginnings- writing workshop
Pokken tournament
Print on Demand: It's more than just printing.
Geek singalong - Disney edition
How to write academic papers
Indian Speculative Fiction - what's hot
Masquerade set-up
From an image to an ending- writing workshop for all writers
Swancon 2019 launch set-up
Swancon 2019 Launch
Masquerade set-up
Kids Masquerade
Drawing Characters 101
Soul Calibur
Swancon 1: Prisms of Memory
Book Launch: The Time of the Stripes, Amanda Bridgeman
Sample Hague Publishing's 2018 releases
More than glitter
Masquerade
Masquerade
Dinner
Barb and Emily judge the Masquerade
Masquerade Chill-out room
Masquerade chill out space
Halo Madness
Masquerade chill-out room
Filking
Kpop with Candice
Welcome to Swancon
Open Console Gaming
Easter Egg Hunt
Faery
Quiet spaces
Knitting 101
The Cup Shanty
Art show
With a whimper, not a bang
Live Action Quidditch
Retro Morning!!
Kitchen Transformations: Crafting with Drain Cleaner and Germs
What to do until the Messiah arrives?
Science fiction's boneyard
The Science in The Martian movie
AI/Machine learning: a guide
Furniture Parkour
Evolution of Table Top Games
Creating the Cleverman Comics: Emily Smith's and Wolf Bylsma's Guest of Honour event
Lunch
Co-op games
Book Covers: Art or Money?
Fans of Sport Panel
Damian's Diabolical Video Quiz!
Post-Worldcon debrief and gloat
Bush Skills
Ryan Griffen's commentary for Cleverman S2 E3 "Dark Clouds"
RIG InterGalactic (a space combat tabletop game) demo and playtest
African Speculative Fiction - what's hot
Murdoch library: Collection formation, academic transformation, and fannish terrorism?
Fan Fund Auction
Quiet Time
Taking a Videogame Company to the Australian Securities Exchange
Comics -> TV/movies VS TV/movies -> comics
Blur
Advanced Fanfic Analysis
Chocolate Tasting
Orbits don't work that way
Fresh Air
Anime with adult protagonists: a guide
Kate Wilhelm: a retrospective
WASFF Business Meeting
Tea Time
Grant Stone’s Swancon History via T-shirts
Bicentential Monster- Shelley's Frankenstein
art show close down
The Colours Out of Space
Cross-fertilising, from genre to aesthetic
Dinner
WA Awards Ceremony (Tin Ducks)
Can you trust that Dog?
More Halo Madness
The Mystery of Murdoch
National Awards Ceremony (Ditmars)
Murdoch Mysteries
Swancon History Panel
Open console gaming
Desiree’s Music Chill-out Room
How to Write a Song
Live action board games!
Natcon Business Meeting
Steven Universe
Apocalyptic outback Australia
My Little Pony viewing
The UnGame
Transfinite to Mastery: Barb de la Hunty's Guest of Honour event
Fanfic as a gateway to canon
TBC
Lunch
LARP
New Who Season 10
Philosophy of Mind in SF
Random crafting
Swancon Feedback Panel
Tidy up and pack away
Closing Ceremony

In [7]:
# Remove sessions that are not really program items from the book's perspective
nonevent_rooms = set(["Meeting Room 1", 'Meetings on 5 foyer', 'Boardroom foyer'])
sessions = [sess for sess in sessions 
            if sess.venues==set() or sess.venues-nonevent_rooms != set()]

In [8]:
replace_room_with_rooms(sessions, 'Swan Rooms', 'Swan Room, Black', 'Swan Room, White')
bridge_rooms(sessions);
fill_empty_room_slots(sessions)


Lunch
Dinner
Lunch
Dinner
Lunch
Dinner
Lunch

In [9]:
set(tuple(cc.venues) for cc in sessions)


Out[9]:
{('Boardroom (main floor)',),
 ('East Grand River',),
 ('East Grand River', 'West Grand River'),
 ('Meeting Room 3',),
 ('Meeting Room 5',),
 ('Meeting Room 5', 'Meeting Room 7'),
 ('Meeting Room 6',),
 ('Meeting Room 7',),
 ('Mount Newman',),
 ('Pilbara',),
 ('Pilbara',
  'Meeting Room 6',
  'Mount Newman',
  'East Grand River',
  'Boardroom (main floor)',
  'Meeting Room 5',
  'Meeting Room 3',
  'Meeting Room 7',
  'West Grand River'),
 ('West Grand River',)}

In [10]:
venue_order = ['West Grand River', 'East Grand River', 'Mount Newman','Pilbara', 'Boardroom (main floor)',
'Meeting Room 3', 'Meeting Room 5', 'Meeting Room 6', 'Meeting Room 7']

In [11]:
from pylatex.utils import escape_latex, NoEscape
from pylatex.utils import NoEscape
from pagelayout import Multicols
from itertools import groupby
from pylatex.base_classes import Environment

class Minipage(Environment):
    def __init__(self, width):
        Environment.__init__(self,arguments=[width])


def write_descriptions(sessions, doc):
    for day_name, day_session in groupby(sessions, lambda ss: ss.day):
        #with doc.create(Section(day_name,numbering=False)):
            with doc.create(Multicols(2)):
                doc.append(NoEscape("[\section*{%s}]" %day_name))
                for session in day_session:
                    if len(session.description)==0:
                        continue
                    title = session.title
                    if "AdultsOnly" in session.tags:
                        title+=NoEscape(" [ADULT]")
                    with doc.create(Subsection(title,numbering=False)):
                        with doc.create(Description()) as desc:
                            doc.append(Command("setlength",[NoEscape("\itemsep"),"0pt"]))
                            doc.append(Command("setlength",[NoEscape("\parsep"),"0pt"]))
                            doc.append(Command("setlength",[NoEscape("\parskip"),"0pt"]))
                            desc.add_item("When:", session.day+", "+session.start_time+" – "+session.end_time)

                            if len(session.venues)>0:
                                desc.add_item("Where:", ", ".join(session.venues))
                            if len(session.people)>0:
                                desc.add_item("Who:", ", ".join(session.people))
                            #if len(session.tags)>0:
                            #    desc.add_item("Tags:", ", ".join(session.tags))
                        doc.append(session.description)
                doc.append(Command("newpage"))

In [12]:
import pylatex
from pylatex import Document, Section, Subsection, Subsubsection, Table, Package, lists
from pylatex.lists import Description

from pylatex.utils import escape_latex, NoEscape
from itertools import groupby
from pylatex.base_classes.command import Options
from pylatex.utils import escape_latex

def write_venues(doc, tt_solver):
    doc.append(textpos_origin('0.9cm','1cm'))
    doc.append(TextcolorboxStyle('sharp corners','center upper', valign='center',
                                 colframe='blue!50!black',colback='blue!10!white',
                                 boxsep='0pt',top='0mm',bottom='0mm',left='0mm',right='1mm'))
            
    for venue in tt_solver.venues:
        
        venue_words = venue.split()
        if len(venue_words) == 2:
            #Split the string onto two lines if it exactly 2 words
            venue_text = venue_words[0] + '\n' + venue_words[1]
        else:
            venue_text = venue
        
        doc.append(FixedTextbox(venue_text,
                                tt_solver.get_venue_x(venue),
                                '0cm', 
                                '1.5cm',
                                tt_solver.get_venue_width())
                               )

def make_pretty_timetable(doc,sessions, tt_solver):
        
    for date, day_sessions in groupby(sessions, lambda ss: ss.start.date()):   
        day_str = str(DAYS[date.weekday()])
        with doc.create(Subsection(NoEscape(day_str+" \hfill "+day_str+" \hfill "+day_str), numbering=False)):
           
            write_venues(doc,tt_solver)
            doc.append(TextcolorboxStyle('rounded corners', 'center upper', valign='center',
                                 colframe='blue!50!black',colback='white!10!white',
                                 boxsep='1pt',top='0mm',bottom='0mm',left='0mm',right='0mm'))
            
            day_sessions = sorted(day_sessions, key = lambda ss: -len(ss.venues) or -len(tt_solver.venues)-1)
            for session in day_sessions:
                #print("*", session.title)
                colback = tt_solver.get_color(session)
                tcb_options = Options(colback=colback) if colback else None
                doc.append(FixedTextbox(NoEscape('%s \\\\ \\tcbfontsize{0.75} %s -- %s ' % 
                                                     tuple(map(escape_latex, (session.title, session.start_time, session.end_time)))),
                                        tt_solver.get_x(session),
                                        tt_solver.get_y(session), 
                                        tt_solver.get_height(session),
                                        tt_solver.get_width(session),
                                        tcb_options=tcb_options))

            doc.append(Command('newpage'))

In [13]:
doc = Document(documentclass="scrreprt")
margins=['tmargin=0.5cm','bmargin=1.5cm','lmargin=1.5cm','rmargin=1cm',]
doc.packages.append(Package('geometry', options=margins))
doc.packages.append(Package('xcolor', options=["svgnames","dvipsnames"]))
doc.packages.append(Package("microtype"))

#doc.packages.append(Package("draftwatermark"))
doc.append(Command("newgeometry",arguments=",".join(margins)))
#doc.append(Command("SetWatermarkText", "Draft v0.7.0"))
#doc.append(Command("SetWatermarkScale", "0.5"))
#doc.append(Command("SetWatermarkColor", "0.9,0.3,0.3", "rgb"))


#############
tt_solver = timetable_metric_solver(sessions,
                                    hour_len=1.7,
                                    venue_width=2.15,
                                    units='cm',
                                    overlap=0.05,
                                    voffset=1.7,             
                                    venue_order = venue_order,
                                    get_tag_colors = get_tag_colors_mono
                                    )

make_pretty_timetable(doc,sessions, tt_solver)
write_descriptions(sessions,doc)

###############
with open("out/exported.tex", 'w') as fh:
    doc.dump(fh)
#####
    
from IPython.display import FileLink, FileLinks
    
#!lualatex --output-directory=out --interaction=nonstopmode out/exported.tex
FileLinks("./out")


Out[13]:

In [ ]:


In [14]:
sessions[12].people


Out[14]:
['Stephen Dedman', 'Andrew Williams', 'Luke Chandler-Hopkins', 'Chris Creagh']

In [15]:
#ROOMs/DAYS

import pylatex
from pylatex import Document, Section, Subsection, Subsubsection, Table, Package,lists
from pylatex.lists import Description
from pylatex.base_classes import Command


from itertools import groupby

from collections import defaultdict
vds = defaultdict(lambda : defaultdict(list))
for date, day_sessions in groupby(sessions, lambda ss: ss.start.date()):
    for sess in day_sessions:
        for venue in sess.venues:
            vds[venue][date].append(sess)

#################################
doc = Document(documentclass="article")
doc.packages.append(Package('enumitem'))
doc.packages.append(Package('calc'))
doc.append(NoEscape(r"\setlist[description]{leftmargin=!,labelwidth=\widthof{\bfseries 13:00  –  14:00}}"))
for venue in vds.keys():
    doc.append(Command("newpage"))
    doc.append(Command("pagestyle","empty"))
    doc.append(Command("LARGE"))
    doc.append(NoEscape(r"\renewcommand{\familydefault}{\sfdefault}"))
    for date in sorted(vds[venue]):
        day = DAYS[date.weekday()]
        with doc.create(Section(NoEscape("\Huge %s\\\\ %s" % (day,venue)), numbering=False)):
            
            with doc.create(Description()) as sched:
                sesses = sorted(vds[venue][date], key=lambda ss: ss.start)
                for sess in sesses:
                    sched.add_item(NoEscape("%s%s" % (sess.start_time, sess.end_time)), sess.title)
            doc.append(Command("newpage"))
            
                

from IPython.display import FileLink, FileLinks
with open("out/exported_days.tex", 'w') as temp_out:
    doc.dump(temp_out)

#!lualatex --output-directory=out --interaction=nonstopmode exported_days.tex
FileLinks("./out")


Out[15]:

In [ ]:


In [ ]: