omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    [Share] a list of rects distributed around 360 degrees

    Pythonista
    circle rects share 360
    5
    44
    29496
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • cvp
      cvp last edited by cvp

      Why pos = cos,sin or pos = sin,cos
      to better understand x,y coordinates with radius,angle

      Phuket2 1 Reply Last reply Reply Quote 1
      • Phuket2
        Phuket2 @cvp last edited by Phuket2

        @cvp , thanks the below code is working now. I can't be sure everything I reported above was correct. Is possible I had some mis matched Params 😂 I still the the code below is cool. That one function just makes it so easy to put/draw objects in a circular shape, many interfaces could require this....well maybe. I just wanted to get that function working, I didn't try to make the class generic. Maybe I am dreaming. But thanks again for your help and diagrams. I was still 14 when I left school and I was a trouble maker. So, it's difficult to understand your diagrams, just because I have no real foundation. I was also looking at wiki for radians, it has some great diagrams and animations, hmmm but still it doesn't sink in. But I am going to try to find a math tutor. But it will not be easy in Thailand. We're I live and language barrier.
        But again thanks....

        
        '''
            Pythonista Forum - @Phuket2
        '''
        
        import calendar
        import editor
        import math
        import ui
        
        # example, playing around, for 12 items its ok no math :)
        _range_12 = (.3, .34, .38, .42, .46, .5, .55, .6, .63, .7, .85, 1.0)
        
        def css_clr_to_rgba(css_name, a):
        	c = ui.parse_color(css_name)
        	return (c[0], c[1], c[2], a)
        	
        def rects_on_circle_path(rect_path, obj_width, margin=2, num_objs=12):
        	def calculate_a_rect(i):
        		a = 2 * math.pi * i/num_objs - math.pi/2
        		# careful: cos,sin! not sin,cos
        		pos = (math.cos(a)*(radius*1), math.sin(a)*(radius*1)) 
        		r1 = ui.Rect(*pos, obj_width, obj_width)
        		r1.x += r.width / 2 - obj_width / 2 + r.x
        		r1.y += r.height / 2 - obj_width / 2 + r.y
        		
        		return r1
        		
        	r = ui.Rect(*rect_path).inset(obj_width / 2 + margin,
        	obj_width / 2 + margin)
        	radius = r.width / 2
        	return r, [calculate_a_rect(i) for i in range(num_objs)]
        	
        	
        def make_button(idx, title):
        	def button_action(sender):
        		print('Button {} was pressed.'.format(sender.title))
        		
        	#btn = ui.Button(title=calendar.month_abbr[i+1])
        	btn = ui.Button(title=title)
        	btn.action = button_action
        	#btn.alpha = _range_12[idx]
        	btn.border_width = .5
        	btn.bg_color = 'white'
        	btn.text_color = btn.tint_color = 'black'
        	return btn
        	
        class MyClass(ui.View):
        	# some ideas
        	_list=['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' ]
        	#_list=['N', 'E' , 'S' , 'W']
        	#_list=['1st', '2nd', '3rd', '4th', '5th']
        	#_list=['0', '90', '180', '270' ]
        	#_list= [str(d) for d in range(0, 12)]
        	_list = [calendar.month_abbr[i] for i in range(1,12)]
        	
        	def __init__(self, *args, **kwargs):
        		super().__init__(*args, **kwargs)
        		self.cir_rect = None
        		self.make_view()
        	def make_view(self):
        		for i in range(len(self._list)):
        			self.add_subview(make_button(i, title=self._list[i]))
        			
        	def layout(self):
        		r, rects = rects_on_circle_path(self.bounds, obj_width=70, 								margin=20, num_objs=len(self._list))
        		self.cir_rect = r
        		for i, btn in enumerate(self.subviews):
        			btn.frame = rects[i]
        			btn.title = self._list[i]
        			btn.corner_radius = btn.width / 2
        		
        	def draw(self):
        		s = ui.Path.oval(*self.cir_rect)
        		with ui.GState():
        			ui.set_color(css_clr_to_rgba('lime', .4))
        			s.line_width = 1
        			s.stroke()
        			
        if __name__ == '__main__':
        	_use_theme = True
        	w = h = 500
        	f = (0, 0, w, h)
        	mc = MyClass(frame=f, bg_color='white', name='Silly Demo')
        	
        	if not _use_theme:
        		mc.present('sheet', animated=False)
        	else:
        		editor.present_themed(mc, theme_name='Solarized Dark', style='sheet',
        		animated=False)
        
        1 Reply Last reply Reply Quote 0
        • cvp
          cvp last edited by

          You're welcome for any mathematic/geometric question, but only if you accept an answer in a very poor English 🙄

          Phuket2 1 Reply Last reply Reply Quote 1
          • Phuket2
            Phuket2 @cvp last edited by

            @cvp , not trying to be smart. But there is nothing wrong with your English

            1 Reply Last reply Reply Quote 0
            • abcabc
              abcabc last edited by

              @Phuket2 I have implemented the circulartextlayout module to support circular layout. It is a slight modification of the textlayout module. Now the rows in layout text represents circular rings. Columns represent angular positions (Columns are not used for size calculations . Only rows are used for size calculations.) I hope it is useful. There is no need to calculate positions and size. The following git repository contains the code and examples:
              https://github.com/balachandrana/textlayout
              You can do a git pull if you have already got this.

              In the following example, the month buttons are displayed in the outer ring and the images (imageview) are displayed
              in the inner ring.

              import circulartextlayout
              import ui
              
              
              layout_text = '''
              ************
              ************
              bbbbbbbbbbbb
              ************
              i*i*i*i*i*i*
              ************
              ************
              '''
              
              image_list = [ ui.Image.named(i) for i in 'Rabbit_Face Mouse_Face Cat_Face Dog_Face Octopus Cow_Face'.split()]
              _range_12 = (.3, .34, .38, .42, .46, .5, .55, .6, .63, .7, .85, 1.0)
              
              def button_action(sender):
                  print('Button {} was pressed.'.format(sender.title))
                      
              titles = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
              
              attributes = {'b': [{'action':button_action, 'font' :('Helvetica', 20),
                                   'bg_color':'orange', 'alpha':_range_12[i],
                                   'border_width':.5, 'text_color':'black', 'tint_color':'black',
                                   'title':j } for i, j in enumerate(titles)],
                           'i': [{'image':i,  'bg_color':'gray'} for i in image_list ]                  
                           }
                                     
              v = circulartextlayout.BuildView(layout_text, width=600, height=600, view_name='Counter',
                  attributes=attributes).build_view()
                  
              for i in range(1, len(titles)+1):
                  v['button'+str(i)].corner_radius = v['button'+str(i)].width*.5
              for i in range(1, len(image_list)+1):
                  v['imageview'+str(i)].corner_radius = v['imageview'+str(i)].width*.5
              v.present('popover')
              
              

              The screeshot for this example is given below:
              http://imgur.com/a/WUwc5

              I have also included an example (circular_directions.py) that is similar to your direction example.

              1 Reply Last reply Reply Quote 0
              • cvp
                cvp last edited by cvp

                If you want an alpha property to your button, but independent of the list length, you can do

                btn = make_button(i, title=self._list[i])
                btn.alpha = (1+i) * (1/len(self._list)) # will go from 1/n to 1.0, where n = Len of list
                self.add_subview(btn)
                

                Thus, no need of _range_12

                1 Reply Last reply Reply Quote 0
                • abcabc
                  abcabc last edited by

                  Just coped the @ccc's code and wanted to set the attributes as in @ccc's code.

                  Phuket2 2 Replies Last reply Reply Quote 0
                  • cvp
                    cvp last edited by

                    @abcabc you're right but this code was only valuable for 12 items, not for 8, for instance in the code for N,....,E,.....,S,....W,..., or for any range

                    1 Reply Last reply Reply Quote 0
                    • abcabc
                      abcabc last edited by

                      Yes. You are right. I will update the code as suggested by you. Thanks for the suggestion.

                      1 Reply Last reply Reply Quote 0
                      • Phuket2
                        Phuket2 @abcabc last edited by

                        @abcabc , @cvp , _range_12 is ok. Just a list of 12 numbers between 0.0 and 1.0. But purposely not done linearly. That's why I put values into the list. To do it without constants you need some math. You can't start a zero or even .1 or even .2 for that matter, the resulting alpha is too faint. Also depending on what you want to do, the progression normally will look better if its not linear.

                        1 Reply Last reply Reply Quote 0
                        • Phuket2
                          Phuket2 @abcabc last edited by Phuket2

                          @abcabc , ran your code. Works well. But you are not centered in the view. Maybe there is a param I can't see. But on my iPad Pro 12 inch I get the below.

                          Edit: your x,y are off. Too much added somewhere

                          1 Reply Last reply Reply Quote 0
                          • Phuket2
                            Phuket2 last edited by

                            Sorry a little out of sync with some of my comments. I am not sure it's just me or not, but I have had a hard time replying the last 40 mins or so.

                            1 Reply Last reply Reply Quote 0
                            • abcabc
                              abcabc last edited by

                              @Phuket2 There could be some problem with centering. I will look into that. Anyway I have to do some more testing.

                              1 Reply Last reply Reply Quote 0
                              • Phuket2
                                Phuket2 last edited by

                                Below is a pretty nice adaptation of using the shared function here, after it's been fixed up 😱

                                But for use in this function or not, the get_rotated_icon function is nice or let's say functional. Simple function, code comes from help from omz on a similar subject. Probably not for 60fps stuff, but I think good for ui stuff.

                                import editor
                                import math
                                import ui
                                
                                # this is a pretty funky function...
                                def get_rotated_icon(named_icon_name, wh = 32, degree = 0):
                                	'''
                                		help from @omz
                                		https://forum.omz-software.com/topic/3180/understanding-ui-transform-rotation
                                	'''
                                	r = ui.Rect(0, 0, wh, wh)
                                	img = ui.Image.named(named_icon_name)
                                	with ui.ImageContext(wh, wh) as ctx:
                                		ui.concat_ctm(ui.Transform.translation(*r.center()))
                                		ui.concat_ctm(ui.Transform.rotation(math.radians(degree)))
                                		ui.concat_ctm(ui.Transform.translation(*r.center() * -1))
                                		img.draw()
                                		return ctx.get_image()
                                		
                                def make_button(idx, title, name = None):
                                	def button_action(sender):
                                		print('Button {} was pressed.'.format(sender.name))
                                		
                                	#btn = ui.Button(title=calendar.month_abbr[i+1])
                                	name = name if name else title
                                	btn = ui.Button(name = name, title=title )
                                	btn.action = button_action
                                	#btn.alpha = _range_12[idx]
                                	btn.border_width = .5
                                	btn.bg_color = 'white'
                                	btn.text_color = btn.tint_color = 'black'
                                	return btn
                                	
                                def css_clr_to_rgba(css_name, a):
                                	c = ui.parse_color(css_name)
                                	return (c[0], c[1], c[2], a)	
                                	
                                def rects_on_circle_path(rect_path, obj_width, margin=2, num_objs=12):
                                	def calculate_a_rect(i):
                                		a = 2 * math.pi * i/num_objs - math.pi/2
                                		# careful: cos,sin! not sin,cos
                                		pos = (math.cos(a)*(radius*1), math.sin(a)*(radius*1)) 
                                		r1 = ui.Rect(*pos, obj_width, obj_width)
                                		r1.x += r.width / 2 - obj_width / 2 + r.x
                                		r1.y += r.height / 2 - obj_width / 2 + r.y
                                		
                                		return r1
                                		
                                	r = ui.Rect(*rect_path).inset(obj_width / 2 + margin,
                                	obj_width / 2 + margin)
                                	radius = r.width / 2
                                	return r, [calculate_a_rect(i) for i in range(num_objs)]
                                
                                class MyClass(ui.View):
                                	# some ideas
                                	_list=['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' ]
                                	
                                	def __init__(self, *args, **kwargs):
                                		super().__init__(*args, **kwargs)
                                		self.cir_rect = None
                                		self.obj_list = []
                                		self.mid_btn = None
                                		
                                		self.make_view()
                                		
                                	def make_view(self):
                                		for i in range(len(self._list)):
                                			obj = make_button(i, title=self._list[i])
                                			obj.image = get_rotated_icon('iob:arrow_up_a_256', wh = 256, degree = i * 45)
                                			self.obj_list.append(obj)
                                			self.add_subview(obj)
                                		
                                		btn = make_button(i, title='C')
                                		self.mid_btn = btn
                                		self.add_subview(btn)
                                		
                                			
                                	def layout(self):
                                		r, rects = rects_on_circle_path(self.bounds, obj_width=70, 								margin=20, num_objs=len(self._list))
                                		self.cir_rect = r
                                		for i, btn in enumerate(self.obj_list):
                                			btn.frame = rects[i]
                                			btn.title = ''
                                			btn.corner_radius = btn.width / 2
                                		
                                		btn = self.mid_btn
                                		btn.center = r.center()
                                		btn.corner_radius = btn.width / 2
                                		
                                	def draw(self):
                                		# just to see the path when testing...
                                		s = ui.Path.oval(*self.cir_rect)
                                		with ui.GState():
                                			ui.set_color(css_clr_to_rgba('lime', .4))
                                			s.line_width = 1
                                			s.stroke()
                                	
                                	
                                if __name__ == '__main__':
                                	_use_theme = True
                                	w=h = 600
                                	f = (0, 0, w, h)
                                	
                                	mc = MyClass(frame=f, bg_color='white')
                                	
                                	if not _use_theme:
                                		mc.present('sheet', animated=False)
                                	else:
                                		editor.present_themed(mc, theme_name='Oceanic', style='sheet', animated=False)
                                				
                                

                                1 Reply Last reply Reply Quote 0
                                • JonB
                                  JonB last edited by

                                  FWIW,
                                  https://gist.github.com/6fc02b7d75eb22111b826cfdb2394697
                                  is an example using ui.Transform to simulate your last imagE. Probably not as useful, but compact in this case.

                                  Phuket2 1 Reply Last reply Reply Quote 0
                                  • abcabc
                                    abcabc last edited by

                                    @JonB very nice. I have changed your code slightly to do a "semi-circular layout" which seems to be used in some of the ios applications.

                                    import ui, calendar
                                    from math import pi, sin, radians
                                    
                                    def make_button(i, v, N):
                                        def button_action(sender):
                                            print('Button {} was pressed.'.format(sender.title))
                                    
                                        btn = ui.Button(title=calendar.month_abbr[i+1])
                                        btn.action = button_action
                                        btn.height=btn.width=64
                                        btn.alpha = sin(radians(15.+75.0/(N-1)*i))
                                        btn.border_width = .5
                                        btn.corner_radius = btn.width *.5
                                        btn.bg_color = 'orange'
                                        btn.text_color = btn.tint_color = 'black'
                                        center_x, center_y = v.bounds.center()
                                        btn.center = (btn.width/2+5.0, center_y) #v.bounds.center()
                                        btn.transform=ui.Transform.translation(0,-(v.height/2-btn.height/2)
                                                            ).concat(ui.Transform.rotation(2.*pi*i/N))
                                        return btn
                                        
                                    v=ui.View(frame=(0,0,576,576))
                                    v.bg_color=(1,1,1)
                                    N = 12
                                    N1 = 7
                                    for i in range(0,N1):
                                        v.add_subview(make_button(i, v, N))
                                    v.present('sheet')
                                    
                                    1 Reply Last reply Reply Quote 0
                                    • Phuket2
                                      Phuket2 last edited by

                                      Nice guys. As painfully as obvious as it is I have never thought of rotating the icons. After I did my compass test, I thought I would try it with arrows, could be used as a type of controller etc. then I was looking for the arrows offset by 45 degrees. Then it was like a big steel ball dropped from a great height on to my head. But also handy as you are only dealing with one image_name. I don't think speed would be ever a problem. Also simple to write out to PNG files if required. Anyway, I find it amazing how many things are sitting right under my nose I don't see

                                      1 Reply Last reply Reply Quote 0
                                      • Phuket2
                                        Phuket2 @JonB last edited by

                                        @JonB , yeah your approach a lot more straight fwd. to be honest I didn't think about rotating the button. But I was also thinking about drawing into the view vrs using buttons. But in this case, no reason not just to use the buttons. Many ways to skin the cat. But I will hold on to your code also, has its own versatility 😬

                                        1 Reply Last reply Reply Quote 0
                                        • JonB
                                          JonB last edited by

                                          For what its worth, ui.concat_ctm along lets you use ui.Transforms inside ImageContexts.
                                          This is perhaps a convolouted example, showing how you can use transforms rather than trying to do math for this sort of thing (in this case the sort of draggable lollipop hour selector). In the end, a little math is needed for the touch handling.
                                          https://gist.github.com/84489b13a17cdd46288f16b50b2f7bc3

                                          Phuket2 1 Reply Last reply Reply Quote 0
                                          • abcabc
                                            abcabc last edited by

                                            Thanks @JonB for sharing this code. FWIW, I have modified this code to make a circular slider.

                                            import ui
                                            from math import pi,atan2
                                            
                                            class CircularSlider(ui.View):
                                                def __init__(self,*args,**kwargs):
                                                    ui.View.__init__(self,*args,**kwargs)
                                                    self.a = 0
                                                    self.value = (self.a+pi)/(2*pi)
                                                    
                                                def draw(self):
                                                    scl=min(self.width,self.height)
                                                    self.scl=scl
                                                    btn_siz=min(22/scl,0.05)
                                                    #work in normalized units
                                                    ui.concat_ctm(ui.Transform.scale(scl,scl))
                                                    #origin at center
                                                    ui.concat_ctm(ui.Transform.translation(.5,.5))
                                                    ui.set_color('#1aa1b5')
                                                    o = ui.Path.oval(-.5+btn_siz, -.5+btn_siz, 1-2*btn_siz, 1-2*btn_siz)
                                                    o.line_width=2/scl
                                                    o.stroke()
                                                    #rotate by angle
                                                    ui.concat_ctm(ui.Transform.rotation(self.a))
                                                    # center origin at button
                                                    ui.concat_ctm(ui.Transform.translation(.5-btn_siz,0))
                                                    #optional: to keep images upright
                                                    #ui.concat_ctm(ui.Transform.rotation(-self.a))
                                                    p=ui.Path.oval(-btn_siz,-btn_siz,2*btn_siz,2*btn_siz)
                                                    p.fill()
                                            
                                                def touch_moved(self,touch):
                                                    dp=touch.location-touch.prev_location
                                                    self.a=atan2(touch.location.y-self.scl/2.,touch.location.x-self.scl/2.)
                                                    self.value = (self.a+pi)/(2*pi)
                                                    self.set_needs_display()
                                                    
                                                def touch_ended(self, touch):
                                                    print(self.value)
                                            
                                            d = CircularSlider(frame=(0,0,500, 500),bg_color='white')
                                            d.present('sheet')
                                            
                                            Phuket2 1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            Powered by NodeBB Forums | Contributors