Comment passer une plage dans une fonction personnalisée dans Google Spreadsheets?


44

Je veux créer une fonction qui prend dans une plage ... quelque chose comme:

function myFunction(range) {
  var firstColumn = range.getColumn();
  // loop over the range
}

Une cellule le référencerait en utilisant:

=myFunction(A1:A4)

Le problème est que, lorsque j'essaie de le faire, le paramètre semble ne transmettre que les valeurs à la fonction. Ainsi, je ne peux utiliser aucune des méthodes Range telles que getColumn(). Lorsque j'essaie de le faire, cela donne l'erreur suivante:

error: TypeError: Impossible de trouver la fonction getColumn dans l'objet 1,2,3.

Comment puis-je envoyer une plage réelle plutôt que simplement les valeurs à l'une de mes fonctions personnalisées?

Réponses:


23

J'ai donc longuement et durement cherché une bonne réponse à cette question et voici ce que j'ai trouvé:

  1. un paramètre de plage non modifié transmet les valeurs des cellules de la plage, pas la plage elle-même (comme l'explique Gergely) ex: lorsque vous faites cela=myFunction(a1:a2)

  2. pour utiliser une plage dans votre fonction, vous devez d'abord la passer sous forme de chaîne (ex:) =myFunction("a1:a2"), puis la transformer en plage avec le code suivant à l'intérieur de la fonction:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Enfin, si vous souhaitez bénéficier des avantages de la transmission d'une plage (comme la copie intelligente de références de plage à d'autres cellules) ET que vous souhaitez la transmettre sous forme de chaîne, vous devez utiliser une fonction par défaut qui accepte une plage en tant que paramètre et génère une chaîne que vous pouvez utiliser. Je détaille les deux voies que j'ai trouvées ci-dessous, mais je préfère la deuxième.

    Pour toutes les feuilles de calcul Google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Pour les nouvelles Google Sheets: =myFunction(CELL("address",A1)&":"&CELL("address",A2));


Pouvez-vous préciser ce que "copier intelligemment les références de plage à d'autres cellules" signifie s'il vous plaît?
Emerson Farrugia

1
Gareautrain. Ça marche, merci.
Jithin Pavithran

@EmersonFarrugia Je fais référence à la possibilité, pour les feuilles Google, de mettre à jour automatiquement les références de cellules relatives lorsque vous copiez des fonctions d'une cellule à une autre
Nathan Hanna

9

rangeest traité comme le tableau 2d de javascript. Vous pouvez obtenir le nombre de lignes avec range.lengthet le nombre de colonnes avec range[0].length. Si vous souhaitez obtenir la valeur de la ligne ret la colonne cutilisation: range[r][c].


J'essaie de savoir quelle est la première colonne de la plage. Par exemple, dans la plage C1: F1, je veux savoir que la plage commence à la colonne C.
Senseful

@Senseful Si vous utilisez votre fonction dans une cellule du type:, =myFunction(C1:F1)la fonction range[0][0]retournera la valeur de C1, range[0][1]retournera la valeur de D1, range[0][2]retournera la valeur de E1, etc. Est-ce clair?
Lipis

Je pense que je ne m'explique pas clairement ... regardez votre réponse à cette question . Vous avez recommandé que je l'utilise =weightedAverage(B3:D3,$B$2:$D$2), je voudrais rendre la fonction plus résistante aux erreurs en évitant d'envoyer le deuxième argument (c.-à-d. Je veux l'appeler ainsi =weightedAverage(B3:D3)), puis que le code sache automatiquement que la plage commence à B et se termine à D, il obtient donc les valeurs correspondantes de la 2e ligne. Remarque: la valeur "2" peut être codée en dur, mais "B" ne devrait pas l'être.
Senseful

1
@ Sensible En général, je suis contre les codes codés en dur, et je pense qu'il est plus clair si la moyenne pondérée prend deux paramètres. Donc, si vous êtes sur le point de coder en dur, vous pourrez faire beaucoup d'autres choses. Je ne trouve pas en ce moment comment pouvons-nous tirer le meilleur Bparti d'une donnée range. Je vous conseillerais de consulter le guide de démarrage ( goo.gl/hm0xT ), si vous ne l’avez pas déjà fait, pour avoir une idée de la façon dont vous pouvez jouer avec des plages et des cellules.
Lipis

6

Lorsque vous transmettez une Rangefonction de feuille de calcul à Google, la structure s'exécute de paramRange.getValues()manière implicite et votre fonction reçoit les valeurs de la plage sous forme de tableau à 2 dimensions de chaînes, de nombres ou d'objets (similaires Date). L' Rangeobjet n'est pas transmis à votre fonction de feuille de calcul personnalisée.

La TYPEOF()fonction ci-dessous vous indiquera quel type de données vous recevez comme paramètre. La formule

=TYPEOF(A1:A4)

appellera le script comme ceci:

function CalculateCell () {
  var resultCell = SpreadsheetApp.getActiveSheet (). getActiveCell ();
  var paramRange = SpreadsheetApp.getActiveSheet (). getRange ('A1: A4');
  var paramValues ​​= paramRange.getValues ​​();

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (resultValue);
}
fonction TYPEOF (valeur) {
  if (typeof valeur! == 'objet')
    retourne typeof value;

  var details = '';
  if (valeur instance de Array) {
    details + = '[' + value.length + ']';
    if (valeur [0] instance de tableau)
      details + = '[' + value [0] .length + ']';
  }

  var className = value.constructor.name;
  retourne className + details;
}

3

En guise d' alternative, inspirée du commentaire de @Lipis, vous pouvez appeler votre fonction avec les coordonnées de ligne / colonne comme paramètres supplémentaires:

=myFunction(B1:C2; ROW(B1); COLUMN(B1); ROW(C2); COLUMN(C2))

Sous cette forme, la formule peut facilement être copiée (ou glissée) dans d'autres cellules et les références de cellule / plage seront automatiquement ajustées.

Votre fonction de script doit être mise à jour en tant que telle:

function myFunction(rangeValues, startRow, startColumn, endRow, endColumn) {
  var range = SpreadsheetApp.getActiveSheet().getRange(startRow, startColumn, endRow - startRow + 1, endColumn - startColumn + 1);
  return "Range: " + range.getA1Notation();
}

Notez que la getRangefonction prend startRow, startColumnet puis nombre de lignes et nombre de colonnes - pas fin ligne et fin colonne. Ainsi, l'arithmétique.


C'est la réponse exacte que j'attends. ROW et COLUMN.
Jianwu Chen le

3

Cela peut être fait . Vous pouvez obtenir une référence à la plage passée en analysant la formule dans la cellule active, qui est la cellule contenant la formule. Cela laisse supposer que la fonction personnalisée est utilisée seule et non comme une partie d'une expression plus complexe: par exemple =myfunction(A1:C3), pas =sqrt(4+myfunction(A1:C3)).

Cette fonction renvoie le premier index de colonne de la plage transmise.

function myfunction(reference) {
  var sheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();
  var args = formula.match(/=\w+\((.*)\)/i)[1].split('!');
  try {
    if (args.length == 1) {
      var range = sheet.getRange(args[0]);
    }
    else {
      sheet = ss.getSheetByName(args[0].replace(/'/g, ''));
      range = sheet.getRange(args[1]);
    }
  }
  catch(e) {
    throw new Error(args.join('!') + ' is not a valid range');
  }

  // everything so far was only range extraction
  // the specific logic of the function begins here

  var firstColumn = range.getColumn();  // or whatever you want to do with the range
  return firstColumn;
}

Je pense que ce message répond également à la question suivante sur Stack Overflow : passer des références de cellule aux fonctions de feuille de calcul
Rubén

2

J'ai étendu l'excellente idée de la réponse de user79865 , de la faire fonctionner pour plus de cas et avec plusieurs arguments passés à la fonction personnalisée.

Pour l'utiliser, copiez le code ci-dessous, puis dans votre appel de fonction personnalisé GetParamRanges(), transmettez-lui votre nom de fonction:

function CustomFunc(ref1, ref2) {

  var ranges = GetParamRanges("CustomFunc"); // substitute your function name here

  // ranges[0] contains the range object for ref1 (or null)
  // ranges[1] contains the range object for ref2 (or null)

  ... do what you want

  return what_you_want;
}

Voici un exemple pour renvoyer la couleur d'une cellule:

/**
* Returns the background color of a cell
*
* @param {cell_ref} The address of the cell
* @return The color of the cell
* @customfunction
*/
function GetColor(ref) {

  return GetParamRanges("GetColor")[0].getBackground();
}

Voici le code et quelques explications supplémentaires:

/**
* Returns an array of the range object(s) referenced by the parameters in a call to a custom function from a cell
* The array will have an entry for each parameter. If the parameter was a reference to a cell or range then 
* its array element will contain the corresponding range object, otherwise it will be null.
*
* Limitations:
* - A range is returned only if a parameter expression is a single reference.
*   For example,=CustomFunc(A1+A2) would not return a range.
* - The parameter expressions in the cell formula may not contain commas or brackets.
*   For example, =CustomFunc(A1:A3,ATAN2(4,3),B:E) would not parse correctly.
* - The custom function may not appear more than once in the cell formula.
* - Sheet names may not contain commas, quotes or closing brackets.
* - The cell formula may contain white space around the commas separating the custom function parameters, or after
*   the custom function name, but not elsewhere within the custom function invocation.
* - There may be other limitations.
* 
* Examples:
*   Cell formula: =CUSTOMFUNC($A$1)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: range object for cell A1 in the sheet containing the formula
*
*   Cell formula: =CUSTOMFUNC(3, 'Expenses'!B7)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: null
*                 ranges[1]: range object for cell B7 in sheet Expenses
* 
*   Cell formula: =sqrt(4+myfunction(A1:C3))
*   Usage:        var ranges = GetParamRanges("MyFunction");
*   Result:       ranges[0]: range object for cells A1 through C3 in the sheet containing the formula
*
*   Cell formula: =CustomFunc(A1+A2, A1, A2)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: null
*                 ranges[1]: range object for cell A1 in the sheet containing the formula
*                 ranges[2]: range object for cell A2 in the sheet containing the formula
*
* @param {funcName} The name of the custom function (string, case-insensitive)
* @return The range(s) referenced by the parameters to the call
*/
function GetParamRanges(funcName) {
  var ourSheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();
  var re = new RegExp(".+" + funcName + "\\s*\\((.*?)\\)","i");
  var ranges=[]; // array of results

  try {
    var args = formula.match(re)[1].split(/\s*,\s*/) // arguments to custom function, separated by commas
    // if there are no args it fails and drops out here

    for (var i=0; i<args.length; i++) {
      var arg=args[i].split('!'); // see if arg has a sheet name
      try {
        if (arg.length == 1) { // if there's no sheet name then use the whole arg as the range definition
          ranges[i] = ourSheet.getRange(arg[0]);
        }
        else { // if there's a sheet name, use it (after removing quotes around it)
          var sheetName=arg[0].replace(/'/g, '');
          var otherSheet=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
          ranges[i] = otherSheet.getRange(arg[1]);
        }
      }
      catch(e) { // assume it couldn't be identified as a range for whatever reason
        ranges[i]=null;
      }
    }
  }
  catch(e) {}

  return ranges
}

1
pas mal du tout, mais veuillez préciser que cela fait de l'hypothèse qu'une fonction de custion n'est utilisée qu'une fois dans une formule. Fi, j'ai utilisé des forums comme: = CountCcolor (B5: B38, A40 $) / 2 + CountCcolor (B5: B38, A41 $) / 2 + CountCcolor (B5: B38, A42 $) / 2 + CountCcolor (B5: B38 , $ A43) / 2 + CountCcolor (B5: B38, $ A44) / 2 Si je comprends bien, cela ne fonctionnera pas de cette façon. Si nous le résolvions, nous serions capables de gérer cela, ce serait formidable.
hase

1

Je distingue deux fonctions personnalisées différentes dans Google Spreadsheet, via le script Google Apps:

  1. Celui qui appelle une API; SpreadsheetApp, ( exemple )
  2. Celui qui ne fait aucun appel, ( exemple )

La première fonction est capable de faire presque n'importe quoi. En plus d'appeler le service Tableur, il peut faire appel à GmailApp ou à tout service mis à disposition par Google. Les appels d'API ralentiront le processus. Une plage peut être transmise ou récupérée via la fonction pour accéder à toute la méthode disponible.

La deuxième fonction est limitée à la "feuille de calcul" uniquement. Ici, on peut utiliser JavaScript pour retravailler les données. Ces fonctions sont normalement très rapides. Une plage passée n'est rien d'autre qu'un tableau 2D contenant des valeurs.


Dans votre question, vous commencez par appeler la .getColumn()méthode. Cela indique clairement que vous avez besoin d'une fonction personnalisée de type 1, comme décrit ci-dessus.

Voir la réponse suivante sur la définition de la feuille de calcul et de la feuille pour créer une fonction personnalisée.


1

Je travaillais là-dessus il y a quelques mois et je suis arrivé à un kludge très simple: créez une nouvelle feuille avec le nom de chaque cellule comme contenu: la cellule A1 pourrait ressembler à:

= arrayformula(cell("address",a1:z500))

Nommez la feuille "Ref". Ensuite, lorsque vous avez besoin d'une référence à une cellule sous forme de chaîne au lieu de son contenu, vous utilisez:

= some_new_function('Ref'!C45)

Bien sûr, vous devrez vérifier si la fonction reçoit une chaîne (une cellule) ou un tableau 1D ou 2D. Si vous obtenez un tableau, il aura toutes les adresses de cellules sous forme de chaînes, mais à partir de la première cellule et de la largeur et de la hauteur, vous pourrez déterminer ce dont vous avez besoin.


0

J'ai travaillé dessus toute la matinée et j'ai vu des preuves de ce que les non-réponses ci-dessus discutent. Senseful et je veux tous les deux l’ADRESSE de la cellule transmise, pas la valeur. Et la réponse est très facile. Cela ne peut pas être fait.

J'ai trouvé des solutions de contournement qui consistent à déterminer quelle cellule contient la formule. Il est difficile de dire si cela aurait aidé Senseful ci-dessus. Alors qu'est-ce que je faisais?

The data.

     [A]      [B]       [C]       [D]     [E]     [F]       [H]
[1] Name      Wins     Losses    Shots   Points   Fouls   Most recent
                                                          WL Ratio
[2] Sophia     4         2         15      7       1         0
[3] Gloria     11        3         11      6       0         0
[4] Rene       2         0         4       0       0         0
[5] Sophia     7         4         18      9       1         1.5

La colonne H est celle de Sophia (Victoires - PrevWins) / (Pertes - PrevLosses)

(7 - 4) / (4 - 2) = 1,5

Mais nous ne savons pas dans quelle ligne Sophia était précédemment apparue.
Tout peut être fait en utilisant VLOOKUP si vous codez en dur A comme colonne de nom. Après VLOOKUP, je récupérais des caractères #NA (nom introuvable) et # DIV0 (dénominateur zéro) et je les enveloppais avec = IF (IF (...)) pour afficher un texte plus agréable dans ces conditions. J'avais maintenant une expression monstrueusement grande qui était difficile à manier et à maintenir. Je voulais donc une expansion des macros (n'existe pas) ou des fonctions personnalisées.

Mais quand j'ai créé un assistant SubtractPrevValue (cellule), il recevait "7" au lieu de B5. Il n'y a pas de moyen intégré pour obtenir des objets de cellule ou de plage à partir des arguments transmis.
Si je fais entrer manuellement le nom de la cellule entre guillemets, je peux le faire ... SubtractPrevValue ("B5"). Mais ce sont vraiment les ischio-jambiers copier / coller et les caractéristiques relatives des cellules.

Mais ensuite j'ai trouvé une solution de contournement.

SpreadsheetApp.getActiveRange () EST LA CELLULE qui contient la formule. C'est tout ce que j'avais vraiment besoin de savoir. Le numéro de la ligne. La fonction suivante prend un numéro de colonne NUMERIC et soustrait l'occurrence précédente de cette colonne.

function SubtractPrevValue(colNum) 
{
  var curCell = SpreadsheetApp.getActiveRange();
  var curSheet = curCell.getSheet();
  var curRowIdx = curCell.getRowIndex();
  var name = curSheet.getRange(curRowIdx, 1).getValue();  // name to match
  var curVal =  curSheet.getRange(curRowIdx, colNum).getValue();

  var foundRowIdx = -1;
  for (var i=curRowIdx-1;i>1;i--)
  { 
    if (curSheet.getRange(i, 2).getValue() == name)
    {
      return curVal - curSheet.getRange(i, colNum).getValue();
    }
  }
  return curVal;  //default if no previous found
}

Mais ensuite, j'ai découvert que c'était vraiment TRÈS lent. Un délai de une et deux secondes s’affiche alors que le message "Thinking" (Penser ...) me ramène à la formule de feuille de calcul massivement illisible et impossible à maintenir.


0

Au lieu de fournir l'objet "Range", les développeurs de Google transmettent un type de données aléatoire. J'ai donc développé la macro suivante pour imprimer la valeur de l'entrée.

function tp_detail(arg)
{
    var details = '';

    try
    {
        if(typeof arg == 'undefined')
           return details += 'empty';

        if (typeof arg !== 'object')
        {
            if (typeof arg == 'undefined')
                return details += 'empty';

            if (arg.map)
            {
                var rv = 'map: {';
                var count = 1;
                for (var a in arg)
                {
                    rv += '[' + count + '] ' + tp_detail(a);
                    count = count + 1;
                }
                rv += '}; '
                return rv;
            }
            return (typeof arg) + '(\'' + arg + '\')';
        }


        if (arg instanceof Array)
        {
            details += 'arr[' + arg.length + ']: {';
            for (var i = 0; i < arg.length; i++)
            {
                details += '[' + i + '] ' + tp_detail(arg[i]) + ';';
            }
            details += '} ';
        }
        else
        {

            var className = arg.constructor.name;
            return className + '(' + arg + ')';
        }
    }
    catch (e)
    {
        var details = '';
        details = 'err : ' + e;
    }


    return details;
}

0

Voici ma compilation des réponses précédentes

**
 *
 * @customfunction
 **/
function GFR(){
  var ar = SpreadsheetApp.getActiveRange();
  var as = SpreadsheetApp.getActive();
  return ar.getFormula()
    .replace(/=gfr\((.*?)\)/i, '$1')
    .split(/[,;]/)
    .reduce(function(p, a1Notation){
      try{
        var range = as.getRange(a1Notation);
        p.push(Utilities.formatString(
          'Is "%s" a Range? %s', 
          a1Notation,
          !!range.getDisplayValues
        ));
      } catch(err){
        p.push([a1Notation + ' ' + err.message]);
      }
    return p;
  },[]);
}

Il prend la formule actuelle et vérifie s'il s'agit d'une plage.

=GFR(A1:A5;1;C1:C2;D1&D2) résultats

|Is "A1:A5" a Range? true|
|1 Range not found       |
|Is "C1:C2" a Range? true|
|D1&D2 Range not found   |

Pour la composition complète, le TC peut appeler quelque chose comme ceci

var range = as.getRange(a1Notation);
var column = range.getColumn();

-1

Créer une fonction et passer la plage en argument:

function fn(input) {
    var cell = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange(SpreadsheetApp.getActiveRange().getFormula().match(/=\w+\((.*)\)/i)[1].split('!'));
    // above ... cell is "range", can convert to array?
    var sum=0;
    for (var i in cell.getValues() ){
        sum = sum + Number(cell.getValues()[i]);
    }  
    return sum;
}

Utilisation dans un tableur:

=fn(A1:A10)

Il montrera la valeur comme sum(A1:A10).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.