# Copyright 2010-2018 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Collection of helpers to visualize cp_model solutions in colab.""" # pylint: disable=g-import-not-at-top import random try: from IPython.display import display from IPython.display import SVG import plotly.figure_factory as ff import plotly.offline as pyo import svgwrite correct_imports = True except ImportError: correct_imports = False def RunFromIPython(): if not correct_imports: return False try: return __IPYTHON__ is not None except NameError: return False def ToDate(v): return '2016-01-01 6:%02i:%02i' % (v / 60, v % 60) class ColorManager(object): """Utility to create colors to use in visualization.""" def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): """Creates an interpolated rgb color between two rgb colors.""" num_intervals = num_steps - 1 dr = (er - sr) / num_intervals dg = (eg - sg) / num_intervals db = (eb - sb) / num_intervals r = sr + dr * step g = sg + dg * step b = sb + db * step return 'rgb(%i, %i, %i)' % (r, g, b) def SeedRandomColor(self, seed=0): random.seed(seed) def RandomColor(self): return 'rgb(%i,%i,%i)' % (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) def DisplayJobshop(starts, durations, machines, name): """Simple function to display a jobshop solution using plotly.""" jobs_count = len(starts) machines_count = len(starts[0]) all_machines = range(0, machines_count) all_jobs = range(0, jobs_count) df = [] for i in all_jobs: for j in all_machines: df.append( dict( Task='Resource%i' % machines[i][j], Start=ToDate(starts[i][j]), Finish=ToDate(starts[i][j] + durations[i][j]), Resource='Job%i' % i)) sorted_df = sorted(df, key=lambda k: k['Task']) pyo.init_notebook_mode() colors = {} cm = ColorManager() cm.SeedRandomColor(0) for i in all_jobs: colors['Job%i' % i] = cm.RandomColor() fig = ff.create_gantt( sorted_df, colors=colors, index_col='Resource', title=name, show_colorbar=False, showgrid_x=True, showgrid_y=True, group_tasks=True) pyo.iplot(fig) class SvgWrapper(object): """Simple SVG wrapper to use in colab.""" def __init__(self, sizex, sizey, scaling=20.0): self.__sizex = sizex self.__sizey = sizey self.__scaling = scaling self.__offset = scaling self.__dwg = svgwrite.Drawing( size=(self.__sizex * self.__scaling + self.__offset, self.__sizey * self.__scaling + self.__offset * 2)) def Display(self): display(SVG(self.__dwg.tostring())) def AddRectangle(self, x, y, dx, dy, fill, stroke='black', label=None): """Draw a rectangle, dx and dy must be >= 0.""" s = self.__scaling o = self.__offset corner = (x * s + o, (self.__sizey - y - dy) * s + o) size = (dx * s - 1, dy * s - 1) self.__dwg.add( self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke)) self.AddText(x + 0.5 * dx, y + 0.5 * dy, label) def AddText(self, x, y, label): text = self.__dwg.text( label, insert=(x * self.__scaling + self.__offset, (self.__sizey - y) * self.__scaling + self.__offset), text_anchor='middle', font_family='sans-serif', font_size='%dpx' % (self.__scaling / 2)) self.__dwg.add(text) def AddXScale(self, step=1): """Add an scale on the x axis.""" o = self.__offset s = self.__scaling y = self.__sizey * s + o / 2.0 + o dy = self.__offset / 4.0 self.__dwg.add( self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke='black')) for i in range(0, int(self.__sizex) + 1, step): self.__dwg.add( self.__dwg.line( (o + i * s, y - dy), (o + i * s, y + dy), stroke='black')) def AddYScale(self, step=1): """Add an scale on the y axis.""" o = self.__offset s = self.__scaling x = o / 2.0 dx = self.__offset / 4.0 self.__dwg.add( self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke='black')) for i in range(0, int(self.__sizey) + 1, step): self.__dwg.add( self.__dwg.line( (x - dx, i * s + o), (x + dx, i * s + o), stroke='black')) def AddTitle(self, title): """Add a title to the drawing.""" text = self.__dwg.text( title, insert=(self.__offset + self.__sizex * self.__scaling / 2.0, self.__offset / 2), text_anchor='middle', font_family='sans-serif', font_size='%dpx' % (self.__scaling / 2)) self.__dwg.add(text)