ortools.sat.colab.visualization
Collection of helpers to visualize cp_model solutions in colab.
1#!/usr/bin/env python3 2# Copyright 2010-2025 Google LLC 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Collection of helpers to visualize cp_model solutions in colab.""" 16 17# pylint: disable=g-import-not-at-top 18import random 19 20try: 21 from IPython.display import display 22 from IPython.display import SVG 23 import plotly.figure_factory as ff 24 import svgwrite 25 26 correct_imports = True 27except ImportError: 28 correct_imports = False 29 30 31def RunFromIPython(): 32 if not correct_imports: 33 return False 34 try: 35 return __IPYTHON__ is not None 36 except NameError: 37 return False 38 39 40def ToDate(v): 41 return "2016-01-01 6:%02i:%02i" % (v / 60, v % 60) 42 43 44class ColorManager: 45 """Utility to create colors to use in visualization.""" 46 47 def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): 48 """Creates an interpolated rgb color between two rgb colors.""" 49 num_intervals = num_steps - 1 50 dr = (er - sr) / num_intervals 51 dg = (eg - sg) / num_intervals 52 db = (eb - sb) / num_intervals 53 r = sr + dr * step 54 g = sg + dg * step 55 b = sb + db * step 56 return "rgb(%i, %i, %i)" % (r, g, b) 57 58 def SeedRandomColor(self, seed=0): 59 random.seed(seed) 60 61 def RandomColor(self): 62 return "rgb(%i,%i,%i)" % ( 63 random.randint(0, 255), 64 random.randint(0, 255), 65 random.randint(0, 255), 66 ) 67 68 69def DisplayJobshop(starts, durations, machines, name): 70 """Simple function to display a jobshop solution using plotly.""" 71 72 jobs_count = len(starts) 73 machines_count = len(starts[0]) 74 all_machines = range(0, machines_count) 75 all_jobs = range(0, jobs_count) 76 df = [] 77 for i in all_jobs: 78 for j in all_machines: 79 df.append( 80 dict( 81 Task="Resource%i" % machines[i][j], 82 Start=ToDate(starts[i][j]), 83 Finish=ToDate(starts[i][j] + durations[i][j]), 84 Resource="Job%i" % i, 85 ) 86 ) 87 88 sorted_df = sorted(df, key=lambda k: k["Task"]) 89 90 colors = {} 91 cm = ColorManager() 92 cm.SeedRandomColor(0) 93 for i in all_jobs: 94 colors["Job%i" % i] = cm.RandomColor() 95 96 fig = ff.create_gantt( 97 sorted_df, 98 colors=colors, 99 index_col="Resource", 100 title=name, 101 show_colorbar=False, 102 showgrid_x=True, 103 showgrid_y=True, 104 group_tasks=True, 105 ) 106 fig.show() 107 108 109class SvgWrapper: 110 """Simple SVG wrapper to use in colab.""" 111 112 def __init__(self, sizex, sizey, scaling=20.0): 113 self.__sizex = sizex 114 self.__sizey = sizey 115 self.__scaling = scaling 116 self.__offset = scaling 117 self.__dwg = svgwrite.Drawing( 118 size=( 119 self.__sizex * self.__scaling + self.__offset, 120 self.__sizey * self.__scaling + self.__offset * 2, 121 ) 122 ) 123 124 def Display(self): 125 display(SVG(self.__dwg.tostring())) 126 127 def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None): 128 """Draw a rectangle, dx and dy must be >= 0.""" 129 s = self.__scaling 130 o = self.__offset 131 corner = (x * s + o, (self.__sizey - y - dy) * s + o) 132 size = (dx * s - 1, dy * s - 1) 133 self.__dwg.add( 134 self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke) 135 ) 136 self.AddText(x + 0.5 * dx, y + 0.5 * dy, label) 137 138 def AddText(self, x, y, label): 139 text = self.__dwg.text( 140 label, 141 insert=( 142 x * self.__scaling + self.__offset, 143 (self.__sizey - y) * self.__scaling + self.__offset, 144 ), 145 text_anchor="middle", 146 font_family="sans-serif", 147 font_size="%dpx" % (self.__scaling / 2), 148 ) 149 self.__dwg.add(text) 150 151 def AddXScale(self, step=1): 152 """Add an scale on the x axis.""" 153 o = self.__offset 154 s = self.__scaling 155 y = self.__sizey * s + o / 2.0 + o 156 dy = self.__offset / 4.0 157 self.__dwg.add( 158 self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black") 159 ) 160 for i in range(0, int(self.__sizex) + 1, step): 161 self.__dwg.add( 162 self.__dwg.line( 163 (o + i * s, y - dy), (o + i * s, y + dy), stroke="black" 164 ) 165 ) 166 167 def AddYScale(self, step=1): 168 """Add an scale on the y axis.""" 169 o = self.__offset 170 s = self.__scaling 171 x = o / 2.0 172 dx = self.__offset / 4.0 173 self.__dwg.add( 174 self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black") 175 ) 176 for i in range(0, int(self.__sizey) + 1, step): 177 self.__dwg.add( 178 self.__dwg.line( 179 (x - dx, i * s + o), (x + dx, i * s + o), stroke="black" 180 ) 181 ) 182 183 def AddTitle(self, title): 184 """Add a title to the drawing.""" 185 text = self.__dwg.text( 186 title, 187 insert=( 188 self.__offset + self.__sizex * self.__scaling / 2.0, 189 self.__offset / 2, 190 ), 191 text_anchor="middle", 192 font_family="sans-serif", 193 font_size="%dpx" % (self.__scaling / 2), 194 ) 195 self.__dwg.add(text)
def
RunFromIPython():
def
ToDate(v):
class
ColorManager:
45class ColorManager: 46 """Utility to create colors to use in visualization.""" 47 48 def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): 49 """Creates an interpolated rgb color between two rgb colors.""" 50 num_intervals = num_steps - 1 51 dr = (er - sr) / num_intervals 52 dg = (eg - sg) / num_intervals 53 db = (eb - sb) / num_intervals 54 r = sr + dr * step 55 g = sg + dg * step 56 b = sb + db * step 57 return "rgb(%i, %i, %i)" % (r, g, b) 58 59 def SeedRandomColor(self, seed=0): 60 random.seed(seed) 61 62 def RandomColor(self): 63 return "rgb(%i,%i,%i)" % ( 64 random.randint(0, 255), 65 random.randint(0, 255), 66 random.randint(0, 255), 67 )
Utility to create colors to use in visualization.
def
ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step):
48 def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): 49 """Creates an interpolated rgb color between two rgb colors.""" 50 num_intervals = num_steps - 1 51 dr = (er - sr) / num_intervals 52 dg = (eg - sg) / num_intervals 53 db = (eb - sb) / num_intervals 54 r = sr + dr * step 55 g = sg + dg * step 56 b = sb + db * step 57 return "rgb(%i, %i, %i)" % (r, g, b)
Creates an interpolated rgb color between two rgb colors.
def
DisplayJobshop(starts, durations, machines, name):
70def DisplayJobshop(starts, durations, machines, name): 71 """Simple function to display a jobshop solution using plotly.""" 72 73 jobs_count = len(starts) 74 machines_count = len(starts[0]) 75 all_machines = range(0, machines_count) 76 all_jobs = range(0, jobs_count) 77 df = [] 78 for i in all_jobs: 79 for j in all_machines: 80 df.append( 81 dict( 82 Task="Resource%i" % machines[i][j], 83 Start=ToDate(starts[i][j]), 84 Finish=ToDate(starts[i][j] + durations[i][j]), 85 Resource="Job%i" % i, 86 ) 87 ) 88 89 sorted_df = sorted(df, key=lambda k: k["Task"]) 90 91 colors = {} 92 cm = ColorManager() 93 cm.SeedRandomColor(0) 94 for i in all_jobs: 95 colors["Job%i" % i] = cm.RandomColor() 96 97 fig = ff.create_gantt( 98 sorted_df, 99 colors=colors, 100 index_col="Resource", 101 title=name, 102 show_colorbar=False, 103 showgrid_x=True, 104 showgrid_y=True, 105 group_tasks=True, 106 ) 107 fig.show()
Simple function to display a jobshop solution using plotly.
class
SvgWrapper:
110class SvgWrapper: 111 """Simple SVG wrapper to use in colab.""" 112 113 def __init__(self, sizex, sizey, scaling=20.0): 114 self.__sizex = sizex 115 self.__sizey = sizey 116 self.__scaling = scaling 117 self.__offset = scaling 118 self.__dwg = svgwrite.Drawing( 119 size=( 120 self.__sizex * self.__scaling + self.__offset, 121 self.__sizey * self.__scaling + self.__offset * 2, 122 ) 123 ) 124 125 def Display(self): 126 display(SVG(self.__dwg.tostring())) 127 128 def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None): 129 """Draw a rectangle, dx and dy must be >= 0.""" 130 s = self.__scaling 131 o = self.__offset 132 corner = (x * s + o, (self.__sizey - y - dy) * s + o) 133 size = (dx * s - 1, dy * s - 1) 134 self.__dwg.add( 135 self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke) 136 ) 137 self.AddText(x + 0.5 * dx, y + 0.5 * dy, label) 138 139 def AddText(self, x, y, label): 140 text = self.__dwg.text( 141 label, 142 insert=( 143 x * self.__scaling + self.__offset, 144 (self.__sizey - y) * self.__scaling + self.__offset, 145 ), 146 text_anchor="middle", 147 font_family="sans-serif", 148 font_size="%dpx" % (self.__scaling / 2), 149 ) 150 self.__dwg.add(text) 151 152 def AddXScale(self, step=1): 153 """Add an scale on the x axis.""" 154 o = self.__offset 155 s = self.__scaling 156 y = self.__sizey * s + o / 2.0 + o 157 dy = self.__offset / 4.0 158 self.__dwg.add( 159 self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black") 160 ) 161 for i in range(0, int(self.__sizex) + 1, step): 162 self.__dwg.add( 163 self.__dwg.line( 164 (o + i * s, y - dy), (o + i * s, y + dy), stroke="black" 165 ) 166 ) 167 168 def AddYScale(self, step=1): 169 """Add an scale on the y axis.""" 170 o = self.__offset 171 s = self.__scaling 172 x = o / 2.0 173 dx = self.__offset / 4.0 174 self.__dwg.add( 175 self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black") 176 ) 177 for i in range(0, int(self.__sizey) + 1, step): 178 self.__dwg.add( 179 self.__dwg.line( 180 (x - dx, i * s + o), (x + dx, i * s + o), stroke="black" 181 ) 182 ) 183 184 def AddTitle(self, title): 185 """Add a title to the drawing.""" 186 text = self.__dwg.text( 187 title, 188 insert=( 189 self.__offset + self.__sizex * self.__scaling / 2.0, 190 self.__offset / 2, 191 ), 192 text_anchor="middle", 193 font_family="sans-serif", 194 font_size="%dpx" % (self.__scaling / 2), 195 ) 196 self.__dwg.add(text)
Simple SVG wrapper to use in colab.
SvgWrapper(sizex, sizey, scaling=20.0)
113 def __init__(self, sizex, sizey, scaling=20.0): 114 self.__sizex = sizex 115 self.__sizey = sizey 116 self.__scaling = scaling 117 self.__offset = scaling 118 self.__dwg = svgwrite.Drawing( 119 size=( 120 self.__sizex * self.__scaling + self.__offset, 121 self.__sizey * self.__scaling + self.__offset * 2, 122 ) 123 )
def
AddRectangle(self, x, y, dx, dy, fill, stroke='black', label=None):
128 def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None): 129 """Draw a rectangle, dx and dy must be >= 0.""" 130 s = self.__scaling 131 o = self.__offset 132 corner = (x * s + o, (self.__sizey - y - dy) * s + o) 133 size = (dx * s - 1, dy * s - 1) 134 self.__dwg.add( 135 self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke) 136 ) 137 self.AddText(x + 0.5 * dx, y + 0.5 * dy, label)
Draw a rectangle, dx and dy must be >= 0.
def
AddText(self, x, y, label):
139 def AddText(self, x, y, label): 140 text = self.__dwg.text( 141 label, 142 insert=( 143 x * self.__scaling + self.__offset, 144 (self.__sizey - y) * self.__scaling + self.__offset, 145 ), 146 text_anchor="middle", 147 font_family="sans-serif", 148 font_size="%dpx" % (self.__scaling / 2), 149 ) 150 self.__dwg.add(text)
def
AddXScale(self, step=1):
152 def AddXScale(self, step=1): 153 """Add an scale on the x axis.""" 154 o = self.__offset 155 s = self.__scaling 156 y = self.__sizey * s + o / 2.0 + o 157 dy = self.__offset / 4.0 158 self.__dwg.add( 159 self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black") 160 ) 161 for i in range(0, int(self.__sizex) + 1, step): 162 self.__dwg.add( 163 self.__dwg.line( 164 (o + i * s, y - dy), (o + i * s, y + dy), stroke="black" 165 ) 166 )
Add an scale on the x axis.
def
AddYScale(self, step=1):
168 def AddYScale(self, step=1): 169 """Add an scale on the y axis.""" 170 o = self.__offset 171 s = self.__scaling 172 x = o / 2.0 173 dx = self.__offset / 4.0 174 self.__dwg.add( 175 self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black") 176 ) 177 for i in range(0, int(self.__sizey) + 1, step): 178 self.__dwg.add( 179 self.__dwg.line( 180 (x - dx, i * s + o), (x + dx, i * s + o), stroke="black" 181 ) 182 )
Add an scale on the y axis.
def
AddTitle(self, title):
184 def AddTitle(self, title): 185 """Add a title to the drawing.""" 186 text = self.__dwg.text( 187 title, 188 insert=( 189 self.__offset + self.__sizex * self.__scaling / 2.0, 190 self.__offset / 2, 191 ), 192 text_anchor="middle", 193 font_family="sans-serif", 194 font_size="%dpx" % (self.__scaling / 2), 195 ) 196 self.__dwg.add(text)
Add a title to the drawing.