Interactively show polygon label on choropleth map

I need to map a polygon shapefile in a choropleth map and interactively show the name of the specific polygon on which I click or hoover with the mouse. The default data tool tip does not work for me because: (1) information gets displayed for each polygon vertex, while I need the information only when clicking or hoovering on the inner surface of the polygon; (2) apparently, the only information it can show are coordinates.
To give an idea of what I am after, I produce hereby a minimum working example. In the last line of this example, I add all polygon names via the text command, associating each polygon to a unique inner point. This determines the placement of the text. What I need, instead, is for such text to be displayed only for the single polygon on which I click or hoover with the mouse.
Any help would be greatly appreciated.
% Minimum working example
% pgn = shaperead('pgn.shp');
% pts = shaperead('pts.shp');
load('pgn.mat')
load('pts.mat')
colorgradient = parula(height(pgn));
cbarlim = [floor(min([pgn.MA1911])) ceil(max([pgn.MA1911]))];
symbpgn = makesymbolspec('Polygon', ...
{'MA1911', cbarlim, 'FaceColor', colorgradient});
mapshow(pgn,'SymbolSpec',symbpgn);
disableDefaultInteractivity(gca) % This prevents polygon vertex coordinates from showing in the tooltip
ax = gca;
ax.CLim = cbarlim;
h = colorbar;
h.Label.String = 'people';
axis equal
axis off
hold on
text([pts.X]',[pts.Y]',{pts.UNIT}') % This is what needs to become interactive

Answers (2)

Hi @UMG,

I went through your comments, so for interactive behavior in your choropleth map using MATLAB, you can utilize the `WindowButtonDownFcn` property of the figure to capture mouse click events.

https://www.mathworks.com/help/matlab/creating_plots/button-down-callback-function.html

https://www.mathworks.com/matlabcentral/answers/458264-using-windowbuttondownfcn-in-app-designer

Here’s a step-by-step guide to implement this functionality:

1. Set Up Your Figure for Interaction: You need to configure your figure to respond to mouse clicks. This involves setting up a callback function that will be executed when a click event occurs.

2. Determine Click Location: When you click on the map, you can retrieve the click location and check if it falls within any polygon.

3. Display Polygon Information: If the click is within a polygon, you can then display the associated information (e.g., name of the polygon) at that location.

Here’s an enhanced version of your MWE that incorporates these steps:

   % Load data
   load('/MATLAB Drive/pts.mat');
   load('/MATLAB Drive/pgn.mat');
   % Create a figure for the map
   figure;
   colorgradient = parula(height(pgn));
   cbarlim = [floor(min([pgn.MA1911])) ceil(max([pgn.MA1911]))];
   symbpgn = makesymbolspec('Polygon', ...
    {'MA1911', cbarlim, 'FaceColor', colorgradient});
  mapshow(pgn,'SymbolSpec',symbpgn);
   disableDefaultInteractivity(gca); % Prevent default tooltip
   % Set axis properties
   ax = gca;
   ax.CLim = cbarlim;
   h = colorbar;
   h.Label.String = 'people';
   axis equal;
   axis off;
   hold on;
   % Plot text labels for polygons at specific points
   text([pts.X]', [pts.Y]', {pts.UNIT}', 'Visible', 'off'); % Initially hidden
   % Set up callback for mouse clicks
   set(gcf, 'WindowButtonDownFcn', @(src, event) displayPolygonName(src,    
   event, pgn, pts));
   function displayPolygonName(~, ~, pgn, pts)
    % Get current point clicked
    cp = get(gca, 'CurrentPoint');
    xClick = cp(1, 1);
    yClick = cp(1, 2);
    % Loop through each polygon to check if click is inside
    for i = 1:length(pgn)
        % Create a polygon object from vertices
        polyShape = polyshape(pgn(i).BoundingBox(:, 1), pgn(i).BoundingBox(:, 2));
        % Check if point is inside polygon
        if inpolygon(xClick, yClick, polyShape.Vertices(:, 1), polyShape.Vertices(:, 
       2))
            % Display corresponding text for clicked polygon
            text(pts(i).X, pts(i).Y, pts(i).UNIT, 'Color', 'red', 'FontSize', 12); 
            break; % Exit loop after finding first match
        end
    end
  end

Please note that makesymbolspec requires Mapping Toolbox. I don’t have access to this toolbox because utilizing ”matlab mobile” version For more information on this function, please click the link below.

https://www.mathworks.com/help/map/ref/makesymbolspec.html

Also, some additional tips to help you further.

Performance Considerations: If your dataset contains a large number of polygons or points, consider implementing spatial indexing techniques (like using `k-d trees` or `R-trees`) to improve the efficiency of point-in-polygon queries.

Dynamic Text Visibility: The above implementation shows text upon clicking but does not hide previous text. You might want to implement logic to clear previously displayed names or manage visibility based on subsequent clicks.

User Experience Enhancements: To further improve usability, consider adding features like highlighting polygons upon hover or using different colors for polygons based on their attributes.

By following this approach, you will be able to create an interactive choropleth map that meets your requirements while providing users with a clear and engaging experience.

Hope this helps.

8 Comments

Thanks for your answer. Your code does not work for me. Specifically, I receive an error for:
set(gcf, 'WindowButtonDownFcn', @(src, event) displayPolygonName(src, event, pgn, pts));
The error I receive is "Invalid expression. When calling a function or indexing a variable, use parentheses. Otherwise, check for mismatched delimiters".
But besides this specific problem, the solution you propose would be very cumbersome for me, as I need to plot many figures with many polygons. Frankly, I was hoping there would me something much simpler that I just didn't know about.
Would it be simpler if, instead of mouse clicking, the information about the polygon was shown just on hoovering upon it? I have updated my question to clarify that also hoovering would be fine.

Hi @UMG,

it seems the error arises from the way the function is being called. When I execute the code, it displayed a message for not having toolbox installed, if I had access to toolbox, I could help you out further. However, after going through your recent request, I understand your need for a more efficient solution, especially when dealing with multiple figures and polygons. Instead of using mouse clicks, you can utilize the WindowButtonMotionFcn property to display information when hovering over polygons. Here’s a simplified approach:

   % Sample data for polygons
   x = [1 2 3 1];
   y = [1 3 2 1];
   p = fill(x, y, 'b'); % Create a polygon
   % Set the WindowButtonMotionFcn to detect mouse movement
   set(gcf, 'WindowButtonMotionFcn', @(src, event)    displayPolygonInfo(src, p));
   function displayPolygonInfo(src, polygon)
    % Get current mouse position
    mousePos = get(gca, 'CurrentPoint');
    xMouse = mousePos(1, 1);
    yMouse = mousePos(1, 2);
    % Check if mouse is inside the polygon
    if inpolygon(xMouse, yMouse, polygon.XData, polygon.YData)
        disp('You are hovering over the polygon!');
    end
  end

Please see attached.

So, in the above code, the displayPolygonInfo function checks if the mouse is over the polygon and displays a message accordingly. This method is more efficient for multiple polygons and enhances user interaction without the need for clicks. Adjust the logic as necessary to suit your specific requirements.

Let me know if this helps.

I can't get it to work. Specifically, I get the following error printed in the console:
Error in @(src,event)displayPolygonInfo(src,pgn)
Error while evaluating Figure WindowButtonMotionFcn.
Error using inpolygon
Too many input arguments.
Error in displayPolygonInfo (line 7)
if inpolygon(xMouse, yMouse, polygon.X, polygon.Y)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error in @(src,event)displayPolygonInfo(src,pgn)
Error while evaluating Figure WindowButtonMotionFcn.
Generally, I would find it very useful if you could write the code to fit the example I've used in my question. I don't think I have the expertise to adapt the code you wrote to my specific needs.
Sorry!

Hi @UMG,

I know it is frustrating when you are trying to implement code and it does not seem to work well. Could you please let me know if the recent code provided by me executed properly in MATLAB as it is without making modifications on your end. Because my recent code did execute without any errors. I will wait for your response.

Hi @Umar,
I confirm that your last code works. But like I said, I am unable to apply it onto a proper map using mapshow, like in my example, so that for each polygon the name changes according to the data.
Thanks for your time.

Hi @UMG,

I have revised the code based on my comments without makesymbolspec since this requires toolbox that I still don’t have access to as mentioned in my previous comments. However, to effectively utilize the provided MATLAB code, it is essential to ensure that the polygon data structure (pgn) contains a properly defined 'Vertices' field because after going through some trial and error test, I realized that the ‘Vertices' field is crucial as it holds the coordinates of the vertices that define each polygon. Below, I will detail how to define this field, update the code accordingly, and explain the final results.

Step 1: Defining the Vertices Field

The 'Vertices' field should be defined as a two-column matrix where each row corresponds to a vertex of the polygon. The first column represents the x-coordinates, and the second column represents the y-coordinates. Here’s an example of how to define the 'Vertices' field for a simple polygon:

   % Example of defining polygon data
   pgn(1).Vertices = [1, 1; 2, 3; 3, 1]; % Triangle
   pgn(2).Vertices = [4, 1; 5, 4; 6, 1]; % Another triangle

In this example, pgn(1) represents a triangle with vertices at (1,1), (2,3), and (3,1), while pgn(2) represents another triangle.

Step 2: Updating the Code

Note: once you copy this code, make sure to format it properly, so it does execute. With the 'Vertices' field defined, the provided code can be executed without issues. Below is the complete code with the defined vertices included:

   % Load data
   load('/MATLAB Drive/pgn.mat'); % Load polygon data
   load('/MATLAB Drive/pts.mat'); % Load point data
   % Example of defining polygon data if not loaded
   pgn(1).Vertices = [1, 1; 2, 3; 3, 1]; % Triangle
   pgn(2).Vertices = [4, 1; 5, 4; 6, 1]; % Another triangle
   % Create a figure for the map
   figure;
   hold on;
   % Define color gradient manually
   numPolygons = length(pgn); % Use length to get the number of polygons
   colorGradient = parula(numPolygons); % Generate a color gradient
% Plot each polygon
 for i = 1:numPolygons
  % Check if the 'Vertices' field exists
  if isfield(pgn(i), 'Vertices') && ~isempty(pgn(i).Vertices)
      % Extract polygon vertices
      vertices = pgn(i).Vertices; 
        % Plot the polygon with a specific color
        fill(vertices(:,1), vertices(:,2), colorGradient(i,:), 
        'EdgeColor', 'k');
    else
        warning('Polygon %d does not have a valid "Vertices" field.', 
        i);
    end
  end
% Set axis properties
axis equal;
axis off;
   % Plot text labels for polygons at specific points (initially hidden)
   textHandles = text([pts.X]', [pts.Y]', {pts.UNIT}', 'Visible', 'off',    'Color', 'red','FontSize', 12);
   % Set up callback for mouse motion
   set(gcf, 'WindowButtonMotionFcn', @(src, event) 
   displayPolygonInfo(src, pgn, 
   pts, textHandles));
function displayPolygonInfo(~, pgn, pts, textHandles)
  % Get current mouse position
  mousePos = get(gca, 'CurrentPoint');
  xMouse = mousePos(1, 1);
  yMouse = mousePos(1, 2);
    % Loop through each polygon to check if mouse is inside
    for i = 1:length(pgn)
        % Check if the 'Vertices' field exists and is not empty
        if isfield(pgn(i), 'Vertices') && ~isempty(pgn(i).Vertices)
            % Create a polygon shape object from vertices
            vertices = pgn(i).Vertices; 
            % Check if point is inside polygon
            if inpolygon(xMouse, yMouse, vertices(:, 1), vertices(:, 2))
                % Show corresponding text for hovered polygon
                set(textHandles(i), 'Visible', 'on'); 
                % Hide others
                for j = 1:length(pgn)
                    if j ~= i
                        set(textHandles(j), 'Visible', 'off');
                    end
                end
                return; % Exit after finding first match
            end
        end
      end
    % If not hovering over any polygon, hide all text
    set(textHandles, 'Visible', 'off');
  end

Please see attached.

After implementing the above code with the defined 'Vertices' field, the following results can be expected, however, bear in mind that this code is not using makesymbolspec which requires toolbox. If you want to use this function in code, then you have to modify the provided code based on your needs.

1. Polygon Visualization: Each polygon will be displayed on the figure with a unique color from the generated color gradient.

2. Interactive Hovering: When the mouse hovers over a polygon, the corresponding text label will appear, providing information about that polygon.

3. Dynamic Feedback: The text labels will dynamically show and hide based on the mouse position, enhancing user interaction.

This implementation not only fulfills the requirement of defining the 'Vertices' field but also ensures a smooth and interactive experience for you to analyze the polygon data.

Hope this should help resolve your problem now.

UMG
UMG on 16 Jul 2025
Edited: UMG on 16 Jul 2025
Dear @Umar, thanks for your reply. Unfortunately, the code you provided does not compile, even using the simple definition of pgn you provided. Besides that, the approach you propose is rather involved relative to how simple this kind of task is. Last but not least, I need this to work with mapshow, as explicitly stated from the very beginning.
As a side note, it is very surprising that MatLab does not have a simple native way to carry out this basic task.

Hi @UMG,

I understand that you're looking for a more streamlined solution that integrates directly with `mapshow` for visualizing polygons on maps. It seems that you are having trouble on your end and looking for a simple solution, so based on your latest comments, I have simplified the approach while ensuring compatibility with your requirements. Here is Step-by-Step Solution

Step 1: Define Polygon Data Structure

Instead of manually defining the vertices as previously suggested, we can create a structure suitable for use with `mapshow`. Here’s how you can define your polygons:

% Example of defining polygon data for mapshow
pgn(1).Vertices = [1, 1; 2, 3; 3, 1]; % Triangle
pgn(2).Vertices = [4, 1; 5, 4; 6, 1]; % Another triangle
% Convert to map shape format required by mapshow
for i = 1:length(pgn)
  pgn(i).ShapeType = 'Polygon';
end

Step 2: Utilize `mapshow`

You can then use `mapshow` to display these polygons. This function is designed for handling geographic data and can be used as follows:

% Create a new figure
figure;
% Use mapshow to plot each polygon
for i = 1:length(pgn)
  if isfield(pgn(i), 'Vertices') && ~isempty(pgn(i).Vertices)
      mapshow(pgn(i).Vertices(:,1), pgn(i).Vertices(:,2), 
    'DisplayType', 'polygon','FaceColor', rand(1,3));
  else
      warning('Polygon %d does not have a valid "Vertices" field.', 
      i);
  end
end
% Set axis properties
axis equal;
axis off;

To add interactivity similar to what you described earlier (hovering to display information), you might need to handle mouse events differently since `mapshow` does not support dynamic text labels out of the box. However, here’s a basic way to implement it which was provided in my previous code and I will repeat the same process as mentioned in my previous comments.

 set(gcf, 'WindowButtonMotionFcn', @(src, event) displayPolygonInfo(src, pgn));
function displayPolygonInfo(~, pgn)
  mousePos = get(gca, 'CurrentPoint');
  xMouse = mousePos(1, 1);
  yMouse = mousePos(1, 2);
    for i = 1:length(pgn)
        vertices = pgn(i).Vertices;
        if inpolygon(xMouse, yMouse, vertices(:,1), vertices(:,2))
            disp(['Hovering over Polygon ', num2str(i)]);
            return;
        end
    end
  end

After implementing this simplified code, the expected results should be

1. Polygon Visualization: Each polygon will be displayed using `mapshow`, automatically managing geographic context.

2. Simplified Code: The approach is more straightforward and tailored to your needs without unnecessary complexity.

3. Interactive Feedback: You will receive console output indicating which polygon is being hovered over.

This final solution should align better with your requirements and provide the simplicity you're looking for while utilizing MATLAB's mapping capabilities effectively.

Sign in to comment.

As of R2025a, you can add Data Tips to polygons.
GT = readgeotable("pgn.shp");
newmap(GT.Shape.ProjectedCRS)
h = geoplot(GT,"ColorVariable","MA1911");
h.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("Unit",GT.UNIT);
cbar = colorbar;
cbar.Label.String = 'people';

Categories

Products

Release

R2024a

Asked:

UMG
on 15 Jul 2025

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!