├── .gitignore ├── example.png ├── heatmapexample.png ├── structout ├── __init__.py ├── heatmap.py ├── intlist.py ├── rnagraph.py ├── graph.py └── intlistV2.py ├── README.md └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smautner/StructOut/HEAD/example.png -------------------------------------------------------------------------------- /heatmapexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smautner/StructOut/HEAD/heatmapexample.png -------------------------------------------------------------------------------- /structout/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from structout.graph import gprint 4 | from structout.intlist import dprint 5 | from structout.intlistV2 import lprint, npprint, iprint, doALine, str_to, scatter, plot, plot_braille, colorize 6 | from structout.heatmap import heatmap 7 | from structout.rnagraph import RNAprint 8 | import numpy as np 9 | 10 | 11 | #def bins(values,minmax=True,length=-1,method='bins',methodhow=16,**kwargs): 12 | # npprint(values,minmax=minmax,length=length,method=method,methodhow=methodhow,**kwargs) 13 | 14 | 15 | 16 | def hist_CounterBased(values): 17 | from collections import Counter 18 | counts = Counter(values) 19 | k=counts.keys() 20 | val = [counts.get(i,0) for i in range(min(k), max(k)+1)] 21 | lprint(val) 22 | 23 | def hist_(values, bins = 40, xlim=None): 24 | val = np.histogram(values,density=False, bins = bins, range = xlim) 25 | print(str_to(min(values) if not xlim else xlim[0]),end = '|') 26 | print(doALine(val[0],showrange = False), end = '|') 27 | print(str_to(max(values) if not xlim else xlim[1])) 28 | 29 | 30 | def hist(values, bins = 40, xlim=None, color = '1'): 31 | val,_ = np.histogram(values,density=False, bins = bins, range = xlim) 32 | print(str_to(min(values) if not xlim else xlim[0]),end = '|') 33 | text = plot_braille(np.arange(bins),val,rows = 1,cols=bins//2,xlim=np.array((0,bins)))[0] 34 | if color: 35 | text = colorize(text,'1') 36 | print(text, end = '|') 37 | print(str_to(max(values) if not xlim else xlim[1])) 38 | 39 | 40 | def testhist(): 41 | hist([1,2,3,4,5,6,10], 40) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StructOut 2 | 3 | prints networkx graphs and large int lists pleasantly to terminal. 4 | 5 | 6 | 7 | ```python 8 | import structout as so 9 | so.lprint(range(1000),length=70) 10 | 11 | import networkx as nx 12 | g=nx.path_graph(5) 13 | so.gprint(g) 14 | 15 | ``` 16 | 17 | ![''](https://raw.githubusercontent.com/smautner/StructOut/master/example.png) 18 | ![''](https://raw.githubusercontent.com/smautner/StructOut/master/heatmapexample.png) 19 | 20 | 21 | ## install 22 | 23 | ``` 24 | pip install structout 25 | ``` 26 | 27 | ## numberlists 28 | 29 | ``` 30 | 31 | # implemented options: -> i should update this at some point :) 32 | def doALine(values, 33 | length=-1,minmax=False, chunk_operation=max, 34 | method = 'log', methodhow =2) 35 | 36 | # there is also a convenience function 'bins' that 37 | # does evenly spaced out bins :) 38 | 39 | ``` 40 | 41 | ## Graphs 42 | 43 | ### Colors 44 | 45 | ```python 46 | # this will color nodes 1,2,3 in one color and 4,0 in another 47 | gprint(graph, color=([1,2,3],[4,0])) 48 | ``` 49 | 50 | - edge labels are always blue 51 | - digraphs should mark the direction with a blue dot 52 | 53 | ### cut graph down to a subgraph 54 | 55 | ```python 56 | # print nodes with max distance 1 to the nodes 1 and 2 57 | gprint(graph, zoomlevel=1, zoomnodes=[1,2]) 58 | ``` 59 | 60 | 61 | ### other options 62 | 63 | ```python 64 | nodelabel='label', # node and edge labels 65 | edgelabel='label', 66 | size=10, # size 67 | pos=None, # pass a coordinate dictionary ; {nodeid : x,y} 68 | n_graphs_per_line= 5 # when passing multiple graphs, wrap here 69 | ``` 70 | 71 | ### RNAprint 72 | ``` 73 | G.-.G.-.G 74 | -. - 75 | G.=.G 76 | - - 77 | G.=.G 78 | - - 79 | G G 80 | - - 81 | G U..... .U 82 | - -..... .- - 83 | A.-.A.-.A.-.A.-.A.-.A.-.A.-.A U.-.U U 84 | = = = = = = - 85 | A.-.A.-.A.-.A.-.A.-.A .U.-.U.-.U.-.U.-.U 86 | - .- 87 | A.=.A 88 | - - 89 | A.=.A. 90 | -. -. 91 | A.-.A 92 | ``` 93 | 94 | ## heatmap 95 | 96 | ``` 97 | heatmap(matrix,dim = (20,20),operator= np.max, wide = True) 98 | ``` 99 | wide makes each symbol take up 2 characters. 100 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import absolute_import 3 | from __future__ import print_function 4 | 5 | import os 6 | import sys 7 | import subprocess 8 | import re 9 | 10 | from setuptools import setup 11 | from setuptools.command.sdist import sdist as _sdist 12 | from setuptools.command.install import install as _install 13 | 14 | VERSION_PY = """ 15 | # This file is originally generated from Git information by running 'setup.py 16 | # version'. Distribution tarballs contain a pre-generated copy of this file. 17 | __version__ = '%s' 18 | """ 19 | 20 | 21 | def update_version_py(): 22 | if not os.path.isdir(".git"): 23 | print("This does not appear to be a Git repository.") 24 | return 25 | try: 26 | #p = subprocess.Popen(["git", "describe","--tags", "--always"], 27 | # stdout=subprocess.PIPE) 28 | p = subprocess.Popen("git rev-list HEAD --count".split(), 29 | stdout=subprocess.PIPE) 30 | 31 | 32 | except EnvironmentError: 33 | print("unable to run git, leaving structout/__version__.py alone") 34 | return 35 | stdout = p.communicate()[0].decode("utf-8") 36 | 37 | print ("stdout:",stdout) 38 | if p.returncode != 0: 39 | print("unable to run git, leaving structout/__version__.py alone") 40 | return 41 | ver = "0.1."+stdout.strip() 42 | #ver = str(int(ver,16)) # pypi doesnt like base 16 43 | f = open("structout/__version__.py", "w") 44 | f.write(VERSION_PY % ver) 45 | f.close() 46 | print("set structout/__version__.py to '%s'" % ver) 47 | 48 | 49 | def get_version(): 50 | try: 51 | f = open("structout/__version__.py") 52 | except EnvironmentError: 53 | return None 54 | for line in f.readlines(): 55 | mo = re.match("__version__ = '([^']+)'", line) 56 | if mo: 57 | ver = mo.group(1) 58 | return ver 59 | return None 60 | 61 | 62 | class sdist(_sdist): 63 | 64 | def run(self): 65 | update_version_py() 66 | self.distribution.metadata.version = get_version() 67 | return _sdist.run(self) 68 | 69 | 70 | class install(_install): 71 | 72 | def run(self): 73 | _install.run(self) 74 | 75 | def checkProgramIsInstalled(self, program, args, where_to_download, 76 | affected_tools): 77 | try: 78 | subprocess.Popen([program, args], stderr=subprocess.PIPE, stdout=subprocess.PIPE) 79 | return True 80 | except EnvironmentError: 81 | # handle file not found error. 82 | # the config file is installed in: 83 | msg = "\n**{0} not found. This " \ 84 | "program is needed for the following "\ 85 | "tools to work properly:\n"\ 86 | " {1}\n"\ 87 | "{0} can be downloaded from here:\n " \ 88 | " {2}\n".format(program, affected_tools, 89 | where_to_download) 90 | sys.stderr.write(msg) 91 | 92 | except Exception as e: 93 | sys.stderr.write("Error: {}".format(e)) 94 | 95 | setup( 96 | name='structout', 97 | version=get_version(), 98 | author='Stefan Mautner', 99 | author_email='myl4stn4m3@cs.uni-freiburg.de', 100 | packages=['structout'], 101 | python_requires='>=3.8', 102 | include_package_data=True, 103 | package_data={}, 104 | url='https://github.com/smautner/StructOut_py3', 105 | license='GPLv3', 106 | description='pretty print data structures', 107 | long_description=open('README.md').read(), 108 | long_description_content_type = 'text/markdown', 109 | install_requires=[ 110 | "networkx","scipy", "sty" 111 | ], 112 | cmdclass={'sdist': sdist, 'install': install} 113 | ) 114 | -------------------------------------------------------------------------------- /structout/heatmap.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | ''' 5 | ok so we want to print a numpy array... 6 | 7 | - compress 8 | - color it 9 | 10 | ''' 11 | 12 | def heatmap(matrix,dim = (20,20),operator= np.max, wide = True, legend = True): 13 | 14 | 15 | if not np.isfinite(matrix).all(): 16 | print('WILL NOT DRAW MATRIX, REMOVE NON FINITE VALUES') 17 | return 18 | canvas = compress2d(matrix,dim=dim, operator=operator) 19 | if wide: 20 | canvas = makewide(canvas) 21 | 22 | # colorlist is generated like this: 23 | #matplotlib.cm.get_cmap('viridis', 128).colors[:,:-1].tolist() 24 | colors = [[0.267004, 0.004874, 0.329415], [0.269944, 0.014625, 0.341379], [0.272594, 0.025563, 0.353093], [0.274952, 0.037752, 0.364543], [0.277018, 0.050344, 0.375715], [0.278791, 0.062145, 0.386592], [0.280267, 0.073417, 0.397163], [0.281446, 0.08432, 0.407414], [0.282327, 0.094955, 0.417331], [0.28291, 0.105393, 0.426902], [0.283197, 0.11568, 0.436115], [0.283187, 0.125848, 0.44496], [0.282884, 0.13592, 0.453427], [0.28229, 0.145912, 0.46151], [0.281412, 0.155834, 0.469201], [0.280255, 0.165693, 0.476498], [0.278826, 0.17549, 0.483397], [0.277134, 0.185228, 0.489898], [0.275191, 0.194905, 0.496005], [0.273006, 0.20452, 0.501721], [0.270595, 0.214069, 0.507052], [0.267968, 0.223549, 0.512008], [0.265145, 0.232956, 0.516599], [0.262138, 0.242286, 0.520837], [0.258965, 0.251537, 0.524736], [0.255645, 0.260703, 0.528312], [0.252194, 0.269783, 0.531579], [0.248629, 0.278775, 0.534556], [0.244972, 0.287675, 0.53726], [0.241237, 0.296485, 0.539709], [0.237441, 0.305202, 0.541921], [0.233603, 0.313828, 0.543914], [0.229739, 0.322361, 0.545706], [0.225863, 0.330805, 0.547314], [0.221989, 0.339161, 0.548752], [0.21813, 0.347432, 0.550038], [0.214298, 0.355619, 0.551184], [0.210503, 0.363727, 0.552206], [0.206756, 0.371758, 0.553117], [0.203063, 0.379716, 0.553925], [0.19943, 0.387607, 0.554642], [0.19586, 0.395433, 0.555276], [0.192357, 0.403199, 0.555836], [0.188923, 0.41091, 0.556326], [0.185556, 0.41857, 0.556753], [0.182256, 0.426184, 0.55712], [0.179019, 0.433756, 0.55743], [0.175841, 0.44129, 0.557685], [0.172719, 0.448791, 0.557885], [0.169646, 0.456262, 0.55803], [0.166617, 0.463708, 0.558119], [0.163625, 0.471133, 0.558148], [0.160665, 0.47854, 0.558115], [0.157729, 0.485932, 0.558013], [0.154815, 0.493313, 0.55784], [0.151918, 0.500685, 0.557587], [0.149039, 0.508051, 0.55725], [0.14618, 0.515413, 0.556823], [0.143343, 0.522773, 0.556295], [0.140536, 0.530132, 0.555659], [0.13777, 0.537492, 0.554906], [0.135066, 0.544853, 0.554029], [0.132444, 0.552216, 0.553018], [0.129933, 0.559582, 0.551864], [0.126453, 0.570633, 0.549841], [0.124395, 0.578002, 0.548287], [0.122606, 0.585371, 0.546557], [0.121148, 0.592739, 0.544641], [0.120092, 0.600104, 0.54253], [0.119512, 0.607464, 0.540218], [0.119483, 0.614817, 0.537692], [0.120081, 0.622161, 0.534946], [0.12138, 0.629492, 0.531973], [0.123444, 0.636809, 0.528763], [0.126326, 0.644107, 0.525311], [0.130067, 0.651384, 0.521608], [0.134692, 0.658636, 0.517649], [0.14021, 0.665859, 0.513427], [0.146616, 0.67305, 0.508936], [0.153894, 0.680203, 0.504172], [0.162016, 0.687316, 0.499129], [0.170948, 0.694384, 0.493803], [0.180653, 0.701402, 0.488189], [0.19109, 0.708366, 0.482284], [0.202219, 0.715272, 0.476084], [0.214, 0.722114, 0.469588], [0.226397, 0.728888, 0.462789], [0.239374, 0.735588, 0.455688], [0.252899, 0.742211, 0.448284], [0.266941, 0.748751, 0.440573], [0.281477, 0.755203, 0.432552], [0.296479, 0.761561, 0.424223], [0.311925, 0.767822, 0.415586], [0.327796, 0.77398, 0.40664], [0.344074, 0.780029, 0.397381], [0.360741, 0.785964, 0.387814], [0.377779, 0.791781, 0.377939], [0.395174, 0.797475, 0.367757], [0.412913, 0.803041, 0.357269], [0.430983, 0.808473, 0.346476], [0.449368, 0.813768, 0.335384], [0.468053, 0.818921, 0.323998], [0.487026, 0.823929, 0.312321], [0.506271, 0.828786, 0.300362], [0.525776, 0.833491, 0.288127], [0.545524, 0.838039, 0.275626], [0.565498, 0.84243, 0.262877], [0.585678, 0.846661, 0.249897], [0.606045, 0.850733, 0.236712], [0.626579, 0.854645, 0.223353], [0.647257, 0.8584, 0.209861], [0.668054, 0.861999, 0.196293], [0.688944, 0.865448, 0.182725], [0.709898, 0.868751, 0.169257], [0.730889, 0.871916, 0.156029], [0.751884, 0.874951, 0.143228], [0.772852, 0.877868, 0.131109], [0.79376, 0.880678, 0.120005], [0.814576, 0.883393, 0.110347], [0.83527, 0.886029, 0.102646], [0.85581, 0.888601, 0.097452], [0.876168, 0.891125, 0.09525], [0.89632, 0.893616, 0.096335], [0.916242, 0.896091, 0.100717], [0.935904, 0.89857, 0.108131], [0.9553, 0.901065, 0.118128], [0.974417, 0.90359, 0.130215], [0.993248, 0.906157, 0.143936]] 25 | 26 | print(colorize(canvas,colors)) 27 | if legend: 28 | print(canvas.min(),getlegend(colors),canvas.max()) 29 | 30 | def getlegend(colors): 31 | return colorize(np.matrix(np.linspace(0,1,40)),colors) 32 | 33 | 34 | 35 | 36 | def colorize(matrix,colors, space = np.linspace): 37 | if not np.isfinite(matrix).all(): 38 | raise Exception('matrix contains non finite values') 39 | mima = matrix.min(), matrix.max() 40 | 41 | # we need bins: 42 | bins = space(*mima, num=len(colors)) 43 | digitized = np.digitize(matrix,bins,right=True) 44 | 45 | return '\n'.join([ ''.join(map(lambda x: color(x,colors),row)) for row in digitized]) 46 | 47 | def color(x, colors): 48 | rgb = ';'.join([ str(int(c*255)) for c in colors[x]]) 49 | return "\x1b[38;2;" + rgb + 'm█\x1b[0m' 50 | 51 | 52 | def makewide(matrix): 53 | canvas = np.empty((matrix.shape[0], matrix.shape[1]*2)) 54 | for row in range(matrix.shape[0]): 55 | for column in range(matrix.shape[1]): 56 | canvas[row,column*2] = matrix[row,column] 57 | canvas[row,column*2+1] = matrix[row,column] 58 | return canvas 59 | 60 | def compress2d(m,dim=(10,10),operator = np.max): 61 | d0 = min(m.shape[0],dim[0]) 62 | d1 = min(m.shape[1],dim[1]) 63 | res = np.zeros((d0,d1)) 64 | ir = np.floor(np.linspace(0,m.shape[0],d0+1)).astype(int) 65 | jr = np.floor(np.linspace(0,m.shape[1],d1+1)).astype(int) 66 | for i in range(d0): 67 | for j in range(d1): 68 | res[i,j] = operator(m[ 69 | ir[i]:ir[i+1], 70 | jr[j]:jr[j+1] ]) 71 | return res 72 | 73 | if __name__ == "__main__": 74 | 75 | z=np.random.rand(10,10) 76 | heatmap(z) 77 | 78 | z=np.random.rand(111,111) 79 | heatmap(compress2d(z)) 80 | 81 | z=np.random.rand(15,15) 82 | heatmap(compress2d(z)) 83 | 84 | z=np.random.rand(14,1) 85 | heatmap(compress2d(z)) 86 | # USE THIS AND .columns to get size of the matrix :) 87 | #os.get_terminal_size().lines 88 | 89 | 90 | -------------------------------------------------------------------------------- /structout/intlist.py: -------------------------------------------------------------------------------- 1 | from scipy.sparse import csr_matrix as csr 2 | import os 3 | import numpy as np 4 | import math 5 | 6 | 7 | 8 | ''' 9 | taking care of int lists: 10 | 11 | 1. compress 12 | 2. log/bin/whatwver 13 | 3. color+numto symbol 14 | 4. add min/max 15 | 16 | then there is the numpy mode :) 17 | ''' 18 | 19 | ########################## 20 | ## COMPRESS -> horizontal 21 | ######################## 22 | 23 | def resize_number_array(values, desired_length,chunk_operation=max): 24 | length=len(values) 25 | if length <=desired_length: 26 | return values 27 | size= float(length)/desired_length 28 | values = [ chunk_operation(values[ int(i*size): int(math.ceil((i+1)*size)) ] ) for i in range(desired_length)] 29 | return values 30 | 31 | #################3 32 | # COMPRESS -> fit the value in a neat integer 33 | ############### 34 | 35 | def digitize(values, method = 'log', methodarg = 2, binminmax=(0,1)): 36 | if method == 'log': 37 | return [ int(math.log(i,methodarg)) for i in values] 38 | if method == 'bins': 39 | return bins(values,methodarg, minmax=binminmax) 40 | if method =='raw': 41 | return values 42 | 43 | 44 | 45 | def bins(values,count, minmax = (0,1)): 46 | mi, ma = minmax 47 | bins = np.arange(mi,ma+.0001,((ma+.0001)-mi)/(count)) 48 | bins[-1]+=.0001 49 | return np.digitize(values,bins)-1 50 | 51 | 52 | ################### 53 | # int to chr 54 | #################### 55 | 56 | 57 | def decorate(values): 58 | # there are 8 colors, so we distribute them over the space of used chars 59 | minmax= min(values),max(values) 60 | colors = bins(values,8,minmax) 61 | return map(colorize_number,values,colors) 62 | 63 | def colorize_number(number, col): 64 | '''http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python''' 65 | number = int(number) 66 | number = str(number) if number < 10 else chr(number+55) 67 | return '\x1b[1;3%d;48m%s\x1b[0m' % (col, number) 68 | 69 | 70 | 71 | ############3 72 | # letzgo 73 | ########### 74 | 75 | def str_to(n, dtype): 76 | if dtype == 'int': 77 | return str(n) 78 | if -10 < n < 10: 79 | return f"{n:.3}" 80 | else: 81 | return str(int(n)) 82 | 83 | 84 | 85 | def getcolumns(): 86 | try: 87 | return os.get_terminal_size().columns 88 | except: 89 | return 100 90 | 91 | def doALine(values, 92 | length=-1, 93 | minmax=False, 94 | ylim=False, 95 | chunk_operation=max, 96 | debug = False, 97 | method = 'auto', 98 | methodhow = 16 ): 99 | 100 | 101 | ############ 102 | # determine how to squish numbers :) 103 | ########### 104 | values = np.array(values) 105 | dtype = 'float' if 'float' in str(values.dtype) else 'int' 106 | if method == 'auto': 107 | if min(values) < 0 or dtype =='float': 108 | method = 'bins' 109 | elif max(values) >= methodhow: 110 | method = 'log' 111 | values +=1 112 | methodhow = 2 113 | else: 114 | method = 'raw' 115 | 116 | 117 | 118 | ############# 119 | # detetermine number of characters we need to squish the numbers into 120 | ############ 121 | if length < 0: 122 | length = getcolumns() 123 | vminmax = (values.min(),values.max()) if not ylim else ylim 124 | if minmax: 125 | pre = str_to(vminmax[0],dtype)+"|" 126 | post = "|"+str_to(vminmax[1],dtype) 127 | else: 128 | pre,post = '','' 129 | space = min(len(values), length-len(pre+post)) 130 | 131 | 132 | values = resize_number_array(values,space,chunk_operation) 133 | values = digitize(values,method,methodhow, binminmax=vminmax) 134 | values = decorate(values) 135 | 136 | return pre+''.join(values)+post 137 | 138 | 139 | def lprint(values,**kwargs): 140 | print (doALine(values,**kwargs)) 141 | 142 | 143 | def npprint(thing,shareylim=True, **kwargs): 144 | thing = csr(thing) 145 | if shareylim: 146 | kwargs['ylim'] = thing.min(), thing.max() 147 | 148 | for i in range(thing.shape[0]): 149 | a = thing.getrow(i).todense().getA1() 150 | lprint(a,**kwargs) 151 | 152 | 153 | 154 | if __name__ == "__main__": 155 | lprint(range(1000), method = 'auto') 156 | lprint(range(16), method ='auto') 157 | 158 | z=np.random.rand(2,300) 159 | npprint(z,minmax=True, method='bins',methodhow=16) 160 | z*=100 161 | npprint(z.astype(np.int64) ,minmax=True, method='bins',methodhow=16) 162 | 163 | 164 | 165 | 166 | 167 | 168 | ############# 169 | # legacy stuff for dictionaries... 170 | # i am rewriring this now and this is not a current usecase 171 | # the solution should be to use csr_sparse in the future 172 | ############# 173 | def dprint(posdict,length=100, chunk_operation=max): 174 | print (numberdict_to_str(posdict,length, chunk_operation=chunk_operation)) 175 | 176 | def access_region(d,start,end): 177 | return [ v for pos,v in d.items() if start<=pos<=end ] 178 | 179 | def resize_number_dict(posdict, desired_length,chunk_operation=max): 180 | ''' 181 | :param posdict: {pos:NUMBER, etc} 182 | :param desired_length: 183 | :return: 184 | ''' 185 | minn =min(posdict) 186 | maxx =max(posdict) 187 | length= maxx-minn 188 | size= float(length)/desired_length 189 | posdict = [chunk_operation([0]+access_region(posdict, int(i*size)+minn, int(math.ceil(i+1)*size+minn))) for i in range(desired_length)] 190 | return posdict 191 | 192 | 193 | def numberdict_to_str(ndict, dlength,chunk_operation=max): 194 | ret = resize_number_dict(ndict, desired_length=dlength,chunk_operation=chunk_operation) 195 | return "".join((decorate(ret))) 196 | 197 | 198 | ########3 199 | # old coloring stuff 200 | ######### 201 | 202 | colorscheme={ 203 | 0:0, 204 | 1:0, 205 | 2:4, 206 | 3:4, 207 | 4:1, 208 | 5:1, 209 | 6:3, 210 | 7:3, 211 | '.':0 212 | } 213 | def int_to_log2chr(i): 214 | ''' 215 | :param i: INTEGER 216 | :return: 217 | 0 -> . 218 | 1-9 -> 1-9 219 | 10+ -> A+ 220 | ''' 221 | if i < 1: 222 | return '.' 223 | else: 224 | z= int(math.log(i,2)) 225 | return z if z <=9 else chr(z+55) 226 | 227 | def decorate_number(num): 228 | ''' 229 | :param num: integer 230 | :return: 231 | string, that integer in COMPRESSED and colored 232 | ''' 233 | num =int_to_log2chr(num) 234 | return colorize_symbol (str(num), colorscheme.get(num, 8)) 235 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /structout/rnagraph.py: -------------------------------------------------------------------------------- 1 | from lmz import Map,Zip,Filter,Grouper,Range,Transpose,Flatten 2 | from collections import defaultdict 3 | import networkx as nx 4 | import structout as so 5 | import numpy as np 6 | 7 | 8 | 9 | 10 | def getvienna_pos(struct): 11 | '''alternative method... ''' 12 | import RNA 13 | rna_object = RNA.get_xy_coordinates(struct) 14 | pos = {i: (rna_object.get(i).X, rna_object.get(i).Y) 15 | for i in range(len(struct))} 16 | return pos 17 | 18 | 19 | def _sequence_dotbracket_to_graph(seq_info=None, seq_struct=None): 20 | """ 21 | Given a sequence and the dotbracket sequence make a graph. useful for testing. 22 | 23 | Parameters 24 | ---------- 25 | seq_info string 26 | node labels eg a sequence string 27 | seq_struct string 28 | dotbracket string 29 | Returns 30 | ------- 31 | returns a nx.Graph 32 | secondary struct associated with seq_struct 33 | """ 34 | graph = nx.Graph() 35 | lifo = defaultdict(list) 36 | open_brace_string={")":"(", 37 | "]":"[", 38 | ">":"<"} 39 | for i, (c, b) in enumerate(zip(seq_info, seq_struct)): 40 | graph.add_node(i, label=c, position=i) 41 | if i > 0: 42 | graph.add_edge(i, i - 1, label='-', type='backbone', len=1) 43 | if b in ['(','[','<']: 44 | lifo[b].append(i) 45 | if b in [')',']','>']: 46 | j = lifo[open_brace_string[b]].pop() 47 | graph.add_edge(i, j, label='=', type='basepair', len=1) 48 | return graph 49 | 50 | 51 | 52 | ######################## 53 | # setting up grammar and direction translations 54 | ####################### 55 | grammar = ''' 56 | start: ( L start R ) * 57 | L: /\(/ 58 | R: /\)/ 59 | 60 | %ignore "," 61 | ''' 62 | 63 | # if the current orientation is KEY, and we see a multiloop, first child goes to value[0], etc 64 | treesplit = { f'R':f"URD", 65 | f'U':f"LUR", 66 | f'D':f"LDR" } 67 | # if the current direction is key, direction of closing bracket is value 68 | close = dict("RD UR DL".split()) 69 | # reverse direction 70 | rev = dict("RL UD DU LR".split()) 71 | 72 | # move into direction 73 | def newpos(pos, d): 74 | if d == f'R': 75 | return pos[0]+1, pos[1] 76 | if d == f'U': 77 | return pos[0], pos[1]-1 78 | if d == f'D': 79 | return pos[0], pos[1]+1 80 | if d == f'L': 81 | return pos[0]-1, pos[1] 82 | else: 83 | assert False 84 | 85 | 86 | ################################### 87 | # make the possition dict... 88 | #################################### 89 | 90 | def getposdict(blob,d, pos, duty): 91 | ''' 92 | recursively the tree to get a posdict containing all nodes 93 | 94 | blob: is ( S ) ... so we can draw the brackets and the relevant company 95 | and check the ccardinality of S 96 | ''' 97 | r = {} 98 | 99 | ######################## 100 | # draw terminals in "...( S )..."; the grammar checks for brackets only, 101 | # but we just draw the unpaired nucleotides until then too... 102 | ######################### 103 | # adjust start possition... to accomodate left and right duty 104 | # breakpoint() 105 | leftlen = blob[0].start_pos - duty[0] 106 | rightlen = duty[1] - blob[2].start_pos 107 | diff = min(leftlen - rightlen +1 ,0) # i assume the +1 compensates for a leftlenrightlen offby one... 108 | for i in range(abs(diff)): 109 | pos = newpos(pos,d) 110 | 111 | # shit unpaired left 112 | for i in range(duty[0], blob[0].start_pos): 113 | pos = newpos(pos,d) 114 | r[i] = pos 115 | 116 | # shit bracket 117 | pos = newpos(pos,d) 118 | r[blob[0].start_pos] = pos 119 | # shit closing bracked 120 | waybackpos = newpos(pos,close[d]) 121 | r[blob[2].start_pos] = waybackpos 122 | 123 | # shit unpaired left 124 | for i in range(blob[2].start_pos+1,duty[1]): 125 | waybackpos = newpos(waybackpos,rev[d]) 126 | r[i] = waybackpos 127 | 128 | 129 | ############################# 130 | # here we take care of all nesting structures: stem, multiloop 131 | ########################### 132 | children = Grouper(blob[1].children,3) 133 | targets = treesplit[d] 134 | if len(children) == 1: 135 | r.update(getposdict(children[0],d,pos,(blob[0].start_pos+1, blob[2].start_pos))) 136 | 137 | elif len(children) == 2: 138 | r.update(getposdict(children[0],targets[0],pos,(blob[0].start_pos+1, children[1][0].start_pos))) 139 | r.update(getposdict(children[1],d,pos,(children[1][0].start_pos, blob[2].start_pos))) 140 | 141 | elif len(children) == 3: 142 | r.update(getposdict(children[0],targets[0],pos ,(blob[0].start_pos+1, children[1][0].start_pos))) 143 | r.update(getposdict(children[1],d,pos,(children[1][0].start_pos+1, children[2][0].start_pos))) 144 | r.update(getposdict(children[2],targets[2], newpos(newpos(pos,d),targets[2]), 145 | (children[2][0].start_pos, blob[2].start_pos))) 146 | else: 147 | ############ 148 | # unpaired nodes in hairpin, 149 | # originally i wanted to place them with graphviz but that looks shitty 150 | ############### 151 | pos = newpos(pos,d) 152 | #pos = newpos(pos,targets[0]) 153 | pos = newpos(pos,rev[close[d]]) 154 | for i in range(blob[0].start_pos+1,blob[2].start_pos): 155 | r [i] = pos 156 | pos = newpos(pos,close[d]) 157 | 158 | return r 159 | 160 | 161 | def RNAprint(g,structure = None, size = 1, springlayout_k = .3): 162 | ### 163 | # get structure annotation right: 164 | ######### 165 | stu = g.graph.get(f'structure', structure) 166 | if stu == None: 167 | assert False, f'no structure in {structure=} {g.graph}' 168 | def nani(ch): 169 | if ch in f'([<': 170 | return f'(' 171 | if ch in f')]>': 172 | return f')' 173 | return f',' 174 | stu = f''.join(Map(nani, stu)) 175 | 176 | #### 177 | # calculate a possition for every character in the strcuture 178 | ######## 179 | from lark import Lark 180 | p = Lark(grammar) 181 | z = p.parse(stu) 182 | # print(p.parse(stu).pretty()) 183 | posdict = getposdict(z.children,f'R',(0,0), (0, len(stu))) 184 | 185 | 186 | ##### 187 | # undocumented nodes get filled by spring layout: 188 | ######## 189 | pos = nx.drawing.spring_layout(g, pos =posdict ,fixed = posdict.keys(), k = springlayout_k) 190 | 191 | ########## 192 | # making sure the graph proportions are ok, and draw 193 | ############# 194 | a = np.array(list(pos.values())) 195 | xmin,ymin = a.min(axis = 0) 196 | xmax,ymax = a.max(axis = 0) 197 | xr = int(size*(xmax-xmin)) 198 | yr = int(size*(ymax-ymin)) 199 | 200 | so.gprint(g, pos = pos, size = (xr*4,yr*2) ) 201 | 202 | 203 | 204 | 205 | 206 | def test(): 207 | seq = 'GACUCGACCUAGCGAGUAUAAACAGGCUUUAGGCUAGGAGCGUGACCACUUCGGUGGUCGGUAGCA' 208 | stu = '((((,,,),,,(,,,))),,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,' 209 | seq = 'AAAAAAAAGGGGGGGGGGUUUUUUUUUUAAAAAAAAAAAA' 210 | stu = ',,,,((((,,((,,,)),,((,,,)),,((,,)))))),,' 211 | g = _sequence_dotbracket_to_graph(seq, stu) 212 | g.graph[f'structure'] = stu 213 | 214 | RNAprint(g) 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /structout/graph.py: -------------------------------------------------------------------------------- 1 | import math 2 | import networkx as nx 3 | from pprint import pprint 4 | from networkx.algorithms.shortest_paths.unweighted import _single_shortest_path_length as short_paths 5 | 6 | 7 | ######### 8 | # set labels and color them 9 | ######### 10 | 11 | def color(symbol, col='red', colordict={'black': 0, 'red': 1, 12 | 'green': 2, 13 | 'yellow': 3, 14 | 'blue': 4, 15 | 'cyan': 6, 16 | 'magenta': 5, 17 | 'gray': 7}): 18 | '''http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python''' 19 | return ['\x1b[1;3%d;48m%s\x1b[0m' % (colordict[col], e) for e in symbol] 20 | 21 | 22 | 23 | def set_print_symbol(g, colorstyle='normal', nodelabel='label', edgelabel='label'): 24 | ''' 25 | g.graph[xx] are settings for how the lines between nodes (or edge-labels) are drawn 26 | node/edege['asciisymbol'] is what is the label how it will be drawn 27 | ''' 28 | g.graph['generic edge'] = "." if colorstyle=='bw' else color('.', 'gray')[0] 29 | g.graph['digraphend'] = color('.', col='blue')[0] 30 | g.graph['colored'] = colorstyle!='bw' 31 | 32 | if isinstance(colorstyle,str): 33 | if colorstyle == "bw": # white 34 | 35 | for n, d in g.nodes(data=True): 36 | d['asciisymbol'] = str(d.get(nodelabel,n)) 37 | 38 | if edgelabel != None: 39 | for a,b,d in g.edges(data=True): 40 | if d.get(edgelabel,None): 41 | d['asciisymbol'] = d[edgelabel] 42 | 43 | else: # default color 44 | 45 | for n, d in g.nodes(data=True): 46 | d['asciisymbol'] = color( str(d.get(nodelabel, n)), 'red') 47 | 48 | if edgelabel != None: 49 | for a,b,d in g.edges(data=True): 50 | if d.get(edgelabel,None): 51 | d['asciisymbol'] = color(d[edgelabel], 'blue') 52 | 53 | else: # colorlists 54 | for nodes, col in zip (colorstyle, ["magenta", "cyan", "yellow", "red", "blue", "green"]): 55 | for n in nodes: 56 | g.nodes[n]['asciisymbol'] = color(str( g.nodes[n].get(nodelabel,n)), col) 57 | for n,d in g.nodes(data=True): 58 | if "asciisymbol" not in d: 59 | g.nodes[n]['asciisymbol'] = color( str( g.nodes[n].get(nodelabel,n)), 'black') 60 | return g 61 | 62 | 63 | #### 64 | # graph to ascii canvas 65 | ### 66 | 67 | 68 | def transform_coordinates(pos,ymax,xmax): 69 | weird_maxx = max([x for (x, y) in pos.values()]) 70 | weird_minx = min([x for (x, y) in pos.values()]) 71 | weird_maxy = max([y for (x, y) in pos.values()]) 72 | weird_miny = min([y for (x, y) in pos.values()]) 73 | 74 | xfac = (float((weird_maxx - weird_minx)) / xmax )or 1 75 | yfac = (float((weird_maxy - weird_miny)) / ymax )or 1 76 | for key in pos.keys(): 77 | wx, wy = pos[key] 78 | pos[key] = (int((wx - weird_minx) / xfac), int((wy - weird_miny) / yfac)) 79 | #pos["debug_%d" % key] = [wx,xfac,weird_minx,weird_maxx, wy,yfac,weird_miny, weird_maxy] 80 | return pos 81 | 82 | 83 | def nx_to_ascii(graph, 84 | size=10, 85 | debug=None, 86 | pos=None): 87 | ''' 88 | debug would be a path to the folder where we write the dot file. 89 | in: nxgraph, (see set print symbol for special fields) 90 | out: a string 91 | ''' 92 | 93 | 94 | # set up canvas 95 | if isinstance(size,int): 96 | ymax = size 97 | xmax = ymax * 2 98 | else: 99 | xmax,ymax = size 100 | canvas = [list(' ' * (xmax + 1)) for i in range(ymax + 1)] 101 | 102 | # layout 103 | if not pos: 104 | #pos = nx.graphviz_layout(graph, prog='neato', args="-Gratio='2'") 105 | #pos=nx.drawing.nx_agraph.graphviz_layout(graph, prog='neato', args="-Gratio='2'") 106 | pos=nx.spring_layout(graph) 107 | 108 | pos= transform_coordinates(pos,ymax,xmax) 109 | 110 | 111 | # draw nodes 112 | def write_on_canvas(x,y,text, nooverwrite=False): 113 | for e in text: 114 | if nooverwrite and canvas[y][x] != ' ': 115 | break 116 | canvas[y][x] = e 117 | if x < xmax: 118 | x += 1 119 | else: 120 | break 121 | 122 | for n, d in graph.nodes(data=True): 123 | x, y = pos[n] 124 | write_on_canvas(x,y,d['asciisymbol']) 125 | 126 | 127 | # draw edges 128 | for a, b,d in graph.edges(data=True): 129 | 130 | ax, ay = pos[a] 131 | bx, by = pos[b] 132 | 133 | #edgelabel 134 | if d.get('asciisymbol',None) != None: 135 | write_on_canvas( (ax+bx)//2 , (ay+by)//2 ,d['asciisymbol'], nooverwrite=True) 136 | 137 | resolution = max(3, int(math.sqrt((ax - bx) ** 2 + (ay - by) ** 2))) 138 | dx = float((bx - ax)) / resolution 139 | dy = float((by - ay)) / resolution 140 | lastwritten_edge = None 141 | for step in range(resolution): 142 | x = int(ax + dx * step) 143 | y = int(ay + dy * step) 144 | if canvas[y][x] == ' ': 145 | canvas[y][x] = graph.graph['generic edge'] #"." if colorstyle=='bw' else color('.', 'black')[0] 146 | lastwritten_edge=(y,x) 147 | 148 | if lastwritten_edge and graph.graph.get('colored',False) and type(graph)==nx.DiGraph: 149 | canvas[lastwritten_edge[0]][lastwritten_edge[1]] = graph.graph['digraphend'] 150 | 151 | 152 | canvas = '\n'.join([''.join(e) for e in canvas]) 153 | if debug: 154 | path = "%s/%s.dot" % (debug, hash(graph)) 155 | canvas += "\nwriting graph:%s" % path 156 | nx.write_dot(graph, path) 157 | 158 | return canvas 159 | 160 | 161 | 162 | 163 | ####### 164 | # main printers 165 | ####### 166 | 167 | def make_picture(g, 168 | color="normal", 169 | nodelabel='label', 170 | edgelabel='label', 171 | size=10, 172 | debug=None, 173 | pos=None, 174 | zoomlevel = 4, 175 | zoomnodes = [], 176 | n_graphs_per_line= 5): 177 | 178 | 179 | 180 | # everything musst be lists: 181 | if type(g) != list: 182 | g = [g] 183 | color = [color] 184 | zoomnodes= [zoomnodes] 185 | else: 186 | # g is already a list 187 | if type(color) !=list: 188 | color = [color]*len(g) 189 | if len(zoomnodes) == 0: 190 | zoomnodes= [[]]*len(g) 191 | else: 192 | print("zoomnodes not supported for multiple graphs") 193 | 194 | 195 | 196 | 197 | # ZOOM 198 | g = list(map( lambda gr, no: do_zoom(gr,zoomlevel,no) ,g,zoomnodes)) 199 | 200 | # set colors 201 | g = list(map(lambda x, col: set_print_symbol(x, colorstyle=col, nodelabel=nodelabel, edgelabel=edgelabel), g, color)) 202 | 203 | # make picture 204 | g = map(lambda x: nx_to_ascii(x, size=size, debug=debug, pos=pos), g) 205 | 206 | # group pictures into rows 207 | return makerows(list(g), n_graphs_per_line=n_graphs_per_line) 208 | 209 | 210 | def do_zoom(gr,zoomlevel, no): 211 | if not no: 212 | return gr 213 | oklist = [a for (a, b) in short_paths(gr,no, zoomlevel)] 214 | return gr.subgraph(oklist) 215 | 216 | ################################# 217 | # down here is utility stuff 218 | ################################# 219 | 220 | def makerows(graph_canvazes, n_graphs_per_line=5): 221 | 222 | allrows = '' 223 | while graph_canvazes: 224 | current = graph_canvazes[:n_graphs_per_line] 225 | g = map(lambda x: x.split("\n"), current) 226 | g = zip(*g) #transpose(g) 227 | res = '' 228 | for row in g: 229 | res += " ".join(row) + '\n' 230 | allrows+=res 231 | graph_canvazes = graph_canvazes[n_graphs_per_line:] 232 | 233 | return allrows 234 | 235 | def gprint(g, **kwargs): 236 | print(make_picture(g, **kwargs)) 237 | 238 | def ginfo(g): 239 | 240 | for n,d in g.nodes(data=True): 241 | d.pop('asciisymbol',None) 242 | print (n,) 243 | pprint (d) 244 | for a,b,d in g.edges(data=True): 245 | d.pop('asciisymbol',None) 246 | print (a,b,) 247 | pprint (d) 248 | 249 | 250 | # test 251 | if __name__ == "__main__": 252 | # simple graph without labels or anything 253 | graph = nx.path_graph(5) 254 | gprint(graph) 255 | 256 | # adding some labels 257 | graph[3][4]['label']='brot' 258 | graph.nodes[0]['label']='null' 259 | gprint(graph) 260 | 261 | # grouping nodes for coloring .. 262 | gprint(graph, color=([1,2,3],[4,0])) 263 | ginfo(graph) 264 | gprint([graph,graph,graph]) 265 | 266 | ''' 267 | getting coordinates of molecules... the molecule thing should be in the eden package afair 268 | import molecule 269 | chem=molecule.nx_to_rdkit(graph) 270 | m.GetConformer().GetAtomPosition(0) 271 | transform coordinates 272 | ''' 273 | -------------------------------------------------------------------------------- /structout/intlistV2.py: -------------------------------------------------------------------------------- 1 | from scipy.sparse import csr_matrix as csr 2 | import os 3 | import numpy as np 4 | import math 5 | 6 | """ 7 | rewriting to have better code.. 8 | does not have all the features of intlist yet i think... 9 | """ 10 | def doALine(values, log = False, chunkF = max, 11 | showrange=True, symbols = '▁▂▃▄▅▆▇█', colors = '0467', 12 | ylim = False, characterlimit = 99999): 13 | ''' 14 | ylim should be in htere in case we print many lines 15 | ''' 16 | # how many digits can we use? 17 | values = np.array(values) 18 | pre, post, space = determine_characterlimit(values, characterlimit, showrange=showrange) 19 | values = horizontalsquish(values, space, chunkF) 20 | if log: 21 | values = np.log2(values) 22 | # -> discretize so we have a low number of symbols 23 | values = binning(values, count = len(symbols)*len(colors), ylim=ylim) 24 | symbols = decorate(values, symbols, colors) 25 | return pre+''.join(symbols)+post 26 | 27 | 28 | ########################## 29 | def determine_characterlimit(values, characterlimit, showrange=True, ignore_val_len =False): 30 | ''' 31 | ignore_val_len: when we print a sequence, it makes sense to limit the output chars, on sparse data we should not 32 | return: 33 | pre and post strings and how many characters we can actually use for output 34 | ''' 35 | if showrange: 36 | pre = str_to(values.min())+"|" 37 | post = "|"+str_to(values.max()) 38 | else: 39 | pre = '' 40 | post='' 41 | maxlength = getcolumns()-len(pre+post) 42 | characterlimit -= len(pre+post) 43 | 44 | space = min(maxlength, len(values)) if not ignore_val_len else maxlength 45 | return pre, post, space 46 | 47 | 48 | 49 | def str_to(num: float) -> str: 50 | """ 51 | copy pasta seems to work... 52 | Expresses a float in a string of exactly 5 characters. 53 | 54 | The function finds the best representation (integer, decimal, or scientific) 55 | that is at most 5 characters long, and then pads it with spaces to 56 | ensure the final string is exactly 5 characters. 57 | 58 | Args: 59 | num: The float number to express. 60 | 61 | Returns: 62 | A string representation of the number, 5 characters long. 63 | """ 64 | # This variable will hold the generated string before padding 65 | result = "" 66 | 67 | # 1. Handle special cases first 68 | if math.isnan(num): 69 | result = "nan" 70 | elif math.isinf(num): 71 | result = "inf" if num > 0 else "-inf" 72 | elif num == 0: 73 | result = "0" 74 | else: 75 | # This variable will be set once a suitable representation is found 76 | found_rep = None 77 | 78 | # 2. Try simple integer representation 79 | if num == int(num): 80 | s_int = str(int(num)) 81 | if len(s_int) <= 5: 82 | found_rep = s_int 83 | 84 | # 3. Try fixed-point decimal notation (if integer didn't work) 85 | if found_rep is None: 86 | for precision in range(4, -1, -1): 87 | s_float = f"{num:.{precision}f}" 88 | if -1 < num < 1: 89 | s_float = s_float.lstrip('0') if num > 0 else "-" + s_float[2:] 90 | 91 | if len(s_float) <= 5: 92 | found_rep = s_float.rstrip('.') 93 | break # Exit loop once a representation is found 94 | 95 | # 4. Fallback to scientific notation 96 | if found_rep is None: 97 | for precision in range(2, -1, -1): 98 | s_sci = f"{num:.{precision}e}".replace('e+', 'e') 99 | if len(s_sci) <= 5: 100 | found_rep = s_sci 101 | break 102 | 103 | # 5. Absolute last resort 104 | if found_rep is None: 105 | s_sci_final = f"{num:.0e}".replace('e+', 'e') 106 | found_rep = s_sci_final[:5] 107 | 108 | result = found_rep 109 | 110 | # Pad with spaces if the length is smaller than 5 111 | return result.ljust(5) 112 | 113 | 114 | 115 | def str_to_old(n): 116 | # return f"{n:.5g}" 117 | #if isinstance(n,int): 118 | 119 | if type(n) == int: 120 | return str(n) 121 | if -10 < n < 10: 122 | return f"{n:.3f}" 123 | if -100 < n < 100: 124 | return f"{n:.2f}" 125 | if -1000 < n < 1000: 126 | return f"{n:.1f}" 127 | else: 128 | return f"{n:.0f}" 129 | 130 | 131 | def getcolumns(): 132 | try: 133 | return os.get_terminal_size().columns 134 | except: 135 | return 100 136 | 137 | 138 | ########################## 139 | def horizontalsquish(values, desired_length,chunk_operation=max): 140 | length=len(values) 141 | if length <=desired_length: 142 | return values 143 | size= float(length)/desired_length 144 | values = [ chunk_operation(values[ int(i*size): int(math.ceil((i+1)*size)) ] ) for i in range(desired_length)] 145 | return np.array(values) 146 | 147 | def binning(values,count, ylim): 148 | #mi, ma = ylim if isinstance(ylim, tuple) else (values.min(), values.max()) 149 | mi, ma = ylim or (values.min(), values.max()) 150 | bins = np.arange(mi,ma+.0001,((ma+.0001)-mi)/(count)) 151 | bins[-1]+=.0001 152 | return np.digitize(values,bins)-1 153 | 154 | 155 | 156 | def decorate(values, symbols, colors): 157 | allcolors = [colorize(s,c) for s in symbols for c in colors] 158 | return ''.join([allcolors[i] for i in values]) 159 | 160 | def colorize(chr, col): 161 | ''' 162 | http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python 163 | https://pypi.org/project/colorama/ 164 | ''' 165 | #number = str(number) if number < 10 else chr(number+55) 166 | if type(col) == str: 167 | return '\x1b[1;3%s;48m%s\x1b[0m' % (col, chr) 168 | 169 | # 24 bit 170 | else: 171 | r,g,b = [ str(int(c*255)) for c in col] 172 | 173 | #print(r,b,g) 174 | 175 | return "\x1b[38;2;" + r + ";" + g + ";" + b + "m" + chr + '\x1b[0m' 176 | 177 | raise Exception("IMPOSSIBLE") 178 | 179 | def lprint(values,**kwargs): 180 | print (doALine(values,**kwargs)) 181 | 182 | def plot(x,y=False): 183 | if isinstance(y,bool): 184 | lprint(x) 185 | else: 186 | scatter(x,y) 187 | 188 | 189 | def npprint(thing,shareylim=True, **kwargs): 190 | thing = csr(thing) 191 | if shareylim: 192 | kwargs['ylim'] = thing.min(), thing.max() 193 | for i in range(thing.shape[0]): 194 | a = thing.getrow(i).todense().getA1() 195 | lprint(a,**kwargs) 196 | 197 | def iprint(dic:dict,bins = 1000,spacemin=False, spacemax=False, **kwargs): # indiscrete print 198 | keys = np.array(list(dic.keys())) 199 | spacemin = spacemin or min(keys) 200 | spacemax = spacemax or max(keys) 201 | discrete = np.digitize(keys, bins=np.linspace(spacemin, spacemax, bins ) ) 202 | 203 | base = [min(dic.values())]*(bins+1) 204 | for k,e in zip(keys,discrete): 205 | base[e]=dic[k] 206 | lprint(base, **kwargs) 207 | 208 | 209 | if __name__ == "__main__": 210 | lprint(range(1000)) 211 | lprint(range(1000), log = True) 212 | z=np.random.rand(2,300) 213 | npprint(z) 214 | z*=100 215 | npprint(z.astype(np.int64)) 216 | npprint(z.astype(np.int64), log=True) 217 | d={0:3, 0.3:6,4:3, 21.4:0.2} 218 | iprint(d) 219 | 220 | ############# 221 | # legacy stuff for dictionaries... 222 | # i am rewriring this now and this is not a current usecase 223 | # the solution should be to use csr_sparse in the future 224 | ############# 225 | def dprint(posdict,length=100, chunk_operation=max): 226 | print (numberdict_to_str(posdict,length, chunk_operation=chunk_operation)) 227 | 228 | def access_region(d,start,end): 229 | return [ v for pos,v in d.items() if start<=pos<=end ] 230 | 231 | def resize_number_dict(posdict, desired_length,chunk_operation=max): 232 | ''' 233 | :param posdict: {pos:NUMBER, etc} 234 | :param desired_length: 235 | :return: 236 | ''' 237 | minn =min(posdict) 238 | maxx =max(posdict) 239 | length= maxx-minn 240 | size= float(length)/desired_length 241 | posdict = [chunk_operation([0]+access_region(posdict, int(i*size)+minn, int(math.ceil(i+1)*size+minn))) for i in range(desired_length)] 242 | return posdict 243 | 244 | 245 | def numberdict_to_str(ndict, dlength,chunk_operation=max): 246 | ret = resize_number_dict(ndict, desired_length=dlength,chunk_operation=chunk_operation) 247 | return "".join((decorate(ret))) 248 | 249 | 250 | 251 | 252 | def scatter(x,y, xlim=(), ylim=(),rows =2, columns = 14): 253 | ''' 254 | - braille will make the core plot. 255 | - we add colored xlim left and right bottom 256 | - we add colored ylim left and right top 257 | ''' 258 | 259 | xlim = xlim or (np.min(x),np.max(x)) 260 | ylim = ylim or (np.min(y), np.max(y)) 261 | xlim=np.array(xlim) 262 | ylim=np.array(ylim) 263 | prex, postx, spacex = determine_characterlimit(xlim, 0000,ignore_val_len=True) 264 | prey, posty, spacey = determine_characterlimit(ylim, 0000,ignore_val_len=True) 265 | maxl = lambda x,y: max(len(x),len(y)) 266 | prelen = maxl(prex, prey) 267 | postlen = maxl(postx, posty) 268 | spacelen = len(prex+postx)+spacex - prelen - postlen 269 | spacelen = columns or spacelen 270 | 271 | chars = plot_braille(x,y,cols = spacelen,rows = rows,xlim= xlim,ylim=ylim) 272 | 273 | for i,row in enumerate(chars): 274 | # first take care of padding: 275 | pre,post = '','' 276 | if i ==0: 277 | pre,post = prey, posty 278 | if i == len(chars)-1: 279 | pre,post = prex, postx 280 | pre = pre.ljust(prelen) 281 | post = post.ljust(postlen) 282 | 283 | if i ==0: 284 | pre, post = colorize(pre,'4'), colorize(post,'4') 285 | if i == len(chars)-1: 286 | pre, post = colorize(pre,'6'), colorize(post,'6') 287 | 288 | print(pre+row+post) 289 | 290 | 291 | 292 | # 1 4 293 | # 2 5 294 | # 3 6 295 | # 7 8 296 | DOT_POS = { 297 | (0, 0): 0, (0, 1): 1, (0, 2): 2, (0, 3): 6, 298 | (1, 0): 3, (1, 1): 4, (1, 2): 5, (1, 3): 7 299 | } 300 | def plot_braille(x, y, rows=20, cols=40, xlim=(),ylim=()): 301 | if len(x) != len(y): 302 | raise ValueError("x and y must be the same length") 303 | 304 | if len(ylim)==0: 305 | ylim = np.array((min(y), max(y))) 306 | 307 | if len(xlim)==0: 308 | xlim = np.array((min(x), max(x))) 309 | # Scale data into pixel coordinates (cols*2 wide, rows*4 tall) 310 | x = np.asarray(x) 311 | y = -np.asarray(y) 312 | ylim = -ylim[::-1] 313 | 314 | # 2x4 pixel grid per Braille character 315 | width_px = cols * 2 316 | height_px = rows * 4 317 | x_bins = np.linspace(*xlim, width_px + 1) 318 | y_bins = np.linspace(*ylim, height_px + 1) 319 | 320 | x_idx = np.digitize(x, x_bins) - 1 321 | y_idx = np.digitize(y, y_bins) - 1 322 | 323 | # Clamp to grid bounds 324 | x_idx = np.clip(x_idx, 0, width_px - 1) 325 | y_idx = np.clip(y_idx, 0, height_px - 1) 326 | 327 | 328 | # Initialize Braille canvas 329 | canvas = np.zeros((rows, cols), dtype=np.uint8) 330 | 331 | for xi, yi in zip(x_idx, y_idx): 332 | char_col = xi // 2 333 | char_row = yi // 4 334 | dot_col = xi % 2 335 | dot_row = yi % 4 336 | 337 | dot_bit = DOT_POS[(dot_col, dot_row)] 338 | canvas[char_row, char_col] |= (1 << dot_bit) 339 | 340 | chars = ["".join(chr(0x2800 + cell) if cell else ' ' for cell in row) for row in canvas] 341 | return chars 342 | 343 | 344 | 345 | 346 | 347 | ########3 348 | # old coloring stuff 349 | ######### 350 | colorscheme={ 351 | 0:0, 352 | 1:0, 353 | 2:4, 354 | 3:4, 355 | 4:1, 356 | 5:1, 357 | 6:3, 358 | 7:3, 359 | '.':0 360 | } 361 | def int_to_log2chr(i): 362 | ''' 363 | :param i: INTEGER 364 | :return: 365 | 0 -> . 366 | 1-9 -> 1-9 367 | 10+ -> A+ 368 | ''' 369 | if i < 1: 370 | return '.' 371 | else: 372 | z= int(math.log(i,2)) 373 | return z if z <=9 else chr(z+55) 374 | 375 | def decorate_number(num): 376 | ''' 377 | :param num: integer 378 | :return: 379 | string, that integer in COMPRESSED and colored 380 | ''' 381 | num =int_to_log2chr(num) 382 | return colorize_symbol (str(num), colorscheme.get(num, 8)) 383 | 384 | 385 | 386 | --------------------------------------------------------------------------------