MapInfo Pro

 View Only
Expand all | Collapse all

Mapbasic to add Thematic Legend in one frame

  • 1.  Mapbasic to add Thematic Legend in one frame

    Posted 07-27-2020 08:32
    I am trying to work out how to add multiple thematic legends into a frame using mapbasic.

    Create Designer Legend From Window 3015 Portrait Default Frame Style "#" Font ("Arial",0,8,0) Frame From Layer 1 Frame From Layer 4

    Puts all the listed layers in but to the side of the map and I can't control where this should be and what the background should be as each theme is a separate element

    .
    If I add a frame first then just the first layer in the list is in the frame but the others aren't.

    So
    Create Frame (8.9717,0.5885) (11.2499, 3.6736) Name "No Content"
    Add Designer Frame Window 3021 Frame From Layer 13 Title "" Into Id 11 Frame From Layer 32 Title "Water Mains Status" Frame From Layer 35 Title "Water Mains Status"


    I have tried to put Into Id 11 after each Frame From Layer xx but that doesn't work.

    What is the 64 bit mapbasic to get every layer into the same frame? It is possible in 32bit but the way the print layouts are handled is different so that code doesn't work.


    Thanks


    ------------------------------
    George Corea
    Mangoesmapping
    ------------------------------


  • 2.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-03-2020 07:28
    Hi George

    The new layout works differently than the old one. Think of a legend window when dealing with legends.

    Each legend needs to go into its own frame. You can insert the first legend into an existing frame and then add additional frames below this one. So it's a bit more cumbersome than you might think. Let me try to create an example for you. I'll be back

    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------



  • 3.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-03-2020 08:29
    Hi George

    You can specify the position using the Create Designer Legend statement but you need to do this for the individual frames, not for the overall Legend.

    Here's an example:
    Create Designer Legend Custom
      Position (2.0,2.0) Units "in"
      Width 2.8 Units "in" Height 5 Units "in"
      Frame From Layer 2 Position (2.0, 2.0) Units "in"
      Frame From Layer 1 Position (2.0, 3.0) Units "in"

    The first Position is ignored as that's used when you create it as a stand-alone window. It's the Positon element for each of the Frame From Layer parts that control where the frame will be placed.

    Also, note that you need to include the Custom keyword at the beginning for MapInfo Pro to use the positions of the individual frames.

    I'd suggest that you only insert the legend from one layer at a time and then inspect the size of that frame. You can now calculate the location of the next frame to insert.


    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------



  • 4.  RE: Mapbasic to add Thematic Legend in one frame

    Posted 08-05-2020 02:35
    Thanks Peter,

    That seems to work if we know the layers in the map but not if we don't and try to build the list.

    For example in the following code I get  

    This seems to be because both the Add Designer Frame Window and the Create Designer Legend Custom don't allow a for statement.

    Include "MapBasic.Def"
    
    DECLARE SUB Main
    
    SUB Main
    	Dim num_windows as integer
    	num_windows = NumWindows()
    	print (num_windows)
    	Dim i,map_windowid,legend_windowid,map_layer_count as integer
    	Dim info_name, info_type, info_windowid as string
    	For i = 1 to num_windows
    	    print ("............")
    	    print (i)
    	info_name = (WindowInfo(i, WIN_INFO_NAME))
    	info_type = (WindowInfo(i, WIN_INFO_TYPE))
    	info_windowid = (WindowInfo(i, WIN_INFO_WINDOWID))
    '	print (info_name)
    '	print (info_type)
    '	print (info_windowid)
    	if info_type="35" then
    		print ("legend window")
    		legend_windowid=int(info_windowid)
    	elseif info_type="1" then
    		print ("map window")
    		map_windowid=int(info_windowid)
    		map_layer_count=MapperInfo(info_windowid, MAPPER_INFO_LAYERS)
    '		print (map_layer_count)
    		'LayerListInfo(map_window_id, numeric_counter, attribute )
    	end if
    	next
    	print ("Collected layer/map info")
    	'OPTIONS for building legend.
    	'Add Designer Frame Window legend_windowid
    		'For i = 1 to  map_layer_count
    		'	Frame From Layer i
    		'next
    	'// This statement works to put the legend in the layout if you don't have the for statement
    	'Add Designer Frame Window legend_windowid Frame From Layer 1 Frame From Layer 3 Frame From Layer 4 Frame From Layer 5 Frame From Layer 6
    	
    	'Create Designer Legend Custom
    		'Position (2.0,2.0) Units "in"
    		'Width 2.8 Units "in" Height 5 Units "in"
    		'For i = 1 to  map_layer_count
    			'print (i)
    			'Frame From Layer i Position (2.0, 2.0) Units "in"
    	'// This statement works if you don't have the for statement
    	'Create Designer Legend Custom Position (2.0,2.0) Units "in" Width 2.8 Units "in" Height 5 Units "in" Frame From Layer 1 Position (2.0, 3.0) Units "in"
    		'next
    
    End Sub​




    ------------------------------
    George Corea
    Mangoesmapping
    ------------------------------



  • 5.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-05-2020 03:48
    Hi George

    I get a bit confused by your example as you have multiple For statements in the code. I guess most of them have been disabled, right?

    Can you try something along these lines:

    SUB Main
    Dim num_windows as integer
    num_windows = NumWindows()
    print (num_windows)
    Dim i,map_windowid,legend_windowid,map_layer_count as integer
    Dim info_name, info_type, info_windowid as string
    For i = 1 to num_windows
       print ("............")
       print (i)
       info_name = (WindowInfo(i, WIN_INFO_NAME))
       info_type = (WindowInfo(i, WIN_INFO_TYPE))
       info_windowid = (WindowInfo(i, WIN_INFO_WINDOWID))
       ' print (info_name)
       ' print (info_type)
       ' print (info_windowid)
       if info_type="35" then
          print ("legend window")
          legend_windowid=int(info_windowid)
       elseif info_type="1" then
          print ("map window")
          map_windowid=int(info_windowid)
          map_layer_count=MapperInfo(info_windowid, MAPPER_INFO_LAYERS)
          ' print (map_layer_count)
          'LayerListInfo(map_window_id, numeric_counter, attribute )
       '**This is where we build the legend in the layout 
       '**I'm just adding a static 2 inces to the position for each layer
       '**- that needs to depend on the size of the previous frame
        For nLayer = 1 to map_layer_count
             Create Designer Legend Custom
                'Position (2.0,2.0) Units "in"
                'Width 2.8 Units "in" Height 5 Units "in"
                Frame From Layer i Position (2.0 + ((i-1) * 2), 2.0) Units "in"
          Next  'nLayer
     
       end if
    next
    print ("Collected layer/map info")

    End Sub​

    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------



  • 6.  RE: Mapbasic to add Thematic Legend in one frame

    Posted 08-05-2020 08:18
    Thanks. It runs but it seems to just create some frames in the Legend Designer with no info in them.

    The output I am looking for is
    and then being able to control where this is placed in the layout. So something like this with a rectangle behind the legend elements.
    I've attached the wor and mb for this. The data is just the sample data from the python examples.

    Regards,

    ------------------------------
    George Corea
    Mangoesmapping
    ------------------------------

    Attachment(s)

    txt
    Print_Layout.MB.txt   1 KB 1 version
    txt
    print_test.WOR.txt   12 KB 1 version


  • 7.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-07-2020 02:18
      |   view attached
    Hi George

    I needed to get my hands dirty to really understand what's going on and give you a solution.
    I think I have found something that you can work with now.

    Here's how the legend looks in the layout with my solution

    I modified your code a bit to find the map from a specific frame in the layout. You can modify this if you already know which frame contains the map or if you have gotten the ID of the map in a different way.

    I get the size and the position of the frame with map to place my legend frames inside the map frame. I chose the easy solution and used the top left corner. If you want the lower right corner, you'll have to create all the frames, figure out the maximum width and total height of the frames, and then calculate how to fit them into the corner and finally use the Alter Designer Frame statement to move all the legend frames.

    For the first layer, you create a legend for, use the Create Designer Legend. Now get the legend window ID from this frame using this expression: 
    legend_windowid = LayoutItemInfo(layout_windowid, LayoutInfo(layout_windowid, LAYOUT_INFO_NUM_ITEMS), LAYOUT_ITEM_INFO_LEGEND_DESIGNER_WINDOW)

    For the remaining layers, use Add Designer Frame and refer to the legend window ID retrieved above.

    Using the maximum width and total height of the legend frames, you can finally create a new frame with a rectangle using the Create Rect statement. You can use the Priority keyword to control the draw order.

    I have included my working solution below.

    Let me know how this works out for you

    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------

    Attachment(s)

    txt
    LayoutAndLegends.mb.txt   2 KB 1 version


  • 8.  RE: Mapbasic to add Thematic Legend in one frame

    Posted 08-10-2020 01:42
      |   view attached
    Thanks. That works really well.

    I am having an issue with creating the rectangle behind the legend items and above the map. 
    I've tried to get the Top Left (TL) and Bottom Right (BR) values for this box but the width doesn't work out correctly. 
    		legend_width = LayoutItemInfo(layout_windowid, LayoutInfo(layout_windowid, LAYOUT_INFO_NUM_ITEMS), LAYOUT_ITEM_INFO_WIDTH)
    		if legend_width_max <= legend_width then
    			legend_width_max = LayoutItemInfo(layout_windowid, LayoutInfo(layout_windowid, LAYOUT_INFO_NUM_ITEMS), LAYOUT_ITEM_INFO_WIDTH)
    			print ("Updated max: "&legend_width_max)
    		End if

     The idea would be to get the max value from a list of all the widths and use this to set the X at the bottom right. I get the max value but it is not long enough.

    Create Rect (fFrameX, fFrameY) (fFrameX+legend_width_max,fFramePositionY​

    Full code is attached.

    ------------------------------
    George Corea
    Mangoesmapping
    ------------------------------

    Attachment(s)

    txt
    Print_Layout.MB.txt   3 KB 1 version


  • 9.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-10-2020 06:21
      |   view attached
    Hi

    I tried using your structure and it seems to work. 

    I did simplify the way you assign values to the max variable by using the Maximum function:
    legend_width = LayoutItemInfo(layout_windowid, LayoutInfo(layout_windowid, LAYOUT_INFO_NUM_ITEMS), LAYOUT_ITEM_INFO_WIDTH) legend_width_max
    = Maximum(legend_width_max, legend_width)
    print "Max Legend width:  " & legend_width_max


    Full example has been attached



    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------

    Attachment(s)

    txt
    LayoutAndLegends2.mb.txt   3 KB 1 version


  • 10.  RE: Mapbasic to add Thematic Legend in one frame

    Posted 08-11-2020 00:00
    Thanks again Peter.

    I am getting the following

    and also

    I am using your v2 code unedited. 

    Also is there a reason the Layout has to be active? can it be made active if it isn't?

    Regards,


    ------------------------------
    George Corea
    Mangoesmapping
    ------------------------------



  • 11.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-11-2020 02:02
    Looking at your example makes me wonder if we need to run thru the legend frames after all have been created? Maybe the placement of the label for the legend element changes when we move from points to Lines/Regions as they are wider.

    When creating the legend, we need to capture and remember the number of the first legend frame. Once all have been created we loop thru the frames again starting with the captured number until the total number of elements on the layout. Here we calculate the maximum width of the frames.

    And no, the Layout doesn't have to be the active window. There are multiple ways to get to the window you want to use. Maybe you create a new window? If you know there's only one open layout window, you can loop through the open windows until you find the layout. Or maybe you create the layout using a template saved in a workspace.

    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Pitney Bowes Software & Data
    ------------------------------



  • 12.  RE: Mapbasic to add Thematic Legend in one frame

    Posted 08-18-2020 22:25
    Thanks Peter.  For the real use case I  will be setting the width manually so it should be fine.

    When I try this on more complex data I get the following error. Is there something we can do to allow it to handle more complex symbology?


    This is using StreetPro (AUS_NSW_CLASSIC_022020) demo data from https://data.precisely.com/samples and the supplied nsw_streetpro.wor file in Mapinfo 19.2

    Regards,

    ------------------------------
    George Corea
    Mangoesmapping
    ------------------------------



  • 13.  RE: Mapbasic to add Thematic Legend in one frame

    Employee
    Posted 08-19-2020 02:56
    Hi George

    You run into an issue here because the symbol for the layer is "invisible". How would you represent that in a legend :-)
    You might have to inspect each layer before trying to add its legend and check whether it's using an invisible style.

    For symbols, this would mean the shape is 31 (and for TrueType fonts also 32?).
    For pens, the pattern is 1.
    And for brushes, the pattern is 1.

    You can use the LayerInfo function to query if the display mode is global:
    If LayerInfo(nMapWindowID, nLayer, LAYER_INFO_DISPLAY) = LAYER_INFO_DISPLAY_GLOBAL Then

    And also use the LayerInfo function to get to the override styles:
    LayerInfo(nMapWindowID, nLayer, LAYER_INFO_OVR_LINE) ' Linear Pen
    LayerInfo(nMapWindowID, nLayer, LAYER_INFO_OVR_SYMBOL) ' Symbol
    LayerInfo(nMapWindowID, nLayer, LAYER_INFO_OVR_PEN) ' Region Border Line
    LayerInfo(nMapWindowID, nLayer, LAYER_INFO_OVR_BRUSH) ' Region Fill

    You might also have to inspect the predominant object types in the layer. You can do this using the TableInfo function:
    TableInfo(sTableName, TAB_INFO_DOMINANT_OBJECT_TYPE)


    ------------------------------
    Peter Horsbøll Møller
    Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------