Ejemplo n.º 1
0
 def __xml__(self):
     from xmle import Elem, Show
     x = Elem('div')
     t = x.elem('table', style="margin-top:1px;")
     if len(self):
         tr = t.elem('tr')
         tr.elem('th', text="key")
         tr.elem('th', text='value', style='text-align:left;')
         for k, v in self.items():
             tr = t.elem('tr')
             tr.elem('td', text=str(k))
             try:
                 if isinstance(v, str) and '\n' not in v and v[:1] != "#":
                     raise ValueError()
                 v_ = Show(v)
             except (AttributeError, ValueError):
                 plaintext = pprint.pformat(v)
                 if isinstance(v, datetime.timedelta):
                     plaintext = str(v)
                 if "\n" in plaintext:
                     tr.elem('td',
                             style='text-align:left;').elem('pre',
                                                            text=plaintext)
                 else:
                     tr.elem('td', style='text-align:left;', text=plaintext)
             else:
                 tr.elem('td', style='text-align:left;') << v_
     else:
         tr = t.elem('tr')
         tr.elem('td', text="<empty>")
     return x
Ejemplo n.º 2
0
 def __xml__(self):
     z = Elem('div')
     table = z.put('table', {'class': 'dataframe larch_dataframe'})
     thead = table.put('thead')
     for i in range(self.index.nlevels):
         thead.put('th', text=self.index.names[i])
     for c in self.columns:
         thead.put('th', text=str(c))
     tbody = table.put('tbody')
     for i in self.index:
         row = tbody.put('tr')
         if self.index.nlevels == 1:
             row.put('th', text=str(i))
         else:
             for ii in i:
                 row.put('th', text=str(ii))
         for c in self.columns:
             cell = self.loc[i, c]
             try:
                 cell_content = cell.__xml__()
             except AttributeError:
                 if isinstance(cell, float):
                     row.put('td', text="{0:.6g}".format(cell))
                 else:
                     row.put('td', text=str(cell))
             else:
                 row.put('td') << cell_content
     return z
Ejemplo n.º 3
0
	def __xml__(self):
		from xmle import Elem, Show
		x = Elem('div')
		t = x.elem('table', style="margin-top:1px;")
		if len(self):
			tr = t.elem('tr')
			tr.elem('th', text="key")
			tr.elem('th', text='value', style='text-align:left;')
			for k,v in self.items():
				tr = t.elem('tr')
				tr.elem('td', text=str(k))
				try:
					if isinstance(v, str) and '\n' not in v and v[:1] != "#":
						raise ValueError()
					v_ = Show(v)
				except (AttributeError, ValueError):
					plaintext = pprint.pformat(v)
					if isinstance(v, datetime.timedelta):
						plaintext = str(v)
					if "\n" in plaintext:
						tr.elem('td', style='text-align:left;').elem('pre', text=plaintext)
					else:
						tr.elem('td', style='text-align:left;', text=plaintext)
				else:
					tr.elem('td', style='text-align:left;') << v_
		else:
			tr = t.elem('tr')
			tr.elem('td', text="<empty>")
		return x
Ejemplo n.º 4
0
	def __xml__(self):
		z = Elem('div')
		table = z.put('table', {'class':'dataframe larch_dataframe'})
		thead = table.put('thead')
		for i in range(self.index.nlevels):
			thead.put('th', text=self.index.names[i])
		for c in self.columns:
			thead.put('th', text=str(c))
		tbody = table.put('tbody')
		for i in self.index:
			row = tbody.put('tr')
			if self.index.nlevels==1:
				row.put('th', text=str(i))
			else:
				for ii in i:
					row.put('th', text=str(ii))
			for c in self.columns:
				cell = self.loc[i, c]
				try:
					cell_content = cell.__xml__()
				except AttributeError:
					if isinstance(cell, float):
						row.put('td', text="{0:.6g}".format(cell))
					else:
						row.put('td', text=str(cell))
				else:
					row.put('td') << cell_content
		return z
Ejemplo n.º 5
0
def logo_in_signature():
    from .images import favicon
    from .. import __version__ as version

    sig = Elem('div')
    sig << Elem(
        'img', {
            'width': "14",
            'height': "14",
            'src': "data:image/png;base64,{}".format(favicon),
            'style': 'position:relative;top:2px;'
        })
    sig << Elem('span', {'class': 'larch_name_signature'},
                text=" Larch {}".format(version))
    return sig
Ejemplo n.º 6
0
def diff_images(imagefile1, imagefile2, return_blocked1=False, tolerance=100):
	'''

	Parameters
	----------
	imagefile1, imagefile2 : images
		Two images to compare.  They should be the same size.  Give by filename or with
		raw data.
	return_blocked1 : bool, default False
		If true, also return an image `Elem` based on imagefile1 showing where the mismatch blocks are.
	tolerance : int
		This can be used as the sensitivity factor, the larger it is the less sensitive the comparison

	Returns
	-------
	int
		The number of mismatched blocks
	Elem
		optional, see return_blocked1
	'''
	screenshot_staging = Image.open(imagefile1)
	screenshot_production = Image.open(imagefile2)
	columns = 60
	rows = 80
	screen_width, screen_height = screenshot_staging.size

	block_width = ((screen_width - 1) // columns) + 1  # this is just a division ceiling
	block_height = ((screen_height - 1) // rows) + 1

	mismatch_blocks = 0

	if return_blocked1:
		degenerate = Image.new(screenshot_staging.mode, screenshot_staging.size, '#FFFFFF')

		if 'A' in screenshot_staging.getbands():
			degenerate.putalpha(screenshot_staging.getchannel('A'))

		screenshot_staging_1 = Image.blend(degenerate, screenshot_staging, 0.25)



	for y in range(0, screen_height, block_height + 1):
		for x in range(0, screen_width, block_width + 1):
			region_staging = _process_region(screenshot_staging, x, y, block_width, block_height, tolerance=tolerance)
			region_production = _process_region(screenshot_production, x, y, block_width, block_height, tolerance=tolerance)

			if region_staging is not None and region_production is not None and region_production != region_staging:
				mismatch_blocks += 1
				if return_blocked1:
					draw = ImageDraw.Draw(screenshot_staging_1)
					draw.rectangle((x, y, x + block_width, y + block_height), outline="red")

	if return_blocked1:
		buffered = BytesIO()
		screenshot_staging_1.save(buffered, format="PNG")
		from xmle import Elem
		blocked1 = Elem.from_png_raw(buffered.getvalue())
		return mismatch_blocks, blocked1

	return mismatch_blocks
Ejemplo n.º 7
0
def plot_as_svg_xhtml(
    fig,
    classname='figure',
    headerlevel=2,
    header=None,
    anchor=1,
    transparent=True,
    tooltip=None,
    bbox_extra_artists=None,
    pad_inches=0.1,
    **format,
):
    existing_format_keys = list(format.keys())
    for key in existing_format_keys:
        if key.upper() != key: format[key.upper()] = format[key]
    if 'GRAPHWIDTH' not in format and 'GRAPHHEIGHT' in format:
        format['GRAPHWIDTH'] = format['GRAPHHEIGHT']
    if 'GRAPHWIDTH' in format and 'GRAPHHEIGHT' not in format:
        format['GRAPHHEIGHT'] = format['GRAPHWIDTH'] * .67
    import xml.etree.ElementTree as ET
    ET.register_namespace("", "http://www.w3.org/2000/svg")
    ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
    imgbuffer = BytesIO()
    fig.savefig(
        imgbuffer,
        dpi=None,
        facecolor="none",
        edgecolor='w',
        orientation='portrait',
        papertype=None,
        format='svg',
        transparent=transparent,
        bbox_inches="tight",
        pad_inches=pad_inches,
        bbox_extra_artists=bbox_extra_artists,
    )
    x = Elem("div", {'class': classname})
    if header:
        x.hn(headerlevel, header, anchor=anchor)
    x << ET.fromstring(imgbuffer.getvalue().decode())
    if tooltip is not None:
        x[0][1].insert(0, Elem("title", text=tooltip))
    return x
Ejemplo n.º 8
0
def floating_table_head():
    floatThead = Elem(
        tag="script",
        attrib={
            'src':
            "https://cdnjs.cloudflare.com/ajax/libs/floatthead/2.0.3/jquery.floatThead.min.js",
        })
    floatTheadA = Elem(tag="script",
                       text="""
	$( document ).ready(function() {
		var $table = $('table.floatinghead');
		$table.floatThead({ position: 'absolute' });
		var $tabledf = $('table.dataframe');
		$tabledf.floatThead({ position: 'absolute' });
	});
	$(window).on("hashchange", function () {
		window.scrollTo(window.scrollX, window.scrollY - 50);
	});
	""")
    return floatThead, floatTheadA
Ejemplo n.º 9
0
	def __xml__(self):
		from xmle import Elem
		x = Elem('div')
		t = x.elem('table', style="margin-top:1px;")
		if len(self):
			tr = t.elem('tr')
			tr.elem('th', text="key")
			tr.elem('th', text='value', style='text-align:left;')
			for k,v in self.items():
				tr = t.elem('tr')
				tr.elem('td', text=str(k))
				try:
					v_ = v.__xml__()
				except AttributeError:
					tr.elem('td', text=str(v), style='text-align:left;')
				else:
					tr.elem('td') << v_
		else:
			tr = t.elem('tr')
			tr.elem('td', text="<empty>")
		return x
Ejemplo n.º 10
0
 def __xml__(self):
     from xmle import Elem
     x = Elem('div')
     t = x.elem('table', style="margin-top:1px;")
     if len(self):
         tr = t.elem('tr')
         tr.elem('th', text="key")
         tr.elem('th', text='value', style='text-align:left;')
         for k, v in self.items():
             tr = t.elem('tr')
             tr.elem('td', text=str(k))
             try:
                 v_ = v.__xml__()
             except AttributeError:
                 tr.elem('td', text=str(v), style='text-align:left;')
             else:
                 tr.elem('td') << v_
     else:
         tr = t.elem('tr')
         tr.elem('td', text="<empty>")
     return x
Ejemplo n.º 11
0
def plot_as_svg_xhtml(pyplot, classname='figure', headerlevel=2, header=None, anchor=1, transparent=True, tooltip=None, **format):
	existing_format_keys = list(format.keys())
	for key in existing_format_keys:
		if key.upper()!=key: format[key.upper()] = format[key]
	if 'GRAPHWIDTH' not in format and 'GRAPHHEIGHT' in format: format['GRAPHWIDTH'] = format['GRAPHHEIGHT']
	if 'GRAPHWIDTH' in format and 'GRAPHHEIGHT' not in format: format['GRAPHHEIGHT'] = format['GRAPHWIDTH']*.67
	import xml.etree.ElementTree as ET
	ET.register_namespace("","http://www.w3.org/2000/svg")
	ET.register_namespace("xlink","http://www.w3.org/1999/xlink")
	imgbuffer = BytesIO()
	pyplot.savefig(imgbuffer, dpi=None, facecolor='w', edgecolor='w',
					orientation='portrait', papertype=None, format='svg',
					transparent=transparent, bbox_inches=None, pad_inches=0.1,
					frameon=None)
	x = Elem("div", {'class':classname})
	if header:
		x.hn(headerlevel, header, anchor=anchor)
	x << ET.fromstring(imgbuffer.getvalue().decode())
	if tooltip is not None:
		x[0][1].insert(0, Elem("title", text=tooltip))
	return x
Ejemplo n.º 12
0
    def _repr_html_(self):
        from xmle import Elem
        xsign = Elem("div", {'class': 'larch_head_tag'})
        from .images import favicon
        p = xsign.elem('p', {'style': 'float:left;margin-top:6px'})
        p.elem('img', {
            'width': "32",
            'height': "32",
            'src': "data:image/png;base64,{}".format(favicon),
            'style': 'float:left;position:relative;top:-3px;padding-right:0.2em;'
        }, tail=f" {self.appname} ")
        p.elem('span', {'class': 'larch_head_tag_ver'}, text=f" {self.version} ")
        p.elem('span', {'class': 'larch_head_tag_pth'}, text=f" {self.path} ")
        from .images import camsyslogo_element
        xsign << camsyslogo_element
        if 'larch' in sys.modules:
            from .images import georgiatechlogo_element
            xsign << georgiatechlogo_element

        if self.extra:
            v = '\n│'.join(sys.version.split('\n'))
            xsign.elem('br')
            xinfo = xsign.elem('div', {'class': 'larch_head_tag_more', 'style':'margin-top:10px; padding:7px'}, text=f'Python {v}')
            xinfo.elem('br', tail=f"EXE - {sys.executable}")
            xinfo.elem('br', tail=f"CWD - {os.getcwd()}")
            xinfo.elem('br', tail=f"PATH - ")
            ul = xinfo.elem('ul', {'style': 'margin-top:0; margin-bottom:0;'})
            for p in sys.path:
                ul.elem('li', text=p)

        from ..util.styles import _default_css_jupyter, _tooltipped_style_css
        style_prefix = "<style>{}\n\n{}</style>\n".format(_default_css_jupyter, _tooltipped_style_css)

        return style_prefix+xsign.tostring()
Ejemplo n.º 13
0
    def _repr_html_(self):
        from ..util.styles import _default_css_jupyter, _tooltipped_style_css
        style_prefix = "<style>{}\n\n{}</style>\n".format(
            _default_css_jupyter, _tooltipped_style_css)
        from xmle import Elem
        xsign = Elem("div", {'class': 'larch_head_tag'})
        from .images import favicon
        p = xsign.elem('p', {'style': 'float:left;margin-top:6px'})
        p.elem('img', {
            'width':
            "32",
            'height':
            "32",
            'src':
            "data:image/png;base64,{}".format(favicon),
            'style':
            'float:left;position:relative;top:-3px;padding-right:0.2em;'
        },
               tail=f" {self.appname} ")
        p.elem('span', {'class': 'larch_head_tag_ver'},
               text=f" {self.version} ")
        if not self.minimal:
            p.elem('span', {'class': 'larch_head_tag_pth'},
                   text=f" {self.path} ")
            from .images import camsyslogo_element
            xsign << camsyslogo_element
            if 'larch' in sys.modules:
                from .images import georgiatechlogo_element
                xsign << georgiatechlogo_element

            if self.extra:
                v = '\n│'.join(sys.version.split('\n'))
                xsign.elem('br')
                xinfo = xsign.elem('div', {
                    'class': 'larch_head_tag_more',
                    'style': 'margin-top:10px; padding:7px'
                },
                                   text=f'Python {v}')
                xinfo.elem('br', tail=f"EXE - {sys.executable}")
                xinfo.elem('br', tail=f"CWD - {os.getcwd()}")
                xinfo.elem('br', tail=f"PATH - ")
                ul = xinfo.elem('ul',
                                {'style': 'margin-top:0; margin-bottom:0;'})
                for p in sys.path:
                    ul.elem('li', text=p)

        return style_prefix + xsign.tostring()
Ejemplo n.º 14
0
eye = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAABgUExURUxpcQCj2wCPxQCa1QCZ1ACd2QCMwgCDuQCP0wCs3wBvoACOxwCGvACOxwCPxgCY0QCPzwCTzACTywCj1QCa2ACU1gCe2QCl3ACi2wCp3QCj5ACY4ACy6ACs6AC37ACt5OoTzCwAAAAUdFJOUwD7XPrR+0UE/f0WnCxwz6rquoflF5GMKAAAApRJREFUWMPtVtmCoyAQFCGKt0aNkUP+/y+nOQRijk12Z99SM5kM3V0F3ZxJ8sUXX/x35B5/RX7dfoddZum5ApzTrPxIQ8eVaUVwsTkUmFRp6Vzv0LNuFJtQhYeC5thlb0iAP2+I2iKyF9kUafLXEpBl3oybKIrVcMS2Cf0xjVW3R5B4XgtwpCOMfLV06BHqB5WEEVmJFXIZ06eDyJN6UALCDJTq6t1Td0o5cyHUUD9UAFuDxbpDjFlYSVDWMXLh5sEg8qQcRIFcDJJjaSqSJ+6rHKV3FmIojwp5kvViXXYozQ8x+t9yVN69ij47KjRXhZYgkFl+2rVtl1qFLAgsSF2b2/QruQQ+kp2h1K2SANXWptnJKGSRlS8EfA0SXSMstU67xhIZSGzbSxyD5OAUQLy94SNFjIfsViSJiSPqJky2Jk3DP100rub3guRZd5hKdHFAMtWWs7G4qMvltCs4/nUPP9nwzskaS2clveXqFYDPvNmFMzNHJNhPjJiZvo/UYyD/IkD0EAjDU4wTsykEM2Y2BXa6CcSWf6eAmS1iLGCLeIwj+zTcKmDW2mncrZjZaWwPYSTfF0IyMDxP7meGj1tIDBswt5Csc7JhmA3xUqzYvEP7KruUCWOcM0bsUq6glwBWJfFuSxo+Rd7ZbyZC/GaK/RM/nAl6O3Ma/P39du5DD5Tfb2d9oLCZ+oj2eKC0Xp/O7P5AsWlQTulMqf6j+4iPND0+64Gg5uG5qus88Jk6jYlW4VCtoOkcMx/qZye7rlofJDht7bHe6pHt9D59cbeYi8VKGHCuc+Z8bwP95cXirzYeNAJA6I9XW7hc+4MGsPu3Ltf4etcsi5l+cL3HD4zGPjCaDx8Yv/DE+Y1H1hdffPEhfgDKU1gnLBbrLAAAAABJRU5ErkJggg=='

favicon_raw = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x03\x00PLTE\x00\x00\x00\r(\x02\x0e)\x02\x10,\x02\x00\x00\x00\x00\x00\x00\x0c\x1a\x08\x7f\x7f\x7f\x00\x00\x00\x00\x00\x00SSS\x00\x00\x00\x02\x02\x02\x7f\x7f\x7f\x162\n\x02\x03\x02\x177\x0b,_\x16\x00\x00\x00\x00\x04\x00,`\x16\r\x1a\x08\x7f\x7f\x7f\r&\x02\x7f\x7f\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x164\n\x00\x00\x00\x174\n\x00\x00\x00\x178\x0b\x00\x00\x00\x176\n@@@)\\\x14+^\x16,b\x17ccc???\x0b\x15\x05\x0c\x17\x06\x12\x12\x12\x0c#\x02\r&\x02\x03\r\x03\x12#\n\x19\x19\x19""\x17\x00\x00\x00\x12*\t\x1dQ\t\x0e\x0e\x0e\x03\x03\x03\x02\x06\x01\x03\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\'X\x13\x0b\x1b\x05"R\x0e\x0f\x0f\x0f\x162\n\x00\x00\x00\x10/\x03\x00\x00\x00\x07\x10\x04\x00\x00\x00\x00\x00\x00\x0f#\x07"S\x0e\x1a;\x0b\x00\x00\x00\x1a<\x0bU\x7fU(Y\x13\x08\x17\x03\x00\x00\x00\x08\x16\x03+]\x16\x00\x00\x00?\x81#\x00\x00\x00\x00\x00\x00\x08\x11\x03\x00\x00\x00D\x9b I\xa6"C\x99 I\xa7"J\xa9#J\xa8"G\xa1!F\xa0!D\x9a G\xa0!G\xa2!F\x9f E\x9d H\xa3!K\xab#H\xa4!M\xaf$\x01\x02\x00P\xb6%K\xac#B\x97\x1f=\x8b\x1c@\x94\x1cT\xc0\'L\xae$E\x9e X\xc8)I\xa5"J\xaa#L\xad#C\x97\x1fA\x96\x1dS\xbd\'O\xb5%?\x93\x1bK\xaa#L\xac#M\xb0#\x02\x04\x01\x03\x07\x01@\x91\x1eD\x9c\x1e=\x91\x1bD\x9e\x1f\x12*\x08D\x9c V\xc5(A\x94\x1e\x14-\t\x08\x13\x04Z\xcf*\r\x1e\x06A\x98\x1d@\x97\x1cF\x9e!>\x8d\x1cB\x99\x1e=\x8e\x1c:\x89\x1aC\x9b\x1e,f\x15C\x9a\x1f\x15/\tN\xb3$\x02\x05\x01T\xbf\'P\xb7%;\x87\x1bM\xb1$1p\x16R\xb6)3u\x18+c\x14S\xb8)B\x96\x1f$R\x11<\x8a\x1c:\x86\x1b\x162\n\x07\x10\x03W\xc6)?\x90\x1eY\xcb)\\\xd1+A\x95\x1fU\xc3(^\xd8,K\xa6&;\x8b\x1aG\x9d$M\xac\'?\x8f\x1dJ\xa9"O\xb0(8\x82\x1aR\xbc&*^\x13M\xb1%:\x83\x1a2r\x17>\x90\x1c5|\x185z\x19H\xa8!Q\xb8&M\xae$:\x85\x1bK\xac$W\xc7)\x0f#\x07Q\xb3(E\xa1\x1f\x04\x08\x02O\xb4%\x10$\x07R\xbb&\x1cA\rS\xbe\'\'Y\x12\x163\n\x04\x0b\x02\x0b\x1a\x05@\x93\x1d\r\x1d\x06U\xc2(\\\xd2+\'W\x12]\xd5,$T\x11<\x8c\x1a@\x91 A\x95\x1fJ\xa3%L\xa9\'K\xa5&E\x9b!Q\xa9-E\x9d!@\x93\x1fR\xbc\'-g\x15M\xad#6\x7f\x18O\xae\'D\xa0\x1fP\xb1(S\xb7)0m\x16\x19:\x0c-i\x15F\xa3 *`\x13J\xad"R\xb5(\\\xd3+b\xde-Q\xb9&P\xb3%1q\x17/k\x16X\xc7)^\xd5,\x186\x0b\x0f!\x07#Q\x11\x11\'\x08 I\x0f\n\x17\x05}\xb8;O\x00\x00\x00WtRNS\x00\xbf\xbf\xbf#\x7f\xb9\x04\x1d\x1b\x02!\x16\x06\xf1\x81\xf1\xf5\x07\x19\xf5\xb9\x10\xbe\rs}1\xf1i\xf1\'\xf0\x86\xf2\x06\xf5\xf5\xf5\t\x04\xb4\xb9\x0e\xb4\xbfM\x96\n\x16]\xec\xf5\x12\xa0\xc7\x9d.ZT\xf3\xd9\xf9\x10\xd8\xf6\xcd\xd4\xfdn|\xf9\xf1\xefz\xef\x06\xf4\x985\xd9\xf5d\xfdK\x92\x9e\x83\x12o\x03\x00\x00\x03$IDAT8\xcbc`\xa0\x02`\xe7\xc5%\xc3\x05\x96\xe1b0\xd4\xd7\x15gdd\x02\x02f0\x00\xb1\x98\x18\x19\xc5u\xf4\x18\x94A\xf2,&S7\xde[\xbf\xe9\xf9\xd6c\x0b\xe7\xbd\xfa\x08$\x16~|\xb9u\xd3\xdd\xf5w6N\xf2cPg\x90`pz\xb0\xb7\x7f\xd2\xc4=y\xe5\xe5y\x8d\x8dI\xfb\x1b\x9b\xcb\x81\xa0j\xcf\x86\r\xbb\x9a\x82C4\x18x\x85<\xaa\xfa#\xee\xc7\xa7G\xc6\x94\xe6\xa7\x9d\x9e}\xad"\xb6\xa22&\xb68\xa9/!*\xf7f\x98\x10\x03;\x8fo\xc2\xc4\x88\xc2\xc4\xb8\x98\x9a\x98\xb7)E\xe7:O~\xf9\x96\xfb5\xb681qj\xce\xcaK\x9a<\x0c\xec\xc2*S\x1b\n\xcb\xe2\xb2\x13#\xb2\x8e.o\xfb\xde\xb9\xfa\\\xf8\x929kJ##\x12ZoUiq2\xb0s\xaaNn/\xfc\x1a\x7fz\xf1\xa9\xb6\xac\xcb-\x8b/.\x08_\xbazngm\xe2\xf4\xd4\x885yl\x1c@\x05\x82\xcd\xed\xd1\xef\x97\xac:s\xaa\xed\xe4\x95\x8e\xf0\xfa\xf0\x86\xb5\xf9\xf3\xeb\x0b\xa6\xef\xc8\x8d|\xd4\x0bQp\xbc!\xfd|x\xf6\x8d\xee\xae\xac\x15k\xbb\xc3W^\x9fsu\xf7\x89\xedo\xc2g\xc7\xaf\xe9\x01+PkN\xa99\xfb\xeb\xe2\xb4\xbfG\xf2\xd7v\x9d\x0f\x9f\xb5\xeal\xd7\xfc\x0bg\xea\x92\xb3"\x13 &\x88\xbc[\xb2o\xef\xa1\xdf\xe1\x1d\xab\xf2W\\\xef\x08\xff\xfa\xf3\xc0\xba\xd7\xe1u\x0b\x96\xcc_\x1a\xd5#\nR\x10\xf8aq\xf8\x89\xf0\xe5\x17\xc2\xaf]\x9d\xb9nYx\xc5\x8fO\xfd32\x97\xcf\xa8\x0b\xef\x8e\xea\x05+P;^\x16\x91\x9c\x14\x9d\xdb\x19~\xa3kuG\xf8\xc1\xcf\xdb\xea\xc3\xa7d\xc7\xa7G\xa5@M\x10yQV\x1cY\xb0\xf3J\xeb\xff\xf0\x7f@\x13\xdav\x85g\xee^1}\xce\x96\xe2\xb8(\x88#E\x9a\xcb\x92\xd2c3\x0e\xe7\'u\x87\xffi\t_0#s_Q\xe9\xce\xea\xe8\xb8\xe4\x1c\xa87\x1b\x17%\xa5\xc6d\x9f\xcc\xc8/\xb9\x1c\x9e\x19\x9eY\xb7\xad(#\xab \r\xa8`\x114\xa0&\xb7\'\'FVgW\xd6\x9e-=\x1a\x9eY\xbf\xf4\x08H>6\xb5/\xa5\xbd\n\xac@uBCa_jtZvVu\xd1\xdc\xf0\xf0eE5\xb3\njb\x8a\xe3Z#n\x81\x15\x08\xabL\x988\x15\xe8\x8d\xc8\xd8\xd2\x92\x8a)\xe1\xe1\x1d\x07j\x0e\xc6D\xa7\'\xa6D\xe5\xacl\xd2\xe2`\xf0\xe1\xb1\xb9\xd4\x9f\x10\x11\x9f\x1a\x19\x9b\x11=\xf3Ix\xf8\xb4\xc71\x19%\xb1\xe9q\x85Qe\xfd759\x18\xd4\x85\x1c\x1e\x1eZ\xb9\xf9\xd1\xa2\xa8\x84\xf8\x84\xa7;\xc2\xc3[\xaaS\xe2S\x12r\xd6\xac\xdc<;gn(7\x03\x83\xbd\xac]\xde\x84\xe4\xb8\xc8\xd8\x92\xda-`\x13\xb6\xa7\xd5\xa6\x95D\xa7\xc7\xa7\xe4<\x0b\x08\xe2a`P\xb6\xe4\xb7\x95\xe3\x93\x91\x93\x13PP\xf2v\x0f\x0fw\xf1\x14P\x10\x10\x90\x93\xe1\xe336\xe7\xf7\xd7\x00\xa6{\x03ikI)VVV~/EY\xe7\xf0pGWE7V\x10\x90\x924\x92\x16\x82\xe4\x0fS\x0en\x16 \xe0\x96\xe7\xb12\xb3\xe0\x17\x96\x07\xf3X\xb89\xb4\xb9H\xcf\x86\xbcbb\x12\x0cT\x06\x00\xd9\xdcJ.\xbbbp\x06\x00\x00\x00\x00IEND\xaeB`\x82'

georgiatechlogo = "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"

camsyslogo = "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"

from xmle import Elem

camsyslogo_white = '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'

georgiatechlogo_element = Elem(
    'img', {
        'src':
        "data:image/png;base64,{}".format(georgiatechlogo),
        'style':
        'float:right;max-height:40px;margin-top:0;margin-right:20px;margin-left:20px'
    })

camsyslogo_element = Elem(
    'img', {
        'src': "data:image/png;base64,{}".format(camsyslogo),
        'style': 'float:right;max-height:44px;margin-top:0'
    })

_use_local_logo = False


def local_logo(filename=None):
    return camsyslogo_white
Ejemplo n.º 15
0
def graph_to_figure(graph, output_format='svg', **format):

    try:
        import pygraphviz as viz
    except ImportError:
        import warnings
        warnings.warn(
            "pygraphviz module not installed, unable to draw nesting tree")
        raise NotImplementedError(
            "pygraphviz module not installed, unable to draw nesting tree")
    existing_format_keys = list(format.keys())
    for key in existing_format_keys:
        if key.upper() != key: format[key.upper()] = format[key]
    if 'SUPPRESSGRAPHSIZE' not in format:
        if 'GRAPHWIDTH' not in format: format['GRAPHWIDTH'] = 6.5
        if 'GRAPHHEIGHT' not in format: format['GRAPHHEIGHT'] = 4
    if 'UNAVAILABLE' not in format: format['UNAVAILABLE'] = True
    # x = XML_Builder("div", {'class':"nesting_graph larch_art"})
    # x.h2("Nesting Structure", anchor=1, attrib={'class':'larch_art_xhtml'})
    from io import BytesIO
    if 'SUPPRESSGRAPHSIZE' not in format:
        G = viz.AGraph(name='Tree',
                       directed=True,
                       size="{GRAPHWIDTH},{GRAPHHEIGHT}".format(**format))
    else:
        G = viz.AGraph(name='Tree', directed=True)
    for n in graph.nodes:
        nname = graph.nodes[n].get('name', n)
        if nname == n:
            G.add_node(n,
                       label='<{1}>'.format(n, nname),
                       style='rounded,solid',
                       shape='box')
        else:
            G.add_node(n,
                       label='<{1} <FONT COLOR="#999999">({0})</FONT>>'.format(
                           n, nname),
                       style='rounded,solid',
                       shape='box')
    try:
        graph.elementals
    except AttributeError:
        pass
    else:
        eG = G.add_subgraph(name='cluster_elemental',
                            nbunch=graph.elementals,
                            color='#cccccc',
                            bgcolor='#eeeeee',
                            label='Elemental Alternatives',
                            labelloc='b',
                            style='rounded,solid')
    unavailable_nodes = set()
    # if format['UNAVAILABLE']:
    # 	if self.is_provisioned():
    # 		try:
    # 			for n, ncode in enumerate(self.alternative_codes()):
    # 				if numpy.sum(self.Data('Avail'),axis=0)[n,0]==0: unavailable_nodes.add(ncode)
    # 		except: raise
    # 	try:
    # 		legible_avail = not isinstance(self.df.queries.avail, str)
    # 	except:
    # 		legible_avail = False
    # 	if legible_avail:
    # 		for ncode,navail in self.df.queries.avail.items():
    # 			try:
    # 				if navail=='0': unavailable_nodes.add(ncode)
    # 			except: raise
    # 	eG.add_subgraph(name='cluster_elemental_unavailable', nbunch=unavailable_nodes, color='#bbbbbb', bgcolor='#dddddd',
    # 				   label='Unavailable Alternatives', labelloc='b', style='rounded,solid')
    try:
        G.add_node(graph.root_id, label="Root")
    except AttributeError:
        pass
    up_nodes = set()
    down_nodes = set()
    for i, j in graph.edges:
        G.add_edge(i, j)
        down_nodes.add(j)
        up_nodes.add(i)
    pyg_imgdata = BytesIO()
    try:
        G.draw(pyg_imgdata, format=output_format,
               prog='dot')  # write postscript in k5.ps with neato layout
    except ValueError as err:
        if 'in path' in str(err):
            import warnings
            warnings.warn(str(err) + "; unable to draw nesting tree in report")
            raise NotImplementedError()
    from xmle import Elem
    if output_format == 'svg':
        import xml.etree.ElementTree as ET
        ET.register_namespace("", "http://www.w3.org/2000/svg")
        ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
        result = ET.fromstring(pyg_imgdata.getvalue().decode())
    else:
        result = Elem('span',
                      attrib={'style': 'color:red'},
                      text=f"Unable to render output_format '{output_format}'")
    x = Elem('div') << result
    return x
Ejemplo n.º 16
0
 def _repr_html_(self):
     from xmle import Elem
     x = Elem('div') << (self.__xml__())
     return x.tostring()
Ejemplo n.º 17
0
    def __xml__(self,
                use_viz=True,
                use_dot=True,
                output='svg',
                figsize=None,
                **format):
        viz = None
        dot = None
        if use_viz:
            try:
                import pygraphviz as viz
            except ImportError:
                if use_dot:
                    try:
                        import pydot as dot
                    except ImportError:
                        pass
        elif use_dot:
            try:
                import pydot as dot
            except ImportError:
                pass

        if viz is None and dot is None:
            import warnings
            if use_viz and use_dot:
                msg = "neither pydot nor pygraphviz modules are installed, unable to draw nesting tree"
            elif use_viz:
                msg = "pygraphviz module not installed, unable to draw nesting tree"
            elif use_dot:
                msg = "pydot module not installed, unable to draw nesting tree"
            else:
                msg = "no drawing module used, unable to draw nesting tree"
            warnings.warn(msg)
            raise NotImplementedError(msg)

        if viz is not None:
            existing_format_keys = list(format.keys())
            for key in existing_format_keys:
                if key.upper() != key: format[key.upper()] = format[key]
            if 'SUPPRESSGRAPHSIZE' not in format:
                if 'GRAPHWIDTH' not in format: format['GRAPHWIDTH'] = 6.5
                if 'GRAPHHEIGHT' not in format: format['GRAPHHEIGHT'] = 4
            if 'UNAVAILABLE' not in format: format['UNAVAILABLE'] = True
            # x = XML_Builder("div", {'class':"nesting_graph larch_art"})
            # x.h2("Nesting Structure", anchor=1, attrib={'class':'larch_art_xhtml'})
            from io import BytesIO
            if 'SUPPRESSGRAPHSIZE' not in format:
                G = viz.AGraph(
                    name='Tree',
                    directed=True,
                    size="{GRAPHWIDTH},{GRAPHHEIGHT}".format(**format))
            else:
                G = viz.AGraph(name='Tree', directed=True)
            for n in self.nodes:
                nname = self.nodes[n].get('name', n)
                if nname == n:
                    G.add_node(n,
                               label='<{1}>'.format(n, nname),
                               style='rounded,solid',
                               shape='box')
                else:
                    G.add_node(
                        n,
                        label='<{1} <FONT COLOR="#999999">({0})</FONT>>'.
                        format(n, nname),
                        style='rounded,solid',
                        shape='box')
            eG = G.add_subgraph(name='cluster_elemental',
                                nbunch=self.elementals,
                                color='#cccccc',
                                bgcolor='#eeeeee',
                                label='Elemental Alternatives',
                                labelloc='b',
                                style='rounded,solid')
            unavailable_nodes = set()
            # if format['UNAVAILABLE']:
            # 	if self.is_provisioned():
            # 		try:
            # 			for n, ncode in enumerate(self.alternative_codes()):
            # 				if numpy.sum(self.Data('Avail'),axis=0)[n,0]==0: unavailable_nodes.add(ncode)
            # 		except: raise
            # 	try:
            # 		legible_avail = not isinstance(self.df.queries.avail, str)
            # 	except:
            # 		legible_avail = False
            # 	if legible_avail:
            # 		for ncode,navail in self.df.queries.avail.items():
            # 			try:
            # 				if navail=='0': unavailable_nodes.add(ncode)
            # 			except: raise
            # 	eG.add_subgraph(name='cluster_elemental_unavailable', nbunch=unavailable_nodes, color='#bbbbbb', bgcolor='#dddddd',
            # 				   label='Unavailable Alternatives', labelloc='b', style='rounded,solid')
            G.add_node(self.root_id, label="Root")
            up_nodes = set()
            down_nodes = set()
            for i, j in self.edges:
                G.add_edge(i, j)
                down_nodes.add(j)
                up_nodes.add(i)
            pyg_imgdata = BytesIO()
            try:
                G.draw(
                    pyg_imgdata, format=output,
                    prog='dot')  # write postscript in k5.ps with neato layout
            except ValueError as err:
                if 'in path' in str(err):
                    import warnings
                    warnings.warn(
                        str(err) + "; unable to draw nesting tree in report")
                    raise NotImplementedError()
            if output == 'svg':
                import xml.etree.ElementTree as ET
                ET.register_namespace("", "http://www.w3.org/2000/svg")
                ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
                return ET.fromstring(pyg_imgdata.getvalue().decode())
            else:
                raise NotImplementedError(f"output {output} with use_viz")
        else:

            pydot = dot

            # set Graphviz graph type
            if self.is_directed():
                graph_type = 'digraph'
            else:
                graph_type = 'graph'
            strict = nx.number_of_selfloops(
                self) == 0 and not self.is_multigraph()

            name = self.name
            graph_defaults = self.graph.get('graph', {})
            if name == '':
                P = pydot.Dot('',
                              graph_type=graph_type,
                              strict=strict,
                              **graph_defaults)
            else:
                P = pydot.Dot('"%s"' % name,
                              graph_type=graph_type,
                              strict=strict,
                              **graph_defaults)
            try:
                P.set_node_defaults(**self.graph['node'])
            except KeyError:
                pass
            try:
                P.set_edge_defaults(**self.graph['edge'])
            except KeyError:
                pass

            cluster_elemental = pydot.Cluster(
                'elemental',
                style='rounded',
                bgcolor='lightgrey',
                color='white',
                rank='same',
                rankdir="LR",
            )

            for n, nodedata in self.nodes(data=True):
                str_nodedata = dict(
                    (k if k != 'name' else 'name_', '"' + str(v) + '"')
                    for k, v in nodedata.items())

                if 'parameter' in nodedata:
                    param_label = '<BR ALIGN="CENTER" /><FONT COLOR="#999999" POINT-SIZE="9"><I>{0}</I></FONT>'.format(
                        nodedata['parameter'])
                else:
                    param_label = ''

                if 'name' in nodedata and n != self.root_id:
                    name = nodedata['name']
                    str_nodedata['label'] = '<' \
                          '<FONT COLOR="#999999" POINT-SIZE="9">({1}) </FONT>' \
                          '{0}' \
                          '{2}>'.format(name,n,param_label)

                # Default styling for nodes can have been overridden
                if n in self.elementals:
                    str_nodedata['style'] = str_nodedata.get('style', 'filled')
                    str_nodedata['fillcolor'] = str_nodedata.get(
                        'fillcolor', 'white')
                elif n == self.root_id:
                    str_nodedata['shape'] = str_nodedata.get(
                        'shape', 'invhouse')
                else:
                    str_nodedata['style'] = str_nodedata.get(
                        'style', 'rounded')
                    str_nodedata['shape'] = str_nodedata.get(
                        'shape', 'rectangle')

                p = pydot.Node(str(n), **str_nodedata)
                P.add_node(p)
                if n in self.elementals:
                    cluster_elemental.add_node(p)

            P.add_subgraph(cluster_elemental)

            if self.is_multigraph():
                for u, v, key, edgedata in self.edges(data=True, keys=True):
                    str_edgedata = dict((k, str(v_))
                                        for k, v_ in edgedata.items()
                                        if k != 'key')
                    if v in self.elementals:
                        str_edgedata['constraint'] = 'false'
                    edge = pydot.Edge(str(u),
                                      str(v),
                                      key=str(key),
                                      **str_edgedata)
                    P.add_edge(edge)

            else:
                for u, v, edgedata in self.edges(data=True):
                    str_edgedata = dict(
                        (k, '"' + str(v) + '"') for k, v in edgedata.items())
                    edge = pydot.Edge(str(u), str(v), **str_edgedata)
                    P.add_edge(edge)

            ###
            from xmle import Elem
            prog = None
            if output == 'svg':
                import xml.etree.ElementTree as ET
                ET.register_namespace("", "http://www.w3.org/2000/svg")
                ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
            elif output == 'png':
                prog = [P.prog, '-Gdpi=300']
                if figsize is not None:
                    prog.append(f"-Gsize={figsize[0]},{figsize[1]}\!")
                e = Elem.from_any(P.create(prog=prog, format=output, **format))
                e.attrib['dpi'] = (300, 300)
                return e
            return Elem.from_any(P.create(prog=prog, format=output, **format))
Ejemplo n.º 18
0
def diff_images(imagefile1, imagefile2, return_blocked1=False, tolerance=100):
    '''

	Parameters
	----------
	imagefile1, imagefile2 : images
		Two images to compare.  They should be the same size.  Give by filename or with
		raw data.
	return_blocked1 : bool, default False
		If true, also return an image `Elem` based on imagefile1 showing where the mismatch blocks are.
	tolerance : int
		This can be used as the sensitivity factor, the larger it is the less sensitive the comparison

	Returns
	-------
	int
		The number of mismatched blocks
	Elem
		optional, see return_blocked1
	'''
    screenshot_staging = Image.open(imagefile1)
    screenshot_production = Image.open(imagefile2)
    columns = 60
    rows = 80
    screen_width, screen_height = screenshot_staging.size

    block_width = (
        (screen_width - 1) // columns) + 1  # this is just a division ceiling
    block_height = ((screen_height - 1) // rows) + 1

    mismatch_blocks = 0

    if return_blocked1:
        degenerate = Image.new(screenshot_staging.mode,
                               screenshot_staging.size, '#FFFFFF')

        if 'A' in screenshot_staging.getbands():
            degenerate.putalpha(screenshot_staging.getchannel('A'))

        screenshot_staging_1 = Image.blend(degenerate, screenshot_staging,
                                           0.25)

    for y in range(0, screen_height, block_height + 1):
        for x in range(0, screen_width, block_width + 1):
            region_staging = _process_region(screenshot_staging,
                                             x,
                                             y,
                                             block_width,
                                             block_height,
                                             tolerance=tolerance)
            region_production = _process_region(screenshot_production,
                                                x,
                                                y,
                                                block_width,
                                                block_height,
                                                tolerance=tolerance)

            if region_staging is not None and region_production is not None and region_production != region_staging:
                mismatch_blocks += 1
                if return_blocked1:
                    draw = ImageDraw.Draw(screenshot_staging_1)
                    draw.rectangle((x, y, x + block_width, y + block_height),
                                   outline="red")

    if return_blocked1:
        buffered = BytesIO()
        screenshot_staging_1.save(buffered, format="PNG")
        from xmle import Elem
        blocked1 = Elem.from_png_raw(buffered.getvalue())
        return mismatch_blocks, blocked1

    return mismatch_blocks
Ejemplo n.º 19
0
    def utility_functions(self, subset=None, resolve_parameters=False):
        """
		Generate an XHTML output of the utility function(s).

		Parameters
		----------
		subset : Collection, optional
			A collection of alternative codes to include. This only has effect if
			there are separate utility_co functions set by alternative. It is
			recommended to use this parameter if there are a very large number of
			alternatives, and the utility functions of most (or all) of them
			can be effectively communicated by showing only a few.
		resolve_parameters : bool, default False
			Whether to resolve the parameters to the current (estimated) value
			in the output.

		Returns
		-------
		xmle.Elem
		"""
        self.unmangle()
        from xmle import Elem
        x = Elem('div')
        t = x.elem('table',
                   style="margin-top:1px;",
                   attrib={'class': 'floatinghead'})
        if len(self.utility_co):
            # t.elem('caption', text=f"Utility Functions",
            # 	   style="caption-side:top;text-align:left;font-family:Roboto;font-weight:700;"
            # 			 "font-style:normal;font-size:100%;padding:0px;color:black;")

            # iterate over all alternatives if a dataframes is attached and lists the alternatives
            try:
                if self.dataservice is not None:
                    alts = self.dataservice.alternative_codes()
                elif self.dataframes is not None:
                    alts = self.dataframes.alternative_codes()
                else:
                    alts = self.utility_co.keys()
            except:
                alts = self.utility_co.keys()
            t_head = t.elem('thead')
            tr = t_head.elem('tr')
            tr.elem('th', text="alt")
            tr.elem('th', text='formula', attrib={'style': 'text-align:left;'})
            t_body = t.elem('tbody')
            for j in alts:
                if subset is None or j in subset:
                    tr = t_body.elem('tr')
                    tr.elem('td', text=str(j))
                    utilitycell = tr.elem('td',
                                          attrib={'style': 'text-align:left;'})
                    utilitycell.elem('div')
                    anything = False
                    if len(self.utility_ca):
                        utilitycell[-1].tail = (utilitycell[-1].tail
                                                or "") + " + "
                        utilitycell << list(
                            self.utility_ca.__xml__(
                                linebreaks=True,
                                resolve_parameters=self,
                                value_in_tooltips=not resolve_parameters))
                        anything = True
                    if j in self.utility_co:
                        v = self.utility_co[j]
                        if len(v):
                            if anything:
                                utilitycell << Elem('br')
                            utilitycell[-1].tail = (utilitycell[-1].tail
                                                    or "") + " + "
                            utilitycell << list(
                                v.__xml__(
                                    linebreaks=True,
                                    resolve_parameters=self,
                                    value_in_tooltips=not resolve_parameters))
                            anything = True
                    if len(self.quantity_ca):
                        if anything:
                            utilitycell << Elem('br')
                        if self.quantity_scale:
                            utilitycell[-1].tail = (utilitycell[-1].tail
                                                    or "") + " + "
                            from .linear import ParameterRef_C
                            utilitycell << list(
                                ParameterRef_C(self.quantity_scale).__xml__(
                                    resolve_parameters=self,
                                    value_in_tooltips=not resolve_parameters))
                            utilitycell[-1].tail = (utilitycell[-1].tail
                                                    or "") + " * log("
                        else:
                            utilitycell[-1].tail = (utilitycell[-1].tail
                                                    or "") + " + log("
                        content = self.quantity_ca.__xml__(
                            linebreaks=True,
                            lineprefix="  ",
                            exponentiate_parameters=True,
                            resolve_parameters=self,
                            value_in_tooltips=not resolve_parameters)
                        utilitycell << list(content)
                        utilitycell.elem('br', tail=")")
        else:
            # there is no differentiation by alternatives, just give one formula
            # t.elem('caption', text=f"Utility Function",
            # 	   style="caption-side:top;text-align:left;font-family:Roboto;font-weight:700;"
            # 			 "font-style:normal;font-size:100%;padding:0px;color:black;")
            tr = t.elem('tr')
            utilitycell = tr.elem('td', attrib={'style': 'text-align:left;'})
            utilitycell.elem('div')
            anything = False
            if len(self.utility_ca):
                utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
                utilitycell << list(
                    self.utility_ca.__xml__(
                        linebreaks=True,
                        resolve_parameters=self,
                        value_in_tooltips=not resolve_parameters))
                anything = True
            if len(self.quantity_ca):
                if anything:
                    utilitycell << Elem('br')
                if self.quantity_scale:
                    utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
                    from .linear import ParameterRef_C
                    utilitycell << list(
                        ParameterRef_C(self.quantity_scale).__xml__(
                            resolve_parameters=self,
                            value_in_tooltips=not resolve_parameters))
                    utilitycell[-1].tail = (utilitycell[-1].tail
                                            or "") + " * log("
                else:
                    utilitycell[-1].tail = (utilitycell[-1].tail
                                            or "") + " + log("
                content = self.quantity_ca.__xml__(
                    linebreaks=True,
                    lineprefix="  ",
                    exponentiate_parameters=True,
                    resolve_parameters=self,
                    value_in_tooltips=not resolve_parameters)
                utilitycell << list(content)
                utilitycell.elem('br', tail=")")
        return x
Ejemplo n.º 20
0
def tooltipped_style():
    return Elem('style',
                attrib={'type': "text/css"},
                text=_tooltipped_style_css)
Ejemplo n.º 21
0
def larch_style():
    return Elem(
        tag='style',
        text=_default_css,
    )
Ejemplo n.º 22
0
Archivo: png.py Proyecto: jpn--/larch
def make_png(
    content,
    dpi=None,
    compress=True,
    output='Elem',
    close_after=True,
    pad_inches=0.1,
    facecolor=None,
    return_size=False,
    return_dpi=False,
):

    _bytes_io = None
    _img = None
    _size = None

    if hasattr(content, '_repr_png_'):
        content = content._repr_png_()

    if isinstance(content, bytes):
        _bytes_io = io.BytesIO(content)

    if 'matplotlib' in str(type(content)):
        from matplotlib import pyplot as plt
        import matplotlib.figure
        if not isinstance(content, matplotlib.figure.Figure):
            try:
                content = content.get_figure()
            except AttributeError:
                if not hasattr(content, 'savefig'):
                    raise TypeError(
                        'matplotlib content must provide `get_figure` or `savefig` method.'
                    )

        # content is a Figure or otherwise has a savefig method
        try:
            fig_number = content.number
        except AttributeError:
            fig_number = None

        if facecolor is None:
            try:
                facecolor = content.get_facecolor()
            except:
                facecolor = 'w'

        try:
            edgecolor = content.get_edgecolor()
        except:
            edgecolor = 'none'

        _bytes_io = io.BytesIO()
        content.savefig(
            _bytes_io,
            dpi=dpi,
            orientation='portrait',
            format='png',
            bbox_inches='tight',
            pad_inches=pad_inches,
            facecolor=facecolor,
            edgecolor=edgecolor,
        )

        if close_after and fig_number is not None:
            plt.close(fig_number)

    if _bytes_io.getvalue()[:4] == b'\x89PNG':
        _img = Image.open(_bytes_io)
        dpi = _img.info.get('dpi', dpi)
        if dpi is None:
            dpi = [96, 96]
        if compress:
            _img = _img.convert(mode='P', palette='ADAPTIVE')
        _bytes_io = io.BytesIO()
        _img.save(_bytes_io, dpi=dpi, format='png')
        _size = _img.size
    else:
        raise ValueError("Not valid PNG data")

    if output.lower() == 'elem':
        result = Elem(
            tag='img',
            src="data:image/png;base64,{}".format(
                base64.standard_b64encode(_bytes_io.getvalue()).decode()),
        )
    elif output.lower() == 'bytesio':
        result = _bytes_io
    else:
        result = _img

    if return_size:
        if isinstance(result, tuple):
            result = (*result, _size)
        else:
            result = result, _size

    if return_dpi:
        if isinstance(result, tuple):
            result = (*result, dpi)
        else:
            result = result, dpi

    return result
Ejemplo n.º 23
0
Archivo: tree.py Proyecto: mindis/larch
    def __xml__(self, **format):
        viz = None
        dot = None
        try:
            import pygraphviz as viz
        except ImportError:
            try:
                import pydot as dot
            except ImportError:
                pass

        if viz is None and dot is None:
            import warnings
            warnings.warn(
                "pygraphviz module not installed, unable to draw nesting tree")
            raise NotImplementedError(
                "pygraphviz module not installed, unable to draw nesting tree")

        if viz is not None:
            existing_format_keys = list(format.keys())
            for key in existing_format_keys:
                if key.upper() != key: format[key.upper()] = format[key]
            if 'SUPPRESSGRAPHSIZE' not in format:
                if 'GRAPHWIDTH' not in format: format['GRAPHWIDTH'] = 6.5
                if 'GRAPHHEIGHT' not in format: format['GRAPHHEIGHT'] = 4
            if 'UNAVAILABLE' not in format: format['UNAVAILABLE'] = True
            # x = XML_Builder("div", {'class':"nesting_graph larch_art"})
            # x.h2("Nesting Structure", anchor=1, attrib={'class':'larch_art_xhtml'})
            from io import BytesIO
            if 'SUPPRESSGRAPHSIZE' not in format:
                G = viz.AGraph(
                    name='Tree',
                    directed=True,
                    size="{GRAPHWIDTH},{GRAPHHEIGHT}".format(**format))
            else:
                G = viz.AGraph(name='Tree', directed=True)
            for n in self.nodes:
                nname = self.nodes[n].get('name', n)
                if nname == n:
                    G.add_node(n,
                               label='<{1}>'.format(n, nname),
                               style='rounded,solid',
                               shape='box')
                else:
                    G.add_node(
                        n,
                        label='<{1} <FONT COLOR="#999999">({0})</FONT>>'.
                        format(n, nname),
                        style='rounded,solid',
                        shape='box')
            eG = G.add_subgraph(name='cluster_elemental',
                                nbunch=self.elementals,
                                color='#cccccc',
                                bgcolor='#eeeeee',
                                label='Elemental Alternatives',
                                labelloc='b',
                                style='rounded,solid')
            unavailable_nodes = set()
            # if format['UNAVAILABLE']:
            # 	if self.is_provisioned():
            # 		try:
            # 			for n, ncode in enumerate(self.alternative_codes()):
            # 				if numpy.sum(self.Data('Avail'),axis=0)[n,0]==0: unavailable_nodes.add(ncode)
            # 		except: raise
            # 	try:
            # 		legible_avail = not isinstance(self.df.queries.avail, str)
            # 	except:
            # 		legible_avail = False
            # 	if legible_avail:
            # 		for ncode,navail in self.df.queries.avail.items():
            # 			try:
            # 				if navail=='0': unavailable_nodes.add(ncode)
            # 			except: raise
            # 	eG.add_subgraph(name='cluster_elemental_unavailable', nbunch=unavailable_nodes, color='#bbbbbb', bgcolor='#dddddd',
            # 				   label='Unavailable Alternatives', labelloc='b', style='rounded,solid')
            G.add_node(self.root_id, label="Root")
            up_nodes = set()
            down_nodes = set()
            for i, j in self.edges:
                G.add_edge(i, j)
                down_nodes.add(j)
                up_nodes.add(i)
            pyg_imgdata = BytesIO()
            try:
                G.draw(
                    pyg_imgdata, format='svg',
                    prog='dot')  # write postscript in k5.ps with neato layout
            except ValueError as err:
                if 'in path' in str(err):
                    import warnings
                    warnings.warn(
                        str(err) + "; unable to draw nesting tree in report")
                    raise NotImplementedError()
            import xml.etree.ElementTree as ET
            ET.register_namespace("", "http://www.w3.org/2000/svg")
            ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
            return ET.fromstring(pyg_imgdata.getvalue().decode())
        else:

            N = self
            pydot = dot

            # set Graphviz graph type
            if N.is_directed():
                graph_type = 'digraph'
            else:
                graph_type = 'graph'
            strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()

            name = N.name
            graph_defaults = N.graph.get('graph', {})
            if name is '':
                P = pydot.Dot('',
                              graph_type=graph_type,
                              strict=strict,
                              **graph_defaults)
            else:
                P = pydot.Dot('"%s"' % name,
                              graph_type=graph_type,
                              strict=strict,
                              **graph_defaults)
            try:
                P.set_node_defaults(**N.graph['node'])
            except KeyError:
                pass
            try:
                P.set_edge_defaults(**N.graph['edge'])
            except KeyError:
                pass

            for n, nodedata in N.nodes(data=True):
                str_nodedata = dict((k if k != 'name' else 'name_', str(v))
                                    for k, v in nodedata.items())
                p = pydot.Node(str(n), **str_nodedata)
                P.add_node(p)

            if N.is_multigraph():
                for u, v, key, edgedata in N.edges(data=True, keys=True):
                    str_edgedata = dict(
                        (k, str(v)) for k, v in edgedata.items() if k != 'key')
                    edge = pydot.Edge(str(u),
                                      str(v),
                                      key=str(key),
                                      **str_edgedata)
                    P.add_edge(edge)

            else:
                for u, v, edgedata in N.edges(data=True):
                    str_edgedata = dict(
                        (k, str(v)) for k, v in edgedata.items())
                    edge = pydot.Edge(str(u), str(v), **str_edgedata)
                    P.add_edge(edge)

            ###
            from xmle import Elem
            return Elem.from_any(P.create_svg())
Ejemplo n.º 24
0
Archivo: model.py Proyecto: jpn--/larch
	def utility_functions(self, subset=None, resolve_parameters=False):
		"""
		Generate an XHTML output of the utility function(s).

		Parameters
		----------
		subset : Collection, optional
			A collection of alterative codes to include. This only has effect if
			there are seperate utility_co functions set by alternative.
		resolve_parameters : bool, default False
			Whether to resolve the parameters to the current (estimated) value
			in the output.

		Returns
		-------
		xmle.Elem
		"""
		self.unmangle()
		from xmle import Elem
		x = Elem('div')
		t = x.elem('table', style="margin-top:1px;", attrib={'class':'floatinghead'})
		if len(self.utility_co):
			# t.elem('caption', text=f"Utility Functions",
			# 	   style="caption-side:top;text-align:left;font-family:Roboto;font-weight:700;"
			# 			 "font-style:normal;font-size:100%;padding:0px;color:black;")
			t_head = t.elem('thead')
			tr = t_head.elem('tr')
			tr.elem('th', text="alt")
			tr.elem('th', text='formula', attrib={'style':'text-align:left;'})
			t_body = t.elem('tbody')
			for j in self.utility_co.keys():
				if subset is None or j in subset:
					tr = t_body.elem('tr')
					tr.elem('td', text=str(j))
					utilitycell = tr.elem('td', attrib={'style':'text-align:left;'})
					utilitycell.elem('div')
					anything = False
					if len(self.utility_ca):
						utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
						utilitycell << list(self.utility_ca.__xml__(linebreaks=True, resolve_parameters=self, value_in_tooltips=not resolve_parameters))
						anything = True
					if j in self.utility_co:
						if anything:
							utilitycell << Elem('br')
						v = self.utility_co[j]
						utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
						utilitycell << list(v.__xml__(linebreaks=True, resolve_parameters=self, value_in_tooltips=not resolve_parameters))
						anything = True
					if len(self.quantity_ca):
						if anything:
							utilitycell << Elem('br')
						if self.quantity_scale:
							utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
							from .linear import ParameterRef_C
							utilitycell << list(ParameterRef_C(self.quantity_scale).__xml__(resolve_parameters=self, value_in_tooltips=not resolve_parameters))
							utilitycell[-1].tail = (utilitycell[-1].tail or "") + " * log("
						else:
							utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + log("
						content = self.quantity_ca.__xml__(linebreaks=True, lineprefix="  ",
														   exponentiate_parameters=True, resolve_parameters=self, value_in_tooltips=not resolve_parameters)
						utilitycell << list(content)
						utilitycell.elem('br', tail=")")
		else:
			# there is no differentiation by alternatives, just give one formula
			# t.elem('caption', text=f"Utility Function",
			# 	   style="caption-side:top;text-align:left;font-family:Roboto;font-weight:700;"
			# 			 "font-style:normal;font-size:100%;padding:0px;color:black;")
			tr = t.elem('tr')
			utilitycell = tr.elem('td', attrib={'style':'text-align:left;'})
			utilitycell.elem('div')
			anything = False
			if len(self.utility_ca):
				utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
				utilitycell << list(self.utility_ca.__xml__(linebreaks=True, resolve_parameters=self, value_in_tooltips=not resolve_parameters))
				anything = True
			if len(self.quantity_ca):
				if anything:
					utilitycell << Elem('br')
				if self.quantity_scale:
					utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + "
					from .linear import ParameterRef_C
					utilitycell << list(ParameterRef_C(self.quantity_scale).__xml__(resolve_parameters=self, value_in_tooltips=not resolve_parameters))
					utilitycell[-1].tail = (utilitycell[-1].tail or "") + " * log("
				else:
					utilitycell[-1].tail = (utilitycell[-1].tail or "") + " + log("
				content = self.quantity_ca.__xml__(linebreaks=True, lineprefix="  ", exponentiate_parameters=True, resolve_parameters=self, value_in_tooltips=not resolve_parameters)
				utilitycell << list(content)
				utilitycell.elem('br', tail=")")
		return x
Ejemplo n.º 25
0
 def __xml__(self, no_data=False, descriptions=True, dictionaries=False):
     from xmle import Elem
     x = Elem('div')
     t = x.elem('table', style="margin-top:1px;")
     t.elem(
         'caption',
         text=f"<larch.{self.__class__.__name__}>",
         style=
         "caption-side:top;text-align:left;font-family:Roboto;font-weight:700;font-style:normal;font-size:100%;padding:0px;"
     )
     #
     try:
         ident = self.ident
     except AttributeError:
         pass
     else:
         tr = t.elem('tr')
         tr.elem('th', text='ident')
         tr.elem('td', text=ident)
     #
     try:
         filename = self.filename
     except AttributeError:
         pass
     else:
         tr = t.elem('tr')
         tr.elem('th', text='file')
         tr.elem('td', text=truncate_path_for_display(filename))
     #
     try:
         filemode = self.filemode
     except AttributeError:
         pass
     else:
         tr = t.elem('tr')
         tr.elem('th', text='mode')
         tr.elem('td', text=truncate_path_for_display(self.filemode))
     #
     try:
         v_pathname = self._groupnode._v_pathname
     except AttributeError:
         pass
     else:
         if self._groupnode._v_pathname != "/":
             tr = t.elem('tr')
             tr.elem('th', text='node')
             tr.elem('td', text=self._groupnode._v_pathname)
     #
     try:
         str_shape = str(self.shape)
     except NoKnownShape:
         pass
     else:
         tr = t.elem('tr')
         tr.elem('th', text='shape')
         tr.elem('td', text=str_shape)
     #
     try:
         str_shape = str(self.metashape)
     except (NoKnownShape, AttributeError):
         pass
     else:
         tr = t.elem('tr')
         tr.elem('th', text='metashape')
         tr.elem('td', text=str_shape)
     #
     try:
         str_durable_mask = f"0x{self.durable_mask:X}"
     except (AttributeError):
         pass
     else:
         if str_durable_mask != '0x0':
             tr = t.elem('tr')
             tr.elem('th', text='durable_mask')
             tr.elem('td', text=str_durable_mask)
     #
     if not no_data:
         if len(self._groupnode._v_children):
             tr = t.elem('tr')
             tr.elem('th', text='data', style='vertical-align:top;')
             td = tr.elem('td')
             t1 = td.elem('table', cls='dictionary')
             t1head = t1.elem('thead')
             t1headr = t1head.elem('tr')
             t1headr.elem('th', text='name')
             t1headr.elem('th', text='dtype')
             if descriptions:
                 t1headr.elem('th', text='description')
             any_sources = 0
             for i in sorted(self._groupnode._v_children.keys()):
                 try:
                     node_dtype = self._groupnode._v_children[i].dtype
                 except (tb.NoSuchNodeError, AttributeError):
                     node_dtype = "<no dtype>"
                 if i not in _reserved_names_:
                     tr1 = t1.elem('tr')
                     tr1.elem('td', text=i)
                     tr1.elem('td', text=node_dtype)
                     if descriptions:
                         try:
                             title = self._groupnode._v_children[
                                 i]._v_attrs['TITLE']
                         except:
                             title = ""
                         else:
                             tr1.elem('td', text=title)
                     try:
                         orig_source = self._groupnode._v_children[
                             i]._v_attrs['ORIGINAL_SOURCE']
                     except:
                         pass
                     else:
                         tr1.elem('td', text=orig_source)
                         any_sources += 1
             if any_sources:
                 t1headr.elem('th', text='source')
         else:
             tr = t.elem('tr')
             tr.elem('th', text='data', style='vertical-align:top;')
             tr.elem('td', text='<empty>')
     return x
Ejemplo n.º 26
0
    def __parameter_table_section(self, pname):

        from xmle import Elem

        pname_str = str(pname)
        pf = self.pf
        # if pname in self.rename_parameters:
        # 	colspan = 0
        # 	if 'std err' in pf.columns:
        # 		colspan += 1
        # 	if 't stat' in pf.columns:
        # 		colspan += 1
        # 	if 'nullvalue' in pf.columns:
        # 		colspan += 1
        # 	return [
        # 		Elem('td', text="{:.4g}".format(pf.loc[self.rename_parameters[pname],'value'])),
        # 		Elem('td', text="= "+self.rename_parameters[pname], colspan=str(colspan)),
        # 	]
        if pf.loc[pname_str, 'holdfast']:
            colspan = 0
            if 'std err' in pf.columns:
                colspan += 1
            if 't stat' in pf.columns:
                colspan += 1
            if 'nullvalue' in pf.columns:
                colspan += 1
            # if pf.loc[pname_str,'holdfast'] == holdfast_reasons.asc_but_never_chosen:
            # 	return [
            # 		Elem('td', text="{:.4g}".format(pf.loc[pname_str,'value'])),
            # 		Elem('td', text="fixed value, never chosen", colspan=str(colspan)),
            # 	]
            # elif pf.loc[pname_str, 'holdfast'] == holdfast_reasons.snap_to_constant_eq:
            # 	return [
            # 		Elem('td', text="{:.4g}".format(pf.loc[pname_str, 'value'])),
            # 		Elem('td', text="fixed value", colspan=str(colspan)),
            # 	]
            # elif pf.loc[pname_str, 'holdfast'] == holdfast_reasons.snap_to_constant_le:
            # 	return [
            # 		Elem('td', text="{:.4g}".format(pf.loc[pname_str, 'value'])),
            # 		Elem('td', text="≤ {:.4g}".format(pf.loc[pname_str, 'value']), colspan=str(colspan)),
            # 	]
            # elif pf.loc[pname_str, 'holdfast'] == holdfast_reasons.snap_to_constant_ge:
            # 	return [
            # 		Elem('td', text="{:.4g}".format(pf.loc[pname_str, 'value'])),
            # 		Elem('td', text="≥ {:.4g}".format(pf.loc[pname_str, 'value']), colspan=str(colspan)),
            # 	]
            # elif pf.loc[pname_str, 'note'] != "" and not pandas.isnull(pf.loc[pname_str, 'note']):
            # 	return [
            # 		Elem('td', text="{:.4g}".format(pf.loc[pname_str,'value'])),
            # 		Elem('td', text=pf.loc[pname_str, 'note'], colspan=str(colspan)),
            # 	]
            # else:
            return [
                Elem('td', text="{:.4g}".format(pf.loc[pname_str, 'value'])),
                Elem('td',
                     text="fixed value",
                     colspan=str(colspan),
                     style="text-align: left;"),
            ]
        else:
            result = [
                Elem('td', text="{:.4g}".format(pf.loc[pname_str, 'value']))
            ]
            if 'std err' in pf.columns:
                result += [
                    Elem('td',
                         text="{:#.3g}".format(pf.loc[pname_str, 'std err'])),
                ]
            if 't stat' in pf.columns:
                result += [
                    Elem('td',
                         text="{:#.2f}".format(pf.loc[pname_str, 't stat'])),
                ]
            if 'nullvalue' in pf.columns:
                result += [
                    Elem('td',
                         text="{:#.2g}".format(pf.loc[pname_str,
                                                      'nullvalue'])),
                ]
            return result