0d3b8feb6074f6caccc2d4aadd020961fbd7eb82
chmalee
  Wed Jun 10 14:39:38 2020 -0700
Add drag reorder of VCF samples to VCF trio display. Also fixes bug in haplotype sort that I didn't discover until adding drag reorder. Sometimes when sorting haplotypes it is the case that the initial best match to a child allele is the same for each parent. Previously I would just advance the first drawn parent to the next best match but now I also check whether advancing the first drawn is actually the best idea and potentially advance the other parent instead, refs #25582

diff --git src/hg/lib/vcfUi.c src/hg/lib/vcfUi.c
index 4d579eb..f5b777e 100644
--- src/hg/lib/vcfUi.c
+++ src/hg/lib/vcfUi.c
@@ -372,30 +372,40 @@
 
 static void vcfCfgMinAlleleFreq(struct cart *cart, struct trackDb *tdb, struct vcfFile *vcff,
 				char *name, boolean parentLevel)
 /* Show input for minimum allele frequency, if we can extract it from the VCF INFO column. */
 {
 printf("<B>Minimum minor allele frequency (if INFO column includes AF or AC+AN):</B>\n");
 double cartMinFreq = cartOrTdbDouble(cart, tdb, VCF_MIN_ALLELE_FREQ_VAR,
 				     VCF_DEFAULT_MIN_ALLELE_FREQ);
 char varName[1024];
 safef(varName, sizeof(varName), "%s." VCF_MIN_ALLELE_FREQ_VAR, name);
 cgiMakeDoubleVarInRange(varName, cartMinFreq, "minor allele frequency between 0.0 and 0.5", 5,
 			"0.0", "0.5");
 puts("<BR>");
 }
 
+static char *getChildSample(struct trackDb *tdb)
+/* Return just the VCF sample name of the phased trio child setting */
+{
+char *childSampleMaybeAlias = cloneString(trackDbLocalSetting(tdb, VCF_PHASED_CHILD_SAMPLE_SETTING));
+char *pt = strchr(childSampleMaybeAlias, '|');
+if (pt != NULL)
+    *pt = '\0';
+return childSampleMaybeAlias;
+}
+
 static struct slPair *vcfPhasedGetSamplesFromTdb(struct trackDb *tdb, boolean hideOtherSamples)
 /* Get the different VCF Phased Trio setings out of trackDb onto a list */
 {
 // cloneString here because we will be munging the result if there are alternate labels
 char *childSampleMaybeAlias = cloneString(trackDbLocalSetting(tdb, VCF_PHASED_CHILD_SAMPLE_SETTING));
 char *parentSamplesMaybeAlias = cloneString(trackDbLocalSetting(tdb, VCF_PHASED_PARENTS_SAMPLE_SETTING));
 char *samples[VCF_PHASED_MAX_OTHER_SAMPLES+1]; // for now only allow at most two parents
 int numOthers = 0;
 if (parentSamplesMaybeAlias && !hideOtherSamples)
     {
     numOthers = chopCommas(cloneString(parentSamplesMaybeAlias), samples);
     if (numOthers > VCF_PHASED_MAX_OTHER_SAMPLES)
         {
         warn("More than %d other samples specified for phased trio", VCF_PHASED_MAX_OTHER_SAMPLES);
         numOthers = VCF_PHASED_MAX_OTHER_SAMPLES;
@@ -419,67 +429,165 @@
     if (val != NULL)
         {
         if (foundAlias != gotAlias)
             errAbort("Either all samples have aliases or none.");
         else
             *val++ = 0;
         }
     char *name = samples[i];
     struct slPair *temp = slPairNew(cloneString(name), cloneString(val));
     slAddHead(&ret, temp);
     }
 slReverse(&ret);
 return ret;
 }
 
-struct slPair *vcfPhasedGetSampleOrder(struct cart *cart, struct trackDb *tdb, boolean parentLevel)
-/* Parse out a trio sample order from either trackDb or the cart */
+struct slPair *vcfPhasedGetSampleOrder(struct cart *cart, struct trackDb *tdb, boolean parentLevel, boolean hideOtherSamples)
+/* Parse out a trio sample order from either trackDb or the cart.
+ * If the trackName.sortChildBelow cart variable is true, then ensure
+ * the vcfChildSample sample is last in the order, otherwise, use what's
+ * in the trackName.vcfSampleOrder cart variable. */
 {
-char sampleOrderVar[1028],hideParentsVar[1028];
+char sampleOrderVar[1024];
 safef(sampleOrderVar, sizeof(sampleOrderVar), "%s.%s", tdb->track, VCF_PHASED_SAMPLE_ORDER_VAR);
-safef(hideParentsVar, sizeof(hideParentsVar), "%s.%s", tdb->track, VCF_PHASED_HIDE_OTHER_VAR);
-boolean hideOtherSamples = cartUsualBooleanClosestToHome(cart, tdb, parentLevel, VCF_PHASED_HIDE_OTHER_VAR, FALSE);
 char *cartOrder = cartOptionalString(cart, sampleOrderVar);
+boolean childBelow = cartUsualBooleanClosestToHome(cart, tdb, parentLevel, VCF_PHASED_CHILD_BELOW_VAR, FALSE);
 struct slPair *tdbOrder = vcfPhasedGetSamplesFromTdb(tdb, hideOtherSamples);
-if (cartOrder != NULL && !hideOtherSamples)
+if (!hideOtherSamples)
+    {
+    // if the user used drag and drop to reorder the trios then that takes precedence
+    // over the childBelow checkbox
+    if (cartOrder != NULL)
         {
-    struct slName *name;
-    struct slName *fromCart = slNameListFromComma(cartOrder);
+        struct slName *name, *fromCart = slNameListFromComma(cartOrder);
         struct slPair *ret = NULL;
         for (name = fromCart; name != NULL; name = name->next)
             {
             struct slPair *temp = slPairFind(tdbOrder, name->name);
-        slAddHead(&ret, temp);
+            struct slPair *toAdd = slPairNew(temp->name, temp->val);
+            slAddHead(&ret, toAdd);
             }
-    slReverse(ret);
+        slReverse(&ret);
         return ret;
         }
+    else if (childBelow)
+        {
+        char *childName = getChildSample(tdb);
+        struct slPair *ret = NULL, *child = NULL, *temp = NULL;
+        for (temp = tdbOrder; temp != NULL; temp = temp->next)
+            {
+            struct slPair *toAdd = slPairNew(temp->name, temp->val);
+            if (sameString(temp->name, childName))
+                child = toAdd;
             else
+                slAddHead(&ret, toAdd);
+            }
+        if (child)
+            slAddHead(&ret, child);
+        slReverse(&ret);
+        return ret;
+        }
+    }
+// we're hiding the parents OR (we unchecked the childBelow checkbox AND we didn't drag reorder)
 return tdbOrder;
 }
 
 static boolean hasSampleAliases(struct trackDb *tdb)
 /* Check whether trackDb has aliases for the sample names  */
 {
 struct slPair *nameVals = vcfPhasedGetSamplesFromTdb(tdb,FALSE);
 return nameVals->val != NULL;
 }
 
+static void vcfPhasedSampleSortUi(struct cart *cart, struct trackDb *tdb, struct vcfFile *vcff, char *name,
+                                    boolean parentLevel)
+/* Put up the UI for sorting the samples */
+{
+struct dyString *sortOrder = dyStringNew(0);
+struct slPair *pair, *tdbOrder = vcfPhasedGetSampleOrder(cart, tdb, parentLevel, FALSE);
+if (slCount(tdbOrder) == 1) // no sorting if there are no parents
+    return;
+char childBelowSortOrder[1024];
+safef(childBelowSortOrder, sizeof(childBelowSortOrder), "%s.%s", name, VCF_PHASED_CHILD_BELOW_VAR);
+boolean isBelowChecked = cartUsualBooleanClosestToHome(cart, tdb, parentLevel, VCF_PHASED_CHILD_BELOW_VAR, FALSE);
+printf("<b>Show child haplotypes below parents:</b>\n");
+cgiMakeCheckBox(childBelowSortOrder, isBelowChecked);
+char *infoText = "Check this box to sort the child haplotypes below the parents, leave unchecked"
+    " to use the default sort order of the child in the middle. Click into each subtrack to arbitrarily"
+    " order the samples which overrides this setting.";
+printInfoIcon(infoText);
+printf("<br>");
+if (!parentLevel)
+    {
+    printf("<b>or:</b><br>\n");
+    printf("<b>Click and drag to change order:</b>\n");
+    printf("<div>\n");
+    printf("<table id=\"%s_table\" class=\"tableWithDragAndDrop\">\n", tdb->track);
+    for (pair = tdbOrder; pair != NULL; pair = pair->next)
+        {
+        char id[256];
+        safef(id, sizeof(id), "%s_drag", pair->name);
+        printf("<tr id=\"%s_row\" class=\"trDraggable\"><td id=\"%s\" class=\"dragHandle\">%s - %s</td></tr>\n", pair->name, id, pair->name, (char *)pair->val);
+        dyStringPrintf(sortOrder, "%s,", pair->name);
+        }
+    printf("</table>\n");
+    printf("</div>\n");
+    printf("<input type=\"hidden\" name=\"%s.%s\" value=\"%s\">",tdb->track, VCF_PHASED_SAMPLE_ORDER_VAR, dyStringCannibalize(&sortOrder));
+    // add the hidden variable for setting the order and the javascript to change it
+    jsInlineF(""
+    "dragReorder.init();\n"
+    "var imgTable = $(\"#%s_table\");\n"
+    "if ($(imgTable).length > 0) {\n"
+    "   $(imgTable).tableDnD({\n"
+    "       onDragClass: \"trDrag\",\n"
+    "       dragHandle: \"dragHandle\",\n"
+    "       scrollAmount: 40,\n"
+    "       onDragStart: function(ev, table, row) {\n"
+    "           mouse.saveOffset(ev);\n"
+    "           table.tableDnDConfig.dragObjects = [ row ]; // defaults to just the one\n"
+    "       },\n"
+    "       onDrop: function(table, row, dragStartIndex) {\n"
+    "           if ($(row).attr('rowIndex') !== dragStartIndex) {\n"
+    "               // NOTE Even if dragging a contiguous set of rows,\n"
+    "               // still only need to check the one under the cursor.\n"
+    "               if (dragReorder.setOrder) {\n"
+    "                   dragReorder.setOrder(table);\n"
+    "               }\n"
+    "               // save the order of the samples into the input variable named above\n"
+    "               var newVal = \"\";\n"
+    "               var inp = $(\"input[name='%s.%s']\")[0];\n"
+    "               for (i = 0; i < table.rows.length; i++) {\n"
+    "                   newVal += table.rows[i].id.slice(0,-4) + \",\";\n"
+    "               }\n"
+    "               if (newVal.slice(-1) === \",\") {\n"
+    "                   newVal = newVal.slice(0,-1);\n"
+    "               }\n"
+    "               inp.value = newVal;\n"
+    "           }\n"
+    "       }\n"
+    "   });\n"
+    "}\n"
+    "", tdb->track, tdb->track, VCF_PHASED_SAMPLE_ORDER_VAR);
+    }
+}
+
 static void vcfCfgPhasedTrioUi(struct cart *cart, struct trackDb *tdb, struct vcfFile *vcff, char *name,
                                 boolean parentLevel)
 /* Put up the phased trio specific config settings */
 {
+//if (!parentLevel) // don't put up this display at the composite level
+vcfPhasedSampleSortUi(cart, tdb, vcff, name, parentLevel);
 if (hasSampleAliases(tdb))
     {
     printf("<b>Label samples by:</b>");
     char defaultLabel[1024], aliasLabel[1024];
     safef(defaultLabel, sizeof(defaultLabel), "%s.%s", name, VCF_PHASED_DEFAULT_LABEL_VAR);
     safef(aliasLabel, sizeof(aliasLabel), "%s.%s", name, VCF_PHASED_ALIAS_LABEL_VAR);
     boolean isDefaultChecked = cartUsualBooleanClosestToHome(cart, tdb, parentLevel, VCF_PHASED_DEFAULT_LABEL_VAR, FALSE);
     boolean isAliasChecked = cartUsualBooleanClosestToHome(cart, tdb, parentLevel, VCF_PHASED_ALIAS_LABEL_VAR, TRUE);
     cgiMakeCheckBox(defaultLabel, isDefaultChecked);
     printf("VCF file sample names &nbsp;");
     cgiMakeCheckBox(aliasLabel, isAliasChecked);
     printf("Family Labels");
     printf("<br>");
     }
 if (trackDbSetting(tdb,VCF_PHASED_PARENTS_SAMPLE_SETTING))