<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: View.takeScreenShot does not include scalebar in ArcGIS JavaScript Maps SDK Questions</title>
    <link>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1639811#M87485</link>
    <description>&lt;P&gt;&lt;SPAN class=""&gt;Hello everyone,&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;For anyone trying to draw custom elements like a scale bar or a North arrow onto a screenshot taken with &lt;/SPAN&gt;&lt;SPAN class=""&gt;view.takeScreenshot()&lt;/SPAN&gt;&lt;SPAN class=""&gt;, here is a complete solution that might help. The key is to take the screenshot first, then draw it onto a temporary HTML canvas where you can add any custom graphics you need.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;The Core Logic: &lt;/SPAN&gt;&lt;SPAN class=""&gt;takeScreenshotWithOverlays&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;This is the main function that orchestrates the process. It takes the screenshot, creates a temporary canvas, and calls the helper functions to draw the scale bar and North arrow.&lt;/SPAN&gt;&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;/**
 * Takes a screenshot of the current map view and draws custom overlays on it.
 * @param {esri/views/MapView} view - The MapView instance to capture.
 * @param {HTMLElement} container - The HTML element where the final image will be displayed.
 */
async function takeScreenshotWithOverlays(view, container) {
    try {
        // 1. Take the initial screenshot from the MapView.
        // This returns a promise that resolves with a Screenshot object.
        const screenshot = await view.takeScreenshot({
            format: "png",
            width: 600,
            height: 338
        });

        // 2. Create an in-memory Image object. This is necessary to handle the
        // raw image data from the screenshot before drawing it on a canvas.
        const image = new Image();

        // 3. Define the 'onload' event handler. This code will execute *after*
        // the screenshot data has been successfully loaded into the Image object.
        image.onload = function() {
            // 4. Create a temporary canvas element to act as our drawing board.
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            const canvasWidth = image.width;
            const canvasHeight = image.height;
            canvas.width = canvasWidth;
            canvas.height = canvasHeight;

            // 5. Draw the original map screenshot as the base layer on our canvas.
            context.drawImage(image, 0, 0);

            // 6. Call our custom functions to draw the overlays on top of the map image.
            // These functions need access to the canvas's drawing context and the map view.
            drawCustomScaleBar(context, view, canvasWidth, canvasHeight);
            drawNorthArrow(context, view, canvasWidth, canvasHeight);

            // 7. Create the final image element that will be displayed in the report.
            const imageElement = document.createElement("img");

            // 8. Convert the entire canvas (map + overlays) into a PNG data URL
            // and set it as the source for our final image element.
            imageElement.src=canvas.toDataURL('image/png');

            // 9. Clear the target container and append the new, final image.
            container.innerHTML = '';
            container.appendChild(imageElement);
        };

        // 10. Set the source of the Image object to the screenshot's data URL.
        // This action triggers the 'onload' event handler defined above.
        image.src=screenshot.dataUrl;

    } catch (err) {
        console.error("Screenshot failed:", err);
        container.innerHTML = '&amp;lt;em&amp;gt;Could not generate map image.&amp;lt;/em&amp;gt;';
    }
}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P&gt;&lt;SPAN class=""&gt;Helper Function: &lt;/SPAN&gt;&lt;SPAN class=""&gt;drawCustomScaleBar&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;This function calculates an appropriate scale based on the map's current extent and draws a scale bar in the bottom-left corner.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;STRONG&gt;&lt;SPAN class=""&gt;Important Note:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN class=""&gt; This method assumes your &lt;/SPAN&gt;&lt;SPAN class=""&gt;MapView&lt;/SPAN&gt;&lt;SPAN class=""&gt; is using a &lt;/SPAN&gt;&lt;STRONG&gt;&lt;SPAN class=""&gt;projected coordinate system&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN class=""&gt; (like UTM or State Plane) where the extent's units are in meters and suitable for planar measurement. It will &lt;/SPAN&gt;&lt;STRONG&gt;&lt;SPAN class=""&gt;not&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN class=""&gt; be accurate for views using Web Mercator (wkid: 3857), as its "meters" are distorted and do not represent a true ground distance.&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;/**
 * Draws a dynamic scale bar onto a canvas context.
 * @param {CanvasRenderingContext2D} context - The context of the canvas to draw on.
 * @param {esri/views/MapView} view - The MapView, used to get the current map scale.
 * @param {number} canvasWidth - The width of the canvas.
 * @param {number} canvasHeight - The height of the canvas.
 */
function drawCustomScaleBar(context, view, canvasWidth, canvasHeight) {
    // The view's extent width is in meters. We convert it to feet for our scale bar.
    const realWorldWidthInFeet = view.extent.width * 3.28084;
    const pixelWidth = canvasWidth;
    const pixelsPerFoot = pixelWidth / realWorldWidthInFeet;
    const oneMileInFeet = 5280;

    // Determine a "nice" length for the scale bar (e.g., 1 mile, 500 feet)
    // by checking what distance would occupy about 40% of the image width.
    let scaleBarDistanceInFeet = oneMileInFeet * 5; // Start with 5 miles
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = oneMileInFeet * 2;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = oneMileInFeet;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 2000; // feet
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 1000;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 500;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 200;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 100;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 50;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 20;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 10;

    const scaleBarWidthInPixels = Math.round(scaleBarDistanceInFeet * pixelsPerFoot);

    // Create a clean label (e.g., "1 mile" or "500 feet").
    let label = '';
    if (scaleBarDistanceInFeet &amp;gt;= oneMileInFeet) {
        const miles = scaleBarDistanceInFeet / oneMileInFeet;
        label = `${miles} ${miles === 1 ? 'mile' : 'miles'}`;
    } else {
        label = `${Math.round(scaleBarDistanceInFeet)} feet`;
    }

    // Define position and appearance.
    const barX = 20;
    const barY = canvasHeight - 40;
    const barHeight = 14;

    // Draw the scale bar rectangle.
    context.fillStyle = 'rgba(0, 0, 0, 0.8)';
    context.strokeStyle = 'rgba(255, 255, 255, 1)';
    context.lineWidth = 1.5;
    context.fillRect(barX, barY, scaleBarWidthInPixels, barHeight);
    context.strokeRect(barX, barY, scaleBarWidthInPixels, barHeight);

    // Draw the text label inside the bar.
    context.fillStyle = 'rgba(255, 255, 255, 0.9)';
    context.font = 'bold 14px Inter, sans-serif';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(label, barX + scaleBarWidthInPixels / 2, barY + barHeight / 2);
}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P&gt;&lt;SPAN class=""&gt;Helper Function: &lt;/SPAN&gt;&lt;SPAN class=""&gt;drawNorthArrow&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;This function draws a North arrow that rotates based on the map's rotation and includes the angle as text.&lt;/SPAN&gt;&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;/**
 * Draws a dynamic North arrow onto a canvas context.
 * @param {CanvasRenderingContext2D} context - The context of the canvas to draw on.
 * @param {esri/views/MapView} view - The MapView, used to get the current map rotation.
 * @param {number} canvasWidth - The width of the canvas.
 * @param {number} canvasHeight - The height of the canvas.
 */
function drawNorthArrow(context, view, canvasWidth, canvasHeight) {
    const rotation = view.rotation; // Get the rotation in degrees from the view.
    const size = 25; // Size of the arrow body.
    const margin = 30; // Margin from the edges.
    const centerX = canvasWidth - margin - size;
    const centerY = canvasHeight - margin - size; // Position from bottom-right.

    context.save(); // Save the canvas state before we transform it.

    // Move the canvas origin to the arrow's location and rotate the entire canvas.
    context.translate(centerX, centerY);
    context.rotate(rotation * Math.PI / 180); // Convert degrees to radians for canvas API.

    // Draw the arrow shape.
    context.beginPath();
    context.moveTo(0, -size);
    context.lineTo(size / 2, size);
    context.lineTo(0, size / 2);
    context.lineTo(-size / 2, size);
    context.closePath();

    context.fillStyle = 'rgba(0, 0, 0, 0.8)';
    context.fill();
    context.strokeStyle = 'rgba(255, 255, 255, 1)';
    context.lineWidth = 1.5;
    context.stroke();

    // Draw the 'N' inside the arrow.
    context.fillStyle = 'white';
    context.font = 'bold 16px Inter, sans-serif';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText('N', 0, 0);

    context.restore(); // Restore the canvas to its original, unrotated state.

    // --- Draw the rotation degrees text with a "halo" for visibility ---
    const angleText = `${rotation.toFixed(1)}°`;
    const textX = centerX;
    const textY = centerY - size - 10; // Position text above the arrow.

    context.font = 'bold 12px Inter, sans-serif';
    context.textAlign = 'center';

    // 1. Draw the white "halo" by stroking the text.
    context.strokeStyle = 'white';
    context.lineWidth = 3;
    context.lineJoin = 'round';
    context.strokeText(angleText, textX, textY);

    // 2. Draw the black text on top of the halo.
    context.fillStyle = 'black';
    context.fillText(angleText, textX, textY);
}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P&gt;&lt;SPAN class=""&gt;Hope this helps!&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;Also I feel I should note that Gemini AI was used to add comments and clarify some of my poor directions.&lt;/P&gt;</description>
    <pubDate>Wed, 06 Aug 2025 20:08:52 GMT</pubDate>
    <dc:creator>JohnEllisDCP</dc:creator>
    <dc:date>2025-08-06T20:08:52Z</dc:date>
    <item>
      <title>View.takeScreenShot does not include scalebar</title>
      <link>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1356190#M82998</link>
      <description>&lt;P&gt;Hi everyone,&amp;nbsp;&lt;/P&gt;&lt;P&gt;in my case, I added the scalebar to the mapview.ui. However, when I use MapView.takeScreenshot to export the current view, it doesn't include the scalebar.&amp;nbsp;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Does anyone know how to add&amp;nbsp;scalebar to the screenshot?&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="current_mapview.png" style="width: 920px;"&gt;&lt;img src="https://community.esri.com/t5/image/serverpage/image-id/87871i883D1B3E2B27235C/image-size/large?v=v2&amp;amp;px=999" role="button" title="current_mapview.png" alt="current_mapview.png" /&gt;&lt;/span&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="exported_screenshot.png" style="width: 660px;"&gt;&lt;img src="https://community.esri.com/t5/image/serverpage/image-id/87872i24B098535C3D32BE/image-size/large?v=v2&amp;amp;px=999" role="button" title="exported_screenshot.png" alt="exported_screenshot.png" /&gt;&lt;/span&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 04 Dec 2023 06:22:04 GMT</pubDate>
      <guid>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1356190#M82998</guid>
      <dc:creator>YangJin</dc:creator>
      <dc:date>2023-12-04T06:22:04Z</dc:date>
    </item>
    <item>
      <title>Re: View.takeScreenShot does not include scalebar</title>
      <link>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1356305#M83000</link>
      <description>&lt;P&gt;The takeScreenshot method only includes map elements, not widgets or other DOM elements.&lt;/P&gt;&lt;P&gt;&lt;A href="https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#takeScreenshot" target="_blank" rel="noopener"&gt;https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#takeScreenshot&lt;/A&gt;&lt;/P&gt;&lt;P&gt;Some people have reported using html2canvas to capture DOM elements, but I haven't tried it with the Maps SDK.&lt;/P&gt;</description>
      <pubDate>Mon, 04 Dec 2023 15:02:51 GMT</pubDate>
      <guid>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1356305#M83000</guid>
      <dc:creator>ReneRubalcava</dc:creator>
      <dc:date>2023-12-04T15:02:51Z</dc:date>
    </item>
    <item>
      <title>Re: View.takeScreenShot does not include scalebar</title>
      <link>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1639811#M87485</link>
      <description>&lt;P&gt;&lt;SPAN class=""&gt;Hello everyone,&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;For anyone trying to draw custom elements like a scale bar or a North arrow onto a screenshot taken with &lt;/SPAN&gt;&lt;SPAN class=""&gt;view.takeScreenshot()&lt;/SPAN&gt;&lt;SPAN class=""&gt;, here is a complete solution that might help. The key is to take the screenshot first, then draw it onto a temporary HTML canvas where you can add any custom graphics you need.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;The Core Logic: &lt;/SPAN&gt;&lt;SPAN class=""&gt;takeScreenshotWithOverlays&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;This is the main function that orchestrates the process. It takes the screenshot, creates a temporary canvas, and calls the helper functions to draw the scale bar and North arrow.&lt;/SPAN&gt;&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;/**
 * Takes a screenshot of the current map view and draws custom overlays on it.
 * @param {esri/views/MapView} view - The MapView instance to capture.
 * @param {HTMLElement} container - The HTML element where the final image will be displayed.
 */
async function takeScreenshotWithOverlays(view, container) {
    try {
        // 1. Take the initial screenshot from the MapView.
        // This returns a promise that resolves with a Screenshot object.
        const screenshot = await view.takeScreenshot({
            format: "png",
            width: 600,
            height: 338
        });

        // 2. Create an in-memory Image object. This is necessary to handle the
        // raw image data from the screenshot before drawing it on a canvas.
        const image = new Image();

        // 3. Define the 'onload' event handler. This code will execute *after*
        // the screenshot data has been successfully loaded into the Image object.
        image.onload = function() {
            // 4. Create a temporary canvas element to act as our drawing board.
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            const canvasWidth = image.width;
            const canvasHeight = image.height;
            canvas.width = canvasWidth;
            canvas.height = canvasHeight;

            // 5. Draw the original map screenshot as the base layer on our canvas.
            context.drawImage(image, 0, 0);

            // 6. Call our custom functions to draw the overlays on top of the map image.
            // These functions need access to the canvas's drawing context and the map view.
            drawCustomScaleBar(context, view, canvasWidth, canvasHeight);
            drawNorthArrow(context, view, canvasWidth, canvasHeight);

            // 7. Create the final image element that will be displayed in the report.
            const imageElement = document.createElement("img");

            // 8. Convert the entire canvas (map + overlays) into a PNG data URL
            // and set it as the source for our final image element.
            imageElement.src=canvas.toDataURL('image/png');

            // 9. Clear the target container and append the new, final image.
            container.innerHTML = '';
            container.appendChild(imageElement);
        };

        // 10. Set the source of the Image object to the screenshot's data URL.
        // This action triggers the 'onload' event handler defined above.
        image.src=screenshot.dataUrl;

    } catch (err) {
        console.error("Screenshot failed:", err);
        container.innerHTML = '&amp;lt;em&amp;gt;Could not generate map image.&amp;lt;/em&amp;gt;';
    }
}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P&gt;&lt;SPAN class=""&gt;Helper Function: &lt;/SPAN&gt;&lt;SPAN class=""&gt;drawCustomScaleBar&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;This function calculates an appropriate scale based on the map's current extent and draws a scale bar in the bottom-left corner.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;P&gt;&lt;STRONG&gt;&lt;SPAN class=""&gt;Important Note:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN class=""&gt; This method assumes your &lt;/SPAN&gt;&lt;SPAN class=""&gt;MapView&lt;/SPAN&gt;&lt;SPAN class=""&gt; is using a &lt;/SPAN&gt;&lt;STRONG&gt;&lt;SPAN class=""&gt;projected coordinate system&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN class=""&gt; (like UTM or State Plane) where the extent's units are in meters and suitable for planar measurement. It will &lt;/SPAN&gt;&lt;STRONG&gt;&lt;SPAN class=""&gt;not&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN class=""&gt; be accurate for views using Web Mercator (wkid: 3857), as its "meters" are distorted and do not represent a true ground distance.&lt;/SPAN&gt;&lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;/**
 * Draws a dynamic scale bar onto a canvas context.
 * @param {CanvasRenderingContext2D} context - The context of the canvas to draw on.
 * @param {esri/views/MapView} view - The MapView, used to get the current map scale.
 * @param {number} canvasWidth - The width of the canvas.
 * @param {number} canvasHeight - The height of the canvas.
 */
function drawCustomScaleBar(context, view, canvasWidth, canvasHeight) {
    // The view's extent width is in meters. We convert it to feet for our scale bar.
    const realWorldWidthInFeet = view.extent.width * 3.28084;
    const pixelWidth = canvasWidth;
    const pixelsPerFoot = pixelWidth / realWorldWidthInFeet;
    const oneMileInFeet = 5280;

    // Determine a "nice" length for the scale bar (e.g., 1 mile, 500 feet)
    // by checking what distance would occupy about 40% of the image width.
    let scaleBarDistanceInFeet = oneMileInFeet * 5; // Start with 5 miles
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = oneMileInFeet * 2;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = oneMileInFeet;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 2000; // feet
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 1000;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 500;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 200;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 100;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 50;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 20;
    if (scaleBarDistanceInFeet * pixelsPerFoot &amp;gt; pixelWidth * 0.4) scaleBarDistanceInFeet = 10;

    const scaleBarWidthInPixels = Math.round(scaleBarDistanceInFeet * pixelsPerFoot);

    // Create a clean label (e.g., "1 mile" or "500 feet").
    let label = '';
    if (scaleBarDistanceInFeet &amp;gt;= oneMileInFeet) {
        const miles = scaleBarDistanceInFeet / oneMileInFeet;
        label = `${miles} ${miles === 1 ? 'mile' : 'miles'}`;
    } else {
        label = `${Math.round(scaleBarDistanceInFeet)} feet`;
    }

    // Define position and appearance.
    const barX = 20;
    const barY = canvasHeight - 40;
    const barHeight = 14;

    // Draw the scale bar rectangle.
    context.fillStyle = 'rgba(0, 0, 0, 0.8)';
    context.strokeStyle = 'rgba(255, 255, 255, 1)';
    context.lineWidth = 1.5;
    context.fillRect(barX, barY, scaleBarWidthInPixels, barHeight);
    context.strokeRect(barX, barY, scaleBarWidthInPixels, barHeight);

    // Draw the text label inside the bar.
    context.fillStyle = 'rgba(255, 255, 255, 0.9)';
    context.font = 'bold 14px Inter, sans-serif';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(label, barX + scaleBarWidthInPixels / 2, barY + barHeight / 2);
}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P&gt;&lt;SPAN class=""&gt;Helper Function: &lt;/SPAN&gt;&lt;SPAN class=""&gt;drawNorthArrow&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN class=""&gt;This function draws a North arrow that rotates based on the map's rotation and includes the angle as text.&lt;/SPAN&gt;&lt;/P&gt;&lt;PRE&gt;&lt;SPAN class=""&gt;/**
 * Draws a dynamic North arrow onto a canvas context.
 * @param {CanvasRenderingContext2D} context - The context of the canvas to draw on.
 * @param {esri/views/MapView} view - The MapView, used to get the current map rotation.
 * @param {number} canvasWidth - The width of the canvas.
 * @param {number} canvasHeight - The height of the canvas.
 */
function drawNorthArrow(context, view, canvasWidth, canvasHeight) {
    const rotation = view.rotation; // Get the rotation in degrees from the view.
    const size = 25; // Size of the arrow body.
    const margin = 30; // Margin from the edges.
    const centerX = canvasWidth - margin - size;
    const centerY = canvasHeight - margin - size; // Position from bottom-right.

    context.save(); // Save the canvas state before we transform it.

    // Move the canvas origin to the arrow's location and rotate the entire canvas.
    context.translate(centerX, centerY);
    context.rotate(rotation * Math.PI / 180); // Convert degrees to radians for canvas API.

    // Draw the arrow shape.
    context.beginPath();
    context.moveTo(0, -size);
    context.lineTo(size / 2, size);
    context.lineTo(0, size / 2);
    context.lineTo(-size / 2, size);
    context.closePath();

    context.fillStyle = 'rgba(0, 0, 0, 0.8)';
    context.fill();
    context.strokeStyle = 'rgba(255, 255, 255, 1)';
    context.lineWidth = 1.5;
    context.stroke();

    // Draw the 'N' inside the arrow.
    context.fillStyle = 'white';
    context.font = 'bold 16px Inter, sans-serif';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText('N', 0, 0);

    context.restore(); // Restore the canvas to its original, unrotated state.

    // --- Draw the rotation degrees text with a "halo" for visibility ---
    const angleText = `${rotation.toFixed(1)}°`;
    const textX = centerX;
    const textY = centerY - size - 10; // Position text above the arrow.

    context.font = 'bold 12px Inter, sans-serif';
    context.textAlign = 'center';

    // 1. Draw the white "halo" by stroking the text.
    context.strokeStyle = 'white';
    context.lineWidth = 3;
    context.lineJoin = 'round';
    context.strokeText(angleText, textX, textY);

    // 2. Draw the black text on top of the halo.
    context.fillStyle = 'black';
    context.fillText(angleText, textX, textY);
}&lt;/SPAN&gt;&lt;/PRE&gt;&lt;P&gt;&lt;SPAN class=""&gt;Hope this helps!&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;Also I feel I should note that Gemini AI was used to add comments and clarify some of my poor directions.&lt;/P&gt;</description>
      <pubDate>Wed, 06 Aug 2025 20:08:52 GMT</pubDate>
      <guid>https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/view-takescreenshot-does-not-include-scalebar/m-p/1639811#M87485</guid>
      <dc:creator>JohnEllisDCP</dc:creator>
      <dc:date>2025-08-06T20:08:52Z</dc:date>
    </item>
  </channel>
</rss>

