Update/Replace inline image on Google Document

NamedRanges indeed get lost when the range is moved, so they're not very good for your scenario. But there's no other way of identifying elements (which is a great misfeature of Google Docs).

In the case of an image you could use its LINK_URL to identify it, which seems to be what Lucidchart uses. It does not get in the way of the user, so it may be a good solution.

About getting a blank line and losing attributes when inserting an image, I imagine (since you haven't shared any code) you're inserting the image directly in the document body instead of a paragraph. Then a paragraph gets created automatically to wrap your image resulting in the blank line and lost of attributes.

Here's some code example:

function initialInsert() {
  var data = Charts.newDataTable().addColumn(
    Charts.ColumnType.STRING, 'Fruits').addColumn(
    Charts.ColumnType.NUMBER, 'Amount').addRow(
    ['Apple',15]).addRow(
    ['Orange',6]).addRow(
    ['Banana',14]).build();
  var chart = Charts.newPieChart().setDataTable(data).build();

  var body = DocumentApp.getActiveDocument().getBody()
  body.appendImage(chart).setLinkUrl('http://mychart');
  //here we're inserting directly in the body, a wrapping paragraph element will be created for us
}

function updateImage() {
  var data = Charts.newDataTable().addColumn(
    Charts.ColumnType.STRING, 'Fruits').addColumn(
    Charts.ColumnType.NUMBER, 'Amount').addRow(
    ['Apple',Math.floor(Math.random()*31)]).addRow( //random int between 0 and 30
    ['Orange',Math.floor(Math.random()*31)]).addRow(
    ['Banana',Math.floor(Math.random()*31)]).build();
  var chart = Charts.newPieChart().setDataTable(data).build();

  var img = getMyImg(DocumentApp.getActiveDocument().getBody(), 'http://mychart');
  //let's insert on the current parent instead of the body 
  var parent = img.getParent(); //probably a paragraph, but does not really matter
  parent.insertInlineImage(parent.getChildIndex(img)+1, chart).setLinkUrl('http://mychart');
  img.removeFromParent();
}

function getMyImg(docBody, linkUrl) {
  var imgs = docBody.getImages();
  for( var i = 0; i < imgs.length; ++i )
    if( imgs[i].getLinkUrl() === linkUrl )
      return imgs[i];
  return null;
}

About the link_url, you could of course do like Lucidchart does and link back to your site. So it's not just broken for the user.


Take a look at my add-on called PlantUML Gizmo.

Here's the code to the insert image function, which deals with replacing images if there's already one selected:

function insertImage(imageDataUrl, imageUrl) {
  /*
   * For debugging cursor info
   */
//  var cursor = DocumentApp.getActiveDocument().getCursor();
//  Logger.log(cursor.getElement().getParent().getType());
//  throw "cursor info: " + cursor.getElement().getType() + " offset = " + cursor.getOffset() + " surrounding text = '" + cursor.getSurroundingText().getText() + "'  parent's type = " + 
//    cursor.getElement().getParent().getType();
  /*
   * end debug
   */
  var doc = DocumentApp.getActiveDocument();
  var selection = doc.getSelection();
  var replaced = false;
  if (selection) {
    var elements = selection.getSelectedElements();
    // delete the selected image (to be replaced)
    if (elements.length == 1 &&
        elements[0].getElement().getType() ==
        DocumentApp.ElementType.INLINE_IMAGE) {
          var parentElement = elements[0].getElement().getParent();  // so we can re-insert cursor
          elements[0].getElement().removeFromParent();
          replaced = true;
          // move cursor to just before deleted image
          doc.setCursor(DocumentApp.getActiveDocument().newPosition(parentElement, 0));
     } else {
          throw "Please select only one image (image replacement) or nothing (image insertion)"
     }
  }
  var cursor = doc.getCursor();
  var blob;

  if (imageDataUrl != "") {
    blob = getBlobFromBase64(imageDataUrl);
  } else {
    blob = getBlobViaFetch(imageUrl);
  }

  var image = cursor.insertInlineImage(blob);  

  image.setLinkUrl(imageUrl);

  // move the cursor to after the image
  var position = doc.newPosition(cursor.getElement(), cursor.getOffset()+1);
  doc.setCursor(position);

  if (cursor.getElement().getType() == DocumentApp.ElementType.PARAGRAPH) {
    Logger.log("Resizing");
    // resize if wider than current page
    var currentParagraph = DocumentApp.getActiveDocument().getCursor().getElement().asParagraph();
    var originalImageWidth = image.getWidth();  // pixels
    var documentWidthPoints = DocumentApp.getActiveDocument().getBody().getPageWidth() - DocumentApp.getActiveDocument().getBody().getMarginLeft() - DocumentApp.getActiveDocument().getBody().getMarginRight();
    var documentWidth = documentWidthPoints * 96 / 72;  // convert to pixels (a guess)
    var paragraphWidthPoints = documentWidthPoints - currentParagraph.getIndentStart() - currentParagraph.getIndentEnd();
    var paragraphWidth = paragraphWidthPoints * 96 / 72;  // convert to pixels (a guess)

    if (originalImageWidth > paragraphWidth) {
      image.setWidth(paragraphWidth);
      // scale proportionally
      image.setHeight(image.getHeight() * image.getWidth() / originalImageWidth);  
    }

  }

}